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"
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"
2022-07-08 11:54:30 +00:00
type discoType int
const (
discoTypeInfo discoType = iota
discoTypeItems
)
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
}
}
2022-02-08 17:09:14 +00:00
_ , ok = iq . Payload . ( * stanza . DiscoInfo )
if ok {
2022-07-08 11:54:30 +00:00
go handleGetDisco ( discoTypeInfo , s , iq )
2022-02-08 17:09:14 +00:00
return
2019-12-10 18:34:55 +00:00
}
2022-07-08 00:38:06 +00:00
_ , ok = iq . Payload . ( * stanza . DiscoItems )
if ok {
2022-07-08 11:54:30 +00:00
go handleGetDisco ( discoTypeItems , s , iq )
2022-07-08 00:38:06 +00:00
return
}
2023-08-28 14:16:57 +00:00
_ , 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
}
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-09-30 10:29:40 +00:00
toJid , err := stanza . NewJid ( msg . To )
if err != nil {
log . Error ( "Invalid to JID!" )
return
}
isGroupchat := msg . Type == "groupchat"
if session . Session . MUC && toJid . Resource != "" {
chat , _ , err := session . GetContactByID ( toID , nil )
if err == nil && session . IsGroup ( chat ) {
if isGroupchat {
gateway . SendErrorMessageWithBody ( msg . From , msg . To , msg . Body , "" , msg . Id , 400 , true , component )
} else {
gateway . SendErrorMessage ( msg . From , msg . To , "PMing room members is not supported, use the real JID" , 406 , true , component )
}
return
}
}
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
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-09-29 20:17:25 +00:00
tgMessage := session . ProcessOutgoingMessage ( toID , text , msg . From , replyId , replaceId , isGroupchat )
2023-09-29 12:24:15 +00:00
if tgMessage != nil {
2023-06-05 08:22:13 +00:00
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 )
} * /
2023-07-16 01:38:10 +00:00
session . AddToOutbox ( replace . Id , resource )
2023-06-05 08:22:13 +00:00
} else {
2023-09-29 12:24:15 +00:00
err = gateway . IdsDB . Set ( session . Session . Login , bare , toID , tgMessage . Id , msg . Id )
2023-06-05 08:22:13 +00:00
if err != nil {
2023-09-29 12:24:15 +00:00
log . Errorf ( "Failed to save ids %v/%v %v" , toID , tgMessage . Id , msg . Id )
}
}
// pong groupchat messages back
2023-09-30 10:29:40 +00:00
if isGroupchat && toJid . Resource == "" {
session . SendMessageToGateway (
toID ,
tgMessage ,
msg . Id ,
false ,
msg . To + "/" + session . GetMUCNickname ( session . GetSenderId ( tgMessage ) ) ,
[ ] string { msg . From } ,
)
2023-06-05 08:22:13 +00:00
}
} 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 , "!" ) ) {
2022-02-05 10:21:56 +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
}
}
}
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 )
2023-09-18 03:21:57 +00:00
return
}
var mucExt stanza . MucPresence
prs . Get ( & mucExt )
if mucExt . XMLName . Space != "" {
2023-09-19 11:57:52 +00:00
handleMUCPresence ( s , prs , mucExt )
2019-11-03 22:15:43 +00:00
}
}
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
2023-09-19 11:57:52 +00:00
func handleMUCPresence ( s xmpp . Sender , p stanza . Presence , mucExt stanza . MucPresence ) {
2023-09-18 03:21:57 +00:00
log . WithFields ( log . Fields {
"type" : p . Type ,
"from" : p . From ,
"to" : p . To ,
} ) . Warn ( "MUC presence" )
log . Debugf ( "%#v" , p )
if p . Type == "" {
toBare , nickname , ok := gateway . SplitJID ( p . To )
if ok {
component , ok := s . ( * xmpp . Component )
if ! ok {
log . Error ( "Not a component" )
return
}
2023-09-18 05:49:31 +00:00
// separate declaration is crucial for passing as pointer to defer
var reply * stanza . Presence
reply = & stanza . Presence { Attrs : stanza . Attrs {
2023-09-18 03:21:57 +00:00
From : toBare ,
To : p . From ,
Id : p . Id ,
} }
defer gateway . ResumableSend ( component , reply )
if nickname == "" {
2023-09-18 05:49:31 +00:00
presenceReplySetError ( reply , 400 )
2023-09-18 03:21:57 +00:00
return
}
chatId , ok := toToID ( toBare )
if ! ok {
2023-09-18 05:49:31 +00:00
presenceReplySetError ( reply , 404 )
2023-09-18 03:21:57 +00:00
return
}
2023-09-19 08:23:39 +00:00
fromBare , fromResource , ok := gateway . SplitJID ( p . From )
2023-09-18 03:21:57 +00:00
if ! ok {
2023-09-18 05:49:31 +00:00
presenceReplySetError ( reply , 400 )
2023-09-18 03:21:57 +00:00
return
}
session , ok := sessions [ fromBare ]
if ! ok || ! session . Session . MUC {
2023-09-28 20:30:28 +00:00
presenceReplySetError ( reply , 407 )
2023-09-18 03:21:57 +00:00
return
}
chat , _ , err := session . GetContactByID ( chatId , nil )
if err != nil || ! session . IsGroup ( chat ) {
2023-09-18 05:49:31 +00:00
presenceReplySetError ( reply , 404 )
2023-09-18 03:21:57 +00:00
return
}
2023-09-19 11:57:52 +00:00
limit , ok := mucExt . History . MaxStanzas . Get ( )
if ! ok {
limit = 20
}
session . JoinMUC ( chatId , fromResource , int32 ( limit ) )
2023-09-18 03:21:57 +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
2022-07-08 11:54:30 +00:00
func handleGetDisco ( dt discoType , s xmpp . Sender , iq * stanza . IQ ) {
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
}
2022-07-08 11:54:30 +00:00
if dt == discoTypeInfo {
disco := answer . DiscoInfo ( )
2022-07-08 12:43:44 +00:00
toID , toOk := toToID ( iq . To )
2023-09-17 04:54:23 +00:00
if ! toOk {
2022-07-08 21:59:51 +00:00
disco . AddIdentity ( "Telegram Gateway" , "gateway" , "telegram" )
2023-09-17 03:16:09 +00:00
disco . AddFeatures ( "jabber:iq:register" )
2022-07-08 21:59:51 +00:00
}
2022-07-08 21:43:56 +00:00
var isMuc bool
2023-09-17 03:16:09 +00:00
bare , _ , fromOk := gateway . SplitJID ( iq . From )
2022-07-08 12:43:44 +00:00
if fromOk {
session , sessionOk := sessions [ bare ]
if sessionOk && session . Session . MUC {
if toOk {
chat , _ , err := session . GetContactByID ( toID , nil )
if err == nil && session . IsGroup ( chat ) {
2022-07-08 21:43:56 +00:00
isMuc = true
2022-07-08 12:43:44 +00:00
disco . AddIdentity ( chat . Title , "conference" , "text" )
2022-07-08 21:43:56 +00:00
disco . AddFeatures (
"http://jabber.org/protocol/muc" ,
"muc_persistent" ,
"muc_hidden" ,
"muc_membersonly" ,
"muc_unmoderated" ,
"muc_nonanonymous" ,
"muc_unsecured" ,
2023-09-29 12:32:48 +00:00
"http://jabber.org/protocol/muc#stable_id" ,
2022-07-08 21:43:56 +00:00
)
fields := [ ] * stanza . Field {
& stanza . Field {
Var : "FORM_TYPE" ,
Type : "hidden" ,
ValuesList : [ ] string { "http://jabber.org/protocol/muc#roominfo" } ,
} ,
& stanza . Field {
Var : "muc#roominfo_description" ,
Label : "Description" ,
ValuesList : [ ] string { session . GetChatDescription ( chat ) } ,
} ,
& stanza . Field {
Var : "muc#roominfo_occupants" ,
Label : "Number of occupants" ,
ValuesList : [ ] string { strconv . FormatInt ( int64 ( session . GetChatMemberCount ( chat ) ) , 10 ) } ,
} ,
}
disco . Form = stanza . NewForm ( fields , "result" )
}
2022-07-08 12:43:44 +00:00
} else {
2023-09-29 12:32:48 +00:00
disco . AddFeatures (
stanza . NSDiscoItems ,
"http://jabber.org/protocol/muc#stable_id" ,
)
2022-07-08 12:43:44 +00:00
disco . AddIdentity ( "Telegram group chats" , "conference" , "text" )
}
2022-07-08 11:54:30 +00:00
}
}
2022-07-08 21:59:51 +00:00
if toOk && ! isMuc {
disco . AddIdentity ( "" , "account" , "registered" )
2022-07-08 21:43:56 +00:00
}
2022-07-08 11:54:30 +00:00
answer . Payload = disco
} else if dt == discoTypeItems {
disco := answer . DiscoItems ( )
_ , ok := toToID ( iq . To )
if ! ok {
2023-09-17 03:16:09 +00:00
bare , _ , ok := gateway . SplitJID ( iq . From )
2022-07-08 11:54:30 +00:00
if ok {
// raw access, no need to create a new instance if not connected
session , ok := sessions [ bare ]
if ok && session . Session . MUC {
bareJid := gateway . Jid . Bare ( )
disco . AddItem ( bareJid , "" , "Telegram group chats" )
for _ , chat := range session . GetGroupChats ( ) {
jid := strconv . FormatInt ( chat . Id , 10 ) + "@" + bareJid
disco . AddItem ( jid , "" , chat . Title )
}
2022-07-08 00:38:06 +00:00
}
}
}
2022-07-08 11:54:30 +00:00
answer . Payload = disco
}
2022-02-08 17:09:14 +00:00
log . Debugf ( "%#v" , answer )
component , ok := s . ( * xmpp . Component )
if ! ok {
log . Error ( "Not a component" )
return
}
_ = gateway . ResumableSend ( component , answer )
}
2023-08-28 14:16:57 +00:00
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" ,
} )
2022-02-05 10:21:56 +00:00
if err != nil {
2023-08-28 14:16:57 +00:00
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 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
}
}
2023-09-18 03:21:57 +00:00
func presenceReplySetError ( reply * stanza . Presence , code int ) {
reply . Type = stanza . PresenceTypeError
reply . Error = stanza . Err {
Code : code ,
}
switch code {
case 400 :
reply . Error . Type = stanza . ErrorTypeModify
reply . Error . Reason = "jid-malformed"
2023-09-28 20:30:28 +00:00
case 407 :
2023-09-18 03:21:57 +00:00
reply . Error . Type = stanza . ErrorTypeAuth
2023-09-28 20:30:28 +00:00
reply . Error . Reason = "registration-required"
2023-09-18 03:21:57 +00:00
case 404 :
reply . Error . Type = stanza . ErrorTypeCancel
reply . Error . Reason = "item-not-found"
default :
log . Error ( "Unknown error code, falling back with empty reason" )
reply . Error . Type = stanza . ErrorTypeCancel
reply . Error . 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" } ,
Content : nickname ,
} ,
} ,
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" } ,
Content : "https://t.me/" + nickname ,
} ,
} ,
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
}