Emergency sync #11

Merged
Guzio merged 13 commits from cadence/out-of-your-element:main into main 2026-03-14 07:10:15 +00:00
4 changed files with 70 additions and 39 deletions
Showing only changes of commit 5f768fee01 - Show all commits

View file

@ -78,7 +78,7 @@ test("edit2changes: bot response", async t => {
newContent: {
$type: "m.room.message",
msgtype: "m.text",
body: "* :ae_botrac4r: [@cadence](https://matrix.to/#/@cadence:cadence.moe) asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.",
body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.",
format: "org.matrix.custom.html",
formatted_body: '* <img data-mx-emoticon height="32" src="mxc://cadence.moe/skqfuItqxNmBYekzmVKyoLzs" title=":ae_botrac4r:" alt=":ae_botrac4r:"> <a href="https://matrix.to/#/@cadence:cadence.moe">@cadence</a> asked <code>­</code>, I respond: Stop drinking paint. (No)<br><br>Hit <img data-mx-emoticon height="32" src="mxc://cadence.moe/OIpqpfxTnHKokcsYqDusxkBT" title=":bn_re:" alt=":bn_re:"> to reroll.',
"m.mentions": {
@ -87,7 +87,7 @@ test("edit2changes: bot response", async t => {
// *** Replaced With: ***
"m.new_content": {
msgtype: "m.text",
body: ":ae_botrac4r: [@cadence](https://matrix.to/#/@cadence:cadence.moe) asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.",
body: ":ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.",
format: "org.matrix.custom.html",
formatted_body: '<img data-mx-emoticon height="32" src="mxc://cadence.moe/skqfuItqxNmBYekzmVKyoLzs" title=":ae_botrac4r:" alt=":ae_botrac4r:"> <a href="https://matrix.to/#/@cadence:cadence.moe">@cadence</a> asked <code>­</code>, I respond: Stop drinking paint. (No)<br><br>Hit <img data-mx-emoticon height="32" src="mxc://cadence.moe/OIpqpfxTnHKokcsYqDusxkBT" title=":bn_re:" alt=":bn_re:"> to reroll.',
"m.mentions": {

View file

@ -146,10 +146,18 @@ function findMention(pjr, maximumWrittenSection, baseOffset, prefix, content) {
// Highlight the relevant part of the message
const start = baseOffset + best.scored.matchedInputTokens[0].index
const end = baseOffset + prefix.length + best.scored.matchedInputTokens.slice(-1)[0].end
const newContent = content.slice(0, start) + "[" + content.slice(start, end) + "](https://matrix.to/#/" + best.mxid + ")" + content.slice(end)
const newNodes = [{
type: "text", content: content.slice(0, start)
}, {
type: "link", target: `https://matrix.to/#/${best.mxid}`, content: [
{type: "text", content: content.slice(start, end)}
]
}, {
type: "text", content: content.slice(end)
}]
return {
mxid: best.mxid,
newContent
newNodes
}
}
}

View file

@ -519,29 +519,60 @@ async function messageToEvent(message, guild, options = {}, di) {
return emojiToKey.emojiToKey({id, name, animated}, message.id) // Register the custom emoji if needed
}))
async function transformParsedVia(parsed) {
for (const node of parsed) {
async function transformParsedVia(parsed, scanTextForMentions) {
for (let n = 0; n < parsed.length; n++) {
const node = parsed[n]
if (node.type === "discordChannel" || node.type === "discordChannelLink") {
node.row = select("channel_room", ["room_id", "name", "nick"], {channel_id: node.id}).get()
if (node.row?.room_id) {
node.via = await getViaServersMemo(node.row.room_id)
}
}
else if (node.type === "text" && typeof node.content === "string") {
// Merge adjacent text nodes into this one
while (parsed[n+1]?.type === "text" && typeof parsed[n+1].content === "string") {
node.content += parsed[n+1].content
parsed.splice(n+1, 1)
}
// Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention.
if (scanTextForMentions) {
let content = node.content
const matches = [...content.matchAll(/(@ ?)([a-z0-9_.#$][^@\n]+)/gi)]
for (let i = matches.length; i--;) {
const m = matches[i]
const prefix = m[1]
const maximumWrittenSection = m[2].toLowerCase()
if (m.index > 0 && !content[m.index-1].match(/ |\(|\n/)) continue // must have space before it
if (maximumWrittenSection.match(/^everyone\b/) || maximumWrittenSection.match(/^here\b/)) continue // ignore @everyone/@here
var roomID = roomID ?? select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get()
assert(roomID)
var pjr = pjr ?? findMentions.processJoined(Object.entries((await di.api.getJoinedMembers(roomID)).joined).map(([mxid, ev]) => ({mxid, displayname: ev.display_name})))
const found = findMentions.findMention(pjr, maximumWrittenSection, m.index, prefix, content)
if (found) {
addMention(found.mxid)
parsed.splice(n, 1, ...found.newNodes)
content = found.newNodes[0].content
}
}
}
}
for (const maybeChildNodesArray of [node, node.content, node.items]) {
if (Array.isArray(maybeChildNodesArray)) {
await transformParsedVia(maybeChildNodesArray)
await transformParsedVia(maybeChildNodesArray, scanTextForMentions && ["blockQuote", "list", "paragraph", "em", "strong", "u", "del", "text"].includes(node.type))
}
}
}
return parsed
}
let html = await markdown.toHtmlWithPostParser(content, transformParsedVia, {
let html = await markdown.toHtmlWithPostParser(content, parsed => transformParsedVia(parsed, customOptions.isTheMessageContent && options.scanTextForMentions !== false), {
discordCallback: getDiscordParseCallbacks(message, guild, true, spoilers),
...customOptions
}, customParser, customHtmlOutput)
let body = await markdown.toHtmlWithPostParser(content, transformParsedVia, {
let body = await markdown.toHtmlWithPostParser(content, parsed => transformParsedVia(parsed, false), { // not scanning plaintext body for mentions as we don't parse whether they're in code
discordCallback: getDiscordParseCallbacks(message, guild, false),
discordOnly: true,
escapeHTML: false,
@ -735,35 +766,12 @@ async function messageToEvent(message, guild, options = {}, di) {
// Then text content
if (message.content && !isOnlyKlipyGIF && !isThinkingInteraction) {
// Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention.
let content = message.content
if (options.scanTextForMentions !== false) {
const matches = [...content.matchAll(/(@ ?)([a-z0-9_.#$][^@\n]+)/gi)]
for (let i = matches.length; i--;) {
const m = matches[i]
const prefix = m[1]
const maximumWrittenSection = m[2].toLowerCase()
if (m.index > 0 && !content[m.index-1].match(/ |\(|\n/)) continue // must have space before it
if (maximumWrittenSection.match(/^everyone\b/) || maximumWrittenSection.match(/^here\b/)) continue // ignore @everyone/@here
var roomID = roomID ?? select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get()
assert(roomID)
var pjr = pjr ?? findMentions.processJoined(Object.entries((await di.api.getJoinedMembers(roomID)).joined).map(([mxid, ev]) => ({mxid, displayname: ev.display_name})))
const found = findMentions.findMention(pjr, maximumWrittenSection, m.index, prefix, content)
if (found) {
addMention(found.mxid)
content = found.newContent
}
}
}
// Scan the content for emojihax and replace them with real emojis
content = content.replaceAll(/\[([a-zA-Z0-9_-]{2,32})(?:~[0-9]+)?\]\(https:\/\/cdn\.discordapp\.com\/emojis\/([0-9]+)\.[^ \n)`]+\)/g, (_, name, id) => {
let content = message.content.replaceAll(/\[([a-zA-Z0-9_-]{2,32})(?:~[0-9]+)?\]\(https:\/\/cdn\.discordapp\.com\/emojis\/([0-9]+)\.[^ \n)`]+\)/g, (_, name, id) => {
return `<:${name}:${id}>`
})
const {body, html} = await transformContent(content)
const {body, html} = await transformContent(content, {isTheMessageContent: true})
await addTextEvent(body, html, msgtype)
}

View file

@ -789,7 +789,7 @@ test("message2event: simple written @mention for matrix user", async t => {
]
},
msgtype: "m.text",
body: "[@ash](https://matrix.to/#/@she_who_brings_destruction:cadence.moe) do you need anything from the store btw as I'm heading there after gym",
body: "@ash do you need anything from the store btw as I'm heading there after gym",
format: "org.matrix.custom.html",
formatted_body: `<a href="https://matrix.to/#/@she_who_brings_destruction:cadence.moe">@ash</a> do you need anything from the store btw as I'm heading there after gym`
}])
@ -838,7 +838,7 @@ test("message2event: many written @mentions for matrix users", async t => {
]
},
msgtype: "m.text",
body: "[@Cadence](https://matrix.to/#/@cadence:cadence.moe), tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and [@huck](https://matrix.to/#/@huckleton:cadence.moe)",
body: "@Cadence, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and @huck",
format: "org.matrix.custom.html",
formatted_body: `<a href="https://matrix.to/#/@cadence:cadence.moe">@Cadence</a>, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and <a href="https://matrix.to/#/@huckleton:cadence.moe">@huck</a>`
}])
@ -890,7 +890,7 @@ test("message2event: written @mentions may match part of the name", async t => {
]
},
msgtype: "m.text",
body: "I wonder if [@cadence](https://matrix.to/#/@secret:cadence.moe) saw this?",
body: "I wonder if @cadence saw this?",
format: "org.matrix.custom.html",
formatted_body: `I wonder if <a href="https://matrix.to/#/@secret:cadence.moe">@cadence</a> saw this?`
}])
@ -941,7 +941,7 @@ test("message2event: written @mentions may match part of the mxid", async t => {
]
},
msgtype: "m.text",
body: "I wonder if [@huck](https://matrix.to/#/@huckleton:cadence.moe) saw this?",
body: "I wonder if @huck saw this?",
format: "org.matrix.custom.html",
formatted_body: `I wonder if <a href="https://matrix.to/#/@huckleton:cadence.moe">@huck</a> saw this?`
}])
@ -962,6 +962,21 @@ test("message2event: written @mentions do not match in URLs", async t => {
}])
})
test("message2event: written @mentions do not match in inline code", async t => {
const events = await messageToEvent({
...data.message.advanced_written_at_mention_for_matrix,
content: "`public @Nullable EntityType<?>`"
}, data.guild.general, {}, {})
t.deepEqual(events, [{
$type: "m.room.message",
"m.mentions": {},
msgtype: "m.text",
body: "`public @Nullable EntityType<?>`",
format: "org.matrix.custom.html",
formatted_body: `<code>public @Nullable EntityType&lt;?&gt;</code>`
}])
})
test("message2event: entire message may match elaborate display name", async t => {
let called = 0
const events = await messageToEvent({
@ -1007,7 +1022,7 @@ test("message2event: entire message may match elaborate display name", async t =
]
},
msgtype: "m.text",
body: "[@Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆](https://matrix.to/#/@wa:cadence.moe)",
body: "@Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆",
format: "org.matrix.custom.html",
formatted_body: `<a href="https://matrix.to/#/@wa:cadence.moe">@Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆</a>`
}])