232 lines
6.1 KiB
Go
232 lines
6.1 KiB
Go
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
|
|
}
|
|
|
|
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(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),
|
|
}
|
|
|
|
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 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
|
|
}
|