diff options
Diffstat (limited to 'xmpp')
-rw-r--r-- | xmpp/handlers.go | 272 |
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 |