| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2025-11-19 14:13:00 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2025-11-19 14:13:00 UTC |
| parent | 440bc7d664f11e606163d8c08bf5ebf1919f4dd5 |
| borogove/Chat.hx | +36 | -6 |
| borogove/Client.hx | +8 | -1 |
| borogove/Command.hx | +23 | -0 |
| npm/index.ts | +1 | -0 |
diff --git a/borogove/Chat.hx b/borogove/Chat.hx index c6d7feb..58f823f 100644 --- a/borogove/Chat.hx +++ b/borogove/Chat.hx @@ -18,6 +18,7 @@ import borogove.calls.PeerConnection; import borogove.calls.Session; #end import borogove.queries.DiscoInfoGet; +import borogove.queries.DiscoItemsGet; import borogove.queries.MAMQuery; using Lambda; using StringTools; @@ -693,6 +694,40 @@ abstract class Chat { } } + /** + Does this chat provide a menu of commands? + **/ + public function hasCommands() { + return commandJids().length > 0; + } + + public function commands(): Promise<Array<Command>> { + return thenshim.PromiseTools.all(commandJids().map(jid -> new Promise((resolve, reject) -> { + final itemsGet = new DiscoItemsGet(jid.asString(), "http://jabber.org/protocol/commands"); + itemsGet.onFinished(() -> { + final bareJid = jid.asBare(); + resolve(itemsGet.getResult().filter(item -> + // Remove advertisement of commands at other JIDs for now + // It's a potential security/privacy issue depending on UX + item.jid != null && item.jid.asBare().equals(jid) && item.node != null + ).map(item -> new Command(client, item))); + }); + client.sendQuery(itemsGet); + }))).then(commands -> commands.flatten()); + } + + private function commandJids() { + final jids = []; + final jid = JID.parse(chatId); + for (resource in Caps.withFeature(getCaps(), "http://jabber.org/protocol/commands")) { + jids.push(resource == "" || resource == null ? jid : jid.withResource(resource)); + } + if (jids.length < 1 && jid.isDomain()) { + jids.push(jid); + } + return jids; + } + private function recomputeUnread(): Promise<Any> { return persistence.getMessagesBefore(client.accountId(), chatId, null, null).then((messages) -> { var i = messages.length; @@ -1167,12 +1202,7 @@ class Channel extends Chat { @:allow(borogove) override private function getCaps():KeyValueIterator<String, Caps> { - return { - hasNext: () -> false, - next: () -> { - return { key: "", value: null }; - } - }; + return ["" => disco].keyValueIterator(); } override public function setPresence(resource:String, presence:Presence) { diff --git a/borogove/Client.hx b/borogove/Client.hx index 51ce95c..30c5ffc 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -961,7 +961,14 @@ class Client extends EventEmitter { if (chat.isTrusted()) { final resources:Map<String, Bool> = []; for (resource in Caps.withIdentity(chat.getCaps(), "gateway", null)) { - resources[resource] = true; + // Sometimes gateway items also have id "gateway" for whatever reason + final identities = chat.getResourceCaps(resource)?.identities ?? []; + if ( + (chat.chatId.indexOf("@") < 0 || identities.find(i -> i.category == "conference") == null) && + identities.find(i -> i.category == "client") == null + ) { + resources[resource] = true; + } } /* Gajim advertises this, so just go with identity instead for (resource in Caps.withFeature(chat.getCaps(), "jabber:iq:gateway")) { diff --git a/borogove/Command.hx b/borogove/Command.hx new file mode 100644 index 0000000..77ab86e --- /dev/null +++ b/borogove/Command.hx @@ -0,0 +1,23 @@ +package borogove; + +import thenshim.Promise; + +import borogove.DataForm; +import borogove.Form; + +@:expose +@:allow(borogove.CommandSession) +class Command { + public final name: String; + private final jid: JID; + private final node: String; + private final client: Client; + + @:allow(borogove) + private function new(client: Client, params: { jid: JID, name: Null<String>, node: String }) { + jid = params.jid; + node = params.node; + name = params.name ?? params.node; + this.client = client; + } +} diff --git a/npm/index.ts b/npm/index.ts index 783882d..e485a61 100644 --- a/npm/index.ts +++ b/npm/index.ts @@ -11,6 +11,7 @@ export import ChatAttachment = borogove.ChatAttachment; export import ChatMessage = borogove.ChatMessage; export import ChatMessageBuilder = borogove.ChatMessageBuilder; export import Client = borogove.Client; +export import Command = borogove.Command; export import Config = borogove.Config; export import CustomEmojiReaction = borogove.CustomEmojiReaction; export import DirectChat = borogove.DirectChat;