Compare commits
5 commits
fd11e3fd10
...
56f959e9f3
Author | SHA1 | Date | |
---|---|---|---|
56f959e9f3 | |||
e999fcf819 | |||
|
355ebfe2af | ||
7756a34a5f | |||
f79833c444 |
3 changed files with 160 additions and 3 deletions
|
@ -33,6 +33,12 @@ const markdownEscapes = [
|
||||||
[/^>/g, '\\>'],
|
[/^>/g, '\\>'],
|
||||||
[/_/g, '\\_'],
|
[/_/g, '\\_'],
|
||||||
[/^(\d+)\. /g, '$1\\. ']
|
[/^(\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({
|
const turndownService = new TurndownService({
|
||||||
|
@ -87,11 +93,15 @@ turndownService.addRule("blockquote", {
|
||||||
|
|
||||||
turndownService.addRule("spoiler", {
|
turndownService.addRule("spoiler", {
|
||||||
filter: function (node, options) {
|
filter: function (node, options) {
|
||||||
return node.hasAttribute("data-mx-spoiler")
|
return node.tagName === "SPAN" && node.hasAttribute("data-mx-spoiler")
|
||||||
},
|
},
|
||||||
|
|
||||||
replacement: function (content, node) {
|
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)
|
const fileReplyContentAlternative = attachmentEmojis.get(repliedToEvent.content.msgtype)
|
||||||
if (fileReplyContentAlternative) {
|
if (fileReplyContentAlternative) {
|
||||||
contentPreview = " " + fileReplyContentAlternative
|
contentPreview = " " + fileReplyContentAlternative
|
||||||
|
} else if (repliedToEvent.unsigned?.redacted_because) {
|
||||||
|
contentPreview = " (in reply to a deleted message)"
|
||||||
} else {
|
} else {
|
||||||
const repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body
|
const repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body
|
||||||
const contentPreviewChunks = chunk(
|
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
|
// It's designed for commonmark, we need to replace the space-space-newline with just newline
|
||||||
content = content.replace(/ \n/g, "\n")
|
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:
|
// SPRITE SHEET EMOJIS FEATURE:
|
||||||
content = await uploadEndOfMessageSpriteSheet(content, attachments, pendingFiles)
|
content = await uploadEndOfMessageSpriteSheet(content, attachments, pendingFiles)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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 => {
|
test("event2message: markdown syntax is escaped", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
await eventToMessage({
|
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 ("> “, including the space, without quotes) and stopping when a line is encountered without the prefix. This prefix is known as the “fallback prefix sequence”.",
|
||||||
|
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 ("> “, 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 => {
|
test("event2message: raw mentioning discord users in plaintext body works", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
await eventToMessage({
|
await eventToMessage({
|
||||||
|
|
|
@ -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'),
|
('!BnKuBPCvyfOkhcUjEu:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'),
|
||||||
('!maggESguZBqGBZtSnr: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'),
|
('!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
|
INSERT INTO lottie (sticker_id, mxc_url) VALUES
|
||||||
('860171525772279849', 'mxc://cadence.moe/ZtvvVbwMIdUZeovWVyGVFCeR');
|
('860171525772279849', 'mxc://cadence.moe/ZtvvVbwMIdUZeovWVyGVFCeR');
|
||||||
|
|
Loading…
Reference in a new issue