|
|
|
@ -7,6 +7,7 @@ import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"io"
|
|
|
|
|
"sort"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
@ -26,6 +27,7 @@ const (
|
|
|
|
|
TypeVCard4
|
|
|
|
|
)
|
|
|
|
|
const NodeVCard4 string = "urn:xmpp:vcard4"
|
|
|
|
|
const NSCommand string = "http://jabber.org/protocol/commands"
|
|
|
|
|
|
|
|
|
|
func logPacketType(p stanza.Packet) {
|
|
|
|
|
log.Warnf("Ignoring packet: %T\n", p)
|
|
|
|
@ -53,14 +55,14 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_, ok = iq.Payload.(*stanza.DiscoInfo)
|
|
|
|
|
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
|
|
|
|
if ok {
|
|
|
|
|
go handleGetDiscoInfo(s, iq)
|
|
|
|
|
go handleGetDiscoInfo(s, iq, discoInfo)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, ok = iq.Payload.(*stanza.DiscoItems)
|
|
|
|
|
discoItems, ok := iq.Payload.(*stanza.DiscoItems)
|
|
|
|
|
if ok {
|
|
|
|
|
go handleGetDiscoItems(s, iq)
|
|
|
|
|
go handleGetDiscoItems(s, iq, discoItems)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, ok = iq.Payload.(*extensions.QueryRegister)
|
|
|
|
@ -74,6 +76,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|
|
|
|
go handleSetQueryRegister(s, iq, query)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
command, ok := iq.Payload.(*stanza.Command)
|
|
|
|
|
if ok {
|
|
|
|
|
go handleSetQueryCommand(s, iq, command)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -223,7 +230,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|
|
|
|
} else {
|
|
|
|
|
toJid, err := stanza.NewJid(msg.To)
|
|
|
|
|
if err == nil && toJid.Bare() == gatewayJid && (strings.HasPrefix(msg.Body, "/") || strings.HasPrefix(msg.Body, "!")) {
|
|
|
|
|
response := session.ProcessTransportCommand(msg.Body, resource)
|
|
|
|
|
response, _ := session.ProcessTransportCommand(msg.Body, resource)
|
|
|
|
|
if response != "" {
|
|
|
|
|
gateway.SendServiceMessage(msg.From, response, component)
|
|
|
|
|
}
|
|
|
|
@ -468,7 +475,22 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
|
|
|
|
_ = gateway.ResumableSend(component, &answer)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|
|
|
|
func getTelegramChatType(from string, to string) (telegram.ChatType, error) {
|
|
|
|
|
toId, ok := toToID(to)
|
|
|
|
|
if ok {
|
|
|
|
|
bare, _, ok := gateway.SplitJID(from)
|
|
|
|
|
if ok {
|
|
|
|
|
session, ok := sessions[bare]
|
|
|
|
|
if ok {
|
|
|
|
|
return session.GetChatType(toId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return telegram.ChatTypeUnknown, errors.New("Unknown chat type")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
|
|
|
|
|
answer, err := stanza.NewIQ(stanza.Attrs{
|
|
|
|
|
Type: stanza.IQTypeResult,
|
|
|
|
|
From: iq.To,
|
|
|
|
@ -483,13 +505,37 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|
|
|
|
|
|
|
|
|
disco := answer.DiscoInfo()
|
|
|
|
|
_, ok := toToID(iq.To)
|
|
|
|
|
if ok {
|
|
|
|
|
disco.AddIdentity("", "account", "registered")
|
|
|
|
|
disco.AddFeatures(stanza.NSMsgChatMarkers)
|
|
|
|
|
disco.AddFeatures(stanza.NSMsgReceipts)
|
|
|
|
|
if di.Node == "" {
|
|
|
|
|
if ok {
|
|
|
|
|
disco.AddIdentity("", "account", "registered")
|
|
|
|
|
disco.AddFeatures(stanza.NSMsgChatMarkers)
|
|
|
|
|
disco.AddFeatures(stanza.NSMsgReceipts)
|
|
|
|
|
} else {
|
|
|
|
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
|
|
|
|
disco.AddFeatures("jabber:iq:register")
|
|
|
|
|
}
|
|
|
|
|
disco.AddFeatures(NSCommand)
|
|
|
|
|
} else {
|
|
|
|
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
|
|
|
|
disco.AddFeatures("jabber:iq:register")
|
|
|
|
|
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
|
|
|
|
|
|
|
|
|
var cmdType telegram.CommandType
|
|
|
|
|
if ok {
|
|
|
|
|
cmdType = telegram.CommandTypeChat
|
|
|
|
|
} else {
|
|
|
|
|
cmdType = telegram.CommandTypeTransport
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, command := range telegram.GetCommands(cmdType) {
|
|
|
|
|
if di.Node == name {
|
|
|
|
|
if chatTypeErr == nil && !telegram.IsCommandForChatType(command, chatType) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
answer.Payload = di
|
|
|
|
|
di.AddIdentity(telegram.CommandToHelpString(name, command), "automation", "command-node")
|
|
|
|
|
di.AddFeatures(NSCommand, "jabber:x:data")
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
answer.Payload = disco
|
|
|
|
|
|
|
|
|
@ -504,7 +550,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|
|
|
|
_ = gateway.ResumableSend(component, answer)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
|
|
|
|
func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoItems) {
|
|
|
|
|
answer, err := stanza.NewIQ(stanza.Attrs{
|
|
|
|
|
Type: stanza.IQTypeResult,
|
|
|
|
|
From: iq.To,
|
|
|
|
@ -517,7 +563,32 @@ func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
answer.Payload = answer.DiscoItems()
|
|
|
|
|
log.Debugf("discoItems: %#v", di)
|
|
|
|
|
|
|
|
|
|
_, ok := toToID(iq.To)
|
|
|
|
|
if di.Node == NSCommand {
|
|
|
|
|
answer.Payload = di
|
|
|
|
|
|
|
|
|
|
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
|
|
|
|
|
|
|
|
|
var cmdType telegram.CommandType
|
|
|
|
|
if ok {
|
|
|
|
|
cmdType = telegram.CommandTypeChat
|
|
|
|
|
} else {
|
|
|
|
|
cmdType = telegram.CommandTypeTransport
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commands := telegram.GetCommands(cmdType)
|
|
|
|
|
for _, name := range telegram.SortedCommandKeys(commands) {
|
|
|
|
|
command := commands[name]
|
|
|
|
|
if chatTypeErr == nil && !telegram.IsCommandForChatType(command, chatType) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
di.AddItem(iq.To, name, telegram.CommandToHelpString(name, command))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
answer.Payload = answer.DiscoItems()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
component, ok := s.(*xmpp.Component)
|
|
|
|
|
if !ok {
|
|
|
|
@ -647,6 +718,179 @@ func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handleSetQueryCommand(s xmpp.Sender, iq *stanza.IQ, command *stanza.Command) {
|
|
|
|
|
component, ok := s.(*xmpp.Component)
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Error("Not a component")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
answer, err := stanza.NewIQ(stanza.Attrs{
|
|
|
|
|
Type: stanza.IQTypeResult,
|
|
|
|
|
From: iq.To,
|
|
|
|
|
To: iq.From,
|
|
|
|
|
Id: iq.Id,
|
|
|
|
|
Lang: "en",
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Failed to create answer IQ: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer gateway.ResumableSend(component, answer)
|
|
|
|
|
|
|
|
|
|
log.Debugf("command: %#v", command)
|
|
|
|
|
|
|
|
|
|
bare, resource, ok := gateway.SplitJID(iq.From)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
toId, toOk := toToID(iq.To)
|
|
|
|
|
|
|
|
|
|
var cmdString string
|
|
|
|
|
var cmdType telegram.CommandType
|
|
|
|
|
form, formOk := command.CommandElement.(*stanza.Form)
|
|
|
|
|
if toOk {
|
|
|
|
|
cmdType = telegram.CommandTypeChat
|
|
|
|
|
} else {
|
|
|
|
|
cmdType = telegram.CommandTypeTransport
|
|
|
|
|
}
|
|
|
|
|
if formOk {
|
|
|
|
|
// just for the case the client messed the order somehow
|
|
|
|
|
sort.Slice(form.Fields, func(i int, j int) bool {
|
|
|
|
|
iField := form.Fields[i]
|
|
|
|
|
jField := form.Fields[j]
|
|
|
|
|
if iField != nil && jField != nil {
|
|
|
|
|
ii, iErr := strconv.ParseInt(iField.Var, 10, 64)
|
|
|
|
|
ji, jErr := strconv.ParseInt(jField.Var, 10, 64)
|
|
|
|
|
return iErr == nil && jErr == nil && ii < ji
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var cmd strings.Builder
|
|
|
|
|
cmd.WriteString("/")
|
|
|
|
|
cmd.WriteString(command.Node)
|
|
|
|
|
for _, field := range form.Fields {
|
|
|
|
|
cmd.WriteString(" ")
|
|
|
|
|
if len(field.ValuesList) > 0 {
|
|
|
|
|
cmd.WriteString(field.ValuesList[0])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmdString = cmd.String()
|
|
|
|
|
} else {
|
|
|
|
|
if command.Action == "" || command.Action == stanza.CommandActionExecute {
|
|
|
|
|
cmd, ok := telegram.GetCommand(cmdType, command.Node)
|
|
|
|
|
if ok && len(cmd.Arguments) > 0 {
|
|
|
|
|
var fields []*stanza.Field
|
|
|
|
|
for i, arg := range cmd.Arguments {
|
|
|
|
|
var required *string
|
|
|
|
|
if i < cmd.RequiredArgs {
|
|
|
|
|
dummyString := ""
|
|
|
|
|
required = &dummyString
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var fieldType string
|
|
|
|
|
var options []stanza.Option
|
|
|
|
|
if toOk && i == 0 {
|
|
|
|
|
switch command.Node {
|
|
|
|
|
case "mute", "kick", "ban", "promote", "unmute", "unban":
|
|
|
|
|
session, ok := sessions[bare]
|
|
|
|
|
if ok {
|
|
|
|
|
var membersList telegram.MembersList
|
|
|
|
|
switch command.Node {
|
|
|
|
|
case "unmute":
|
|
|
|
|
membersList = telegram.MembersListRestricted
|
|
|
|
|
case "unban":
|
|
|
|
|
membersList = telegram.MembersListBannedAndAdministrators
|
|
|
|
|
}
|
|
|
|
|
members, err := session.GetChatMembers(toId, true, "", membersList)
|
|
|
|
|
if err == nil {
|
|
|
|
|
fieldType = stanza.FieldTypeListSingle
|
|
|
|
|
for _, member := range members {
|
|
|
|
|
senderId := session.GetSenderId(member.MemberId)
|
|
|
|
|
options = append(options, stanza.Option{
|
|
|
|
|
Label: session.FormatContact(senderId),
|
|
|
|
|
ValuesList: []string{strconv.FormatInt(senderId, 10)},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
field := stanza.Field{
|
|
|
|
|
Var: strconv.FormatInt(int64(i), 10),
|
|
|
|
|
Label: arg,
|
|
|
|
|
Required: required,
|
|
|
|
|
Type: fieldType,
|
|
|
|
|
Options: options,
|
|
|
|
|
}
|
|
|
|
|
fields = append(fields, &field)
|
|
|
|
|
log.Debugf("field: %#v", field)
|
|
|
|
|
}
|
|
|
|
|
form := stanza.Form{
|
|
|
|
|
Type: stanza.FormTypeForm,
|
|
|
|
|
Title: command.Node,
|
|
|
|
|
Instructions: []string{cmd.Description},
|
|
|
|
|
Fields: fields,
|
|
|
|
|
}
|
|
|
|
|
answer.Payload = &stanza.Command{
|
|
|
|
|
SessionId: command.Node,
|
|
|
|
|
Node: command.Node,
|
|
|
|
|
Status: stanza.CommandStatusExecuting,
|
|
|
|
|
CommandElement: &form,
|
|
|
|
|
}
|
|
|
|
|
log.Debugf("form: %#v", form)
|
|
|
|
|
} else {
|
|
|
|
|
cmdString = "/" + command.Node
|
|
|
|
|
}
|
|
|
|
|
} else if command.Action == stanza.CommandActionCancel {
|
|
|
|
|
answer.Payload = &stanza.Command{
|
|
|
|
|
SessionId: command.Node,
|
|
|
|
|
Node: command.Node,
|
|
|
|
|
Status: stanza.CommandStatusCancelled,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if cmdString != "" {
|
|
|
|
|
session, ok := sessions[bare]
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var response string
|
|
|
|
|
var success bool
|
|
|
|
|
if toOk {
|
|
|
|
|
response, _, success = session.ProcessChatCommand(toId, cmdString)
|
|
|
|
|
} else {
|
|
|
|
|
response, success = session.ProcessTransportCommand(cmdString, resource)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var noteType string
|
|
|
|
|
if success {
|
|
|
|
|
noteType = stanza.CommandNoteTypeInfo
|
|
|
|
|
} else {
|
|
|
|
|
noteType = stanza.CommandNoteTypeErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
answer.Payload = &stanza.Command{
|
|
|
|
|
SessionId: command.Node,
|
|
|
|
|
Node: command.Node,
|
|
|
|
|
Status: stanza.CommandStatusCompleted,
|
|
|
|
|
CommandElement: &stanza.Note{
|
|
|
|
|
Text: response,
|
|
|
|
|
Type: noteType,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Debugf("command response: %#v", answer.Payload)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {
|
|
|
|
|
answer.Type = stanza.IQTypeError
|
|
|
|
|
answer.Payload = *payload
|
|
|
|
|