telegabber/xmpp/handlers.go

1094 lines
26 KiB
Go
Raw Normal View History

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"
"github.com/pkg/errors"
2019-12-10 18:34:55 +00:00
"io"
"sort"
2019-11-24 17:10:29 +00:00
"strconv"
"strings"
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
log "github.com/sirupsen/logrus"
"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"
const NSCommand string = "http://jabber.org/protocol/commands"
2022-06-28 23:34:14 +00:00
func logPacketType(p stanza.Packet) {
2019-11-14 20:11:38 +00:00
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
}
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
}
}
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
2022-02-08 17:09:14 +00:00
if ok {
go handleGetDiscoInfo(s, iq, discoInfo)
2022-02-08 17:09:14 +00:00
return
2019-12-10 18:34:55 +00:00
}
discoItems, ok := iq.Payload.(*stanza.DiscoItems)
2023-08-28 14:16:57 +00:00
if ok {
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
}
command, ok := iq.Payload.(*stanza.Command)
if ok {
go handleSetQueryCommand(s, iq, command)
return
}
2019-12-10 18:34:55 +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 {
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)
if !ok {
2019-11-24 17:10:29 +00:00
return
}
gatewayJid := gateway.Jid.Bare()
session, ok := sessions[bare]
2019-11-24 17:10:29 +00:00
if !ok {
if msg.To == gatewayJid {
2023-08-28 14:16:57 +00:00
gateway.SubscribeToTransport(component, msg.From)
} else {
log.Error("Message from stranger")
}
return
2019-11-24 17:10:29 +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!"))
}
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
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)
} */
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)
if err == nil {
session.AddToOutbox(msg.Id, resource)
session.UpdateLastChatMessageId(toID, msg.Id)
} 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
}
return
} 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
}
}
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)
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-18 21:43:11 +00:00
}
2019-10-22 16:36:54 +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-24 17:10:29 +00:00
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
}
2023-03-18 21:43:11 +00:00
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"
}
2019-11-24 17:10:29 +00:00
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
2023-03-18 21:43:11 +00:00
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) {
2022-01-05 21:04:22 +00:00
sessionLock.Lock()
delete(sessions, bare)
2022-01-05 21:04:22 +00:00
sessionLock.Unlock()
2022-01-03 03:54:13 +00:00
}
// 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"))
2020-01-05 13:03:10 +00:00
} 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))
}
2020-01-05 13:03:10 +00:00
go session.ProcessStatusUpdate(
status.ID,
description,
show,
newArgs...,
2020-01-05 13:03:10 +00:00
)
}
session.UpdateChatNicknames()
}
}()
}
}
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")
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)
component, ok := s.(*xmpp.Component)
if !ok {
log.Error("Not a component")
return
}
_ = gateway.ResumableSend(component, &answer)
2019-12-19 20:58:20 +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")
}
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)
return
2022-02-08 17:09:14 +00:00
}
disco := answer.DiscoInfo()
_, 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 {
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
} 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
}
}
}
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)
}
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
}
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
} else {
2024-02-10 18:46:02 +00:00
cmdType = telegram.CommandTypeTransport
}
commands := telegram.GetCommands(cmdType)
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-02-10 18:46:02 +00:00
} else {
answer.Payload = answer.DiscoItems()
}
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)
}
}
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
}
2024-02-10 18:46:02 +00:00
toId, toOk := toToID(iq.To)
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-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
}
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{
2024-02-10 20:22:24 +00:00
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,
}
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,
CommandElement: &form,
2024-02-03 15:38:00 +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,
}
}
}
if cmdString != "" {
session, ok := sessions[bare]
if !ok {
return
}
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
}
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,
},
}
}
log.Debugf("command response: %#v", answer.Payload)
}
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",
}
}
}
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
}
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
},
},
})
}
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-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"},
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 != "" {
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-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
}