git » sdk » commit 4907cd1

Support for listing chat commands

author Stephen Paul Weber
2025-11-19 14:13:00 UTC
committer Stephen Paul Weber
2025-11-19 14:13:00 UTC
parent 440bc7d664f11e606163d8c08bf5ebf1919f4dd5

Support for listing chat commands

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;