Skip to content

Commit

Permalink
docs(examples): add basic examples
Browse files Browse the repository at this point in the history
Update #166
  • Loading branch information
ernado committed Jul 11, 2021
1 parent ca8de5b commit 1c0073d
Show file tree
Hide file tree
Showing 8 changed files with 357 additions and 4 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ This project is fully non-commercial and not affiliated with any commercial orga
* Graceful [request cancellation](https://core.telegram.org/mtproto/service_messages#cancellation-of-an-rpc-query) via context
* WebSocket transport support (works in WASM)

## Example
## Examples

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

Also take a look at
* [gotd/bot](https://github.com/gotd/bot) with updates recovery enabled, used as canary for stability testing
* [gotd/cli](https://github.com/gotd/cli), command line interface for subset of telegram methods.

### Auth

Expand Down
10 changes: 10 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Examples

Most examples use environment variable client builders.
You can do it manually, see `bot-auth-manual` for example.

1. Go to [https://my.telegram.org/apps](https://my.telegram.org/apps) and grab `APP_ID`, `APP_HASH`
2. Set `SESSION_FILE` to something like `~/session.yourbot.json` for persistent auth
3. Run example.

Please don't share `APP_ID` or `APP_HASH`, it can't be easily rotated.
90 changes: 90 additions & 0 deletions examples/auth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Binary termAuth implements authentication example for user using terminal.
package main

import (
"bufio"
"context"
"flag"
"fmt"
"os"
"strings"
"syscall"

"go.uber.org/zap"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/xerrors"

"github.com/gotd/td/examples"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/auth"
"github.com/gotd/td/tg"
)

// noSignUp can be embedded to prevent signing up.
type noSignUp struct{}

func (c noSignUp) SignUp(ctx context.Context) (auth.UserInfo, error) {
return auth.UserInfo{}, xerrors.New("not implemented")
}

func (c noSignUp) AcceptTermsOfService(ctx context.Context, tos tg.HelpTermsOfService) error {
return &auth.SignUpRequired{TermsOfService: tos}
}

// termAuth implements authentication via terminal.
type termAuth struct {
noSignUp

phone string
}

func (a termAuth) Phone(_ context.Context) (string, error) {
return a.phone, nil
}

func (a termAuth) Password(_ context.Context) (string, error) {
fmt.Print("Enter 2FA password: ")
bytePwd, err := terminal.ReadPassword(syscall.Stdin)
if err != nil {
return "", err
}
return strings.TrimSpace(string(bytePwd)), nil
}

func (a termAuth) Code(_ context.Context, _ *tg.AuthSentCode) (string, error) {
fmt.Print("Enter code: ")
code, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(code), nil
}

func main() {
phone := flag.String("phone", "", "phone number to authenticate")
flag.Parse()

examples.Run(func(ctx context.Context, log *zap.Logger) error {
// Setting up authentication flow helper based on terminal auth.
flow := auth.NewFlow(
termAuth{phone: *phone},
auth.SendCodeOptions{},
)

client, err := telegram.ClientFromEnvironment(telegram.Options{
Logger: log,
})
if err != nil {
return err
}
return client.Run(ctx, func(ctx context.Context) error {
if err := client.Auth().IfNecessary(ctx, flow); err != nil {
return err
}

log.Info("Success")

return nil
})
})
}
90 changes: 90 additions & 0 deletions examples/bot-auth-manual/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Binary bot-auth-manual implements example of custom session storage and
// manually setting up client options without environment variables.
package main

import (
"context"
"flag"
"sync"

"go.uber.org/zap"

"github.com/gotd/td/examples"
"github.com/gotd/td/session"
"github.com/gotd/td/telegram"
)

// memorySession implements in-memory session storage.
// Goroutine-safe.
type memorySession struct {
mux sync.RWMutex
data []byte
}

// LoadSession loads session from memory.
func (s *memorySession) LoadSession(context.Context) ([]byte, error) {
if s == nil {
return nil, session.ErrNotFound
}

s.mux.RLock()
defer s.mux.RUnlock()

if len(s.data) == 0 {
return nil, session.ErrNotFound
}

cpy := append([]byte(nil), s.data...)

return cpy, nil
}

// StoreSession stores session to memory.
func (s *memorySession) StoreSession(ctx context.Context, data []byte) error {
s.mux.Lock()
s.data = data
s.mux.Unlock()
return nil
}

func main() {
// Grab those from https://my.telegram.org/apps.
appID := flag.Int("api-id", 0, "app id")
appHash := flag.String("api-hash", "hash", "app hash")
// Get it from bot father.
token := flag.String("token", "", "bot token")
flag.Parse()

// Using custom session storage.
// You can save session to database, e.g. Redis, MongoDB or postgres.
// See memorySession for implementation details.
sessionStorage := &memorySession{}

examples.Run(func(ctx context.Context, log *zap.Logger) error {
client := telegram.NewClient(*appID, *appHash, telegram.Options{
SessionStorage: sessionStorage,
Logger: log,
})

return client.Run(ctx, func(ctx context.Context) error {
// Checking auth status.
status, err := client.Auth().Status(ctx)
if err != nil {
return err
}
// Can be already authenticated if we have valid session in
// session storage.
if !status.Authorized {
// Otherwise, perform bot authentication.
if _, err := client.Auth().Bot(ctx, *token); err != nil {
return err
}
}

// All good, manually authenticated.
log.Info("Done")

return nil
})
})
}
51 changes: 51 additions & 0 deletions examples/bot-echo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Binary bot-echo implements basic example for bot.
package main

import (
"context"

"go.uber.org/zap"

"github.com/gotd/td/examples"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/message"
"github.com/gotd/td/tg"
)

func main() {
// Environment variables:
// BOT_TOKEN: token from BotFather
// APP_ID: app_id of Telegram app.
// APP_HASH: app_hash of Telegram app.
// SESSION_FILE: path to session file
// SESSION_DIR: path to session directory, if SESSION_FILE is not set
examples.Run(func(ctx context.Context, log *zap.Logger) error {
// Dispatcher handles incoming updates.
dispatcher := tg.NewUpdateDispatcher()
opts := telegram.Options{
Logger: log,
UpdateHandler: dispatcher,
}
return telegram.BotFromEnvironment(ctx, opts, func(ctx context.Context, client *telegram.Client) error {
// Raw MTProto API client, allows making raw RPC calls.
api := tg.NewClient(client)

// Helper for sending messages.
sender := message.NewSender(api)

// Setting up handler for incoming message.
dispatcher.OnNewMessage(func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage) error {
m, ok := u.Message.(*tg.Message)
if !ok || m.Out {
// Outgoing message, not interesting.
return nil
}

// Sending reply.
_, err := sender.Reply(entities, u).Text(ctx, m.Message)
return err
})
return nil
}, telegram.RunUntilCanceled)
})
}
82 changes: 82 additions & 0 deletions examples/bot-upload/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Binary bot-upload implements upload example for bot.
package main

import (
"context"
"errors"
"flag"
"fmt"

"go.uber.org/zap"

"github.com/gotd/td/examples"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/message"
"github.com/gotd/td/telegram/message/html"
"github.com/gotd/td/telegram/uploader"
"github.com/gotd/td/tg"
)

func main() {
// Environment variables:
// BOT_TOKEN: token from BotFather
// APP_ID: app_id of Telegram app.
// APP_HASH: app_hash of Telegram app.
// SESSION_FILE: path to session file
// SESSION_DIR: path to session directory, if SESSION_FILE is not set
filePath := flag.String("file", "", "file to upload")
targetDomain := flag.String("target", "", "target to upload, e.g. channel name")
flag.Parse()

examples.Run(func(ctx context.Context, log *zap.Logger) error {
if *filePath == "" || *targetDomain == "" {
return errors.New("no --file or --target provided")
}

// The performUpload will be called after client initialization.
performUpload := func(ctx context.Context, client *telegram.Client) error {
// Raw MTProto API client, allows making raw RPC calls.
api := tg.NewClient(client)

// Helper for uploading. Automatically uses big file upload when needed.
u := uploader.NewUploader(api)

// Helper for sending messages.
sender := message.NewSender(api).WithUploader(u)

// Uploading directly from path. Note that you can do it from
// io.Reader or buffer, see From* methods of uploader.
log.Info("Uploading file")
upload, err := u.FromPath(ctx, *filePath)
if err != nil {
return fmt.Errorf("failed to upload: %w", err)
}

// Now we have uploaded file handle, sending it as styled message.
// First, preparing message.
document := message.UploadedDocument(upload,
html.String(nil, `Upload: <b>From bot</b>`),
)

// You can set MIME type, send file as video or audio by using
// document builder:
document.
MIME("audio/mp3").
Filename("some-audio.mp3").
Audio()

// Resolving target. Can be telephone number or @nickname of user,
// group or channel.
target := sender.Resolve(*targetDomain)

// Sending message with media.
log.Info("Sending file")
if _, err := target.Media(ctx, document); err != nil {
return fmt.Errorf("send: %w", err)
}

return nil
}
return telegram.BotFromEnvironment(ctx, telegram.Options{Logger: log}, nil, performUpload)
})
}
6 changes: 6 additions & 0 deletions examples/examples.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Package examples contains usage examples for gotd features.
package examples

import (
_ "github.com/gotd/td"
)
22 changes: 22 additions & 0 deletions examples/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package examples

import (
"context"

"go.uber.org/zap"
)

// Run runs f callback with context and logger, panics on error.
func Run(f func(ctx context.Context, log *zap.Logger) error) {
log, err := zap.NewDevelopment()
if err != nil {
panic(err)
}
defer func() { _ = log.Sync() }()
// No graceful shutdown.
ctx := context.Background()
if err := f(ctx, log); err != nil {
log.Fatal("Run failed", zap.Error(err))
}
// Done.
}

0 comments on commit 1c0073d

Please sign in to comment.