script for capturing message update events
This commit is contained in:
parent
f501718691
commit
cae591e5fd
5 changed files with 154 additions and 95 deletions
|
@ -1,94 +1,5 @@
|
||||||
// @ts-check
|
async function editMessage() {
|
||||||
|
// Action time!
|
||||||
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!
|
|
||||||
|
|
||||||
// 1. Replace all the things.
|
// 1. Replace all the things.
|
||||||
|
|
||||||
|
@ -113,6 +24,5 @@ async function editMessage(message, guild) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return eventIDs
|
return eventIDs
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.editMessage = editMessage
|
{eventsToReplace, eventsToRedact, eventsToSend}
|
||||||
|
|
96
d2m/converters/edit-to-changes.js
Normal file
96
d2m/converters/edit-to-changes.js
Normal 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
|
|
@ -14,7 +14,7 @@ class DiscordClient {
|
||||||
* @param {string} discordToken
|
* @param {string} discordToken
|
||||||
* @param {boolean} listen whether to set up the event listeners for OOYE to operate
|
* @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.discordToken = discordToken
|
||||||
this.snow = new SnowTransfer(discordToken)
|
this.snow = new SnowTransfer(discordToken)
|
||||||
this.cloud = new CloudStorm(discordToken, {
|
this.cloud = new CloudStorm(discordToken, {
|
||||||
|
@ -44,7 +44,9 @@ class DiscordClient {
|
||||||
this.guilds = new Map()
|
this.guilds = new Map()
|
||||||
/** @type {Map<string, Array<string>>} */
|
/** @type {Map<string, Array<string>>} */
|
||||||
this.guildChannelMap = new Map()
|
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)
|
this.cloud.on("error", console.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
51
scripts/capture-message-update-events.js
Normal file
51
scripts/capture-message-update-events.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
// ****
|
||||||
|
const interestingFields = ["author", "content", "edited_timestamp", "mentions", "attachments", "embeds", "type", "message_reference", "referenced_message", "sticker_items"]
|
||||||
|
// *****
|
||||||
|
|
||||||
|
function fieldToPresenceValue(field) {
|
||||||
|
if (field === undefined) return 0
|
||||||
|
else if (field === null) return 1
|
||||||
|
else if (Array.isArray(field) && field.length === 0) return 10
|
||||||
|
else if (typeof field === "object" && Object.keys(field).length === 0) return 20
|
||||||
|
else if (field === "") return 30
|
||||||
|
else return 99
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlite = require("better-sqlite3")
|
||||||
|
const HeatSync = require("heatsync")
|
||||||
|
|
||||||
|
const config = require("../config")
|
||||||
|
const passthrough = require("../passthrough")
|
||||||
|
|
||||||
|
const sync = new HeatSync({watchFS: false})
|
||||||
|
|
||||||
|
Object.assign(passthrough, {config, sync})
|
||||||
|
|
||||||
|
const DiscordClient = require("../d2m/discord-client", false)
|
||||||
|
|
||||||
|
const discord = new DiscordClient(config.discordToken, false)
|
||||||
|
passthrough.discord = discord
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
await discord.cloud.connect()
|
||||||
|
console.log("Discord gateway started")
|
||||||
|
|
||||||
|
const f = event => onPacket(discord, event, () => discord.cloud.off("event", f))
|
||||||
|
discord.cloud.on("event", f)
|
||||||
|
})()
|
||||||
|
|
||||||
|
const events = new sqlite("scripts/events.db")
|
||||||
|
const sql = "INSERT INTO \"update\" (json, " + interestingFields.join(", ") + ") VALUES (" + "?".repeat(interestingFields.length + 1).split("").join(", ") + ")"
|
||||||
|
console.log(sql)
|
||||||
|
const prepared = events.prepare(sql)
|
||||||
|
|
||||||
|
/** @param {DiscordClient} discord */
|
||||||
|
function onPacket(discord, event, unsubscribe) {
|
||||||
|
if (event.t === "MESSAGE_UPDATE") {
|
||||||
|
const data = [JSON.stringify(event.d), ...interestingFields.map(f => fieldToPresenceValue(event.d[f]))]
|
||||||
|
console.log(data)
|
||||||
|
prepared.run(...data)
|
||||||
|
}
|
||||||
|
}
|
BIN
scripts/events.db
Normal file
BIN
scripts/events.db
Normal file
Binary file not shown.
Loading…
Reference in a new issue