script for capturing message update events

This commit is contained in:
Cadence Ember 2023-08-15 17:20:31 +12:00
parent f501718691
commit cae591e5fd
5 changed files with 154 additions and 95 deletions

View file

@ -1,94 +1,5 @@
// @ts-check
const assert = require("assert")
const passthrough = require("../../passthrough")
const { discord, sync, db } = passthrough
/** @type {import("../converters/message-to-event")} */
const messageToEvent = sync.require("../converters/message-to-event")
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
/** @type {import("./register-user")} */
const registerUser = sync.require("./register-user")
/** @type {import("../actions/create-room")} */
const createRoom = sync.require("../actions/create-room")
/**
* @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
* @param {import("discord-api-types/v10").APIGuild} guild
*/
async function editMessage(message, guild) {
// Figure out what events we will be replacing
const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id)
const senderMxid = await registerUser.ensureSimJoined(message.author, roomID)
/** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */
const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id)
// Figure out what we will be replacing them with
const newEvents = await messageToEvent.messageToEvent(message, guild, api)
// Match the new events to the old events
/*
Rules:
+ The events must have the same type.
+ The events must have the same subtype.
Events will therefore be divided into three categories:
*/
/** 1. Events that are matched, and should be edited by sending another m.replace event */
let eventsToReplace = []
/** 2. Events that are present in the old version only, and should be blanked or redacted */
let eventsToRedact = []
/** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */
let eventsToSend = []
// For each old event...
outer: while (newEvents.length) {
const newe = newEvents[0]
// Find a new event to pair it with...
let handled = false
for (let i = 0; i < oldEventRows.length; i++) {
const olde = oldEventRows[i]
if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) {
// Found one!
// Set up the pairing
eventsToReplace.push({
old: olde,
new: newe
})
// These events have been handled now, so remove them from the source arrays
newEvents.shift()
oldEventRows.splice(i, 1)
// Go all the way back to the start of the next iteration of the outer loop
continue outer
}
}
// 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)
newEvents.shift()
}
// Anything remaining in oldEventRows is present in the old version only and should be redacted.
eventsToRedact = oldEventRows
// Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed!
// (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.)
// So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed.
eventsToReplace = eventsToReplace.filter(ev => {
// Discord does not allow files, images, attachments, or videos to be edited.
if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") {
return false
}
// Discord does not allow stickers to be edited.
if (ev.old.event_type === "m.sticker") {
return false
}
// Anything else is fair game.
return true
})
// Action time!
async function editMessage() {
// Action time!
// 1. Replace all the things.
@ -113,6 +24,5 @@ async function editMessage(message, guild) {
}
return eventIDs
}
module.exports.editMessage = editMessage
{eventsToReplace, eventsToRedact, eventsToSend}

View file

@ -0,0 +1,96 @@
// @ts-check
const assert = require("assert")
const passthrough = require("../../passthrough")
const { discord, sync, db } = passthrough
/** @type {import("./message-to-event")} */
const messageToEvent = sync.require("../converters/message-to-event")
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
/** @type {import("../actions/register-user")} */
const registerUser = sync.require("./register-user")
/** @type {import("../actions/create-room")} */
const createRoom = sync.require("../actions/create-room")
/**
* @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
* IMPORTANT: This may not have all the normal fields! The API documentation doesn't provide possible types, just says it's all optional!
* Since I don't have a spec, I will have to capture some real traffic and add it as test cases... I hope they don't change anything later...
* @param {import("discord-api-types/v10").APIGuild} guild
*/
async function editToChanges(message, guild) {
// Figure out what events we will be replacing
const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id)
const senderMxid = await registerUser.ensureSimJoined(message.author, roomID)
/** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */
const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id)
// Figure out what we will be replacing them with
const newEvents = await messageToEvent.messageToEvent(message, guild, api)
// Match the new events to the old events
/*
Rules:
+ The events must have the same type.
+ The events must have the same subtype.
Events will therefore be divided into three categories:
*/
/** 1. Events that are matched, and should be edited by sending another m.replace event */
let eventsToReplace = []
/** 2. Events that are present in the old version only, and should be blanked or redacted */
let eventsToRedact = []
/** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */
let eventsToSend = []
// For each old event...
outer: while (newEvents.length) {
const newe = newEvents[0]
// Find a new event to pair it with...
let handled = false
for (let i = 0; i < oldEventRows.length; i++) {
const olde = oldEventRows[i]
if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) {
// Found one!
// Set up the pairing
eventsToReplace.push({
old: olde,
new: newe
})
// These events have been handled now, so remove them from the source arrays
newEvents.shift()
oldEventRows.splice(i, 1)
// Go all the way back to the start of the next iteration of the outer loop
continue outer
}
}
// 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)
newEvents.shift()
}
// Anything remaining in oldEventRows is present in the old version only and should be redacted.
eventsToRedact = oldEventRows
// Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed!
// (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.)
// So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed.
eventsToReplace = eventsToReplace.filter(ev => {
// Discord does not allow files, images, attachments, or videos to be edited.
if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") {
return false
}
// Discord does not allow stickers to be edited.
if (ev.old.event_type === "m.sticker") {
return false
}
// Anything else is fair game.
return true
})
return {eventsToReplace, eventsToRedact, eventsToSend}
}
module.exports.editMessage = editMessage

View file

@ -14,7 +14,7 @@ class DiscordClient {
* @param {string} discordToken
* @param {boolean} listen whether to set up the event listeners for OOYE to operate
*/
constructor(discordToken, listen) {
constructor(discordToken, listen = true) {
this.discordToken = discordToken
this.snow = new SnowTransfer(discordToken)
this.cloud = new CloudStorm(discordToken, {
@ -44,7 +44,9 @@ class DiscordClient {
this.guilds = new Map()
/** @type {Map<string, Array<string>>} */
this.guildChannelMap = new Map()
this.cloud.on("event", message => discordPackets.onPacket(this, message))
if (listen) {
this.cloud.on("event", message => discordPackets.onPacket(this, message))
}
this.cloud.on("error", console.error)
}
}