Skip to content

Commit 0761031

Browse files
ernadoclaude
andcommitted
feat(storage): add storage.Open/Close and persist session in all examples
Add a one-call storage.Open(path) that opens (creating if needed) a bbolt-backed Storage and owns the database, with a matching Close. This makes enabling persistence a two-liner instead of opening bbolt by hand. Every example (echo, buttons, inline, media, rich, background, advanced) now opens a per-example <name>-session.bbolt and passes it as Options.Storage, so bots resume across restarts without re-authorizing. Update the guide, README and CHANGELOG, and gitignore *.bbolt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8dfd0f9 commit 0761031

12 files changed

Lines changed: 111 additions & 7 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ gen
2020
# Bot db files
2121
state/*.bbolt
2222

23+
# Session/peers/state stores written by the examples.
24+
*.bbolt
25+
2326
# Stray binaries from `go build ./examples/<name>` run at the repo root.
2427
/echo
2528
/buttons

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ messages follow [Conventional Commits](https://www.conventionalcommits.org/).
1717

1818
### Added
1919

20+
- **`storage.Open`/`Close`** — a one-call opener that creates (or reuses) a
21+
bbolt-backed `Storage` and owns the database, so persisting a session is
22+
`storage.Open("bot.bbolt")` plus a deferred `Close`. Every example now
23+
persists its session by default.
2024
- **Rich messages** (Bot API 10.1) — `SendRichMessage`/`SendRichHTML`/
2125
`SendRichMarkdown` send structured page-block content built with
2226
`github.com/gotd/td/telegram/message/rich`. The `examples/rich` bot showcases

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,10 @@ errors, pooling) and [`examples/`](./examples) for runnable bots
7979
(`echo`, `buttons`, `inline`, `media`, `rich`, `background`, `advanced`).
8080

8181
`Options.Storage` is optional — leave it nil to keep session, peers and update
82-
state in memory (nothing survives a restart). `storage.BBoltStorage` persists
83-
all of it to a single bbolt file.
82+
state in memory (nothing survives a restart). `storage.Open("bot.bbolt")`
83+
persists all of it to a single bbolt file; close it on shutdown. Every example
84+
under [`examples/`](./examples) persists its session this way by default, so
85+
they reconnect without re-authorizing.
8486

8587
## Package layout
8688

docs/guide.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,23 @@ botapi.Options{
410410
## Persistence
411411

412412
By default everything is in memory (nothing survives a restart). Provide a
413-
`Storage` to persist the session, peer access hashes and update state:
413+
`Storage` to persist the session, peer access hashes and update state.
414+
`storage.Open` is the one-call form — it opens (creating it if needed) a
415+
bbolt file and owns it, so close it on shutdown:
414416

415417
```go
416-
db, _ := bbolt.Open("bot.bbolt", 0o666, nil)
417-
opts := botapi.Options{AppID: appID, AppHash: appHash, Storage: storage.NewBBoltStorage(db)}
418+
store, err := storage.Open("bot.bbolt")
419+
if err != nil {
420+
return err
421+
}
422+
defer store.Close()
423+
opts := botapi.Options{AppID: appID, AppHash: appHash, Storage: store}
418424
```
419425

426+
To share a `*bbolt.DB` you already manage, wrap it with
427+
`storage.NewBBoltStorage(db)` instead and close the db yourself. Every bot under
428+
`examples/` persists its session this way by default.
429+
420430
## Running many bots
421431

422432
`pool.Pool` lazily starts and multiplexes bots by token over one process — the

examples/advanced/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/gotd/td/telegram/message/rich"
2727

2828
"github.com/gotd/botapi"
29+
"github.com/gotd/botapi/storage"
2930
)
3031

3132
const samplePhotoURL = "https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg"
@@ -39,10 +40,18 @@ func main() {
3940
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
4041
}
4142

43+
// Persist session, peers and update state so the bot resumes across restarts.
44+
store, err := storage.Open("advanced-session.bbolt")
45+
if err != nil {
46+
log.Fatal("Open storage", zap.Error(err))
47+
}
48+
defer func() { _ = store.Close() }()
49+
4250
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
4351
AppID: appID,
4452
AppHash: os.Getenv("APP_HASH"),
4553
Logger: logzap.New(log),
54+
Storage: store,
4655
FloodWait: true, // transparently wait out flood limits
4756
})
4857
if err != nil {

examples/background/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"go.uber.org/zap"
3636

3737
"github.com/gotd/botapi"
38+
"github.com/gotd/botapi/storage"
3839
)
3940

4041
func main() {
@@ -46,10 +47,18 @@ func main() {
4647
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
4748
}
4849

50+
// Persist session, peers and update state so the bot resumes across restarts.
51+
sess, err := storage.Open("background-session.bbolt")
52+
if err != nil {
53+
log.Fatal("Open storage", zap.Error(err))
54+
}
55+
defer func() { _ = sess.Close() }()
56+
4957
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
5058
AppID: appID,
5159
AppHash: os.Getenv("APP_HASH"),
5260
Logger: logzap.New(log),
61+
Storage: sess,
5362
})
5463
if err != nil {
5564
log.Fatal("Create bot", zap.Error(err))

examples/buttons/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"go.uber.org/zap"
2020

2121
"github.com/gotd/botapi"
22+
"github.com/gotd/botapi/storage"
2223
)
2324

2425
func menu() *botapi.InlineKeyboardMarkup {
@@ -44,10 +45,18 @@ func main() {
4445
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
4546
}
4647

48+
// Persist session, peers and update state so the bot resumes across restarts.
49+
store, err := storage.Open("buttons-session.bbolt")
50+
if err != nil {
51+
log.Fatal("Open storage", zap.Error(err))
52+
}
53+
defer func() { _ = store.Close() }()
54+
4755
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
4856
AppID: appID,
4957
AppHash: os.Getenv("APP_HASH"),
5058
Logger: logzap.New(log),
59+
Storage: store,
5160
})
5261
if err != nil {
5362
log.Fatal("Create bot", zap.Error(err))

examples/echo/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"go.uber.org/zap"
1919

2020
"github.com/gotd/botapi"
21+
"github.com/gotd/botapi/storage"
2122
)
2223

2324
func main() {
@@ -29,10 +30,18 @@ func main() {
2930
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
3031
}
3132

33+
// Persist session, peers and update state so the bot resumes across restarts.
34+
store, err := storage.Open("echo-session.bbolt")
35+
if err != nil {
36+
log.Fatal("Open storage", zap.Error(err))
37+
}
38+
defer func() { _ = store.Close() }()
39+
3240
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
3341
AppID: appID,
3442
AppHash: os.Getenv("APP_HASH"),
3543
Logger: logzap.New(log),
44+
Storage: store,
3645
})
3746
if err != nil {
3847
log.Fatal("Create bot", zap.Error(err))

examples/inline/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"go.uber.org/zap"
2020

2121
"github.com/gotd/botapi"
22+
"github.com/gotd/botapi/storage"
2223
)
2324

2425
func main() {
@@ -30,10 +31,18 @@ func main() {
3031
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
3132
}
3233

34+
// Persist session, peers and update state so the bot resumes across restarts.
35+
store, err := storage.Open("inline-session.bbolt")
36+
if err != nil {
37+
log.Fatal("Open storage", zap.Error(err))
38+
}
39+
defer func() { _ = store.Close() }()
40+
3341
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
3442
AppID: appID,
3543
AppHash: os.Getenv("APP_HASH"),
3644
Logger: logzap.New(log),
45+
Storage: store,
3746
})
3847
if err != nil {
3948
log.Fatal("Create bot", zap.Error(err))

examples/media/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"go.uber.org/zap"
2525

2626
"github.com/gotd/botapi"
27+
"github.com/gotd/botapi/storage"
2728
)
2829

2930
// A small public domain image to send for /photo.
@@ -38,10 +39,18 @@ func main() {
3839
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
3940
}
4041

42+
// Persist session, peers and update state so the bot resumes across restarts.
43+
store, err := storage.Open("media-session.bbolt")
44+
if err != nil {
45+
log.Fatal("Open storage", zap.Error(err))
46+
}
47+
defer func() { _ = store.Close() }()
48+
4149
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
4250
AppID: appID,
4351
AppHash: os.Getenv("APP_HASH"),
4452
Logger: logzap.New(log),
53+
Storage: store,
4554
})
4655
if err != nil {
4756
log.Fatal("Create bot", zap.Error(err))

0 commit comments

Comments
 (0)