Wrangle generated embeds; fix edit m.mentions

This commit is contained in:
Cadence Ember
2026-01-30 20:01:08 +13:00
parent 44208b6fd5
commit af9e2d89a5
3 changed files with 35 additions and 15 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
"author": "Cadence, PapiOphidian", "author": "Cadence, PapiOphidian",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"engines": { "engines": {
"node": ">=20" "node": ">=22"
}, },
"dependencies": { "dependencies": {
"@chriscdn/promise-semaphore": "^3.0.1", "@chriscdn/promise-semaphore": "^3.0.1",
+25 -13
View File
@@ -46,7 +46,7 @@ async function editToChanges(message, guild, api) {
// Now, this code path is only used by generated embeds for messages that were originally sent from Matrix. // Now, this code path is only used by generated embeds for messages that were originally sent from Matrix.
const originallyFromMatrix = oldEventRows.find(r => r.part === 0)?.source === 0 const originallyFromMatrix = oldEventRows.find(r => r.part === 0)?.source === 0
const isGeneratedEmbed = !("content" in message) || originallyFromMatrix const mightBeGeneratedEmbed = !("content" in message) || originallyFromMatrix
// Figure out who to send as // Figure out who to send as
@@ -79,7 +79,7 @@ async function editToChanges(message, guild, api) {
*/ */
/** /**
* 1. Events that are matched, and should be edited by sending another m.replace event * 1. Events that are matched, and should be edited by sending another m.replace event
* @type {{old: typeof oldEventRows[0], newFallbackContent: typeof newFallbackContent[0], newInnerContent: typeof newInnerContent[0]}[]} * @type {{old: typeof oldEventRows[0], oldMentions?: any, newFallbackContent: typeof newFallbackContent[0], newInnerContent: typeof newInnerContent[0]}[]}
*/ */
let eventsToReplace = [] let eventsToReplace = []
/** /**
@@ -133,20 +133,20 @@ async function editToChanges(message, guild, api) {
// If this is a generated embed update, only allow the embeds to be updated, since the system only sends data about events. Ignore changes to other things. // If this is a generated embed update, only allow the embeds to be updated, since the system only sends data about events. Ignore changes to other things.
// This also prevents Matrix events that were re-subtyped during conversion (e.g. large image -> text link) from being mistakenly included. // This also prevents Matrix events that were re-subtyped during conversion (e.g. large image -> text link) from being mistakenly included.
if (isGeneratedEmbed) { if (mightBeGeneratedEmbed) {
unchangedEvents = unchangedEvents.concat( unchangedEvents = unchangedEvents.concat(
dUtils.filterTo(eventsToRedact, e => e.old.event_subtype === "m.notice" && e.old.source === 1), // Move everything except embeds from eventsToRedact to unchangedEvents. dUtils.filterTo(eventsToRedact, e => e.old.event_subtype === "m.notice" && e.old.source === 1), // Move everything except embeds from eventsToRedact to unchangedEvents.
dUtils.filterTo(eventsToReplace, e => e.old.event_subtype === "m.notice" && e.old.source === 1) // Move everything except embeds from eventsToReplace to unchangedEvents. dUtils.filterTo(eventsToReplace, e => e.old.event_subtype === "m.notice" && e.old.source === 1) // Move everything except embeds from eventsToReplace to unchangedEvents.
) )
eventsToSend = eventsToSend.filter(e => e.msgtype === "m.notice") // Don't send new events that aren't the embed. eventsToSend = eventsToSend.filter(e => e.msgtype === "m.notice") // Don't send new events that aren't the embed.
}
// Don't post new generated embeds for messages if it's been a while since the message was sent. Detached embeds look weird. // Don't post new generated embeds for messages if it's been a while since the message was sent. Detached embeds look weird.
const messageTooOld = message.timestamp && new Date(message.timestamp).getTime() < Date.now() - 30 * 1000 // older than 30 seconds ago const messageTooOld = message.timestamp && new Date(message.timestamp).getTime() < Date.now() - 30 * 1000 // older than 30 seconds ago
// Don't post new generated embeds for messages if the setting was disabled. // Don't post new generated embeds for messages if the setting was disabled.
const embedsEnabled = select("guild_space", "url_preview", {guild_id: guild?.id}).pluck().get() ?? 1 const embedsEnabled = select("guild_space", "url_preview", {guild_id: guild?.id}).pluck().get() ?? 1
if (messageTooOld || !embedsEnabled) { if ((messageTooOld || !embedsEnabled) && !message.author.bot) {
eventsToSend = [] eventsToSend = eventsToSend.filter(e => e.msgtype !== "m.notice") // Only send events that aren't embeds.
}
} }
// Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed!
@@ -161,6 +161,7 @@ async function editToChanges(message, guild, api) {
const event = eventsToReplace[i] const event = eventsToReplace[i]
if (!eventIsText(event)) continue // not text, can't analyse if (!eventIsText(event)) continue // not text, can't analyse
const oldEvent = await api.getEvent(roomID, eventsToReplace[i].old.event_id) const oldEvent = await api.getEvent(roomID, eventsToReplace[i].old.event_id)
eventsToReplace[i].oldMentions = oldEvent.content["m.mentions"]
const oldEventBodyWithoutQuotedReply = oldEvent.content.body?.replace(/^(>.*\n)*\n*/sm, "") const oldEventBodyWithoutQuotedReply = oldEvent.content.body?.replace(/^(>.*\n)*\n*/sm, "")
if (oldEventBodyWithoutQuotedReply !== event.newInnerContent.body) continue // event changed, must replace it if (oldEventBodyWithoutQuotedReply !== event.newInnerContent.body) continue // event changed, must replace it
// Move it from eventsToRedact to unchangedEvents. // Move it from eventsToRedact to unchangedEvents.
@@ -210,7 +211,7 @@ async function editToChanges(message, guild, api) {
// Removing unnecessary properties before returning // Removing unnecessary properties before returning
return { return {
roomID, roomID,
eventsToReplace: eventsToReplace.map(e => ({oldID: e.old.event_id, newContent: makeReplacementEventContent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})), eventsToReplace: eventsToReplace.map(e => ({oldID: e.old.event_id, newContent: makeReplacementEventContent(e.old.event_id, e.oldMentions, e.newFallbackContent, e.newInnerContent)})),
eventsToRedact: eventsToRedact.map(e => e.old.event_id), eventsToRedact: eventsToRedact.map(e => e.old.event_id),
eventsToSend, eventsToSend,
senderMxid, senderMxid,
@@ -221,14 +222,25 @@ async function editToChanges(message, guild, api) {
/** /**
* @template T * @template T
* @param {string} oldID * @param {string} oldID
* @param {any} oldMentions
* @param {T} newFallbackContent * @param {T} newFallbackContent
* @param {T} newInnerContent * @param {T} newInnerContent
* @returns {import("../../types").Event.ReplacementContent<T>} content * @returns {import("../../types").Event.ReplacementContent<T>} content
*/ */
function makeReplacementEventContent(oldID, newFallbackContent, newInnerContent) { function makeReplacementEventContent(oldID, oldMentions, newFallbackContent, newInnerContent) {
const mentions = {}
const newMentionUsers = new Set(newFallbackContent["m.mentions"]?.user_ids || [])
const oldMentionUsers = new Set(oldMentions?.user_ids || [])
const mentionDiff = newMentionUsers.difference(oldMentionUsers)
if (mentionDiff.size) {
mentions.user_ids = [...mentionDiff.values()]
}
if (newFallbackContent["m.mentions"]?.room && !oldMentions?.room) {
mentions.room = true
}
const content = { const content = {
"m.mentions": {},
...newFallbackContent, ...newFallbackContent,
"m.mentions": mentions,
"m.new_content": { "m.new_content": {
...newInnerContent ...newInnerContent
}, },
+9 -1
View File
@@ -42,7 +42,14 @@ test("edit2changes: bot response", async t => {
const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.bot_response, data.guild.general, { const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.bot_response, data.guild.general, {
getEvent(roomID, eventID) { getEvent(roomID, eventID) {
t.equal(eventID, "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY") t.equal(eventID, "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY")
return {content: {body: "dummy"}} return {
content: {
"m.mentions": {
user_ids: ["@cadence:cadence.moe"],
},
body: "dummy"
}
}
}, },
async getJoinedMembers(roomID) { async getJoinedMembers(roomID) {
t.equal(roomID, "!hYnGGlPHlbujVVfktC:cadence.moe") t.equal(roomID, "!hYnGGlPHlbujVVfktC:cadence.moe")
@@ -365,6 +372,7 @@ test("edit2changes: generated embed", async t => {
test("edit2changes: generated embed on a reply", async t => { test("edit2changes: generated embed on a reply", async t => {
let called = 0 let called = 0
data.message_update.embed_generated_on_reply.timestamp = new Date().toISOString()
const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.embed_generated_on_reply, data.guild.general, { const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.embed_generated_on_reply, data.guild.general, {
getEvent(roomID, eventID) { getEvent(roomID, eventID) {
called++ called++