MTGo File Upload & Download
This example shows a bot that receives photos and documents, downloads them, and can re-upload files on command. It demonstrates the complete file transfer lifecycle with progress callbacks.
Full Code
package main
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"strings"
tg "github.com/mtgo-labs/mtgo/telegram"
"github.com/mtgo-labs/mtgo/telegram/types"
)
const downloadDir = "./downloads"
func main() {
apiID := mustAtoi(mustEnv("API_ID"))
apiHash := mustEnv("API_HASH")
botToken := mustEnv("BOT_TOKEN")
os.MkdirAll(downloadDir, 0755)
client, err := tg.NewClient(apiID, apiHash, &tg.Config{
BotToken: botToken,
SessionName: "file_bot",
SavePeers: true,
})
if err != nil {
log.Fatalf("new client: %v", err)
}
// /upload <path> - upload a file from the server
client.OnMessage(func(c *tg.Client, msg *types.Message) {
path := strings.TrimSpace(msg.Args())
if path == "" {
msg.Reply("Usage: /upload <file_path>")
return
}
// Check file exists
info, err := os.Stat(path)
if err != nil {
msg.Reply(fmt.Sprintf("File not found: %s", path))
return
}
// Notify user
msg.Reply(fmt.Sprintf("Uploading %s (%.1f KB)...", filepath.Base(path), float64(info.Size())/1024))
// Upload with progress
uploadResult, err := client.UploadFromFile(context.Background(), path,
tg.WithProgress(func(sent, total int64) {
pct := float64(sent) / float64(total) * 100
if int(pct)%25 == 0 {
fmt.Printf(" Upload: %.0f%%\n", pct)
}
}),
)
if err != nil {
msg.Reply(fmt.Sprintf("Upload failed: %v", err))
return
}
// Determine media type from extension
ext := strings.ToLower(filepath.Ext(path))
var media tg.InputMedia
switch ext {
case ".jpg", ".jpeg", ".png", ".webp":
media = tg.NewPhotoMedia(uploadResult, filepath.Base(path))
default:
media = tg.NewDocumentMedia(uploadResult, filepath.Base(path))
}
// Send the uploaded file
_, err = client.SendMedia(context.Background(),
tg.ChatRef(msg.ChatID()),
media,
fmt.Sprintf("Uploaded: %s", filepath.Base(path)),
)
if err != nil {
msg.Reply(fmt.Sprintf("Send failed: %v", err))
}
}, tg.Command("upload"))
// Handle incoming photos - download them
client.OnMessage(func(c *tg.Client, msg *types.Message) {
if msg.Photo == nil {
return
}
photo := msg.Photo
// Get the largest size
size := photo.Sizes[len(photo.Sizes)-1]
fileLoc := size.Location
filename := fmt.Sprintf("photo_%d.jpg", msg.ID)
filepath := filepath.Join(downloadDir, filename)
err := client.DownloadToFile(context.Background(), fileLoc, filepath,
tg.WithDownloadProgress(func(received, total int64) {
pct := float64(received) / float64(total) * 100
if int(pct)%25 == 0 {
fmt.Printf(" Download %s: %.0f%%\n", filename, pct)
}
}),
)
if err != nil {
log.Printf("download photo error: %v", err)
return
}
log.Printf("Downloaded photo: %s", filepath)
msg.Reply(fmt.Sprintf("Photo saved to %s", filepath))
}, tg.Private)
// Handle incoming documents - download them
client.OnMessage(func(c *tg.Client, msg *types.Message) {
if msg.Document == nil {
return
}
doc := msg.Document
fileLoc := doc.Location
filename := doc.Filename
if filename == "" {
filename = fmt.Sprintf("doc_%d", msg.ID)
}
filepath := filepath.Join(downloadDir, filename)
err := client.DownloadToFile(context.Background(), fileLoc, filepath,
tg.WithDownloadProgress(func(received, total int64) {
pct := float64(received) / float64(total) * 100
if int(pct)%25 == 0 {
fmt.Printf(" Download %s: %.0f%%\n", filename, pct)
}
}),
)
if err != nil {
log.Printf("download document error: %v", err)
return
}
log.Printf("Downloaded document: %s", filepath)
msg.Reply(fmt.Sprintf("Document saved to %s (%d bytes)", filepath, doc.Size))
}, tg.Private)
if err := client.Connect(0); err != nil {
log.Fatalf("connect: %v", err)
}
defer client.Stop()
bot, err := client.GetMe(context.Background())
if err != nil {
log.Fatalf("get me: %v", err)
}
fmt.Printf("Bot @%s is running\n", bot.Username)
fmt.Printf("Downloads will be saved to %s\n", downloadDir)
client.Idle()
}
func mustEnv(key string) string {
v := os.Getenv(key)
if v == "" {
log.Fatalf("environment variable %s is required", key)
}
return v
}
func mustAtoi(s string) int {
var n int
if _, err := fmt.Sscanf(s, "%d", &n); err != nil {
log.Fatalf("invalid integer %q: %v", s, err)
}
return n
}How It Works
Uploading Files
The /upload command demonstrates the full upload flow:
1. Upload the file:
uploadResult, err := client.UploadFromFile(ctx, "./photo.jpg",
tg.WithProgress(func(sent, total int64) {
fmt.Printf("Upload: %.1f%%\n", float64(sent)/float64(total)*100)
}),
)UploadFromFile handles chunking, parallel uploads, and retries internally.
2. Create a media object:
media := tg.NewPhotoMedia(uploadResult, "photo.jpg")
// or for documents:
media := tg.NewDocumentMedia(uploadResult, "document.pdf")3. Send the media:
client.SendMedia(ctx, chatRef, media, "Optional caption")Downloading Photos
When a photo message arrives, MTGo parses the photo sizes:
photo := msg.Photo
size := photo.Sizes[len(photo.Sizes)-1] // largest size
fileLoc := size.LocationPhotos have multiple sizes (thumbnail → full). The last size is typically the highest resolution.
Downloading Documents
Documents are simpler—they have a single file location:
doc := msg.Document
fileLoc := doc.Location
filename := doc.FilenameProgress Tracking
Both uploads and downloads support progress callbacks:
tg.WithProgress(func(sent, total int64) { ... }) // upload
tg.WithDownloadProgress(func(received, total int64) { ... }) // downloadThe callback receives the bytes transferred so far and the total size. Use these to build progress bars, log statistics, or update the user.
File Type Detection
The example uses file extension to determine media type:
switch ext {
case ".jpg", ".jpeg", ".png", ".webp":
media = tg.NewPhotoMedia(uploadResult, name)
default:
media = tg.NewDocumentMedia(uploadResult, name)
}MTGo also supports specialized media types:
NewVideoMedia— for.mp4filesNewAudioMedia— for.mp3,.oggfilesNewAnimationMedia— for.giffiles
Running
export API_ID=12345
export API_HASH=your_api_hash
export BOT_TOKEN=your_bot_token
go run main.go- Send a photo or document to the bot in a private chat — it downloads to
./downloads/ - Send
/upload /path/to/file— it uploads and sends the file back
