From aaf7233c89af4dfbc2a8b9f5e11b4ff5e9713567 Mon Sep 17 00:00:00 2001 From: bodqhrohro Date: Mon, 4 Nov 2019 00:15:43 +0200 Subject: [PATCH] Presence/iq handling and rudimental telegram client --- config.yml.example | 1 + config/config.go | 6 +-- config_schema.json | 6 ++- go.mod | 3 ++ go.sum | 13 +++++ telegabber.go | 10 ++-- telegram/client.go | 118 +++++++++++++++++++++++++++++++++++++++++++ test/good_config.yml | 1 + xmpp/component.go | 21 ++++++-- xmpp/handlers.go | 95 ++++++++++++++++++++++++++++++++-- 10 files changed, 258 insertions(+), 16 deletions(-) create mode 100644 telegram/client.go diff --git a/config.yml.example b/config.yml.example index 82945b4..a31cffd 100644 --- a/config.yml.example +++ b/config.yml.example @@ -13,6 +13,7 @@ :device_model: 'telegabber' :application_version: '2.0' :use_chat_info_database: false + :use_secret_chats: true :xmpp: :loglevel: :warn diff --git a/config/config.go b/config/config.go index 6759dd8..bcd493e 100644 --- a/config/config.go +++ b/config/config.go @@ -42,9 +42,8 @@ type TelegramContentConfig struct { // TelegramTdlibConfig is for :tdlib: subtree type TelegramTdlibConfig struct { - Path string `yaml:":lib_path"` - Logfile string `yaml:":logfile"` - Client TelegramTdlibClientConfig `yaml:":client"` + Path string `yaml:":lib_path"` + Client TelegramTdlibClientConfig `yaml:":client"` } // TelegramTdlibClientConfig is for :client: subtree @@ -54,6 +53,7 @@ type TelegramTdlibClientConfig struct { DeviceModel string `yaml:":device_model"` ApplicationVersion string `yaml:":application_version"` UseChatInfoDatabase bool `yaml:":use_chat_info_database"` + UseSecretChats bool `yaml:":use_secret_chats"` } // ReadConfig reads the specified config file, validates it and returns a struct diff --git a/config_schema.json b/config_schema.json index 434bde5..48454a5 100644 --- a/config_schema.json +++ b/config_schema.json @@ -39,7 +39,8 @@ "required": [":api_id", ":api_hash"], "properties": { ":api_id": { - "$ref": "#/definitions/non-empty-string" + "type": "string", + "pattern": "^[0-9]+$" }, ":api_hash": { "$ref": "#/definitions/non-empty-string" @@ -52,6 +53,9 @@ }, ":use_chat_info_database": { "type": "boolean" + }, + ":use_secret_chats": { + "type": "boolean" } } } diff --git a/go.mod b/go.mod index c6f2878..73af52f 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,11 @@ module dev.narayana.im/narayana/telegabber go 1.13 require ( + github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7 github.com/pkg/errors v0.8.1 github.com/santhosh-tekuri/jsonschema v1.2.4 + github.com/sirupsen/logrus v1.4.2 + github.com/zelenin/go-tdlib v0.1.0 gopkg.in/yaml.v2 v2.2.4 gosrc.io/xmpp v0.1.3 ) diff --git a/go.sum b/go.sum index 4734020..5c53fc6 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,21 @@ +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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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/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= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/zelenin/go-tdlib v0.1.0 h1:Qq+FGE0/EWdsRB6m26ULDndu2DtW558aFXNzi0Y/FqQ= +github.com/zelenin/go-tdlib v0.1.0/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/telegabber.go b/telegabber.go index c5ee366..6754038 100644 --- a/telegabber.go +++ b/telegabber.go @@ -1,14 +1,15 @@ package main import ( - "log" - "dev.narayana.im/narayana/telegabber/config" "dev.narayana.im/narayana/telegabber/xmpp" + + log "github.com/sirupsen/logrus" ) // YAML config, compatible with the format of Zhabogram 2.0.0 const configPath string = "config.yml" + // JSON schema (not for editing by a user) const schemaPath string = "./config_schema.json" @@ -18,7 +19,10 @@ func main() { log.Fatal(err) } - cm := xmpp.NewComponent(config.XMPP) + cm, err := xmpp.NewComponent(config.XMPP, config.Telegram) + if err != nil { + log.Fatal(err) + } // reconnect automatically log.Fatal(cm.Run()) diff --git a/telegram/client.go b/telegram/client.go new file mode 100644 index 0000000..b0cee44 --- /dev/null +++ b/telegram/client.go @@ -0,0 +1,118 @@ +package telegram + +import ( + "fmt" + "github.com/pkg/errors" + "path/filepath" + "strconv" + + "dev.narayana.im/narayana/telegabber/config" + + "github.com/zelenin/go-tdlib/client" +) + +var logConstants = map[string]int32{ + "fatal": 0, + "error": 1, + "warn": 2, + "info": 3, + "debug": 4, + "verbose": 5, + "all": 1023, +} + +func stringToLogConstant(c string) int32 { + level, ok := logConstants[c] + if !ok { + level = 0 + } + + return level +} + +type TelegramClient struct { + client *client.Client + jid string + parameters *client.TdlibParameters + online bool + logVerbosity client.Option +} + +// NewClient instantiates a Telegram App +func NewClient(conf config.TelegramConfig, jid string) (TelegramClient, error) { + logVerbosity := client.WithLogVerbosity(&client.SetLogVerbosityLevelRequest{ + NewVerbosityLevel: stringToLogConstant(conf.Loglevel), + }) + + apiId, err := strconv.Atoi(conf.Tdlib.Client.APIID) + if err != nil { + return TelegramClient{}, errors.Wrap(err, "Wrong api_id") + } + + parameters := client.TdlibParameters{ + UseTestDc: false, + + DatabaseDirectory: filepath.Join("./sessions/", jid), + FilesDirectory: filepath.Join("./sessions/", jid, "/files/"), + + UseFileDatabase: true, + UseChatInfoDatabase: conf.Tdlib.Client.UseChatInfoDatabase, + UseMessageDatabase: true, + UseSecretChats: conf.Tdlib.Client.UseSecretChats, + + ApiId: int32(apiId), + ApiHash: conf.Tdlib.Client.APIHash, + + SystemLanguageCode: "en", + DeviceModel: conf.Tdlib.Client.DeviceModel, + SystemVersion: "1.0.0", + ApplicationVersion: conf.Tdlib.Client.ApplicationVersion, + + EnableStorageOptimizer: true, + IgnoreFileNames: false, + } + + return TelegramClient{ + parameters: ¶meters, + jid: jid, + logVerbosity: logVerbosity, + }, nil +} + +func updateHandler(tdlibClient *client.Client) { + listener := tdlibClient.GetListener() + defer listener.Close() + + for update := range listener.Updates { + if update.GetClass() == client.ClassUpdate { + fmt.Printf("%#v", update) + } + } +} + +func (c *TelegramClient) Connect() error { + if c.online { + return nil + } + + authorizer := client.ClientAuthorizer() + authorizer.TdlibParameters <- c.parameters + + tdlibClient, err := client.NewClient(authorizer, c.logVerbosity) + if err != nil { + return errors.Wrap(err, "Coudn't initialize a Telegram client instance") + } + + c.client = tdlibClient + c.online = true + + go updateHandler(c.client) + + return nil +} + +func (c *TelegramClient) Disconnect() { + if !c.online { + return + } +} diff --git a/test/good_config.yml b/test/good_config.yml index 3ea257c..a60bb4b 100644 --- a/test/good_config.yml +++ b/test/good_config.yml @@ -13,6 +13,7 @@ :device_model: 'telegabber' :application_version: '2.0' :use_chat_info_database: false + :use_secret_chats: true :xmpp: :loglevel: :warn diff --git a/xmpp/component.go b/xmpp/component.go index d55ed6b..1562224 100644 --- a/xmpp/component.go +++ b/xmpp/component.go @@ -1,16 +1,25 @@ package xmpp import ( - "log" - "dev.narayana.im/narayana/telegabber/config" "gosrc.io/xmpp" ) +var jid *xmpp.Jid +var tgConf config.TelegramConfig + // NewComponent starts a new component and wraps it in // a stream manager that you should start yourself -func NewComponent(conf config.XMPPConfig) *xmpp.StreamManager { +func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig) (*xmpp.StreamManager, error) { + var err error + jid, err = xmpp.NewJid(conf.Jid) + if err != nil { + return nil, err + } + + tgConf = tc + options := xmpp.ComponentOptions{ Address: conf.Host + ":" + conf.Port, Domain: conf.Jid, @@ -19,14 +28,16 @@ func NewComponent(conf config.XMPPConfig) *xmpp.StreamManager { } router := xmpp.NewRouter() + router.HandleFunc("iq", HandleIq) + router.HandleFunc("presence", HandlePresence) router.HandleFunc("message", HandleMessage) component, err := xmpp.NewComponent(options, router) if err != nil { - log.Fatalf("%+v", err) + return nil, err } cm := xmpp.NewStreamManager(component, nil) - return cm + return cm, nil } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 874af49..c7acdce 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -1,22 +1,109 @@ package xmpp import ( - "fmt" - "os" + "dev.narayana.im/narayana/telegabber/telegram" + log "github.com/sirupsen/logrus" "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" ) +var sessions map[string]telegram.TelegramClient + +func logPacketType(p stanza.Packet) { + log.Warn("Ignoring packet: %T\n", p) +} + +// HandleIq processes an incoming XMPP iq +func HandleIq(s xmpp.Sender, p stanza.Packet) { + iq, ok := p.(stanza.IQ) + if !ok { + logPacketType(p) + return + } + + log.Printf("Iq: %#v\n", iq) +} + // HandleMessage processes an incoming XMPP message func HandleMessage(s xmpp.Sender, p stanza.Packet) { msg, ok := p.(stanza.Message) if !ok { - _, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p) + logPacketType(p) return } - _, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From) + log.Printf("Message: %#v\n", msg) reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body} _ = s.Send(reply) } + +// HandlePresence processes an incoming XMPP presence +func HandlePresence(s xmpp.Sender, p stanza.Packet) { + prs, ok := p.(stanza.Presence) + if !ok { + logPacketType(p) + return + } + + if prs.Type == "subscribe" { + handleSubscription(s, prs) + } else if prs.To == jid.Bare() { + handlePresence(s, prs) + } +} + +func handleSubscription(s xmpp.Sender, p stanza.Presence) { + log.WithFields(log.Fields{ + "from": p.From, + "to": p.To, + }).Warn("Subscription request") + log.Debugf("%#v", p) + + reply := stanza.Presence{Attrs: stanza.Attrs{ + From: p.To, + To: p.From, + Id: p.Id, + Type: "subscribed", + }} + + _ = s.Send(reply) +} + +func handlePresence(s xmpp.Sender, p stanza.Presence) { + presenceType := p.Type + if presenceType == "" { + presenceType = "online" + } + + log.WithFields(log.Fields{ + "type": presenceType, + "from": p.From, + "to": p.To, + }).Warn("Presence") + log.Debugf("%#v", p) + + fromJid, err := xmpp.NewJid(p.From) + if err != nil { + log.Error("Invalid from JID!") + return + } + bareFromJid := fromJid.Bare() + session, ok := sessions[bareFromJid] + if !ok { + client, err := telegram.NewClient(tgConf, bareFromJid) + if err != nil { + log.Error("Invalid from JID!") + } + sessions[bareFromJid] = client + } + + switch p.Type { + case "unsubscribed": + delete(sessions, bareFromJid) + case "unavailable", "error": + session.Disconnect() + case "": + session.Connect() + } +}