Skip to content

Commit 1e2a46b

Browse files
ernadoclaude
andcommitted
docs(examples): add media bot example
Demonstrate media handling: /photo sends a photo by URL, incoming photos are echoed back by file_id (no re-upload), and incoming documents are echoed back with metadata resolved via GetFile. Uses inline predicates for photo/document messages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 92f15e8 commit 1e2a46b

2 files changed

Lines changed: 114 additions & 3 deletions

File tree

docs/roadmap.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ Deferred within Phase 5: payment answers
170170
-`pool.Pool` re-pointed at public `Bot` (`pool/`): lazy per-token start,
171171
shared startup for concurrent callers, per-token bbolt storage, idle GC
172172
(`RunGC`), `Kill`/`Close`.
173-
- Examples: `examples/echo` (handler + middleware), `examples/buttons`
174-
(inline keyboards + callback queries), `examples/inline` (inline mode). Media
175-
bot still to add.
173+
- Examples: `examples/echo` (handler + middleware), `examples/buttons`
174+
(inline keyboards + callback queries), `examples/inline` (inline mode),
175+
`examples/media` (send by URL, echo incoming media by file_id, `GetFile`).
176176
- ☑ Allocation tests on hot paths (`bench_test.go`): entity/markup/user
177177
conversion and `file_unique_id`, with `-benchmem`.
178178
- ☐ Conformance test against kept `botdoc` extractor (API-version drift guard)

examples/media/main.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Command media is a bot built on github.com/gotd/botapi that demonstrates
2+
// sending and receiving media:
3+
//
4+
// - /photo sends a photo by URL.
5+
// - sending the bot a photo echoes it back by file_id (no re-upload).
6+
// - sending the bot a document echoes it back and reports its size via
7+
// GetFile.
8+
//
9+
// Run it with an MTProto app identity (https://my.telegram.org) and a BotFather
10+
// token:
11+
//
12+
// APP_ID=12345 APP_HASH=abcdef BOT_TOKEN=123:abc go run ./examples/media
13+
package main
14+
15+
import (
16+
"context"
17+
"fmt"
18+
"os"
19+
"os/signal"
20+
"strconv"
21+
"time"
22+
23+
"go.uber.org/zap"
24+
25+
"github.com/gotd/botapi"
26+
)
27+
28+
// A small public domain image to send for /photo.
29+
const samplePhotoURL = "https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg"
30+
31+
func main() {
32+
log, _ := zap.NewDevelopment()
33+
defer func() { _ = log.Sync() }()
34+
35+
appID, err := strconv.Atoi(os.Getenv("APP_ID"))
36+
if err != nil {
37+
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
38+
}
39+
40+
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
41+
AppID: appID,
42+
AppHash: os.Getenv("APP_HASH"),
43+
Logger: log,
44+
})
45+
if err != nil {
46+
log.Fatal("Create bot", zap.Error(err))
47+
}
48+
49+
bot.Use(botapi.Recover(), botapi.Timeout(time.Minute))
50+
51+
bot.OnCommand("start", func(c *botapi.Context) error {
52+
_, err := c.Reply("Send me a photo or a file and I'll send it back. Or try /photo.")
53+
return err
54+
})
55+
56+
// Send a photo by URL — Telegram fetches it server-side.
57+
bot.OnCommand("photo", func(c *botapi.Context) error {
58+
chat, ok := c.Chat()
59+
if !ok {
60+
return nil
61+
}
62+
_, err := c.Bot.SendPhoto(c, chat, botapi.FileURL(samplePhotoURL), "Here's a cat 🐈")
63+
return err
64+
})
65+
66+
// Echo incoming photos back by file_id (no download/re-upload round trip).
67+
bot.OnMessage(func(c *botapi.Context) error {
68+
photos := c.Message().Photo
69+
largest := photos[len(photos)-1] // last size is the highest resolution
70+
chat, _ := c.Chat()
71+
_, err := c.Bot.SendPhoto(c, chat, botapi.FileID(largest.FileID),
72+
fmt.Sprintf("%d×%d, file_unique_id=%s", largest.Width, largest.Height, largest.FileUniqueID))
73+
return err
74+
}, hasPhoto)
75+
76+
// Echo incoming documents back, reporting metadata resolved from the file_id.
77+
bot.OnMessage(func(c *botapi.Context) error {
78+
doc := c.Message().Document
79+
chat, _ := c.Chat()
80+
81+
file, err := c.Bot.GetFile(c, doc.FileID)
82+
if err != nil {
83+
return err
84+
}
85+
caption := fmt.Sprintf("%s (%d bytes)\nfile_unique_id=%s",
86+
doc.FileName, doc.FileSize, file.FileUniqueID)
87+
88+
_, err = c.Bot.SendDocument(c, chat, botapi.FileID(doc.FileID), caption)
89+
return err
90+
}, hasDocument)
91+
92+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
93+
defer cancel()
94+
95+
log.Info("Starting media bot")
96+
if err := bot.Run(ctx); err != nil {
97+
log.Fatal("Run", zap.Error(err))
98+
}
99+
}
100+
101+
// hasPhoto matches messages that carry a photo.
102+
func hasPhoto(u *botapi.Update) bool {
103+
m := u.EffectiveMessage()
104+
return m != nil && len(m.Photo) > 0
105+
}
106+
107+
// hasDocument matches messages that carry a document.
108+
func hasDocument(u *botapi.Update) bool {
109+
m := u.EffectiveMessage()
110+
return m != nil && m.Document != nil
111+
}

0 commit comments

Comments
 (0)