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.
202 lines
5.4 KiB
202 lines
5.4 KiB
3 years ago
|
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)
|
||
|
}
|