TeleDrive — dùng Telegram làm ổ lưu trữ
Hai cách biến Telegram thành ổ lưu trữ: một web React nói thẳng MTProto qua GramJS, một backend Go dùng Bot API và MongoDB giữ metadata.
- Go
- React
- MongoDB
- Telegram API
- Vite
TeleDrive là hai codebase song song trả lời cùng một câu hỏi: có dùng được Telegram làm ổ lưu trữ đám mây thật sự không. Tôi viết cả hai vì Telegram có hai API rất khác nhau, và chúng khác nhau ở đúng chỗ quan trọng nhất.
Hai API, hai bài toán khác nhau#
Bot API là HTTP thuần, ai cũng gọi được bằng curl, nhưng trần upload là 50MB mỗi
file và người gửi là một con bot chứ không phải bạn. MTProto là giao thức mà client
Telegram thật sự dùng: file tối đa 2GB, nhưng phải đăng nhập bằng tài khoản người dùng và
tự giữ session. Không thể chọn "cái tốt hơn" — chúng phục vụ hai mô hình tin cậy khác nhau,
nên tôi làm cả hai.
Bản backend là Go + Gin + MongoDB. Người dùng đăng ký bằng email, tự tạo bot qua BotFather,
rồi khai bot_token, chat_id và topic_id của riêng mình. MongoDB dùng chung nhưng bot
thì riêng từng người, nên file của ai nằm trong nhóm chat của người đó — tôi không giữ file
của ai cả.
Bản web là React + Vite + GramJS và không có backend nào hết. Trình duyệt mở thẳng kết nối MTProto tới Telegram.
Bot API: dễ deploy, đổi lại trần 50MB#
Server Go chỉ là ống dẫn: nhận multipart từ client, đóng gói lại rồi đẩy sang
sendDocument. Nếu nhóm bật Topics thì phải kèm message_thread_id, và bot cần quyền
admin mới gửi vào topic được.
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)
/* Nhóm bật Topics: mỗi kho file là một topic, thiếu id này thì file rơi ra ngoài. */
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)
// Thứ duy nhất cần giữ lại từ response là file_id — đó chính là "địa chỉ" của file.
}MongoDB chỉ lưu năm field: tên file, telegram_file_id, chủ sở hữu, kích thước, thời
điểm tạo. Tải về là getFile rồi stream ngược lại cho client. Xác thực dùng JWT access
15 phút, refresh 7 ngày; riêng upload đi qua một ticket JWT sống 5 phút tách khỏi
access token, để WebView trong app mobile gửi form được mà không phải nhét access token
vào multipart.
MTProto: 2GB mỗi file, session nằm trong trình duyệt#
Bản web đăng nhập bằng QR (signInUserWithQrCode), rồi cất StringSession vào IndexedDB.
Session không rời khỏi máy, và tôi không có server để mà lộ. Đổi lại, tôi phải tự lo phần
mà Bot API làm hộ: chia file thành nhiều phần tải song song.
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')
/* Nhiều worker = nhiều phần tải song song. File nhỏ thì 1 worker còn nhanh hơn. */
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 })
}Ở đây không có database nào cả: danh sách file chính là lịch sử chat, đọc bằng
iterMessages với InputMessagesFilterDocument. Thư mục, tag và ghi chú chỉ sống trong
IndexedDB, nên đổi máy là mất cách sắp xếp — file vẫn còn nguyên, chỉ có cấu trúc là không
theo được.
Rate limit là vấn đề thật, không phải lý thuyết#
Lưới thumbnail khiến hàng trăm request bắn ra cùng lúc, và Telegram trả về
FLOOD_WAIT_x. Tôi đưa mọi API call qua một queue giới hạn 15 request/giây, có ưu tiên
cho thứ đang hiển thị trên màn hình. Khi bắt được FLOOD_WAIT_x, queue tự dừng đúng
x giây rồi chạy tiếp thay vì để từng request lẻ tự chết.
Kết quả#
- Hai hướng tiếp cận chạy được, và ranh giới giữa chúng rất rõ: cần file lớn thì MTProto, cần nhiều người dùng chung một hệ thống thì Bot API
- Bản Bot API không vượt được trần 50MB mỗi file — đó là giới hạn của API, không phải của code
- Bản MTProto phải nhúng
api_id/api_hashvào bundle trình duyệt; đó là đánh đổi cố hữu của kiến trúc frontend-only - Job dọn dẹp ở backend chỉ xoá metadata trong MongoDB sau thời gian giữ, file trong Telegram vẫn còn — muốn xoá thật phải xoá tin nhắn
- Cả hai đều phụ thuộc vào điều khoản của Telegram: đây là bài tập kỹ thuật, không phải thứ tôi dám giao dữ liệu quan trọng