You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
5.4 KiB
201 lines
5.4 KiB
package telegram |
|
|
|
import ( |
|
"context" |
|
"io" |
|
"sync" |
|
"time" |
|
|
|
"github.com/cenkalti/backoff/v4" |
|
"go.uber.org/atomic" |
|
"go.uber.org/zap" |
|
|
|
"github.com/gotd/td/bin" |
|
"github.com/gotd/td/clock" |
|
"github.com/gotd/td/internal/mtproto" |
|
"github.com/gotd/td/internal/pool" |
|
"github.com/gotd/td/internal/tdsync" |
|
"github.com/gotd/td/session" |
|
"github.com/gotd/td/telegram/dcs" |
|
"github.com/gotd/td/telegram/internal/manager" |
|
"github.com/gotd/td/telegram/internal/version" |
|
"github.com/gotd/td/tg" |
|
) |
|
|
|
// UpdateHandler will be called on received updates from Telegram. |
|
type UpdateHandler interface { |
|
Handle(ctx context.Context, u tg.UpdatesClass) error |
|
} |
|
|
|
// UpdateHandlerFunc type is an adapter to allow the use of |
|
// ordinary function as update handler. |
|
// |
|
// UpdateHandlerFunc(f) is an UpdateHandler that calls f. |
|
type UpdateHandlerFunc func(ctx context.Context, u tg.UpdatesClass) error |
|
|
|
// Handle calls f(ctx, u) |
|
func (f UpdateHandlerFunc) Handle(ctx context.Context, u tg.UpdatesClass) error { |
|
return f(ctx, u) |
|
} |
|
|
|
type clientStorage interface { |
|
Load(ctx context.Context) (*session.Data, error) |
|
Save(ctx context.Context, data *session.Data) error |
|
} |
|
|
|
type clientConn interface { |
|
Run(ctx context.Context) error |
|
Invoke(ctx context.Context, input bin.Encoder, output bin.Decoder) error |
|
Ping(ctx context.Context) error |
|
} |
|
|
|
// Client represents a MTProto client to Telegram. |
|
type Client struct { |
|
// tg provides RPC calls via Client. Uses invoker below. |
|
tg *tg.Client // immutable |
|
// invoker implements tg.Invoker on top of Client and mw. |
|
invoker tg.Invoker // immutable |
|
// mw is list of middlewares used in invoker, can be blank. |
|
mw []Middleware // immutable |
|
|
|
// Telegram device information. |
|
device DeviceConfig // immutable |
|
|
|
// MTProto options. |
|
opts mtproto.Options // immutable |
|
|
|
// DCList state. |
|
// Domain list (for websocket) |
|
domains map[int]string // immutable |
|
// Denotes to use Test DCs. |
|
testDC bool // immutable |
|
|
|
// Connection state. Guarded by connMux. |
|
session *pool.SyncSession |
|
cfg *manager.AtomicConfig |
|
conn clientConn |
|
connMux sync.Mutex |
|
// Connection factory fields. |
|
create connConstructor // immutable |
|
resolver dcs.Resolver // immutable |
|
connBackoff func() backoff.BackOff // immutable |
|
defaultMode manager.ConnMode // immutable |
|
connsCounter atomic.Int64 |
|
|
|
// Restart signal channel. |
|
restart chan struct{} // immutable |
|
// Migration state. |
|
migrationTimeout time.Duration // immutable |
|
migration chan struct{} |
|
|
|
// Connections to non-primary DC. |
|
subConns map[int]CloseInvoker |
|
subConnsMux sync.Mutex |
|
sessions map[int]*pool.SyncSession |
|
sessionsMux sync.Mutex |
|
|
|
// Wrappers for external world, like logs or PRNG. |
|
rand io.Reader // immutable |
|
log *zap.Logger // immutable |
|
clock clock.Clock // immutable |
|
|
|
// Client context. Will be canceled by Run on exit. |
|
ctx context.Context |
|
cancel context.CancelFunc |
|
|
|
// Client config. |
|
appID int // immutable |
|
appHash string // immutable |
|
// Session storage. |
|
storage clientStorage // immutable, nillable |
|
|
|
// Ready signal channel, sends signal when client connection is ready. |
|
// Resets on reconnect. |
|
ready *tdsync.ResetReady // immutable |
|
|
|
// Telegram updates handler. |
|
updateHandler UpdateHandler // immutable |
|
// Denotes that no update mode is enabled. |
|
noUpdatesMode bool // immutable |
|
} |
|
|
|
// NewClient creates new unstarted client. |
|
func NewClient(appID int, appHash string, opt Options) *Client { |
|
opt.setDefaults() |
|
|
|
mode := manager.ConnModeUpdates |
|
if opt.NoUpdates { |
|
mode = manager.ConnModeData |
|
} |
|
client := &Client{ |
|
rand: opt.Random, |
|
log: opt.Logger, |
|
appID: appID, |
|
appHash: appHash, |
|
updateHandler: opt.UpdateHandler, |
|
session: pool.NewSyncSession(pool.Session{ |
|
DC: opt.DC, |
|
}), |
|
domains: opt.DCList.Domains, |
|
testDC: opt.DCList.Test, |
|
cfg: manager.NewAtomicConfig(tg.Config{ |
|
DCOptions: opt.DCList.Options, |
|
}), |
|
create: defaultConstructor(), |
|
resolver: opt.Resolver, |
|
defaultMode: mode, |
|
connBackoff: opt.ReconnectionBackoff, |
|
clock: opt.Clock, |
|
device: opt.Device, |
|
migrationTimeout: opt.MigrationTimeout, |
|
noUpdatesMode: opt.NoUpdates, |
|
mw: opt.Middlewares, |
|
} |
|
client.init() |
|
|
|
// Including version into client logger to help with debugging. |
|
if v := version.GetVersion(); v != "" { |
|
client.log = client.log.With(zap.String("v", v)) |
|
} |
|
|
|
if opt.SessionStorage != nil { |
|
client.storage = &session.Loader{ |
|
Storage: opt.SessionStorage, |
|
} |
|
} |
|
|
|
client.opts = mtproto.Options{ |
|
PublicKeys: opt.PublicKeys, |
|
Random: opt.Random, |
|
Logger: opt.Logger, |
|
AckBatchSize: opt.AckBatchSize, |
|
AckInterval: opt.AckInterval, |
|
RetryInterval: opt.RetryInterval, |
|
MaxRetries: opt.MaxRetries, |
|
CompressThreshold: opt.CompressThreshold, |
|
MessageID: opt.MessageID, |
|
Clock: opt.Clock, |
|
|
|
Types: getTypesMapping(), |
|
} |
|
client.conn = client.createPrimaryConn(nil) |
|
|
|
return client |
|
} |
|
|
|
// init sets fields which needs explicit initialization, like maps or channels. |
|
func (c *Client) init() { |
|
if c.domains == nil { |
|
c.domains = map[int]string{} |
|
} |
|
if c.cfg == nil { |
|
c.cfg = manager.NewAtomicConfig(tg.Config{}) |
|
} |
|
c.ready = tdsync.NewResetReady() |
|
c.restart = make(chan struct{}) |
|
c.migration = make(chan struct{}, 1) |
|
c.sessions = map[int]*pool.SyncSession{} |
|
c.subConns = map[int]CloseInvoker{} |
|
c.invoker = chainMiddlewares(InvokeFunc(c.invokeDirect), c.mw...) |
|
c.tg = tg.NewClient(c.invoker) |
|
}
|
|
|