2019-10-22 16:36:54 +00:00
package xmpp
import (
2019-12-10 18:34:55 +00:00
"bytes"
"encoding/base64"
2022-06-28 23:34:14 +00:00
"encoding/xml"
2023-08-28 14:16:57 +00:00
"fmt"
2019-11-07 21:09:53 +00:00
"github.com/pkg/errors"
2019-12-10 18:34:55 +00:00
"io"
2024-02-03 09:24:22 +00:00
"sort"
2019-11-24 17:10:29 +00:00
"strconv"
"strings"
2019-11-07 21:09:53 +00:00
2019-11-19 20:25:14 +00:00
"dev.narayana.im/narayana/telegabber/persistence"
2023-06-01 20:37:38 +00:00
"dev.narayana.im/narayana/telegabber/telegram"
2019-12-10 18:34:55 +00:00
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
2019-11-24 17:10:29 +00:00
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
2019-11-19 20:25:14 +00:00
2019-11-03 22:15:43 +00:00
log "github.com/sirupsen/logrus"
2023-08-07 00:04:49 +00:00
"github.com/soheilhy/args"
2019-10-22 16:36:54 +00:00
"gosrc.io/xmpp"
"gosrc.io/xmpp/stanza"
)
2022-06-28 23:34:14 +00:00
const (
TypeVCardTemp byte = iota
TypeVCard4
)
const NodeVCard4 string = "urn:xmpp:vcard4"
2024-01-31 02:38:46 +00:00
const NSCommand string = "http://jabber.org/protocol/commands"
2022-06-28 23:34:14 +00:00
2019-11-03 22:15:43 +00:00
func logPacketType ( p stanza . Packet ) {
2019-11-14 20:11:38 +00:00
log . Warnf ( "Ignoring packet: %T\n" , p )
2019-11-03 22:15:43 +00:00
}
// HandleIq processes an incoming XMPP iq
func HandleIq ( s xmpp . Sender , p stanza . Packet ) {
2021-12-18 16:04:24 +00:00
iq , ok := p . ( * stanza . IQ )
2019-11-03 22:15:43 +00:00
if ! ok {
logPacketType ( p )
return
}
2019-12-10 18:34:55 +00:00
log . Debugf ( "%#v" , iq )
if iq . Type == "get" {
_ , ok := iq . Payload . ( * extensions . IqVcardTemp )
if ok {
2022-06-28 23:34:14 +00:00
go handleGetVcardIq ( s , iq , TypeVCardTemp )
2022-02-08 17:09:14 +00:00
return
}
2022-06-28 23:34:14 +00:00
pubsub , ok := iq . Payload . ( * stanza . PubSubGeneric )
if ok {
if pubsub . Items != nil && pubsub . Items . Node == NodeVCard4 {
go handleGetVcardIq ( s , iq , TypeVCard4 )
return
}
}
2024-01-31 02:38:46 +00:00
discoInfo , ok := iq . Payload . ( * stanza . DiscoInfo )
2022-02-08 17:09:14 +00:00
if ok {
2024-01-31 02:38:46 +00:00
go handleGetDiscoInfo ( s , iq , discoInfo )
2022-02-08 17:09:14 +00:00
return
2019-12-10 18:34:55 +00:00
}
2024-01-31 02:38:46 +00:00
discoItems , ok := iq . Payload . ( * stanza . DiscoItems )
2023-08-28 14:16:57 +00:00
if ok {
2024-01-31 02:38:46 +00:00
go handleGetDiscoItems ( s , iq , discoItems )
2023-08-28 14:16:57 +00:00
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
}
2024-01-31 02:38:46 +00:00
command , ok := iq . Payload . ( * stanza . Command )
if ok {
go handleSetQueryCommand ( s , iq , command )
return
}
2019-12-10 18:34:55 +00:00
}
2019-11-03 22:15:43 +00:00
}
2019-10-29 01:23:57 +00:00
// HandleMessage processes an incoming XMPP message
2019-10-22 16:36:54 +00:00
func HandleMessage ( s xmpp . Sender , p stanza . Packet ) {
msg , ok := p . ( stanza . Message )
if ! ok {
2019-11-03 22:15:43 +00:00
logPacketType ( p )
2019-10-22 16:36:54 +00:00
return
}
2019-11-24 17:10:29 +00:00
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 )
2023-03-18 21:43:11 +00:00
bare , resource , ok := gateway . SplitJID ( msg . From )
2022-02-05 10:21:56 +00:00
if ! ok {
2019-11-24 17:10:29 +00:00
return
}
2022-02-13 21:36:18 +00:00
gatewayJid := gateway . Jid . Bare ( )
2022-02-05 10:21:56 +00:00
session , ok := sessions [ bare ]
2019-11-24 17:10:29 +00:00
if ! ok {
2022-02-13 21:36:18 +00:00
if msg . To == gatewayJid {
2023-08-28 14:16:57 +00:00
gateway . SubscribeToTransport ( component , msg . From )
2022-02-13 21:36:18 +00:00
} else {
log . Error ( "Message from stranger" )
}
2023-01-13 08:59:35 +00:00
return
2019-11-24 17:10:29 +00:00
}
2022-02-05 10:21:56 +00:00
toID , ok := toToID ( msg . To )
if ok {
2023-03-14 21:16:02 +00:00
var reply extensions . Reply
var fallback extensions . Fallback
2023-06-05 08:22:13 +00:00
var replace extensions . Replace
2023-03-14 21:16:02 +00:00
msg . Get ( & reply )
msg . Get ( & fallback )
2023-06-05 08:22:13 +00:00
msg . Get ( & replace )
2023-03-14 21:16:02 +00:00
log . Debugf ( "reply: %#v" , reply )
log . Debugf ( "fallback: %#v" , fallback )
2023-06-05 08:22:13 +00:00
log . Debugf ( "replace: %#v" , replace )
2023-03-14 21:16:02 +00:00
var replyId int64
var err error
text := msg . Body
if len ( reply . Id ) > 0 {
2023-06-08 17:14:55 +00:00
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!" ) )
}
2023-03-14 21:16:02 +00:00
}
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!" ) )
}
2023-08-08 04:54:24 +00:00
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 )
2023-03-14 21:16:02 +00:00
}
}
2023-06-05 08:22:13 +00:00
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
}
}
2023-03-14 21:16:02 +00:00
2023-07-09 03:52:30 +00:00
session . SendMessageLock . Lock ( )
defer session . SendMessageLock . Unlock ( )
2023-06-05 08:22:13 +00:00
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 )
} * /
2024-01-27 02:02:47 +00:00
session . AddToEditOutbox ( replace . Id , resource )
2023-06-05 08:22:13 +00:00
} else {
err = gateway . IdsDB . Set ( session . Session . Login , bare , toID , tgMessageId , msg . Id )
2024-01-27 02:02:47 +00:00
if err == nil {
2024-04-11 02:17:58 +00:00
// session.AddToOutbox(msg.Id, resource)
2024-01-29 09:28:15 +00:00
session . UpdateLastChatMessageId ( toID , msg . Id )
2024-01-27 02:02:47 +00:00
} else {
2023-06-05 08:22:13 +00:00
log . Errorf ( "Failed to save ids %v/%v %v" , toID , tgMessageId , msg . Id )
}
}
} else {
/ *
2023-06-08 17:33:22 +00:00
// 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 )
}
} * /
2023-06-05 08:22:13 +00:00
}
2022-02-05 10:21:56 +00:00
return
2022-05-26 11:14:38 +00:00
} else {
toJid , err := stanza . NewJid ( msg . To )
if err == nil && toJid . Bare ( ) == gatewayJid && ( strings . HasPrefix ( msg . Body , "/" ) || strings . HasPrefix ( msg . Body , "!" ) ) {
2024-02-18 09:36:23 +00:00
response , _ := session . ProcessTransportCommand ( msg . Body , resource )
2019-11-24 17:10:29 +00:00
if response != "" {
2023-03-05 08:00:53 +00:00
gateway . SendServiceMessage ( msg . From , response , component )
2019-11-24 17:10:29 +00:00
}
return
}
}
log . Warn ( "Unknown purpose of the message, skipping" )
}
2023-03-18 21:43:11 +00:00
if msg . Body == "" {
2023-08-02 21:08:06 +00:00
var privilege1 extensions . ComponentPrivilege1
if ok := msg . Get ( & privilege1 ) ; ok {
log . Debugf ( "privilege1: %#v" , privilege1 )
2023-03-18 21:43:11 +00:00
}
2023-08-02 21:08:06 +00:00
for _ , perm := range privilege1 . Perms {
2023-03-18 21:43:11 +00:00
if perm . Access == "message" && perm . Type == "outgoing" {
2023-08-02 21:08:06 +00:00
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
2023-03-18 21:43:11 +00:00
}
}
2024-01-27 11:13:45 +00:00
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
}
2023-03-18 21:43:11 +00:00
}
if msg . Type == "error" {
log . Errorf ( "MESSAGE ERROR: %#v" , p )
2023-03-19 22:12:06 +00:00
if msg . XMLName . Space == "jabber:component:accept" && msg . Error . Code == 401 {
suffix := "@" + msg . From
for bare := range sessions {
if strings . HasSuffix ( bare , suffix ) {
2023-06-08 17:33:22 +00:00
gateway . SendServiceMessage ( bare , "Your server \"" + msg . From + "\" does not allow to send carbons" , component )
2023-03-19 22:12:06 +00:00
}
}
}
2023-03-18 21:43:11 +00:00
}
2019-10-22 16:36:54 +00:00
}
2019-11-03 22:15:43 +00:00
// 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 )
2019-11-07 21:09:53 +00:00
}
2019-11-24 17:10:29 +00:00
if prs . To == gateway . Jid . Bare ( ) {
2019-11-03 22:15:43 +00:00
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" ,
} }
2020-01-10 13:02:25 +00:00
component , ok := s . ( * xmpp . Component )
if ! ok {
log . Error ( "Not a component" )
return
}
_ = gateway . ResumableSend ( component , reply )
2022-02-05 10:21:56 +00:00
toID , ok := toToID ( p . To )
if ! ok {
return
}
2023-03-18 21:43:11 +00:00
bare , _ , ok := gateway . SplitJID ( p . From )
2022-02-05 10:21:56 +00:00
if ! ok {
return
}
session , ok := getTelegramInstance ( bare , & persistence . Session { } , component )
if ! ok {
return
}
go session . ProcessStatusUpdate ( toID , "" , "" , gateway . SPImmed ( false ) )
2019-11-03 22:15:43 +00:00
}
func handlePresence ( s xmpp . Sender , p stanza . Presence ) {
presenceType := p . Type
if presenceType == "" {
presenceType = "online"
}
2019-11-24 17:10:29 +00:00
component , ok := s . ( * xmpp . Component )
if ! ok {
log . Error ( "Not a component" )
return
}
2019-11-03 22:15:43 +00:00
log . WithFields ( log . Fields {
"type" : presenceType ,
"from" : p . From ,
"to" : p . To ,
} ) . Warn ( "Presence" )
log . Debugf ( "%#v" , p )
2019-12-22 01:04:45 +00:00
// create session
2023-03-18 21:43:11 +00:00
bare , resource , ok := gateway . SplitJID ( p . From )
2022-02-05 10:21:56 +00:00
if ! ok {
2019-11-03 22:15:43 +00:00
return
}
2022-02-05 10:21:56 +00:00
session , ok := getTelegramInstance ( bare , & persistence . Session { } , component )
2019-11-03 22:15:43 +00:00
if ! ok {
2019-11-12 15:50:25 +00:00
return
2019-11-03 22:15:43 +00:00
}
switch p . Type {
2019-12-22 01:04:45 +00:00
// destroy session
2019-11-20 21:45:30 +00:00
case "unsubscribed" , "unsubscribe" :
2022-02-05 10:21:56 +00:00
if session . Disconnect ( resource , false ) {
2022-01-05 21:04:22 +00:00
sessionLock . Lock ( )
2022-02-05 10:21:56 +00:00
delete ( sessions , bare )
2022-01-05 21:04:22 +00:00
sessionLock . Unlock ( )
2022-01-03 03:54:13 +00:00
}
2019-12-22 01:04:45 +00:00
// go offline
2019-11-03 22:15:43 +00:00
case "unavailable" , "error" :
2022-02-05 10:21:56 +00:00
session . Disconnect ( resource , false )
2019-12-22 01:04:45 +00:00
// go online
2022-02-13 17:33:43 +00:00
case "probe" , "" , "online" , "subscribe" :
2019-12-15 19:30:54 +00:00
// due to the weird implementation of go-tdlib wrapper, it won't
2019-11-07 21:09:53 +00:00
// return the client instance until successful authorization
go func ( ) {
2022-02-05 10:21:56 +00:00
err := session . Connect ( resource )
2019-11-07 21:09:53 +00:00
if err != nil {
log . Error ( errors . Wrap ( err , "TDlib connection failure" ) )
2020-01-05 13:03:10 +00:00
} else {
for status := range session . StatusesRange ( ) {
2023-08-07 00:04:49 +00:00
show , description , typ := status . Destruct ( )
newArgs := [ ] args . V {
gateway . SPImmed ( false ) ,
}
if typ != "" {
newArgs = append ( newArgs , gateway . SPType ( typ ) )
}
2020-01-05 13:03:10 +00:00
go session . ProcessStatusUpdate (
status . ID ,
2023-08-07 00:04:49 +00:00
description ,
show ,
newArgs ... ,
2020-01-05 13:03:10 +00:00
)
}
2023-06-30 13:54:39 +00:00
session . UpdateChatNicknames ( )
2019-11-07 21:09:53 +00:00
}
} ( )
2019-11-03 22:15:43 +00:00
}
}
2019-12-19 20:58:20 +00:00
2022-06-28 23:34:14 +00:00
func handleGetVcardIq ( s xmpp . Sender , iq * stanza . IQ , typ byte ) {
2019-12-19 20:58:20 +00:00
log . WithFields ( log . Fields {
"from" : iq . From ,
"to" : iq . To ,
} ) . Warn ( "VCard request" )
2021-12-18 16:04:24 +00:00
fromJid , err := stanza . NewJid ( iq . From )
2019-12-19 20:58:20 +00:00
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
}
2023-06-01 20:37:38 +00:00
info , err := session . GetVcardInfo ( toID )
2019-12-19 20:58:20 +00:00
if err != nil {
log . Error ( err )
return
}
answer := stanza . IQ {
Attrs : stanza . Attrs {
From : iq . To ,
To : iq . From ,
Id : iq . Id ,
Type : "result" ,
} ,
2023-06-01 20:37:38 +00:00
Payload : makeVCardPayload ( typ , iq . To , info , session ) ,
2019-12-19 20:58:20 +00:00
}
log . Debugf ( "%#v" , answer )
2020-01-10 13:02:25 +00:00
component , ok := s . ( * xmpp . Component )
if ! ok {
log . Error ( "Not a component" )
return
}
2021-12-18 16:04:24 +00:00
_ = gateway . ResumableSend ( component , & answer )
2019-12-19 20:58:20 +00:00
}
2022-02-05 10:21:56 +00:00
2024-02-15 09:40:57 +00:00
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" )
}
2024-01-31 02:38:46 +00:00
func handleGetDiscoInfo ( s xmpp . Sender , iq * stanza . IQ , di * stanza . DiscoInfo ) {
2022-02-08 17:09:14 +00:00
answer , err := stanza . NewIQ ( stanza . Attrs {
Type : stanza . IQTypeResult ,
From : iq . To ,
2022-02-08 20:25:58 +00:00
To : iq . From ,
Id : iq . Id ,
2022-02-08 17:09:14 +00:00
Lang : "en" ,
} )
if err != nil {
log . Errorf ( "Failed to create answer IQ: %v" , err )
2022-06-19 23:56:18 +00:00
return
2022-02-08 17:09:14 +00:00
}
disco := answer . DiscoInfo ( )
2022-06-19 23:56:18 +00:00
_ , ok := toToID ( iq . To )
2024-02-10 18:46:02 +00:00
if di . Node == "" {
if ok {
disco . AddIdentity ( "" , "account" , "registered" )
disco . AddFeatures ( stanza . NSMsgChatMarkers )
disco . AddFeatures ( stanza . NSMsgReceipts )
} else {
2024-01-31 02:38:46 +00:00
disco . AddIdentity ( "Telegram Gateway" , "gateway" , "telegram" )
disco . AddFeatures ( "jabber:iq:register" )
2024-02-10 18:46:02 +00:00
}
disco . AddFeatures ( NSCommand )
} else {
2024-02-15 09:40:57 +00:00
chatType , chatTypeErr := getTelegramChatType ( iq . From , iq . To )
2024-02-10 18:46:02 +00:00
var cmdType telegram . CommandType
if ok {
cmdType = telegram . CommandTypeChat
2024-01-31 02:38:46 +00:00
} else {
2024-02-10 18:46:02 +00:00
cmdType = telegram . CommandTypeTransport
}
for name , command := range telegram . GetCommands ( cmdType ) {
if di . Node == name {
2024-02-15 09:40:57 +00:00
if chatTypeErr == nil && ! telegram . IsCommandForChatType ( command , chatType ) {
break
}
2024-02-10 18:46:02 +00:00
answer . Payload = di
di . AddIdentity ( telegram . CommandToHelpString ( name , command ) , "automation" , "command-node" )
di . AddFeatures ( NSCommand , "jabber:x:data" )
break
2024-01-31 02:38:46 +00:00
}
}
2022-06-19 23:56:18 +00:00
}
2022-02-08 17:09:14 +00:00
answer . Payload = disco
log . Debugf ( "%#v" , answer )
component , ok := s . ( * xmpp . Component )
if ! ok {
log . Error ( "Not a component" )
return
}
_ = gateway . ResumableSend ( component , answer )
}
2024-01-31 02:38:46 +00:00
func handleGetDiscoItems ( s xmpp . Sender , iq * stanza . IQ , di * stanza . DiscoItems ) {
2023-08-28 14:16:57 +00:00
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
}
2024-01-31 02:38:46 +00:00
log . Debugf ( "discoItems: %#v" , di )
_ , ok := toToID ( iq . To )
2024-02-10 18:46:02 +00:00
if di . Node == NSCommand {
answer . Payload = di
2024-02-15 09:40:57 +00:00
chatType , chatTypeErr := getTelegramChatType ( iq . From , iq . To )
2024-02-10 18:46:02 +00:00
var cmdType telegram . CommandType
if ok {
cmdType = telegram . CommandTypeChat
2024-01-31 02:38:46 +00:00
} else {
2024-02-10 18:46:02 +00:00
cmdType = telegram . CommandTypeTransport
}
commands := telegram . GetCommands ( cmdType )
2024-02-10 21:27:08 +00:00
for _ , name := range telegram . SortedCommandKeys ( commands ) {
command := commands [ name ]
2024-02-15 09:40:57 +00:00
if chatTypeErr == nil && ! telegram . IsCommandForChatType ( command , chatType ) {
continue
}
2024-02-10 18:46:02 +00:00
di . AddItem ( iq . To , name , telegram . CommandToHelpString ( name , command ) )
2024-01-31 02:38:46 +00:00
}
2024-02-10 18:46:02 +00:00
} else {
answer . Payload = answer . DiscoItems ( )
2024-01-31 02:38:46 +00:00
}
2023-08-28 14:16:57 +00:00
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 )
}
}
2024-01-31 02:38:46 +00:00
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 )
2024-02-03 09:24:22 +00:00
bare , resource , ok := gateway . SplitJID ( iq . From )
if ! ok {
return
}
2024-02-10 18:46:02 +00:00
toId , toOk := toToID ( iq . To )
2024-02-03 09:24:22 +00:00
var cmdString string
2024-02-10 18:46:02 +00:00
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
2024-01-31 02:38:46 +00:00
}
2024-02-10 18:46:02 +00:00
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 ] )
}
}
2024-02-03 15:33:37 +00:00
2024-02-10 18:46:02 +00:00
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 {
2024-02-10 20:22:24 +00:00
var required * string
if i < cmd . RequiredArgs {
dummyString := ""
required = & dummyString
}
2024-02-18 07:48:02 +00:00
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
2024-05-10 23:53:16 +00:00
switch command . Node {
// allow empty form
case "mute" , "unmute" :
options = append ( options , stanza . Option {
ValuesList : [ ] string { "" } ,
} )
}
2024-02-18 07:48:02 +00:00
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 {
2024-02-10 20:22:24 +00:00
Var : strconv . FormatInt ( int64 ( i ) , 10 ) ,
Label : arg ,
Required : required ,
2024-02-18 07:48:02 +00:00
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 ,
2024-02-03 09:24:22 +00:00
}
2024-02-03 15:38:00 +00:00
answer . Payload = & stanza . Command {
2024-02-10 18:46:02 +00:00
SessionId : command . Node ,
Node : command . Node ,
Status : stanza . CommandStatusExecuting ,
2024-02-18 07:48:02 +00:00
CommandElement : & form ,
2024-02-03 15:38:00 +00:00
}
2024-02-18 07:48:02 +00:00
log . Debugf ( "form: %#v" , form )
2024-02-10 18:46:02 +00:00
} else {
cmdString = "/" + command . Node
}
} else if command . Action == stanza . CommandActionCancel {
answer . Payload = & stanza . Command {
SessionId : command . Node ,
Node : command . Node ,
Status : stanza . CommandStatusCancelled ,
2024-01-31 02:38:46 +00:00
}
2024-02-03 09:24:22 +00:00
}
}
2024-01-31 02:38:46 +00:00
2024-02-03 09:24:22 +00:00
if cmdString != "" {
session , ok := sessions [ bare ]
if ! ok {
return
}
2024-01-31 02:38:46 +00:00
2024-02-10 18:46:02 +00:00
var response string
2024-02-18 09:36:23 +00:00
var success bool
2024-02-10 18:46:02 +00:00
if toOk {
2024-02-18 09:36:23 +00:00
response , _ , success = session . ProcessChatCommand ( toId , cmdString )
2024-02-10 18:46:02 +00:00
} else {
2024-02-18 09:36:23 +00:00
response , success = session . ProcessTransportCommand ( cmdString , resource )
}
var noteType string
if success {
noteType = stanza . CommandNoteTypeInfo
} else {
noteType = stanza . CommandNoteTypeErr
2024-02-10 18:46:02 +00:00
}
2024-02-03 09:24:22 +00:00
answer . Payload = & stanza . Command {
SessionId : command . Node ,
Node : command . Node ,
Status : stanza . CommandStatusCompleted ,
CommandElement : & stanza . Note {
Text : response ,
2024-02-18 09:36:23 +00:00
Type : noteType ,
2024-02-03 09:24:22 +00:00
} ,
2024-01-31 02:38:46 +00:00
}
2024-02-03 09:24:22 +00:00
2024-01-31 02:38:46 +00:00
}
2024-02-18 07:48:02 +00:00
log . Debugf ( "command response: %#v" , answer . Payload )
2024-01-31 02:38:46 +00:00
}
2023-08-28 14:16:57 +00:00
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 {
2023-08-28 14:20:50 +00:00
Code : code ,
Type : stanza . ErrorTypeModify ,
2023-08-28 14:16:57 +00:00
Reason : "bad-request" ,
}
case 405 :
answer . Error = & stanza . Err {
2023-08-28 14:20:50 +00:00
Code : code ,
Type : stanza . ErrorTypeCancel ,
2023-08-28 14:16:57 +00:00
Reason : "not-allowed" ,
2023-08-28 14:20:50 +00:00
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" ,
2023-08-28 14:16:57 +00:00
}
case 406 :
answer . Error = & stanza . Err {
2023-08-28 14:20:50 +00:00
Code : code ,
Type : stanza . ErrorTypeModify ,
2023-08-28 14:16:57 +00:00
Reason : "not-acceptable" ,
2023-08-28 14:20:50 +00:00
Text : "Phone number already provided, chat with the transport for further instruction" ,
2023-08-28 14:16:57 +00:00
}
case 500 :
answer . Error = & stanza . Err {
2023-08-28 14:20:50 +00:00
Code : code ,
Type : stanza . ErrorTypeWait ,
2023-08-28 14:16:57 +00:00
Reason : "internal-server-error" ,
}
default :
log . Error ( "Unknown error code, falling back with empty reason" )
answer . Error = & stanza . Err {
2023-08-28 14:20:50 +00:00
Code : code ,
Type : stanza . ErrorTypeCancel ,
2023-08-28 14:16:57 +00:00
Reason : "undefined-condition" ,
}
}
}
2022-02-05 10:21:56 +00:00
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
}
2022-06-28 23:34:14 +00:00
2023-06-01 20:37:38 +00:00
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 ( ) )
}
}
2022-06-28 23:34:14 +00:00
if typ == TypeVCardTemp {
vcard := & extensions . IqVcardTemp { }
2023-06-01 20:37:38 +00:00
vcard . Fn . Text = info . Fn
if base64Photo != "" {
2022-06-28 23:34:14 +00:00
vcard . Photo . Type . Text = "image/jpeg"
2023-06-01 20:37:38 +00:00
vcard . Photo . Binval . Text = base64Photo
2022-06-28 23:34:14 +00:00
}
2023-08-01 01:25:24 +00:00
vcard . Nickname . Text = strings . Join ( info . Nicknames , "," )
2023-06-01 20:37:38 +00:00
vcard . N . Given . Text = info . Given
vcard . N . Family . Text = info . Family
vcard . Tel . Number . Text = info . Tel
vcard . Desc . Text = info . Info
2022-06-28 23:34:14 +00:00
return vcard
} else if typ == TypeVCard4 {
nodes := [ ] stanza . Node { }
2023-06-01 20:37:38 +00:00
if info . Fn != "" {
2022-06-28 23:34:14 +00:00
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "fn" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "text" } ,
2023-06-01 20:37:38 +00:00
Content : info . Fn ,
2022-06-28 23:34:14 +00:00
} ,
} ,
} )
}
2023-06-01 20:37:38 +00:00
if base64Photo != "" {
2022-06-28 23:34:14 +00:00
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "photo" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "uri" } ,
2023-06-01 20:37:38 +00:00
Content : "data:image/jpeg;base64," + base64Photo ,
2022-06-28 23:34:14 +00:00
} ,
} ,
} )
}
2023-08-01 01:25:24 +00:00
for _ , nickname := range info . Nicknames {
2022-06-28 23:34:14 +00:00
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "nickname" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "text" } ,
2023-08-01 01:25:24 +00:00
Content : nickname ,
2022-06-28 23:34:14 +00:00
} ,
} ,
2022-06-30 00:29:41 +00:00
} , stanza . Node {
XMLName : xml . Name { Local : "impp" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "uri" } ,
2023-08-01 01:25:24 +00:00
Content : "https://t.me/" + nickname ,
2022-06-30 00:29:41 +00:00
} ,
} ,
2022-06-28 23:34:14 +00:00
} )
}
2023-06-01 20:37:38 +00:00
if info . Family != "" || info . Given != "" {
2022-06-28 23:34:14 +00:00
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "n" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "surname" } ,
2023-06-01 20:37:38 +00:00
Content : info . Family ,
2022-06-28 23:34:14 +00:00
} ,
stanza . Node {
XMLName : xml . Name { Local : "given" } ,
2023-06-01 20:37:38 +00:00
Content : info . Given ,
2022-06-28 23:34:14 +00:00
} ,
} ,
} )
}
2023-06-01 20:37:38 +00:00
if info . Tel != "" {
2022-06-28 23:34:14 +00:00
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "tel" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "uri" } ,
2023-06-01 20:37:38 +00:00
Content : "tel:" + info . Tel ,
2022-06-28 23:34:14 +00:00
} ,
} ,
} )
}
2023-06-01 20:37:38 +00:00
if info . Info != "" {
2022-06-29 13:56:27 +00:00
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "note" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "text" } ,
2023-06-01 20:37:38 +00:00
Content : info . Info ,
2022-06-29 13:56:27 +00:00
} ,
} ,
} )
}
2022-06-28 23:34:14 +00:00
pubsub := & stanza . PubSubGeneric {
Items : & stanza . Items {
Node : NodeVCard4 ,
List : [ ] stanza . Item {
stanza . Item {
2022-06-30 00:33:51 +00:00
Id : id ,
2022-06-28 23:34:14 +00:00
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
}