Skip to content

Commit 1c0073d

Browse files
committed
docs(examples): add basic examples
Update #166
1 parent ca8de5b commit 1c0073d

File tree

8 files changed

+357
-4
lines changed

8 files changed

+357
-4
lines changed

README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ This project is fully non-commercial and not affiliated with any commercial orga
5151
* Graceful [request cancellation](https://core.telegram.org/mtproto/service_messages#cancellation-of-an-rpc-query) via context
5252
* WebSocket transport support (works in WASM)
5353

54-
## Example
54+
## Examples
5555

56-
You can see `cmd/gotdecho` for simple echo bot example or [gotd/bot](https://github.com/gotd/bot) that can
57-
recover from restarts and fetch missed updates (and also is used as canary for stability testing).
58-
Also take a look at [gotd/cli](https://github.com/gotd/cli), command line interface for subset of telegram methods.
56+
See `examples` directory.
57+
58+
Also take a look at
59+
* [gotd/bot](https://github.com/gotd/bot) with updates recovery enabled, used as canary for stability testing
60+
* [gotd/cli](https://github.com/gotd/cli), command line interface for subset of telegram methods.
5961

6062
### Auth
6163

examples/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Examples
2+
3+
Most examples use environment variable client builders.
4+
You can do it manually, see `bot-auth-manual` for example.
5+
6+
1. Go to [https://my.telegram.org/apps](https://my.telegram.org/apps) and grab `APP_ID`, `APP_HASH`
7+
2. Set `SESSION_FILE` to something like `~/session.yourbot.json` for persistent auth
8+
3. Run example.
9+
10+
Please don't share `APP_ID` or `APP_HASH`, it can't be easily rotated.

examples/auth/main.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Binary termAuth implements authentication example for user using terminal.
2+
package main
3+
4+
import (
5+
"bufio"
6+
"context"
7+
"flag"
8+
"fmt"
9+
"os"
10+
"strings"
11+
"syscall"
12+
13+
"go.uber.org/zap"
14+
"golang.org/x/crypto/ssh/terminal"
15+
"golang.org/x/xerrors"
16+
17+
"github.com/gotd/td/examples"
18+
"github.com/gotd/td/telegram"
19+
"github.com/gotd/td/telegram/auth"
20+
"github.com/gotd/td/tg"
21+
)
22+
23+
// noSignUp can be embedded to prevent signing up.
24+
type noSignUp struct{}
25+
26+
func (c noSignUp) SignUp(ctx context.Context) (auth.UserInfo, error) {
27+
return auth.UserInfo{}, xerrors.New("not implemented")
28+
}
29+
30+
func (c noSignUp) AcceptTermsOfService(ctx context.Context, tos tg.HelpTermsOfService) error {
31+
return &auth.SignUpRequired{TermsOfService: tos}
32+
}
33+
34+
// termAuth implements authentication via terminal.
35+
type termAuth struct {
36+
noSignUp
37+
38+
phone string
39+
}
40+
41+
func (a termAuth) Phone(_ context.Context) (string, error) {
42+
return a.phone, nil
43+
}
44+
45+
func (a termAuth) Password(_ context.Context) (string, error) {
46+
fmt.Print("Enter 2FA password: ")
47+
bytePwd, err := terminal.ReadPassword(syscall.Stdin)
48+
if err != nil {
49+
return "", err
50+
}
51+
return strings.TrimSpace(string(bytePwd)), nil
52+
}
53+
54+
func (a termAuth) Code(_ context.Context, _ *tg.AuthSentCode) (string, error) {
55+
fmt.Print("Enter code: ")
56+
code, err := bufio.NewReader(os.Stdin).ReadString('\n')
57+
if err != nil {
58+
return "", err
59+
}
60+
return strings.TrimSpace(code), nil
61+
}
62+
63+
func main() {
64+
phone := flag.String("phone", "", "phone number to authenticate")
65+
flag.Parse()
66+
67+
examples.Run(func(ctx context.Context, log *zap.Logger) error {
68+
// Setting up authentication flow helper based on terminal auth.
69+
flow := auth.NewFlow(
70+
termAuth{phone: *phone},
71+
auth.SendCodeOptions{},
72+
)
73+
74+
client, err := telegram.ClientFromEnvironment(telegram.Options{
75+
Logger: log,
76+
})
77+
if err != nil {
78+
return err
79+
}
80+
return client.Run(ctx, func(ctx context.Context) error {
81+
if err := client.Auth().IfNecessary(ctx, flow); err != nil {
82+
return err
83+
}
84+
85+
log.Info("Success")
86+
87+
return nil
88+
})
89+
})
90+
}

examples/bot-auth-manual/main.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Binary bot-auth-manual implements example of custom session storage and
2+
// manually setting up client options without environment variables.
3+
package main
4+
5+
import (
6+
"context"
7+
"flag"
8+
"sync"
9+
10+
"go.uber.org/zap"
11+
12+
"github.com/gotd/td/examples"
13+
"github.com/gotd/td/session"
14+
"github.com/gotd/td/telegram"
15+
)
16+
17+
// memorySession implements in-memory session storage.
18+
// Goroutine-safe.
19+
type memorySession struct {
20+
mux sync.RWMutex
21+
data []byte
22+
}
23+
24+
// LoadSession loads session from memory.
25+
func (s *memorySession) LoadSession(context.Context) ([]byte, error) {
26+
if s == nil {
27+
return nil, session.ErrNotFound
28+
}
29+
30+
s.mux.RLock()
31+
defer s.mux.RUnlock()
32+
33+
if len(s.data) == 0 {
34+
return nil, session.ErrNotFound
35+
}
36+
37+
cpy := append([]byte(nil), s.data...)
38+
39+
return cpy, nil
40+
}
41+
42+
// StoreSession stores session to memory.
43+
func (s *memorySession) StoreSession(ctx context.Context, data []byte) error {
44+
s.mux.Lock()
45+
s.data = data
46+
s.mux.Unlock()
47+
return nil
48+
}
49+
50+
func main() {
51+
// Grab those from https://my.telegram.org/apps.
52+
appID := flag.Int("api-id", 0, "app id")
53+
appHash := flag.String("api-hash", "hash", "app hash")
54+
// Get it from bot father.
55+
token := flag.String("token", "", "bot token")
56+
flag.Parse()
57+
58+
// Using custom session storage.
59+
// You can save session to database, e.g. Redis, MongoDB or postgres.
60+
// See memorySession for implementation details.
61+
sessionStorage := &memorySession{}
62+
63+
examples.Run(func(ctx context.Context, log *zap.Logger) error {
64+
client := telegram.NewClient(*appID, *appHash, telegram.Options{
65+
SessionStorage: sessionStorage,
66+
Logger: log,
67+
})
68+
69+
return client.Run(ctx, func(ctx context.Context) error {
70+
// Checking auth status.
71+
status, err := client.Auth().Status(ctx)
72+
if err != nil {
73+
return err
74+
}
75+
// Can be already authenticated if we have valid session in
76+
// session storage.
77+
if !status.Authorized {
78+
// Otherwise, perform bot authentication.
79+
if _, err := client.Auth().Bot(ctx, *token); err != nil {
80+
return err
81+
}
82+
}
83+
84+
// All good, manually authenticated.
85+
log.Info("Done")
86+
87+
return nil
88+
})
89+
})
90+
}

examples/bot-echo/main.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Binary bot-echo implements basic example for bot.
2+
package main
3+
4+
import (
5+
"context"
6+
7+
"go.uber.org/zap"
8+
9+
"github.com/gotd/td/examples"
10+
"github.com/gotd/td/telegram"
11+
"github.com/gotd/td/telegram/message"
12+
"github.com/gotd/td/tg"
13+
)
14+
15+
func main() {
16+
// Environment variables:
17+
// BOT_TOKEN: token from BotFather
18+
// APP_ID: app_id of Telegram app.
19+
// APP_HASH: app_hash of Telegram app.
20+
// SESSION_FILE: path to session file
21+
// SESSION_DIR: path to session directory, if SESSION_FILE is not set
22+
examples.Run(func(ctx context.Context, log *zap.Logger) error {
23+
// Dispatcher handles incoming updates.
24+
dispatcher := tg.NewUpdateDispatcher()
25+
opts := telegram.Options{
26+
Logger: log,
27+
UpdateHandler: dispatcher,
28+
}
29+
return telegram.BotFromEnvironment(ctx, opts, func(ctx context.Context, client *telegram.Client) error {
30+
// Raw MTProto API client, allows making raw RPC calls.
31+
api := tg.NewClient(client)
32+
33+
// Helper for sending messages.
34+
sender := message.NewSender(api)
35+
36+
// Setting up handler for incoming message.
37+
dispatcher.OnNewMessage(func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage) error {
38+
m, ok := u.Message.(*tg.Message)
39+
if !ok || m.Out {
40+
// Outgoing message, not interesting.
41+
return nil
42+
}
43+
44+
// Sending reply.
45+
_, err := sender.Reply(entities, u).Text(ctx, m.Message)
46+
return err
47+
})
48+
return nil
49+
}, telegram.RunUntilCanceled)
50+
})
51+
}

examples/bot-upload/main.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Binary bot-upload implements upload example for bot.
2+
package main
3+
4+
import (
5+
"context"
6+
"errors"
7+
"flag"
8+
"fmt"
9+
10+
"go.uber.org/zap"
11+
12+
"github.com/gotd/td/examples"
13+
"github.com/gotd/td/telegram"
14+
"github.com/gotd/td/telegram/message"
15+
"github.com/gotd/td/telegram/message/html"
16+
"github.com/gotd/td/telegram/uploader"
17+
"github.com/gotd/td/tg"
18+
)
19+
20+
func main() {
21+
// Environment variables:
22+
// BOT_TOKEN: token from BotFather
23+
// APP_ID: app_id of Telegram app.
24+
// APP_HASH: app_hash of Telegram app.
25+
// SESSION_FILE: path to session file
26+
// SESSION_DIR: path to session directory, if SESSION_FILE is not set
27+
filePath := flag.String("file", "", "file to upload")
28+
targetDomain := flag.String("target", "", "target to upload, e.g. channel name")
29+
flag.Parse()
30+
31+
examples.Run(func(ctx context.Context, log *zap.Logger) error {
32+
if *filePath == "" || *targetDomain == "" {
33+
return errors.New("no --file or --target provided")
34+
}
35+
36+
// The performUpload will be called after client initialization.
37+
performUpload := func(ctx context.Context, client *telegram.Client) error {
38+
// Raw MTProto API client, allows making raw RPC calls.
39+
api := tg.NewClient(client)
40+
41+
// Helper for uploading. Automatically uses big file upload when needed.
42+
u := uploader.NewUploader(api)
43+
44+
// Helper for sending messages.
45+
sender := message.NewSender(api).WithUploader(u)
46+
47+
// Uploading directly from path. Note that you can do it from
48+
// io.Reader or buffer, see From* methods of uploader.
49+
log.Info("Uploading file")
50+
upload, err := u.FromPath(ctx, *filePath)
51+
if err != nil {
52+
return fmt.Errorf("failed to upload: %w", err)
53+
}
54+
55+
// Now we have uploaded file handle, sending it as styled message.
56+
// First, preparing message.
57+
document := message.UploadedDocument(upload,
58+
html.String(nil, `Upload: <b>From bot</b>`),
59+
)
60+
61+
// You can set MIME type, send file as video or audio by using
62+
// document builder:
63+
document.
64+
MIME("audio/mp3").
65+
Filename("some-audio.mp3").
66+
Audio()
67+
68+
// Resolving target. Can be telephone number or @nickname of user,
69+
// group or channel.
70+
target := sender.Resolve(*targetDomain)
71+
72+
// Sending message with media.
73+
log.Info("Sending file")
74+
if _, err := target.Media(ctx, document); err != nil {
75+
return fmt.Errorf("send: %w", err)
76+
}
77+
78+
return nil
79+
}
80+
return telegram.BotFromEnvironment(ctx, telegram.Options{Logger: log}, nil, performUpload)
81+
})
82+
}

examples/examples.go

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Package examples contains usage examples for gotd features.
2+
package examples
3+
4+
import (
5+
_ "github.com/gotd/td"
6+
)

examples/run.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package examples
2+
3+
import (
4+
"context"
5+
6+
"go.uber.org/zap"
7+
)
8+
9+
// Run runs f callback with context and logger, panics on error.
10+
func Run(f func(ctx context.Context, log *zap.Logger) error) {
11+
log, err := zap.NewDevelopment()
12+
if err != nil {
13+
panic(err)
14+
}
15+
defer func() { _ = log.Sync() }()
16+
// No graceful shutdown.
17+
ctx := context.Background()
18+
if err := f(ctx, log); err != nil {
19+
log.Fatal("Run failed", zap.Error(err))
20+
}
21+
// Done.
22+
}

0 commit comments

Comments
 (0)