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

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)
}