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 }