package telegram import ( "github.com/pkg/errors" "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 FirstName chan string LastName chan string isClosed bool } 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: _, err := c.RegisterUser(&client.RegisterUserRequest{ FirstName: <-stateHandler.FirstName, LastName: <-stateHandler.LastName, }) return err 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() { stateHandler.isClosed = true close(stateHandler.TdlibParameters) close(stateHandler.PhoneNumber) close(stateHandler.Code) close(stateHandler.State) close(stateHandler.Password) close(stateHandler.FirstName) close(stateHandler.LastName) } // Connect starts TDlib connection func (c *Client) Connect(resource string) error { // avoid conflict if another authorization is pending already c.locks.authorizationReady.Wait() if c.Online() { c.roster(resource) 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), FirstName: make(chan string, 1), LastName: 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 { c.locks.authorizationReady.Done() return errors.Wrap(err, "Couldn't initialize a Telegram client instance") } c.client = tdlibClient // stage 3: if a client is succesfully created, AuthorizationStateReady is already reached 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 } go c.updateHandler() c.online = true c.locks.authorizationReady.Done() c.addResource(resource) go func() { _, err = c.client.GetChats(&client.GetChatsRequest{ Limit: chatsLimit, }) if err != nil { log.Errorf("Could not retrieve chats: %v", err) } gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribe")) gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribed")) gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in as: "+c.Session.Login)) }() return nil } // Disconnect drops TDlib connection and // returns the flag indicating if disconnecting is permitted func (c *Client) Disconnect(resource string, quit bool) bool { if !quit { c.deleteResource(resource) } // other resources are still active if (len(c.resources) > 0 || c.Session.KeepOnline) && !quit { log.Infof("Resource %v for account %v has disconnected, %v remaining", resource, c.Session.Login, len(c.resources)) log.Debugf("Resources: %#v", c.resources) return false } // already disconnected if !c.Online() { return false } 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() return true } func (c *Client) interactor() { for { state, ok := <-c.authorizer.State if !ok { log.Warn("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 1b: wait for registration case client.TypeAuthorizationStateWaitRegistration: log.Warn("Waiting for full name...") gateway.SendMessage(c.jid, "", "This number is not registered yet! Please, enter your name via /setname John Doe", 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) } } } 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 }