finalise message editing

This commit is contained in:
Cadence Ember 2023-08-29 01:31:52 +12:00
parent 53b5438756
commit 56fe710392
4 changed files with 157 additions and 63 deletions

View file

@ -58,6 +58,20 @@ async function sendMessageWithWebhook(channelID, data, threadID) {
return result return result
} }
/**
* @param {string} channelID
* @param {string} messageID
* @param {DiscordTypes.RESTPatchAPIWebhookWithTokenMessageJSONBody & {files?: {name: string, file: Buffer}[]}} data
* @param {string} [threadID]
*/
async function editMessageWithWebhook(channelID, messageID, data, threadID) {
const result = await withWebhook(channelID, async webhook => {
return discord.snow.webhook.editWebhookMessage(webhook.id, webhook.token, messageID, {...data, thread_id: threadID})
})
return result
}
module.exports.ensureWebhook = ensureWebhook module.exports.ensureWebhook = ensureWebhook
module.exports.withWebhook = withWebhook module.exports.withWebhook = withWebhook
module.exports.sendMessageWithWebhook = sendMessageWithWebhook module.exports.sendMessageWithWebhook = sendMessageWithWebhook
module.exports.editMessageWithWebhook = editMessageWithWebhook

View file

@ -34,9 +34,11 @@ async function sendEvent(event) {
/** @type {DiscordTypes.APIMessage[]} */ /** @type {DiscordTypes.APIMessage[]} */
const messageResponses = [] const messageResponses = []
let eventPart = 0 // 0 is primary, 1 is supporting let eventPart = 0 // 0 is primary, 1 is supporting
// for (const message of messagesToEdit) { for (const data of messagesToEdit) {
// eventPart = 1 const messageResponse = await channelWebhook.editMessageWithWebhook(channelID, data.id, data.message, threadID)
// TODO ... eventPart = 1
messageResponses.push(messageResponse)
}
for (const message of messagesToSend) { for (const message of messagesToSend) {
const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID)
db.prepare("REPLACE INTO message_channel (message_id, channel_id) VALUES (?, ?)").run(messageResponse.id, channelID) db.prepare("REPLACE INTO message_channel (message_id, channel_id) VALUES (?, ?)").run(messageResponse.id, channelID)

View file

@ -141,19 +141,10 @@ async function eventToMessage(event, guild, di) {
if (member.displayname) displayName = member.displayname if (member.displayname) displayName = member.displayname
if (member.avatar_url) avatarURL = utils.getPublicUrlForMxc(member.avatar_url) if (member.avatar_url) avatarURL = utils.getPublicUrlForMxc(member.avatar_url)
// Convert content depending on what the message is
let content = event.content.body // ultimate fallback let content = event.content.body // ultimate fallback
if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) {
let input = event.content.formatted_body
if (event.content.msgtype === "m.emote") {
input = `* ${displayName} ${input}`
}
// Note: Element's renderers on Web and Android currently collapse whitespace, like the browser does. Turndown also collapses whitespace which is good for me.
// If later I'm using a client that doesn't collapse whitespace and I want turndown to follow suit, uncomment the following line of code, and it Just Works:
// input = input.replace(/ /g, " ")
// There is also a corresponding test to uncomment, named "event2message: whitespace is retained"
// Convert content depending on what the message is
if (event.content.msgtype === "m.text" || event.content.msgtype === "m.emote") {
// Handling edits. If the edit was an edit of a reply, edits do not include the reply reference, so we need to fetch up to 2 more events. // Handling edits. If the edit was an edit of a reply, edits do not include the reply reference, so we need to fetch up to 2 more events.
// this event ---is an edit of--> original event ---is a reply to--> past event // this event ---is an edit of--> original event ---is a reply to--> past event
await (async () => { await (async () => {
@ -167,16 +158,26 @@ async function eventToMessage(event, guild, di) {
if (!originalEventId) return if (!originalEventId) return
messageIDsToEdit = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? ORDER BY part").pluck().all(originalEventId) messageIDsToEdit = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? ORDER BY part").pluck().all(originalEventId)
if (!messageIDsToEdit.length) return if (!messageIDsToEdit.length) return
// Ok, it's an edit.
event.content = event.content["m.new_content"]
// Is it editing a reply? We need special handling if it is.
// Get the original event, then check if it was a reply // Get the original event, then check if it was a reply
const originalEvent = await di.api.getEvent(event.room_id, originalEventId) const originalEvent = await di.api.getEvent(event.room_id, originalEventId)
if (!originalEvent) return if (!originalEvent) return
const repliedToEventId = originalEvent.content["m.relates_to"]?.["m.in_reply_to"]?.event_id const repliedToEventId = originalEvent.content["m.relates_to"]?.["m.in_reply_to"]?.event_id
if (!repliedToEventId) return if (!repliedToEventId) return
// After all that, it's an edit of a reply. // After all that, it's an edit of a reply.
// We'll be sneaky and prepare the message data so that everything else can handle it just like original messages. // We'll be sneaky and prepare the message data so that the next steps can handle it just like original messages.
Object.assign(event.content, event.content["m.new_content"]) Object.assign(event.content, {
input = event.content.formatted_body || event.content.body "m.relates_to": {
relatesTo["m.in_reply_to"] = {event_id: repliedToEventId} "m.in_reply_to": {
event_id: repliedToEventId
}
}
})
})() })()
// Handling replies. We'll look up the data of the replied-to event from the Matrix homeserver. // Handling replies. We'll look up the data of the replied-to event from the Matrix homeserver.
@ -206,6 +207,12 @@ async function eventToMessage(event, guild, di) {
replyLine = `> ${replyLine}\n> ${contentPreview}\n` replyLine = `> ${replyLine}\n> ${contentPreview}\n`
})() })()
if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) {
let input = event.content.formatted_body
if (event.content.msgtype === "m.emote") {
input = `* ${displayName} ${input}`
}
// Handling mentions of Discord users // Handling mentions of Discord users
input = input.replace(/("https:\/\/matrix.to\/#\/(@[^"]+)")>/g, (whole, attributeValue, mxid) => { input = input.replace(/("https:\/\/matrix.to\/#\/(@[^"]+)")>/g, (whole, attributeValue, mxid) => {
if (!utils.eventSenderIsFromDiscord(mxid)) return whole if (!utils.eventSenderIsFromDiscord(mxid)) return whole
@ -242,6 +249,11 @@ async function eventToMessage(event, guild, di) {
} }
}) })
// Note: Element's renderers on Web and Android currently collapse whitespace, like the browser does. Turndown also collapses whitespace which is good for me.
// If later I'm using a client that doesn't collapse whitespace and I want turndown to follow suit, uncomment the following line of code, and it Just Works:
// input = input.replace(/ /g, " ")
// There is also a corresponding test to uncomment, named "event2message: whitespace is retained"
// @ts-ignore bad type from turndown // @ts-ignore bad type from turndown
content = turndownService.turndown(input) content = turndownService.turndown(input)
@ -249,9 +261,16 @@ async function eventToMessage(event, guild, di) {
content = content.replace(/ \n/g, "\n") content = content.replace(/ \n/g, "\n")
} else { } else {
// Looks like we're using the plaintext body! // Looks like we're using the plaintext body!
content = event.content.body
if (event.content.msgtype === "m.emote") {
content = `* ${displayName} ${content}`
}
// Markdown needs to be escaped // Markdown needs to be escaped
content = content.replace(/([*_~`#])/g, `\\$1`) content = content.replace(/([*_~`#])/g, `\\$1`)
} }
}
content = replyLine + content content = replyLine + content
@ -266,8 +285,10 @@ async function eventToMessage(event, guild, di) {
const messagesToEdit = [] const messagesToEdit = []
const messagesToSend = [] const messagesToSend = []
for (let i = 0; i < messages.length; i++) { for (let i = 0; i < messages.length; i++) {
if (messageIDsToEdit.length) { const next = messageIDsToEdit[0]
messagesToEdit.push({id: messageIDsToEdit.shift(), message: messages[i]}) if (next) {
messagesToEdit.push({id: next, message: messages[i]})
messageIDsToEdit.shift()
} else { } else {
messagesToSend.push(messages[i]) messagesToSend.push(messages[i])
} }

View file

@ -584,7 +584,7 @@ test("event2message: editing a plaintext body message", async t => {
"room_id": "!PnyBKvUBOhjuCucEfk:cadence.moe" "room_id": "!PnyBKvUBOhjuCucEfk:cadence.moe"
}, data.guild.general, { }, data.guild.general, {
api: { api: {
getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$7LIdiJCEqjcWUrpzWzS8TELOlFfBEe4ytgS7zn2lbSs", { getEvent: mockGetEvent(t, "!PnyBKvUBOhjuCucEfk:cadence.moe", "$7LIdiJCEqjcWUrpzWzS8TELOlFfBEe4ytgS7zn2lbSs", {
type: "m.room.message", type: "m.room.message",
sender: "@cadence:cadence.moe", sender: "@cadence:cadence.moe",
content: { content: {
@ -609,6 +609,63 @@ test("event2message: editing a plaintext body message", async t => {
) )
}) })
test("event2message: editing a formatted body message", async t => {
t.deepEqual(
await eventToMessage({
"type": "m.room.message",
"sender": "@cadence:cadence.moe",
"content": {
"msgtype": "m.text",
"body": " * **well, I guess it's no longer brand new... it's existed for mere seconds...**",
"format": "org.matrix.custom.html",
"formatted_body": "* <strong>well, I guess it's no longer brand new... it's existed for mere seconds...</strong>",
"m.new_content": {
"msgtype": "m.text",
"body": "**well, I guess it's no longer brand new... it's existed for mere seconds...**",
"format": "org.matrix.custom.html",
"formatted_body": "<strong>well, I guess it's no longer brand new... it's existed for mere seconds...</strong>"
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$7LIdiJCEqjcWUrpzWzS8TELOlFfBEe4ytgS7zn2lbSs"
}
},
"origin_server_ts": 1693223873912,
"unsigned": {
"age": 42,
"transaction_id": "m1693223873796.842"
},
"event_id": "$KxGwvVNzNcmlVbiI2m5kX-jMFNi3Jle71-uu1j7P7vM",
"room_id": "!PnyBKvUBOhjuCucEfk:cadence.moe"
}, data.guild.general, {
api: {
getEvent: mockGetEvent(t, "!PnyBKvUBOhjuCucEfk:cadence.moe", "$7LIdiJCEqjcWUrpzWzS8TELOlFfBEe4ytgS7zn2lbSs", {
type: "m.room.message",
sender: "@cadence:cadence.moe",
content: {
msgtype: "m.text",
body: "**brand new, never before seen message**",
format: "org.matrix.custom.html",
formatted_body: "<strong>brand new, never before seen message</strong>"
}
})
}
}),
{
messagesToDelete: [],
messagesToEdit: [{
id: "1145688633186193479",
message: {
username: "cadence [they]",
content: "**well, I guess it's no longer brand new... it's existed for mere seconds...**",
avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU"
}
}],
messagesToSend: []
}
)
})
test("event2message: rich reply to a matrix user's long message with formatting", async t => { test("event2message: rich reply to a matrix user's long message with formatting", async t => {
t.deepEqual( t.deepEqual(
await eventToMessage({ await eventToMessage({