aboutsummaryrefslogtreecommitdiff
path: root/xmpp
diff options
context:
space:
mode:
Diffstat (limited to 'xmpp')
-rw-r--r--xmpp/handlers.go272
1 files changed, 258 insertions, 14 deletions
diff --git a/xmpp/handlers.go b/xmpp/handlers.go
index 811cef6..1d77bc4 100644
--- a/xmpp/handlers.go
+++ b/xmpp/handlers.go
@@ -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