From e9e65b6778d52cf140247bf2f548c5e698183c1d Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 15 Aug 2022 06:10:29 -0400 Subject: [PATCH 01/51] Registration support --- telegram/commands.go | 30 +++++++++++++++++++----------- telegram/connect.go | 18 +++++++++++++++++- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index bd9f0f8..55267ab 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -290,25 +290,33 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } // set My Name case "setname": - if !c.Online() { - return notOnline - } - var firstname string var lastname string if len(args) > 0 { firstname = args[0] } + if firstname == "" { + return "The name should contain at least one character" + } if len(args) > 1 { - lastname = args[1] + lastname = rawCmdArguments(cmdline, 1) } - _, err := c.client.SetName(&client.SetNameRequest{ - FirstName: firstname, - LastName: lastname, - }) - if err != nil { - return errors.Wrap(err, "Couldn't set name").Error() + if c.authorizer != nil && !c.authorizer.isClosed { + c.authorizer.FirstName <- firstname + c.authorizer.LastName <- lastname + } else { + if !c.Online() { + return notOnline + } + + _, err := c.client.SetName(&client.SetNameRequest{ + FirstName: firstname, + LastName: lastname, + }) + if err != nil { + return errors.Wrap(err, "Couldn't set name").Error() + } } // set About case "setbio": diff --git a/telegram/connect.go b/telegram/connect.go index 37f719e..ed0a46b 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -18,6 +18,9 @@ type clientAuthorizer struct { Code chan string State chan client.AuthorizationState Password chan string + FirstName chan string + LastName chan string + isClosed bool } func (stateHandler *clientAuthorizer) Handle(c *client.Client, state client.AuthorizationState) error { @@ -52,7 +55,11 @@ func (stateHandler *clientAuthorizer) Handle(c *client.Client, state client.Auth return err case client.TypeAuthorizationStateWaitRegistration: - return client.ErrNotSupportedAuthorizationState + _, err := c.RegisterUser(&client.RegisterUserRequest{ + FirstName: <-stateHandler.FirstName, + LastName: <-stateHandler.LastName, + }) + return err case client.TypeAuthorizationStateWaitPassword: _, err := c.CheckAuthenticationPassword(&client.CheckAuthenticationPasswordRequest{ @@ -77,11 +84,14 @@ func (stateHandler *clientAuthorizer) Handle(c *client.Client, state client.Auth } func (stateHandler *clientAuthorizer) Close() { + stateHandler.isClosed = true close(stateHandler.TdlibParameters) close(stateHandler.PhoneNumber) close(stateHandler.Code) close(stateHandler.State) close(stateHandler.Password) + close(stateHandler.FirstName) + close(stateHandler.LastName) } // Connect starts TDlib connection @@ -102,6 +112,8 @@ func (c *Client) Connect(resource string) error { Code: make(chan string, 1), State: make(chan client.AuthorizationState, 10), Password: make(chan string, 1), + FirstName: make(chan string, 1), + LastName: make(chan string, 1), } c.locks.authorizationReady.Add(1) @@ -212,6 +224,10 @@ func (c *Client) interactor() { case client.TypeAuthorizationStateWaitCode: log.Warn("Waiting for authorization code...") gateway.SendMessage(c.jid, "", "Please, enter authorization code via /code 12345", c.xmpp) + // stage 1b: wait for registration + case client.TypeAuthorizationStateWaitRegistration: + log.Warn("Waiting for full name...") + gateway.SendMessage(c.jid, "", "This number is not registered yet! Please, enter your name via /setname John Doe", c.xmpp) // stage 2: wait for 2fa case client.TypeAuthorizationStateWaitPassword: log.Warn("Waiting for 2FA password...") From ddc9c8ff76ee6675e138250763903cffe8899ed7 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 15 Aug 2022 06:51:09 -0400 Subject: [PATCH 02/51] Make commands not bound to a certain chat available as transport commands too --- telegram/commands.go | 168 ++++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 66 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index 55267ab..95afd8a 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -52,6 +52,10 @@ var transportCommands = map[string]command{ "setpassword": command{"[old] [new]", "set or remove password"}, "config": command{"[param] [value]", "view or update configuration options"}, "report": command{"[chat] [comment]", "report a chat by id or @username"}, + "add": command{"@username", "add @username to your chat list"}, + "join": command{"https://t.me/invite_link", "join to chat via invite link or @publicname"}, + "supergroup": command{"title description", "create new supergroup «title» with «description»"}, + "channel": command{"title description", "create new channel «title» with «description»"}, } var chatCommands = map[string]command{ @@ -395,6 +399,14 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } else { return "Reported" } + case "add": + return c.cmdAdd(args) + case "join": + return c.cmdJoin(args) + case "supergroup": + return c.cmdSupergroup(args, cmdline) + case "channel": + return c.cmdChannel(args, cmdline) case "help": return helpString(helpTypeTransport) } @@ -616,78 +628,16 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } // add @contact case "add": - if len(args) < 1 { - return notEnoughArguments, true - } - - chat, err := c.client.SearchPublicChat(&client.SearchPublicChatRequest{ - Username: args[0], - }) - if err != nil { - return err.Error(), true - } - if chat == nil { - return "No error, but chat is nil", true - } - - c.subscribeToID(chat.Id, chat) + return c.cmdAdd(args), true // join https://t.me/publichat or @publicchat case "join": - if len(args) < 1 { - return notEnoughArguments, true - } - - if strings.HasPrefix(args[0], "@") { - chat, err := c.client.SearchPublicChat(&client.SearchPublicChatRequest{ - Username: args[0], - }) - if err != nil { - return err.Error(), true - } - if chat == nil { - return "No error, but chat is nil", true - } - _, err = c.client.JoinChat(&client.JoinChatRequest{ - ChatId: chat.Id, - }) - if err != nil { - return err.Error(), true - } - } else { - _, err := c.client.JoinChatByInviteLink(&client.JoinChatByInviteLinkRequest{ - InviteLink: args[0], - }) - if err != nil { - return err.Error(), true - } - } + return c.cmdJoin(args), true // create new supergroup case "supergroup": - if len(args) < 1 { - return notEnoughArguments, true - } - - _, err := c.client.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{ - Title: args[0], - Description: rawCmdArguments(cmdline, 1), - }) - if err != nil { - return err.Error(), true - } + return c.cmdSupergroup(args, cmdline), true // create new channel case "channel": - if len(args) < 1 { - return notEnoughArguments, true - } - - _, err := c.client.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{ - Title: args[0], - Description: rawCmdArguments(cmdline, 1), - IsChannel: true, - }) - if err != nil { - return err.Error(), true - } + return c.cmdChannel(args, cmdline), true // create new secret chat with current user case "secret": _, err := c.client.CreateNewSecretChat(&client.CreateNewSecretChatRequest{ @@ -1084,3 +1034,89 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "", true } + +func (c *Client) cmdAdd(args []string) string { + if len(args) < 1 { + return notEnoughArguments + } + + chat, err := c.client.SearchPublicChat(&client.SearchPublicChatRequest{ + Username: args[0], + }) + if err != nil { + return err.Error() + } + if chat == nil { + return "No error, but chat is nil" + } + + c.subscribeToID(chat.Id, chat) + + return "" +} + +func (c *Client) cmdJoin(args []string) string { + if len(args) < 1 { + return notEnoughArguments + } + + if strings.HasPrefix(args[0], "@") { + chat, err := c.client.SearchPublicChat(&client.SearchPublicChatRequest{ + Username: args[0], + }) + if err != nil { + return err.Error() + } + if chat == nil { + return "No error, but chat is nil" + } + _, err = c.client.JoinChat(&client.JoinChatRequest{ + ChatId: chat.Id, + }) + if err != nil { + return err.Error() + } + } else { + _, err := c.client.JoinChatByInviteLink(&client.JoinChatByInviteLinkRequest{ + InviteLink: args[0], + }) + if err != nil { + return err.Error() + } + } + + return "" +} + +func (c *Client) cmdSupergroup(args []string, cmdline string) string { + if len(args) < 1 { + return notEnoughArguments + } + + _, err := c.client.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{ + Title: args[0], + Description: rawCmdArguments(cmdline, 1), + }) + if err != nil { + return err.Error() + } + + return "" +} + +func (c *Client) cmdChannel(args []string, cmdline string) string { + if len(args) < 1 { + return notEnoughArguments + } + + _, err := c.client.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{ + Title: args[0], + Description: rawCmdArguments(cmdline, 1), + IsChannel: true, + }) + if err != nil { + return err.Error() + } + + return "" +} From 326b436fc2f19800b273281cf334ef14a8ad137a Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 15 Aug 2022 06:52:33 -0400 Subject: [PATCH 03/51] Version 1.3.0 --- telegabber.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegabber.go b/telegabber.go index 89b9e4c..2f5a6b3 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -const version string = "1.2.1" +const version string = "1.3.0" var sm *goxmpp.StreamManager var component *goxmpp.Component From 68e3581724f04366c114f267b3264075bc635061 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 13 Jan 2023 03:59:35 -0500 Subject: [PATCH 04/51] Fix a crash on commands to unauthorized transport --- xmpp/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 7c671d9..d5653ce 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -93,8 +93,8 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { gateway.SendPresence(component, msg.From, gateway.SPType("subscribed")) } else { log.Error("Message from stranger") - return } + return } toID, ok := toToID(msg.To) From b3b53b6145c4b8e31d134942507c3ce0226182a4 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 15 Jan 2023 20:35:13 -0500 Subject: [PATCH 05/51] OOB --- persistence/sessions.go | 11 +++++++++++ persistence/sessions_test.go | 2 ++ telegram/utils.go | 25 ++++++++++++++++--------- telegram/utils_test.go | 10 ++++++++-- xmpp/gateway/gateway.go | 15 +++++++++++++++ 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/persistence/sessions.go b/persistence/sessions.go index bc71fd6..ed95f60 100644 --- a/persistence/sessions.go +++ b/persistence/sessions.go @@ -39,6 +39,7 @@ type Session struct { KeepOnline bool `yaml:":keeponline"` RawMessages bool `yaml:":rawmessages"` AsciiArrows bool `yaml:":asciiarrows"` + OOBMode bool `yaml:":oobmode"` } var configKeys = []string{ @@ -46,6 +47,7 @@ var configKeys = []string{ "keeponline", "rawmessages", "asciiarrows", + "oobmode", } var sessionDB *SessionsYamlDB @@ -118,6 +120,8 @@ func (s *Session) Get(key string) (string, error) { return fromBool(s.RawMessages), nil case "asciiarrows": return fromBool(s.AsciiArrows), nil + case "oobmode": + return fromBool(s.OOBMode), nil } return "", errors.New("Unknown session property") @@ -161,6 +165,13 @@ func (s *Session) Set(key string, value string) (string, error) { } s.AsciiArrows = b return value, nil + case "oobmode": + b, err := toBool(value) + if err != nil { + return "", err + } + s.OOBMode = b + return value, nil } return "", errors.New("Unknown session property") diff --git a/persistence/sessions_test.go b/persistence/sessions_test.go index 76f71f9..c8e3077 100644 --- a/persistence/sessions_test.go +++ b/persistence/sessions_test.go @@ -47,6 +47,7 @@ func TestSessionToMap(t *testing.T) { session := Session{ Timezone: "klsf", RawMessages: true, + OOBMode: true, } m := session.ToMap() sample := map[string]string{ @@ -54,6 +55,7 @@ func TestSessionToMap(t *testing.T) { "keeponline": "false", "rawmessages": "true", "asciiarrows": "false", + "oobmode": "true", } if !reflect.DeepEqual(m, sample) { t.Errorf("Map does not match the sample: %v", m) diff --git a/telegram/utils.go b/telegram/utils.go index 17e9861..1545939 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -350,10 +350,10 @@ func (c *Client) formatForward(fwd *client.MessageForwardInfo) string { return "Unknown forward type" } -func (c *Client) formatFile(file *client.File, compact bool) string { +func (c *Client) formatFile(file *client.File, compact bool) (string, string) { log.Debugf("file: %#v", file) if file == nil || file.Local == nil || file.Remote == nil { - return "" + return "", "" } gateway.StorageLock.Lock() @@ -367,7 +367,7 @@ func (c *Client) formatFile(file *client.File, compact bool) string { _, err := os.Stat(src) if err != nil { log.Errorf("Cannot access source file: %v", err) - return "" + return "", "" } size64 := uint64(file.Size) @@ -385,7 +385,7 @@ func (c *Client) formatFile(file *client.File, compact bool) string { log.Warn(err.Error()) } else { log.Errorf("File moving error: %v", err) - return "" + return "", "" } } gateway.CachedStorageSize += size64 @@ -410,9 +410,9 @@ func (c *Client) formatFile(file *client.File, compact bool) string { } if compact { - return link + return link, link } else { - return fmt.Sprintf("%s (%v kbytes) | %s", filepath.Base(src), file.Size/1024, link) + return fmt.Sprintf("%s (%v kbytes) | %s", filepath.Base(src), file.Size/1024, link), link } } @@ -749,7 +749,7 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { - var text string + var text, oob string content := message.Content if content != nil && content.MessageContentType() == client.TypeMessageChatChangePhoto { chat, err := c.client.GetChat(&client.GetChatRequest{ @@ -772,7 +772,10 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { preview = c.ensureDownloadFile(preview) var prefix strings.Builder - prefix.WriteString(c.messageToPrefix(message, c.formatFile(preview, true), c.formatFile(file, false))) + previewName, _ := c.formatFile(preview, true) + fileName, link := c.formatFile(file, false) + prefix.WriteString(c.messageToPrefix(message, previewName, fileName)) + oob = link if text != "" { // \n if it is groupchat and message is not empty if chatId < 0 { @@ -786,6 +789,10 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { text = prefix.String() } + + if c.Session.OOBMode && oob != "" { + text = oob + } } // mark message as read @@ -795,7 +802,7 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { ForceRead: true, }) // forward message to XMPP - gateway.SendMessage(c.jid, strconv.FormatInt(chatId, 10), text, c.xmpp) + gateway.SendMessageWithOOB(c.jid, strconv.FormatInt(chatId, 10), text, c.xmpp, oob) } // ProcessOutgoingMessage executes commands or sends messages to mapped chats diff --git a/telegram/utils_test.go b/telegram/utils_test.go index f0140ae..bfd6c49 100644 --- a/telegram/utils_test.go +++ b/telegram/utils_test.go @@ -146,10 +146,13 @@ func TestFormatFile(t *testing.T) { }, } - content := c.formatFile(&file, false) + content, link := c.formatFile(&file, false) if content != ". (23 kbytes) | " { t.Errorf("Wrong file label: %v", content) } + if link != "" { + t.Errorf("Wrong file link: %v", link) + } } func TestFormatPreview(t *testing.T) { @@ -168,10 +171,13 @@ func TestFormatPreview(t *testing.T) { }, } - content := c.formatFile(&file, true) + content, link := c.formatFile(&file, true) if content != "" { t.Errorf("Wrong preview label: %v", content) } + if link != "" { + t.Errorf("Wrong preview link: %v", link) + } } func TestMessageToTextSticker(t *testing.T) { diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index d4620e6..f4ec690 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -28,6 +28,15 @@ var DirtySessions = false // SendMessage creates and sends a message stanza func SendMessage(to string, from string, body string, component *xmpp.Component) { + sendMessageWrapper(to, from, body, component, "") +} + +// SendMessageWithOOB creates and sends a message stanza with OOB URL +func SendMessageWithOOB(to string, from string, body string, component *xmpp.Component, oob string) { + sendMessageWrapper(to, from, body, component, oob) +} + +func sendMessageWrapper(to string, from string, body string, component *xmpp.Component, oob string) { componentJid := Jid.Full() var logFrom string @@ -54,6 +63,12 @@ func SendMessage(to string, from string, body string, component *xmpp.Component) Body: body, } + if oob != "" { + message.Extensions = append(message.Extensions, stanza.OOB{ + URL: oob, + }) + } + sendMessage(&message, component) } From 559d6443e5a1d3654b5542ccaadf6b33537f752d Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 16 Jan 2023 08:02:41 -0500 Subject: [PATCH 06/51] Make OOB compatible with RawMessages --- telegram/utils.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/telegram/utils.go b/telegram/utils.go index 1545939..6878fc6 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -764,34 +764,34 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { text = c.messageToText(message, false) // OTR support (I do not know why would you need it, seriously) - if !(strings.HasPrefix(text, "?OTR") || c.Session.RawMessages) { + if !(strings.HasPrefix(text, "?OTR") || (c.Session.RawMessages && !c.Session.OOBMode)) { file, preview := c.contentToFile(content) // download file and preview (if present) file = c.ensureDownloadFile(file) preview = c.ensureDownloadFile(preview) - var prefix strings.Builder previewName, _ := c.formatFile(preview, true) fileName, link := c.formatFile(file, false) - prefix.WriteString(c.messageToPrefix(message, previewName, fileName)) + oob = link - if text != "" { - // \n if it is groupchat and message is not empty - if chatId < 0 { - prefix.WriteString("\n") - } else if chatId > 0 { - prefix.WriteString(" | ") + if c.Session.OOBMode && oob != "" { + text = oob + } else if !c.Session.RawMessages { + var prefix strings.Builder + prefix.WriteString(c.messageToPrefix(message, previewName, fileName)) + if text != "" { + // \n if it is groupchat and message is not empty + if chatId < 0 { + prefix.WriteString("\n") + } else if chatId > 0 { + prefix.WriteString(" | ") + } + + prefix.WriteString(text) } - - prefix.WriteString(text) + text = prefix.String() } - - text = prefix.String() - } - - if c.Session.OOBMode && oob != "" { - text = oob } } From d3c6360e3ccb7924dd582269d33dcb27932a054e Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Tue, 17 Jan 2023 21:57:38 -0500 Subject: [PATCH 07/51] Separate labels for OOB messages --- telegram/utils.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/telegram/utils.go b/telegram/utils.go index 6878fc6..dabb8a2 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -749,7 +749,7 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { - var text, oob string + var text, oob, auxText string content := message.Content if content != nil && content.MessageContentType() == client.TypeMessageChatChangePhoto { chat, err := c.client.GetChat(&client.GetChatRequest{ @@ -776,6 +776,10 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { oob = link if c.Session.OOBMode && oob != "" { + typ := message.Content.MessageContentType() + if typ != client.TypeMessageSticker { + auxText = text + } text = oob } else if !c.Session.RawMessages { var prefix strings.Builder @@ -803,6 +807,9 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { }) // forward message to XMPP gateway.SendMessageWithOOB(c.jid, strconv.FormatInt(chatId, 10), text, c.xmpp, oob) + if auxText != "" { + gateway.SendMessage(c.jid, strconv.FormatInt(chatId, 10), auxText, c.xmpp) + } } // ProcessOutgoingMessage executes commands or sends messages to mapped chats From d66f87485d2a96ad9723c537c3fd11ee27579104 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Tue, 17 Jan 2023 23:42:10 -0500 Subject: [PATCH 08/51] Print commit hash --- Makefile | 4 +++- telegabber.go | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1f75f76..48c5d7e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ .PHONY: all test +COMMIT := $(shell git rev-parse --short HEAD) + all: - go build -o telegabber + go build -ldflags "-X main.commit=${COMMIT}" -o telegabber test: go test -v ./config ./ ./telegram ./xmpp ./xmpp/gateway ./persistence ./telegram/formatter diff --git a/telegabber.go b/telegabber.go index 2f5a6b3..903a5c5 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,8 @@ import ( goxmpp "gosrc.io/xmpp" ) -const version string = "1.3.0" +var version string = "1.4.0-dev" +var commit string var sm *goxmpp.StreamManager var component *goxmpp.Component @@ -25,6 +26,10 @@ var cleanupDone chan struct{} var sigintChannel chan os.Signal func main() { + if commit != "" { + version = fmt.Sprintf("%v-%v", version, commit) + } + var profilingPort = flag.Int("profiling-port", 0, "The port for pprof server") // YAML config, compatible with the format of Zhabogram 2.0.0 var configPath = flag.String("config", "config.yml", "Config file path") @@ -55,6 +60,8 @@ func main() { SetLogrusLevel(config.XMPP.Loglevel) + log.Infof("Starting telegabber version %v", version) + sm, component, err = xmpp.NewComponent(config.XMPP, config.Telegram) if err != nil { log.Fatal(err) From 18a17cb7a8abe0180bb4b1e8dd1f468956796922 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 23 Jan 2023 02:30:02 -0500 Subject: [PATCH 09/51] Fix crash on login --- telegram/client.go | 2 +- telegram/connect.go | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/telegram/client.go b/telegram/client.go index 49816db..71d8125 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -63,7 +63,7 @@ type Client struct { } type clientLocks struct { - authorizationReady sync.WaitGroup + authorizationReady sync.Mutex chatMessageLocks map[int64]*sync.Mutex resourcesLock sync.Mutex } diff --git a/telegram/connect.go b/telegram/connect.go index ed0a46b..0b9a195 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -96,11 +96,14 @@ func (stateHandler *clientAuthorizer) Close() { // Connect starts TDlib connection func (c *Client) Connect(resource string) error { + log.Warn("Attempting to connect to Telegram network...") + // avoid conflict if another authorization is pending already - c.locks.authorizationReady.Wait() + c.locks.authorizationReady.Lock() if c.Online() { c.roster(resource) + c.locks.authorizationReady.Unlock() return nil } @@ -116,15 +119,13 @@ func (c *Client) Connect(resource string) error { LastName: make(chan string, 1), } - c.locks.authorizationReady.Add(1) - go c.interactor() c.authorizer.TdlibParameters <- c.parameters tdlibClient, err := client.NewClient(c.authorizer, c.options...) if err != nil { - c.locks.authorizationReady.Done() + c.locks.authorizationReady.Unlock() return errors.Wrap(err, "Couldn't initialize a Telegram client instance") } @@ -142,7 +143,7 @@ func (c *Client) Connect(resource string) error { go c.updateHandler() c.online = true - c.locks.authorizationReady.Done() + c.locks.authorizationReady.Unlock() c.addResource(resource) go func() { From b3d66993e5962d2b961631e3afa578967a7b3d8a Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 23 Jan 2023 09:14:53 -0500 Subject: [PATCH 10/51] Version 1.4.0 --- telegabber.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegabber.go b/telegabber.go index 903a5c5..295ec40 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.4.0-dev" +var version string = "1.4.0" var commit string var sm *goxmpp.StreamManager From 6e32c62f8dac5ccc97dd0a6067965ba2689f3c86 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 3 Mar 2023 21:41:45 -0500 Subject: [PATCH 11/51] Assign IDs from Telegram to XMPP messages --- telegram/commands.go | 5 ++++- telegram/connect.go | 8 ++++---- telegram/handlers.go | 4 ++-- telegram/utils.go | 14 +++++++++++--- xmpp/gateway/gateway.go | 11 ++++++----- xmpp/handlers.go | 2 +- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index 95afd8a..957c335 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -183,10 +183,13 @@ func (c *Client) unsubscribe(chatID int64) error { func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) { for i := len(messages) - 1; i >= 0; i-- { + message := messages[i] + gateway.SendMessage( c.jid, strconv.FormatInt(chatID, 10), - c.formatMessage(0, 0, false, messages[i]), + c.formatMessage(0, 0, false, message), + strconv.FormatInt(message.Id, 10), c.xmpp, ) } diff --git a/telegram/connect.go b/telegram/connect.go index 0b9a195..b4957b1 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -219,20 +219,20 @@ func (c *Client) interactor() { 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) + gateway.SendMessage(c.jid, "", "Please, enter your Telegram login via /login 12345", "", c.xmpp) } // stage 1: wait for auth code case client.TypeAuthorizationStateWaitCode: log.Warn("Waiting for authorization code...") - gateway.SendMessage(c.jid, "", "Please, enter authorization code via /code 12345", c.xmpp) + gateway.SendMessage(c.jid, "", "Please, enter authorization code via /code 12345", "", c.xmpp) // stage 1b: wait for registration case client.TypeAuthorizationStateWaitRegistration: log.Warn("Waiting for full name...") - gateway.SendMessage(c.jid, "", "This number is not registered yet! Please, enter your name via /setname John Doe", c.xmpp) + gateway.SendMessage(c.jid, "", "This number is not registered yet! Please, enter your name via /setname John Doe", "", c.xmpp) // stage 2: wait for 2fa case client.TypeAuthorizationStateWaitPassword: log.Warn("Waiting for 2FA password...") - gateway.SendMessage(c.jid, "", "Please, enter 2FA passphrase via /password 12345", c.xmpp) + gateway.SendMessage(c.jid, "", "Please, enter 2FA passphrase via /password 12345", "", c.xmpp) } } } diff --git a/telegram/handlers.go b/telegram/handlers.go index 126d858..517fb54 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -242,7 +242,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { textContent.Text.Entities, markupFunction, )) - gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, c.xmpp) + gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp) } } @@ -256,7 +256,7 @@ func (c *Client) updateDeleteMessages(update *client.UpdateDeleteMessages) { deleteChar = "✗ " } text := deleteChar + strings.Join(int64SliceToStringSlice(update.MessageIds), ",") - gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, c.xmpp) + gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "", c.xmpp) } } diff --git a/telegram/utils.go b/telegram/utils.go index dabb8a2..e58f6bf 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -806,9 +806,11 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { ForceRead: true, }) // forward message to XMPP - gateway.SendMessageWithOOB(c.jid, strconv.FormatInt(chatId, 10), text, c.xmpp, oob) + sId := strconv.FormatInt(message.Id, 10) + sChatId := strconv.FormatInt(chatId, 10) + gateway.SendMessageWithOOB(c.jid, sChatId, text, sId, c.xmpp, oob) if auxText != "" { - gateway.SendMessage(c.jid, strconv.FormatInt(chatId, 10), auxText, c.xmpp) + gateway.SendMessage(c.jid, sChatId, auxText, sId, c.xmpp) } } @@ -823,7 +825,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str // try to execute commands response, isCommand := c.ProcessChatCommand(chatID, text) if response != "" { - gateway.SendMessage(returnJid, strconv.FormatInt(chatID, 10), response, c.xmpp) + gateway.SendMessage(returnJid, strconv.FormatInt(chatID, 10), response, "", c.xmpp) } // do not send on success if isCommand { @@ -849,6 +851,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to fetch the uploaded file: %s", err.Error()), + "", c.xmpp, ) return nil @@ -861,6 +864,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Received status code %v", response.StatusCode), + "", c.xmpp, ) return nil @@ -872,6 +876,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to create a temporary directory: %s", err.Error()), + "", c.xmpp, ) return nil @@ -882,6 +887,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to create a temporary file: %s", err.Error()), + "", c.xmpp, ) return nil @@ -893,6 +899,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to write a temporary file: %s", err.Error()), + "", c.xmpp, ) return nil @@ -943,6 +950,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Not sent: %s", err.Error()), + "", c.xmpp, ) } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index f4ec690..daa8f8a 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -27,16 +27,16 @@ var Jid *stanza.Jid var DirtySessions = false // SendMessage creates and sends a message stanza -func SendMessage(to string, from string, body string, component *xmpp.Component) { - sendMessageWrapper(to, from, body, component, "") +func SendMessage(to string, from string, body string, id string, component *xmpp.Component) { + sendMessageWrapper(to, from, body, id, component, "") } // SendMessageWithOOB creates and sends a message stanza with OOB URL -func SendMessageWithOOB(to string, from string, body string, component *xmpp.Component, oob string) { - sendMessageWrapper(to, from, body, component, oob) +func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, oob string) { + sendMessageWrapper(to, from, body, id, component, oob) } -func sendMessageWrapper(to string, from string, body string, component *xmpp.Component, oob string) { +func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, oob string) { componentJid := Jid.Full() var logFrom string @@ -59,6 +59,7 @@ func sendMessageWrapper(to string, from string, body string, component *xmpp.Com From: messageFrom, To: to, Type: "chat", + Id: id, }, Body: body, } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index d5653ce..d83fef4 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -106,7 +106,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { if err == nil && toJid.Bare() == gatewayJid && (strings.HasPrefix(msg.Body, "/") || strings.HasPrefix(msg.Body, "!")) { response := session.ProcessTransportCommand(msg.Body, resource) if response != "" { - gateway.SendMessage(msg.From, "", response, component) + gateway.SendMessage(msg.From, "", response, "", component) } return } From 4a5b83dff5c568871d5624202e2ee27e5d0de242 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 5 Mar 2023 03:00:53 -0500 Subject: [PATCH 12/51] Show XEP-0461 replies from Telegram --- telegram/commands.go | 2 + telegram/connect.go | 8 +-- telegram/handlers.go | 4 +- telegram/utils.go | 99 ++++++++++++++++++++--------------- telegram/utils_test.go | 62 ++++++++++++++++++++-- xmpp/extensions/extensions.go | 18 +++++++ xmpp/gateway/gateway.go | 31 +++++++++-- xmpp/handlers.go | 2 +- 8 files changed, 168 insertions(+), 58 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index 957c335..943d071 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -184,6 +184,7 @@ func (c *Client) unsubscribe(chatID int64) error { func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) { for i := len(messages) - 1; i >= 0; i-- { message := messages[i] + reply, _ := c.getMessageReply(message) gateway.SendMessage( c.jid, @@ -191,6 +192,7 @@ func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) { c.formatMessage(0, 0, false, message), strconv.FormatInt(message.Id, 10), c.xmpp, + reply, ) } } diff --git a/telegram/connect.go b/telegram/connect.go index b4957b1..2633980 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -219,20 +219,20 @@ func (c *Client) interactor() { 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) + gateway.SendServiceMessage(c.jid, "Please, enter your Telegram login via /login 12345", c.xmpp) } // stage 1: wait for auth code case client.TypeAuthorizationStateWaitCode: log.Warn("Waiting for authorization code...") - gateway.SendMessage(c.jid, "", "Please, enter authorization code via /code 12345", "", c.xmpp) + gateway.SendServiceMessage(c.jid, "Please, enter authorization code via /code 12345", c.xmpp) // stage 1b: wait for registration case client.TypeAuthorizationStateWaitRegistration: log.Warn("Waiting for full name...") - gateway.SendMessage(c.jid, "", "This number is not registered yet! Please, enter your name via /setname John Doe", "", c.xmpp) + gateway.SendServiceMessage(c.jid, "This number is not registered yet! Please, enter your name via /setname John Doe", c.xmpp) // stage 2: wait for 2fa case client.TypeAuthorizationStateWaitPassword: log.Warn("Waiting for 2FA password...") - gateway.SendMessage(c.jid, "", "Please, enter 2FA passphrase via /password 12345", "", c.xmpp) + gateway.SendServiceMessage(c.jid, "Please, enter 2FA passphrase via /password 12345", c.xmpp) } } } diff --git a/telegram/handlers.go b/telegram/handlers.go index 517fb54..307562a 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -242,7 +242,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { textContent.Text.Entities, markupFunction, )) - gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp) + gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp, nil) } } @@ -256,7 +256,7 @@ func (c *Client) updateDeleteMessages(update *client.UpdateDeleteMessages) { deleteChar = "✗ " } text := deleteChar + strings.Join(int64SliceToStringSlice(update.MessageIds), ",") - gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "", c.xmpp) + gateway.SendTextMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, c.xmpp) } } diff --git a/telegram/utils.go b/telegram/utils.go index e58f6bf..f06abd7 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -261,6 +261,46 @@ func (c *Client) formatContact(chatID int64) string { return str } +func (c *Client) getSenderId(message *client.Message) (senderId int64) { + if message.SenderId != nil { + switch message.SenderId.MessageSenderType() { + case client.TypeMessageSenderUser: + senderUser, _ := message.SenderId.(*client.MessageSenderUser) + senderId = senderUser.UserId + case client.TypeMessageSenderChat: + senderChat, _ := message.SenderId.(*client.MessageSenderChat) + senderId = senderChat.ChatId + } + } + + return +} + +func (c *Client) formatSender(message *client.Message) string { + return c.formatContact(c.getSenderId(message)) +} + +func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply, replyMsg *client.Message) { + if message.ReplyToMessageId != 0 { + var err error + replyMsg, err = c.client.GetMessage(&client.GetMessageRequest{ + ChatId: message.ChatId, + MessageId: message.ReplyToMessageId, + }) + if err != nil { + log.Errorf("", err.Error()) + return + } + + reply = &gateway.Reply { + Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()), + Id: strconv.FormatInt(message.ReplyToMessageId, 10), + } + } + + return +} + func (c *Client) formatMessage(chatID int64, messageID int64, preview bool, message *client.Message) string { var err error if message == nil { @@ -279,18 +319,7 @@ func (c *Client) formatMessage(chatID int64, messageID int64, preview bool, mess var str strings.Builder // add messageid and sender - var senderId int64 - if message.SenderId != nil { - switch message.SenderId.MessageSenderType() { - case client.TypeMessageSenderUser: - senderUser, _ := message.SenderId.(*client.MessageSenderUser) - senderId = senderUser.UserId - case client.TypeMessageSenderChat: - senderChat, _ := message.SenderId.(*client.MessageSenderChat) - senderId = senderChat.ChatId - } - } - str.WriteString(fmt.Sprintf("%v | %s | ", message.Id, c.formatContact(senderId))) + str.WriteString(fmt.Sprintf("%v | %s | ", message.Id, c.formatSender(message))) // add date if !preview { str.WriteString( @@ -681,7 +710,7 @@ func (c *Client) contentToFile(content client.MessageContent) (*client.File, *cl return nil, nil } -func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string) string { +func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) string { prefix := []string{} // message direction var directionChar string @@ -700,21 +729,12 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string, } prefix = append(prefix, directionChar+strconv.FormatInt(message.Id, 10)) // show sender in group chats - if message.ChatId < 0 && message.SenderId != nil { - var senderId int64 - switch message.SenderId.MessageSenderType() { - case client.TypeMessageSenderUser: - senderUser, _ := message.SenderId.(*client.MessageSenderUser) - senderId = senderUser.UserId - case client.TypeMessageSenderChat: - senderChat, _ := message.SenderId.(*client.MessageSenderChat) - senderId = senderChat.ChatId - } - prefix = append(prefix, c.formatContact(senderId)) + if message.ChatId < 0 { + prefix = append(prefix, c.formatSender(message)) } // reply to if message.ReplyToMessageId != 0 { - prefix = append(prefix, "reply: "+c.formatMessage(message.ChatId, message.ReplyToMessageId, true, nil)) + prefix = append(prefix, "reply: "+c.formatMessage(message.ChatId, message.ReplyToMessageId, true, replyMsg)) } if message.ForwardInfo != nil { prefix = append(prefix, "fwd: "+c.formatForward(message.ForwardInfo)) @@ -750,6 +770,9 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { var text, oob, auxText string + + reply, replyMsg := c.getMessageReply(message) + content := message.Content if content != nil && content.MessageContentType() == client.TypeMessageChatChangePhoto { chat, err := c.client.GetChat(&client.GetChatRequest{ @@ -783,7 +806,7 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { text = oob } else if !c.Session.RawMessages { var prefix strings.Builder - prefix.WriteString(c.messageToPrefix(message, previewName, fileName)) + prefix.WriteString(c.messageToPrefix(message, previewName, fileName, replyMsg)) if text != "" { // \n if it is groupchat and message is not empty if chatId < 0 { @@ -808,9 +831,9 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { // forward message to XMPP sId := strconv.FormatInt(message.Id, 10) sChatId := strconv.FormatInt(chatId, 10) - gateway.SendMessageWithOOB(c.jid, sChatId, text, sId, c.xmpp, oob) + gateway.SendMessageWithOOB(c.jid, sChatId, text, sId, c.xmpp, reply, oob) if auxText != "" { - gateway.SendMessage(c.jid, sChatId, auxText, sId, c.xmpp) + gateway.SendMessage(c.jid, sChatId, auxText, sId, c.xmpp, reply) } } @@ -825,7 +848,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str // try to execute commands response, isCommand := c.ProcessChatCommand(chatID, text) if response != "" { - gateway.SendMessage(returnJid, strconv.FormatInt(chatID, 10), response, "", c.xmpp) + gateway.SendTextMessage(returnJid, strconv.FormatInt(chatID, 10), response, c.xmpp) } // do not send on success if isCommand { @@ -847,11 +870,10 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str if chatID != 0 && c.content.Upload != "" && strings.HasPrefix(text, c.content.Upload) { response, err := http.Get(text) if err != nil { - gateway.SendMessage( + gateway.SendTextMessage( returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to fetch the uploaded file: %s", err.Error()), - "", c.xmpp, ) return nil @@ -860,11 +882,10 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str defer response.Body.Close() if response.StatusCode != 200 { - gateway.SendMessage( + gateway.SendTextMessage( returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Received status code %v", response.StatusCode), - "", c.xmpp, ) return nil @@ -872,22 +893,20 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str tempDir, err := ioutil.TempDir("", "telegabber-*") if err != nil { - gateway.SendMessage( + gateway.SendTextMessage( returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to create a temporary directory: %s", err.Error()), - "", c.xmpp, ) return nil } tempFile, err := os.Create(filepath.Join(tempDir, filepath.Base(text))) if err != nil { - gateway.SendMessage( + gateway.SendTextMessage( returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to create a temporary file: %s", err.Error()), - "", c.xmpp, ) return nil @@ -895,11 +914,10 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str _, err = io.Copy(tempFile, response.Body) if err != nil { - gateway.SendMessage( + gateway.SendTextMessage( returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Failed to write a temporary file: %s", err.Error()), - "", c.xmpp, ) return nil @@ -946,11 +964,10 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str InputMessageContent: message, }) if err != nil { - gateway.SendMessage( + gateway.SendTextMessage( returnJid, strconv.FormatInt(chatID, 10), fmt.Sprintf("Not sent: %s", err.Error()), - "", c.xmpp, ) } diff --git a/telegram/utils_test.go b/telegram/utils_test.go index bfd6c49..03ab8bd 100644 --- a/telegram/utils_test.go +++ b/telegram/utils_test.go @@ -389,7 +389,7 @@ func TestMessageToPrefix1(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "", "") + prefix := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "", "", nil) if prefix != "➡ 42 | fwd: ziz" { t.Errorf("Wrong prefix: %v", prefix) } @@ -404,7 +404,7 @@ func TestMessageToPrefix2(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "y.jpg", "") + prefix := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "y.jpg", "", nil) if prefix != "⬅ 56 | fwd: (zaz) | preview: y.jpg" { t.Errorf("Wrong prefix: %v", prefix) } @@ -419,7 +419,7 @@ func TestMessageToPrefix3(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "a.jpg") + prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "a.jpg", nil) if prefix != "< 56 | fwd: (zuz) | file: a.jpg" { t.Errorf("Wrong prefix: %v", prefix) } @@ -430,7 +430,7 @@ func TestMessageToPrefix4(t *testing.T) { Id: 23, IsOutgoing: true, } - prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "") + prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "", nil) if prefix != "> 23" { t.Errorf("Wrong prefix: %v", prefix) } @@ -445,8 +445,60 @@ func TestMessageToPrefix5(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "h.jpg", "a.jpg") + prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "h.jpg", "a.jpg", nil) if prefix != "< 560 | fwd: (zyz) | preview: h.jpg | file: a.jpg" { t.Errorf("Wrong prefix: %v", prefix) } } + +func TestMessageToPrefix6(t *testing.T) { + message := client.Message{ + Id: 23, + IsOutgoing: true, + ReplyToMessageId: 42, + } + reply := client.Message{ + Id: 42, + Content: &client.MessageText{ + Text: &client.FormattedText{ + Text: "tist", + }, + }, + } + prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "", &reply) + if prefix != "> 23 | reply: 42 | | tist" { + t.Errorf("Wrong prefix: %v", prefix) + } +} + +func GetSenderIdEmpty(t *testing.T) { + message := client.Message{} + senderId := (&Client{}).getSenderId(&message) + if senderId != 0 { + t.Errorf("Wrong sender id: %v", senderId) + } +} + +func GetSenderIdUser(t *testing.T) { + message := client.Message{ + SenderId: &client.MessageSenderUser{ + UserId: 42, + }, + } + senderId := (&Client{}).getSenderId(&message) + if senderId != 42 { + t.Errorf("Wrong sender id: %v", senderId) + } +} + +func GetSenderIdChat(t *testing.T) { + message := client.Message{ + SenderId: &client.MessageSenderChat{ + ChatId: -42, + }, + } + senderId := (&Client{}).getSenderId(&message) + if senderId != -42 { + t.Errorf("Wrong sender id: %v", senderId) + } +} diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index fac5e7b..ce27656 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -111,6 +111,13 @@ type IqVcardDesc struct { Text string `xml:",chardata"` } +// Reply is from XEP-0461 +type Reply struct { + XMLName xml.Name `xml:"urn:xmpp:reply:0 reply"` + To string `xml:"to,attr"` + Id string `xml:"id,attr"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space @@ -131,6 +138,11 @@ func (c IqVcardTemp) GetSet() *stanza.ResultSet { return c.ResultSet } +// Namespace is a namespace! +func (c Reply) Namespace() string { + return c.XMLName.Space +} + func init() { // presence nick stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{ @@ -149,4 +161,10 @@ func init() { "vcard-temp", "vCard", }, IqVcardTemp{}) + + // reply + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:reply:0", + "reply", + }, Reply{}) } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index daa8f8a..d309ade 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -13,6 +13,11 @@ import ( "gosrc.io/xmpp/stanza" ) +type Reply struct { + Author string + Id string +} + const NSNick string = "http://jabber.org/protocol/nick" // Queue stores presences to send later @@ -27,16 +32,26 @@ var Jid *stanza.Jid var DirtySessions = false // SendMessage creates and sends a message stanza -func SendMessage(to string, from string, body string, id string, component *xmpp.Component) { - sendMessageWrapper(to, from, body, id, component, "") +func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply) { + sendMessageWrapper(to, from, body, id, component, reply, "") +} + +// SendServiceMessage creates and sends a simple message stanza from transport +func SendServiceMessage(to string, body string, component *xmpp.Component) { + sendMessageWrapper(to, "", body, "", component, nil, "") +} + +// SendTextMessage creates and sends a simple message stanza +func SendTextMessage(to string, from string, body string, component *xmpp.Component) { + sendMessageWrapper(to, from, body, "", component, nil, "") } // SendMessageWithOOB creates and sends a message stanza with OOB URL -func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, oob string) { - sendMessageWrapper(to, from, body, id, component, oob) +func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string) { + sendMessageWrapper(to, from, body, id, component, reply, oob) } -func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, oob string) { +func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string) { componentJid := Jid.Full() var logFrom string @@ -69,6 +84,12 @@ func sendMessageWrapper(to string, from string, body string, id string, componen URL: oob, }) } + if reply != nil { + message.Extensions = append(message.Extensions, extensions.Reply{ + To: reply.Author, + Id: reply.Id, + }) + } sendMessage(&message, component) } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index d83fef4..b023bc5 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -106,7 +106,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { if err == nil && toJid.Bare() == gatewayJid && (strings.HasPrefix(msg.Body, "/") || strings.HasPrefix(msg.Body, "!")) { response := session.ProcessTransportCommand(msg.Body, resource) if response != "" { - gateway.SendMessage(msg.From, "", response, "", component) + gateway.SendServiceMessage(msg.From, response, component) } return } From b1135b070b751998cb98eded6d29bdd7b800ecb9 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Wed, 8 Mar 2023 07:51:19 -0500 Subject: [PATCH 13/51] Cut fallback quotes out --- telegram/utils.go | 37 ++++++++++++++++++++------- xmpp/extensions/extensions.go | 47 +++++++++++++++++++++++++++++++++++ xmpp/gateway/gateway.go | 5 ++++ 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/telegram/utils.go b/telegram/utils.go index f06abd7..fe6ca14 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -30,6 +30,7 @@ var spaceRegex = regexp.MustCompile(`\s+`) var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n") const newlineChar string = "\n" +const messageHeaderSeparator string = " | " // GetContactByUsername resolves username to user id retrieves user and chat information func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) { @@ -710,7 +711,15 @@ func (c *Client) contentToFile(content client.MessageContent) (*client.File, *cl return nil, nil } -func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) string { +func (c *Client) countCharsInLines(lines *[]string) (count int) { + for _, line := range *lines { + count += len(line) + } + return +} + +func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) (string, int, int) { + var replyStart, replyEnd int prefix := []string{} // message direction var directionChar string @@ -734,7 +743,10 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string, } // reply to if message.ReplyToMessageId != 0 { - prefix = append(prefix, "reply: "+c.formatMessage(message.ChatId, message.ReplyToMessageId, true, replyMsg)) + replyStart = c.countCharsInLines(&prefix) + (len(prefix) - 1) * len(messageHeaderSeparator) + replyLine := "reply: "+c.formatMessage(message.ChatId, message.ReplyToMessageId, true, replyMsg) + prefix = append(prefix, replyLine) + replyEnd = replyStart + len(replyLine) + len(messageHeaderSeparator) } if message.ForwardInfo != nil { prefix = append(prefix, "fwd: "+c.formatForward(message.ForwardInfo)) @@ -748,7 +760,7 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string, prefix = append(prefix, "file: "+fileString) } - return strings.Join(prefix, " | ") + return strings.Join(prefix, messageHeaderSeparator), replyStart, replyEnd } func (c *Client) ensureDownloadFile(file *client.File) *client.File { @@ -805,19 +817,26 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { } text = oob } else if !c.Session.RawMessages { - var prefix strings.Builder - prefix.WriteString(c.messageToPrefix(message, previewName, fileName, replyMsg)) + var newText strings.Builder + + prefix, replyStart, replyEnd := c.messageToPrefix(message, previewName, fileName, replyMsg) + newText.WriteString(prefix) + if reply != nil { + reply.Start = uint64(replyStart) + reply.End = uint64(replyEnd) + } + if text != "" { // \n if it is groupchat and message is not empty if chatId < 0 { - prefix.WriteString("\n") + newText.WriteString("\n") } else if chatId > 0 { - prefix.WriteString(" | ") + newText.WriteString(" | ") } - prefix.WriteString(text) + newText.WriteString(text) } - text = prefix.String() + text = newText.String() } } } diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index ce27656..7e44bae 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -2,6 +2,7 @@ package extensions import ( "encoding/xml" + "strconv" "gosrc.io/xmpp/stanza" ) @@ -118,6 +119,28 @@ type Reply struct { Id string `xml:"id,attr"` } +// Fallback is from XEP-0428 +type Fallback struct { + XMLName xml.Name `xml:"urn:xmpp:fallback:0 fallback"` + For string `xml:"for,attr"` + Body []FallbackBody + Subject []FallbackSubject +} + +// FallbackBody is from XEP-0428 +type FallbackBody struct { + XMLName xml.Name `xml:"body"` + Start string `xml:"start,attr"` + End string `xml:"end,attr"` +} + +// FallbackSubject is from XEP-0428 +type FallbackSubject struct { + XMLName xml.Name `xml:"subject"` + Start string `xml:"start,attr"` + End string `xml:"end,attr"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space @@ -143,6 +166,24 @@ func (c Reply) Namespace() string { return c.XMLName.Space } +// Namespace is a namespace! +func (c Fallback) Namespace() string { + return c.XMLName.Space +} + +// NewReplyFallback initializes a fallback range +func NewReplyFallback(start uint64, end uint64) Fallback { + return Fallback{ + For: "urn:xmpp:reply:0", + Body: []FallbackBody{ + FallbackBody{ + Start: strconv.FormatUint(start, 10), + End: strconv.FormatUint(end, 10), + }, + }, + } +} + func init() { // presence nick stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{ @@ -167,4 +208,10 @@ func init() { "urn:xmpp:reply:0", "reply", }, Reply{}) + + // fallback + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:fallback:0", + "fallback", + }, Fallback{}) } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index d309ade..de8b495 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -16,6 +16,8 @@ import ( type Reply struct { Author string Id string + Start uint64 + End uint64 } const NSNick string = "http://jabber.org/protocol/nick" @@ -89,6 +91,9 @@ func sendMessageWrapper(to string, from string, body string, id string, componen To: reply.Author, Id: reply.Id, }) + if reply.End > 0 { + message.Extensions = append(message.Extensions, extensions.NewReplyFallback(reply.Start, reply.End)) + } } sendMessage(&message, component) From 7bd165a738d6c39d2f9e530c67118f8c639ffb0b Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Wed, 8 Mar 2023 08:39:43 -0500 Subject: [PATCH 14/51] Fix tests --- telegram/utils_test.go | 48 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/telegram/utils_test.go b/telegram/utils_test.go index 03ab8bd..91002ee 100644 --- a/telegram/utils_test.go +++ b/telegram/utils_test.go @@ -389,10 +389,16 @@ func TestMessageToPrefix1(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "", "", nil) + prefix, replyStart, replyEnd := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "", "", nil) if prefix != "➡ 42 | fwd: ziz" { t.Errorf("Wrong prefix: %v", prefix) } + if replyStart != 0 { + t.Errorf("Wrong replyStart: %v", replyStart) + } + if replyEnd != 0 { + t.Errorf("Wrong replyEnd: %v", replyEnd) + } } func TestMessageToPrefix2(t *testing.T) { @@ -404,10 +410,16 @@ func TestMessageToPrefix2(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "y.jpg", "", nil) + prefix, replyStart, replyEnd := (&Client{Session: &persistence.Session{}}).messageToPrefix(&message, "y.jpg", "", nil) if prefix != "⬅ 56 | fwd: (zaz) | preview: y.jpg" { t.Errorf("Wrong prefix: %v", prefix) } + if replyStart != 0 { + t.Errorf("Wrong replyStart: %v", replyStart) + } + if replyEnd != 0 { + t.Errorf("Wrong replyEnd: %v", replyEnd) + } } func TestMessageToPrefix3(t *testing.T) { @@ -419,10 +431,16 @@ func TestMessageToPrefix3(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "a.jpg", nil) + prefix, replyStart, replyEnd := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "a.jpg", nil) if prefix != "< 56 | fwd: (zuz) | file: a.jpg" { t.Errorf("Wrong prefix: %v", prefix) } + if replyStart != 0 { + t.Errorf("Wrong replyStart: %v", replyStart) + } + if replyEnd != 0 { + t.Errorf("Wrong replyEnd: %v", replyEnd) + } } func TestMessageToPrefix4(t *testing.T) { @@ -430,10 +448,16 @@ func TestMessageToPrefix4(t *testing.T) { Id: 23, IsOutgoing: true, } - prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "", nil) + prefix, replyStart, replyEnd := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "", nil) if prefix != "> 23" { t.Errorf("Wrong prefix: %v", prefix) } + if replyStart != 0 { + t.Errorf("Wrong replyStart: %v", replyStart) + } + if replyEnd != 0 { + t.Errorf("Wrong replyEnd: %v", replyEnd) + } } func TestMessageToPrefix5(t *testing.T) { @@ -445,10 +469,16 @@ func TestMessageToPrefix5(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "h.jpg", "a.jpg", nil) + prefix, replyStart, replyEnd := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "h.jpg", "a.jpg", nil) if prefix != "< 560 | fwd: (zyz) | preview: h.jpg | file: a.jpg" { t.Errorf("Wrong prefix: %v", prefix) } + if replyStart != 0 { + t.Errorf("Wrong replyStart: %v", replyStart) + } + if replyEnd != 0 { + t.Errorf("Wrong replyEnd: %v", replyEnd) + } } func TestMessageToPrefix6(t *testing.T) { @@ -465,10 +495,16 @@ func TestMessageToPrefix6(t *testing.T) { }, }, } - prefix := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "", &reply) + prefix, replyStart, replyEnd := (&Client{Session: &persistence.Session{AsciiArrows: true}}).messageToPrefix(&message, "", "", &reply) if prefix != "> 23 | reply: 42 | | tist" { t.Errorf("Wrong prefix: %v", prefix) } + if replyStart != 4 { + t.Errorf("Wrong replyStart: %v", replyStart) + } + if replyEnd != 26 { + t.Errorf("Wrong replyEnd: %v", replyEnd) + } } func GetSenderIdEmpty(t *testing.T) { From 90807b2d9e0565629a913d3b28b09c5fc9d746e6 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Tue, 14 Mar 2023 17:16:02 -0400 Subject: [PATCH 15/51] Convert XEP-0461 replies to Telegram --- telegabber.go | 2 +- telegram/commands.go | 6 ++--- telegram/utils.go | 14 +++++++----- xmpp/extensions/extensions.go | 12 +++++----- xmpp/handlers.go | 41 ++++++++++++++++++++++++++++++++++- 5 files changed, 59 insertions(+), 16 deletions(-) diff --git a/telegabber.go b/telegabber.go index 295ec40..0554cb9 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.4.0" +var version string = "1.5.0-dev" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index 943d071..160e486 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -488,7 +488,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "Last message is empty", true } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "") + content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", 0) if content != nil { c.client.EditMessageText(&client.EditMessageTextRequest{ @@ -505,7 +505,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "Not enough arguments", true } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "") + content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", 0) if content != nil { _, err := c.client.SendMessage(&client.SendMessageRequest{ @@ -584,7 +584,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 1), "") + content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 1), "", 0) if content != nil { _, err := c.client.SendMessage(&client.SendMessageRequest{ diff --git a/telegram/utils.go b/telegram/utils.go index fe6ca14..68dd524 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -857,7 +857,7 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { } // ProcessOutgoingMessage executes commands or sends messages to mapped chats -func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string) client.InputMessageContent { +func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, replyId int64) client.InputMessageContent { if !c.Online() { // we're offline return nil @@ -879,9 +879,13 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str // quotations var reply int64 - replySlice := replyRegex.FindStringSubmatch(text) - if len(replySlice) > 1 { - reply, _ = strconv.ParseInt(replySlice[1], 10, 64) + if replyId == 0 { + replySlice := replyRegex.FindStringSubmatch(text) + if len(replySlice) > 1 { + reply, _ = strconv.ParseInt(replySlice[1], 10, 64) + } + } else { + reply = replyId } // attach a file @@ -949,7 +953,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str } // remove first line from text - if file != nil || reply != 0 { + if file != nil || (reply != 0 && replyId == 0) { newlinePos := strings.Index(text, newlineChar) if newlinePos != -1 { text = text[newlinePos+1:] diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 7e44bae..c982581 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -121,22 +121,22 @@ type Reply struct { // Fallback is from XEP-0428 type Fallback struct { - XMLName xml.Name `xml:"urn:xmpp:fallback:0 fallback"` - For string `xml:"for,attr"` - Body []FallbackBody - Subject []FallbackSubject + XMLName xml.Name `xml:"urn:xmpp:fallback:0 fallback"` + For string `xml:"for,attr"` + Body []FallbackBody `xml:"urn:xmpp:fallback:0 body"` + Subject []FallbackSubject `xml:"urn:xmpp:fallback:0 subject"` } // FallbackBody is from XEP-0428 type FallbackBody struct { - XMLName xml.Name `xml:"body"` + XMLName xml.Name `xml:"urn:xmpp:fallback:0 body"` Start string `xml:"start,attr"` End string `xml:"end,attr"` } // FallbackSubject is from XEP-0428 type FallbackSubject struct { - XMLName xml.Name `xml:"subject"` + XMLName xml.Name `xml:"urn:xmpp:fallback:0 subject"` Start string `xml:"start,attr"` End string `xml:"end,attr"` } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index b023bc5..acf62a2 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -99,7 +99,46 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { toID, ok := toToID(msg.To) if ok { - session.ProcessOutgoingMessage(toID, msg.Body, msg.From) + var reply extensions.Reply + var fallback extensions.Fallback + msg.Get(&reply) + msg.Get(&fallback) + log.Debugf("reply: %#v", reply) + log.Debugf("fallback: %#v", fallback) + + var replyId int64 + var err error + text := msg.Body + if len(reply.Id) > 0 { + id := reply.Id + if id[0] == 'e' { + id = id[1:] + } + replyId, err = strconv.ParseInt(id, 10, 64) + if err != nil { + log.Warn(errors.Wrap(err, "Failed to parse message ID!")) + } + + if replyId != 0 && fallback.For == "urn:xmpp:reply:0" && len(fallback.Body) > 0 { + body := fallback.Body[0] + var start, end int64 + start, err = strconv.ParseInt(body.Start, 10, 64) + if err != nil { + log.WithFields(log.Fields{ + "start": body.Start, + }).Warn(errors.Wrap(err, "Failed to parse fallback start!")) + } + end, err = strconv.ParseInt(body.End, 10, 64) + if err != nil { + log.WithFields(log.Fields{ + "end": body.End, + }).Warn(errors.Wrap(err, "Failed to parse fallback end!")) + } + text = text[:start] + text[end:] + } + } + + session.ProcessOutgoingMessage(toID, text, msg.From, replyId) return } else { toJid, err := stanza.NewJid(msg.To) From 42ed16bf9e9d72bf226045f1f291b9d58e2a6200 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 18 Mar 2023 17:43:11 -0400 Subject: [PATCH 16/51] Simulate carbons --- README.md | 31 ++++++++++++++ persistence/sessions.go | 11 +++++ telegram/commands.go | 5 +++ telegram/handlers.go | 2 +- telegram/utils.go | 74 +++++++++++++++++++++++++++++++-- xmpp/extensions/extensions.go | 77 +++++++++++++++++++++++++++++++++++ xmpp/gateway/gateway.go | 77 +++++++++++++++++++++++++++++++---- xmpp/handlers.go | 34 +++++++++------- 8 files changed, 284 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 0ea9f73..36aa7ca 100644 --- a/README.md +++ b/README.md @@ -142,3 +142,34 @@ server { ``` Finally, update `:upload:` in your config.yml to match `server_name` in nginx config. + +### Carbons ### + +Telegabber needs special privileges according to XEP-0356 to simulate message carbons from the users (to display messages they have sent earlier or via other clients). Example configuration for Prosody: + +``` +modules_enabled = { + [...] + + "privilege"; +} + +[...] + +Component "telegabber.yourdomain.tld" + component_secret = "yourpassword" + modules_enabled = {"privilege"} + +[...] + +VirtualHost "yourdomain.tld" + [...] + + privileged_entities = { + [...] + + ["telegabber.yourdomain.tld"] = { + message = "outgoing"; + }, + } +``` diff --git a/persistence/sessions.go b/persistence/sessions.go index ed95f60..c0750a3 100644 --- a/persistence/sessions.go +++ b/persistence/sessions.go @@ -40,6 +40,7 @@ type Session struct { RawMessages bool `yaml:":rawmessages"` AsciiArrows bool `yaml:":asciiarrows"` OOBMode bool `yaml:":oobmode"` + Carbons bool `yaml:":carbons"` } var configKeys = []string{ @@ -48,6 +49,7 @@ var configKeys = []string{ "rawmessages", "asciiarrows", "oobmode", + "carbons", } var sessionDB *SessionsYamlDB @@ -122,6 +124,8 @@ func (s *Session) Get(key string) (string, error) { return fromBool(s.AsciiArrows), nil case "oobmode": return fromBool(s.OOBMode), nil + case "carbons": + return fromBool(s.Carbons), nil } return "", errors.New("Unknown session property") @@ -172,6 +176,13 @@ func (s *Session) Set(key string, value string) (string, error) { } s.OOBMode = b return value, nil + case "carbons": + b, err := toBool(value) + if err != nil { + return "", err + } + s.Carbons = b + return value, nil } return "", errors.New("Unknown session property") diff --git a/telegram/commands.go b/telegram/commands.go index 160e486..368a461 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -193,6 +193,7 @@ func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) { strconv.FormatInt(message.Id, 10), c.xmpp, reply, + false, ) } } @@ -361,6 +362,10 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } case "config": if len(args) > 1 { + if !gateway.MessageOutgoingPermission && args[0] == "carbons" && args[1] == "true" { + return "The server did not allow to enable carbons" + } + value, err := c.Session.Set(args[0], args[1]) if err != nil { return err.Error() diff --git a/telegram/handlers.go b/telegram/handlers.go index 307562a..84e3748 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -242,7 +242,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { textContent.Text.Entities, markupFunction, )) - gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp, nil) + gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false) } } diff --git a/telegram/utils.go b/telegram/utils.go index 68dd524..99492e1 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -109,6 +109,33 @@ func (c *Client) GetContactByID(id int64, chat *client.Chat) (*client.Chat, *cli return chat, user, nil } +// IsPM checks if a chat is PM +func (c *Client) IsPM(id int64) (bool, error) { + if !c.Online() || id == 0 { + return false, errOffline + } + + var err error + + chat, ok := c.cache.GetChat(id) + if !ok { + chat, err = c.client.GetChat(&client.GetChatRequest{ + ChatId: id, + }) + if err != nil { + return false, err + } + + c.cache.SetChat(id, chat) + } + + chatType := chat.Type.ChatTypeType() + if chatType == client.TypeChatTypePrivate || chatType == client.TypeChatTypeSecret { + return true, nil + } + return false, nil +} + func (c *Client) userStatusToText(status client.UserStatus, chatID int64) (string, string, string) { var show, textStatus, presenceType string @@ -782,6 +809,7 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { var text, oob, auxText string + var err error reply, replyMsg := c.getMessageReply(message) @@ -847,12 +875,33 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { MessageIds: []int64{message.Id}, ForceRead: true, }) + // forward message to XMPP sId := strconv.FormatInt(message.Id, 10) sChatId := strconv.FormatInt(chatId, 10) - gateway.SendMessageWithOOB(c.jid, sChatId, text, sId, c.xmpp, reply, oob) - if auxText != "" { - gateway.SendMessage(c.jid, sChatId, auxText, sId, c.xmpp, reply) + + var jids []string + var isPM bool + if gateway.MessageOutgoingPermission && c.Session.Carbons { + isPM, err = c.IsPM(chatId) + if err != nil { + log.Errorf("Could not determine if chat is PM: %v", err) + } + } + isOutgoing := isPM && message.IsOutgoing + if isOutgoing { + for resource := range c.resourcesRange() { + jids = append(jids, c.jid + "/" + resource) + } + } else { + jids = []string{c.jid} + } + + for _, jid := range jids { + gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isOutgoing) + if auxText != "" { + gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, isOutgoing) + } } } @@ -1024,6 +1073,25 @@ func (c *Client) deleteResource(resource string) { } } +func (c *Client) resourcesRange() chan string { + c.locks.resourcesLock.Lock() + + resourceChan := make(chan string, 1) + + go func() { + defer func() { + c.locks.resourcesLock.Unlock() + close(resourceChan) + }() + + for resource := range c.resources { + resourceChan <- resource + } + }() + + return resourceChan +} + // resend statuses to (to another resource, for example) func (c *Client) roster(resource string) { if _, ok := c.resources[resource]; ok { diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index c982581..2cf7d45 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -141,6 +141,45 @@ type FallbackSubject struct { End string `xml:"end,attr"` } +// CarbonReceived is from XEP-0280 +type CarbonReceived struct { + XMLName xml.Name `xml:"urn:xmpp:carbons:2 received"` + Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"` +} + +// CarbonSent is from XEP-0280 +type CarbonSent struct { + XMLName xml.Name `xml:"urn:xmpp:carbons:2 sent"` + Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"` +} + +// ComponentPrivilege is from XEP-0356 +type ComponentPrivilege struct { + XMLName xml.Name `xml:"urn:xmpp:privilege:1 privilege"` + Perms []ComponentPerm `xml:"perm"` + Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"` +} + +// ComponentPerm is from XEP-0356 +type ComponentPerm struct { + XMLName xml.Name `xml:"perm"` + Access string `xml:"access,attr"` + Type string `xml:"type,attr"` + Push bool `xml:"push,attr"` +} + +// ClientMessage is a jabber:client NS message compatible with Prosody's XEP-0356 implementation +type ClientMessage struct { + XMLName xml.Name `xml:"jabber:client message"` + stanza.Attrs + + Subject string `xml:"subject,omitempty"` + Body string `xml:"body,omitempty"` + Thread string `xml:"thread,omitempty"` + Error stanza.Err `xml:"error,omitempty"` + Extensions []stanza.MsgExtension `xml:",omitempty"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space @@ -171,6 +210,26 @@ func (c Fallback) Namespace() string { return c.XMLName.Space } +// Namespace is a namespace! +func (c CarbonReceived) Namespace() string { + return c.XMLName.Space +} + +// Namespace is a namespace! +func (c CarbonSent) Namespace() string { + return c.XMLName.Space +} + +// Namespace is a namespace! +func (c ComponentPrivilege) Namespace() string { + return c.XMLName.Space +} + +// Name is a packet name +func (ClientMessage) Name() string { + return "message" +} + // NewReplyFallback initializes a fallback range func NewReplyFallback(start uint64, end uint64) Fallback { return Fallback{ @@ -214,4 +273,22 @@ func init() { "urn:xmpp:fallback:0", "fallback", }, Fallback{}) + + // carbon received + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:carbons:2", + "received", + }, CarbonReceived{}) + + // carbon sent + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:carbons:2", + "sent", + }, CarbonSent{}) + + // component privilege + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:privilege:1", + "privilege", + }, ComponentPrivilege{}) } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index de8b495..534ee7e 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -2,6 +2,7 @@ package gateway import ( "encoding/xml" + "github.com/pkg/errors" "strings" "sync" @@ -33,31 +34,44 @@ var Jid *stanza.Jid // were changed and need to be re-flushed to the YamlDB var DirtySessions = false +// MessageOutgoingPermission allows to fake outgoing messages by foreign JIDs +var MessageOutgoingPermission = false + // SendMessage creates and sends a message stanza -func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply) { - sendMessageWrapper(to, from, body, id, component, reply, "") +func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isOutgoing bool) { + sendMessageWrapper(to, from, body, id, component, reply, "", isOutgoing) } // SendServiceMessage creates and sends a simple message stanza from transport func SendServiceMessage(to string, body string, component *xmpp.Component) { - sendMessageWrapper(to, "", body, "", component, nil, "") + sendMessageWrapper(to, "", body, "", component, nil, "", false) } // SendTextMessage creates and sends a simple message stanza func SendTextMessage(to string, from string, body string, component *xmpp.Component) { - sendMessageWrapper(to, from, body, "", component, nil, "") + sendMessageWrapper(to, from, body, "", component, nil, "", false) } // SendMessageWithOOB creates and sends a message stanza with OOB URL -func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string) { - sendMessageWrapper(to, from, body, id, component, reply, oob) +func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isOutgoing bool) { + sendMessageWrapper(to, from, body, id, component, reply, oob, isOutgoing) } -func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string) { +func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isOutgoing bool) { + toJid, err := stanza.NewJid(to) + if err != nil { + log.WithFields(log.Fields{ + "to": to, + }).Error(errors.Wrap(err, "Invalid to JID!")) + return + } + bareTo := toJid.Bare() + componentJid := Jid.Full() var logFrom string var messageFrom string + var messageTo string if from == "" { logFrom = componentJid messageFrom = componentJid @@ -65,6 +79,12 @@ func sendMessageWrapper(to string, from string, body string, id string, componen logFrom = from messageFrom = from + "@" + componentJid } + if isOutgoing { + messageTo = messageFrom + messageFrom = bareTo + "/" + Jid.Resource + } else { + messageTo = to + } log.WithFields(log.Fields{ "from": logFrom, @@ -74,7 +94,7 @@ func sendMessageWrapper(to string, from string, body string, id string, componen message := stanza.Message{ Attrs: stanza.Attrs{ From: messageFrom, - To: to, + To: messageTo, Type: "chat", Id: id, }, @@ -96,7 +116,34 @@ func sendMessageWrapper(to string, from string, body string, id string, componen } } - sendMessage(&message, component) + if isOutgoing { + carbonMessage := extensions.ClientMessage{ + Attrs: stanza.Attrs{ + From: bareTo, + To: to, + Type: "chat", + }, + } + carbonMessage.Extensions = append(carbonMessage.Extensions, extensions.CarbonSent{ + Forwarded: stanza.Forwarded{ + Stanza: extensions.ClientMessage(message), + }, + }) + privilegeMessage := stanza.Message{ + Attrs: stanza.Attrs{ + From: Jid.Bare(), + To: toJid.Domain, + }, + } + privilegeMessage.Extensions = append(privilegeMessage.Extensions, extensions.ComponentPrivilege{ + Forwarded: stanza.Forwarded{ + Stanza: carbonMessage, + }, + }) + sendMessage(&privilegeMessage, component) + } else { + sendMessage(&message, component) + } } // SetNickname sets a new nickname for a contact @@ -297,3 +344,15 @@ func ResumableSend(component *xmpp.Component, packet stanza.Packet) error { } return err } + +// SplitJID tokenizes a JID string to bare JID and resource +func SplitJID(from string) (string, string, bool) { + fromJid, err := stanza.NewJid(from) + if err != nil { + log.WithFields(log.Fields{ + "from": from, + }).Error(errors.Wrap(err, "Invalid from JID!")) + return "", "", false + } + return fromJid.Bare(), fromJid.Resource, true +} diff --git a/xmpp/handlers.go b/xmpp/handlers.go index acf62a2..2bdbaef 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -79,7 +79,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { }).Warn("Message") log.Debugf("%#v", msg) - bare, resource, ok := splitFrom(msg.From) + bare, resource, ok := gateway.SplitJID(msg.From) if !ok { return } @@ -152,6 +152,23 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { } log.Warn("Unknown purpose of the message, skipping") } + + if msg.Body == "" { + var privilege extensions.ComponentPrivilege + if ok := msg.Get(&privilege); ok { + log.Debugf("privilege: %#v", privilege) + } + + for _, perm := range privilege.Perms { + if perm.Access == "message" && perm.Type == "outgoing" { + gateway.MessageOutgoingPermission = true + } + } + } + + if msg.Type == "error" { + log.Errorf("MESSAGE ERROR: %#v", p) + } } // HandlePresence processes an incoming XMPP presence @@ -196,7 +213,7 @@ func handleSubscription(s xmpp.Sender, p stanza.Presence) { if !ok { return } - bare, _, ok := splitFrom(p.From) + bare, _, ok := gateway.SplitJID(p.From) if !ok { return } @@ -227,7 +244,7 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) { log.Debugf("%#v", p) // create session - bare, resource, ok := splitFrom(p.From) + bare, resource, ok := gateway.SplitJID(p.From) if !ok { return } @@ -385,17 +402,6 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) { _ = gateway.ResumableSend(component, answer) } -func splitFrom(from string) (string, string, bool) { - fromJid, err := stanza.NewJid(from) - if err != nil { - log.WithFields(log.Fields{ - "from": from, - }).Error(errors.Wrap(err, "Invalid from JID!")) - return "", "", false - } - return fromJid.Bare(), fromJid.Resource, true -} - func toToID(to string) (int64, bool) { toParts := strings.Split(to, "@") if len(toParts) < 2 { From 281b9691af8dda2225c74efb99b98f4acc33c457 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 18 Mar 2023 17:45:45 -0400 Subject: [PATCH 17/51] Add carbon option to tests --- persistence/sessions_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/persistence/sessions_test.go b/persistence/sessions_test.go index c8e3077..df5e26d 100644 --- a/persistence/sessions_test.go +++ b/persistence/sessions_test.go @@ -56,6 +56,7 @@ func TestSessionToMap(t *testing.T) { "rawmessages": "true", "asciiarrows": "false", "oobmode": "true", + "carbons": "false", } if !reflect.DeepEqual(m, sample) { t.Errorf("Map does not match the sample: %v", m) From 22b46c71ce932cc97fa6d437a7473a653bb59a41 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 18 Mar 2023 17:47:08 -0400 Subject: [PATCH 18/51] gofmt --- telegram/commands.go | 8 ++++---- telegram/handlers.go | 2 +- telegram/utils.go | 8 ++++---- xmpp/extensions/extensions.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index 368a461..2a72219 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -52,10 +52,10 @@ var transportCommands = map[string]command{ "setpassword": command{"[old] [new]", "set or remove password"}, "config": command{"[param] [value]", "view or update configuration options"}, "report": command{"[chat] [comment]", "report a chat by id or @username"}, - "add": command{"@username", "add @username to your chat list"}, - "join": command{"https://t.me/invite_link", "join to chat via invite link or @publicname"}, - "supergroup": command{"title description", "create new supergroup «title» with «description»"}, - "channel": command{"title description", "create new channel «title» with «description»"}, + "add": command{"@username", "add @username to your chat list"}, + "join": command{"https://t.me/invite_link", "join to chat via invite link or @publicname"}, + "supergroup": command{"title description", "create new supergroup «title» with «description»"}, + "channel": command{"title description", "create new channel «title» with «description»"}, } var chatCommands = map[string]command{ diff --git a/telegram/handlers.go b/telegram/handlers.go index 84e3748..bd768ae 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -242,7 +242,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { textContent.Text.Entities, markupFunction, )) - gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false) + gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false) } } diff --git a/telegram/utils.go b/telegram/utils.go index 99492e1..905a97b 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -320,7 +320,7 @@ func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply, return } - reply = &gateway.Reply { + reply = &gateway.Reply{ Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()), Id: strconv.FormatInt(message.ReplyToMessageId, 10), } @@ -770,8 +770,8 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string, } // reply to if message.ReplyToMessageId != 0 { - replyStart = c.countCharsInLines(&prefix) + (len(prefix) - 1) * len(messageHeaderSeparator) - replyLine := "reply: "+c.formatMessage(message.ChatId, message.ReplyToMessageId, true, replyMsg) + replyStart = c.countCharsInLines(&prefix) + (len(prefix)-1)*len(messageHeaderSeparator) + replyLine := "reply: " + c.formatMessage(message.ChatId, message.ReplyToMessageId, true, replyMsg) prefix = append(prefix, replyLine) replyEnd = replyStart + len(replyLine) + len(messageHeaderSeparator) } @@ -891,7 +891,7 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { isOutgoing := isPM && message.IsOutgoing if isOutgoing { for resource := range c.resourcesRange() { - jids = append(jids, c.jid + "/" + resource) + jids = append(jids, c.jid+"/"+resource) } } else { jids = []string{c.jid} diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 2cf7d45..78de47d 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -237,7 +237,7 @@ func NewReplyFallback(start uint64, end uint64) Fallback { Body: []FallbackBody{ FallbackBody{ Start: strconv.FormatUint(start, 10), - End: strconv.FormatUint(end, 10), + End: strconv.FormatUint(end, 10), }, }, } From 0a2c4e09d9179108208461a58990f3397dad5a6c Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 18 Mar 2023 20:13:00 -0400 Subject: [PATCH 19/51] Add `hideids` configuration option --- persistence/sessions.go | 11 +++++++ persistence/sessions_test.go | 1 + telegram/utils.go | 57 ++++++++++++++++++++++++------------ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/persistence/sessions.go b/persistence/sessions.go index c0750a3..1658cc9 100644 --- a/persistence/sessions.go +++ b/persistence/sessions.go @@ -41,6 +41,7 @@ type Session struct { AsciiArrows bool `yaml:":asciiarrows"` OOBMode bool `yaml:":oobmode"` Carbons bool `yaml:":carbons"` + HideIds bool `yaml:":hideids"` } var configKeys = []string{ @@ -50,6 +51,7 @@ var configKeys = []string{ "asciiarrows", "oobmode", "carbons", + "hideids", } var sessionDB *SessionsYamlDB @@ -126,6 +128,8 @@ func (s *Session) Get(key string) (string, error) { return fromBool(s.OOBMode), nil case "carbons": return fromBool(s.Carbons), nil + case "hideids": + return fromBool(s.HideIds), nil } return "", errors.New("Unknown session property") @@ -183,6 +187,13 @@ func (s *Session) Set(key string, value string) (string, error) { } s.Carbons = b return value, nil + case "hideids": + b, err := toBool(value) + if err != nil { + return "", err + } + s.HideIds = b + return value, nil } return "", errors.New("Unknown session property") diff --git a/persistence/sessions_test.go b/persistence/sessions_test.go index df5e26d..8ca6f4f 100644 --- a/persistence/sessions_test.go +++ b/persistence/sessions_test.go @@ -57,6 +57,7 @@ func TestSessionToMap(t *testing.T) { "asciiarrows": "false", "oobmode": "true", "carbons": "false", + "hideids": "false", } if !reflect.DeepEqual(m, sample) { t.Errorf("Map does not match the sample: %v", m) diff --git a/telegram/utils.go b/telegram/utils.go index 905a97b..9a247c4 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -746,34 +746,51 @@ func (c *Client) countCharsInLines(lines *[]string) (count int) { } func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) (string, int, int) { + isPM, err := c.IsPM(message.ChatId) + if err != nil { + log.Errorf("Could not determine if chat is PM: %v", err) + } + var replyStart, replyEnd int prefix := []string{} // message direction var directionChar string - if c.Session.AsciiArrows { - if message.IsOutgoing { - directionChar = "> " + if !isPM || !gateway.MessageOutgoingPermission || !c.Session.Carbons { + if c.Session.AsciiArrows { + if message.IsOutgoing { + directionChar = "> " + } else { + directionChar = "< " + } } else { - directionChar = "< " - } - } else { - if message.IsOutgoing { - directionChar = "➡ " - } else { - directionChar = "⬅ " + if message.IsOutgoing { + directionChar = "➡ " + } else { + directionChar = "⬅ " + } } } - prefix = append(prefix, directionChar+strconv.FormatInt(message.Id, 10)) + if !isPM || !c.Session.HideIds { + prefix = append(prefix, directionChar+strconv.FormatInt(message.Id, 10)) + } // show sender in group chats - if message.ChatId < 0 { - prefix = append(prefix, c.formatSender(message)) + if !isPM { + sender := c.formatSender(message) + if sender != "" { + prefix = append(prefix, sender) + } } // reply to if message.ReplyToMessageId != 0 { - replyStart = c.countCharsInLines(&prefix) + (len(prefix)-1)*len(messageHeaderSeparator) + if len(prefix) > 0 { + replyStart = c.countCharsInLines(&prefix) + (len(prefix)-1)*len(messageHeaderSeparator) + } replyLine := "reply: " + c.formatMessage(message.ChatId, message.ReplyToMessageId, true, replyMsg) prefix = append(prefix, replyLine) - replyEnd = replyStart + len(replyLine) + len(messageHeaderSeparator) + replyEnd = replyStart + len(replyLine) + if len(prefix) > 0 { + replyEnd += len(messageHeaderSeparator) + } } if message.ForwardInfo != nil { prefix = append(prefix, "fwd: "+c.formatForward(message.ForwardInfo)) @@ -856,10 +873,12 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { if text != "" { // \n if it is groupchat and message is not empty - if chatId < 0 { - newText.WriteString("\n") - } else if chatId > 0 { - newText.WriteString(" | ") + if prefix != "" { + if chatId < 0 { + newText.WriteString("\n") + } else if chatId > 0 { + newText.WriteString(" | ") + } } newText.WriteString(text) From 75f0532193440294f4bbf7cd687174b5e9a16249 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 19 Mar 2023 18:12:06 -0400 Subject: [PATCH 20/51] Warn about undelivered carbons to foreign servers --- telegabber.go | 2 +- xmpp/handlers.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/telegabber.go b/telegabber.go index 0554cb9..72353bb 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.5.0-dev" +var version string = "1.5.0" var commit string var sm *goxmpp.StreamManager diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 2bdbaef..db2b6ea 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -168,6 +168,15 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { if msg.Type == "error" { log.Errorf("MESSAGE ERROR: %#v", p) + + if msg.XMLName.Space == "jabber:component:accept" && msg.Error.Code == 401 { + suffix := "@" + msg.From + for bare := range sessions { + if strings.HasSuffix(bare, suffix) { + gateway.SendServiceMessage(bare, "Your server \"" + msg.From + "\" does not allow to send carbons", component) + } + } + } } } From 8663a29e157aae6e68cc880a86b3a666da37bfc9 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 1 Jun 2023 16:37:38 -0400 Subject: [PATCH 21/51] Add /vcard command --- telegabber.go | 2 +- telegram/commands.go | 20 +++++++ telegram/utils.go | 129 ++++++++++++++++++++++++++++++++++++------- xmpp/handlers.go | 106 ++++++++++++++++------------------- 4 files changed, 177 insertions(+), 80 deletions(-) diff --git a/telegabber.go b/telegabber.go index 72353bb..c8d6a8f 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.5.0" +var version string = "1.6.0-dev" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index 2a72219..ec06f36 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -64,6 +64,7 @@ var chatCommands = map[string]command{ "silent": command{"message", "send a message without sound"}, "schedule": command{"{online | 2006-01-02T15:04:05 | 15:04:05} message", "schedules a message either to timestamp or to whenever the user goes online"}, "forward": command{"message_id target_chat", "forwards a message"}, + "vcard": command{"", "print vCard as text"}, "add": command{"@username", "add @username to your chat list"}, "join": command{"https://t.me/invite_link", "join to chat via invite link or @publicname"}, "group": command{"title", "create groupchat «title» with current user"}, @@ -172,6 +173,10 @@ func rawCmdArguments(cmdline string, start uint8) string { return "" } +func keyValueString(key, value string) string { + return fmt.Sprintf("%s: %s", key, value) +} + func (c *Client) unsubscribe(chatID int64) error { return gateway.SendPresence( c.xmpp, @@ -636,6 +641,21 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) c.ProcessIncomingMessage(targetChatId, message) } } + // print vCard + case "vcard": + info, err := c.GetVcardInfo(chatID) + if err != nil { + return err.Error(), true + } + _, link := c.PermastoreFile(info.Photo, true) + entries := []string{ + keyValueString("Chat title", info.Fn), + keyValueString("Photo", link), + keyValueString("Username", info.Nickname), + keyValueString("Full name", info.Given + " " + info.Family), + keyValueString("Phone number", info.Tel), + } + return strings.Join(entries, "\n"), true // add @contact case "add": return c.cmdAdd(args), true diff --git a/telegram/utils.go b/telegram/utils.go index 9a247c4..851c6c1 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -24,6 +24,16 @@ import ( "github.com/zelenin/go-tdlib/client" ) +type VCardInfo struct { + Fn string + Photo *client.File + Nickname string + Given string + Family string + Tel string + Info string +} + var errOffline = errors.New("TDlib instance is offline") var spaceRegex = regexp.MustCompile(`\s+`) @@ -207,7 +217,7 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o var photo string if chat != nil && chat.Photo != nil { - file, path, err := c.OpenPhotoFile(chat.Photo.Small, 1) + file, path, err := c.ForceOpenFile(chat.Photo.Small, 1) if err == nil { defer file.Close() @@ -408,6 +418,20 @@ func (c *Client) formatForward(fwd *client.MessageForwardInfo) string { } func (c *Client) formatFile(file *client.File, compact bool) (string, string) { + if file == nil { + return "", "" + } + src, link := c.PermastoreFile(file, false) + + if compact { + return link, link + } else { + return fmt.Sprintf("%s (%v kbytes) | %s", filepath.Base(src), file.Size/1024, link), link + } +} + +// PermastoreFile steals a file out of TDlib control into an independent shared directory +func (c *Client) PermastoreFile(file *client.File, clone bool) (string, string) { log.Debugf("file: %#v", file) if file == nil || file.Local == nil || file.Remote == nil { return "", "" @@ -434,18 +458,57 @@ func (c *Client) formatFile(file *client.File, compact bool) (string, string) { dest := c.content.Path + "/" + basename // destination path link = c.content.Link + "/" + basename // download link - // move - err = os.Rename(src, dest) - if err != nil { - linkErr := err.(*os.LinkError) - if linkErr.Err.Error() == "file exists" { - log.Warn(err.Error()) + if clone { + file, path, err := c.ForceOpenFile(file, 1) + if err == nil { + defer file.Close() + + // mode + mode := os.FileMode(0644) + fi, err := os.Stat(path) + if err == nil { + mode = fi.Mode().Perm() + } + + // create destination + tempFile, err := os.OpenFile(dest, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode) + if err != nil { + pathErr := err.(*os.PathError) + if pathErr.Err.Error() == "file exists" { + log.Warn(err.Error()) + return src, link + } else { + log.Errorf("File creation error: %v", err) + return "", "" + } + } + defer tempFile.Close() + // copy + _, err = io.Copy(tempFile, file) + if err != nil { + log.Errorf("File copying error: %v", err) + return "", "" + } + } else if path != "" { + log.Errorf("Source file does not exist: %v", path) + return "", "" } else { - log.Errorf("File moving error: %v", err) + log.Errorf("PHOTO: %#v", err.Error()) return "", "" } + } else { + // move + err = os.Rename(src, dest) + if err != nil { + linkErr := err.(*os.LinkError) + if linkErr.Err.Error() == "file exists" { + log.Warn(err.Error()) + } else { + log.Errorf("File moving error: %v", err) + return "", "" + } + } } - gateway.CachedStorageSize += size64 // chown if c.content.User != "" { @@ -464,13 +527,12 @@ func (c *Client) formatFile(file *client.File, compact bool) (string, string) { log.Errorf("Wrong user name for chown: %v", err) } } + + // copy or move should have succeeded at this point + gateway.CachedStorageSize += size64 } - if compact { - return link, link - } else { - return fmt.Sprintf("%s (%v kbytes) | %s", filepath.Base(src), file.Size/1024, link), link - } + return src, link } func (c *Client) formatBantime(hours int64) int32 { @@ -1148,20 +1210,20 @@ func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*clie }) } -// OpenPhotoFile reliably obtains a photo if possible -func (c *Client) OpenPhotoFile(photoFile *client.File, priority int32) (*os.File, string, error) { - if photoFile == nil { - return nil, "", errors.New("Photo file not found") +// ForceOpenFile reliably obtains a file if possible +func (c *Client) ForceOpenFile(tgFile *client.File, priority int32) (*os.File, string, error) { + if tgFile == nil { + return nil, "", errors.New("File not found") } - path := photoFile.Local.Path + path := tgFile.Local.Path file, err := os.Open(path) if err == nil { return file, path, nil } else // obtain the photo right now if still not downloaded - if !photoFile.Local.IsDownloadingCompleted { - tdFile, tdErr := c.DownloadFile(photoFile.Id, priority, true) + if !tgFile.Local.IsDownloadingCompleted { + tdFile, tdErr := c.DownloadFile(tgFile.Id, priority, true) if tdErr == nil { path = tdFile.Local.Path file, err = os.Open(path) @@ -1248,3 +1310,28 @@ func (c *Client) prepareDiskSpace(size uint64) { } } } + +func (c *Client) GetVcardInfo(toID int64) (VCardInfo, error) { + var info VCardInfo + chat, user, err := c.GetContactByID(toID, nil) + if err != nil { + return info, err + } + + if chat != nil { + info.Fn = chat.Title + + if chat.Photo != nil { + info.Photo = chat.Photo.Small + } + info.Info = c.GetChatDescription(chat) + } + if user != nil { + info.Nickname = user.Username + info.Given = user.FirstName + info.Family = user.LastName + info.Tel = user.PhoneNumber + } + + return info, nil +} diff --git a/xmpp/handlers.go b/xmpp/handlers.go index db2b6ea..780478a 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -10,6 +10,7 @@ import ( "strings" "dev.narayana.im/narayana/telegabber/persistence" + "dev.narayana.im/narayana/telegabber/telegram" "dev.narayana.im/narayana/telegabber/xmpp/extensions" "dev.narayana.im/narayana/telegabber/xmpp/gateway" @@ -319,45 +320,12 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) { log.Error("Invalid IQ to") return } - chat, user, err := session.GetContactByID(toID, nil) + info, err := session.GetVcardInfo(toID) if err != nil { log.Error(err) return } - var fn, photo, nickname, given, family, tel, info string - if chat != nil { - fn = chat.Title - - if chat.Photo != nil { - file, path, err := session.OpenPhotoFile(chat.Photo.Small, 32) - if err == nil { - defer file.Close() - - buf := new(bytes.Buffer) - binval := base64.NewEncoder(base64.StdEncoding, buf) - _, err = io.Copy(binval, file) - binval.Close() - if err == nil { - photo = buf.String() - } else { - log.Errorf("Error calculating base64: %v", path) - } - } else if path != "" { - log.Errorf("Photo does not exist: %v", path) - } else { - log.Errorf("PHOTO: %#v", err.Error()) - } - } - info = session.GetChatDescription(chat) - } - if user != nil { - nickname = user.Username - given = user.FirstName - family = user.LastName - tel = user.PhoneNumber - } - answer := stanza.IQ{ Attrs: stanza.Attrs{ From: iq.To, @@ -365,7 +333,7 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) { Id: iq.Id, Type: "result", }, - Payload: makeVCardPayload(typ, iq.To, fn, photo, nickname, given, family, tel, info), + Payload: makeVCardPayload(typ, iq.To, info, session), } log.Debugf("%#v", answer) @@ -426,53 +394,75 @@ func toToID(to string) (int64, bool) { return toID, true } -func makeVCardPayload(typ byte, id, fn, photo, nickname, given, family, tel, info string) stanza.IQPayload { +func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *telegram.Client) stanza.IQPayload { + var base64Photo string + if info.Photo != nil { + file, path, err := session.ForceOpenFile(info.Photo, 32) + if err == nil { + defer file.Close() + + buf := new(bytes.Buffer) + binval := base64.NewEncoder(base64.StdEncoding, buf) + _, err = io.Copy(binval, file) + binval.Close() + if err == nil { + base64Photo = buf.String() + } else { + log.Errorf("Error calculating base64: %v", path) + } + } else if path != "" { + log.Errorf("Photo does not exist: %v", path) + } else { + log.Errorf("PHOTO: %#v", err.Error()) + } + } + if typ == TypeVCardTemp { vcard := &extensions.IqVcardTemp{} - vcard.Fn.Text = fn - if photo != "" { + vcard.Fn.Text = info.Fn + if base64Photo != "" { vcard.Photo.Type.Text = "image/jpeg" - vcard.Photo.Binval.Text = photo + vcard.Photo.Binval.Text = base64Photo } - vcard.Nickname.Text = nickname - vcard.N.Given.Text = given - vcard.N.Family.Text = family - vcard.Tel.Number.Text = tel - vcard.Desc.Text = info + vcard.Nickname.Text = info.Nickname + vcard.N.Given.Text = info.Given + vcard.N.Family.Text = info.Family + vcard.Tel.Number.Text = info.Tel + vcard.Desc.Text = info.Info return vcard } else if typ == TypeVCard4 { nodes := []stanza.Node{} - if fn != "" { + if info.Fn != "" { nodes = append(nodes, stanza.Node{ XMLName: xml.Name{Local: "fn"}, Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "text"}, - Content: fn, + Content: info.Fn, }, }, }) } - if photo != "" { + if base64Photo != "" { nodes = append(nodes, stanza.Node{ XMLName: xml.Name{Local: "photo"}, Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "uri"}, - Content: "data:image/jpeg;base64," + photo, + Content: "data:image/jpeg;base64," + base64Photo, }, }, }) } - if nickname != "" { + if info.Nickname != "" { nodes = append(nodes, stanza.Node{ XMLName: xml.Name{Local: "nickname"}, Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "text"}, - Content: nickname, + Content: info.Nickname, }, }, }, stanza.Node{ @@ -480,44 +470,44 @@ func makeVCardPayload(typ byte, id, fn, photo, nickname, given, family, tel, inf Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "uri"}, - Content: "https://t.me/" + nickname, + Content: "https://t.me/" + info.Nickname, }, }, }) } - if family != "" || given != "" { + if info.Family != "" || info.Given != "" { nodes = append(nodes, stanza.Node{ XMLName: xml.Name{Local: "n"}, Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "surname"}, - Content: family, + Content: info.Family, }, stanza.Node{ XMLName: xml.Name{Local: "given"}, - Content: given, + Content: info.Given, }, }, }) } - if tel != "" { + if info.Tel != "" { nodes = append(nodes, stanza.Node{ XMLName: xml.Name{Local: "tel"}, Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "uri"}, - Content: "tel:" + tel, + Content: "tel:" + info.Tel, }, }, }) } - if info != "" { + if info.Info != "" { nodes = append(nodes, stanza.Node{ XMLName: xml.Name{Local: "note"}, Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "text"}, - Content: info, + Content: info.Info, }, }, }) From 9a84e9a8b6b7a6f953301e54f19cdf4be73592e1 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 3 Jun 2023 00:20:03 -0400 Subject: [PATCH 22/51] Store message ids in Badger DB --- Makefile | 2 +- README.md | 1 + badger/ids.go | 132 ++++++++++++++++++++++++++++++++++++++++ badger/ids_test.go | 72 ++++++++++++++++++++++ go.mod | 3 +- go.sum | 121 ++++++++++++++++++++++++++++++++++++ telegabber.go | 4 +- telegram/commands.go | 6 +- telegram/utils.go | 9 ++- xmpp/component.go | 10 ++- xmpp/gateway/gateway.go | 4 ++ xmpp/handlers.go | 2 +- 12 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 badger/ids.go create mode 100644 badger/ids_test.go diff --git a/Makefile b/Makefile index 48c5d7e..048987d 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ all: go build -ldflags "-X main.commit=${COMMIT}" -o telegabber test: - go test -v ./config ./ ./telegram ./xmpp ./xmpp/gateway ./persistence ./telegram/formatter + go test -v ./config ./ ./telegram ./xmpp ./xmpp/gateway ./persistence ./telegram/formatter ./badger lint: $(GOPATH)/bin/golint ./... diff --git a/README.md b/README.md index 36aa7ca..ae80f4a 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ It is good idea to obtain Telegram API ID from [**https://my.telegram.org**](htt * `--profiling-port=xxxx`: start the pprof server on port `xxxx`. Access is limited to localhost. * `--config=/bla/bla/config.yml`: set the config file path (default: `config.yml`). * `--schema=/bla/bla/schema.json`: set the schema file path (default: `./config_schema.json`). +* `--ids=/bla/bla/ids`: set the folder for ids database (default: `ids`). ### How to receive files from Telegram ### diff --git a/badger/ids.go b/badger/ids.go new file mode 100644 index 0000000..80fb9ad --- /dev/null +++ b/badger/ids.go @@ -0,0 +1,132 @@ +package badger + +import ( + "bytes" + "errors" + "fmt" + "strconv" + + badger "github.com/dgraph-io/badger/v4" + log "github.com/sirupsen/logrus" +) + +// IdsDB represents a Badger database +type IdsDB struct { + db *badger.DB +} + +// IdsDBOpen returns a new DB object +func IdsDBOpen(path string) IdsDB { + bdb, err := badger.Open(badger.DefaultOptions(path)) + if err != nil { + log.Errorf("Failed to open ids database: %v, falling back to in-memory database", path) + bdb, err = badger.Open(badger.DefaultOptions("").WithInMemory(true)) + if err != nil { + log.Fatalf("Couldn't initialize the ids database") + } + } + + return IdsDB{ + db: bdb, + } +} + +// Set stores an id pair +func (db *IdsDB) Set(tgAccount, xmppAccount string, tgChatId, tgMsgId int64, xmppId string) error { + bPrefix := toKeyPrefix(tgAccount, xmppAccount) + bTgId := toTgByteString(tgChatId, tgMsgId) + bXmppId := toXmppByteString(xmppId) + bTgKey := toByteKey(bPrefix, bTgId, "tg") + bXmppKey := toByteKey(bPrefix, bXmppId, "xmpp") + + return db.db.Update(func(txn *badger.Txn) error { + if err := txn.Set(bTgKey, bXmppId); err != nil { + return err + } + return txn.Set(bXmppKey, bTgId) + }) +} + +func (db *IdsDB) getByteValue(key []byte) ([]byte, error) { + var valCopy []byte + err := db.db.View(func(txn *badger.Txn) error { + item, err := txn.Get(key) + if err != nil { + return err + } + + valCopy, err = item.ValueCopy(nil) + return err + }) + return valCopy, err +} + +// GetByTgIds obtains an XMPP id by Telegram chat/message ids +func (db *IdsDB) GetByTgIds(tgAccount, xmppAccount string, tgChatId, tgMsgId int64) (string, error) { + val, err := db.getByteValue(toByteKey( + toKeyPrefix(tgAccount, xmppAccount), + toTgByteString(tgChatId, tgMsgId), + "tg", + )) + if err != nil { + return "", err + } + return string(val), nil +} + +// GetByXmppId obtains Telegram chat/message ids by an XMPP id +func (db *IdsDB) GetByXmppId(tgAccount, xmppAccount, xmppId string) (int64, int64, error) { + val, err := db.getByteValue(toByteKey( + toKeyPrefix(tgAccount, xmppAccount), + toXmppByteString(xmppId), + "xmpp", + )) + if err != nil { + return 0, 0, err + } + return splitTgByteString(val) +} + +func toKeyPrefix(tgAccount, xmppAccount string) []byte { + return []byte(fmt.Sprintf("%v/%v/", tgAccount, xmppAccount)) +} + +func toByteKey(prefix, suffix []byte, typ string) []byte { + key := make([]byte, 0, len(prefix) + len(suffix) + 6) + key = append(key, prefix...) + key = append(key, []byte(typ)...) + key = append(key, []byte("/")...) + key = append(key, suffix...) + return key +} + +func toTgByteString(tgChatId, tgMsgId int64) []byte { + return []byte(fmt.Sprintf("%v/%v", tgChatId, tgMsgId)) +} + +func toXmppByteString(xmppId string) []byte { + return []byte(xmppId) +} + +func splitTgByteString(val []byte) (int64, int64, error) { + parts := bytes.Split(val, []byte("/")) + if len(parts) != 2 { + return 0, 0, errors.New("Couldn't parse tg id pair") + } + tgChatId, err := strconv.ParseInt(string(parts[0]), 10, 64) + if err != nil { + return 0, 0, err + } + tgMsgId, err := strconv.ParseInt(string(parts[1]), 10, 64) + return tgChatId, tgMsgId, err +} + +// Gc compacts the value log +func (db *IdsDB) Gc() { + db.db.RunValueLogGC(0.7) +} + +// Close closes a DB +func (db *IdsDB) Close() { + db.db.Close() +} diff --git a/badger/ids_test.go b/badger/ids_test.go new file mode 100644 index 0000000..efafdeb --- /dev/null +++ b/badger/ids_test.go @@ -0,0 +1,72 @@ +package badger + +import ( + "reflect" + "testing" +) + +func TestToKeyPrefix(t *testing.T) { + if !reflect.DeepEqual(toKeyPrefix("+123456789", "test@example.com"), []byte("+123456789/test@example.com/")) { + t.Error("Wrong prefix") + } +} + +func TestToByteKey(t *testing.T) { + if !reflect.DeepEqual(toByteKey([]byte("ababa/galamaga/"), []byte("123"), "ppp"), []byte("ababa/galamaga/ppp/123")) { + t.Error("Wrong key") + } +} + +func TestToTgByteString(t *testing.T) { + if !reflect.DeepEqual(toTgByteString(-2345, 6789), []byte("-2345/6789")) { + t.Error("Wrong tg string") + } +} + +func TestToXmppByteString(t *testing.T) { + if !reflect.DeepEqual(toXmppByteString("aboba"), []byte("aboba")) { + t.Error("Wrong xmpp string") + } +} + +func TestSplitTgByteStringUnparsable(t *testing.T) { + _, _, err := splitTgByteString([]byte("@#U*&$(@#")) + if err == nil { + t.Error("Unparsable should not be parsed") + return + } + if err.Error() != "Couldn't parse tg id pair" { + t.Error("Wrong parse error") + } +} + +func TestSplitTgByteManyParts(t *testing.T) { + _, _, err := splitTgByteString([]byte("a/b/c/d")) + if err == nil { + t.Error("Should not parse many parts") + return + } + if err.Error() != "Couldn't parse tg id pair" { + t.Error("Wrong parse error") + } +} + +func TestSplitTgByteNonNumeric(t *testing.T) { + _, _, err := splitTgByteString([]byte("0/a")) + if err == nil { + t.Error("Should not parse non-numeric msgid") + } +} + +func TestSplitTgByteSuccess(t *testing.T) { + chatId, msgId, err := splitTgByteString([]byte("-198282398/23798478")) + if err != nil { + t.Error("Should be parsed well") + } + if chatId != -198282398 { + t.Error("Wrong chatId") + } + if msgId != 23798478 { + t.Error("Wrong msgId") + } +} diff --git a/go.mod b/go.mod index 41f4e67..a999878 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.13 require ( github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7 - github.com/pkg/errors v0.8.1 + github.com/dgraph-io/badger/v4 v4.1.0 // indirect + github.com/pkg/errors v0.9.1 github.com/santhosh-tekuri/jsonschema v1.2.4 github.com/sirupsen/logrus v1.4.2 github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e diff --git a/go.sum b/go.sum index 5fa5f81..0134061 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,13 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dev.narayana.im/narayana/go-xmpp v0.0.0-20211218155535-e55463fc9829 h1:qe81G6+t1V1ySRMa7lSu5CayN5aP5GEiHXL2DYwHzuA= dev.narayana.im/narayana/go-xmpp v0.0.0-20211218155535-e55463fc9829/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f h1:6249ajbMjgYz53Oq0IjTvjHXbxTfu29Mj1J/6swRHs4= dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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= @@ -20,15 +23,30 @@ github.com/bodqhrohro/go-xmpp v0.2.1-0.20211205194122-f8c4ecb59d8b h1:rTK55SNCBm github.com/bodqhrohro/go-xmpp v0.2.1-0.20211205194122-f8c4ecb59d8b/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= github.com/bodqhrohro/go-xmpp v0.2.1-0.20211218153313-a8aadd78b65b h1:VDi8z3PzEDhQzazRRuv1fkv662DT3Mm/TY/Lni2Sgrc= github.com/bodqhrohro/go-xmpp v0.2.1-0.20211218153313-a8aadd78b65b/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chromedp/cdproto v0.0.0-20190614062957-d6d2f92b486d/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= github.com/chromedp/cdproto v0.0.0-20190812224334-39ef923dcb8d/go.mod h1:0YChpVzuLJC5CPr+x3xkHN6Z8KOSXjNbL7qV8Wc4GW0= github.com/chromedp/cdproto v0.0.0-20190926234355-1b4886c6fad6/go.mod h1:0YChpVzuLJC5CPr+x3xkHN6Z8KOSXjNbL7qV8Wc4GW0= 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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/dgraph-io/badger/v4 v4.1.0 h1:E38jc0f+RATYrycSUf9LMv/t47XAy+3CApyYSq4APOQ= +github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -40,24 +58,45 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godcong/go-tdlib v0.4.4-0.20211203152853-64d22ab8d4ac h1:5FQGW4yHSkbwm+4i/8ef7FvkIFt4NOM4HexSbvPduRo= github.com/godcong/go-tdlib v0.4.4-0.20211203152853-64d22ab8d4ac/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 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/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190908185732-236ed259b199/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -68,14 +107,20 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 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.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -83,8 +128,14 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e h1:Xmvww1DnnCj49ZNwQAw069Kc6X3Q0MUd9OiFQ092yQQ= github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e/go.mod h1:RUak+ZC0a3E2NDNxZmA34Hk4Jm9nJMCSKLpKeB06clM= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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= @@ -92,43 +143,112 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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= github.com/zelenin/go-tdlib v0.5.2 h1:inEATEM0Pz6/HBI3wTlhd+brDHpmoXGgwdSb8/V6GiA= github.com/zelenin/go-tdlib v0.5.2/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= go.coder.com/go-tools v0.0.0-20190317003359-0c6a35b74a16/go.mod h1:iKV5yK9t+J5nG9O3uF6KYdPEz3dyfMyB15MN1rbQ8Qw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190927073244-c990c680b611 h1:q9u40nxWT5zRClI/uU9dHCiYGottAg6Nzz4YUQyHxdA= golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -142,6 +262,7 @@ gosrc.io/xmpp v0.5.2-0.20211214110136-5f99e1cd06e1 h1:E3uJqX6ImJL9AFdjGbiW04jq8I gosrc.io/xmpp v0.5.2-0.20211214110136-5f99e1cd06e1/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= nhooyr.io/websocket v1.6.5 h1:8TzpkldRfefda5JST+CnOH135bzVPz5uzfn/AF+gVKg= nhooyr.io/websocket v1.6.5/go.mod h1:F259lAzPRAH0htX2y3ehpJe09ih1aSHN7udWki1defY= diff --git a/telegabber.go b/telegabber.go index c8d6a8f..d133d80 100644 --- a/telegabber.go +++ b/telegabber.go @@ -35,6 +35,8 @@ func main() { var configPath = flag.String("config", "config.yml", "Config file path") // JSON schema (not for editing by a user) var schemaPath = flag.String("schema", "./config_schema.json", "Schema file path") + // Folder for Badger DB of message ids + var idsPath = flag.String("ids", "ids", "Ids folder path") var versionFlag = flag.Bool("version", false, "Print the version and exit") flag.Parse() @@ -62,7 +64,7 @@ func main() { log.Infof("Starting telegabber version %v", version) - sm, component, err = xmpp.NewComponent(config.XMPP, config.Telegram) + sm, component, err = xmpp.NewComponent(config.XMPP, config.Telegram, *idsPath) if err != nil { log.Fatal(err) } diff --git a/telegram/commands.go b/telegram/commands.go index ec06f36..59fe1c7 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -498,7 +498,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "Last message is empty", true } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", 0) + content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", "", 0) if content != nil { c.client.EditMessageText(&client.EditMessageTextRequest{ @@ -515,7 +515,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "Not enough arguments", true } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", 0) + content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", "", 0) if content != nil { _, err := c.client.SendMessage(&client.SendMessageRequest{ @@ -594,7 +594,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 1), "", 0) + content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 1), "", "", 0) if content != nil { _, err := c.client.SendMessage(&client.SendMessageRequest{ diff --git a/telegram/utils.go b/telegram/utils.go index 851c6c1..0a2ae1f 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -987,7 +987,7 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { } // ProcessOutgoingMessage executes commands or sends messages to mapped chats -func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, replyId int64) client.InputMessageContent { +func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, id string, replyId int64) client.InputMessageContent { if !c.Online() { // we're offline return nil @@ -1111,7 +1111,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str } if chatID != 0 { - _, err := c.client.SendMessage(&client.SendMessageRequest{ + tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{ ChatId: chatID, ReplyToMessageId: reply, InputMessageContent: message, @@ -1123,6 +1123,11 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str fmt.Sprintf("Not sent: %s", err.Error()), c.xmpp, ) + } else { + err = gateway.IdsDB.Set(c.Session.Login, c.jid, tgMessage.ChatId, tgMessage.Id, id) + if err != nil { + log.Errorf("Failed to save ids %v/%v %v", tgMessage.ChatId, tgMessage.Id, id) + } } return nil } else { diff --git a/xmpp/component.go b/xmpp/component.go index 0f23d50..f0c481d 100644 --- a/xmpp/component.go +++ b/xmpp/component.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "dev.narayana.im/narayana/telegabber/badger" "dev.narayana.im/narayana/telegabber/config" "dev.narayana.im/narayana/telegabber/persistence" "dev.narayana.im/narayana/telegabber/telegram" @@ -38,7 +39,7 @@ 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) (*xmpp.StreamManager, *xmpp.Component, error) { +func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig, idsPath string) (*xmpp.StreamManager, *xmpp.Component, error) { var err error gateway.Jid, err = stanza.NewJid(conf.Jid) @@ -53,6 +54,8 @@ func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig) (*xmpp.Strea } } + gateway.IdsDB = badger.IdsDBOpen(idsPath) + tgConf = tc if tc.Content.Quota != "" { @@ -163,6 +166,8 @@ func heartbeat(component *xmpp.Component) { // it would be resolved on the next iteration SaveSessions() } + + gateway.IdsDB.Gc() } } @@ -240,6 +245,9 @@ func Close(component *xmpp.Component) { // save sessions SaveSessions() + // flush the ids database + gateway.IdsDB.Close() + // close stream component.Disconnect() } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 534ee7e..29c8a07 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -6,6 +6,7 @@ import ( "strings" "sync" + "dev.narayana.im/narayana/telegabber/badger" "dev.narayana.im/narayana/telegabber/xmpp/extensions" log "github.com/sirupsen/logrus" @@ -30,6 +31,9 @@ var QueueLock = sync.Mutex{} // Jid stores the component's JID object var Jid *stanza.Jid +// IdsDB provides a disk-backed bidirectional dictionary of Telegram and XMPP ids +var IdsDB badger.IdsDB + // DirtySessions denotes that some Telegram session configurations // were changed and need to be re-flushed to the YamlDB var DirtySessions = false diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 780478a..5178acd 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -139,7 +139,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { } } - session.ProcessOutgoingMessage(toID, text, msg.From, replyId) + session.ProcessOutgoingMessage(toID, text, msg.From, msg.Id, replyId) return } else { toJid, err := stanza.NewJid(msg.To) From a5c90340ad2da16a68ddcbfa3d9573f0664067ca Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 3 Jun 2023 20:25:00 -0400 Subject: [PATCH 23/51] Refactor ProcessOutgoingMessage --- telegram/commands.go | 6 +-- telegram/utils.go | 119 +++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 71 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index 59fe1c7..f36108a 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -498,7 +498,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "Last message is empty", true } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", "", 0) + content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 0)) if content != nil { c.client.EditMessageText(&client.EditMessageTextRequest{ @@ -515,7 +515,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "Not enough arguments", true } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 0), "", "", 0) + content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 0)) if content != nil { _, err := c.client.SendMessage(&client.SendMessageRequest{ @@ -594,7 +594,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } } - content := c.ProcessOutgoingMessage(0, rawCmdArguments(cmdline, 1), "", "", 0) + content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 1)) if content != nil { _, err := c.client.SendMessage(&client.SendMessageRequest{ diff --git a/telegram/utils.go b/telegram/utils.go index 0a2ae1f..dd63248 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -986,22 +986,27 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { } } +// PrepareMessageContent creates a simple text message +func (c *Client) PrepareOutgoingMessageContent(text string) client.InputMessageContent { + return c.prepareOutgoingMessageContent(text, nil) +} + // ProcessOutgoingMessage executes commands or sends messages to mapped chats -func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, id string, replyId int64) client.InputMessageContent { +func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, id string, replyId int64) { if !c.Online() { // we're offline - return nil + return } - if returnJid != "" && (strings.HasPrefix(text, "/") || strings.HasPrefix(text, "!")) { + if strings.HasPrefix(text, "/") || strings.HasPrefix(text, "!") { // try to execute commands response, isCommand := c.ProcessChatCommand(chatID, text) if response != "" { - gateway.SendTextMessage(returnJid, strconv.FormatInt(chatID, 10), response, c.xmpp) + c.returnMessage(returnJid, chatID, response) } // do not send on success if isCommand { - return nil + return } } @@ -1020,60 +1025,30 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str // attach a file var file *client.InputFileLocal - if chatID != 0 && c.content.Upload != "" && strings.HasPrefix(text, c.content.Upload) { + if c.content.Upload != "" && strings.HasPrefix(text, c.content.Upload) { response, err := http.Get(text) if err != nil { - gateway.SendTextMessage( - returnJid, - strconv.FormatInt(chatID, 10), - fmt.Sprintf("Failed to fetch the uploaded file: %s", err.Error()), - c.xmpp, - ) - return nil + c.returnError(returnJid, chatID, "Failed to fetch the uploaded file", err) } if response != nil && response.Body != nil { defer response.Body.Close() if response.StatusCode != 200 { - gateway.SendTextMessage( - returnJid, - strconv.FormatInt(chatID, 10), - fmt.Sprintf("Received status code %v", response.StatusCode), - c.xmpp, - ) - return nil + c.returnMessage(returnJid, chatID, fmt.Sprintf("Received status code %v", response.StatusCode)) } tempDir, err := ioutil.TempDir("", "telegabber-*") if err != nil { - gateway.SendTextMessage( - returnJid, - strconv.FormatInt(chatID, 10), - fmt.Sprintf("Failed to create a temporary directory: %s", err.Error()), - c.xmpp, - ) - return nil + c.returnError(returnJid, chatID, "Failed to create a temporary directory", err) } tempFile, err := os.Create(filepath.Join(tempDir, filepath.Base(text))) if err != nil { - gateway.SendTextMessage( - returnJid, - strconv.FormatInt(chatID, 10), - fmt.Sprintf("Failed to create a temporary file: %s", err.Error()), - c.xmpp, - ) - return nil + c.returnError(returnJid, chatID, "Failed to create a temporary file", err) } _, err = io.Copy(tempFile, response.Body) if err != nil { - gateway.SendTextMessage( - returnJid, - strconv.FormatInt(chatID, 10), - fmt.Sprintf("Failed to write a temporary file: %s", err.Error()), - c.xmpp, - ) - return nil + c.returnError(returnJid, chatID, "Failed to write a temporary file", err) } file = &client.InputFileLocal{ @@ -1092,47 +1067,55 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str } } + content := c.prepareOutgoingMessageContent(text, file) + + tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{ + ChatId: chatID, + ReplyToMessageId: reply, + InputMessageContent: content, + }) + if err != nil { + gateway.SendTextMessage( + returnJid, + strconv.FormatInt(chatID, 10), + fmt.Sprintf("Not sent: %s", err.Error()), + c.xmpp, + ) + } else { + err = gateway.IdsDB.Set(c.Session.Login, c.jid, tgMessage.ChatId, tgMessage.Id, id) + if err != nil { + log.Errorf("Failed to save ids %v/%v %v", tgMessage.ChatId, tgMessage.Id, id) + } + } +} + +func (c *Client) returnMessage(returnJid string, chatID int64, text string) { + gateway.SendTextMessage(returnJid, strconv.FormatInt(chatID, 10), text, c.xmpp) +} + +func (c *Client) returnError(returnJid string, chatID int64, msg string, err error) { + c.returnMessage(returnJid, chatID, fmt.Sprintf("%s: %s", msg, err.Error())) +} + +func (c *Client) prepareOutgoingMessageContent(text string, file *client.InputFileLocal) client.InputMessageContent { formattedText := &client.FormattedText{ Text: text, } - var message client.InputMessageContent + var content client.InputMessageContent if file != nil { // we can try to send a document - message = &client.InputMessageDocument{ + content = &client.InputMessageDocument{ Document: file, Caption: formattedText, } } else { // compile our message - message = &client.InputMessageText{ + content = &client.InputMessageText{ Text: formattedText, } } - - if chatID != 0 { - tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{ - ChatId: chatID, - ReplyToMessageId: reply, - InputMessageContent: message, - }) - if err != nil { - gateway.SendTextMessage( - returnJid, - strconv.FormatInt(chatID, 10), - fmt.Sprintf("Not sent: %s", err.Error()), - c.xmpp, - ) - } else { - err = gateway.IdsDB.Set(c.Session.Login, c.jid, tgMessage.ChatId, tgMessage.Id, id) - if err != nil { - log.Errorf("Failed to save ids %v/%v %v", tgMessage.ChatId, tgMessage.Id, id) - } - } - return nil - } else { - return message - } + return content } // StatusesRange proxies the following function from unexported cache From 7215d11d7973b9896c6223938649c75165fa3ae7 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 5 Jun 2023 04:22:13 -0400 Subject: [PATCH 24/51] XEP-0308 message editing --- badger/ids.go | 98 +++++++++++++++++++++++++++++++++++ telegram/handlers.go | 5 ++ telegram/utils.go | 39 ++++++++------ xmpp/extensions/extensions.go | 17 ++++++ xmpp/handlers.go | 43 ++++++++++++++- 5 files changed, 184 insertions(+), 18 deletions(-) diff --git a/badger/ids.go b/badger/ids.go index 80fb9ad..079887a 100644 --- a/badger/ids.go +++ b/badger/ids.go @@ -121,6 +121,104 @@ func splitTgByteString(val []byte) (int64, int64, error) { return tgChatId, tgMsgId, err } +// ReplaceIdPair replaces an old entry by XMPP ID with both new XMPP and Tg ID +func (db *IdsDB) ReplaceIdPair(tgAccount, xmppAccount, oldXmppId, newXmppId string, newMsgId int64) error { + // read old pair + chatId, oldMsgId, err := db.GetByXmppId(tgAccount, xmppAccount, oldXmppId) + if err != nil { + return err + } + + bPrefix := toKeyPrefix(tgAccount, xmppAccount) + + bOldTgId := toTgByteString(chatId, oldMsgId) + bOldXmppId := toXmppByteString(oldXmppId) + bOldTgKey := toByteKey(bPrefix, bOldTgId, "tg") + bOldXmppKey := toByteKey(bPrefix, bOldXmppId, "xmpp") + + bTgId := toTgByteString(chatId, newMsgId) + bXmppId := toXmppByteString(newXmppId) + bTgKey := toByteKey(bPrefix, bTgId, "tg") + bXmppKey := toByteKey(bPrefix, bXmppId, "xmpp") + + return db.db.Update(func(txn *badger.Txn) error { + // save new pair + if err := txn.Set(bTgKey, bXmppId); err != nil { + return err + } + if err := txn.Set(bXmppKey, bTgId); err != nil { + return err + } + // delete old pair + if err := txn.Delete(bOldTgKey); err != nil { + return err + } + return txn.Delete(bOldXmppKey) + }) +} + +// ReplaceXmppId replaces an old XMPP ID with new XMPP ID and keeps Tg ID intact +func (db *IdsDB) ReplaceXmppId(tgAccount, xmppAccount, oldXmppId, newXmppId string) error { + // read old Tg IDs + chatId, msgId, err := db.GetByXmppId(tgAccount, xmppAccount, oldXmppId) + if err != nil { + return err + } + + bPrefix := toKeyPrefix(tgAccount, xmppAccount) + + bOldXmppId := toXmppByteString(oldXmppId) + bOldXmppKey := toByteKey(bPrefix, bOldXmppId, "xmpp") + + bTgId := toTgByteString(chatId, msgId) + bXmppId := toXmppByteString(newXmppId) + bTgKey := toByteKey(bPrefix, bTgId, "tg") + bXmppKey := toByteKey(bPrefix, bXmppId, "xmpp") + + return db.db.Update(func(txn *badger.Txn) error { + // save new pair + if err := txn.Set(bTgKey, bXmppId); err != nil { + return err + } + if err := txn.Set(bXmppKey, bTgId); err != nil { + return err + } + // delete old xmpp->tg entry + return txn.Delete(bOldXmppKey) + }) +} + +// ReplaceTgId replaces an old Tg ID with new Tg ID and keeps Tg chat ID and XMPP ID intact +func (db *IdsDB) ReplaceTgId(tgAccount, xmppAccount string, chatId, oldMsgId, newMsgId int64) error { + // read old XMPP ID + xmppId, err := db.GetByTgIds(tgAccount, xmppAccount, chatId, oldMsgId) + if err != nil { + return err + } + + bPrefix := toKeyPrefix(tgAccount, xmppAccount) + + bOldTgId := toTgByteString(chatId, oldMsgId) + bOldTgKey := toByteKey(bPrefix, bOldTgId, "tg") + + bTgId := toTgByteString(chatId, newMsgId) + bXmppId := toXmppByteString(xmppId) + bTgKey := toByteKey(bPrefix, bTgId, "tg") + bXmppKey := toByteKey(bPrefix, bXmppId, "xmpp") + + return db.db.Update(func(txn *badger.Txn) error { + // save new pair + if err := txn.Set(bTgKey, bXmppId); err != nil { + return err + } + if err := txn.Set(bXmppKey, bTgId); err != nil { + return err + } + // delete old tg->xmpp entry + return txn.Delete(bOldTgKey) + }) +} + // Gc compacts the value log func (db *IdsDB) Gc() { db.db.RunValueLogGC(0.7) diff --git a/telegram/handlers.go b/telegram/handlers.go index bd768ae..6de59e7 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -272,6 +272,11 @@ func (c *Client) updateAuthorizationState(update *client.UpdateAuthorizationStat // clean uploaded files func (c *Client) updateMessageSendSucceeded(update *client.UpdateMessageSendSucceeded) { + log.Debugf("replace message %v with %v", update.OldMessageId, update.Message.Id) + if err := gateway.IdsDB.ReplaceTgId(c.Session.Login, c.jid, update.Message.ChatId, update.OldMessageId, update.Message.Id); err != nil { + log.Error("failed to replace %v with %v: %v", update.OldMessageId, update.Message.Id, err.Error()) + } + file, _ := c.contentToFile(update.Message.Content) if file != nil && file.Local != nil { c.cleanTempFile(file.Local.Path) diff --git a/telegram/utils.go b/telegram/utils.go index dd63248..ecce6da 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -991,14 +991,14 @@ func (c *Client) PrepareOutgoingMessageContent(text string) client.InputMessageC return c.prepareOutgoingMessageContent(text, nil) } -// ProcessOutgoingMessage executes commands or sends messages to mapped chats -func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, id string, replyId int64) { +// ProcessOutgoingMessage executes commands or sends messages to mapped chats, returns message id +func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, replyId int64, replaceId int64) int64 { if !c.Online() { // we're offline - return + return 0 } - if strings.HasPrefix(text, "/") || strings.HasPrefix(text, "!") { + if replaceId == 0 && (strings.HasPrefix(text, "/") || strings.HasPrefix(text, "!")) { // try to execute commands response, isCommand := c.ProcessChatCommand(chatID, text) if response != "" { @@ -1006,7 +1006,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str } // do not send on success if isCommand { - return + return 0 } } @@ -1014,7 +1014,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str // quotations var reply int64 - if replyId == 0 { + if replaceId == 0 && replyId == 0 { replySlice := replyRegex.FindStringSubmatch(text) if len(replySlice) > 1 { reply, _ = strconv.ParseInt(replySlice[1], 10, 64) @@ -1069,24 +1069,29 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str content := c.prepareOutgoingMessageContent(text, file) + if replaceId != 0 { + tgMessage, err := c.client.EditMessageText(&client.EditMessageTextRequest{ + ChatId: chatID, + MessageId: replaceId, + InputMessageContent: content, + }) + if err != nil { + c.returnError(returnJid, chatID, "Not edited", err) + return 0 + } + return tgMessage.Id + } + tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{ ChatId: chatID, ReplyToMessageId: reply, InputMessageContent: content, }) if err != nil { - gateway.SendTextMessage( - returnJid, - strconv.FormatInt(chatID, 10), - fmt.Sprintf("Not sent: %s", err.Error()), - c.xmpp, - ) - } else { - err = gateway.IdsDB.Set(c.Session.Login, c.jid, tgMessage.ChatId, tgMessage.Id, id) - if err != nil { - log.Errorf("Failed to save ids %v/%v %v", tgMessage.ChatId, tgMessage.Id, id) - } + c.returnError(returnJid, chatID, "Not sent", err) + return 0 } + return tgMessage.Id } func (c *Client) returnMessage(returnJid string, chatID int64, text string) { diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 78de47d..2d547af 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -180,6 +180,12 @@ type ClientMessage struct { Extensions []stanza.MsgExtension `xml:",omitempty"` } +// Replace is from XEP-0308 +type Replace struct { + XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"` + Id string `xml:"id,attr"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space @@ -225,6 +231,11 @@ func (c ComponentPrivilege) Namespace() string { return c.XMLName.Space } +// Namespace is a namespace! +func (c Replace) Namespace() string { + return c.XMLName.Space +} + // Name is a packet name func (ClientMessage) Name() string { return "message" @@ -291,4 +302,10 @@ func init() { "urn:xmpp:privilege:1", "privilege", }, ComponentPrivilege{}) + + // message edit + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:message-correct:0", + "replace", + }, Replace{}) } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 5178acd..51fd831 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -102,10 +102,13 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { if ok { var reply extensions.Reply var fallback extensions.Fallback + var replace extensions.Replace msg.Get(&reply) msg.Get(&fallback) + msg.Get(&replace) log.Debugf("reply: %#v", reply) log.Debugf("fallback: %#v", fallback) + log.Debugf("replace: %#v", replace) var replyId int64 var err error @@ -138,8 +141,46 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { text = text[:start] + text[end:] } } + var replaceId int64 + if replace.Id != "" { + chatId, msgId, err := gateway.IdsDB.GetByXmppId(session.Session.Login, bare, replace.Id) + if err == nil { + if chatId != toID { + gateway.SendTextMessage(msg.From, strconv.FormatInt(toID, 10), "", component) + return + } + replaceId = msgId + log.Debugf("replace tg: %#v %#v", chatId, msgId) + } else { + gateway.SendTextMessage(msg.From, strconv.FormatInt(toID, 10), "", component) + return + } + } - session.ProcessOutgoingMessage(toID, text, msg.From, msg.Id, replyId) + tgMessageId := session.ProcessOutgoingMessage(toID, text, msg.From, replyId, replaceId) + if tgMessageId != 0 { + if replaceId != 0 { + // not needed (is it persistent among clients though?) + /* err = gateway.IdsDB.ReplaceIdPair(session.Session.Login, bare, replace.Id, msg.Id, tgMessageId) + if err != nil { + log.Errorf("Failed to replace id %v with %v %v", replace.Id, msg.Id, tgMessageId) + } */ + } else { + err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessageId, msg.Id) + if err != nil { + log.Errorf("Failed to save ids %v/%v %v", toID, tgMessageId, msg.Id) + } + } + } else { + /* + // if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway + if replaceId != 0 { + err = gateway.IdsDB.ReplaceXmppId(session.Session.Login, bare, replace.Id, msg.Id) + if err != nil { + log.Errorf("Failed to replace id %v with %v", replace.Id, msg.Id) + } + } */ + } return } else { toJid, err := stanza.NewJid(msg.To) From 945b9c063b8bcf20f0df370fd1f68457f84f3361 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 8 Jun 2023 13:14:55 -0400 Subject: [PATCH 25/51] Reply own messages --- xmpp/handlers.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 51fd831..5034551 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -114,13 +114,23 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { var err error text := msg.Body if len(reply.Id) > 0 { - id := reply.Id - if id[0] == 'e' { - id = id[1:] - } - replyId, err = strconv.ParseInt(id, 10, 64) - if err != nil { - log.Warn(errors.Wrap(err, "Failed to parse message ID!")) + chatId, msgId, err := gateway.IdsDB.GetByXmppId(session.Session.Login, bare, reply.Id) + if err == nil { + if chatId != toID { + log.Warnf("Chat mismatch: %v ≠ %v", chatId, toID) + } else { + replyId = msgId + log.Debugf("replace tg: %#v %#v", chatId, msgId) + } + } else { + id := reply.Id + if id[0] == 'e' { + id = id[1:] + } + replyId, err = strconv.ParseInt(id, 10, 64) + if err != nil { + log.Warn(errors.Wrap(err, "Failed to parse message ID!")) + } } if replyId != 0 && fallback.For == "urn:xmpp:reply:0" && len(fallback.Body) > 0 { From 79fc0ddbe5010f54707441cb3f15c43192124ed2 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 8 Jun 2023 13:27:40 -0400 Subject: [PATCH 26/51] Set origin id, if available, to replies bridged from Telegram --- telegram/utils.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/telegram/utils.go b/telegram/utils.go index ecce6da..a4a2bd4 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -330,9 +330,13 @@ func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply, return } + replyId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, message.ChatId, message.ReplyToMessageId) + if err != nil { + replyId = strconv.FormatInt(message.ReplyToMessageId, 10) + } reply = &gateway.Reply{ Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()), - Id: strconv.FormatInt(message.ReplyToMessageId, 10), + Id: replyId, } } From 00f1417cb2ed8199633a12989e9befb53298d407 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 8 Jun 2023 13:33:22 -0400 Subject: [PATCH 27/51] Version 1.6.0 --- badger/ids.go | 2 +- telegabber.go | 2 +- telegram/commands.go | 2 +- telegram/handlers.go | 2 +- telegram/utils.go | 12 ++++++------ xmpp/handlers.go | 16 ++++++++-------- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/badger/ids.go b/badger/ids.go index 079887a..1295e8a 100644 --- a/badger/ids.go +++ b/badger/ids.go @@ -92,7 +92,7 @@ func toKeyPrefix(tgAccount, xmppAccount string) []byte { } func toByteKey(prefix, suffix []byte, typ string) []byte { - key := make([]byte, 0, len(prefix) + len(suffix) + 6) + key := make([]byte, 0, len(prefix)+len(suffix)+6) key = append(key, prefix...) key = append(key, []byte(typ)...) key = append(key, []byte("/")...) diff --git a/telegabber.go b/telegabber.go index d133d80..48db544 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.6.0-dev" +var version string = "1.6.0" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index f36108a..5e16a6a 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -652,7 +652,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) keyValueString("Chat title", info.Fn), keyValueString("Photo", link), keyValueString("Username", info.Nickname), - keyValueString("Full name", info.Given + " " + info.Family), + keyValueString("Full name", info.Given+" "+info.Family), keyValueString("Phone number", info.Tel), } return strings.Join(entries, "\n"), true diff --git a/telegram/handlers.go b/telegram/handlers.go index 6de59e7..57402ab 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -274,7 +274,7 @@ func (c *Client) updateAuthorizationState(update *client.UpdateAuthorizationStat func (c *Client) updateMessageSendSucceeded(update *client.UpdateMessageSendSucceeded) { log.Debugf("replace message %v with %v", update.OldMessageId, update.Message.Id) if err := gateway.IdsDB.ReplaceTgId(c.Session.Login, c.jid, update.Message.ChatId, update.OldMessageId, update.Message.Id); err != nil { - log.Error("failed to replace %v with %v: %v", update.OldMessageId, update.Message.Id, err.Error()) + log.Errorf("failed to replace %v with %v: %v", update.OldMessageId, update.Message.Id, err.Error()) } file, _ := c.contentToFile(update.Message.Content) diff --git a/telegram/utils.go b/telegram/utils.go index a4a2bd4..58f7712 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -25,13 +25,13 @@ import ( ) type VCardInfo struct { - Fn string - Photo *client.File + Fn string + Photo *client.File Nickname string - Given string - Family string - Tel string - Info string + Given string + Family string + Tel string + Info string } var errOffline = errors.New("TDlib instance is offline") diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 5034551..d5666b1 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -183,13 +183,13 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { } } else { /* - // if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway - if replaceId != 0 { - err = gateway.IdsDB.ReplaceXmppId(session.Session.Login, bare, replace.Id, msg.Id) - if err != nil { - log.Errorf("Failed to replace id %v with %v", replace.Id, msg.Id) - } - } */ + // if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway + if replaceId != 0 { + err = gateway.IdsDB.ReplaceXmppId(session.Session.Login, bare, replace.Id, msg.Id) + if err != nil { + log.Errorf("Failed to replace id %v with %v", replace.Id, msg.Id) + } + } */ } return } else { @@ -225,7 +225,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { suffix := "@" + msg.From for bare := range sessions { if strings.HasSuffix(bare, suffix) { - gateway.SendServiceMessage(bare, "Your server \"" + msg.From + "\" does not allow to send carbons", component) + gateway.SendServiceMessage(bare, "Your server \""+msg.From+"\" does not allow to send carbons", component) } } } From edf2c08a5b46e48277a5173653cfe8b156a412e5 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 11 Jun 2023 13:53:36 -0400 Subject: [PATCH 28/51] Bump Go version to 1.19 --- go.mod | 25 ++++++++++++++-- go.sum | 83 +++++++-------------------------------------------- telegabber.go | 2 +- 3 files changed, 33 insertions(+), 77 deletions(-) diff --git a/go.mod b/go.mod index a999878..a4e26d2 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module dev.narayana.im/narayana/telegabber -go 1.13 +go 1.19 require ( - github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7 - github.com/dgraph-io/badger/v4 v4.1.0 // indirect + github.com/dgraph-io/badger/v4 v4.1.0 github.com/pkg/errors v0.9.1 github.com/santhosh-tekuri/jsonschema v1.2.4 github.com/sirupsen/logrus v1.4.2 @@ -14,4 +13,24 @@ require ( gosrc.io/xmpp v0.5.2-0.20211214110136-5f99e1cd06e1 ) +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/klauspost/compress v1.12.3 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + go.opencensus.io v0.22.5 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + nhooyr.io/websocket v1.6.5 // indirect +) + replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f diff --git a/go.sum b/go.sum index 0134061..011bc93 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -dev.narayana.im/narayana/go-xmpp v0.0.0-20211218155535-e55463fc9829 h1:qe81G6+t1V1ySRMa7lSu5CayN5aP5GEiHXL2DYwHzuA= -dev.narayana.im/narayana/go-xmpp v0.0.0-20211218155535-e55463fc9829/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f h1:6249ajbMjgYz53Oq0IjTvjHXbxTfu29Mj1J/6swRHs4= dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= -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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -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-tdlib v0.4.4-0.20211229000346-ee6018be8ec0 h1:9ysLk2hG2q0NeNdX6StzS+4fTAG2FeZJYKKegCuB4q4= -github.com/bodqhrohro/go-tdlib v0.4.4-0.20211229000346-ee6018be8ec0/go.mod h1:sOdXFpJ3zn6RHRc8aNVkJYALHpoplwBgMwIbRCYABIg= -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= -github.com/bodqhrohro/go-xmpp v0.2.1-0.20191105232737-9abd5be0aa1b/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= -github.com/bodqhrohro/go-xmpp v0.2.1-0.20211205194122-f8c4ecb59d8b h1:rTK55SNCBmssyRgNAweVwVVfuoRstI8RbL+8Ys/RzxE= -github.com/bodqhrohro/go-xmpp v0.2.1-0.20211205194122-f8c4ecb59d8b/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= -github.com/bodqhrohro/go-xmpp v0.2.1-0.20211218153313-a8aadd78b65b h1:VDi8z3PzEDhQzazRRuv1fkv662DT3Mm/TY/Lni2Sgrc= -github.com/bodqhrohro/go-xmpp v0.2.1-0.20211218153313-a8aadd78b65b/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -33,10 +13,6 @@ 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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= @@ -44,6 +20,7 @@ github.com/dgraph-io/badger/v4 v4.1.0 h1:E38jc0f+RATYrycSUf9LMv/t47XAy+3CApyYSq4 github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -56,8 +33,6 @@ github.com/go-interpreter/wagon v0.6.0/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/godcong/go-tdlib v0.4.4-0.20211203152853-64d22ab8d4ac h1:5FQGW4yHSkbwm+4i/8ef7FvkIFt4NOM4HexSbvPduRo= -github.com/godcong/go-tdlib v0.4.4-0.20211203152853-64d22ab8d4ac/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= @@ -74,17 +49,14 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 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/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190908185732-236ed259b199/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -92,11 +64,13 @@ github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/Vq github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -107,20 +81,15 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 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.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -128,41 +97,30 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e h1:Xmvww1DnnCj49ZNwQAw069Kc6X3Q0MUd9OiFQ092yQQ= github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e/go.mod h1:RUak+ZC0a3E2NDNxZmA34Hk4Jm9nJMCSKLpKeB06clM= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -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= github.com/zelenin/go-tdlib v0.5.2 h1:inEATEM0Pz6/HBI3wTlhd+brDHpmoXGgwdSb8/V6GiA= github.com/zelenin/go-tdlib v0.5.2/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= go.coder.com/go-tools v0.0.0-20190317003359-0c6a35b74a16/go.mod h1:iKV5yK9t+J5nG9O3uF6KYdPEz3dyfMyB15MN1rbQ8Qw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -170,7 +128,6 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -181,8 +138,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -192,37 +147,25 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190927073244-c990c680b611 h1:q9u40nxWT5zRClI/uU9dHCiYGottAg6Nzz4YUQyHxdA= golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -232,8 +175,6 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -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= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -248,7 +189,7 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -256,10 +197,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gosrc.io/xmpp v0.1.3 h1:VYP1bA35irlQ1ZAJqNhJOz8NSsSTkzQRhREfmuG1H80= -gosrc.io/xmpp v0.1.3/go.mod h1:fWixaMaFvx8cxXcJVJ5kU9csMeD/JN8on7ybassU8rY= -gosrc.io/xmpp v0.5.2-0.20211214110136-5f99e1cd06e1 h1:E3uJqX6ImJL9AFdjGbiW04jq8IQ+NcOK+JSiWq2TbRw= -gosrc.io/xmpp v0.5.2-0.20211214110136-5f99e1cd06e1/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/telegabber.go b/telegabber.go index 48db544..78b893b 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.6.0" +var version string = "1.6.1-dev" var commit string var sm *goxmpp.StreamManager From fdd867cf7a435346a552a8da7f446b59b5e1213e Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 16 Jun 2023 00:34:49 -0400 Subject: [PATCH 29/51] Add /cancelauth command --- telegram/commands.go | 10 +++++++++- telegram/connect.go | 30 +++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index 5e16a6a..cafeb0a 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -44,6 +44,7 @@ var permissionsReadonly = client.ChatPermissions{} var transportCommands = map[string]command{ "login": command{"phone", "sign in"}, "logout": command{"", "sign out"}, + "cancelauth": command{"", "quit the signin wizard"}, "code": command{"", "check one-time code"}, "password": command{"", "check 2fa password"}, "setusername": command{"", "update @username"}, @@ -230,7 +231,7 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string switch cmd { case "login", "code", "password": if cmd == "login" && c.Session.Login != "" { - return "" + return "Phone number already provided, use /cancelauth to start over" } if len(args) < 1 { @@ -286,6 +287,13 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } c.Session.Login = "" + // cancel auth + case "cancelauth": + if c.Online() { + return "Not allowed when online" + } + c.cancelAuth() + return "Cancelled" // set @username case "setusername": if !c.Online() { diff --git a/telegram/connect.go b/telegram/connect.go index 2633980..8324319 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -24,6 +24,9 @@ type clientAuthorizer struct { } func (stateHandler *clientAuthorizer) Handle(c *client.Client, state client.AuthorizationState) error { + if stateHandler.isClosed { + return errors.New("Channel is closed") + } stateHandler.State <- state switch state.AuthorizationStateType() { @@ -84,6 +87,9 @@ func (stateHandler *clientAuthorizer) Handle(c *client.Client, state client.Auth } func (stateHandler *clientAuthorizer) Close() { + if stateHandler.isClosed { + return + } stateHandler.isClosed = true close(stateHandler.TdlibParameters) close(stateHandler.PhoneNumber) @@ -191,11 +197,7 @@ func (c *Client) Disconnect(resource string, quit bool) bool { ) } - _, err := c.client.Close() - if err != nil { - log.Errorf("Couldn't close the Telegram instance: %v; %#v", err, c) - } - c.forceClose() + c.close() return true } @@ -242,6 +244,24 @@ func (c *Client) forceClose() { c.authorizer = nil } +func (c *Client) close() { + if c.authorizer != nil && !c.authorizer.isClosed { + c.authorizer.Close() + } + if c.client != nil { + _, err := c.client.Close() + if err != nil { + log.Errorf("Couldn't close the Telegram instance: %v; %#v", err, c) + } + } + c.forceClose() +} + +func (c *Client) cancelAuth() { + c.close() + c.Session.Login = "" +} + // Online checks if the updates listener is alive func (c *Client) Online() bool { return c.online From 739fc4110a9c12aa51cbd6361e5c4398c8ed7ff8 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 16 Jun 2023 00:44:48 -0400 Subject: [PATCH 30/51] Fix a crash by auth commands when online --- telegram/commands.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/telegram/commands.go b/telegram/commands.go index cafeb0a..2f879b1 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -259,6 +259,10 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string return telegramNotInitialized } + if c.authorizer.isClosed { + return "Authorization is done already" + } + switch cmd { // sign in case "login": @@ -290,7 +294,7 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string // cancel auth case "cancelauth": if c.Online() { - return "Not allowed when online" + return "Not allowed when online, use /logout instead" } c.cancelAuth() return "Cancelled" From f8ad8c0204e2effabce4545c91992146a560168d Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 30 Jun 2023 08:48:36 -0400 Subject: [PATCH 31/51] Update chat title in chats cache --- telegram/handlers.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/telegram/handlers.go b/telegram/handlers.go index 57402ab..65e5f2f 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -294,8 +294,13 @@ func (c *Client) updateChatTitle(update *client.UpdateChatTitle) { gateway.SetNickname(c.jid, strconv.FormatInt(update.ChatId, 10), update.Title, c.xmpp) // set also the status (for group chats only) - _, user, _ := c.GetContactByID(update.ChatId, nil) + chat, user, _ := c.GetContactByID(update.ChatId, nil) if user == nil { c.ProcessStatusUpdate(update.ChatId, update.Title, "chat", gateway.SPImmed(true)) } + + // update chat title in the cache + if chat != nil { + chat.Title = update.Title + } } From 30b3fd16153fab727315f014150d98b367ccd6ad Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 30 Jun 2023 09:54:39 -0400 Subject: [PATCH 32/51] Force update nicknames via PubSub and presences on reconnect --- telegram/utils.go | 15 +++++++++++++++ xmpp/handlers.go | 1 + 2 files changed, 16 insertions(+) diff --git a/telegram/utils.go b/telegram/utils.go index 58f7712..a9e0bc8 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -1332,3 +1332,18 @@ func (c *Client) GetVcardInfo(toID int64) (VCardInfo, error) { return info, nil } + +func (c *Client) UpdateChatNicknames() { + for _, id := range c.cache.ChatsKeys() { + chat, ok := c.cache.GetChat(id) + if ok { + gateway.SendPresence( + c.xmpp, + c.jid, + gateway.SPFrom(strconv.FormatInt(id, 10)), + gateway.SPNickname(chat.Title), + ) + gateway.SetNickname(c.jid, strconv.FormatInt(id, 10), chat.Title, c.xmpp) + } + } +} diff --git a/xmpp/handlers.go b/xmpp/handlers.go index d5666b1..1286914 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -342,6 +342,7 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) { gateway.SPImmed(false), ) } + session.UpdateChatNicknames() } }() } From 959dc061ff30ba1cf5c699adc0f7d1d991d7afa5 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 8 Jul 2023 23:52:30 -0400 Subject: [PATCH 33/51] Send carbons for outgoing messages to other resources --- telegram/client.go | 6 +++- telegram/commands.go | 7 ++-- telegram/handlers.go | 28 +++++++++------- telegram/utils.go | 74 +++++++++++++++++++++++++++++------------ xmpp/gateway/gateway.go | 14 ++++---- xmpp/handlers.go | 3 ++ 6 files changed, 89 insertions(+), 43 deletions(-) diff --git a/telegram/client.go b/telegram/client.go index 71d8125..61d46aa 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -52,6 +52,7 @@ type Client struct { jid string Session *persistence.Session resources map[string]bool + outbox map[string]string content *config.TelegramContentConfig cache *cache.Cache online bool @@ -59,13 +60,15 @@ type Client struct { DelayedStatuses map[int64]*DelayedStatus DelayedStatusesLock sync.Mutex - locks clientLocks + locks clientLocks + SendMessageLock sync.Mutex } type clientLocks struct { authorizationReady sync.Mutex chatMessageLocks map[int64]*sync.Mutex resourcesLock sync.Mutex + outboxLock sync.Mutex } // NewClient instantiates a Telegram App @@ -121,6 +124,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component jid: jid, Session: session, resources: make(map[string]bool), + outbox: make(map[string]string), content: &conf.Content, cache: cache.NewCache(), options: options, diff --git a/telegram/commands.go b/telegram/commands.go index 2f879b1..53b5d75 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -513,11 +513,14 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 0)) if content != nil { - c.client.EditMessageText(&client.EditMessageTextRequest{ + _, err = c.client.EditMessageText(&client.EditMessageTextRequest{ ChatId: chatID, MessageId: message.Id, InputMessageContent: content, }) + if err != nil { + return "Message editing error", true + } } else { return "Message processing error", true } @@ -650,7 +653,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } if messages != nil && messages.Messages != nil { for _, message := range messages.Messages { - c.ProcessIncomingMessage(targetChatId, message) + c.ProcessIncomingMessage(targetChatId, message, "") } } // print vCard diff --git a/telegram/handlers.go b/telegram/handlers.go index 65e5f2f..8173ecd 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -203,26 +203,30 @@ func (c *Client) updateChatLastMessage(update *client.UpdateChatLastMessage) { // message received func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { - go func() { - chatId := update.Message.ChatId + chatId := update.Message.ChatId - // guarantee sequential message delivering per chat - lock := c.getChatMessageLock(chatId) + c.SendMessageLock.Lock() + c.SendMessageLock.Unlock() + xmppId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, chatId, update.Message.Id) + var ignoredResource string + if err == nil { + ignoredResource = c.popFromOutbox(xmppId) + } else { + log.Infof("Couldn't retrieve XMPP message ids for %v, an echo may happen", update.Message.Id) + } + log.Warnf("xmppId: %v, ignoredResource: %v", xmppId, ignoredResource) + + // guarantee sequential message delivering per chat + lock := c.getChatMessageLock(chatId) + go func() { lock.Lock() defer lock.Unlock() - // ignore self outgoing messages - if update.Message.IsOutgoing && - update.Message.SendingState != nil && - update.Message.SendingState.MessageSendingStateType() == client.TypeMessageSendingStatePending { - return - } - log.WithFields(log.Fields{ "chat_id": chatId, }).Warn("New message from chat") - c.ProcessIncomingMessage(chatId, update.Message) + c.ProcessIncomingMessage(chatId, update.Message, ignoredResource) }() } diff --git a/telegram/utils.go b/telegram/utils.go index a9e0bc8..9664857 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -890,9 +890,34 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { } // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side -func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { - var text, oob, auxText string +func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message, ignoredResource string) { + var jids []string + var isPM bool var err error + if gateway.MessageOutgoingPermission && c.Session.Carbons { + isPM, err = c.IsPM(chatId) + if err != nil { + log.Errorf("Could not determine if chat is PM: %v", err) + } + } + isOutgoing := message.IsOutgoing + isCarbon := isPM && isOutgoing + log.Warnf("isOutgoing: %v", isOutgoing) + if isOutgoing { + for resource := range c.resourcesRange() { + if ignoredResource == "" || resource != ignoredResource { + jids = append(jids, c.jid+"/"+resource) + } + } + if len(jids) == 0 { + log.Info("The only resource is ignored, aborting") + return + } + } else { + jids = []string{c.jid} + } + + var text, oob, auxText string reply, replyMsg := c.getMessageReply(message) @@ -965,27 +990,10 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { sId := strconv.FormatInt(message.Id, 10) sChatId := strconv.FormatInt(chatId, 10) - var jids []string - var isPM bool - if gateway.MessageOutgoingPermission && c.Session.Carbons { - isPM, err = c.IsPM(chatId) - if err != nil { - log.Errorf("Could not determine if chat is PM: %v", err) - } - } - isOutgoing := isPM && message.IsOutgoing - if isOutgoing { - for resource := range c.resourcesRange() { - jids = append(jids, c.jid+"/"+resource) - } - } else { - jids = []string{c.jid} - } - for _, jid := range jids { - gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isOutgoing) + gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isCarbon) if auxText != "" { - gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, isOutgoing) + gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, isCarbon) } } } @@ -1172,9 +1180,12 @@ func (c *Client) resourcesRange() chan string { // resend statuses to (to another resource, for example) func (c *Client) roster(resource string) { + c.locks.resourcesLock.Lock() if _, ok := c.resources[resource]; ok { + c.locks.resourcesLock.Unlock() return // we know it } + c.locks.resourcesLock.Unlock() log.Warnf("Sending roster for %v", resource) @@ -1347,3 +1358,24 @@ func (c *Client) UpdateChatNicknames() { } } } + +// AddToOutbox remembers the resource from which a message with given ID was sent +func (c *Client) AddToOutbox(xmppId, resource string) { + c.locks.outboxLock.Lock() + defer c.locks.outboxLock.Unlock() + + c.outbox[xmppId] = resource +} + +func (c *Client) popFromOutbox(xmppId string) string { + c.locks.outboxLock.Lock() + defer c.locks.outboxLock.Unlock() + + resource, ok := c.outbox[xmppId] + if ok { + delete(c.outbox, xmppId) + } else { + log.Warnf("No %v xmppId in outbox", xmppId) + } + return resource +} diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 29c8a07..7e54ee5 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -42,8 +42,8 @@ var DirtySessions = false var MessageOutgoingPermission = false // SendMessage creates and sends a message stanza -func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isOutgoing bool) { - sendMessageWrapper(to, from, body, id, component, reply, "", isOutgoing) +func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isCarbon bool) { + sendMessageWrapper(to, from, body, id, component, reply, "", isCarbon) } // SendServiceMessage creates and sends a simple message stanza from transport @@ -57,11 +57,11 @@ func SendTextMessage(to string, from string, body string, component *xmpp.Compon } // SendMessageWithOOB creates and sends a message stanza with OOB URL -func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isOutgoing bool) { - sendMessageWrapper(to, from, body, id, component, reply, oob, isOutgoing) +func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isCarbon bool) { + sendMessageWrapper(to, from, body, id, component, reply, oob, isCarbon) } -func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isOutgoing bool) { +func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isCarbon bool) { toJid, err := stanza.NewJid(to) if err != nil { log.WithFields(log.Fields{ @@ -83,7 +83,7 @@ func sendMessageWrapper(to string, from string, body string, id string, componen logFrom = from messageFrom = from + "@" + componentJid } - if isOutgoing { + if isCarbon { messageTo = messageFrom messageFrom = bareTo + "/" + Jid.Resource } else { @@ -120,7 +120,7 @@ func sendMessageWrapper(to string, from string, body string, id string, componen } } - if isOutgoing { + if isCarbon { carbonMessage := extensions.ClientMessage{ Attrs: stanza.Attrs{ From: bareTo, diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 1286914..e6671bc 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -167,6 +167,8 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { } } + session.SendMessageLock.Lock() + defer session.SendMessageLock.Unlock() tgMessageId := session.ProcessOutgoingMessage(toID, text, msg.From, replyId, replaceId) if tgMessageId != 0 { if replaceId != 0 { @@ -181,6 +183,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { log.Errorf("Failed to save ids %v/%v %v", toID, tgMessageId, msg.Id) } } + session.AddToOutbox(msg.Id, resource) } else { /* // if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway From e954c73bd2d881bc448b6bb2b3cb3a4ad37d0139 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 15 Jul 2023 21:38:10 -0400 Subject: [PATCH 34/51] Do not ack with edited message to the XEP-0308 sender resource --- telegram/handlers.go | 23 +++++++++++++++++++++-- telegram/utils.go | 33 +++++++++++++++++++-------------- xmpp/handlers.go | 3 ++- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/telegram/handlers.go b/telegram/handlers.go index 8173ecd..abc1f5d 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -214,7 +214,6 @@ func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { } else { log.Infof("Couldn't retrieve XMPP message ids for %v, an echo may happen", update.Message.Id) } - log.Warnf("xmppId: %v, ignoredResource: %v", xmppId, ignoredResource) // guarantee sequential message delivering per chat lock := c.getChatMessageLock(chatId) @@ -233,6 +232,24 @@ func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { // message content updated func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { markupFunction := formatter.EntityToXEP0393 + + c.SendMessageLock.Lock() + c.SendMessageLock.Unlock() + xmppId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, update.ChatId, update.MessageId) + var ignoredResource string + if err == nil { + ignoredResource = c.popFromOutbox(xmppId) + } else { + log.Infof("Couldn't retrieve XMPP message ids for %v, an echo may happen", update.MessageId) + } + log.Infof("ignoredResource: %v", ignoredResource) + + jids := c.getCarbonFullJids(true, ignoredResource) + if len(jids) == 0 { + log.Info("The only resource is ignored, aborting") + return + } + if update.NewContent.MessageContentType() == client.TypeMessageText { textContent := update.NewContent.(*client.MessageText) var editChar string @@ -246,7 +263,9 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { textContent.Text.Entities, markupFunction, )) - gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false) + for _, jid := range jids { + gateway.SendMessage(jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false) + } } } diff --git a/telegram/utils.go b/telegram/utils.go index 9664857..9486349 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -891,7 +891,6 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message, ignoredResource string) { - var jids []string var isPM bool var err error if gateway.MessageOutgoingPermission && c.Session.Carbons { @@ -900,21 +899,13 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message, i log.Errorf("Could not determine if chat is PM: %v", err) } } + isOutgoing := message.IsOutgoing isCarbon := isPM && isOutgoing - log.Warnf("isOutgoing: %v", isOutgoing) - if isOutgoing { - for resource := range c.resourcesRange() { - if ignoredResource == "" || resource != ignoredResource { - jids = append(jids, c.jid+"/"+resource) - } - } - if len(jids) == 0 { - log.Info("The only resource is ignored, aborting") - return - } - } else { - jids = []string{c.jid} + jids := c.getCarbonFullJids(isOutgoing, ignoredResource) + if len(jids) == 0 { + log.Info("The only resource is ignored, aborting") + return } var text, oob, auxText string @@ -1379,3 +1370,17 @@ func (c *Client) popFromOutbox(xmppId string) string { } return resource } + +func (c *Client) getCarbonFullJids(isOutgoing bool, ignoredResource string) []string { + var jids []string + if isOutgoing { + for resource := range c.resourcesRange() { + if ignoredResource == "" || resource != ignoredResource { + jids = append(jids, c.jid+"/"+resource) + } + } + } else { + jids = []string{c.jid} + } + return jids +} diff --git a/xmpp/handlers.go b/xmpp/handlers.go index e6671bc..c5ec029 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -177,13 +177,14 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { if err != nil { log.Errorf("Failed to replace id %v with %v %v", replace.Id, msg.Id, tgMessageId) } */ + session.AddToOutbox(replace.Id, resource) } else { err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessageId, msg.Id) if err != nil { log.Errorf("Failed to save ids %v/%v %v", toID, tgMessageId, msg.Id) } + session.AddToOutbox(msg.Id, resource) } - session.AddToOutbox(msg.Id, resource) } else { /* // if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway From 563cb2d624598efdd3819daef00c64079d8a20e1 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 16 Jul 2023 08:19:11 -0400 Subject: [PATCH 35/51] Avoid webpage preview updates being sent as message edits --- telegram/client.go | 3 +++ telegram/handlers.go | 20 ++++++++++++++++++++ telegram/utils.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/telegram/client.go b/telegram/client.go index 61d46aa..5cc15a4 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -53,6 +53,7 @@ type Client struct { Session *persistence.Session resources map[string]bool outbox map[string]string + editQueue map[ChatMessageId]bool content *config.TelegramContentConfig cache *cache.Cache online bool @@ -69,6 +70,7 @@ type clientLocks struct { chatMessageLocks map[int64]*sync.Mutex resourcesLock sync.Mutex outboxLock sync.Mutex + editQueueLock sync.Mutex } // NewClient instantiates a Telegram App @@ -125,6 +127,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component Session: session, resources: make(map[string]bool), outbox: make(map[string]string), + editQueue: make(map[ChatMessageId]bool), content: &conf.Content, cache: cache.NewCache(), options: options, diff --git a/telegram/handlers.go b/telegram/handlers.go index abc1f5d..93ac284 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -104,6 +104,13 @@ func (c *Client) updateHandler() { } c.updateNewMessage(typedUpdate) log.Debugf("%#v", typedUpdate.Message) + case client.TypeUpdateMessageEdited: + typedUpdate, ok := update.(*client.UpdateMessageEdited) + if !ok { + uhOh() + } + c.updateMessageEdited(typedUpdate) + log.Debugf("%#v", typedUpdate) case client.TypeUpdateMessageContent: typedUpdate, ok := update.(*client.UpdateMessageContent) if !ok { @@ -229,6 +236,11 @@ func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { }() } +// message content edited +func (c *Client) updateMessageEdited(update *client.UpdateMessageEdited) { + c.addToEditQueue(update.ChatId, update.MessageId) +} + // message content updated func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { markupFunction := formatter.EntityToXEP0393 @@ -244,6 +256,14 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { } log.Infof("ignoredResource: %v", ignoredResource) + if !c.deleteFromEditQueue(update.ChatId, update.MessageId) { + log.WithFields(log.Fields{ + "chatId": update.ChatId, + "messageId": update.MessageId, + }).Infof("Content update with no preceding message edit, ignoring") + return + } + jids := c.getCarbonFullJids(true, ignoredResource) if len(jids) == 0 { log.Info("The only resource is ignored, aborting") diff --git a/telegram/utils.go b/telegram/utils.go index 9486349..da66189 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -24,6 +24,7 @@ import ( "github.com/zelenin/go-tdlib/client" ) +// VCardInfo contains intermediate data to produce a vCard type VCardInfo struct { Fn string Photo *client.File @@ -34,6 +35,12 @@ type VCardInfo struct { Info string } +// ChatMessageId uniquely identifies a Telegram message +type ChatMessageId struct { + ChatId int64 + MessageId int64 +} + var errOffline = errors.New("TDlib instance is offline") var spaceRegex = regexp.MustCompile(`\s+`) @@ -1384,3 +1391,29 @@ func (c *Client) getCarbonFullJids(isOutgoing bool, ignoredResource string) []st } return jids } + +func (c *Client) addToEditQueue(chatId, messageId int64) { + c.locks.editQueueLock.Lock() + defer c.locks.editQueueLock.Unlock() + + c.editQueue[ChatMessageId{ + ChatId: chatId, + MessageId: messageId, + }] = true +} + +func (c *Client) deleteFromEditQueue(chatId, messageId int64) bool { + c.locks.editQueueLock.Lock() + defer c.locks.editQueueLock.Unlock() + + key := ChatMessageId{ + ChatId: chatId, + MessageId: messageId, + } + _, ok := c.editQueue[key] + if ok { + delete(c.editQueue, key) + } + + return ok +} From eadef987be11dc22a89c2aad990814bd89add770 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 21 Jul 2023 07:45:44 -0400 Subject: [PATCH 36/51] Revert "Avoid webpage preview updates being sent as message edits" This reverts commit 563cb2d624598efdd3819daef00c64079d8a20e1. --- telegram/client.go | 3 --- telegram/handlers.go | 20 -------------------- telegram/utils.go | 33 --------------------------------- 3 files changed, 56 deletions(-) diff --git a/telegram/client.go b/telegram/client.go index 5cc15a4..61d46aa 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -53,7 +53,6 @@ type Client struct { Session *persistence.Session resources map[string]bool outbox map[string]string - editQueue map[ChatMessageId]bool content *config.TelegramContentConfig cache *cache.Cache online bool @@ -70,7 +69,6 @@ type clientLocks struct { chatMessageLocks map[int64]*sync.Mutex resourcesLock sync.Mutex outboxLock sync.Mutex - editQueueLock sync.Mutex } // NewClient instantiates a Telegram App @@ -127,7 +125,6 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component Session: session, resources: make(map[string]bool), outbox: make(map[string]string), - editQueue: make(map[ChatMessageId]bool), content: &conf.Content, cache: cache.NewCache(), options: options, diff --git a/telegram/handlers.go b/telegram/handlers.go index 93ac284..abc1f5d 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -104,13 +104,6 @@ func (c *Client) updateHandler() { } c.updateNewMessage(typedUpdate) log.Debugf("%#v", typedUpdate.Message) - case client.TypeUpdateMessageEdited: - typedUpdate, ok := update.(*client.UpdateMessageEdited) - if !ok { - uhOh() - } - c.updateMessageEdited(typedUpdate) - log.Debugf("%#v", typedUpdate) case client.TypeUpdateMessageContent: typedUpdate, ok := update.(*client.UpdateMessageContent) if !ok { @@ -236,11 +229,6 @@ func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { }() } -// message content edited -func (c *Client) updateMessageEdited(update *client.UpdateMessageEdited) { - c.addToEditQueue(update.ChatId, update.MessageId) -} - // message content updated func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { markupFunction := formatter.EntityToXEP0393 @@ -256,14 +244,6 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { } log.Infof("ignoredResource: %v", ignoredResource) - if !c.deleteFromEditQueue(update.ChatId, update.MessageId) { - log.WithFields(log.Fields{ - "chatId": update.ChatId, - "messageId": update.MessageId, - }).Infof("Content update with no preceding message edit, ignoring") - return - } - jids := c.getCarbonFullJids(true, ignoredResource) if len(jids) == 0 { log.Info("The only resource is ignored, aborting") diff --git a/telegram/utils.go b/telegram/utils.go index da66189..9486349 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -24,7 +24,6 @@ import ( "github.com/zelenin/go-tdlib/client" ) -// VCardInfo contains intermediate data to produce a vCard type VCardInfo struct { Fn string Photo *client.File @@ -35,12 +34,6 @@ type VCardInfo struct { Info string } -// ChatMessageId uniquely identifies a Telegram message -type ChatMessageId struct { - ChatId int64 - MessageId int64 -} - var errOffline = errors.New("TDlib instance is offline") var spaceRegex = regexp.MustCompile(`\s+`) @@ -1391,29 +1384,3 @@ func (c *Client) getCarbonFullJids(isOutgoing bool, ignoredResource string) []st } return jids } - -func (c *Client) addToEditQueue(chatId, messageId int64) { - c.locks.editQueueLock.Lock() - defer c.locks.editQueueLock.Unlock() - - c.editQueue[ChatMessageId{ - ChatId: chatId, - MessageId: messageId, - }] = true -} - -func (c *Client) deleteFromEditQueue(chatId, messageId int64) bool { - c.locks.editQueueLock.Lock() - defer c.locks.editQueueLock.Unlock() - - key := ChatMessageId{ - ChatId: chatId, - MessageId: messageId, - } - _, ok := c.editQueue[key] - if ok { - delete(c.editQueue, key) - } - - return ok -} From 748366ad6a9dc4b2a269d5499ae1d5d7e8526762 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sat, 22 Jul 2023 10:46:35 -0400 Subject: [PATCH 37/51] Avoid webpage preview updates being sent as message edits (by hash matching) --- telegram/client.go | 7 ++++++ telegram/handlers.go | 6 ++++- telegram/utils.go | 57 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/telegram/client.go b/telegram/client.go index 61d46aa..5b0217a 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -2,6 +2,7 @@ package telegram import ( "github.com/pkg/errors" + "hash/maphash" "path/filepath" "strconv" "sync" @@ -60,6 +61,9 @@ type Client struct { DelayedStatuses map[int64]*DelayedStatus DelayedStatusesLock sync.Mutex + lastMsgHashes map[int64]uint64 + msgHashSeed maphash.Seed + locks clientLocks SendMessageLock sync.Mutex } @@ -69,6 +73,7 @@ type clientLocks struct { chatMessageLocks map[int64]*sync.Mutex resourcesLock sync.Mutex outboxLock sync.Mutex + lastMsgHashesLock sync.Mutex } // NewClient instantiates a Telegram App @@ -129,6 +134,8 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component cache: cache.NewCache(), options: options, DelayedStatuses: make(map[int64]*DelayedStatus), + lastMsgHashes: make(map[int64]uint64), + msgHashSeed: maphash.MakeSeed(), locks: clientLocks{ chatMessageLocks: make(map[int64]*sync.Mutex), }, diff --git a/telegram/handlers.go b/telegram/handlers.go index abc1f5d..cc6e635 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -226,6 +226,8 @@ func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { }).Warn("New message from chat") c.ProcessIncomingMessage(chatId, update.Message, ignoredResource) + + c.updateLastMessageHash(update.Message.ChatId, update.Message.Id, update.Message.Content) }() } @@ -233,6 +235,8 @@ func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { markupFunction := formatter.EntityToXEP0393 + defer c.updateLastMessageHash(update.ChatId, update.MessageId, update.NewContent) + c.SendMessageLock.Lock() c.SendMessageLock.Unlock() xmppId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, update.ChatId, update.MessageId) @@ -250,7 +254,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { return } - if update.NewContent.MessageContentType() == client.TypeMessageText { + if update.NewContent.MessageContentType() == client.TypeMessageText && c.hasLastMessageHashChanged(update.ChatId, update.MessageId, update.NewContent) { textContent := update.NewContent.(*client.MessageText) var editChar string if c.Session.AsciiArrows { diff --git a/telegram/utils.go b/telegram/utils.go index 9486349..4835696 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -2,8 +2,10 @@ package telegram import ( "crypto/sha1" + "encoding/binary" "fmt" "github.com/pkg/errors" + "hash/maphash" "io" "io/ioutil" "net/http" @@ -1384,3 +1386,58 @@ func (c *Client) getCarbonFullJids(isOutgoing bool, ignoredResource string) []st } return jids } + +func (c *Client) calculateMessageHash(messageId int64, content client.MessageContent) uint64 { + var h maphash.Hash + h.SetSeed(c.msgHashSeed) + + buf8 := make([]byte, 8) + binary.BigEndian.PutUint64(buf8, uint64(messageId)) + h.Write(buf8) + + if content != nil && content.MessageContentType() == client.TypeMessageText { + textContent, ok := content.(*client.MessageText) + if !ok { + uhOh() + } + + if textContent.Text != nil { + h.WriteString(textContent.Text.Text) + for _, entity := range textContent.Text.Entities { + buf4 := make([]byte, 4) + binary.BigEndian.PutUint32(buf4, uint32(entity.Offset)) + h.Write(buf4) + binary.BigEndian.PutUint32(buf4, uint32(entity.Length)) + h.Write(buf4) + h.WriteString(entity.Type.TextEntityTypeType()) + } + } + } + + return h.Sum64() +} + +func (c *Client) updateLastMessageHash(chatId, messageId int64, content client.MessageContent) { + c.locks.lastMsgHashesLock.Lock() + defer c.locks.lastMsgHashesLock.Unlock() + + c.lastMsgHashes[chatId] = c.calculateMessageHash(messageId, content) +} + +func (c *Client) hasLastMessageHashChanged(chatId, messageId int64, content client.MessageContent) bool { + c.locks.lastMsgHashesLock.Lock() + defer c.locks.lastMsgHashesLock.Unlock() + + oldHash, ok := c.lastMsgHashes[chatId] + newHash := c.calculateMessageHash(messageId, content) + + if !ok { + log.Warnf("Last message hash for chat %v does not exist", chatId) + } + log.WithFields(log.Fields{ + "old hash": oldHash, + "new hash": newHash, + }).Info("Message hashes") + + return !ok || oldHash != newHash +} From ef831fc9725601a94149ce94c3fb686afc77e0a5 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 31 Jul 2023 21:25:24 -0400 Subject: [PATCH 38/51] Migrate to TDLib 1.8.14 (multiple usernames support) --- go.mod | 1 + go.sum | 2 ++ telegram/client.go | 4 +-- telegram/commands.go | 26 +++++++++----- telegram/connect.go | 12 ++----- telegram/handlers.go | 2 +- telegram/utils.go | 80 ++++++++++++++++++++++++++++++++---------- telegram/utils_test.go | 47 +++++++++++++++++++++++++ xmpp/handlers.go | 8 ++--- 9 files changed, 138 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index a4e26d2..db7c380 100644 --- a/go.mod +++ b/go.mod @@ -34,3 +34,4 @@ require ( ) replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f +replace github.com/zelenin/go-tdlib => dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615 diff --git a/go.sum b/go.sum index 011bc93..2565582 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615 h1:RRUZJSro+k8FkazNx7QEYLVoO4wZtchvsd0Y2RBWjeU= +dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU= dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f h1:6249ajbMjgYz53Oq0IjTvjHXbxTfu29Mj1J/6swRHs4= dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= diff --git a/telegram/client.go b/telegram/client.go index 5b0217a..e9acd20 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -45,7 +45,7 @@ type DelayedStatus struct { type Client struct { client *client.Client authorizer *clientAuthorizer - parameters *client.TdlibParameters + parameters *client.SetTdlibParametersRequest options []client.Option me *client.User @@ -100,7 +100,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component datadir = "./sessions/" // ye olde defaute } - parameters := client.TdlibParameters{ + parameters := client.SetTdlibParametersRequest{ UseTestDc: false, DatabaseDirectory: filepath.Join(datadir, jid), diff --git a/telegram/commands.go b/telegram/commands.go index 53b5d75..3828ec2 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -18,8 +18,7 @@ const notEnoughArguments string = "Not enough arguments" const telegramNotInitialized string = "Telegram connection is not initialized yet" const notOnline string = "Not online" -var permissionsAdmin = client.ChatMemberStatusAdministrator{ - CanBeEdited: true, +var permissionsAdmin = client.ChatAdministratorRights{ CanChangeInfo: true, CanPostMessages: true, CanEditMessages: true, @@ -30,14 +29,20 @@ var permissionsAdmin = client.ChatMemberStatusAdministrator{ CanPromoteMembers: false, } var permissionsMember = client.ChatPermissions{ - CanSendMessages: true, - CanSendMediaMessages: true, + CanSendBasicMessages: true, + CanSendAudios: true, + CanSendDocuments: true, + CanSendPhotos: true, + CanSendVideos: true, + CanSendVideoNotes: true, + CanSendVoiceNotes: true, CanSendPolls: true, CanSendOtherMessages: true, CanAddWebPagePreviews: true, CanChangeInfo: true, CanInviteUsers: true, CanPinMessages: true, + CanManageTopics: true, } var permissionsReadonly = client.ChatPermissions{} @@ -666,7 +671,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) entries := []string{ keyValueString("Chat title", info.Fn), keyValueString("Photo", link), - keyValueString("Username", info.Nickname), + keyValueString("Usernames", c.usernamesToString(info.Nicknames)), keyValueString("Full name", info.Given+" "+info.Family), keyValueString("Phone number", info.Tel), } @@ -883,7 +888,10 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } // clone the permissions - status := permissionsAdmin + status := client.ChatMemberStatusAdministrator{ + CanBeEdited: true, + Rights: &permissionsAdmin, + } if len(args) > 1 { status.CustomTitle = args[1] @@ -933,9 +941,9 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) return "Invalid TTL", true } } - _, err = c.client.SetChatMessageTtl(&client.SetChatMessageTtlRequest{ - ChatId: chatID, - Ttl: int32(ttl), + _, err = c.client.SetChatMessageAutoDeleteTime(&client.SetChatMessageAutoDeleteTimeRequest{ + ChatId: chatID, + MessageAutoDeleteTime: int32(ttl), }) if err != nil { diff --git a/telegram/connect.go b/telegram/connect.go index 8324319..ef03428 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -13,7 +13,7 @@ import ( const chatsLimit int32 = 999 type clientAuthorizer struct { - TdlibParameters chan *client.TdlibParameters + TdlibParameters chan *client.SetTdlibParametersRequest PhoneNumber chan string Code chan string State chan client.AuthorizationState @@ -31,13 +31,7 @@ func (stateHandler *clientAuthorizer) Handle(c *client.Client, state client.Auth switch state.AuthorizationStateType() { case client.TypeAuthorizationStateWaitTdlibParameters: - _, err := c.SetTdlibParameters(&client.SetTdlibParametersRequest{ - Parameters: <-stateHandler.TdlibParameters, - }) - return err - - case client.TypeAuthorizationStateWaitEncryptionKey: - _, err := c.CheckDatabaseEncryptionKey(&client.CheckDatabaseEncryptionKeyRequest{}) + _, err := c.SetTdlibParameters(<-stateHandler.TdlibParameters) return err case client.TypeAuthorizationStateWaitPhoneNumber: @@ -116,7 +110,7 @@ func (c *Client) Connect(resource string) error { log.Warn("Connecting to Telegram network...") c.authorizer = &clientAuthorizer{ - TdlibParameters: make(chan *client.TdlibParameters, 1), + TdlibParameters: make(chan *client.SetTdlibParametersRequest, 1), PhoneNumber: make(chan string, 1), Code: make(chan string, 1), State: make(chan client.AuthorizationState, 10), diff --git a/telegram/handlers.go b/telegram/handlers.go index cc6e635..cedea63 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -233,7 +233,7 @@ func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { // message content updated func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { - markupFunction := formatter.EntityToXEP0393 + markupFunction := c.getFormatter() defer c.updateLastMessageHash(update.ChatId, update.MessageId, update.NewContent) diff --git a/telegram/utils.go b/telegram/utils.go index 4835696..e1e9317 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -27,13 +27,13 @@ import ( ) type VCardInfo struct { - Fn string - Photo *client.File - Nickname string - Given string - Family string - Tel string - Info string + Fn string + Photo *client.File + Nicknames []string + Given string + Family string + Tel string + Info string } var errOffline = errors.New("TDlib instance is offline") @@ -286,12 +286,15 @@ func (c *Client) formatContact(chatID int64) string { if chat != nil { str = fmt.Sprintf("%s (%v)", chat.Title, chat.Id) } else if user != nil { - username := user.Username - if username == "" { - username = strconv.FormatInt(user.Id, 10) + var usernames string + if user.Usernames != nil { + usernames = c.usernamesToString(user.Usernames.ActiveUsernames) + } + if usernames == "" { + usernames = strconv.FormatInt(user.Id, 10) } - str = fmt.Sprintf("%s %s (%v)", user.FirstName, user.LastName, username) + str = fmt.Sprintf("%s %s (%v)", user.FirstName, user.LastName, usernames) } else { str = strconv.FormatInt(chatID, 10) } @@ -566,7 +569,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string { return "" } - markupFunction := formatter.EntityToXEP0393 + markupFunction := c.getFormatter() switch message.Content.MessageContentType() { case client.TypeMessageSticker: sticker, _ := message.Content.(*client.MessageSticker) @@ -737,6 +740,22 @@ func (c *Client) messageToText(message *client.Message, preview bool) string { return strings.Join(rows, "\n") } + case client.TypeMessageChatSetMessageAutoDeleteTime: + ttl, _ := message.Content.(*client.MessageChatSetMessageAutoDeleteTime) + name := c.formatContact(ttl.FromUserId) + if name == "" { + if ttl.MessageAutoDeleteTime == 0 { + return "The self-destruct timer was disabled" + } else { + return fmt.Sprintf("The self-destruct timer was set to %v seconds", ttl.MessageAutoDeleteTime) + } + } else { + if ttl.MessageAutoDeleteTime == 0 { + return fmt.Sprintf("%s disabled the self-destruct timer", name) + } else { + return fmt.Sprintf("%s set the self-destruct timer to %v seconds", name, ttl.MessageAutoDeleteTime) + } + } } return fmt.Sprintf("unknown message (%s)", message.Content.MessageContentType()) @@ -751,7 +770,7 @@ func (c *Client) contentToFile(content client.MessageContent) (*client.File, *cl case client.TypeMessageSticker: sticker, _ := content.(*client.MessageSticker) file := sticker.Sticker.Sticker - if sticker.Sticker.IsAnimated && sticker.Sticker.Thumbnail != nil && sticker.Sticker.Thumbnail.File != nil { + if sticker.Sticker.Format.StickerFormatType() != client.TypeStickerTypeRegular && sticker.Sticker.Thumbnail != nil && sticker.Sticker.Thumbnail.File != nil { file = sticker.Sticker.Thumbnail.File } return file, nil @@ -1192,7 +1211,7 @@ func (c *Client) roster(resource string) { } // get last messages from specified chat -func (c *Client) getLastMessages(id int64, query string, from int64, count int32) (*client.Messages, error) { +func (c *Client) getLastMessages(id int64, query string, from int64, count int32) (*client.FoundChatMessages, error) { return c.client.SearchChatMessages(&client.SearchChatMessagesRequest{ ChatId: id, Query: query, @@ -1245,10 +1264,18 @@ func (c *Client) GetChatDescription(chat *client.Chat) string { UserId: privateType.UserId, }) if err == nil { - if fullInfo.Bio != "" { - return fullInfo.Bio - } else if fullInfo.Description != "" { - return fullInfo.Description + if fullInfo.Bio != nil && fullInfo.Bio.Text != "" { + return formatter.Format( + fullInfo.Bio.Text, + fullInfo.Bio.Entities, + c.getFormatter(), + ) + } else if fullInfo.BotInfo != nil { + if fullInfo.BotInfo.ShortDescription != "" { + return fullInfo.BotInfo.ShortDescription + } else { + return fullInfo.BotInfo.Description + } } } else { log.Warnf("Coudln't retrieve private chat info: %v", err.Error()) @@ -1328,7 +1355,10 @@ func (c *Client) GetVcardInfo(toID int64) (VCardInfo, error) { info.Info = c.GetChatDescription(chat) } if user != nil { - info.Nickname = user.Username + if user.Usernames != nil { + info.Nicknames = make([]string, len(user.Usernames.ActiveUsernames)) + copy(info.Nicknames, user.Usernames.ActiveUsernames) + } info.Given = user.FirstName info.Family = user.LastName info.Tel = user.PhoneNumber @@ -1441,3 +1471,15 @@ func (c *Client) hasLastMessageHashChanged(chatId, messageId int64, content clie return !ok || oldHash != newHash } + +func (c *Client) getFormatter() func(*client.TextEntity) (*formatter.Insertion, *formatter.Insertion) { + return formatter.EntityToXEP0393 +} + +func (c *Client) usernamesToString(usernames []string) string { + var atUsernames []string + for _, username := range usernames { + atUsernames = append(atUsernames, "@"+username) + } + return strings.Join(atUsernames, ", ") +} diff --git a/telegram/utils_test.go b/telegram/utils_test.go index 91002ee..18be215 100644 --- a/telegram/utils_test.go +++ b/telegram/utils_test.go @@ -369,6 +369,53 @@ func TestMessageAnimation(t *testing.T) { } } +func TestMessageTtl1(t *testing.T) { + ttl := client.Message{ + Content: &client.MessageChatSetMessageAutoDeleteTime{}, + } + text := (&Client{}).messageToText(&ttl, false) + if text != "The self-destruct timer was disabled" { + t.Errorf("Wrong anonymous off ttl label: %v", text) + } +} + +func TestMessageTtl2(t *testing.T) { + ttl := client.Message{ + Content: &client.MessageChatSetMessageAutoDeleteTime{ + MessageAutoDeleteTime: 3, + }, + } + text := (&Client{}).messageToText(&ttl, false) + if text != "The self-destruct timer was set to 3 seconds" { + t.Errorf("Wrong anonymous ttl label: %v", text) + } +} + +func TestMessageTtl3(t *testing.T) { + ttl := client.Message{ + Content: &client.MessageChatSetMessageAutoDeleteTime{ + FromUserId: 3, + }, + } + text := (&Client{}).messageToText(&ttl, false) + if text != "unknown contact: TDlib instance is offline disabled the self-destruct timer" { + t.Errorf("Wrong off ttl label: %v", text) + } +} + +func TestMessageTtl4(t *testing.T) { + ttl := client.Message{ + Content: &client.MessageChatSetMessageAutoDeleteTime{ + FromUserId: 3, + MessageAutoDeleteTime: 3, + }, + } + text := (&Client{}).messageToText(&ttl, false) + if text != "unknown contact: TDlib instance is offline set the self-destruct timer to 3 seconds" { + t.Errorf("Wrong ttl label: %v", text) + } +} + func TestMessageUnknown(t *testing.T) { unknown := client.Message{ Content: &client.MessageExpiredPhoto{}, diff --git a/xmpp/handlers.go b/xmpp/handlers.go index c5ec029..fd1afad 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -481,7 +481,7 @@ func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *tel vcard.Photo.Type.Text = "image/jpeg" vcard.Photo.Binval.Text = base64Photo } - vcard.Nickname.Text = info.Nickname + vcard.Nickname.Text = strings.Join(info.Nicknames, ",") vcard.N.Given.Text = info.Given vcard.N.Family.Text = info.Family vcard.Tel.Number.Text = info.Tel @@ -512,13 +512,13 @@ func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *tel }, }) } - if info.Nickname != "" { + for _, nickname := range info.Nicknames { nodes = append(nodes, stanza.Node{ XMLName: xml.Name{Local: "nickname"}, Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "text"}, - Content: info.Nickname, + Content: nickname, }, }, }, stanza.Node{ @@ -526,7 +526,7 @@ func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *tel Nodes: []stanza.Node{ stanza.Node{ XMLName: xml.Name{Local: "uri"}, - Content: "https://t.me/" + info.Nickname, + Content: "https://t.me/" + nickname, }, }, }) From a595d9db0af3e5c08abf4df32de61e653af0711c Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 31 Jul 2023 21:37:05 -0400 Subject: [PATCH 39/51] Version 1.7.0 --- telegabber.go | 2 +- telegram/commands.go | 4 ++-- telegram/utils_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/telegabber.go b/telegabber.go index 78b893b..5bb69d6 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.6.1-dev" +var version string = "1.7.0" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index 3828ec2..e164ce1 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -942,8 +942,8 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } } _, err = c.client.SetChatMessageAutoDeleteTime(&client.SetChatMessageAutoDeleteTimeRequest{ - ChatId: chatID, - MessageAutoDeleteTime: int32(ttl), + ChatId: chatID, + MessageAutoDeleteTime: int32(ttl), }) if err != nil { diff --git a/telegram/utils_test.go b/telegram/utils_test.go index 18be215..e54ddb5 100644 --- a/telegram/utils_test.go +++ b/telegram/utils_test.go @@ -406,7 +406,7 @@ func TestMessageTtl3(t *testing.T) { func TestMessageTtl4(t *testing.T) { ttl := client.Message{ Content: &client.MessageChatSetMessageAutoDeleteTime{ - FromUserId: 3, + FromUserId: 3, MessageAutoDeleteTime: 3, }, } From 131f6eba38734212e039845b6cd9deabbc9d978d Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 31 Jul 2023 22:00:58 -0400 Subject: [PATCH 40/51] Use previews only instead of TGS stickers --- telegabber.go | 2 +- telegram/utils.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/telegabber.go b/telegabber.go index 5bb69d6..cfeecda 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.7.0" +var version string = "1.7.1" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/utils.go b/telegram/utils.go index e1e9317..cd25c22 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -770,7 +770,7 @@ func (c *Client) contentToFile(content client.MessageContent) (*client.File, *cl case client.TypeMessageSticker: sticker, _ := content.(*client.MessageSticker) file := sticker.Sticker.Sticker - if sticker.Sticker.Format.StickerFormatType() != client.TypeStickerTypeRegular && sticker.Sticker.Thumbnail != nil && sticker.Sticker.Thumbnail.File != nil { + if sticker.Sticker.Format.StickerFormatType() == client.TypeStickerFormatTgs && sticker.Sticker.Thumbnail != nil && sticker.Sticker.Thumbnail.File != nil { file = sticker.Sticker.Thumbnail.File } return file, nil From a5f6c600357d4000b4a904e36cac1256ca3ac01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C4=BCja=20Pav=C4=BCikhin?= Date: Tue, 1 Aug 2023 22:36:27 +0000 Subject: [PATCH 41/51] Add building in fcking docker environment --- .gitignore | 1 + Dockerfile | 36 ++++++++++++++++++++++++++++++++++++ Makefile | 6 ++++++ 3 files changed, 43 insertions(+) create mode 100644 Dockerfile diff --git a/.gitignore b/.gitignore index 58426dd..b132b72 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ telegabber sessions/ session.dat session.dat.new +release/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6fea570 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +FROM golang:1.19-bookworm AS base + +RUN apt-get update +run apt-get install -y libssl-dev cmake build-essential gperf libz-dev make git + +FROM base AS tdlib + +ARG TD_COMMIT +ARG MAKEOPTS +RUN git clone https://github.com/tdlib/td /src/ +RUN git -C /src/ checkout "${TD_COMMIT}" +RUN mkdir build +WORKDIR /build/ +RUN cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/compiled/ /src/ +RUN cmake --build . ${MAKEOPTS} +RUN make install + +FROM base AS cache +ARG VERSION +COPY --from=tdlib /compiled/ /usr/local/ +COPY ./ /src +RUN git -C /src checkout "${VERSION}" +WORKDIR /src +RUN go get + +FROM cache AS build +ARG MAKEOPTS +WORKDIR /src +RUN make ${MAKEOPTS} + +FROM scratch AS telegabber +COPY --from=build /src/telegabber /usr/local/bin/ +ENTRYPOINT ["/usr/local/bin/telegabber"] + +FROM scratch AS binaries +COPY --from=telegabber /usr/local/bin/telegabber / diff --git a/Makefile b/Makefile index 048987d..5869f9e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ .PHONY: all test COMMIT := $(shell git rev-parse --short HEAD) +TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" +VERSION := "v1.7.1" +MAKEOPTS := "-j4" all: go build -ldflags "-X main.commit=${COMMIT}" -o telegabber @@ -10,3 +13,6 @@ test: lint: $(GOPATH)/bin/golint ./... + +build_indocker: + docker build --build-arg "TD_COMMIT=${TD_COMMIT}" --build-arg "VERSION=${VERSION}" --build-arg "MAKEOPTS=${MAKEOPTS}" --output=release --target binaries . From 8fc9edd7e70aeca266ab2860198de49bdc2ab585 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Tue, 1 Aug 2023 20:03:34 -0400 Subject: [PATCH 42/51] Prevent messages to a certain resource from being carbon-copied --- Makefile | 2 +- telegabber.go | 2 +- xmpp/gateway/gateway.go | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5869f9e..2eb17ac 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.7.1" +VERSION := "v1.7.2" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index cfeecda..9ce070f 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.7.1" +var version string = "1.7.2" var commit string var sm *goxmpp.StreamManager diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 7e54ee5..1be2fca 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -119,6 +119,9 @@ func sendMessageWrapper(to string, from string, body string, id string, componen message.Extensions = append(message.Extensions, extensions.NewReplyFallback(reply.Start, reply.End)) } } + if !isCarbon && toJid.Resource != "" { + message.Extensions = append(message.Extensions, stanza.HintNoCopy{}) + } if isCarbon { carbonMessage := extensions.ClientMessage{ From 3c917c16983c1afdd4a21d8021461585a1e785c9 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Wed, 2 Aug 2023 13:53:34 -0400 Subject: [PATCH 43/51] Carbons in group chats --- telegabber.go | 2 +- telegram/utils.go | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/telegabber.go b/telegabber.go index 9ce070f..671e13b 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.7.2" +var version string = "1.8.0-dev" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/utils.go b/telegram/utils.go index cd25c22..a91c6da 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -837,12 +837,15 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string, if err != nil { log.Errorf("Could not determine if chat is PM: %v", err) } + isCarbonsEnabled := gateway.MessageOutgoingPermission && c.Session.Carbons + // with carbons, hide for all messages in PM and only for outgoing in group chats + hideSender := isCarbonsEnabled && (message.IsOutgoing || isPM) var replyStart, replyEnd int prefix := []string{} // message direction var directionChar string - if !isPM || !gateway.MessageOutgoingPermission || !c.Session.Carbons { + if !hideSender { if c.Session.AsciiArrows { if message.IsOutgoing { directionChar = "> " @@ -861,7 +864,7 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string, prefix = append(prefix, directionChar+strconv.FormatInt(message.Id, 10)) } // show sender in group chats - if !isPM { + if !hideSender { sender := c.formatSender(message) if sender != "" { prefix = append(prefix, sender) @@ -912,17 +915,12 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message, ignoredResource string) { - var isPM bool - var err error + var isCarbon bool + isOutgoing := message.IsOutgoing if gateway.MessageOutgoingPermission && c.Session.Carbons { - isPM, err = c.IsPM(chatId) - if err != nil { - log.Errorf("Could not determine if chat is PM: %v", err) - } + isCarbon = isOutgoing } - isOutgoing := message.IsOutgoing - isCarbon := isPM && isOutgoing jids := c.getCarbonFullJids(isOutgoing, ignoredResource) if len(jids) == 0 { log.Info("The only resource is ignored, aborting") From 608f67551297a14e2e23603413bbce66f6ad5cd9 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Wed, 2 Aug 2023 16:41:18 -0400 Subject: [PATCH 44/51] Revert sending carbons for outgoing messages to other resources (they duplicate what clients already send to each other) --- Makefile | 2 +- telegabber.go | 2 +- telegram/commands.go | 2 +- telegram/handlers.go | 19 ++++++++----------- telegram/utils.go | 11 +++-------- xmpp/handlers.go | 1 - 6 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 2eb17ac..6732740 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.7.2" +VERSION := "v1.7.3" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index 9ce070f..3d7d2ea 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.7.2" +var version string = "1.7.3" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index e164ce1..206e049 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -658,7 +658,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) } if messages != nil && messages.Messages != nil { for _, message := range messages.Messages { - c.ProcessIncomingMessage(targetChatId, message, "") + c.ProcessIncomingMessage(targetChatId, message) } } // print vCard diff --git a/telegram/handlers.go b/telegram/handlers.go index cedea63..0d1cda9 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -205,27 +205,24 @@ func (c *Client) updateChatLastMessage(update *client.UpdateChatLastMessage) { func (c *Client) updateNewMessage(update *client.UpdateNewMessage) { chatId := update.Message.ChatId - c.SendMessageLock.Lock() - c.SendMessageLock.Unlock() - xmppId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, chatId, update.Message.Id) - var ignoredResource string - if err == nil { - ignoredResource = c.popFromOutbox(xmppId) - } else { - log.Infof("Couldn't retrieve XMPP message ids for %v, an echo may happen", update.Message.Id) - } - // guarantee sequential message delivering per chat lock := c.getChatMessageLock(chatId) go func() { lock.Lock() defer lock.Unlock() + // ignore self outgoing messages + if update.Message.IsOutgoing && + update.Message.SendingState != nil && + update.Message.SendingState.MessageSendingStateType() == client.TypeMessageSendingStatePending { + return + } + log.WithFields(log.Fields{ "chat_id": chatId, }).Warn("New message from chat") - c.ProcessIncomingMessage(chatId, update.Message, ignoredResource) + c.ProcessIncomingMessage(chatId, update.Message) c.updateLastMessageHash(update.Message.ChatId, update.Message.Id, update.Message.Content) }() diff --git a/telegram/utils.go b/telegram/utils.go index cd25c22..62ce945 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -911,7 +911,7 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { } // ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side -func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message, ignoredResource string) { +func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { var isPM bool var err error if gateway.MessageOutgoingPermission && c.Session.Carbons { @@ -921,13 +921,8 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message, i } } - isOutgoing := message.IsOutgoing - isCarbon := isPM && isOutgoing - jids := c.getCarbonFullJids(isOutgoing, ignoredResource) - if len(jids) == 0 { - log.Info("The only resource is ignored, aborting") - return - } + isCarbon := isPM && message.IsOutgoing + jids := c.getCarbonFullJids(isCarbon, "") var text, oob, auxText string diff --git a/xmpp/handlers.go b/xmpp/handlers.go index fd1afad..4e3354e 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -183,7 +183,6 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { if err != nil { log.Errorf("Failed to save ids %v/%v %v", toID, tgMessageId, msg.Id) } - session.AddToOutbox(msg.Id, resource) } } else { /* From c03ccfdfb713d4fcb089600d9fd91f03e469daca Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Wed, 2 Aug 2023 17:08:06 -0400 Subject: [PATCH 45/51] Support urn:xmpp:privilege:2 --- Makefile | 2 +- telegabber.go | 2 +- telegram/commands.go | 2 +- telegram/utils.go | 4 ++-- xmpp/extensions/extensions.go | 26 ++++++++++++++++++++++---- xmpp/gateway/gateway.go | 22 +++++++++++++++------- xmpp/handlers.go | 21 ++++++++++++++++----- 7 files changed, 58 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 6732740..7857e17 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.7.3" +VERSION := "v1.7.4" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index 3d7d2ea..df13dd6 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.7.3" +var version string = "1.7.4" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index 206e049..0c83945 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -384,7 +384,7 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } case "config": if len(args) > 1 { - if !gateway.MessageOutgoingPermission && args[0] == "carbons" && args[1] == "true" { + if gateway.MessageOutgoingPermissionVersion == 0 && args[0] == "carbons" && args[1] == "true" { return "The server did not allow to enable carbons" } diff --git a/telegram/utils.go b/telegram/utils.go index 62ce945..4caf88c 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -842,7 +842,7 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string, prefix := []string{} // message direction var directionChar string - if !isPM || !gateway.MessageOutgoingPermission || !c.Session.Carbons { + if !isPM || gateway.MessageOutgoingPermissionVersion == 0 || !c.Session.Carbons { if c.Session.AsciiArrows { if message.IsOutgoing { directionChar = "> " @@ -914,7 +914,7 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { var isPM bool var err error - if gateway.MessageOutgoingPermission && c.Session.Carbons { + if gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons { isPM, err = c.IsPM(chatId) if err != nil { log.Errorf("Could not determine if chat is PM: %v", err) diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 2d547af..192b630 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -154,12 +154,19 @@ type CarbonSent struct { } // ComponentPrivilege is from XEP-0356 -type ComponentPrivilege struct { +type ComponentPrivilege1 struct { XMLName xml.Name `xml:"urn:xmpp:privilege:1 privilege"` Perms []ComponentPerm `xml:"perm"` Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"` } +// ComponentPrivilege is from XEP-0356 +type ComponentPrivilege2 struct { + XMLName xml.Name `xml:"urn:xmpp:privilege:2 privilege"` + Perms []ComponentPerm `xml:"perm"` + Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"` +} + // ComponentPerm is from XEP-0356 type ComponentPerm struct { XMLName xml.Name `xml:"perm"` @@ -227,7 +234,12 @@ func (c CarbonSent) Namespace() string { } // Namespace is a namespace! -func (c ComponentPrivilege) Namespace() string { +func (c ComponentPrivilege1) Namespace() string { + return c.XMLName.Space +} + +// Namespace is a namespace! +func (c ComponentPrivilege2) Namespace() string { return c.XMLName.Space } @@ -297,11 +309,17 @@ func init() { "sent", }, CarbonSent{}) - // component privilege + // component privilege v1 stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ "urn:xmpp:privilege:1", "privilege", - }, ComponentPrivilege{}) + }, ComponentPrivilege1{}) + + // component privilege v2 + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:privilege:2", + "privilege", + }, ComponentPrivilege2{}) // message edit stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 1be2fca..7a2500e 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -38,8 +38,8 @@ var IdsDB badger.IdsDB // were changed and need to be re-flushed to the YamlDB var DirtySessions = false -// MessageOutgoingPermission allows to fake outgoing messages by foreign JIDs -var MessageOutgoingPermission = false +// MessageOutgoingPermissionVersion contains a XEP-0356 version to fake outgoing messages by foreign JIDs +var MessageOutgoingPermissionVersion = 0 // SendMessage creates and sends a message stanza func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isCarbon bool) { @@ -142,11 +142,19 @@ func sendMessageWrapper(to string, from string, body string, id string, componen To: toJid.Domain, }, } - privilegeMessage.Extensions = append(privilegeMessage.Extensions, extensions.ComponentPrivilege{ - Forwarded: stanza.Forwarded{ - Stanza: carbonMessage, - }, - }) + if MessageOutgoingPermissionVersion == 2 { + privilegeMessage.Extensions = append(privilegeMessage.Extensions, extensions.ComponentPrivilege2{ + Forwarded: stanza.Forwarded{ + Stanza: carbonMessage, + }, + }) + } else { + privilegeMessage.Extensions = append(privilegeMessage.Extensions, extensions.ComponentPrivilege1{ + Forwarded: stanza.Forwarded{ + Stanza: carbonMessage, + }, + }) + } sendMessage(&privilegeMessage, component) } else { sendMessage(&message, component) diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 4e3354e..6679a72 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -209,14 +209,25 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { } if msg.Body == "" { - var privilege extensions.ComponentPrivilege - if ok := msg.Get(&privilege); ok { - log.Debugf("privilege: %#v", privilege) + var privilege1 extensions.ComponentPrivilege1 + if ok := msg.Get(&privilege1); ok { + log.Debugf("privilege1: %#v", privilege1) } - for _, perm := range privilege.Perms { + for _, perm := range privilege1.Perms { if perm.Access == "message" && perm.Type == "outgoing" { - gateway.MessageOutgoingPermission = true + gateway.MessageOutgoingPermissionVersion = 1 + } + } + + var privilege2 extensions.ComponentPrivilege2 + if ok := msg.Get(&privilege2); ok { + log.Debugf("privilege2: %#v", privilege2) + } + + for _, perm := range privilege2.Perms { + if perm.Access == "message" && perm.Type == "outgoing" { + gateway.MessageOutgoingPermissionVersion = 2 } } } From 9377d7a15538a6c0af97937806ecd55eb112beb3 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 6 Aug 2023 20:04:49 -0400 Subject: [PATCH 46/51] Save/read unavailable presence type in cache --- Makefile | 2 +- telegabber.go | 2 +- telegram/cache/cache.go | 10 ++++++++++ telegram/utils.go | 40 ++++++++++++++++++++++++++++++++++++---- xmpp/handlers.go | 14 +++++++++++--- 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 7857e17..33bedad 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.7.4" +VERSION := "v1.7.5" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index df13dd6..37d5890 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.7.4" +var version string = "1.7.5" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/cache/cache.go b/telegram/cache/cache.go index 3d9608d..6847d3e 100644 --- a/telegram/cache/cache.go +++ b/telegram/cache/cache.go @@ -133,3 +133,13 @@ func (cache *Cache) SetStatus(id int64, show string, status string) { Description: status, } } + +// Destruct splits a cached status into show, description and type +func (status *Status) Destruct() (show, description, typ string) { + show, description = status.XMPP, status.Description + if show == "unavailable" { + typ = show + show = "" + } + return +} diff --git a/telegram/utils.go b/telegram/utils.go index 4caf88c..2578671 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -243,15 +243,33 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o cachedStatus, ok := c.cache.GetStatus(chatID) if status == "" { if ok { - show, status = cachedStatus.XMPP, cachedStatus.Description + var typ string + show, status, typ = cachedStatus.Destruct() + if presenceType == "" { + presenceType = typ + } + log.WithFields(log.Fields{ + "show": show, + "status": status, + "presenceType": presenceType, + }).Debug("Cached status") } else if user != nil && user.Status != nil { show, status, presenceType = c.userStatusToText(user.Status, chatID) + log.WithFields(log.Fields{ + "show": show, + "status": status, + "presenceType": presenceType, + }).Debug("Status to text") } else { show, status = "chat", chat.Title } } - c.cache.SetStatus(chatID, show, status) + cacheShow := show + if presenceType == "unavailable" { + cacheShow = presenceType + } + c.cache.SetStatus(chatID, cacheShow, status) newArgs := []args.V{ gateway.SPFrom(strconv.FormatInt(chatID, 10)), @@ -1366,12 +1384,26 @@ func (c *Client) UpdateChatNicknames() { for _, id := range c.cache.ChatsKeys() { chat, ok := c.cache.GetChat(id) if ok { + newArgs := []args.V{ + gateway.SPFrom(strconv.FormatInt(id, 10)), + gateway.SPNickname(chat.Title), + } + + cachedStatus, ok := c.cache.GetStatus(id) + if ok { + show, status, typ := cachedStatus.Destruct() + newArgs = append(newArgs, gateway.SPShow(show), gateway.SPStatus(status)) + if typ != "" { + newArgs = append(newArgs, gateway.SPType(typ)) + } + } + gateway.SendPresence( c.xmpp, c.jid, - gateway.SPFrom(strconv.FormatInt(id, 10)), - gateway.SPNickname(chat.Title), + newArgs..., ) + gateway.SetNickname(c.jid, strconv.FormatInt(id, 10), chat.Title, c.xmpp) } } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 6679a72..e85dfc9 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -15,6 +15,7 @@ import ( "dev.narayana.im/narayana/telegabber/xmpp/gateway" log "github.com/sirupsen/logrus" + "github.com/soheilhy/args" "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" ) @@ -349,11 +350,18 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) { log.Error(errors.Wrap(err, "TDlib connection failure")) } else { for status := range session.StatusesRange() { + show, description, typ := status.Destruct() + newArgs := []args.V{ + gateway.SPImmed(false), + } + if typ != "" { + newArgs = append(newArgs, gateway.SPType(typ)) + } go session.ProcessStatusUpdate( status.ID, - status.Description, - status.XMPP, - gateway.SPImmed(false), + description, + show, + newArgs..., ) } session.UpdateChatNicknames() From 64515e2c666067953e3a9680b4f0db84f3838498 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Tue, 8 Aug 2023 00:54:24 -0400 Subject: [PATCH 47/51] Fix replies to messages with non-ASCII characters --- Makefile | 2 +- telegabber.go | 2 +- xmpp/handlers.go | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 33bedad..ef6aef4 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.7.5" +VERSION := "v1.7.6" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index 37d5890..3802764 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.7.5" +var version string = "1.7.6" var commit string var sm *goxmpp.StreamManager diff --git a/xmpp/handlers.go b/xmpp/handlers.go index e85dfc9..36f9cf9 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -149,7 +149,12 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { "end": body.End, }).Warn(errors.Wrap(err, "Failed to parse fallback end!")) } - text = text[:start] + text[end:] + + fullRunes := []rune(text) + cutRunes := make([]rune, 0, len(text)-int(end-start)) + cutRunes = append(cutRunes, fullRunes[:start]...) + cutRunes = append(cutRunes, fullRunes[end:]...) + text = string(cutRunes) } } var replaceId int64 From 20994e29953dfc9c238f69d919912e0c26e36b97 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 28 Aug 2023 10:16:57 -0400 Subject: [PATCH 48/51] In-Band Registration (XEP-0077) --- telegram/commands.go | 54 ++++----- telegram/connect.go | 40 ++++++- xmpp/extensions/extensions.go | 36 ++++++ xmpp/gateway/gateway.go | 6 + xmpp/handlers.go | 204 +++++++++++++++++++++++++++++++++- 5 files changed, 304 insertions(+), 36 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index 0c83945..b4920d4 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -15,7 +15,8 @@ import ( ) const notEnoughArguments string = "Not enough arguments" -const telegramNotInitialized string = "Telegram connection is not initialized yet" +const TelegramNotInitialized string = "Telegram connection is not initialized yet" +const TelegramAuthDone string = "Authorization is done already" const notOnline string = "Not online" var permissionsAdmin = client.ChatAdministratorRights{ @@ -244,40 +245,29 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } if cmd == "login" { - wasSessionLoginEmpty := c.Session.Login == "" - c.Session.Login = args[0] - - if wasSessionLoginEmpty && c.authorizer == nil { - go func() { - err := c.Connect(resource) - if err != nil { - log.Error(errors.Wrap(err, "TDlib connection failure")) - } - }() - // a quirk for authorizer to become ready. If it's still not, - // nothing bad: the command just needs to be resent again - time.Sleep(1e5) + err := c.TryLogin(resource, args[0]) + if err != nil { + return err.Error() } - } - if c.authorizer == nil { - return telegramNotInitialized - } - - if c.authorizer.isClosed { - return "Authorization is done already" - } - - switch cmd { - // sign in - case "login": c.authorizer.PhoneNumber <- args[0] - // check auth code - case "code": - c.authorizer.Code <- args[0] - // check auth password - case "password": - c.authorizer.Password <- args[0] + } else { + if c.authorizer == nil { + return TelegramNotInitialized + } + + if c.authorizer.isClosed { + return TelegramAuthDone + } + + switch cmd { + // check auth code + case "code": + c.authorizer.Code <- args[0] + // check auth password + case "password": + c.authorizer.Password <- args[0] + } } // sign out case "logout": diff --git a/telegram/connect.go b/telegram/connect.go index ef03428..ab9c19c 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -3,6 +3,7 @@ package telegram import ( "github.com/pkg/errors" "strconv" + "time" "dev.narayana.im/narayana/telegabber/xmpp/gateway" @@ -154,14 +155,49 @@ func (c *Client) Connect(resource string) error { log.Errorf("Could not retrieve chats: %v", err) } - gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribe")) - gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribed")) + gateway.SubscribeToTransport(c.xmpp, c.jid) gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in as: "+c.Session.Login)) }() return nil } +func (c *Client) TryLogin(resource string, login string) error { + wasSessionLoginEmpty := c.Session.Login == "" + c.Session.Login = login + + if wasSessionLoginEmpty && c.authorizer == nil { + go func() { + err := c.Connect(resource) + if err != nil { + log.Error(errors.Wrap(err, "TDlib connection failure")) + } + }() + // a quirk for authorizer to become ready. If it's still not, + // nothing bad: just re-login again + time.Sleep(1e5) + } + + if c.authorizer == nil { + return errors.New(TelegramNotInitialized) + } + + if c.authorizer.isClosed { + return errors.New(TelegramAuthDone) + } + + return nil +} + +func (c *Client) SetPhoneNumber(login string) error { + if c.authorizer == nil || c.authorizer.isClosed { + return errors.New("Authorization not needed") + } + + c.authorizer.PhoneNumber <- login + return nil +} + // Disconnect drops TDlib connection and // returns the flag indicating if disconnecting is permitted func (c *Client) Disconnect(resource string, quit bool) bool { diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 192b630..8e2f743 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -193,6 +193,26 @@ type Replace struct { Id string `xml:"id,attr"` } +// QueryRegister is from XEP-0077 +type QueryRegister struct { + XMLName xml.Name `xml:"jabber:iq:register query"` + Instructions string `xml:"instructions"` + Username string `xml:"username"` + Registered *QueryRegisterRegistered `xml:"registered"` + Remove *QueryRegisterRemove `xml:"remove"` + ResultSet *stanza.ResultSet `xml:"set,omitempty"` +} + +// QueryRegisterRegistered is a child element from XEP-0077 +type QueryRegisterRegistered struct { + XMLName xml.Name `xml:"registered"` +} + +// QueryRegisterRemove is a child element from XEP-0077 +type QueryRegisterRemove struct { + XMLName xml.Name `xml:"remove"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space @@ -248,6 +268,16 @@ func (c Replace) Namespace() string { return c.XMLName.Space } +// Namespace is a namespace! +func (c QueryRegister) Namespace() string { + return c.XMLName.Space +} + +// GetSet getsets! +func (c QueryRegister) GetSet() *stanza.ResultSet { + return c.ResultSet +} + // Name is a packet name func (ClientMessage) Name() string { return "message" @@ -326,4 +356,10 @@ func init() { "urn:xmpp:message-correct:0", "replace", }, Replace{}) + + // register query + stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ + "jabber:iq:register", + "query", + }, QueryRegister{}) } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 7a2500e..dfe2ebf 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -360,6 +360,12 @@ func ResumableSend(component *xmpp.Component, packet stanza.Packet) error { return err } +// SubscribeToTransport ensures a two-way subscription to the transport +func SubscribeToTransport(component *xmpp.Component, jid string) { + SendPresence(component, jid, SPType("subscribe")) + SendPresence(component, jid, SPType("subscribed")) +} + // SplitJID tokenizes a JID string to bare JID and resource func SplitJID(from string) (string, string, bool) { fromJid, err := stanza.NewJid(from) diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 36f9cf9..fdcf647 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "encoding/xml" + "fmt" "github.com/pkg/errors" "io" "strconv" @@ -57,6 +58,22 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { go handleGetDiscoInfo(s, iq) return } + _, ok = iq.Payload.(*stanza.DiscoItems) + if ok { + go handleGetDiscoItems(s, iq) + return + } + _, ok = iq.Payload.(*extensions.QueryRegister) + if ok { + go handleGetQueryRegister(s, iq) + return + } + } else if iq.Type == "set" { + query, ok := iq.Payload.(*extensions.QueryRegister) + if ok { + go handleSetQueryRegister(s, iq, query) + return + } } } @@ -91,8 +108,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { session, ok := sessions[bare] if !ok { if msg.To == gatewayJid { - gateway.SendPresence(component, msg.From, gateway.SPType("subscribe")) - gateway.SendPresence(component, msg.From, gateway.SPType("subscribed")) + gateway.SubscribeToTransport(component, msg.From) } else { log.Error("Message from stranger") } @@ -444,6 +460,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) { disco.AddIdentity("", "account", "registered") } else { disco.AddIdentity("Telegram Gateway", "gateway", "telegram") + disco.AddFeatures("jabber:iq:register") } answer.Payload = disco @@ -458,6 +475,189 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) { _ = gateway.ResumableSend(component, answer) } +func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) { + 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.Payload = answer.DiscoItems() + + component, ok := s.(*xmpp.Component) + if !ok { + log.Error("Not a component") + return + } + + _ = gateway.ResumableSend(component, answer) +} + +func handleGetQueryRegister(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 + } + + var login string + bare, _, ok := gateway.SplitJID(iq.From) + if ok { + session, ok := sessions[bare] + if ok { + login = session.Session.Login + } + } + + var query stanza.IQPayload + if login == "" { + query = extensions.QueryRegister{ + Instructions: fmt.Sprintf("Authorization in Telegram is a multi-step process, so please accept %v to your contacts and follow further instructions (provide the authentication code there, etc.).\nFor now, please provide your login.", iq.To), + } + } else { + query = extensions.QueryRegister{ + Instructions: "Already logged in", + Username: login, + Registered: &extensions.QueryRegisterRegistered{}, + } + } + answer.Payload = query + + log.Debugf("%#v", query) + + _ = gateway.ResumableSend(component, answer) + + if login == "" { + gateway.SubscribeToTransport(component, iq.From) + } +} + +func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) { + 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 + } + + defer gateway.ResumableSend(component, answer) + + if query.Remove != nil { + iqAnswerSetError(answer, query, 405) + return + } + + var login string + var session *telegram.Client + bare, resource, ok := gateway.SplitJID(iq.From) + if ok { + session, ok = sessions[bare] + if ok { + login = session.Session.Login + } + } + + if login == "" { + if !ok { + session, ok = getTelegramInstance(bare, &persistence.Session{}, component) + if !ok { + iqAnswerSetError(answer, query, 500) + return + } + } + + err := session.TryLogin(resource, query.Username) + if err != nil { + if err.Error() == telegram.TelegramAuthDone { + iqAnswerSetError(answer, query, 406) + } else { + iqAnswerSetError(answer, query, 500) + } + return + } + + err = session.SetPhoneNumber(query.Username) + if err != nil { + iqAnswerSetError(answer, query, 500) + return + } + + // everything okay, the response should be empty with no payload/error at this point + gateway.SubscribeToTransport(component, iq.From) + } else { + iqAnswerSetError(answer, query, 406) + } +} + +func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) { + answer.Type = stanza.IQTypeError + answer.Payload = *payload + switch code { + case 400: + answer.Error = &stanza.Err{ + Code: code, + Type: stanza.ErrorTypeModify, + Reason: "bad-request", + } + case 405: + answer.Error = &stanza.Err{ + Code: code, + Type: stanza.ErrorTypeCancel, + Reason: "not-allowed", + Text: "Logging out is dangerous. If you are sure you would be able to receive the authentication code again, issue the /logout command to the transport", + } + case 406: + answer.Error = &stanza.Err{ + Code: code, + Type: stanza.ErrorTypeModify, + Reason: "not-acceptable", + Text: "Phone number already provided, chat with the transport for further instruction", + } + case 500: + answer.Error = &stanza.Err{ + Code: code, + Type: stanza.ErrorTypeWait, + Reason: "internal-server-error", + } + default: + log.Error("Unknown error code, falling back with empty reason") + answer.Error = &stanza.Err{ + Code: code, + Type: stanza.ErrorTypeCancel, + Reason: "undefined-condition", + } + } +} + func toToID(to string) (int64, bool) { toParts := strings.Split(to, "@") if len(toParts) < 2 { From aa561c5be606c14cfd211df694ef0be856195df7 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 28 Aug 2023 10:20:50 -0400 Subject: [PATCH 49/51] Version 1.8.0 --- Makefile | 2 +- telegabber.go | 2 +- telegram/utils.go | 8 ++++---- xmpp/handlers.go | 24 ++++++++++++------------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index d6323d0..724c73d 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.8.0-dev" +VERSION := "v1.8.0" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index 671e13b..f409599 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.8.0-dev" +var version string = "1.8.0" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/utils.go b/telegram/utils.go index 4d77fc4..47a851a 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -249,15 +249,15 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o presenceType = typ } log.WithFields(log.Fields{ - "show": show, - "status": status, + "show": show, + "status": status, "presenceType": presenceType, }).Debug("Cached status") } else if user != nil && user.Status != nil { show, status, presenceType = c.userStatusToText(user.Status, chatID) log.WithFields(log.Fields{ - "show": show, - "status": status, + "show": show, + "status": status, "presenceType": presenceType, }).Debug("Status to text") } else { diff --git a/xmpp/handlers.go b/xmpp/handlers.go index fdcf647..4c27b3c 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -624,35 +624,35 @@ func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code switch code { case 400: answer.Error = &stanza.Err{ - Code: code, - Type: stanza.ErrorTypeModify, + Code: code, + Type: stanza.ErrorTypeModify, Reason: "bad-request", } case 405: answer.Error = &stanza.Err{ - Code: code, - Type: stanza.ErrorTypeCancel, + Code: code, + Type: stanza.ErrorTypeCancel, Reason: "not-allowed", - Text: "Logging out is dangerous. If you are sure you would be able to receive the authentication code again, issue the /logout command to the transport", + Text: "Logging out is dangerous. If you are sure you would be able to receive the authentication code again, issue the /logout command to the transport", } case 406: answer.Error = &stanza.Err{ - Code: code, - Type: stanza.ErrorTypeModify, + Code: code, + Type: stanza.ErrorTypeModify, Reason: "not-acceptable", - Text: "Phone number already provided, chat with the transport for further instruction", + Text: "Phone number already provided, chat with the transport for further instruction", } case 500: answer.Error = &stanza.Err{ - Code: code, - Type: stanza.ErrorTypeWait, + Code: code, + Type: stanza.ErrorTypeWait, Reason: "internal-server-error", } default: log.Error("Unknown error code, falling back with empty reason") answer.Error = &stanza.Err{ - Code: code, - Type: stanza.ErrorTypeCancel, + Code: code, + Type: stanza.ErrorTypeCancel, Reason: "undefined-condition", } } From 4588170d1e43db780c551177f5996598fe25bc6e Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 31 Aug 2023 17:26:35 -0400 Subject: [PATCH 50/51] Harden the authorizer access to prevent crashes --- Makefile | 2 +- telegabber.go | 2 +- telegram/client.go | 3 +++ telegram/commands.go | 6 ++++++ telegram/connect.go | 24 ++++++++++++++++++++++++ 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 724c73d..e139c00 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.8.0" +VERSION := "v1.8.1" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index f409599..85c5fbd 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.8.0" +var version string = "1.8.1" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/client.go b/telegram/client.go index e9acd20..6f6d719 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -74,6 +74,9 @@ type clientLocks struct { resourcesLock sync.Mutex outboxLock sync.Mutex lastMsgHashesLock sync.Mutex + + authorizerReadLock sync.Mutex + authorizerWriteLock sync.Mutex } // NewClient instantiates a Telegram App diff --git a/telegram/commands.go b/telegram/commands.go index b4920d4..b729973 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -244,6 +244,9 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string return notEnoughArguments } + c.locks.authorizerWriteLock.Lock() + defer c.locks.authorizerWriteLock.Unlock() + if cmd == "login" { err := c.TryLogin(resource, args[0]) if err != nil { @@ -324,10 +327,13 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string lastname = rawCmdArguments(cmdline, 1) } + c.locks.authorizerWriteLock.Lock() if c.authorizer != nil && !c.authorizer.isClosed { c.authorizer.FirstName <- firstname c.authorizer.LastName <- lastname + c.locks.authorizerWriteLock.Unlock() } else { + c.locks.authorizerWriteLock.Unlock() if !c.Online() { return notOnline } diff --git a/telegram/connect.go b/telegram/connect.go index ab9c19c..6c49aa9 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -110,6 +110,7 @@ func (c *Client) Connect(resource string) error { log.Warn("Connecting to Telegram network...") + c.locks.authorizerWriteLock.Lock() c.authorizer = &clientAuthorizer{ TdlibParameters: make(chan *client.SetTdlibParametersRequest, 1), PhoneNumber: make(chan string, 1), @@ -123,6 +124,7 @@ func (c *Client) Connect(resource string) error { go c.interactor() c.authorizer.TdlibParameters <- c.parameters + c.locks.authorizerWriteLock.Unlock() tdlibClient, err := client.NewClient(c.authorizer, c.options...) if err != nil { @@ -178,6 +180,9 @@ func (c *Client) TryLogin(resource string, login string) error { time.Sleep(1e5) } + c.locks.authorizerReadLock.Lock() + defer c.locks.authorizerReadLock.Unlock() + if c.authorizer == nil { return errors.New(TelegramNotInitialized) } @@ -190,6 +195,9 @@ func (c *Client) TryLogin(resource string, login string) error { } func (c *Client) SetPhoneNumber(login string) error { + c.locks.authorizerWriteLock.Lock() + defer c.locks.authorizerWriteLock.Unlock() + if c.authorizer == nil || c.authorizer.isClosed { return errors.New("Authorization not needed") } @@ -234,9 +242,16 @@ func (c *Client) Disconnect(resource string, quit bool) bool { func (c *Client) interactor() { for { + c.locks.authorizerReadLock.Lock() + if c.authorizer == nil { + log.Warn("Authorizer is lost, halting the interactor") + c.locks.authorizerReadLock.Unlock() + return + } state, ok := <-c.authorizer.State if !ok { log.Warn("Interactor is disconnected") + c.locks.authorizerReadLock.Unlock() return } @@ -266,18 +281,27 @@ func (c *Client) interactor() { log.Warn("Waiting for 2FA password...") gateway.SendServiceMessage(c.jid, "Please, enter 2FA passphrase via /password 12345", c.xmpp) } + c.locks.authorizerReadLock.Unlock() } } func (c *Client) forceClose() { + c.locks.authorizerReadLock.Lock() + c.locks.authorizerWriteLock.Lock() + defer c.locks.authorizerReadLock.Unlock() + defer c.locks.authorizerWriteLock.Unlock() + c.online = false c.authorizer = nil } func (c *Client) close() { + c.locks.authorizerWriteLock.Lock() if c.authorizer != nil && !c.authorizer.isClosed { c.authorizer.Close() } + c.locks.authorizerWriteLock.Unlock() + if c.client != nil { _, err := c.client.Close() if err != nil { From 282a6fc21b9626ab1bdc9c5a78162d90b7d28aa2 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 31 Aug 2023 18:24:30 -0400 Subject: [PATCH 51/51] Hotfix: prevent lockup on login --- Makefile | 2 +- telegabber.go | 2 +- telegram/commands.go | 9 ++++++--- telegram/connect.go | 5 +++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index e139c00..5c001af 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMMIT := $(shell git rev-parse --short HEAD) TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1" -VERSION := "v1.8.1" +VERSION := "v1.8.2" MAKEOPTS := "-j4" all: diff --git a/telegabber.go b/telegabber.go index 85c5fbd..a1efd12 100644 --- a/telegabber.go +++ b/telegabber.go @@ -15,7 +15,7 @@ import ( goxmpp "gosrc.io/xmpp" ) -var version string = "1.8.1" +var version string = "1.8.2" var commit string var sm *goxmpp.StreamManager diff --git a/telegram/commands.go b/telegram/commands.go index b729973..87fff72 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -244,17 +244,20 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string return notEnoughArguments } - c.locks.authorizerWriteLock.Lock() - defer c.locks.authorizerWriteLock.Unlock() - if cmd == "login" { err := c.TryLogin(resource, args[0]) if err != nil { return err.Error() } + c.locks.authorizerWriteLock.Lock() + defer c.locks.authorizerWriteLock.Unlock() + c.authorizer.PhoneNumber <- args[0] } else { + c.locks.authorizerWriteLock.Lock() + defer c.locks.authorizerWriteLock.Unlock() + if c.authorizer == nil { return TelegramNotInitialized } diff --git a/telegram/connect.go b/telegram/connect.go index 6c49aa9..b1b8b10 100644 --- a/telegram/connect.go +++ b/telegram/connect.go @@ -122,6 +122,7 @@ func (c *Client) Connect(resource string) error { } go c.interactor() + log.Warn("Interactor launched") c.authorizer.TdlibParameters <- c.parameters c.locks.authorizerWriteLock.Unlock() @@ -180,8 +181,8 @@ func (c *Client) TryLogin(resource string, login string) error { time.Sleep(1e5) } - c.locks.authorizerReadLock.Lock() - defer c.locks.authorizerReadLock.Unlock() + c.locks.authorizerWriteLock.Lock() + defer c.locks.authorizerWriteLock.Unlock() if c.authorizer == nil { return errors.New(TelegramNotInitialized)