@ -4,19 +4,16 @@ import (
"bytes"
"encoding/base64"
"encoding/xml"
"fmt"
"github.com/pkg/errors"
"io"
"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"
)
@ -58,22 +55,6 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
go handleGetDiscoInfo ( s , iq )
return
}
_ , ok = iq . Payload . ( * stanza . DiscoItems )
if ok {
go handleGetDiscoItems ( s , iq )
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
}
}
}
@ -108,7 +89,8 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
session , ok := sessions [ bare ]
if ! ok {
if msg . To == gatewayJid {
gateway . SubscribeToTransport ( component , msg . From )
gateway . SendPresence ( component , msg . From , gateway . SPType ( "subscribe" ) )
gateway . SendPresence ( component , msg . From , gateway . SPType ( "subscribed" ) )
} else {
log . Error ( "Message from stranger" )
}
@ -119,35 +101,22 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
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!" ) )
}
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 {
@ -165,60 +134,11 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
"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
text = text [ : start ] + text [ end : ]
}
}
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 )
}
} * /
}
session . ProcessOutgoingMessage ( toID , text , msg . From , replyId )
return
} else {
toJid , err := stanza . NewJid ( msg . To )
@ -234,51 +154,16 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
}
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 privilege extensions . ComponentPrivilege
if ok := msg . Get ( & privilege ) ; ok {
log . Debugf ( "privilege: %#v" , privilege )
}
var privilege2 extensions . ComponentPrivilege2
if ok := msg . Get ( & privilege2 ) ; ok {
log . Debugf ( "privilege2: %#v" , privilege2 )
}
for _ , perm := range privilege2 . Perms {
for _ , perm := range privilege . Perms {
if perm . Access == "message" && perm . Type == "outgoing" {
gateway . MessageOutgoingPermission Version = 2
gateway . MessageOutgoingPermission = true
}
}
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" {
@ -288,7 +173,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
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 )
gateway . SendServiceMessage ( bare , "Your server \"" + msg . From + "\" does not allow to send carbons" , component )
}
}
}
@ -398,21 +283,13 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) {
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 ,
d escription,
s how ,
newArgs... ,
status . Description ,
status . XMPP ,
gateway . SPImmed ( false ) ,
)
}
session . UpdateChatNicknames ( )
}
} ( )
}
@ -442,12 +319,45 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
log . Error ( "Invalid IQ to" )
return
}
info, err := session . GetVcardInfo ( toID )
chat, user , err := session . GetContactByID ( toID , nil )
if err != nil {
log . Error ( err )
return
}
var fn , photo , nickname , given , family , tel , info string
if chat != nil {
fn = chat . Title
if chat . Photo != nil {
file , path , err := session . OpenPhotoFile ( chat . Photo . Small , 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 {
photo = 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 ( ) )
}
}
info = session . GetChatDescription ( chat )
}
if user != nil {
nickname = user . Username
given = user . FirstName
family = user . LastName
tel = user . PhoneNumber
}
answer := stanza . IQ {
Attrs : stanza . Attrs {
From : iq . To ,
@ -455,7 +365,7 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
Id : iq . Id ,
Type : "result" ,
} ,
Payload : makeVCardPayload ( typ , iq . To , info, session ) ,
Payload : makeVCardPayload ( typ , iq . To , fn, photo , nickname , given , family , tel , info ) ,
}
log . Debugf ( "%#v" , answer )
@ -469,6 +379,11 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
}
func handleGetDiscoInfo ( s xmpp . Sender , iq * stanza . IQ ) {
iqDisco , ok := iq . Payload . ( * stanza . DiscoInfo )
if ! ok {
log . Error ( "Not a disco info request" )
return
}
answer , err := stanza . NewIQ ( stanza . Attrs {
Type : stanza . IQTypeResult ,
From : iq . To ,
@ -481,16 +396,15 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
return
}
disco := answer . DiscoInfo ( )
_ , ok := toToID ( iq . To )
typ gateway . ContactType
if ok {
disco . AddIdentity ( "" , "account" , "registered" )
disco . AddFeatures ( stanza . NSMsgChatMarkers )
disco . AddFeatures ( stanza . NSMsgReceipts )
typ = gateway . ContactPM
} else {
disco . AddIdentity ( "Telegram Gateway" , "gateway" , "telegram" )
disco . AddFeatures ( "jabber:iq:register" )
typ = gateway . ContactTransport
}
disco := gateway . GetDiscoInfo ( typ , [ ] string { } )
disco . Node = iqDisco . Node
answer . Payload = disco
log . Debugf ( "%#v" , answer )
@ -504,189 +418,6 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
_ = gateway . ResumableSend ( component , answer )
}
func handleGetDiscoItems ( s xmpp . Sender , iq * stanza . IQ ) {
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
}
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 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 {
@ -702,69 +433,47 @@ func toToID(to string) (int64, bool) {
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 ( ) )
}
}
func makeVCardPayload ( typ byte , id , fn , photo , nickname , given , family , tel , info string ) stanza . IQPayload {
if typ == TypeVCardTemp {
vcard := & extensions . IqVcardTemp { }
vcard . Fn . Text = in fo. F n
if base64P hoto != "" {
vcard . Fn . Text = fn
if photo != "" {
vcard . Photo . Type . Text = "image/jpeg"
vcard . Photo . Binval . Text = base64P hoto
vcard . Photo . Binval . Text = photo
}
vcard . Nickname . Text = stri ngs. Join ( info . N icknames, "," )
vcard . N . Given . Text = info. G iven
vcard . N . Family . Text = in fo. F amily
vcard . Tel . Number . Text = info. T el
vcard . Desc . Text = info . Info
vcard . Nickname . Text = nickname
vcard . N . Given . Text = given
vcard . N . Family . Text = family
vcard . Tel . Number . Text = tel
vcard . Desc . Text = info
return vcard
} else if typ == TypeVCard4 {
nodes := [ ] stanza . Node { }
if in fo. F n != "" {
if fn != "" {
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "fn" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "text" } ,
Content : in fo. F n,
Content : fn,
} ,
} ,
} )
}
if base64P hoto != "" {
if p hoto != "" {
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," + base64P hoto,
Content : "data:image/jpeg;base64," + p hoto,
} ,
} ,
} )
}
for _ , nickname := range info . Nicknames {
if nickname != "" {
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "nickname" } ,
Nodes : [ ] stanza . Node {
@ -783,39 +492,39 @@ func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *tel
} ,
} )
}
if in fo. F amily != "" || info. G iven != "" {
if family != "" || g iven != "" {
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "n" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "surname" } ,
Content : in fo. F amily,
Content : family,
} ,
stanza . Node {
XMLName : xml . Name { Local : "given" } ,
Content : info. G iven,
Content : g iven,
} ,
} ,
} )
}
if info. T el != "" {
if t el != "" {
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "tel" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "uri" } ,
Content : "tel:" + info. T el,
Content : "tel:" + t el,
} ,
} ,
} )
}
if info . Info != "" {
if info != "" {
nodes = append ( nodes , stanza . Node {
XMLName : xml . Name { Local : "note" } ,
Nodes : [ ] stanza . Node {
stanza . Node {
XMLName : xml . Name { Local : "text" } ,
Content : info .Info ,
Content : info ,
} ,
} ,
} )