1
0
Fork 0

record more update events

This commit is contained in:
Cadence Ember 2023-08-16 20:44:38 +12:00
parent b1ca71f37c
commit 8f6bb86b92
6 changed files with 122 additions and 72 deletions

View file

@ -27,7 +27,7 @@ async function sendMessage(message, guild) {
await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) await registerUser.syncUser(message.author, message.member, message.guild_id, roomID)
} }
const events = await messageToEvent.messageToEvent(message, guild, api) const events = await messageToEvent.messageToEvent(message, guild, {}, {api})
const eventIDs = [] const eventIDs = []
let eventPart = 0 // 0 is primary, 1 is supporting let eventPart = 0 // 0 is primary, 1 is supporting
for (const event of events) { for (const event of events) {

View file

@ -29,7 +29,9 @@ async function editToChanges(message, guild) {
// Figure out what we will be replacing them with // Figure out what we will be replacing them with
const newEvents = await messageToEvent.messageToEvent(message, guild, api) const newFallbackContent = await messageToEvent.messageToEvent(message, guild, {includeEditFallbackStar: true}, {api})
const newInnerContent = await messageToEvent.messageToEvent(message, guild, {includeReplyFallback: false}, {api})
assert.ok(newFallbackContent.length === newInnerContent.length)
// Match the new events to the old events // Match the new events to the old events
@ -47,21 +49,27 @@ async function editToChanges(message, guild) {
let eventsToSend = [] let eventsToSend = []
// 4. Events that are matched and have definitely not changed, so they don't need to be edited or replaced at all. This is represented as nothing. // 4. Events that are matched and have definitely not changed, so they don't need to be edited or replaced at all. This is represented as nothing.
function shift() {
newFallbackContent.shift()
newInnerContent.shift()
}
// For each old event... // For each old event...
outer: while (newEvents.length) { outer: while (newFallbackContent.length) {
const newe = newEvents[0] const newe = newFallbackContent[0]
// Find a new event to pair it with... // Find a new event to pair it with...
for (let i = 0; i < oldEventRows.length; i++) { for (let i = 0; i < oldEventRows.length; i++) {
const olde = oldEventRows[i] const olde = oldEventRows[i]
if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype ?? null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to
// Found one! // Found one!
// Set up the pairing // Set up the pairing
eventsToReplace.push({ eventsToReplace.push({
old: olde, old: olde,
new: newe newFallbackContent: newFallbackContent[0],
newInnerContent: newInnerContent[0]
}) })
// These events have been handled now, so remove them from the source arrays // These events have been handled now, so remove them from the source arrays
newEvents.shift() shift()
oldEventRows.splice(i, 1) oldEventRows.splice(i, 1)
// Go all the way back to the start of the next iteration of the outer loop // Go all the way back to the start of the next iteration of the outer loop
continue outer continue outer
@ -69,7 +77,7 @@ async function editToChanges(message, guild) {
} }
// If we got this far, we could not pair it to an existing event, so it'll have to be a new one // If we got this far, we could not pair it to an existing event, so it'll have to be a new one
eventsToSend.push(newe) eventsToSend.push(newe)
newEvents.shift() shift()
} }
// Anything remaining in oldEventRows is present in the old version only and should be redacted. // Anything remaining in oldEventRows is present in the old version only and should be redacted.
eventsToRedact = oldEventRows eventsToRedact = oldEventRows
@ -92,7 +100,7 @@ async function editToChanges(message, guild) {
// Removing unnecessary properties before returning // Removing unnecessary properties before returning
eventsToRedact = eventsToRedact.map(e => e.event_id) eventsToRedact = eventsToRedact.map(e => e.event_id)
eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.new)})) eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)}))
return {eventsToReplace, eventsToRedact, eventsToSend} return {eventsToReplace, eventsToRedact, eventsToSend}
} }
@ -100,31 +108,26 @@ async function editToChanges(message, guild) {
/** /**
* @template T * @template T
* @param {string} oldID * @param {string} oldID
* @param {T} content * @param {T} newFallbackContent
* @param {T} newInnerContent
* @returns {import("../../types").Event.ReplacementContent<T>} content * @returns {import("../../types").Event.ReplacementContent<T>} content
*/ */
function eventToReplacementEvent(oldID, content) { function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) {
const newContent = { const content = {
...content, ...newFallbackContent,
"m.mentions": {}, "m.mentions": {},
"m.new_content": { "m.new_content": {
...content ...newInnerContent
}, },
"m.relates_to": { "m.relates_to": {
rel_type: "m.replace", rel_type: "m.replace",
event_id: oldID event_id: oldID
} }
} }
if (typeof newContent.body === "string") { delete content["m.new_content"]["$type"]
newContent.body = "* " + newContent.body
}
if (typeof newContent.formatted_body === "string") {
newContent.formatted_body = "* " + newContent.formatted_body
}
delete newContent["m.new_content"]["$type"]
// Client-Server API spec 11.37.3: Any m.relates_to property within m.new_content is ignored. // Client-Server API spec 11.37.3: Any m.relates_to property within m.new_content is ignored.
delete newContent["m.new_content"]["m.relates_to"] delete content["m.new_content"]["m.relates_to"]
return newContent return content
} }
module.exports.editToChanges = editToChanges module.exports.editToChanges = editToChanges

View file

@ -47,9 +47,13 @@ test("edit2changes: edit of reply to skull webp attachment with content", async
oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M",
new: { new: {
$type: "m.room.message", $type: "m.room.message",
// TODO: read "edits of replies" in the spec!!!
msgtype: "m.text", msgtype: "m.text",
body: "* Edit", body: "> Extremity: Image\n\n* Edit",
format: "org.matrix.custom.html",
formatted_body:
'<mx-reply><blockquote><a href="https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q">In reply to</a> Extremity'
+ '<br>Image</blockquote></mx-reply>'
+ '* Edit',
"m.mentions": {}, "m.mentions": {},
"m.new_content": { "m.new_content": {
msgtype: "m.text", msgtype: "m.text",
@ -60,7 +64,6 @@ test("edit2changes: edit of reply to skull webp attachment with content", async
rel_type: "m.replace", rel_type: "m.replace",
event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M"
} }
// TODO: read "edits of replies" in the spec!!!
} }
}]) }])
}) })

View file

@ -55,9 +55,12 @@ function getDiscordParseCallbacks(message, useHTML) {
/** /**
* @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIMessage} message
* @param {import("discord-api-types/v10").APIGuild} guild * @param {import("discord-api-types/v10").APIGuild} guild
* @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API * @param {{includeReplyFallback?: boolean, includeEditFallbackStar?: boolean}} options default values:
* - includeReplyFallback: true
* - includeEditFallbackStar: false
* @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API
*/ */
async function messageToEvent(message, guild, api) { async function messageToEvent(message, guild, options = {}, di) {
const events = [] const events = []
/** /**
@ -99,7 +102,7 @@ async function messageToEvent(message, guild, api) {
} }
if (repliedToEventOriginallyFromMatrix) { if (repliedToEventOriginallyFromMatrix) {
// Need to figure out who sent that event... // Need to figure out who sent that event...
const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) const event = await di.api.getEvent(repliedToEventRoomId, repliedToEventId)
repliedToEventSenderMxid = event.sender repliedToEventSenderMxid = event.sender
// Need to add the sender to m.mentions // Need to add the sender to m.mentions
addMention(repliedToEventSenderMxid) addMention(repliedToEventSenderMxid)
@ -133,7 +136,7 @@ async function messageToEvent(message, guild, api) {
if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) {
const writtenMentionsText = matches.map(m => m[1].toLowerCase()) const writtenMentionsText = matches.map(m => m[1].toLowerCase())
const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id)
const {joined} = await api.getJoinedMembers(roomID) const {joined} = await di.api.getJoinedMembers(roomID)
for (const [mxid, member] of Object.entries(joined)) { for (const [mxid, member] of Object.entries(joined)) {
if (!userRegex.some(rx => mxid.match(rx))) { if (!userRegex.some(rx => mxid.match(rx))) {
const localpart = mxid.match(/@([^:]*)/) const localpart = mxid.match(/@([^:]*)/)
@ -143,8 +146,15 @@ async function messageToEvent(message, guild, api) {
} }
} }
// Star * prefix for fallback edits
if (options.includeEditFallbackStar) {
body = "* " + body
html = "* " + html
}
// Fallback body/formatted_body for replies // Fallback body/formatted_body for replies
if (repliedToEventId) { // This branch is optional - do NOT change anything apart from the reply fallback, since it may not be run
if (repliedToEventId && options.includeReplyFallback !== false) {
let repliedToDisplayName let repliedToDisplayName
let repliedToUserHtml let repliedToUserHtml
if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) {

View file

@ -30,7 +30,7 @@ function mockGetEvent(t, roomID_in, eventID_in, outer) {
} }
test("message2event: simple plaintext", async t => { test("message2event: simple plaintext", async t => {
const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) const events = await messageToEvent(data.message.simple_plaintext, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
@ -40,7 +40,7 @@ test("message2event: simple plaintext", async t => {
}) })
test("message2event: simple user mention", async t => { test("message2event: simple user mention", async t => {
const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) const events = await messageToEvent(data.message.simple_user_mention, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
@ -52,7 +52,7 @@ test("message2event: simple user mention", async t => {
}) })
test("message2event: simple room mention", async t => { test("message2event: simple room mention", async t => {
const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) const events = await messageToEvent(data.message.simple_room_mention, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
@ -64,7 +64,7 @@ test("message2event: simple room mention", async t => {
}) })
test("message2event: simple message link", async t => { test("message2event: simple message link", async t => {
const events = await messageToEvent(data.message.simple_message_link, data.guild.general) const events = await messageToEvent(data.message.simple_message_link, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
@ -76,7 +76,7 @@ test("message2event: simple message link", async t => {
}) })
test("message2event: attachment with no content", async t => { test("message2event: attachment with no content", async t => {
const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) const events = await messageToEvent(data.message.attachment_no_content, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
@ -94,7 +94,7 @@ test("message2event: attachment with no content", async t => {
}) })
test("message2event: stickers", async t => { test("message2event: stickers", async t => {
const events = await messageToEvent(data.message.sticker, data.guild.general) const events = await messageToEvent(data.message.sticker, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
@ -127,7 +127,7 @@ test("message2event: stickers", async t => {
}) })
test("message2event: skull webp attachment with content", async t => { test("message2event: skull webp attachment with content", async t => {
const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general) const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
@ -150,7 +150,7 @@ test("message2event: skull webp attachment with content", async t => {
}) })
test("message2event: reply to skull webp attachment with content", async t => { test("message2event: reply to skull webp attachment with content", async t => {
const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general) const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general, {})
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
"m.relates_to": { "m.relates_to": {
@ -183,15 +183,17 @@ test("message2event: reply to skull webp attachment with content", async t => {
}) })
test("message2event: simple reply to matrix user", async t => { test("message2event: simple reply to matrix user", async t => {
const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, { const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {}, {
getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { api: {
type: "m.room.message", getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", {
content: { type: "m.room.message",
msgtype: "m.text", content: {
body: "so can you reply to my webhook uwu" msgtype: "m.text",
}, body: "so can you reply to my webhook uwu"
sender: "@cadence:cadence.moe" },
}) sender: "@cadence:cadence.moe"
})
}
}) })
t.deepEqual(events, [{ t.deepEqual(events, [{
$type: "m.room.message", $type: "m.room.message",
@ -215,34 +217,66 @@ test("message2event: simple reply to matrix user", async t => {
}]) }])
}) })
test("message2event: simple reply to matrix user, reply fallbacks disabled", async t => {
const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {includeReplyFallback: false}, {
api: {
getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", {
type: "m.room.message",
content: {
msgtype: "m.text",
body: "so can you reply to my webhook uwu"
},
sender: "@cadence:cadence.moe"
})
}
})
t.deepEqual(events, [{
$type: "m.room.message",
"m.relates_to": {
"m.in_reply_to": {
event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4"
}
},
"m.mentions": {
user_ids: [
"@cadence:cadence.moe"
]
},
msgtype: "m.text",
body: "Reply"
}])
})
test("message2event: simple written @mention for matrix user", async t => { test("message2event: simple written @mention for matrix user", async t => {
const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, { const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, {
async getJoinedMembers(roomID) { api: {
t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") async getJoinedMembers(roomID) {
return new Promise(resolve => { t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe")
setTimeout(() => { return new Promise(resolve => {
resolve({ setTimeout(() => {
joined: { resolve({
"@cadence:cadence.moe": { joined: {
display_name: "cadence [they]", "@cadence:cadence.moe": {
avatar_url: "whatever" display_name: "cadence [they]",
}, avatar_url: "whatever"
"@huckleton:cadence.moe": { },
display_name: "huck", "@huckleton:cadence.moe": {
avatar_url: "whatever" display_name: "huck",
}, avatar_url: "whatever"
"@_ooye_botrac4r:cadence.moe": { },
display_name: "botrac4r", "@_ooye_botrac4r:cadence.moe": {
avatar_url: "whatever" display_name: "botrac4r",
}, avatar_url: "whatever"
"@_ooye_bot:cadence.moe": { },
display_name: "Out Of Your Element", "@_ooye_bot:cadence.moe": {
avatar_url: "whatever" display_name: "Out Of Your Element",
avatar_url: "whatever"
}
} }
} })
}) })
}) })
}) }
} }
}) })
t.deepEqual(events, [{ t.deepEqual(events, [{

Binary file not shown.