Skip to content

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

go
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:

go
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:

go
media := tg.NewPhotoMedia(uploadResult, "photo.jpg")
// or for documents:
media := tg.NewDocumentMedia(uploadResult, "document.pdf")

3. Send the media:

go
client.SendMedia(ctx, chatRef, media, "Optional caption")

Downloading Photos

When a photo message arrives, MTGo parses the photo sizes:

go
photo := msg.Photo
size := photo.Sizes[len(photo.Sizes)-1]  // largest size
fileLoc := size.Location

Photos have multiple sizes (thumbnail → full). The last size is typically the highest resolution.

Downloading Documents

Documents are simpler—they have a single file location:

go
doc := msg.Document
fileLoc := doc.Location
filename := doc.Filename

Progress Tracking

Both uploads and downloads support progress callbacks:

go
tg.WithProgress(func(sent, total int64) { ... })          // upload
tg.WithDownloadProgress(func(received, total int64) { ... }) // download

The 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:

go
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 .mp4 files
  • NewAudioMedia — for .mp3, .ogg files
  • NewAnimationMedia — for .gif files

Running

bash
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

Released under the Apache-2.0 License.