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



                               
              
 

                                                          
                                        
                                            

 

                            
                              
                                                              



                                                      


                                   


                                                                                                       


                                                      



                                                              
                                                                              



















                                                                                                     




                                                                     










                                                                                                   
                          

                                                  
                          








                                                               


                                  
                                    




                                           

                                     

 
                                  
                                                 

                                                                
                                                                     
                                         
 
                       
                                  
                                                   


                          

                                                     
                                          
                                         
                                                                                 



                                                                          

                                                      
         
 
                         
                                       
 
                                                    
                                            
 
                                                                        
                       
                                                   
                                                                                         


                              









                                                                                                  
 
                            
                       
                                           
                               
 

                                                                   
                                          



                                                                       
 
                                                           
                                                                                  
           



                  















                                                                                       

                                                  
 











                                                         


                                                  







                                                             






                                                              
                                                                    



                                                                                                                                   
                               
                        
                            
         
 

                                                          
                                                
                                                

                                                                 
         
 
                 

                   

 

                               





                                                                              

                                                 
                                                              
                                                           




                                                                         
                                        
 
                                  
                                     




                                                                           
                                                                                                                               
                         
                                              

                                                                     
                                                                                                                     


                                                                   
                                                                                                                                                     
                                        

                                                               
                                                                                                                     
                 
                                                   
         
 
 
                               




                                                  
                        
                          

 
                          
                                          


                                                          

                                            













                                                                                           

                                                 
                       
 
package telegram

import (
	"github.com/pkg/errors"
	"time"

	"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.SetTdlibParametersRequest
	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 {
	if stateHandler.isClosed {
		return errors.New("Channel is closed")
	}
	stateHandler.State <- state

	switch state.AuthorizationStateType() {
	case client.TypeAuthorizationStateWaitTdlibParameters:
		_, err := c.SetTdlibParameters(<-stateHandler.TdlibParameters)
		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 nil

	case client.TypeAuthorizationStateClosing:
		return nil

	case client.TypeAuthorizationStateClosed:
		return client.ErrNotSupportedAuthorizationState
	}

	return client.ErrNotSupportedAuthorizationState
}

func (stateHandler *clientAuthorizer) Close() {
	if stateHandler.isClosed {
		return
	}
	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.locks.authorizerWriteLock.Lock()
	c.authorizer = &clientAuthorizer{
		TdlibParameters: make(chan *client.SetTdlibParametersRequest, 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()
	log.Warn("Interactor launched")

	c.authorizer.TdlibParameters <- c.parameters
	c.locks.authorizerWriteLock.Unlock()

	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.SubscribeToTransport(c.xmpp, c.jid)
		c.sendPresence(gateway.SPStatus("Logged in as: "+c.Session.Login))
	}()

	return nil
}

func (c *Client) TryLogin(resource string, login string) error {
	wasSessionLoginEmpty := c.Session.Login == ""
	c.Session.Login = login

	if wasSessionLoginEmpty && c.authorizer == nil {
		go func() {
			err := c.Connect(resource)
			if err != nil {
				log.Error(errors.Wrap(err, "TDlib connection failure"))
			}
		}()
		// a quirk for authorizer to become ready. If it's still not,
		// nothing bad: just re-login again
		time.Sleep(1e5)
	}

	c.locks.authorizerWriteLock.Lock()
	defer c.locks.authorizerWriteLock.Unlock()

	if c.authorizer == nil {
		return errors.New(TelegramNotInitialized)
	}

	if c.authorizer.isClosed {
		return errors.New(TelegramAuthDone)
	}

	return nil
}

func (c *Client) SetPhoneNumber(login string) error {
	c.locks.authorizerWriteLock.Lock()
	defer c.locks.authorizerWriteLock.Unlock()

	if c.authorizer == nil || c.authorizer.isClosed {
		return errors.New("Authorization not needed")
	}

	c.authorizer.PhoneNumber <- 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() {
		args := gateway.SimplePresence(id, "unavailable")
		c.sendPresence(args...)
	}

	c.close()

	return true
}

func (c *Client) interactor() {
	for {
		c.locks.authorizerReadLock.Lock()
		if c.authorizer == nil {
			log.Warn("Authorizer is lost, halting the interactor")
			c.locks.authorizerReadLock.Unlock()
			return
		}
		state, ok := <-c.authorizer.State
		if !ok {
			log.Warn("Interactor is disconnected")
			c.locks.authorizerReadLock.Unlock()
			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.SendServiceMessage(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.SendServiceMessage(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.SendServiceMessage(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.SendServiceMessage(c.jid, "Please, enter 2FA passphrase via /password 12345", c.xmpp)
		}
		c.locks.authorizerReadLock.Unlock()
	}
}

func (c *Client) forceClose() {
	c.locks.authorizerReadLock.Lock()
	c.locks.authorizerWriteLock.Lock()
	defer c.locks.authorizerReadLock.Unlock()
	defer c.locks.authorizerWriteLock.Unlock()

	c.online = false
	c.authorizer = nil
}

func (c *Client) close() {
	c.locks.authorizerWriteLock.Lock()
	if c.authorizer != nil && !c.authorizer.isClosed {
		c.authorizer.Close()
	}
	c.locks.authorizerWriteLock.Unlock()

	if c.client != nil {
		_, err := c.client.Close()
		if err != nil {
			log.Errorf("Couldn't close the Telegram instance: %v; %#v", err, c)
		}
	}
	c.forceClose()
}

func (c *Client) cancelAuth() {
	c.close()
	c.Session.Login = ""
}

// Online checks if the updates listener is alive
func (c *Client) Online() bool {
	return c.online
}