| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-06-22 17:48:48 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-06-22 17:48:48 UTC |
| parent | a2945f0396c467357806835ba428825c831d032b |
| 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)