aboutsummaryrefslogblamecommitdiff
path: root/telegram/connect.go
blob: 0b9a1955d78a0e521931e8ca2a703e585dfbc227 (plain) (tree)
1
2
3
4
5
6
7
8
9



                               
                 
 

                                                          
                                        
                                            

 

                            





                                                      


                                   

































                                                                                                       




                                                                     























                                                                                                   
                                    




                                           

                                     

 
                                  
                                                 

                                                                
                                                                     
                                         
 
                       
                                  
                                                   


                          

                                                     





                                                                          

                                                      
         
 
                         
 
                                                    
 
                                                                        
                       
                                                   
                                                                                         


                              









                                                                                                  
 
                            
                       
                                           
                               
 

                                                                   
                                          



                                                                       
 



                                                                                                       



                  






                                                              
                                                                    



                                                                                                                                   
                               
                        
                            
         
 

                                                          
                                                
                                                






                                                                  
 

                                  
                                                                                   
         


                      

 



                                                 
                                                              




                                                                         
                                        
 
                                  
                                     






                                                                                                                            
                                              


                                                                                                                  



                                                                                                                                                  
                                        


                                                                                                                  

                 
 
 
                               
                        
                          

 

                                                 
                       
 
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 {
	log.Warn("Attempting to connect to Telegram network...")

	// avoid conflict if another authorization is pending already
	c.locks.authorizationReady.Lock()

	if c.Online() {
		c.roster(resource)
		c.locks.authorizationReady.Unlock()
		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),
	}

	go c.interactor()

	c.authorizer.TdlibParameters <- c.parameters

	tdlibClient, err := client.NewClient(c.authorizer, c.options...)
	if err != nil {
		c.locks.authorizationReady.Unlock()
		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.Unlock()
	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
}