Compare commits

...

5 Commits

Author SHA1 Message Date
Cadence Ember 56f959e9f3 m->d: test: spoiler reasons 2024-02-13 23:03:56 +13:00
Cadence Ember e999fcf819 m->d: test: Line break between reply and quote msg 2024-02-13 23:03:56 +13:00
Wonder Collective 355ebfe2af m->d: spoiler reasons & reply-quote separation
a few m2d converter improvements
2024-02-13 23:03:56 +13:00
Cadence Ember 7756a34a5f m->d: Gracefully handle replies to redacted event 2024-02-13 22:58:21 +13:00
Cadence Ember f79833c444 m->d: Only care about data-mx-spoiler on span 2024-02-13 22:58:11 +13:00
3 changed files with 160 additions and 3 deletions

View File

@ -33,6 +33,12 @@ const markdownEscapes = [
[/^>/g, '\\>'],
[/_/g, '\\_'],
[/^(\d+)\. /g, '$1\\. ']
/*
Strikethrough is deliberately not escaped. Usually when Matrix users type ~~ it's not because they wanted to send ~~,
it's because they wanted strikethrough and it didn't work because their client doesn't support it.
As bridge developers, we can choose between "messages should look as similar as possible" vs "it was most likely intended to be strikethrough".
I went with the latter. Even though the appearance doesn't match, I'd rather it displayed as originally intended for 80% of the readers than for 0%.
*/
]
const turndownService = new TurndownService({
@ -87,11 +93,15 @@ turndownService.addRule("blockquote", {
turndownService.addRule("spoiler", {
filter: function (node, options) {
return node.hasAttribute("data-mx-spoiler")
return node.tagName === "SPAN" && node.hasAttribute("data-mx-spoiler")
},
replacement: function (content, node) {
return "||" + content + "||"
if (node.getAttribute("data-mx-spoiler")) {
// escape parentheses so it can't become a link
return `\\(${node.getAttribute("data-mx-spoiler")}\\) ||${content}||`
}
return `||${content}||`
}
})
@ -509,6 +519,8 @@ async function eventToMessage(event, guild, di) {
const fileReplyContentAlternative = attachmentEmojis.get(repliedToEvent.content.msgtype)
if (fileReplyContentAlternative) {
contentPreview = " " + fileReplyContentAlternative
} else if (repliedToEvent.unsigned?.redacted_because) {
contentPreview = " (in reply to a deleted message)"
} else {
const repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body
const contentPreviewChunks = chunk(
@ -628,6 +640,9 @@ async function eventToMessage(event, guild, di) {
// It's designed for commonmark, we need to replace the space-space-newline with just newline
content = content.replace(/ \n/g, "\n")
// If there's a blockquote at the start of the message body and this message is a reply, they should be visually separated
if (replyLine && content.startsWith("> ")) content = "\n" + content
// SPRITE SHEET EMOJIS FEATURE:
content = await uploadEndOfMessageSpriteSheet(content, attachments, pendingFiles)
} else {

View File

@ -234,6 +234,37 @@ test("event2message: spoilers work", async t => {
)
})
test("event2message: spoiler reasons work", async t => {
t.deepEqual(
await eventToMessage({
content: {
msgtype: "m.text",
body: "wrong body",
format: "org.matrix.custom.html",
formatted_body: `<span data-mx-spoiler="cw crossword spoilers you'll never believe. don't tell anybody">zoe kills a 5 letter noun at the end</span>`
},
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
origin_server_ts: 1688301929913,
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe",
sender: "@cadence:cadence.moe",
type: "m.room.message",
unsigned: {
age: 405299
}
}),
{
ensureJoined: [],
messagesToDelete: [],
messagesToEdit: [],
messagesToSend: [{
username: "cadence [they]",
content: "\\(cw crossword spoilers you'll never believe. don't tell anybody\\) ||zoe kills a 5 letter noun at the end||",
avatar_url: undefined
}]
}
)
})
test("event2message: markdown syntax is escaped", async t => {
t.deepEqual(
await eventToMessage({
@ -1778,6 +1809,115 @@ test("event2message: with layered rich replies, the preview should only be the r
)
})
test("event2message: if event is a reply and starts with a quote, they should be separated by a blank line, so that they don't visually merge together", async t => {
t.deepEqual(
await eventToMessage({
type: "m.room.message",
sender: "@aflower:syndicated.gay",
content: {
body: "> <@aflower:syndicated.gay> i have a feeling that clients are *meant to* strip these reply fallbacks too, just that none of them, in reality, do\n\n>To strip the fallback on the <code>body</code>, the client should iterate over each line of the string, removing any lines that start with the fallback prefix (&quot;&gt; &ldquo;, including the space, without quotes) and stopping when a line is encountered without the prefix. This prefix is known as the &ldquo;fallback prefix sequence&rdquo;.",
format: "org.matrix.custom.html",
formatted_body: "<mx-reply><blockquote><a href=\"https://matrix.to/#/!TqlyQmifxGUggEmdBN:cadence.moe/$tTYQcke93fwocsc1K6itwUq85EG0RZ0ksCuIglKioks\">In reply to</a> <a href=\"https://matrix.to/#/@aflower:syndicated.gay\">@aflower:syndicated.gay</a><br/>i have a feeling that clients are <em>meant to</em> strip these reply fallbacks too, just that none of them, in reality, do</blockquote></mx-reply><blockquote>\n<p>To strip the fallback on the <code>body</code>, the client should iterate over each line of the string, removing any lines that start with the fallback prefix (&quot;&gt; “, including the space, without quotes) and stopping when a line is encountered without the prefix. This prefix is known as the “fallback prefix sequence”.</p>\n</blockquote>",
"im.nheko.relations.v1.relations": [
{
event_id: "$tTYQcke93fwocsc1K6itwUq85EG0RZ0ksCuIglKioks",
rel_type: "im.nheko.relations.v1.in_reply_to"
}
],
"m.relates_to": {
"m.in_reply_to": {
event_id: "$tTYQcke93fwocsc1K6itwUq85EG0RZ0ksCuIglKioks"
}
},
msgtype: "m.text"
},
room_id: "!TqlyQmifxGUggEmdBN:cadence.moe",
event_id: "$nCvtZeBFedYuEavt4OftloCHc0kaFW2ktHCfIOklhjU",
}, data.guild.general, {
api: {
getEvent: mockGetEvent(t, "!TqlyQmifxGUggEmdBN:cadence.moe", "$tTYQcke93fwocsc1K6itwUq85EG0RZ0ksCuIglKioks", {
sender: "@aflower:syndicated.gay",
type: "m.room.message",
content: {
body: "i have a feeling that clients are *meant to* strip these reply fallbacks too, just that none of them, in reality, do",
format: "org.matrix.custom.html",
formatted_body: "i have a feeling that clients are <em>meant to</em> strip these reply fallbacks too, just that none of them, in reality, do",
msgtype: "m.text"
}
})
}
}),
{
ensureJoined: [],
messagesToDelete: [],
messagesToEdit: [],
messagesToSend: [{
username: "Rose",
content: "> <:L1:1144820033948762203><:L2:1144820084079087647>Ⓜ️**Rose**:"
+ "\n> i have a feeling that clients are meant to strip..."
+ "\n"
+ "\n> To strip the fallback on the `body`, the client should iterate over each line of the string, removing any lines that start with the fallback prefix (\"> “, including the space, without quotes) and stopping when a line is encountered without the prefix. This prefix is known as the “fallback prefix sequence”.",
avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/syndicated.gay/ZkBUPXCiXTjdJvONpLJmcbKP"
}]
}
)
})
test("event2message: rich reply to a deleted event", async t => {
t.deepEqual(
await eventToMessage({
type: "m.room.message",
sender: "@ampflower:matrix.org",
content: {
msgtype: "m.text",
body: "> <@ampflower:matrix.org> \n\nHuh it did the same thing here too",
format: "org.matrix.custom.html",
formatted_body: "<mx-reply><blockquote><a href=\"https://matrix.to/#/!TqlyQmifxGUggEmdBN:cadence.moe/$f-noT-d-Eo_Xgpc05Ww89ErUXku4NwKWYGHLzWKo1kU?via=cadence.moe\">In reply to</a> <a href=\"https://matrix.to/#/@ampflower:matrix.org\">@ampflower:matrix.org</a><br></blockquote></mx-reply>Huh it did the same thing here too",
"m.relates_to": {
"m.in_reply_to": {
event_id: "$f-noT-d-Eo_Xgpc05Ww89ErUXku4NwKWYGHLzWKo1kU"
}
}
},
event_id: "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8",
room_id: "!TqlyQmifxGUggEmdBN:cadence.moe"
}, data.guild.general, {
api: {
getEvent: mockGetEvent(t, "!TqlyQmifxGUggEmdBN:cadence.moe", "$f-noT-d-Eo_Xgpc05Ww89ErUXku4NwKWYGHLzWKo1kU", {
type: "m.room.message",
sender: "@ampflower:matrix.org",
content: {},
origin_server_ts: 1707798292953,
unsigned: {
redacted_because: {
type: "m.room.redaction",
room_id: "!TqlyQmifxGUggEmdBN:cadence.moe",
sender: "@_ooye_bot:cadence.moe",
content: {},
redacts: "$uyOzmYhqcgF5i0bZb4MrAIEKEvzDOLgXdlRr1zfvWo0",
origin_server_ts: 1707798294565,
event_id: "$enCV-40Sut8llwALAV0T3qjwK7MvO9jgY9C4DHbxKXA",
user_id: "@_ooye_bot:cadence.moe",
},
},
user_id: "@ampflower:matrix.org"
})
}
}),
{
ensureJoined: [],
messagesToDelete: [],
messagesToEdit: [],
messagesToSend: [{
username: "Ampflower 🌺",
content: "> <:L1:1144820033948762203><:L2:1144820084079087647>Ⓜ️**Ampflower 🌺** (in reply to a deleted message)"
+ "\nHuh it did the same thing here too",
avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/PRfhXYBTOalvgQYtmCLeUXko"
}]
}
)
})
test("event2message: raw mentioning discord users in plaintext body works", async t => {
t.deepEqual(
await eventToMessage({

View File

@ -115,7 +115,9 @@ INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES
('!BnKuBPCvyfOkhcUjEu:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'),
('!maggESguZBqGBZtSnr:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'),
('!CzvdIdUQXgUjDVKxeU:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'),
('!cBxtVRxDlZvSVhJXVK:cadence.moe', '@Milan:tchncs.de', 'Milan', NULL);
('!cBxtVRxDlZvSVhJXVK:cadence.moe', '@Milan:tchncs.de', 'Milan', NULL),
('!TqlyQmifxGUggEmdBN:cadence.moe', '@ampflower:matrix.org', 'Ampflower 🌺', 'mxc://cadence.moe/PRfhXYBTOalvgQYtmCLeUXko'),
('!TqlyQmifxGUggEmdBN:cadence.moe', '@aflower:syndicated.gay', 'Rose', 'mxc://syndicated.gay/ZkBUPXCiXTjdJvONpLJmcbKP');
INSERT INTO lottie (sticker_id, mxc_url) VALUES
('860171525772279849', 'mxc://cadence.moe/ZtvvVbwMIdUZeovWVyGVFCeR');