From 78a43058722d289adab18d9647e925aa2317f04a Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 18 Oct 2025 16:42:40 -0400 Subject: [PATCH] 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 {