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


            

                         
                      
             
                               
            
              

                 
 
                                                         
                                                      
                                                             
                                                          
 
                                        
                                  



                              




                                           
                                                              
 
                                     
                                             



                                               
                                




                                



                                                             
                                                                 

                              






                                                                                   
                                                               
                       
                                                               
                              
                 
                                                                 
                       
                                                                 












                                                                   




                                                                
         

 
                                                   


                                                    
                                


                      












                                                  
                                                                
                        


                              

                                                
                                             
                        
                                                 
                                                                                 

                                                                  
                         
                              

                 

                                          

                                                        
                                                      

                                          
                                         

                                                             
                                                           




                                              
















                                                                                                                      
















                                                                                                                 





                                                                                             

                                 














                                                                                                                                                                     
 

                                                              







                                                                                                                                          
                                                                                     

                                                                                                                       
                                                       
                                                                                        
                                                                                             
                                                




                                                                                                                    






                                                                                                                                      
                         
                              


                                                                                                                                               
                                                                                                  
                                                   
                                                                                                 





                                                                    

                           


                                                                 

                 
                                                       
                                                                                











                                                                                

                         























                                                                            



                                                   




                                                                                            
                                                                                                                                                  


                                 
         
 










                                                     
         
                                         

















                                                           






                                                   




                                
                                               







                                                                                   







                                                       





                                            






                                     
                         
                                                      
                

                      
                                                                                   
                
                      


                       
                          
                                           
                                                        
                                          
                                              
                                            
                 
                     
                                    
                                                   
                    
                                                
                                                                                

                                                                            
                                                        

                                                                                       

                                                                             






                                                                                              

                                                                       


                                                            

                                         
                                                             

                         

         
 
                                                               




                                  
                                              
















                                                         
                                               




                              






                                       
                                                                     


                                 





                                            
                                                     
 
 














                                                                             
                                                                             


                                                 

                              



                                                                 
                      


                                   
                              





                                                                      

                                                                                    


                                            

                                                                            


                                                          
                        




                                                                          


                                                                                                            



                                                                                                                         

                         
         












                                                    
                                                                               











                                                                 


                                         


                                   

                                                                            


                                                          
                        



                                                               

                                                                           


                                                                                                    
                                                                                            
                 

                                                    
         
































































































































                                                                                                                                                                                                                                                                        






















                                                                                   



                                                       
                                   

                            















                                                                                
                         











                                                                    
 






                                                                                          




                                                                       






























                                                                                                                                              


                                                                                          










                                                                                
                                 
                                                                 


                                                                                      
                                                              
                                 
                                                             







                                                                         
                         

                 
 




                                             
 
                                   
                                
                         
                                                                                          
                        







                                                                                                
                 






                                                                      
                                               
                          
                 
 
         

                                                           

 





                                                                                       

                                                       



                                              

                                                       
                                              
                                                                                                                                                                                   


                                           

                                                       
                                                 
                                                                                                                 


                                           

                                                     




                                                                               

                                                       




                                                      













                                                             
 






















                                                                                                                


                                                  

                                       
                                                            
                                                             
                 
                                                                       



                                                 



                                        
                                  




                                                                                 
                                                                 



                                          
                                      




                                                                                
                                                                                                 



                                          
                                                         




                                                                                 
                                                                  

                                          




                                                                                
                                                                                    

                                          

                          
                                                          




                                                                                    
                                                                     


                                                                                  
                                                                    



                                          
                                   




                                                                                
                                                                           



                                          
                                    




                                                                                 
                                                                   



                                          





                                                    
                                                       



















                                                                                                                  
package xmpp

import (
	"bytes"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"github.com/pkg/errors"
	"io"
	"sort"
	"strconv"
	"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"

	log "github.com/sirupsen/logrus"
	"github.com/soheilhy/args"
	"gosrc.io/xmpp"
	"gosrc.io/xmpp/stanza"
)

const (
	TypeVCardTemp byte = iota
	TypeVCard4
)
const NodeVCard4 string = "urn:xmpp:vcard4"
const NSCommand string = "http://jabber.org/protocol/commands"

func logPacketType(p stanza.Packet) {
	log.Warnf("Ignoring packet: %T\n", p)
}

// HandleIq processes an incoming XMPP iq
func HandleIq(s xmpp.Sender, p stanza.Packet) {
	iq, ok := p.(*stanza.IQ)
	if !ok {
		logPacketType(p)
		return
	}

	log.Debugf("%#v", iq)
	if iq.Type == "get" {
		_, ok := iq.Payload.(*extensions.IqVcardTemp)
		if ok {
			go handleGetVcardIq(s, iq, TypeVCardTemp)
			return
		}
		pubsub, ok := iq.Payload.(*stanza.PubSubGeneric)
		if ok {
			if pubsub.Items != nil && pubsub.Items.Node == NodeVCard4 {
				go handleGetVcardIq(s, iq, TypeVCard4)
				return
			}
		}
		discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
		if ok {
			go handleGetDiscoInfo(s, iq, discoInfo)
			return
		}
		discoItems, ok := iq.Payload.(*stanza.DiscoItems)
		if ok {
			go handleGetDiscoItems(s, iq, discoItems)
			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
		}
		command, ok := iq.Payload.(*stanza.Command)
		if ok {
			go handleSetQueryCommand(s, iq, command)
			return
		}
	}
}

// HandleMessage processes an incoming XMPP message
func HandleMessage(s xmpp.Sender, p stanza.Packet) {
	msg, ok := p.(stanza.Message)
	if !ok {
		logPacketType(p)
		return
	}

	component, ok := s.(*xmpp.Component)
	if !ok {
		log.Error("Not a component")
		return
	}

	if msg.Type != "error" && msg.Body != "" {
		log.WithFields(log.Fields{
			"from": msg.From,
			"to":   msg.To,
		}).Warn("Message")
		log.Debugf("%#v", msg)

		bare, resource, ok := gateway.SplitJID(msg.From)
		if !ok {
			return
		}

		gatewayJid := gateway.Jid.Bare()

		session, ok := sessions[bare]
		if !ok {
			if msg.To == gatewayJid {
				gateway.SubscribeToTransport(component, msg.From)
			} else {
				log.Error("Message from stranger")
			}
			return
		}

		toID, ok := toToID(msg.To)
		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
			text := msg.Body
			if len(reply.Id) > 0 {
				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 {
					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!"))
					}

					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
			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), "<ERROR: Chat mismatch>", component)
						return
					}
					replaceId = msgId
					log.Debugf("replace tg: %#v %#v", chatId, msgId)
				} else {
					gateway.SendTextMessage(msg.From, strconv.FormatInt(toID, 10), "<ERROR: Could not find matching message to edit>", component)
					return
				}
			}

			session.SendMessageLock.Lock()
			defer session.SendMessageLock.Unlock()
			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)
					} */
					session.AddToEditOutbox(replace.Id, resource)
				} else {
					err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessageId, msg.Id)
					if err == nil {
						// session.AddToOutbox(msg.Id, resource)
						session.UpdateLastChatMessageId(toID, msg.Id)
					} else {
						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)
			if err == nil && toJid.Bare() == gatewayJid && (strings.HasPrefix(msg.Body, "/") || strings.HasPrefix(msg.Body, "!")) {
				response, _ := session.ProcessTransportCommand(msg.Body, resource)
				if response != "" {
					gateway.SendServiceMessage(msg.From, response, component)
				}
				return
			}
		}
		log.Warn("Unknown purpose of the message, skipping")
	}

	if msg.Body == "" {
		var privilege1 extensions.ComponentPrivilege1
		if ok := msg.Get(&privilege1); ok {
			log.Debugf("privilege1: %#v", privilege1)
		}

		for _, perm := range privilege1.Perms {
			if perm.Access == "message" && perm.Type == "outgoing" {
				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
			}
		}

		var displayed stanza.MarkDisplayed
		msg.Get(&displayed)
		if displayed.ID != "" {
			log.Debugf("displayed: %#v", displayed)

			bare, _, ok := gateway.SplitJID(msg.From)
			if !ok {
				return
			}
			session, ok := sessions[bare]
			if !ok {
				return
			}
			toID, ok := toToID(msg.To)
			if !ok {
				return
			}
			msgId, err := strconv.ParseInt(displayed.ID, 10, 64)
			if err == nil {
				session.MarkAsRead(toID, msgId)
			}
			return
		}
	}

	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)
				}
			}
		}
	}
}

// HandlePresence processes an incoming XMPP presence
func HandlePresence(s xmpp.Sender, p stanza.Packet) {
	prs, ok := p.(stanza.Presence)
	if !ok {
		logPacketType(p)
		return
	}

	if prs.Type == "subscribe" {
		handleSubscription(s, prs)
	}
	if prs.To == gateway.Jid.Bare() {
		handlePresence(s, prs)
	}
}

func handleSubscription(s xmpp.Sender, p stanza.Presence) {
	log.WithFields(log.Fields{
		"from": p.From,
		"to":   p.To,
	}).Warn("Subscription request")
	log.Debugf("%#v", p)

	reply := stanza.Presence{Attrs: stanza.Attrs{
		From: p.To,
		To:   p.From,
		Id:   p.Id,
		Type: "subscribed",
	}}

	component, ok := s.(*xmpp.Component)
	if !ok {
		log.Error("Not a component")
		return
	}

	_ = gateway.ResumableSend(component, reply)

	toID, ok := toToID(p.To)
	if !ok {
		return
	}
	bare, _, ok := gateway.SplitJID(p.From)
	if !ok {
		return
	}
	session, ok := getTelegramInstance(bare, &persistence.Session{}, component)
	if !ok {
		return
	}
	go session.ProcessStatusUpdate(toID, "", "", gateway.SPImmed(false))
}

func handlePresence(s xmpp.Sender, p stanza.Presence) {
	presenceType := p.Type
	if presenceType == "" {
		presenceType = "online"
	}

	component, ok := s.(*xmpp.Component)
	if !ok {
		log.Error("Not a component")
		return
	}

	log.WithFields(log.Fields{
		"type": presenceType,
		"from": p.From,
		"to":   p.To,
	}).Warn("Presence")
	log.Debugf("%#v", p)

	// create session
	bare, resource, ok := gateway.SplitJID(p.From)
	if !ok {
		return
	}
	session, ok := getTelegramInstance(bare, &persistence.Session{}, component)
	if !ok {
		return
	}

	switch p.Type {
	// destroy session
	case "unsubscribed", "unsubscribe":
		if session.Disconnect(resource, false) {
			sessionLock.Lock()
			delete(sessions, bare)
			sessionLock.Unlock()
		}
	// go offline
	case "unavailable", "error":
		session.Disconnect(resource, false)
	// go online
	case "probe", "", "online", "subscribe":
		// due to the weird implementation of go-tdlib wrapper, it won't
		// return the client instance until successful authorization
		go func() {
			err := session.Connect(resource)
			if err != nil {
				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,
						description,
						show,
						newArgs...,
					)
				}
				session.UpdateChatNicknames()
			}
		}()
	}
}

func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
	log.WithFields(log.Fields{
		"from": iq.From,
		"to":   iq.To,
	}).Warn("VCard request")

	fromJid, err := stanza.NewJid(iq.From)
	if err != nil {
		log.Error("Invalid from JID!")
		return
	}

	session, ok := sessions[fromJid.Bare()]
	if !ok {
		log.Error("IQ from stranger")
		return
	}

	toParts := strings.Split(iq.To, "@")
	toID, err := strconv.ParseInt(toParts[0], 10, 64)
	if err != nil {
		log.Error("Invalid IQ to")
		return
	}
	info, err := session.GetVcardInfo(toID)
	if err != nil {
		log.Error(err)
		return
	}

	answer := stanza.IQ{
		Attrs: stanza.Attrs{
			From: iq.To,
			To:   iq.From,
			Id:   iq.Id,
			Type: "result",
		},
		Payload: makeVCardPayload(typ, iq.To, info, session),
	}
	log.Debugf("%#v", answer)

	component, ok := s.(*xmpp.Component)
	if !ok {
		log.Error("Not a component")
		return
	}

	_ = gateway.ResumableSend(component, &answer)
}

func getTelegramChatType(from string, to string) (telegram.ChatType, error) {
	toId, ok := toToID(to)
	if ok {
		bare, _, ok := gateway.SplitJID(from)
		if ok {
			session, ok := sessions[bare]
			if ok {
				return session.GetChatType(toId)
			}
		}
	}

	return telegram.ChatTypeUnknown, errors.New("Unknown chat type")
}

func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
	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
	}

	disco := answer.DiscoInfo()
	_, ok := toToID(iq.To)
	if di.Node == "" {
		if ok {
			disco.AddIdentity("", "account", "registered")
			disco.AddFeatures(stanza.NSMsgChatMarkers)
			disco.AddFeatures(stanza.NSMsgReceipts)
		} else {
			disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
			disco.AddFeatures("jabber:iq:register")
		}
		disco.AddFeatures(NSCommand)
	} else {
		chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)

		var cmdType telegram.CommandType
		if ok {
			cmdType = telegram.CommandTypeChat
		} else {
			cmdType = telegram.CommandTypeTransport
		}

		for name, command := range telegram.GetCommands(cmdType) {
			if di.Node == name {
				if chatTypeErr == nil && !telegram.IsCommandForChatType(command, chatType) {
					break
				}
				answer.Payload = di
				di.AddIdentity(telegram.CommandToHelpString(name, command), "automation", "command-node")
				di.AddFeatures(NSCommand, "jabber:x:data")
				break
			}
		}
	}
	answer.Payload = disco

	log.Debugf("%#v", answer)

	component, ok := s.(*xmpp.Component)
	if !ok {
		log.Error("Not a component")
		return
	}

	_ = gateway.ResumableSend(component, answer)
}

func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoItems) {
	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
	}

	log.Debugf("discoItems: %#v", di)

	_, ok := toToID(iq.To)
	if di.Node == NSCommand {
		answer.Payload = di

		chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)

		var cmdType telegram.CommandType
		if ok {
			cmdType = telegram.CommandTypeChat
		} else {
			cmdType = telegram.CommandTypeTransport
		}

		commands := telegram.GetCommands(cmdType)
		for _, name := range telegram.SortedCommandKeys(commands) {
			command := commands[name]
			if chatTypeErr == nil && !telegram.IsCommandForChatType(command, chatType) {
				continue
			}
			di.AddItem(iq.To, name, telegram.CommandToHelpString(name, command))
		}
	} else {
		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 handleSetQueryCommand(s xmpp.Sender, iq *stanza.IQ, command *stanza.Command) {
	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)

	log.Debugf("command: %#v", command)

	bare, resource, ok := gateway.SplitJID(iq.From)
	if !ok {
		return
	}
	toId, toOk := toToID(iq.To)

	var cmdString string
	var cmdType telegram.CommandType
	form, formOk := command.CommandElement.(*stanza.Form)
	if toOk {
		cmdType = telegram.CommandTypeChat
	} else {
		cmdType = telegram.CommandTypeTransport
	}
	if formOk {
		// just for the case the client messed the order somehow
		sort.Slice(form.Fields, func(i int, j int) bool {
			iField := form.Fields[i]
			jField := form.Fields[j]
			if iField != nil && jField != nil {
				ii, iErr := strconv.ParseInt(iField.Var, 10, 64)
				ji, jErr := strconv.ParseInt(jField.Var, 10, 64)
				return iErr == nil && jErr == nil && ii < ji
			}
			return false
		})

		var cmd strings.Builder
		cmd.WriteString("/")
		cmd.WriteString(command.Node)
		for _, field := range form.Fields {
			cmd.WriteString(" ")
			if len(field.ValuesList) > 0 {
				cmd.WriteString(field.ValuesList[0])
			}
		}

		cmdString = cmd.String()
	} else {
		if command.Action == "" || command.Action == stanza.CommandActionExecute {
			cmd, ok := telegram.GetCommand(cmdType, command.Node)
			if ok && len(cmd.Arguments) > 0 {
				var fields []*stanza.Field
				for i, arg := range cmd.Arguments {
					var required *string
					if i < cmd.RequiredArgs {
						dummyString := ""
						required = &dummyString
					}

					var fieldType string
					var options []stanza.Option
					if toOk && i == 0 {
						switch command.Node {
						case "mute", "kick", "ban", "promote", "unmute", "unban":
							session, ok := sessions[bare]
							if ok {
								var membersList telegram.MembersList
								switch command.Node {
								case "unmute":
									membersList = telegram.MembersListRestricted
								case "unban":
									membersList = telegram.MembersListBannedAndAdministrators
								}
								members, err := session.GetChatMembers(toId, true, "", membersList)
								if err == nil {
									fieldType = stanza.FieldTypeListSingle
									for _, member := range members {
										senderId := session.GetSenderId(member.MemberId)
										options = append(options, stanza.Option{
											Label:      session.FormatContact(senderId),
											ValuesList: []string{strconv.FormatInt(senderId, 10)},
										})
									}
								}
							}
						}
					}

					field := stanza.Field{
						Var:      strconv.FormatInt(int64(i), 10),
						Label:    arg,
						Required: required,
						Type:     fieldType,
						Options:  options,
					}
					fields = append(fields, &field)
					log.Debugf("field: %#v", field)
				}
				form := stanza.Form{
					Type:         stanza.FormTypeForm,
					Title:        command.Node,
					Instructions: []string{cmd.Description},
					Fields:       fields,
				}
				answer.Payload = &stanza.Command{
					SessionId:      command.Node,
					Node:           command.Node,
					Status:         stanza.CommandStatusExecuting,
					CommandElement: &form,
				}
				log.Debugf("form: %#v", form)
			} else {
				cmdString = "/" + command.Node
			}
		} else if command.Action == stanza.CommandActionCancel {
			answer.Payload = &stanza.Command{
				SessionId: command.Node,
				Node:      command.Node,
				Status:    stanza.CommandStatusCancelled,
			}
		}
	}

	if cmdString != "" {
		session, ok := sessions[bare]
		if !ok {
			return
		}

		var response string
		var success bool
		if toOk {
			response, _, success = session.ProcessChatCommand(toId, cmdString)
		} else {
			response, success = session.ProcessTransportCommand(cmdString, resource)
		}

		var noteType string
		if success {
			noteType = stanza.CommandNoteTypeInfo
		} else {
			noteType = stanza.CommandNoteTypeErr
		}

		answer.Payload = &stanza.Command{
			SessionId:      command.Node,
			Node:           command.Node,
			Status:         stanza.CommandStatusCompleted,
			CommandElement: &stanza.Note{
				Text: response,
				Type: noteType,
			},
		}

	}

	log.Debugf("command response: %#v", answer.Payload)
}

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 {
		return 0, false
	}
	toID, err := strconv.ParseInt(toParts[0], 10, 64)
	if err != nil {
		log.WithFields(log.Fields{
			"to": to,
		}).Error(errors.Wrap(err, "Invalid to JID!"))
		return 0, false
	}
	return toID, true
}

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 = info.Fn
		if base64Photo != "" {
			vcard.Photo.Type.Text = "image/jpeg"
			vcard.Photo.Binval.Text = base64Photo
		}
		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
		vcard.Desc.Text = info.Info

		return vcard
	} else if typ == TypeVCard4 {
		nodes := []stanza.Node{}
		if info.Fn != "" {
			nodes = append(nodes, stanza.Node{
				XMLName: xml.Name{Local: "fn"},
				Nodes: []stanza.Node{
					stanza.Node{
						XMLName: xml.Name{Local: "text"},
						Content: info.Fn,
					},
				},
			})
		}
		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," + base64Photo,
					},
				},
			})
		}
		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: nickname,
					},
				},
			}, stanza.Node{
				XMLName: xml.Name{Local: "impp"},
				Nodes: []stanza.Node{
					stanza.Node{
						XMLName: xml.Name{Local: "uri"},
						Content: "https://t.me/" + nickname,
					},
				},
			})
		}
		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: info.Family,
					},
					stanza.Node{
						XMLName: xml.Name{Local: "given"},
						Content: info.Given,
					},
				},
			})
		}
		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:" + info.Tel,
					},
				},
			})
		}
		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.Info,
					},
				},
			})
		}

		pubsub := &stanza.PubSubGeneric{
			Items: &stanza.Items{
				Node: NodeVCard4,
				List: []stanza.Item{
					stanza.Item{
						Id: id,
						Any: &stanza.Node{
							XMLName: xml.Name{Local: "vcard"},
							Attrs: []xml.Attr{
								xml.Attr{
									Name:  xml.Name{Local: "xmlns"},
									Value: "urn:ietf:params:xml:ns:vcard-4.0",
								},
							},
							Nodes: nodes,
						},
					},
				},
			},
		}

		return pubsub
	}

	return nil
}