From e7c6318e48fa4d89495b26adb9a210dbc7609a63 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 9 Oct 2025 11:32:33 -0400 Subject: [PATCH 1/4] Add /cancelauth command && unsubscribe from chats more eagerly --- Makefile | 2 +- telegabber.go | 2 +- telegram/commands.go | 14 +++++++++++--- telegram/connect.go | 4 +++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 3182744..71aeabf 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "5bbfc1cf5dab94f82e02f3430ded7241d4653551" -VERSION := "v1.12.6" +VERSION := "v1.12.7" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index cbc58b3..6fb5d5b 100644 --- a/telegabber.go +++ b/telegabber.go @@ -16,7 +16,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.12.6" +var version string = "1.12.7" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index 1c066e0..fa38efb 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -54,6 +54,7 @@ var transportCommands = map[string]command{ "help": command{0, []string{}, "help", false, nil}, "login": command{1, []string{"phone"}, "sign in", false, nil}, "logout": command{0, []string{}, "sign out", true, nil}, + "cleanup": command{0, []string{}, "unsubscribe from all known chats", false, nil}, "cancelauth": command{0, []string{}, "quit the signin wizard", false, nil}, "code": command{1, []string{"xxxxx"}, "check one-time code", false, nil}, "password": command{1, []string{"********"}, "check 2fa password", false, nil}, @@ -279,6 +280,12 @@ func (c *Client) unsubscribe(chatID int64) error { return c.sendPresence(args...) } +func (c *Client) unsubscribeFromAll() { + for _, id := range c.cache.ChatsKeys() { + c.unsubscribe(id) + } +} + func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) { for i := len(messages) - 1; i >= 0; i-- { message := messages[i] @@ -378,11 +385,12 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) (strin return errors.Wrap(err, "Logout error").Error(), false } - for _, id := range c.cache.OwnChatsKeys() { - c.unsubscribe(id) - } + c.unsubscribeFromAll() c.Session.Login = "" + // cleanup + case "cleanup": + c.unsubscribeFromAll() // cancel auth case "cancelauth": if c.Online() { diff --git a/telegram/connect.go b/telegram/connect.go index 8a2b928..27d63c7 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -153,11 +153,13 @@ func (c *Client) Connect(resource string) error { c.addResource(resource) go func() { - _, err = c.client.GetChats(&client.GetChatsRequest{ + chats, err := c.client.GetChats(&client.GetChatsRequest{ Limit: chatsLimit, }) if err != nil { log.Errorf("Could not retrieve chats: %v", err) + } else { + log.Infof("Obtained ≈%v chats for initialization", chats.TotalCount) } gateway.SubscribeToTransport(c.xmpp, c.jid) From aaca93e66d10e55950b4f3c8f4542646b4d9cc34 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 10 Oct 2025 07:44:44 -0400 Subject: [PATCH 2/4] Respond to jabber:version (XEP-0092) queries --- telegabber.go | 2 +- xmpp/component.go | 3 ++- xmpp/gateway/gateway.go | 3 +++ xmpp/handlers.go | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/telegabber.go b/telegabber.go index 6fb5d5b..fefbc93 100644 --- a/telegabber.go +++ b/telegabber.go @@ -68,7 +68,7 @@ func main() { log.Infof("Starting telegabber version %v", version) - sm, component, err = xmpp.NewComponent(config.XMPP, config.Telegram, *idsPath) + sm, component, err = xmpp.NewComponent(config.XMPP, config.Telegram, *idsPath, version) if err != nil { log.Fatal(err) } diff --git a/xmpp/component.go b/xmpp/component.go index b7ccd7b..6a0ae98 100644 --- a/xmpp/component.go +++ b/xmpp/component.go @@ -39,10 +39,11 @@ var sizeRegex = regexp.MustCompile("\\A([0-9]+) ?([KMGTPE]?B?)\\z") // NewComponent starts a new component and wraps it in // a stream manager that you should start yourself -func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig, idsPath string) (*xmpp.StreamManager, *xmpp.Component, error) { +func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig, idsPath string, version string) (*xmpp.StreamManager, *xmpp.Component, error) { var err error gateway.Jid, err = stanza.NewJid(conf.Jid) + gateway.Version = version if err != nil { return nil, nil, err } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 0b6b492..1efba08 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -50,6 +50,9 @@ var QueueLock = sync.Mutex{} // Jid stores the component's JID object var Jid *stanza.Jid +// Version stores this software's version +var Version string + // IdsDB provides a disk-backed bidirectional dictionary of Telegram and XMPP ids var IdsDB badger.IdsDB diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 5c00419..a926523 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -75,6 +75,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { go handleGetQueryRegister(s, iq) return } + _, ok = iq.Payload.(*stanza.Version) + if ok { + go handleGetVersion(s, iq) + return + } } else if iq.Type == stanza.IQTypeSet { query, ok := iq.Payload.(*extensions.QueryRegister) if ok { @@ -643,6 +648,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) { disco.AddFeatures("jabber:iq:register") } disco.AddFeatures(gateway.NSCommand) + disco.AddFeatures("jabber:iq:version") } else { chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To) @@ -801,6 +807,32 @@ func handleGetQueryRegister(s xmpp.Sender, iq *stanza.IQ) { } } +func handleGetVersion(s xmpp.Sender, iq *stanza.IQ) { + component, ok := s.(*xmpp.Component) + if !ok { + log.Error("Not a component") + return + } + + answer, err := stanza.NewIQ(stanza.Attrs{ + Type: stanza.IQTypeResult, + From: iq.To, + To: iq.From, + Id: iq.Id, + Lang: "en", + }) + if err != nil { + log.Errorf("Failed to create answer IQ: %v", err) + return + } + + answer.Version().SetInfo(gateway.Jid.Resource, gateway.Version, "") + + log.Debugf("%#v", answer.Payload) + + _ = gateway.ResumableSend(component, answer) +} + func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) { component, ok := s.(*xmpp.Component) if !ok { From 78a43058722d289adab18d9647e925aa2317f04a Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 18 Oct 2025 16:42:40 -0400 Subject: [PATCH 3/4] Respond to urn:xmpp:time (XEP-0202) queries --- telegram/commands.go | 6 +--- telegram/utils.go | 7 +++++ xmpp/extensions/extensions.go | 24 ++++++++++++++++ xmpp/handlers.go | 52 +++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index fa38efb..1f65520 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -667,11 +667,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool, state = &client.MessageSchedulingStateSendWhenOnline{} result = due } else { - if c.Session.Timezone == "" { - due += "Z" - } else { - due += c.Session.Timezone - } + due += c.GetTZD() switch 0 { default: diff --git a/telegram/utils.go b/telegram/utils.go index 9177eb8..48e4b5c 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -1792,6 +1792,13 @@ func (c *Client) usernamesToString(usernames []string) string { return strings.Join(atUsernames, ", ") } +func (c *Client) GetTZD() string { + if c.Session.Timezone == "" { + return "Z" + } + return c.Session.Timezone +} + // GetChatMembers retrieves a list of chat members. "Limited" mode works only if there are no more than 20 members at all func (c *Client) GetChatMembers(chatID int64, limited bool, query string, membersList MembersList) ([]*client.ChatMember, error) { var filters []client.ChatMembersFilter diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 8e2f743..2509cf8 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -213,6 +213,14 @@ type QueryRegisterRemove struct { XMLName xml.Name `xml:"remove"` } +// EntityTime is from XEP-0202 +type EntityTime struct { + XMLName xml.Name `xml:"urn:xmpp:time time"` + Tzo string `xml:"tzo"` + Utc string `xml:"utc"` + ResultSet *stanza.ResultSet `xml:"set,omitempty"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space @@ -278,6 +286,16 @@ func (c QueryRegister) GetSet() *stanza.ResultSet { return c.ResultSet } +// Namespace is a namespace! +func (c EntityTime) Namespace() string { + return c.XMLName.Space +} + +// GetSet getsets! +func (c EntityTime) GetSet() *stanza.ResultSet { + return c.ResultSet +} + // Name is a packet name func (ClientMessage) Name() string { return "message" @@ -362,4 +380,10 @@ func init() { "jabber:iq:register", "query", }, QueryRegister{}) + + // entity time + stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ + "urn:xmpp:time", + "time", + }, EntityTime{}) } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index a926523..b6d6822 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "sync" + "time" "dev.narayana.im/narayana/telegabber/persistence" "dev.narayana.im/narayana/telegabber/telegram" @@ -80,6 +81,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { go handleGetVersion(s, iq) return } + _, ok = iq.Payload.(*extensions.EntityTime) + if ok { + go handleGetEntityTime(s, iq) + return + } } else if iq.Type == stanza.IQTypeSet { query, ok := iq.Payload.(*extensions.QueryRegister) if ok { @@ -649,6 +655,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) { } disco.AddFeatures(gateway.NSCommand) disco.AddFeatures("jabber:iq:version") + disco.AddFeatures("urn:xmpp:time") } else { chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To) @@ -833,6 +840,51 @@ func handleGetVersion(s xmpp.Sender, iq *stanza.IQ) { _ = gateway.ResumableSend(component, answer) } +func handleGetEntityTime(s xmpp.Sender, iq *stanza.IQ) { + component, ok := s.(*xmpp.Component) + if !ok { + log.Error("Not a component") + return + } + + // separate declaration is crucial for passing as pointer to defer + var answer *stanza.IQ + var err error + answer, err = stanza.NewIQ(stanza.Attrs{ + Type: stanza.IQTypeResult, + From: iq.To, + To: iq.From, + Id: iq.Id, + Lang: "en", + }) + if err != nil { + log.Errorf("Failed to create answer IQ: %v", err) + return + } + + defer gateway.ResumableSend(component, answer) + + fromJid, err := stanza.NewJid(iq.From) + if err != nil { + log.Error("Invalid from JID!") + return + } + + session, ok := sessions[fromJid.Bare()] + if !ok { + log.Error("IQ from stranger") + return + } + + entityTime := extensions.EntityTime{ + Tzo: session.GetTZD(), + Utc: time.Now().UTC().Format(time.RFC3339), + } + answer.Payload = &entityTime + + log.Debugf("%#v", entityTime) +} + func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) { component, ok := s.(*xmpp.Component) if !ok { From e073ded9e4cbcb4bbabff825f002233cb5cc4174 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 19 Oct 2025 13:07:16 -0400 Subject: [PATCH 4/4] Version 1.12.8 --- Makefile | 2 +- telegabber.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 71aeabf..4db1e45 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "5bbfc1cf5dab94f82e02f3430ded7241d4653551" -VERSION := "v1.12.7" +VERSION := "v1.12.8" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index fefbc93..8bd46f5 100644 --- a/telegabber.go +++ b/telegabber.go @@ -16,7 +16,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.12.7" +var version string = "1.12.8" var commit string var sm *goxmpp.StreamManager