319 lines
8.2 KiB
Go
319 lines
8.2 KiB
Go
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
|
|
}
|