package telegram import ( "github.com/pkg/errors" "math" "strconv" "dev.narayana.im/narayana/telegabber/xmpp/gateway" log "github.com/sirupsen/logrus" "github.com/zelenin/go-tdlib/client" ) const chatsLimit int32 = 999 type clientAuthorizer struct { TdlibParameters chan *client.TdlibParameters PhoneNumber chan string Code chan string State chan client.AuthorizationState Password chan string } func (stateHandler *clientAuthorizer) Handle(c *client.Client, state client.AuthorizationState) error { stateHandler.State <- state switch state.AuthorizationStateType() { case client.TypeAuthorizationStateWaitTdlibParameters: _, err := c.SetTdlibParameters(&client.SetTdlibParametersRequest{ Parameters: <-stateHandler.TdlibParameters, }) return err case client.TypeAuthorizationStateWaitEncryptionKey: _, err := c.CheckDatabaseEncryptionKey(&client.CheckDatabaseEncryptionKeyRequest{}) return err case client.TypeAuthorizationStateWaitPhoneNumber: _, err := c.SetAuthenticationPhoneNumber(&client.SetAuthenticationPhoneNumberRequest{ PhoneNumber: <-stateHandler.PhoneNumber, Settings: &client.PhoneNumberAuthenticationSettings{ AllowFlashCall: false, IsCurrentPhoneNumber: false, AllowSmsRetrieverApi: false, }, }) return err case client.TypeAuthorizationStateWaitCode: _, err := c.CheckAuthenticationCode(&client.CheckAuthenticationCodeRequest{ Code: <-stateHandler.Code, }) return err case client.TypeAuthorizationStateWaitRegistration: return client.ErrNotSupportedAuthorizationState case client.TypeAuthorizationStateWaitPassword: _, err := c.CheckAuthenticationPassword(&client.CheckAuthenticationPasswordRequest{ Password: <-stateHandler.Password, }) return err case client.TypeAuthorizationStateReady: return nil case client.TypeAuthorizationStateLoggingOut: return client.ErrNotSupportedAuthorizationState case client.TypeAuthorizationStateClosing: return client.ErrNotSupportedAuthorizationState case client.TypeAuthorizationStateClosed: return client.ErrNotSupportedAuthorizationState } return client.ErrNotSupportedAuthorizationState } func (stateHandler *clientAuthorizer) Close() { close(stateHandler.TdlibParameters) close(stateHandler.PhoneNumber) close(stateHandler.Code) close(stateHandler.State) close(stateHandler.Password) } // Connect starts TDlib connection func (c *Client) Connect() error { // avoid conflict if another authorization is pending already c.locks.authorizationReady.Wait() if c.Online() { return nil } log.Warn("Connecting to Telegram network...") c.authorizer = &clientAuthorizer{ TdlibParameters: make(chan *client.TdlibParameters, 1), PhoneNumber: make(chan string, 1), Code: make(chan string, 1), State: make(chan client.AuthorizationState, 10), Password: make(chan string, 1), } c.locks.authorizationReady.Add(1) go c.interactor() c.authorizer.TdlibParameters <- c.parameters tdlibClient, err := client.NewClient(c.authorizer, c.options...) if err != nil { return errors.Wrap(err, "Couldn't initialize a Telegram client instance") } c.client = tdlibClient c.locks.authorizationReady.Done() c.online = true go c.updateHandler() return nil } // Disconnect drops TDlib connection func (c *Client) Disconnect() { // already disconnected if !c.Online() { return } log.Warn("Disconnecting from Telegram network...") // we're offline (unsubscribe if logout) for _, id := range c.cache.ChatsKeys() { gateway.SendPresence( c.xmpp, c.jid, gateway.SPFrom(strconv.FormatInt(id, 10)), gateway.SPType("unavailable"), ) } _, err := c.client.Close() if err != nil { log.Errorf("Couldn't close the Telegram instance: %v; %#v", err, c) c.forceClose() } } func (c *Client) interactor() { for { state, ok := <-c.authorizer.State if !ok { log.Error("Interactor is disconnected") return } stateType := state.AuthorizationStateType() log.Infof("Telegram authorization state: %#v", stateType) log.Debugf("%#v", state) switch stateType { // stage 0: set login case client.TypeAuthorizationStateWaitPhoneNumber: log.Warn("Logging in...") if c.Session.Login != "" { c.authorizer.PhoneNumber <- c.Session.Login } else { gateway.SendMessage(c.jid, "", "Please, enter your Telegram login via /login 12345", c.xmpp) } // stage 1: wait for auth code case client.TypeAuthorizationStateWaitCode: log.Warn("Waiting for authorization code...") gateway.SendMessage(c.jid, "", "Please, enter authorization code via /code 12345", c.xmpp) // stage 2: wait for 2fa case client.TypeAuthorizationStateWaitPassword: log.Warn("Waiting for 2FA password...") gateway.SendMessage(c.jid, "", "Please, enter 2FA passphrase via /password 12345", c.xmpp) // stage 3: auth completed case client.TypeAuthorizationStateReady: var err error c.locks.authorizationReady.Wait() log.Warn("Authorization successful!") c.me, err = c.client.GetMe() if err != nil { log.Error("Could not retrieve me info") } else if c.Session.Login == "" { c.Session.Login = c.me.PhoneNumber } _, err = c.client.GetChats(&client.GetChatsRequest{ OffsetOrder: client.JsonInt64(math.MaxInt64), Limit: chatsLimit, }) if err != nil { log.Errorf("Could not retrieve chats: %v", err) } gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in "+c.Session.Login)) return } } } func (c *Client) forceClose() { c.online = false c.authorizer = nil } // Online checks if the updates listener is alive func (c *Client) Online() bool { return c.online }