aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBohdan Horbeshko <bodqhrohro@gmail.com>2024-01-31 05:38:46 +0300
committerBohdan Horbeshko <bodqhrohro@gmail.com>2024-01-31 05:38:46 +0300
commitfd0d7411c2f5e1e0368d3494318ce05195d0a56d (patch)
treef2802177abea8a9a2a5a1062f66bcb1f2359715a
parent20e6d2558e868d61d7168ffe5ca4f3bff0a240c7 (diff)
Basic Ad-Hoc support for transport commands
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--telegram/commands.go58
-rw-r--r--xmpp/handlers.go100
4 files changed, 133 insertions, 29 deletions
diff --git a/go.mod b/go.mod
index 949667e..fe7aeb4 100644
--- a/go.mod
+++ b/go.mod
@@ -33,5 +33,5 @@ require (
nhooyr.io/websocket v1.6.5 // indirect
)
-replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f
+replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20240131013505-18c46e6c59fd
replace github.com/zelenin/go-tdlib => dev.narayana.im/narayana/go-tdlib v0.0.0-20240124222245-b4c12addb061
diff --git a/go.sum b/go.sum
index d44752b..f5e218f 100644
--- a/go.sum
+++ b/go.sum
@@ -7,6 +7,8 @@ dev.narayana.im/narayana/go-tdlib v0.0.0-20240124222245-b4c12addb061 h1:CWAQT74L
dev.narayana.im/narayana/go-tdlib v0.0.0-20240124222245-b4c12addb061/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU=
dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f h1:6249ajbMjgYz53Oq0IjTvjHXbxTfu29Mj1J/6swRHs4=
dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
+dev.narayana.im/narayana/go-xmpp v0.0.0-20240131013505-18c46e6c59fd h1:+UW+E7JjI88aH4beDn1cw6D8rs1I061hN91HU4Y4pT8=
+dev.narayana.im/narayana/go-xmpp v0.0.0-20240131013505-18c46e6c59fd/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
diff --git a/telegram/commands.go b/telegram/commands.go
index 9251ebb..1c10d12 100644
--- a/telegram/commands.go
+++ b/telegram/commands.go
@@ -48,6 +48,7 @@ var permissionsMember = client.ChatPermissions{
var permissionsReadonly = client.ChatPermissions{}
var transportCommands = map[string]command{
+ "help": command{"", "help"},
"login": command{"phone", "sign in"},
"logout": command{"", "sign out"},
"cancelauth": command{"", "quit the signin wizard"},
@@ -66,6 +67,7 @@ var transportCommands = map[string]command{
}
var chatCommands = map[string]command{
+ "help": command{"", "help"},
"d": command{"[n]", "delete your last message(s)"},
"s": command{"edited message", "edit your last message"},
"silent": command{"message", "send a message without sound"},
@@ -110,38 +112,56 @@ type command struct {
}
type configurationOption command
-type helpType int
+// CommandType disinguishes command sets by chat
+type CommandType int
const (
- helpTypeTransport helpType = iota
- helpTypeChat
+ CommandTypeTransport CommandType = iota
+ CommandTypeChat
)
-func helpString(ht helpType) string {
- var str strings.Builder
+// GetCommands exposes the set of commands
+func GetCommands(typ CommandType) map[string]command {
var commandMap map[string]command
- switch ht {
- case helpTypeTransport:
+ switch typ {
+ case CommandTypeTransport:
commandMap = transportCommands
- case helpTypeChat:
+ case CommandTypeChat:
commandMap = chatCommands
}
+ return commandMap
+}
+
+// CommandToHelpString builds a text description of a command
+func CommandToHelpString(name string, cmd command) string {
+ var str strings.Builder
+
+ str.WriteString("/")
+ str.WriteString(name)
+ if cmd.arguments != "" {
+ str.WriteString(" ")
+ str.WriteString(cmd.arguments)
+ }
+ str.WriteString(" — ")
+ str.WriteString(cmd.description)
+
+ return str.String()
+}
+
+func helpString(typ CommandType) string {
+ var str strings.Builder
+
+ commandMap := GetCommands(typ)
+
str.WriteString("Available commands:\n")
for name, command := range commandMap {
- str.WriteString("/")
- str.WriteString(name)
- if command.arguments != "" {
- str.WriteString(" ")
- str.WriteString(command.arguments)
- }
- str.WriteString(" — ")
- str.WriteString(command.description)
+ str.WriteString(CommandToHelpString(name, command))
str.WriteString("\n")
}
- if ht == helpTypeTransport {
+ if typ == CommandTypeTransport {
str.WriteString("Configuration options\n")
for name, option := range transportConfigurationOptions {
str.WriteString(name)
@@ -448,7 +468,7 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string
case "channel":
return c.cmdChannel(args, cmdline)
case "help":
- return helpString(helpTypeTransport)
+ return helpString(CommandTypeTransport)
}
return ""
@@ -1088,7 +1108,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool)
return strings.Join(entries, "\n"), true
case "help":
- return helpString(helpTypeChat), true
+ return helpString(CommandTypeChat), true
default:
return "", false
}
diff --git a/xmpp/handlers.go b/xmpp/handlers.go
index 8c6ba37..a062f0c 100644
--- a/xmpp/handlers.go
+++ b/xmpp/handlers.go
@@ -26,6 +26,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 +54,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 +75,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
+ }
}
}
@@ -468,7 +474,7 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
_ = gateway.ResumableSend(component, &answer)
}
-func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
+func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
answer, err := stanza.NewIQ(stanza.Attrs{
Type: stanza.IQTypeResult,
From: iq.To,
@@ -488,8 +494,20 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
disco.AddFeatures(stanza.NSMsgChatMarkers)
disco.AddFeatures(stanza.NSMsgReceipts)
} else {
- disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
- disco.AddFeatures("jabber:iq:register")
+ if di.Node == "" {
+ disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
+ disco.AddFeatures("jabber:iq:register")
+ disco.AddFeatures(NSCommand)
+ } else {
+ for name, command := range telegram.GetCommands(telegram.CommandTypeTransport) {
+ if di.Node == name {
+ answer.Payload = di
+ di.AddIdentity(telegram.CommandToHelpString(name, command), "automation", "command-node")
+ di.AddFeatures(NSCommand, "jabber:x:data")
+ break
+ }
+ }
+ }
}
answer.Payload = disco
@@ -504,7 +522,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 +535,20 @@ func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
return
}
- answer.Payload = answer.DiscoItems()
+ log.Debugf("discoItems: %#v", di)
+
+ _, ok := toToID(iq.To)
+ if !ok {
+ commands := telegram.GetCommands(telegram.CommandTypeTransport)
+ if di.Node == NSCommand {
+ answer.Payload = di
+ for name, command := range commands {
+ di.AddItem(iq.To, name, telegram.CommandToHelpString(name, command))
+ }
+ } else {
+ answer.Payload = answer.DiscoItems()
+ }
+ }
component, ok := s.(*xmpp.Component)
if !ok {
@@ -647,6 +678,57 @@ 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)
+
+ if command.Action == "" || command.Action == stanza.CommandActionExecute {
+ _, ok := toToID(iq.To)
+ if !ok {
+ bare, resource, ok := gateway.SplitJID(iq.From)
+ if !ok {
+ return
+ }
+
+ session, ok := sessions[bare]
+ if !ok {
+ return
+ }
+
+ response := session.ProcessTransportCommand("/" + command.Node, resource)
+
+ answer.Payload = &stanza.Command{
+ Node: command.Node,
+ Status: stanza.CommandStatusCompleted,
+ CommandElement: &stanza.Note{
+ Text: response,
+ Type: stanza.CommandNoteTypeInfo,
+ },
+ }
+ log.Debugf("command response: %#v", answer.Payload)
+ }
+ }
+}
+
func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {
answer.Type = stanza.IQTypeError
answer.Payload = *payload