From 9c25d4ad8f2ccfa156a8cfc4c0d4d7dc35bdd95c Mon Sep 17 00:00:00 2001
From: bodqhrohro <bodqhrohro@gmail.com>
Date: Sun, 1 Dec 2019 15:13:45 +0200
Subject: Handle updates of newmessage

---
 telegram/client.go   |   2 +
 telegram/handlers.go |  58 ++++++++++++
 telegram/utils.go    | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 303 insertions(+), 4 deletions(-)

(limited to 'telegram')

diff --git a/telegram/client.go b/telegram/client.go
index e8643ce..a7fe332 100644
--- a/telegram/client.go
+++ b/telegram/client.go
@@ -53,6 +53,7 @@ type Client struct {
 	xmpp    *xmpp.Component
 	jid     string
 	Session *persistence.Session
+	content *config.TelegramContentConfig
 
 	locks  clientLocks
 	online bool
@@ -101,6 +102,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component
 		xmpp:         component,
 		jid:          jid,
 		Session:      session,
+		content:      &conf.Content,
 		logVerbosity: logVerbosity,
 		locks:        clientLocks{},
 	}, nil
diff --git a/telegram/handlers.go b/telegram/handlers.go
index 5ff5dad..d2d5b61 100644
--- a/telegram/handlers.go
+++ b/telegram/handlers.go
@@ -2,6 +2,7 @@ package telegram
 
 import (
 	"strconv"
+	"strings"
 
 	"dev.narayana.im/narayana/telegabber/xmpp/gateway"
 
@@ -38,6 +39,12 @@ func (c *Client) updateHandler() {
 					uhOh()
 				}
 				c.updateNewChat(typedUpdate)
+			case client.TypeUpdateNewMessage:
+				typedUpdate, ok := update.(*client.UpdateNewMessage)
+				if !ok {
+					uhOh()
+				}
+				c.updateNewMessage(typedUpdate)
 			default:
 				// log only handled types
 				continue
@@ -97,3 +104,54 @@ func (c *Client) updateNewChat(update *client.UpdateNewChat) {
 		c.processStatusUpdate(int32(update.Chat.Id), update.Chat.Title, "chat")
 	}
 }
+
+func (c *Client) updateNewMessage(update *client.UpdateNewMessage) {
+	// 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": update.Message.ChatId,
+	}).Warn("New message from chat")
+
+	text := c.messageToText(update.Message)
+	file, filename := c.contentToFilename(update.Message.Content)
+
+	// download file(s)
+	if file != nil && !file.Local.IsDownloadingCompleted {
+		c.client.DownloadFile(&client.DownloadFileRequest{
+			FileId:      file.Id,
+			Priority:    32,
+			Synchronous: true,
+		})
+	}
+	// OTR support (I do not know why would you need it, seriously)
+	if !strings.HasPrefix(text, "?OTR") {
+		var prefix strings.Builder
+		prefix.WriteString(c.messageToPrefix(update.Message, c.formatContent(file, filename)))
+		if text != "" {
+			// \n if it is groupchat and message is not empty
+			if update.Message.ChatId < 0 {
+				prefix.WriteString("\n")
+			} else if update.Message.ChatId > 0 {
+				prefix.WriteString(" | ")
+			}
+
+			prefix.WriteString(text)
+		}
+
+		text = prefix.String()
+	}
+
+	// mark message as read
+	c.client.ViewMessages(&client.ViewMessagesRequest{
+		ChatId:     update.Message.ChatId,
+		MessageIds: []int64{update.Message.Id},
+		ForceRead:  true,
+	})
+	// forward message to XMPP
+	gateway.SendMessage(c.jid, strconv.Itoa(int(update.Message.ChatId)), text, c.xmpp)
+}
diff --git a/telegram/utils.go b/telegram/utils.go
index ac17906..6ec4405 100644
--- a/telegram/utils.go
+++ b/telegram/utils.go
@@ -2,10 +2,15 @@ package telegram
 
 import (
 	"crypto/sha1"
+	"crypto/sha256"
+	"fmt"
 	"github.com/pkg/errors"
 	"io"
 	"os"
+	"path/filepath"
+	"regexp"
 	"strconv"
+	"strings"
 	"time"
 
 	"dev.narayana.im/narayana/telegabber/xmpp/gateway"
@@ -17,6 +22,10 @@ import (
 
 var errOffline = errors.New("TDlib instance is offline")
 
+var spaceRegex = regexp.MustCompile(`\s+`)
+
+const newlineChar string = "\n"
+
 // GetContactByUsername resolves username to user id retrieves user and chat information
 func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) {
 	if !c.online {
@@ -95,10 +104,7 @@ func userStatusToText(status client.UserStatus) (string, string) {
 	case client.TypeUserStatusEmpty:
 		show, textStatus = "unavailable", "Last seen a long time ago"
 	case client.TypeUserStatusOffline:
-		offlineStatus, ok := status.(*client.UserStatusOffline)
-		if !ok {
-			log.Fatal("Status type changed before conversion!")
-		}
+		offlineStatus, _ := status.(*client.UserStatusOffline)
 		// this will stop working in 2038 O\
 		elapsed := time.Now().Unix() - int64(offlineStatus.WasOnline)
 		if elapsed < 3600 {
@@ -167,6 +173,239 @@ func (c *Client) processStatusUpdate(chatID int32, status string, show string, a
 	return nil
 }
 
+func (c *Client) formatContact(chatID int32) string {
+	if chatID == 0 {
+		return ""
+	}
+
+	chat, user, err := c.GetContactByID(chatID, nil)
+	if err != nil {
+		return "unknown contact: " + err.Error()
+	}
+
+	var str string
+	if chat != nil {
+		str = fmt.Sprintf("%s (%v)", chat.Title, chat.Id)
+	} else if user != nil {
+		username := user.Username
+		if username == "" {
+			username = strconv.Itoa(int(user.Id))
+		}
+
+		str = fmt.Sprintf("%s %s (%v)", user.FirstName, user.LastName, username)
+	} else {
+		str = strconv.Itoa(int(chatID))
+	}
+
+	str = spaceRegex.ReplaceAllString(str, " ")
+
+	return str
+}
+
+func (c *Client) formatMessage(chatID int64, messageID int64, preview bool, message *client.Message) string {
+	var err error
+	if message == nil {
+		message, err = c.client.GetMessage(&client.GetMessageRequest{
+			ChatId:    chatID,
+			MessageId: messageID,
+		})
+		if err != nil {
+			return "<error fetching message>"
+		}
+	}
+
+	if message == nil {
+		return ""
+	}
+
+	var str strings.Builder
+	str.WriteString(fmt.Sprintf("%v | %s | ", message.Id, c.formatContact(message.SenderUserId)))
+	// TODO: timezone
+	if !preview {
+		str.WriteString(time.Unix(int64(message.Date), 0).Format("02 Jan 2006 15:04:05 | "))
+	}
+
+	var text string
+	switch message.Content.MessageContentType() {
+	case client.TypeMessageText:
+		messageText, _ := message.Content.(*client.MessageText)
+		text = messageText.Text.Text
+		// TODO: handle other message types with labels (not supported in Zhabogram!)
+	}
+	if text != "" {
+		if !preview {
+			str.WriteString(text)
+		} else {
+			newlinePos := strings.Index(text, newlineChar)
+			if !preview || newlinePos == -1 {
+				str.WriteString(text)
+			} else {
+				str.WriteString(text[0:newlinePos])
+			}
+		}
+	}
+
+	return str.String()
+}
+
+func (c *Client) formatContent(file *client.File, filename string) string {
+	if file == nil {
+		return ""
+	}
+
+	return fmt.Sprintf(
+		"%s (%v kbytes) | %s/%s%s",
+		filename,
+		file.Size/1024,
+		c.content.Link,
+		fmt.Sprintf("%x", sha256.Sum256([]byte(file.Remote.Id))),
+		filepath.Ext(filename),
+	)
+}
+
+func (c *Client) messageToText(message *client.Message) string {
+	switch message.Content.MessageContentType() {
+	case client.TypeMessageSticker:
+		sticker, _ := message.Content.(*client.MessageSticker)
+		return sticker.Sticker.Emoji
+	case client.TypeMessageBasicGroupChatCreate, client.TypeMessageSupergroupChatCreate:
+		return "has created chat"
+	case client.TypeMessageChatJoinByLink:
+		return "joined chat via invite link"
+	case client.TypeMessageChatAddMembers:
+		addMembers, _ := message.Content.(*client.MessageChatAddMembers)
+
+		text := "invited "
+		if len(addMembers.MemberUserIds) > 0 {
+			text += c.formatContact(addMembers.MemberUserIds[0])
+		}
+
+		return text
+	case client.TypeMessageChatDeleteMember:
+		deleteMember, _ := message.Content.(*client.MessageChatDeleteMember)
+		return "kicked " + c.formatContact(deleteMember.UserId)
+	case client.TypeMessagePinMessage:
+		pinMessage, _ := message.Content.(*client.MessagePinMessage)
+		return "pinned message: " + c.formatMessage(message.ChatId, pinMessage.MessageId, false, nil)
+	case client.TypeMessageChatChangeTitle:
+		changeTitle, _ := message.Content.(*client.MessageChatChangeTitle)
+		return "chat title set to: " + changeTitle.Title
+	case client.TypeMessageLocation:
+		location, _ := message.Content.(*client.MessageLocation)
+		return fmt.Sprintf(
+			"coordinates: %v,%v | https://www.google.com/maps/search/%v,%v/",
+			location.Location.Latitude,
+			location.Location.Longitude,
+			location.Location.Latitude,
+			location.Location.Longitude,
+		)
+	case client.TypeMessagePhoto:
+		photo, _ := message.Content.(*client.MessagePhoto)
+		return photo.Caption.Text
+	case client.TypeMessageAudio:
+		audio, _ := message.Content.(*client.MessageAudio)
+		return audio.Caption.Text
+	case client.TypeMessageVideo:
+		video, _ := message.Content.(*client.MessageVideo)
+		return video.Caption.Text
+	case client.TypeMessageDocument:
+		document, _ := message.Content.(*client.MessageDocument)
+		return document.Caption.Text
+	case client.TypeMessageText:
+		text, _ := message.Content.(*client.MessageText)
+		return text.Text.Text
+	case client.TypeMessageVoiceNote:
+		voice, _ := message.Content.(*client.MessageVoiceNote)
+		return voice.Caption.Text
+	case client.TypeMessageVideoNote:
+		return ""
+	case client.TypeMessageAnimation:
+		animation, _ := message.Content.(*client.MessageAnimation)
+		return animation.Caption.Text
+	}
+
+	return fmt.Sprintf("unknown message (%s)", message.Content.MessageContentType())
+}
+
+func (c *Client) contentToFilename(content client.MessageContent) (*client.File, string) {
+	switch content.MessageContentType() {
+	case client.TypeMessageSticker:
+		sticker, _ := content.(*client.MessageSticker)
+		return sticker.Sticker.Sticker, "sticker.webp"
+	case client.TypeMessageVoiceNote:
+		voice, _ := content.(*client.MessageVoiceNote)
+		return voice.VoiceNote.Voice, fmt.Sprintf("voice note (%v s.).oga", voice.VoiceNote.Duration)
+	case client.TypeMessageVideoNote:
+		video, _ := content.(*client.MessageVideoNote)
+		return video.VideoNote.Video, fmt.Sprintf("video note (%v s.).mp4", video.VideoNote.Duration)
+	case client.TypeMessageAnimation:
+		animation, _ := content.(*client.MessageAnimation)
+		return animation.Animation.Animation, "animation.mp4"
+	case client.TypeMessagePhoto:
+		photo, _ := content.(*client.MessagePhoto)
+		sizes := photo.Photo.Sizes
+		file := sizes[len(sizes)-1].Photo
+		if len(sizes) > 1 {
+			return file, strconv.Itoa(int(file.Id)) + ".jpg"
+		} else {
+			return nil, ""
+		}
+	case client.TypeMessageAudio:
+		audio, _ := content.(*client.MessageAudio)
+		return audio.Audio.Audio, audio.Audio.FileName
+	case client.TypeMessageVideo:
+		video, _ := content.(*client.MessageVideo)
+		return video.Video.Video, video.Video.FileName
+	case client.TypeMessageDocument:
+		document, _ := content.(*client.MessageDocument)
+		return document.Document.Document, document.Document.FileName
+	}
+
+	return nil, ""
+}
+
+func (c *Client) messageToPrefix(message *client.Message, fileString string) string {
+	prefix := []string{}
+	// message direction
+	var directionChar string
+	if message.IsOutgoing {
+		directionChar = "➡ "
+	} else {
+		directionChar = "⬅ "
+	}
+	prefix = append(prefix, directionChar+strconv.Itoa(int(message.Id)))
+	// show sender in group chats
+	if message.ChatId < 0 && message.SenderUserId != 0 {
+		prefix = append(prefix, c.formatContact(message.SenderUserId))
+	}
+	if message.ForwardInfo != nil {
+		switch message.ForwardInfo.Origin.MessageForwardOriginType() {
+		case client.TypeMessageForwardOriginUser:
+			originUser := message.ForwardInfo.Origin.(*client.MessageForwardOriginUser)
+			prefix = append(prefix, "fwd: "+c.formatContact(originUser.SenderUserId))
+		case client.TypeMessageForwardOriginHiddenUser:
+			originUser := message.ForwardInfo.Origin.(*client.MessageForwardOriginHiddenUser)
+			prefix = append(prefix, fmt.Sprintf("fwd: anonymous (%s)", originUser.SenderName))
+		case client.TypeMessageForwardOriginChannel:
+			channel := message.ForwardInfo.Origin.(*client.MessageForwardOriginChannel)
+			var signature string
+			if channel.AuthorSignature != "" {
+				signature = fmt.Sprintf(" (%s)", channel.AuthorSignature)
+			}
+			prefix = append(prefix, "fwd: "+c.formatContact(int32(channel.ChatId))+signature)
+		}
+	}
+	// reply to
+	if message.ReplyToMessageId != 0 {
+		prefix = append(prefix, "reply: "+c.formatMessage(message.ChatId, message.ReplyToMessageId, true, nil))
+	}
+	if fileString != "" {
+		prefix = append(prefix, "file: "+fileString)
+	}
+
+	return strings.Join(prefix, " | ")
+}
+
 func (c *Client) ProcessOutgoingMessage(chatID int, text string, messageID int) {
 	// TODO
 }
-- 
cgit v1.2.3