git » sdk » commit c420d7d

Allow inviting someone to a chat

author Stephen Paul Weber
2025-11-29 03:43:52 UTC
committer Stephen Paul Weber
2025-11-29 03:43:52 UTC
parent 07603c4140cb9435d9808e951334c5194cbbdb1b

Allow inviting someone to a chat

borogove/Chat.hx +60 -0
borogove/Client.hx +1 -1

diff --git a/borogove/Chat.hx b/borogove/Chat.hx
index c4edfa9..b0a4f13 100644
--- a/borogove/Chat.hx
+++ b/borogove/Chat.hx
@@ -748,6 +748,30 @@ abstract class Chat {
 		return Caps.withFeature(getCaps(), "urn:xmpp:noreply:0").length < 1;
 	}
 
+	/**
+		Invite another chat's participants to participate in this one
+	**/
+	public function invite(chat: Chat, threadId: Null<String> = null) {
+		final attr: DynamicAccess<String> = {
+			xmlns: "jabber:x:conference",
+			jid: chatId
+		};
+		if (threadId != null) {
+			attr.set("continue", "true");
+			attr.set("thread", threadId);
+		}
+		chat.sendMessageStanza(
+			new Stanza("message").tag("x", attr).up()
+		);
+	}
+
+	/**
+		Can the user invite others to this chat?
+	**/
+	public function canInvite() {
+		return false;
+	}
+
 	/**
 		This chat's primary mode of interaction is via commands
 	**/
@@ -1315,6 +1339,32 @@ class Channel extends Chat {
 		return disco?.data?.find(d -> d.field("FORM_TYPE")?.value?.at(0) == "http://jabber.org/protocol/muc#roominfo");
 	}
 
+
+	override public function invite(chat: Chat, threadId: Null<String> = null) {
+		if (isPrivate()) {
+			client.sendStanza(
+				new Stanza("iq", { to: chatId })
+					.tag("query", { xmlns: "http://jabber.org/protocol/muc#admin" })
+					.tag("item", { affiliation: "member", jid: chat.chatId })
+					.up().up()
+			);
+		}
+
+		super.invite(chat, threadId);
+	}
+
+	override public function canInvite() {
+		if (!isPrivate()) return true;
+		if (_nickInUse == null) return false;
+
+		final p = presence[_nickInUse];
+		if (p == null) return false;
+
+		if (p.mucUser.role == "moderator") return true;
+
+		return false;
+	}
+
 	override public function canSend() {
 		if (!super.canSend()) return false;
 		if (_nickInUse == null) return true;
@@ -1801,6 +1851,16 @@ class Channel extends Chat {
 	@HaxeCBridge.noemit // on superclass as abstract
 	public function close() {
 		if (typingTimer != null) typingTimer.stop();
+		if (uiState == Invited) {
+			for (invite in invites()) {
+				client.sendStanza(
+					new Stanza("message", { id: ID.long(), to: chatId })
+						.tag("x", { xmlns: "http://jabber.org/protocol/muc#user" })
+						.tag("decline", { to: invite.attr.get("from") })
+						.up().up()
+				);
+			}
+		}
 		uiState = Closed;
 		persistence.storeChats(client.accountId(), [this]);
 		selfPing(false);
diff --git a/borogove/Client.hx b/borogove/Client.hx
index 86f6fa1..faff450 100644
--- a/borogove/Client.hx
+++ b/borogove/Client.hx
@@ -1637,7 +1637,7 @@ class Client extends EventEmitter {
 
 	private function serverBlocked(blocked: String) {
 		final chat = getChat(blocked) ?? getDirectChat(blocked, false);
-		chat.block(null, false);
+		chat.block(false, null, false);
 	}
 
 	// This is called right before we're going to trigger for all chats anyway, so don't bother with single triggers