Basic Ad-Hoc support for transport commands

This commit is contained in:
Bohdan Horbeshko 2024-01-30 21:38:46 -05:00
parent 20e6d2558e
commit fd0d7411c2
4 changed files with 133 additions and 29 deletions

2
go.mod
View file

@ -33,5 +33,5 @@ require (
nhooyr.io/websocket v1.6.5 // indirect 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 replace github.com/zelenin/go-tdlib => dev.narayana.im/narayana/go-tdlib v0.0.0-20240124222245-b4c12addb061

2
go.sum
View file

@ -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-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 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-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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= 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= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=

View file

@ -48,6 +48,7 @@ var permissionsMember = client.ChatPermissions{
var permissionsReadonly = client.ChatPermissions{} var permissionsReadonly = client.ChatPermissions{}
var transportCommands = map[string]command{ var transportCommands = map[string]command{
"help": command{"", "help"},
"login": command{"phone", "sign in"}, "login": command{"phone", "sign in"},
"logout": command{"", "sign out"}, "logout": command{"", "sign out"},
"cancelauth": command{"", "quit the signin wizard"}, "cancelauth": command{"", "quit the signin wizard"},
@ -66,6 +67,7 @@ var transportCommands = map[string]command{
} }
var chatCommands = map[string]command{ var chatCommands = map[string]command{
"help": command{"", "help"},
"d": command{"[n]", "delete your last message(s)"}, "d": command{"[n]", "delete your last message(s)"},
"s": command{"edited message", "edit your last message"}, "s": command{"edited message", "edit your last message"},
"silent": command{"message", "send a message without sound"}, "silent": command{"message", "send a message without sound"},
@ -110,38 +112,56 @@ type command struct {
} }
type configurationOption command type configurationOption command
type helpType int // CommandType disinguishes command sets by chat
type CommandType int
const ( const (
helpTypeTransport helpType = iota CommandTypeTransport CommandType = iota
helpTypeChat CommandTypeChat
) )
func helpString(ht helpType) string { // GetCommands exposes the set of commands
var str strings.Builder func GetCommands(typ CommandType) map[string]command {
var commandMap map[string]command var commandMap map[string]command
switch ht { switch typ {
case helpTypeTransport: case CommandTypeTransport:
commandMap = transportCommands commandMap = transportCommands
case helpTypeChat: case CommandTypeChat:
commandMap = chatCommands 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") str.WriteString("Available commands:\n")
for name, command := range commandMap { for name, command := range commandMap {
str.WriteString("/") str.WriteString(CommandToHelpString(name, command))
str.WriteString(name)
if command.arguments != "" {
str.WriteString(" ")
str.WriteString(command.arguments)
}
str.WriteString(" — ")
str.WriteString(command.description)
str.WriteString("\n") str.WriteString("\n")
} }
if ht == helpTypeTransport { if typ == CommandTypeTransport {
str.WriteString("Configuration options\n") str.WriteString("Configuration options\n")
for name, option := range transportConfigurationOptions { for name, option := range transportConfigurationOptions {
str.WriteString(name) str.WriteString(name)
@ -448,7 +468,7 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string
case "channel": case "channel":
return c.cmdChannel(args, cmdline) return c.cmdChannel(args, cmdline)
case "help": case "help":
return helpString(helpTypeTransport) return helpString(CommandTypeTransport)
} }
return "" return ""
@ -1088,7 +1108,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool)
return strings.Join(entries, "\n"), true return strings.Join(entries, "\n"), true
case "help": case "help":
return helpString(helpTypeChat), true return helpString(CommandTypeChat), true
default: default:
return "", false return "", false
} }

View file

@ -26,6 +26,7 @@ const (
TypeVCard4 TypeVCard4
) )
const NodeVCard4 string = "urn:xmpp:vcard4" const NodeVCard4 string = "urn:xmpp:vcard4"
const NSCommand string = "http://jabber.org/protocol/commands"
func logPacketType(p stanza.Packet) { func logPacketType(p stanza.Packet) {
log.Warnf("Ignoring packet: %T\n", p) log.Warnf("Ignoring packet: %T\n", p)
@ -53,14 +54,14 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
return return
} }
} }
_, ok = iq.Payload.(*stanza.DiscoInfo) discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
if ok { if ok {
go handleGetDiscoInfo(s, iq) go handleGetDiscoInfo(s, iq, discoInfo)
return return
} }
_, ok = iq.Payload.(*stanza.DiscoItems) discoItems, ok := iq.Payload.(*stanza.DiscoItems)
if ok { if ok {
go handleGetDiscoItems(s, iq) go handleGetDiscoItems(s, iq, discoItems)
return return
} }
_, ok = iq.Payload.(*extensions.QueryRegister) _, ok = iq.Payload.(*extensions.QueryRegister)
@ -74,6 +75,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
go handleSetQueryRegister(s, iq, query) go handleSetQueryRegister(s, iq, query)
return 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) _ = 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{ answer, err := stanza.NewIQ(stanza.Attrs{
Type: stanza.IQTypeResult, Type: stanza.IQTypeResult,
From: iq.To, From: iq.To,
@ -488,8 +494,20 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
disco.AddFeatures(stanza.NSMsgChatMarkers) disco.AddFeatures(stanza.NSMsgChatMarkers)
disco.AddFeatures(stanza.NSMsgReceipts) disco.AddFeatures(stanza.NSMsgReceipts)
} else { } else {
disco.AddIdentity("Telegram Gateway", "gateway", "telegram") if di.Node == "" {
disco.AddFeatures("jabber:iq:register") 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 answer.Payload = disco
@ -504,7 +522,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
_ = gateway.ResumableSend(component, answer) _ = 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{ answer, err := stanza.NewIQ(stanza.Attrs{
Type: stanza.IQTypeResult, Type: stanza.IQTypeResult,
From: iq.To, From: iq.To,
@ -517,7 +535,20 @@ func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
return 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) component, ok := s.(*xmpp.Component)
if !ok { 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) { func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {
answer.Type = stanza.IQTypeError answer.Type = stanza.IQTypeError
answer.Payload = *payload answer.Payload = *payload