diff --git a/telegabber.go b/telegabber.go index 731d193..5994d6c 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/telegram/commands.go b/telegram/commands.go index c271977..9878a07 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}, @@ -358,13 +359,14 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) (strin return errors.Wrap(err, "Logout error").Error(), false } - for _, id := range c.cache.OwnChatsKeys() { - c.leaveChat(id) - } + c.unsubscribeFromAll() c.Session.Login = "" c.wizardStageOrPrompt(LoginStageCancel, "") c.online = false + // cleanup + case "cleanup": + c.unsubscribeFromAll() // cancel auth case "cancelauth": if c.Online() { @@ -687,11 +689,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/connect.go b/telegram/connect.go index 86b8d4a..4336145 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -155,11 +155,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) diff --git a/telegram/utils.go b/telegram/utils.go index 82bb870..04821fd 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -2948,6 +2948,13 @@ func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message, p } } +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) { if membersList == MembersListCreators { @@ -3084,6 +3091,12 @@ func (c *Client) unsubscribe(chatID int64) error { return c.sendPresence(args...) } +func (c *Client) unsubscribeFromAll() { + for _, id := range c.cache.ChatsKeys() { + c.leaveChat(id) + } +} + func (c *Client) leaveChat(chatID int64) error { chat, err := c.GetChatByID(chatID, nil, true) diff --git a/xmpp/component.go b/xmpp/component.go index 20ae0a6..8a6baed 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/extensions/extensions.go b/xmpp/extensions/extensions.go index dcf8b3f..9d8914a 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -482,6 +482,14 @@ type MAM2MetadataEnd struct { Timestamp string `xml:"timestamp,attr,omitempty"` } +// 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 @@ -562,6 +570,16 @@ func (c MessageDelayLegacy) Namespace() string { return c.XMLName.Space } +// 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" @@ -895,4 +913,10 @@ func init() { "urn:xmpp:mam:2", "metadata", }, MAM2Metadata{}) + + // entity time + stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ + "urn:xmpp:time", + "time", + }, EntityTime{}) } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 863950c..dbcf720 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -61,6 +61,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 a8b87e7..d0ee57b 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -108,6 +108,16 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { go handleGetMetadataMAM2(s, iq) return } + _, ok = iq.Payload.(*stanza.Version) + if ok { + go handleGetVersion(s, iq) + return + } + _, ok = iq.Payload.(*extensions.EntityTime) + if ok { + go handleGetEntityTime(s, iq) + return + } } else if iq.Type == stanza.IQTypeSet { queryRegister, ok := iq.Payload.(*extensions.QueryRegister) if ok { @@ -1072,6 +1082,8 @@ 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") + disco.AddFeatures("urn:xmpp:time") } else if di.Node == "x-roomuser-item" { bare, _, fromOk := gateway.SplitJID(iq.From) if fromOk { @@ -1254,6 +1266,77 @@ 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 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 handleGetQueryMucAdmin(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryMucAdmin) { component, answer, ok := iqResultStub(s, iq) if !ok {