package telegram
import (
"context"
"io"
"os"
"path/filepath"
"strconv"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/go-faster/errors"
"go.uber.org/zap"
"golang.org/x/net/proxy"
"github.com/gotd/td/clock"
"github.com/gotd/td/internal/crypto"
"github.com/gotd/td/session"
"github.com/gotd/td/telegram/auth"
"github.com/gotd/td/telegram/dcs"
"github.com/gotd/td/tgerr"
)
func sessionDir () (string , error ) {
dir , ok := os .LookupEnv ("SESSION_DIR" )
if ok {
return filepath .Abs (dir )
}
dir , err := os .UserHomeDir ()
if err != nil {
dir = "."
}
return filepath .Abs (filepath .Join (dir , ".td" ))
}
func OptionsFromEnvironment (opts Options ) (Options , error ) {
if opts .SessionStorage == nil {
sessionFile , ok := os .LookupEnv ("SESSION_FILE" )
if !ok {
dir , err := sessionDir ()
if err != nil {
return Options {}, errors .Wrap (err , "SESSION_DIR not set or invalid" )
}
sessionFile = filepath .Join (dir , "session.json" )
}
dir , _ := filepath .Split (sessionFile )
if err := os .MkdirAll (dir , 0700 ); err != nil {
return Options {}, errors .Wrap (err , "session dir creation" )
}
opts .SessionStorage = &session .FileStorage {
Path : sessionFile ,
}
}
if opts .Resolver == nil {
opts .Resolver = dcs .Plain (dcs .PlainOptions {
Dial : proxy .Dial ,
})
}
return opts , nil
}
func ClientFromEnvironment (opts Options ) (*Client , error ) {
appID , err := strconv .Atoi (os .Getenv ("APP_ID" ))
if err != nil {
return nil , errors .Wrap (err , "APP_ID not set or invalid" )
}
appHash := os .Getenv ("APP_HASH" )
if appHash == "" {
return nil , errors .New ("no APP_HASH provided" )
}
opts , err = OptionsFromEnvironment (opts )
if err != nil {
return nil , err
}
return NewClient (appID , appHash , opts ), nil
}
func retry (ctx context .Context , logger *zap .Logger , cb func (ctx context .Context ) error ) error {
b := backoff .WithContext (backoff .NewExponentialBackOff (), ctx )
retryableErrors := []string {
"NEED_MEMBER_INVALID" ,
"AUTH_KEY_UNREGISTERED" ,
"API_ID_PUBLISHED_FLOOD" ,
}
return backoff .Retry (func () error {
if err := cb (ctx ); err != nil {
logger .Warn ("TestClient run failed" , zap .Error (err ))
if tgerr .Is (err , retryableErrors ...) {
return err
}
if timeout , ok := AsFloodWait (err ); ok {
timer := clock .System .Timer (timeout + 1 *time .Second )
defer clock .StopTimer (timer )
select {
case <- timer .C ():
return err
case <- ctx .Done ():
return ctx .Err ()
}
}
if errors .Is (err , io .EOF ) || errors .Is (err , io .ErrUnexpectedEOF ) {
return err
}
return backoff .Permanent (err )
}
return nil
}, b )
}
func TestClient (ctx context .Context , opts Options , cb func (ctx context .Context , client *Client ) error ) error {
if opts .DC == 0 {
opts .DC = 2
}
if opts .DCList .Zero () {
opts .DCList = dcs .Test ()
}
logger := zap .NewNop ()
if opts .Logger != nil {
logger = opts .Logger .Named ("test" )
}
return retry (ctx , logger , func (retryCtx context .Context ) error {
client := NewClient (TestAppID , TestAppHash , opts )
return client .Run (retryCtx , func (runCtx context .Context ) error {
if err := client .Auth ().IfNecessary (runCtx , auth .NewFlow (
auth .Test (crypto .DefaultRand (), opts .DC ),
auth .SendCodeOptions {},
)); err != nil {
return errors .Wrap (err , "auth flow" )
}
return cb (runCtx , client )
})
})
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .