TeleDrive, using Telegram as a cloud drive
Two ways to turn Telegram into a drive: a React web app speaking MTProto through GramJS, and a Go backend on the Bot API with MongoDB holding the metadata.
- Go
- React
- MongoDB
- Telegram API
- Vite
TeleDrive is two parallel codebases answering the same question: can Telegram actually work as a cloud drive. I wrote both because Telegram exposes two very different APIs, and they differ in exactly the place that matters most here.
Two APIs, two different problems#
The Bot API is plain HTTP — anyone can drive it with curl — but it caps uploads at
50MB per file, and the sender is a bot rather than you. MTProto is the protocol real
Telegram clients speak: 2GB per file, but it needs a user account login and you have to
hold the session yourself. There is no "better one" to pick; they serve two different trust
models, so I built both.
The backend is Go + Gin + MongoDB. A user registers by email, creates their own bot through
BotFather, then supplies their own bot_token, chat_id and topic_id. MongoDB is shared
but the bots are not, so everyone's files land in their own group chat — I never hold
anyone's data.
The web app is React + Vite + GramJS with no backend at all. The browser opens an MTProto connection straight to Telegram.
Bot API: easy to ship, 50MB ceiling in return#
The Go server is a pipe: take the multipart upload, repackage it, push it to
sendDocument. If the group has Topics enabled you must pass message_thread_id, and the
bot needs admin rights to post into a topic at all.
func uploadSingleFileToTelegram(fh *multipart.FileHeader, user models.User) (string, int64, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("chat_id", user.TelegramConfig.ChatID)
/* Forum groups: each drive is one topic — without this id the file lands outside it. */
if user.TelegramConfig.TopicID != "" {
writer.WriteField("message_thread_id", user.TelegramConfig.TopicID)
}
part, _ := writer.CreateFormFile("document", fh.Filename)
size, _ := io.Copy(part, fileReader)
writer.Close()
endpoint := fmt.Sprintf("https://api.telegram.org/bot%s/sendDocument", user.TelegramConfig.BotToken)
// The only thing worth keeping from the response is file_id — that is the file's address.
}MongoDB stores five fields: filename, telegram_file_id, owner, size, created-at.
Downloads go through getFile and get streamed back. Auth is a 15-minute access token and
a 7-day refresh token, except uploads, which use a separate 5-minute JWT ticket so a
mobile WebView can post a form without stuffing an access token into the multipart body.
MTProto: 2GB per file, session lives in the browser#
The web app logs in by QR (signInUserWithQrCode) and keeps the StringSession in
IndexedDB. The session never leaves the machine, and there is no server of mine to leak it.
In exchange I have to handle what the Bot API did for free: splitting a file into parallel
parts.
export const MAX_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024
async function uploadFile(file: File) {
if (file.size > MAX_UPLOAD_BYTES) throw new Error('FILE_TOO_LARGE')
/* More workers = more parts in flight. For small files one worker is actually faster. */
const workers =
file.size < 5 * 1024 * 1024 ? 1 :
file.size < 50 * 1024 * 1024 ? 4 :
file.size < 200 * 1024 * 1024 ? 8 : 16
await client.sendFile(peer, { file, forceDocument: true, workers })
}There is no database here: the file list is the chat history, read through
iterMessages with InputMessagesFilterDocument. Folders, tags and notes live only in
IndexedDB, so switching machines loses the organisation — the files are all still there,
the structure just doesn't follow.
Rate limits are a real problem, not a theoretical one#
A grid of thumbnails fires hundreds of requests at once and Telegram answers with
FLOOD_WAIT_x. Every call goes through one queue capped at 15 requests per second, with
priority for whatever is currently on screen. When a FLOOD_WAIT_x comes back the queue
pauses for exactly those seconds and resumes, instead of letting individual requests die
one by one.
Outcome#
- Both approaches work, and the line between them is sharp: large files call for MTProto, many users on one system call for the Bot API
- The Bot API build cannot get past 50MB per file — that is the API's limit, not something the code can work around
- The MTProto build has to ship
api_id/api_hashinside the browser bundle, which is the inherent cost of a frontend-only architecture - The backend cleanup job only deletes metadata from MongoDB after the retention window; the files stay in Telegram, so real deletion means deleting the messages
- Both depend on Telegram's terms of service: this is an engineering exercise, not somewhere I would put data I actually care about