diff --git a/go.mod b/go.mod index 7f0bbc9..fe60906 100644 --- a/go.mod +++ b/go.mod @@ -14,3 +14,5 @@ require ( ) replace gosrc.io/xmpp => github.com/bodqhrohro/go-xmpp v0.1.4-0.20191106203535-f3b463f3b26c + +replace github.com/zelenin/go-tdlib => github.com/bodqhrohro/go-tdlib v0.1.2-0.20191121233100-48d2382034fb diff --git a/go.sum b/go.sum index 519b0eb..a6dda34 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,11 @@ github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7 h1:GbV1Lv3lVHsSeKAqPTBem72OCsGjXntW4jfJdXciE+w= github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7/go.mod h1:ZzkRfuaFj8etIYMj/ECtXtgfz72RE6U+dos27b3XIwk= github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= +github.com/bodqhrohro/go-tdlib v0.1.1 h1:lmHognymABxP3cmHkfAGhGnWaJaZ3htpJ7RSbZacin4= +github.com/bodqhrohro/go-tdlib v0.1.2-0.20191121200156-e826071d3317 h1:+mv4FwWXl8hTa7PrhekwVzPknH+rHqB60jIPBi2XqI8= +github.com/bodqhrohro/go-tdlib v0.1.2-0.20191121200156-e826071d3317/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= +github.com/bodqhrohro/go-tdlib v0.1.2-0.20191121233100-48d2382034fb h1:y5PnjdAnNVS0q8xuwjm3TxBfLriJmykQdoGiyYZB3s0= +github.com/bodqhrohro/go-tdlib v0.1.2-0.20191121233100-48d2382034fb/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= github.com/bodqhrohro/go-xmpp v0.1.4-0.20191106203535-f3b463f3b26c h1:LzcQyE+Gs+0kAbpnPAUD68FvUCieKZip44URAmH70PI= github.com/bodqhrohro/go-xmpp v0.1.4-0.20191106203535-f3b463f3b26c/go.mod h1:fWixaMaFvx8cxXcJVJ5kU9csMeD/JN8on7ybassU8rY= github.com/bodqhrohro/go-xmpp v0.2.1-0.20191105232737-9abd5be0aa1b h1:9BLd/SNO4JJZLRl1Qb1v9mNivIlHuwHDe2c8hQvBxFA= @@ -12,6 +17,7 @@ github.com/chromedp/cdproto v0.0.0-20190926234355-1b4886c6fad6/go.mod h1:0YChpVz github.com/chromedp/chromedp v0.3.1-0.20190619195644-fd957a4d2901/go.mod h1:mJdvfrVn594N9tfiPecUidF6W5jPRKHymqHfzbobPsM= github.com/chromedp/chromedp v0.4.0/go.mod h1:DC3QUn4mJ24dwjcaGQLoZrhm4X/uPHZ6spDbS2uFhm4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -25,6 +31,7 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -55,6 +62,7 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= @@ -67,6 +75,7 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/telegram/client.go b/telegram/client.go index 833d883..c8187fc 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -10,6 +10,7 @@ import ( "dev.narayana.im/narayana/telegabber/persistence" "github.com/zelenin/go-tdlib/client" + "gosrc.io/xmpp" ) var logConstants = map[string]int32{ @@ -34,6 +35,8 @@ func stringToLogConstant(c string) int32 { // Client stores the metadata for lazily invoked TDlib instance type Client struct { client *client.Client + authorizer *client.ClientAuthorizerType + xmpp *xmpp.Component jid string parameters *client.TdlibParameters Session *persistence.Session @@ -42,14 +45,14 @@ type Client struct { } // NewClient instantiates a Telegram App -func NewClient(conf config.TelegramConfig, jid string, session *persistence.Session) (Client, error) { +func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component, session *persistence.Session) (*Client, error) { logVerbosity := client.WithLogVerbosity(&client.SetLogVerbosityLevelRequest{ NewVerbosityLevel: stringToLogConstant(conf.Loglevel), }) apiID, err := strconv.Atoi(conf.Tdlib.Client.APIID) if err != nil { - return Client{}, errors.Wrap(err, "Wrong api_id") + return &Client{}, errors.Wrap(err, "Wrong api_id") } parameters := client.TdlibParameters{ @@ -75,8 +78,9 @@ func NewClient(conf config.TelegramConfig, jid string, session *persistence.Sess IgnoreFileNames: false, } - return Client{ + return &Client{ parameters: ¶meters, + xmpp: component, jid: jid, Session: session, logVerbosity: logVerbosity, @@ -93,3 +97,7 @@ func updateHandler(tdlibClient *client.Client) { } } } + +func (c *Client) ProcessOutgoingMessage(chatID int, text string, messageID int) { + // TODO +} diff --git a/telegram/commands.go b/telegram/commands.go new file mode 100644 index 0000000..64a5120 --- /dev/null +++ b/telegram/commands.go @@ -0,0 +1,33 @@ +package telegram + +const notEnoughArguments string = "Not enough arguments" +const telegramNotInitialized string = "Telegram connection is not initialized yet" + +// ProcessTransportCommand executes commands sent directly to the component +func (c *Client) ProcessTransportCommand(cmd string, args []string) string { + switch cmd { + case "login", "code", "password": + if cmd == "login" && c.Session.Login != "" { + return "" + } + + if len(args) < 1 { + return notEnoughArguments + } + if c.authorizer == nil { + return telegramNotInitialized + } + + switch cmd { + case "login": + c.authorizer.PhoneNumber <- args[0] + c.Session.Login = args[0] + case "code": + c.authorizer.Code <- args[0] + case "password": + c.authorizer.Password <- args[0] + } + } + + return "" +} diff --git a/telegram/connect.go b/telegram/connect.go index c30f9a0..7e92bc8 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -3,6 +3,8 @@ package telegram import ( "github.com/pkg/errors" + "dev.narayana.im/narayana/telegabber/xmpp/gateway" + log "github.com/sirupsen/logrus" "github.com/zelenin/go-tdlib/client" ) @@ -15,26 +17,15 @@ func (c *Client) Connect() error { log.Warn("Connecting to Telegram network...") - authorizer := client.ClientAuthorizer() - go func() { - for { - state, ok := <-authorizer.State - if !ok { - return - } + c.authorizer = client.ClientAuthorizer() - ok = authorizationStateHandler(state) - if !ok { - return - } - } - }() + go c.interactor() - authorizer.TdlibParameters <- c.parameters + c.authorizer.TdlibParameters <- c.parameters - tdlibClient, err := client.NewClient(authorizer, c.logVerbosity) + tdlibClient, err := client.NewClient(c.authorizer, c.logVerbosity) if err != nil { - return errors.Wrap(err, "Coudn't initialize a Telegram client instance") + return errors.Wrap(err, "Couldn't initialize a Telegram client instance") } c.client = tdlibClient @@ -59,10 +50,34 @@ func (c *Client) Disconnect() { c.online = false } -func authorizationStateHandler(state client.AuthorizationState) bool { - switch state.AuthorizationStateType() { - // TODO - } +func (c *Client) interactor() { + for { + state, ok := <-c.authorizer.State + if !ok { + return + } - return true + stateType := state.AuthorizationStateType() + log.Infof("Telegram authorization state: %#v", stateType) + + switch stateType { + 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) + } + case client.TypeAuthorizationStateWaitCode: + log.Warn("Waiting for authorization code...") + gateway.SendMessage(c.jid, "", "Please, enter authorization code via /code 12345", c.xmpp) + case client.TypeAuthorizationStateWaitPassword: + log.Warn("Waiting for 2FA password...") + gateway.SendMessage(c.jid, "", "Please, enter 2FA passphrase via /password 12345", c.xmpp) + case client.TypeAuthorizationStateReady: + log.Warn("Authorization successful!") + // TODO + return + } + } } diff --git a/xmpp/component.go b/xmpp/component.go index fa1a650..7e2d16e 100644 --- a/xmpp/component.go +++ b/xmpp/component.go @@ -8,6 +8,7 @@ import ( "dev.narayana.im/narayana/telegabber/config" "dev.narayana.im/narayana/telegabber/persistence" "dev.narayana.im/narayana/telegabber/telegram" + "dev.narayana.im/narayana/telegabber/xmpp/gateway" log "github.com/sirupsen/logrus" "github.com/soheilhy/args" @@ -17,9 +18,8 @@ import ( const pollingInterval time.Duration = 1e7 -var jid *xmpp.Jid var tgConf config.TelegramConfig -var sessions map[string]telegram.Client +var sessions map[string]*telegram.Client var queue map[string]*stanza.Presence var db persistence.SessionsYamlDB @@ -28,18 +28,13 @@ var db persistence.SessionsYamlDB func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig) (*xmpp.StreamManager, *xmpp.Component, error) { var err error - jid, err = xmpp.NewJid(conf.Jid) + gateway.Jid, err = xmpp.NewJid(conf.Jid) if err != nil { return nil, nil, err } tgConf = tc - err = loadSessions(conf.Db) - if err != nil { - return nil, nil, err - } - options := xmpp.ComponentOptions{ Address: conf.Host + ":" + conf.Port, Domain: conf.Jid, @@ -57,6 +52,11 @@ func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig) (*xmpp.Strea return nil, nil, err } + err = loadSessions(conf.Db, component) + if err != nil { + return nil, nil, err + } + sm := xmpp.NewStreamManager(component, nil) go heartbeat(component) @@ -99,10 +99,10 @@ func heartbeat(component *xmpp.Component) { } } -func loadSessions(dbPath string) error { +func loadSessions(dbPath string, component *xmpp.Component) error { var err error - sessions = make(map[string]telegram.Client) + sessions = make(map[string]*telegram.Client) db, err = persistence.LoadSessions(dbPath) if err != nil { @@ -111,7 +111,7 @@ func loadSessions(dbPath string) error { db.Transaction(func() bool { for jid, session := range db.Data.Sessions { - getTelegramInstance(jid, &session) + getTelegramInstance(jid, &session, component) } return false @@ -120,10 +120,11 @@ func loadSessions(dbPath string) error { return nil } -func getTelegramInstance(jid string, savedSession *persistence.Session) (telegram.Client, bool) { +func getTelegramInstance(jid string, savedSession *persistence.Session, component *xmpp.Component) (*telegram.Client, bool) { + var err error session, ok := sessions[jid] if !ok { - session, err := telegram.NewClient(tgConf, jid, savedSession) + session, err = telegram.NewClient(tgConf, jid, component, savedSession) if err != nil { log.Error(errors.Wrap(err, "TDlib initialization failure")) return session, false @@ -195,7 +196,7 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence { func sendPresence(component *xmpp.Component, to string, args ...args.V) error { var logFrom string - bareJid := jid.Bare() + bareJid := gateway.Jid.Bare() if SPFrom.IsSet(args) { logFrom = SPFrom.Get(args) } else { @@ -212,7 +213,12 @@ func sendPresence(component *xmpp.Component, to string, args ...args.V) error { // explicit check, as marshalling is expensive if log.GetLevel() == log.DebugLevel { - log.Debug(xml.Marshal(presence)) + xmlPresence, err := xml.Marshal(presence) + if err == nil { + log.Debug(string(xmlPresence)) + } else { + log.Debugf("%#v", presence) + } } immed := SPImmed.Get(args) diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go new file mode 100644 index 0000000..0c22677 --- /dev/null +++ b/xmpp/gateway/gateway.go @@ -0,0 +1,53 @@ +package gateway + +import ( + "encoding/xml" + + log "github.com/sirupsen/logrus" + "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" +) + +// Jid stores the component's JID object +var Jid *xmpp.Jid + +// SendMessage creates and sends a message stanza +func SendMessage(to string, from string, body string, component *xmpp.Component) { + componentJid := Jid.Full() + + var logFrom string + var messageFrom string + if from == "" { + logFrom = componentJid + messageFrom = componentJid + } else { + logFrom = from + messageFrom = from + "@" + componentJid + } + + log.WithFields(log.Fields{ + "from": logFrom, + "to": to, + }).Warn("Got message") + + message := stanza.Message{ + Attrs: stanza.Attrs{ + From: messageFrom, + To: to, + Type: "chat", + }, + Body: body, + } + + // explicit check, as marshalling is expensive + if log.GetLevel() == log.DebugLevel { + xmlMessage, err := xml.Marshal(message) + if err == nil { + log.Debug(string(xmlMessage)) + } else { + log.Debugf("%#v", message) + } + } + + _ = component.Send(message) +} diff --git a/xmpp/handlers.go b/xmpp/handlers.go index ba93a11..d3abeab 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -2,8 +2,11 @@ package xmpp import ( "github.com/pkg/errors" + "strconv" + "strings" "dev.narayana.im/narayana/telegabber/persistence" + "dev.narayana.im/narayana/telegabber/xmpp/gateway" log "github.com/sirupsen/logrus" "gosrc.io/xmpp" @@ -33,9 +36,52 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { return } - log.Printf("Message: %#v\n", msg) - reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body} - _ = s.Send(reply) + component, ok := s.(*xmpp.Component) + if !ok { + log.Error("Not a component") + return + } + + if msg.Type != "error" && msg.Body != "" { + log.WithFields(log.Fields{ + "from": msg.From, + "to": msg.To, + }).Warn("Message") + log.Debugf("%#v", msg) + + fromJid, err := xmpp.NewJid(msg.From) + if err != nil { + log.Error("Invalid from JID!") + return + } + + session, ok := sessions[fromJid.Bare()] + if !ok { + log.Error("Message from stranger") + return + } + + toParts := strings.Split(msg.To, "@") + toID := toParts[0] + if len(toParts) > 1 { + toIDInt, err := strconv.Atoi(toID) + if err != nil { + session.ProcessOutgoingMessage(toIDInt, msg.Body, 0) + return + } + } else if toID == gateway.Jid.Bare() { + bodyFields := strings.Fields(msg.Body) + cmd := bodyFields[0] + if strings.HasPrefix(cmd, "/") { + response := session.ProcessTransportCommand(cmd[1:], bodyFields[1:]) + if response != "" { + gateway.SendMessage(msg.From, "", response, component) + } + return + } + } + log.Warn("Unknown purpose of the message, skipping") + } } // HandlePresence processes an incoming XMPP presence @@ -49,7 +95,7 @@ func HandlePresence(s xmpp.Sender, p stanza.Packet) { if prs.Type == "subscribe" { handleSubscription(s, prs) } - if prs.To == jid.Bare() { + if prs.To == gateway.Jid.Bare() { handlePresence(s, prs) } } @@ -77,6 +123,12 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) { presenceType = "online" } + component, ok := s.(*xmpp.Component) + if !ok { + log.Error("Not a component") + return + } + log.WithFields(log.Fields{ "type": presenceType, "from": p.From, @@ -90,7 +142,7 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) { return } bareFromJid := fromJid.Bare() - session, ok := getTelegramInstance(bareFromJid, &persistence.Session{}) + session, ok := getTelegramInstance(bareFromJid, &persistence.Session{}, component) if !ok { return } @@ -101,7 +153,7 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) { delete(sessions, bareFromJid) case "unavailable", "error": session.Disconnect() - case "": + case "", "online": // due to the weird implentation of go-tdlib wrapper, it won't // return the client instance until successful authorization go func() {