git » sdk » commit 0e879bc

Allow owner to set channel photo

author Stephen Paul Weber
2026-06-22 17:48:48 UTC
committer Stephen Paul Weber
2026-06-22 17:48:48 UTC
parent a2945f0396c467357806835ba428825c831d032b

Allow owner to set channel photo

borogove/AttachmentSource.js.hx +1 -1
borogove/Chat.hx +30 -0
test/TestChat.hx +67 -0

diff --git a/borogove/AttachmentSource.js.hx b/borogove/AttachmentSource.js.hx
index cd7754d..81c602f 100644
--- a/borogove/AttachmentSource.js.hx
+++ b/borogove/AttachmentSource.js.hx
@@ -1,7 +1,7 @@
 package borogove;
 
 @:forward
-abstract AttachmentSource(js.html.File) {
+abstract AttachmentSource(js.html.File) from js.html.File {
 	public inline function tinkSource() {
 		return tink.io.Source.ofJsFile(this.name, this);
 	}
diff --git a/borogove/Chat.hx b/borogove/Chat.hx
index 7dfdb72..120b553 100644
--- a/borogove/Chat.hx
+++ b/borogove/Chat.hx
@@ -1646,6 +1646,36 @@ class Channel extends Chat {
 		});
 	}
 
+	/**
+		Can the user set the photo of this channel?
+	**/
+	public function canSetPhoto() {
+		return isOwner();
+	}
+
+	/**
+		Set the current photo for this channel on the server, if user is an owner
+
+		@param photo to set
+	**/
+	public function setPhoto(source: AttachmentSource) {
+		if (source.size > 128000) throw "Size limit of 128KB";
+		final out = new haxe.io.BytesOutput();
+		final sink = new tink.io.std.OutputSink("bytes", out, tink.io.Worker.get());
+		source.tinkSource().pipeTo(sink).handle(_ -> {
+			final bytes = out.getBytes();
+			final b64 = haxe.crypto.Base64.encode(bytes);
+
+			client.sendStanza(
+				new Stanza("iq", { type: "set", to: chatId })
+					.tag("vCard", { xmlns: "vcard-temp" })
+					.tag("PHOTO")
+					.textTag("TYPE", source.type)
+					.textTag("BINVAL", b64)
+			);
+		});
+	}
+
 	@:allow(borogove)
 	private function getCaps():KeyValueIterator<Null<String>, Caps> {
 		var hasNext = true;
diff --git a/test/TestChat.hx b/test/TestChat.hx
index c34165f..79be54d 100644
--- a/test/TestChat.hx
+++ b/test/TestChat.hx
@@ -524,6 +524,73 @@ class TestChat extends utest.Test {
 		final chat = client.getDirectChat("+123456789@" + domain);
 		Assert.isTrue(chat.canAudioCall());
 	}
+
+	public function testCanSetPhoto() {
+		final persistence = new Dummy();
+		final client = new Client("test@example.com", persistence);
+		final chat = new borogove.Chat.Channel(client, client.stream, persistence, "channel@example.com");
+
+		Assert.isFalse(chat.canSetPhoto());
+
+		chat.self = new Member("me", "myself", null, true, [new Role("member", "")], JID.parse("test@example.com"), new Map(), null);
+		Assert.isFalse(chat.canSetPhoto());
+
+		chat.self = new Member("me", "myself", null, true, [new Role("owner", "")], JID.parse("test@example.com"), new Map(), null);
+		Assert.isTrue(chat.canSetPhoto());
+	}
+
+	public function testSetPhoto(async: Async) {
+		final persistence = new Dummy();
+		final client = new Client("test@example.com", persistence);
+		final chat = new borogove.Chat.Channel(client, client.stream, persistence, "channel@example.com");
+
+		#if js
+		final source = new js.html.File([], "", { type: "image/png" });
+		#else
+		final source = Type.createEmptyInstance(borogove.AttachmentSource);
+		Reflect.setField(source, "path", "/dev/null");
+		Reflect.setField(source, "size", 100);
+		Reflect.setField(source, "type", "image/png");
+		#end
+
+		client.stream.on("sendStanza", (stanza: Stanza) -> {
+			if (stanza.name == "iq" && stanza.attr.get("type") == "set") {
+				final vcard = stanza.getChild("vCard", "vcard-temp");
+				if (vcard != null) {
+					final photo = vcard.getChild("PHOTO");
+					Assert.notNull(photo);
+					Assert.equals("image/png", photo.getChild("TYPE").getText());
+					Assert.equals("", photo.getChild("BINVAL").getText());
+					async.done();
+					return EventHandled;
+				}
+			}
+			return EventUnhandled;
+		});
+
+		chat.setPhoto(source);
+	}
+
+	public function testSetPhotoTooBig() {
+		final persistence = new Dummy();
+		final client = new Client("test@example.com", persistence);
+		final chat = new borogove.Chat.Channel(client, client.stream, persistence, "channel@example.com");
+
+		#if js
+		var s = "";
+		for (i in 1...200000) {
+			s = s += "a";
+		}
+		final source = new js.html.File([s], "", { type: "image/png" });
+		#else
+		final source = Type.createEmptyInstance(borogove.AttachmentSource);
+		Reflect.setField(source, "path", "/dev/null");
+		Reflect.setField(source, "size", 200000);
+		Reflect.setField(source, "type", "image/png");
+		#end
+
+		Assert.raises(() -> chat.setPhoto(source), String);
+	}
 }
 
 @:access(borogove)