aboutsummaryrefslogblamecommitdiff
path: root/telegram/handlers.go
blob: a8cb81417a4bed4d867ce0457be7eb72bfd7d570 (plain) (tree)
1
2
3
4
5
6
7
8
9


                
             
                 
                 
              
 
                                                                

                                                          
                                        
                                            





                                         















                                                              









                                                               












                                                                              
                                                                   





                                                                                    
                                                                     





                                                                                 
                                                                   













                                                                                         





                                                                                    
                                                                      





                                                                                        
                                                                         





                                                                                        





                                                                                            









                                                         
                      
                                                        
                                                    

                                                                                            
 
 
                      
                                                                    

                                                                                                                   

 
                      
                                                              

                                                                                                     
                                                                                      



                                                                              
                 
 
                                                            
 

                                                                                   
                 
 

                                                                                        

                 
 
 













                                                                              
                   
                                                                    
                   

                                               
                                                                   
                                                    








                                                                                                                        
 
                                          
                                          
                                                
 





                                                                                                        
                                       


                                                                                               
                         

                                                                     
                                                        
 

                                                 



                                                                                
                         


                                                                                        
                                                                                                         









                                                                                         
 

                                                      

                 

                                                                  
                                           
                                                               


                                          
                                                                                       
           
 
 
                          
                                                                            
                                                   

                                                                             






                                                                                             



                                                                          
                                                                                              


         
                     

                                                                            

                                                                                              

         
 








                                                                                    
package telegram

import (
	"fmt"
	"strconv"
	"strings"
	"sync"

	"dev.narayana.im/narayana/telegabber/telegram/formatter"
	"dev.narayana.im/narayana/telegabber/xmpp/gateway"

	log "github.com/sirupsen/logrus"
	"github.com/zelenin/go-tdlib/client"
)

func uhOh() {
	log.Fatal("Update type mismatch")
}

func int64SliceToStringSlice(ints []int64) []string {
	strings := make([]string, len(ints))
	wg := sync.WaitGroup{}

	for i, xi := range ints {
		wg.Add(1)
		go func(i int, xi int64) {
			strings[i] = strconv.FormatInt(xi, 10)
			wg.Done()
		}(i, xi)
	}
	wg.Wait()

	return strings
}

func (c *Client) getChatMessageLock(chatID int64) *sync.Mutex {
	lock, ok := c.locks.chatMessageLocks[chatID]
	if !ok {
		lock = &sync.Mutex{}
		c.locks.chatMessageLocks[chatID] = lock
	}

	return lock
}

func (c *Client) updateHandler() {
	listener := c.client.GetListener()
	defer listener.Close()

	for update := range listener.Updates {
		if update.GetClass() == client.ClassUpdate {
			switch update.GetType() {
			case client.TypeUpdateUser:
				typedUpdate, ok := update.(*client.UpdateUser)
				if !ok {
					uhOh()
				}
				c.updateUser(typedUpdate)
				log.Debugf("%#v", typedUpdate.User)
			case client.TypeUpdateUserStatus:
				typedUpdate, ok := update.(*client.UpdateUserStatus)
				if !ok {
					uhOh()
				}
				c.updateUserStatus(typedUpdate)
				log.Debugf("%#v", typedUpdate.Status)
			case client.TypeUpdateNewChat:
				typedUpdate, ok := update.(*client.UpdateNewChat)
				if !ok {
					uhOh()
				}
				c.updateNewChat(typedUpdate)
				log.Debugf("%#v", typedUpdate.Chat)
			case client.TypeUpdateChatPosition:
				typedUpdate, ok := update.(*client.UpdateChatPosition)
				if !ok {
					uhOh()
				}
				c.updateChatPosition(typedUpdate)
				log.Debugf("%#v", typedUpdate)
			case client.TypeUpdateChatLastMessage:
				typedUpdate, ok := update.(*client.UpdateChatLastMessage)
				if !ok {
					uhOh()
				}
				c.updateChatLastMessage(typedUpdate)
				log.Debugf("%#v", typedUpdate)
			case client.TypeUpdateNewMessage:
				typedUpdate, ok := update.(*client.UpdateNewMessage)
				if !ok {
					uhOh()
				}
				c.updateNewMessage(typedUpdate)
				log.Debugf("%#v", typedUpdate.Message)
			case client.TypeUpdateMessageContent:
				typedUpdate, ok := update.(*client.UpdateMessageContent)
				if !ok {
					uhOh()
				}
				c.updateMessageContent(typedUpdate)
				log.Debugf("%#v", typedUpdate.NewContent)
			case client.TypeUpdateDeleteMessages:
				typedUpdate, ok := update.(*client.UpdateDeleteMessages)
				if !ok {
					uhOh()
				}
				c.updateDeleteMessages(typedUpdate)
			case client.TypeUpdateAuthorizationState:
				typedUpdate, ok := update.(*client.UpdateAuthorizationState)
				if !ok {
					uhOh()
				}
				c.updateAuthorizationState(typedUpdate)
			default:
				// log only handled types
				continue
			}

			log.Debugf("%#v", update)
		}
	}
}

// new user discovered
func (c *Client) updateUser(update *client.UpdateUser) {
	c.cache.SetUser(update.User.Id, update.User)
	show, status, presenceType := c.userStatusToText(update.User.Status, update.User.Id)
	go c.ProcessStatusUpdate(update.User.Id, status, show, gateway.SPType(presenceType))
}

// user status changed
func (c *Client) updateUserStatus(update *client.UpdateUserStatus) {
	show, status, presenceType := c.userStatusToText(update.Status, update.UserId)
	go c.ProcessStatusUpdate(update.UserId, status, show, gateway.SPImmed(false), gateway.SPType(presenceType))
}

// new chat discovered
func (c *Client) updateNewChat(update *client.UpdateNewChat) {
	go func() {
		if update.Chat != nil && update.Chat.Photo != nil && update.Chat.Photo.Small != nil {
			_, err := c.DownloadFile(update.Chat.Photo.Small.Id, 10, true)

			if err != nil {
				log.Error("Failed to download the chat photo")
			}
		}

		c.cache.SetChat(update.Chat.Id, update.Chat)

		if update.Chat.Positions != nil && len(update.Chat.Positions) > 0 {
			c.subscribeToID(update.Chat.Id, update.Chat)
		}

		if update.Chat.Id < 0 {
			c.ProcessStatusUpdate(update.Chat.Id, update.Chat.Title, "chat")
		}
	}()
}

// chat position is updated
func (c *Client) updateChatPosition(update *client.UpdateChatPosition) {
	if update.Position != nil && update.Position.Order != 0 {
		go c.subscribeToID(update.ChatId, nil)
	}
}

// chat last message is updated
func (c *Client) updateChatLastMessage(update *client.UpdateChatLastMessage) {
	if update.Positions != nil && len(update.Positions) > 0 {
		go c.subscribeToID(update.ChatId, nil)
	}
}

// message received
func (c *Client) updateNewMessage(update *client.UpdateNewMessage) {
	go func() {
		chatId := update.Message.ChatId

		// guarantee sequential message delivering per chat
		lock := c.getChatMessageLock(chatId)
		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")

		var text string
		content := update.Message.Content
		if content != nil && content.MessageContentType() == client.TypeMessageChatChangePhoto {
			chat, err := c.client.GetChat(&client.GetChatRequest{
				ChatId: chatId,
			})
			if err == nil {
				c.cache.SetChat(chatId, chat)
				go c.ProcessStatusUpdate(chatId, "", "", gateway.SPImmed(true))
				text = "<Chat photo has changed>"
			}
		} else {
			text = c.messageToText(update.Message, false)
			file := c.contentToFile(content)

			// download file (if one)
			if file != nil {
				newFile, err := c.DownloadFile(file.Id, 1, true)
				if err == nil {
					file = newFile
				}
			}
			// OTR support (I do not know why would you need it, seriously)
			if !(strings.HasPrefix(text, "?OTR") || c.Session.RawMessages) {
				var prefix strings.Builder
				prefix.WriteString(c.messageToPrefix(update.Message, c.formatFile(file)))
				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)
				}

				text = prefix.String()
			}
		}

		// mark message as read
		c.client.ViewMessages(&client.ViewMessagesRequest{
			ChatId:     chatId,
			MessageIds: []int64{update.Message.Id},
			ForceRead:  true,
		})
		// forward message to XMPP
		gateway.SendMessage(c.jid, strconv.FormatInt(chatId, 10), text, c.xmpp)
	}()
}

// message content updated
func (c *Client) updateMessageContent(update *client.UpdateMessageContent) {
	markupFunction := formatter.EntityToXEP0393
	if update.NewContent.MessageContentType() == client.TypeMessageText {
		textContent := update.NewContent.(*client.MessageText)
		var editChar string
		if c.Session.AsciiArrows {
			editChar = "e "
		} else {
			editChar = "✎ "
		}
		text := editChar + fmt.Sprintf("%v | %s", update.MessageId, formatter.Format(
			textContent.Text.Text,
			formatter.SortEntities(textContent.Text.Entities),
			markupFunction,
		))
		gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, c.xmpp)
	}
}

// message(s) deleted
func (c *Client) updateDeleteMessages(update *client.UpdateDeleteMessages) {
	if update.IsPermanent {
		text := "✗ " + strings.Join(int64SliceToStringSlice(update.MessageIds), ",")
		gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, c.xmpp)
	}
}

func (c *Client) updateAuthorizationState(update *client.UpdateAuthorizationState) {
	switch update.AuthorizationState.AuthorizationStateType() {
	case client.TypeAuthorizationStateClosing:
		log.Warn("Closing the updates listener")
	case client.TypeAuthorizationStateClosed:
		log.Warn("Closed the updates listener")
		c.forceClose()
	}
}