borogove
created pr with
borogove/multi-dm.1
added borogove/multi-dm.2
1: 0ef2769 = 1: 41e89c8 Use to for chatId if not in recipients
2: 90e5a6a = 2: c323e3d clone should clone attributes
3: d86ea28 = 3: a1199ae Actually send to all recipients
4: 1c1061f ! 4: 94bf87d Never add self as counterpart
5: 9c8aa41 = 5: 32b9fad Extended addressing for sent messages specifies all recipients
6: 2da9124 = 6: ce4d9e1 Always add all recipients to outbound message
7: e7db006 = 7: 350343e Use a shared helper for adding extended addressing
8: 4530ece = 8: 40f662a Add startMultiChat to create multiDMs
9: 6810ba9 = 9: 8043c53 Never include ourselves in incoming chat ids
-: ------- > 10: 68e7246 Cannot MAM or bookmark a multiDM
cmds
checkout latest patchset:
ssh pr.pico.sh pull borogove:multi-dm | git am -3checkout specific patchset revision:
ssh pr.pico.sh pull borogove:multi-dm [rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh borogove:multi-dmadd comment to patch request:
ssh pr.pico.sh comment borogove:multi-dm "lgtm!"
Patchset
borogove/multi-dm.2
Use to for chatId if not in recipients
Stephen Paul Weber
clone should clone attributes
2026-11-08T19:15:14ZStephen Paul Weber
Actually send to all recipients
2026-11-09T16:18:06ZStephen Paul Weber
Never add self as counterpart
2026-11-09T16:24:50ZStephen Paul Weber
Extended addressing for sent messages specifies all recipients
2026-11-08T19:20:04ZStephen Paul Weber
→ Always add all recipients to outbound message
2026-11-08T19:20:46ZStephen Paul Weber
Use a shared helper for adding extended addressing
2026-11-08T19:21:50ZStephen Paul Weber
Add startMultiChat to create multiDMs
2026-11-08T19:32:30ZStephen Paul Weber
Never include ourselves in incoming chat ids
2026-11-08T21:01:35ZStephen Paul Weber
Cannot MAM or bookmark a multiDM
2026-11-09T19:40:59ZStephen Paul Weber
2026-11-10T17:34:01Z
Always add all recipients to outbound message
Stephen Paul Weber
2026-11-08T19:21:50ZSemantic diff summary
0 added,
0 modified,
0 signature changed,
0 removed
across 0 analyzed files
(2 files skipped: unsupported file type)
+4
-8
borogove/Chat.hx
#
| ... | ... | @@ -1278,8 +1278,8 @@ class DirectChat extends Chat { | |
| 1278 | 1278 | message.from = client.jid; | |
| 1279 | 1279 | message.sender = message.from.asBare(); | |
| 1280 | 1280 | message.replyTo = [message.sender]; | |
| 1281 | - | message.recipients = counterparts().map((p) -> JID.parse(p)); | |
| 1282 | - | message.to = message.recipients[0]; | |
| 1281 | + | message.recipients = _fullCounterparts.map((p) -> JID.parse(p)); | |
| 1282 | + | message.to = JID.parse(counterparts()[0]); | |
| 1283 | 1283 | if (message.localId == null) message.localId = ID.unique(); | |
| 1284 | 1284 | return message; | |
| 1285 | 1285 | } |
| ... | ... | @@ -1309,11 +1309,10 @@ class DirectChat extends Chat { | |
| 1309 | 1309 | if (typingTimer != null) typingTimer.stop(); | |
| 1310 | 1310 | client.chatActivity(this); | |
| 1311 | 1311 | message = prepareOutgoingMessage(message); | |
| 1312 | - | message.to = message.recipients[0]; // Just pick one for the stanza we re-parse | |
| 1312 | + | final outboxItem = outbox.newItem(); | |
| 1313 | 1313 | final fromStanza = Message.fromStanza(message.build().asStanza(), client.jid).parsed; | |
| 1314 | 1314 | switch (fromStanza) { | |
| 1315 | 1315 | case ChatMessageStanza(_): | |
| 1316 | - | final outboxItem = outbox.newItem(); | |
| 1317 | 1316 | client.storeMessageBuilder(message).then((stored) -> { | |
| 1318 | 1317 | final stanza = message.build().asStanza(); | |
| 1319 | 1318 | if (isActive != null) { |
| ... | ... | @@ -1334,10 +1333,7 @@ class DirectChat extends Chat { | |
| 1334 | 1333 | }); | |
| 1335 | 1334 | case ReactionUpdateStanza(update): | |
| 1336 | 1335 | persistence.storeReaction(client.accountId(), update).then((stored) -> { | |
| 1337 | - | for (recipient in message.recipients) { | |
| 1338 | - | message.to = recipient; | |
| 1339 | - | client.sendStanza(message.build().asStanza()); | |
| 1340 | - | } | |
| 1336 | + | sendMessageStanza(message.build().asStanza(), outboxItem, e2eePreference); | |
| 1341 | 1337 | if (stored != null) client.notifyMessageHandlers(stored, ReactionEvent); | |
| 1342 | 1338 | }); | |
| 1343 | 1339 | default: |
+33
-0
test/TestChat.hx
#
| ... | ... | @@ -1231,6 +1231,39 @@ class TestChat extends utest.Test { | |
| 1231 | 1231 | } | |
| 1232 | 1232 | #end | |
| 1233 | 1233 | ||
| 1234 | + | public function testSendMessageMultiDMSendsOnlyToRoutingJid(async: Async) { | |
| 1235 | + | final persistence = new RecordingPersistence(); | |
| 1236 | + | final client = new Client("me@example.com", persistence); | |
| 1237 | + | final routing = "multi@example.com"; | |
| 1238 | + | final chat = new borogove.Chat.DirectChat(client, client.stream, persistence, routing); | |
| 1239 | + | chat._fullCounterparts = ["bob@example.com", "carol@example.com"]; | |
| 1240 | + | ||
| 1241 | + | client.stream.on("sendStanza", (stanza: Stanza) -> { | |
| 1242 | + | if (stanza.name != "message" || stanza.getChildText("body") != "hello") return EventUnhandled; | |
| 1243 | + | ||
| 1244 | + | Assert.equals(1, persistence.storedMessages.length); | |
| 1245 | + | if (persistence.storedMessages.length > 0) { | |
| 1246 | + | final stored = persistence.storedMessages[0]; | |
| 1247 | + | Assert.equals(routing, stored.chatId()); | |
| 1248 | + | Assert.equals(routing, stored.to?.asString()); | |
| 1249 | + | Assert.same(["bob@example.com", "carol@example.com"], stored.recipients.map(r -> r.asBare().asString())); | |
| 1250 | + | } | |
| 1251 | + | ||
| 1252 | + | Assert.equals(routing, stanza.attr.get("to")); | |
| 1253 | + | final addresses = stanza.getChild("addresses", "http://jabber.org/protocol/address"); | |
| 1254 | + | Assert.notNull(addresses); | |
| 1255 | + | if (addresses != null) { | |
| 1256 | + | Assert.same(["bob@example.com", "carol@example.com"], addresses.allTags("address").map(a -> a.attr.get("jid"))); | |
| 1257 | + | } | |
| 1258 | + | async.done(); | |
| 1259 | + | return EventHandled; | |
| 1260 | + | }); | |
| 1261 | + | ||
| 1262 | + | final builder = new ChatMessageBuilder(); | |
| 1263 | + | builder.text = "hello"; | |
| 1264 | + | chat.sendMessage(builder, borogove.Chat.OutgoingE2EEPreference.NoE2EE); | |
| 1265 | + | } | |
| 1266 | + | ||
| 1234 | 1267 | public function testSendMessageExtendedMultiDMSendsToEachParticipant(async: Async) { | |
| 1235 | 1268 | final persistence = new RecordingPersistence(); | |
| 1236 | 1269 | final client = new Client("me@example.com", persistence); |