, I respond: Stop drinking paint. (No), I respond: Stop drinking paint. (No), I respond: Stop drinking paint. (No), I respond: Stop drinking paint. (No)In reply to Extremity' + + '
Image
↪️ Brad used /stats — interaction loading...",
- "m.mentions": {},
- msgtype: "m.notice",
- }])
-})
-
test("message2event embeds: nothing but a field", async t => {
const events = await messageToEvent(data.message_with_embeds.nothing_but_a_field, data.guild.general, {})
t.deepEqual(events, [{
+ $type: "m.room.message",
+ body: "> ↪️ @papiophidian: used `/stats`",
+ format: "org.matrix.custom.html",
+ formatted_body: "↪️ @papiophidian used /stats",
+ "m.mentions": {},
+ msgtype: "m.text",
+ }, {
$type: "m.room.message",
"m.mentions": {},
msgtype: "m.notice",
- body: "↪️ PapiOphidian used `/stats`"
- + "\n| ### Amanda 🎵#2192 :online:"
+ body: "| ### Amanda 🎵#2192 :online:"
+ "\n| willow tree, branch 0"
+ "\n| **❯ Uptime:**\n| 3m 55s\n| **❯ Memory:**\n| 64.45MB",
format: "org.matrix.custom.html",
- formatted_body: '↪️ PapiOphidian used /stats'
- + '", "m.mentions": {} }]) - t.equal(called, 1, "should call getJoinedMembers once") + t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") }) test("message2event embeds: crazy html is all escaped", async t => { @@ -152,12 +154,17 @@ test("message2event embeds: title without url", async t => { const events = await messageToEvent(data.message_with_embeds.title_without_url, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", - msgtype: "m.notice", - body: "↪️ PapiOphidian used `/stats`" - + "\n| ## Hi, I'm Amanda!\n| \n| I condone pirating music!", + body: "> ↪️ @papiophidian: used `/stats`", format: "org.matrix.custom.html", - formatted_body: 'Amanda 🎵#2192
' + formatted_body: '
' @@ -94,7 +86,17 @@ test("message2event embeds: blockquote in embed", async t => { let called = 0 const events = await messageToEvent(data.message_with_embeds.blockquote_in_embed, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), + async getStateEvent(roomID, type, key) { + called++ + t.equal(roomID, "!qzDBLKlildpzrrOnFZ:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { called++ t.equal(roomID, "!qzDBLKlildpzrrOnFZ:cadence.moe") @@ -122,7 +124,7 @@ test("message2event embeds: blockquote in embed", async t => { formatted_body: "Amanda 🎵#2192
' + '
willow tree, branch 0' + '
❯ Uptime:
3m 55s' + '
❯ Memory:
64.45MBreply draft
The following is a message composed via consensus of the Stinker Council.
For those who are not currently aware of our existence, we represent the organization known as Wonderland. Our previous mission centered around the assortment and study of puzzling objects, entities and other assorted phenomena. This mission was the focus of our organization for more than 28 years.
Due to circumstances outside of our control, this directive has now changed. Our new mission will be the extermination of the stinker race.
There will be no further communication.
↪️ PapiOphidian used /stats'
- + ``, + formatted_body: "Hi, I'm Amanda!
I condone pirating music!
↪️ @papiophidian used /stats",
+ "m.mentions": {},
+ msgtype: "m.text",
+ }, {
+ $type: "m.room.message",
+ msgtype: "m.notice",
+ body: "| ## Hi, I'm Amanda!\n| \n| I condone pirating music!",
+ format: "org.matrix.custom.html",
+ formatted_body: ``, "m.mentions": {} }]) }) @@ -166,12 +173,17 @@ test("message2event embeds: url without title", async t => { const events = await messageToEvent(data.message_with_embeds.url_without_title, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", - msgtype: "m.notice", - body: "↪️ PapiOphidian used `/stats`" - + "\n| I condone pirating music!", + body: "> ↪️ @papiophidian: used `/stats`", format: "org.matrix.custom.html", - formatted_body: 'Hi, I'm Amanda!
I condone pirating music!
↪️ PapiOphidian used /stats'
- + ``, + formatted_body: "I condone pirating music!
↪️ @papiophidian used /stats",
+ "m.mentions": {},
+ msgtype: "m.text",
+ }, {
+ $type: "m.room.message",
+ msgtype: "m.notice",
+ body: "| I condone pirating music!",
+ format: "org.matrix.custom.html",
+ formatted_body: ``, "m.mentions": {} }]) }) @@ -180,12 +192,17 @@ test("message2event embeds: author without url", async t => { const events = await messageToEvent(data.message_with_embeds.author_without_url, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", - msgtype: "m.notice", - body: "↪️ PapiOphidian used `/stats`" - + "\n| ## Amanda\n| \n| I condone pirating music!", + body: "> ↪️ @papiophidian: used `/stats`", format: "org.matrix.custom.html", - formatted_body: 'I condone pirating music!
↪️ PapiOphidian used /stats'
- + ``, + formatted_body: "Amanda
I condone pirating music!
↪️ @papiophidian used /stats",
+ "m.mentions": {},
+ msgtype: "m.text",
+ }, {
+ $type: "m.room.message",
+ msgtype: "m.notice",
+ body: "| ## Amanda\n| \n| I condone pirating music!",
+ format: "org.matrix.custom.html",
+ formatted_body: ``, "m.mentions": {} }]) }) @@ -194,12 +211,17 @@ test("message2event embeds: author url without name", async t => { const events = await messageToEvent(data.message_with_embeds.author_url_without_name, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", - msgtype: "m.notice", - body: "↪️ PapiOphidian used `/stats`" - + "\n| I condone pirating music!", + body: "> ↪️ @papiophidian: used `/stats`", format: "org.matrix.custom.html", - formatted_body: 'Amanda
I condone pirating music!
↪️ PapiOphidian used /stats'
- + ``, + formatted_body: "I condone pirating music!
↪️ @papiophidian used /stats",
+ "m.mentions": {},
+ msgtype: "m.text",
+ }, {
+ $type: "m.room.message",
+ msgtype: "m.notice",
+ body: "| I condone pirating music!",
+ format: "org.matrix.custom.html",
+ formatted_body: ``, "m.mentions": {} }]) }) @@ -299,21 +321,6 @@ test("message2event embeds: youtube video", async t => { }]) }) -test("message2event embeds: embed not bridged if its link was spoilered", async t => { - const events = await messageToEvent({ - ...data.message_with_embeds.youtube_video, - content: "||https://youtu.be/kDMHHw8JqLE?si=NaqNjVTtXugHeG_E\n\n\nJutomi I'm gonna make these sounds in your walls tonight||" - }, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "[spoiler]", - format: "org.matrix.custom.html", - formatted_body: `https://youtu.be/kDMHHw8JqLE?si=NaqNjVTtXugHeG_EI condone pirating music!
➿ Cute Corgi Waddle", - "m.mentions": {} - }]) -}) - test("message2event embeds: if discord creates an embed preview for a discord channel link, don't copy that embed", async t => { const events = await messageToEvent(data.message_with_embeds.discord_server_included_punctuation_bad_discord, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), + async getStateEvent(roomID, type, key) { + t.equal(roomID, "!TqlyQmifxGUggEmdBN:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { t.equal(roomID, "!TqlyQmifxGUggEmdBN:cadence.moe") return { diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index 7f77b81..9a975d2 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -14,23 +14,20 @@ const file = sync.require("../../matrix/file") const emojiToKey = sync.require("./emoji-to-key") /** @type {import("../actions/lottie")} */ const lottie = sync.require("../actions/lottie") -/** @type {import("../../matrix/utils")} */ -const mxUtils = sync.require("../../matrix/utils") +/** @type {import("../../m2d/converters/utils")} */ +const mxUtils = sync.require("../../m2d/converters/utils") /** @type {import("../../discord/utils")} */ const dUtils = sync.require("../../discord/utils") -/** @type {import("./find-mentions")} */ -const findMentions = sync.require("./find-mentions") -/** @type {import("../../discord/interactions/poll-responses")} */ -const pollResponses = sync.require("../../discord/interactions/poll-responses") const {reg} = require("../../matrix/read-registration") +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) + /** * @param {DiscordTypes.APIMessage} message * @param {DiscordTypes.APIGuild} guild * @param {boolean} useHTML - * @param {string[]} spoilers */ -function getDiscordParseCallbacks(message, guild, useHTML, spoilers = []) { +function getDiscordParseCallbacks(message, guild, useHTML) { return { /** @param {{id: string, type: "discordUser"}} node */ user: node => { @@ -39,8 +36,8 @@ function getDiscordParseCallbacks(message, guild, useHTML, spoilers = []) { const username = message.mentions?.find(ment => ment.id === node.id)?.username || message.referenced_message?.mentions?.find(ment => ment.id === node.id)?.username || (interaction?.user.id === node.id ? interaction.user.username : null) - || (message.author?.id === node.id ? message.author.username : null) - || "unknown-user" + || (message.author.id === node.id ? message.author.username : null) + || node.id if (mxid && useHTML) { return `@${username}` } else { @@ -93,10 +90,6 @@ function getDiscordParseCallbacks(message, guild, useHTML, spoilers = []) { here: () => { if (message.mention_everyone) return "@room" return "@here" - }, - spoiler: node => { - spoilers.push(node.raw) - return useHTML } } } @@ -109,10 +102,9 @@ const embedTitleParser = markdown.markdownEngine.parserFor({ /** * @param {{room?: boolean, user_ids?: string[]}} mentions - * @param {Omit
↪️ ${mxid ? tag`${username}` : username} used /${interaction.name}${thinkingText}`
- }
-}
-
/**
* @param {DiscordTypes.APIMessage} message
* @param {DiscordTypes.APIGuild} guild
@@ -273,7 +210,6 @@ function getFormattedInteraction(interaction, isThinkingInteraction) {
* @returns {Promise<{$type: string, $sender?: string, [x: string]: any}[]>}
*/
async function messageToEvent(message, guild, options = {}, di) {
- message = structuredClone(message)
const events = []
/* c8 ignore next 7 */
@@ -285,38 +221,6 @@ async function messageToEvent(message, guild, options = {}, di) {
return []
}
- if (message.type === DiscordTypes.MessageType.PollResult) {
- const pollMessageID = message.message_reference?.message_id
- if (!pollMessageID) return []
- const event_id = select("event_message", "event_id", {message_id: pollMessageID}).pluck().get()
- const roomID = select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get()
- const pollQuestionText = select("poll", "question_text", {message_id: pollMessageID}).pluck().get()
- if (!event_id || !roomID || !pollQuestionText) return [] // drop it if the corresponding poll start was not bridged
-
- const rep = new mxUtils.MatrixStringBuilder()
- rep.addLine(`The poll ${pollQuestionText} has closed.`, tag`The poll ${pollQuestionText} has closed.`)
-
- const {messageString} = pollResponses.getCombinedResults(pollMessageID, true) // poll results have already been double-checked before this point, so these totals will be accurate
- rep.addLine(markdown.toHTML(messageString, {discordOnly: true, escapeHTML: false}), markdown.toHTML(messageString, {}))
-
- const {body, formatted_body} = rep.get()
-
- return [{
- $type: "org.matrix.msc3381.poll.end",
- "m.relates_to": {
- rel_type: "m.reference",
- event_id
- },
- "org.matrix.msc3381.poll.end": {},
- "org.matrix.msc1767.text": body,
- "org.matrix.msc1767.html": formatted_body,
- body: body,
- format: "org.matrix.custom.html",
- formatted_body: formatted_body,
- msgtype: "m.text"
- }]
- }
-
if (message.type === DiscordTypes.MessageType.ThreadStarterMessage) {
// This is the message that appears at the top of a thread when the thread was based off an existing message.
// It's just a message reference, no content.
@@ -335,8 +239,14 @@ async function messageToEvent(message, guild, options = {}, di) {
}
const interaction = message.interaction_metadata || message.interaction
- const isInteraction = message.type === DiscordTypes.MessageType.ChatInputCommand && !!interaction && "name" in interaction
- const isThinkingInteraction = isInteraction && !!((message.flags || 0) & DiscordTypes.MessageFlags.Loading)
+ if (message.type === DiscordTypes.MessageType.ChatInputCommand && interaction && "name" in interaction) {
+ // Commands are sent by the responding bot. Need to attach the metadata of the person using the command at the top.
+ let content = message.content
+ if (content) content = `\n${content}`
+ else if ((message.flags || 0) & DiscordTypes.MessageFlags.Loading) content = " — interaction loading..."
+ content = `> ↪️ <@${interaction.user.id}> used \`/${interaction.name}\`${content}`
+ message = {...message, content} // editToChanges reuses the object so we can't mutate it. have to clone it
+ }
/**
@type {{room?: boolean, user_ids?: string[]}}
@@ -355,9 +265,8 @@ async function messageToEvent(message, guild, options = {}, di) {
- So make sure we don't do anything in this case.
*/
const mentions = {}
- /** @type {{event_id: string, room_id: string, source: number, channel_id: string}?} */
+ /** @type {{event_id: string, room_id: string, source: number}?} */
let repliedToEventRow = null
- let repliedToEventInDifferentRoom = false
let repliedToUnknownEvent = false
let repliedToEventSenderMxid = null
@@ -371,9 +280,9 @@ async function messageToEvent(message, guild, options = {}, di) {
// Mentions scenarios 1 and 2, part A. i.e. translate relevant message.mentions to m.mentions
// (Still need to do scenarios 1 and 2 part B, and scenario 3.)
if (message.type === DiscordTypes.MessageType.Reply && message.message_reference?.message_id) {
- const row = await getHistoricalEventRow(message.message_reference?.message_id)
- if (row && "event_id" in row) {
- repliedToEventRow = Object.assign(row, {channel_id: row.reference_channel_id})
+ const row = from("event_message").join("message_channel", "message_id").join("channel_room", "channel_id").select("event_id", "room_id", "source").and("WHERE message_id = ? AND part = 0").get(message.message_reference.message_id)
+ if (row) {
+ repliedToEventRow = row
} else if (message.referenced_message) {
repliedToUnknownEvent = true
}
@@ -385,8 +294,8 @@ async function messageToEvent(message, guild, options = {}, di) {
assert(message.embeds[0].description)
const match = message.embeds[0].description.match(/\/channels\/[0-9]*\/[0-9]*\/([0-9]{2,})/)
if (match) {
- const row = await getHistoricalEventRow(match[1])
- if (row && "event_id" in row) {
+ const row = from("event_message").join("message_channel", "message_id").join("channel_room", "channel_id").select("event_id", "room_id", "source").and("WHERE message_id = ? AND part = 0").get(match[1])
+ if (row) {
/*
we generate a partial referenced_message based on what PK provided. we don't need everything, since this will only be used for further message-to-event converting.
the following properties are necessary:
@@ -404,7 +313,7 @@ async function messageToEvent(message, guild, options = {}, di) {
}
}
message.embeds.shift()
- repliedToEventRow = Object.assign(row, {channel_id: row.reference_channel_id})
+ repliedToEventRow = row
}
}
}
@@ -431,34 +340,6 @@ async function messageToEvent(message, guild, options = {}, di) {
return promise
}
- /**
- * @param {string} messageID
- * @param {string} [timestampChannelID]
- */
- async function getHistoricalEventRow(messageID, timestampChannelID) {
- /** @type {{room_id: string} | {event_id: string, room_id: string, reference_channel_id: string, source: number} | null | undefined} */
- let row = from("event_message").join("message_room", "message_id").join("historical_channel_room", "historical_room_index")
- .select("event_id", "room_id", "reference_channel_id", "source").where({message_id: messageID}).and("ORDER BY part ASC").get()
- if (!row && timestampChannelID) {
- const ts = dUtils.snowflakeToTimestampExact(messageID)
- const oldestRow = from("historical_channel_room").selectUnsafe("max(upgraded_timestamp)", "room_id")
- .where({reference_channel_id: timestampChannelID}).and("and upgraded_timestamp < ?").get(ts)
- if (oldestRow?.room_id) {
- row = {room_id: oldestRow.room_id}
- try {
- const {event_id} = await di.api.getEventForTimestamp(oldestRow.room_id, ts)
- row = {
- event_id,
- room_id: oldestRow.room_id,
- reference_channel_id: oldestRow.reference_channel_id,
- source: 1
- }
- } catch (e) {}
- }
- }
- return row
- }
-
/**
* Translate Discord message links to Matrix event links.
* If OOYE has handled this message in the past, this is an instant database lookup.
@@ -470,13 +351,27 @@ async function messageToEvent(message, guild, options = {}, di) {
for (const match of [...content.matchAll(/https:\/\/(?:ptb\.|canary\.|www\.)?discord(?:app)?\.com\/channels\/[0-9]+\/([0-9]+)\/([0-9]+)/g)]) {
assert(typeof match.index === "number")
const [_, channelID, messageID] = match
- const result = await (async () => {
- const row = await getHistoricalEventRow(messageID, channelID)
- if (!row) return `${match[0]} [event is from another server]`
- const via = await getViaServersMemo(row.room_id)
- if (!("event_id" in row)) return `[unknown event in https://matrix.to/#/${row.room_id}?${via}]`
- return `https://matrix.to/#/${row.room_id}/${row.event_id}?${via}`
- })()
+ let result
+
+ const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
+ if (roomID) {
+ const eventID = select("event_message", "event_id", {message_id: messageID}).pluck().get()
+ const via = await getViaServersMemo(roomID)
+ if (eventID && roomID) {
+ result = `https://matrix.to/#/${roomID}/${eventID}?${via}`
+ } else {
+ const ts = dUtils.snowflakeToTimestampExact(messageID)
+ try {
+ const {event_id} = await di.api.getEventForTimestamp(roomID, ts)
+ result = `https://matrix.to/#/${roomID}/${event_id}?${via}`
+ } catch (e) {
+ // M_NOT_FOUND: Unable to find event from In reply to ${repliedToUserHtml}` - + `` - + html - body = `${repliedToDisplayName}: ${repliedToBody}`.split("\n").map(line => "> " + line).join("\n") // scenario 1 part B for mentions - + "\n\n" + body - } else { // repliedToUnknownEvent - const dateDisplay = dUtils.howOldUnbridgedMessage(referenced.timestamp, message.timestamp) - html = `
${repliedToHtml}
In reply to ${dateDisplay} from ${repliedToDisplayName}:` - + `` - + html - body = `In reply to ${dateDisplay}:\n${repliedToDisplayName}: ${repliedToBody}`.split("\n").map(line => "> " + line).join("\n") - + "\n\n" + body - } + if ((repliedToEventRow || repliedToUnknownEvent) && options.includeReplyFallback !== false) { + let repliedToDisplayName + let repliedToUserHtml + if (repliedToEventRow?.source === 0 && repliedToEventSenderMxid) { + const match = repliedToEventSenderMxid.match(/^@([^:]*)/) + assert(match) + repliedToDisplayName = message.referenced_message?.author.username || match[1] || "a Matrix user" // grab the localpart as the display name, whatever + repliedToUserHtml = `${repliedToDisplayName}` + } else { + repliedToDisplayName = message.referenced_message?.author.global_name || message.referenced_message?.author.username || "a Discord user" + repliedToUserHtml = repliedToDisplayName + } + let repliedToContent = message.referenced_message?.content + if (repliedToContent?.match(/^(-# )?> (-# )?<:L1:/)) { + // If the Discord user is replying to a Matrix user's reply, the fallback is going to contain the emojis and stuff from the bridged rep of the Matrix user's reply quote. + // Need to remove that previous reply rep from this fallback body. The fallbody body should only contain the Matrix user's actual message. + // ┌──────A─────┐ A reply rep starting with >quote or -#smalltext >quote. Match until the end of the line. + // ┆ ┆┌─B─┐ There may be up to 2 reply rep lines in a row if it was created in the old format. Match all lines. + repliedToContent = repliedToContent.replace(/^((-# )?> .*\n){1,2}/, "") + } + if (repliedToContent == "") repliedToContent = "[Media]" + else if (!repliedToContent) repliedToContent = "[Replied-to message content wasn't provided by Discord]" + const {body: repliedToBody, html: repliedToHtml} = await transformContent(repliedToContent) + if (repliedToEventRow) { + // Generate a reply pointing to the Matrix event we found + html = `
${repliedToHtml}
In reply to ${repliedToUserHtml}` + + `
${repliedToHtml}
In reply to ${dateDisplay} from ${repliedToDisplayName}:` + + `` + + html + body = (`In reply to ${dateDisplay}:\n${repliedToDisplayName}: ` + + repliedToBody).split("\n").map(line => "> " + line).join("\n") + + "\n\n" + body } - } - - if (isInteraction && !isThinkingInteraction && events.length === 0) { - const formattedInteraction = getFormattedInteraction(interaction, false) - body = `${formattedInteraction.body}\n${body}` - html = `${formattedInteraction.html}${html}` } const newTextMessageEvent = { $type: "m.room.message", "m.mentions": mentions, msgtype, - body: body, - format: "org.matrix.custom.html", - formatted_body: html + body: body + } + + const isPlaintext = body === html + + if (!isPlaintext || options.alwaysReturnFormattedBody) { + Object.assign(newTextMessageEvent, { + format: "org.matrix.custom.html", + formatted_body: html + }) } events.push(newTextMessageEvent) @@ -666,35 +541,24 @@ async function messageToEvent(message, guild, options = {}, di) { message.content = `added a new emoji, ${message.content} :${name}:` } - // Send Klipy GIFs in customised form - let isKlipyGIF = false - let isOnlyKlipyGIF = false - if (message.embeds?.length === 1 && message.embeds[0].provider?.name === "Klipy" && message.embeds[0].video?.url) { - isKlipyGIF = true - if (message.content.match(/^https?:\/\/klipy\.com[^ \n]+$/)) { - isOnlyKlipyGIF = true - } - } - // Forwarded content appears first - if (message.message_reference?.type === DiscordTypes.MessageReferenceType.Forward && message.message_reference.message_id && message.message_snapshots?.length) { + if (message.message_reference?.type === DiscordTypes.MessageReferenceType.Forward && message.message_snapshots?.length) { // Forwarded notice - const row = await getHistoricalEventRow(message.message_reference.message_id, message.message_reference.channel_id) + const eventID = select("event_message", "event_id", {message_id: message.message_reference.message_id}).pluck().get() const room = select("channel_room", ["room_id", "name", "nick"], {channel_id: message.message_reference.channel_id}).get() const forwardedNotice = new mxUtils.MatrixStringBuilder() if (room) { const roomName = room && (room.nick || room.name) - if (row && "event_id" in row) { - const via = await getViaServersMemo(row.room_id) + const via = await getViaServersMemo(room.room_id) + if (eventID) { forwardedNotice.addLine( `[🔀 Forwarded from #${roomName}]`, - tag`🔀 Forwarded from ${roomName} [jump to event]` + tag`🔀 Forwarded from ${roomName}` ) } else { - const via = await getViaServersMemo(room.room_id) forwardedNotice.addLine( `[🔀 Forwarded from #${roomName}]`, - tag`🔀 Forwarded from ${roomName} [jump to room]` + tag`🔀 Forwarded from ${roomName}` ) } } else { @@ -711,6 +575,7 @@ async function messageToEvent(message, guild, options = {}, di) { // Indent for (const event of forwardedEvents) { if (["m.text", "m.notice"].includes(event.msgtype)) { + event.msgtype = "m.notice" event.body = event.body.split("\n").map(l => "» " + l).join("\n") event.formatted_body = `
${repliedToHtml}
${event.formatted_body}` } @@ -727,42 +592,26 @@ async function messageToEvent(message, guild, options = {}, di) { events.push(...forwardedEvents) } - if (isThinkingInteraction) { - const formattedInteraction = getFormattedInteraction(interaction, true) - await addTextEvent(formattedInteraction.body, formattedInteraction.html, "m.notice") - } - // Then text content - if (message.content && !isOnlyKlipyGIF && !isThinkingInteraction) { + if (message.content) { // Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention. - let content = message.content - if (options.scanTextForMentions !== false) { - const matches = [...content.matchAll(/(@ ?)([a-z0-9_.#$][^@\n]+)/gi)] - for (let i = matches.length; i--;) { - const m = matches[i] - const prefix = m[1] - const maximumWrittenSection = m[2].toLowerCase() - if (m.index > 0 && !content[m.index-1].match(/ |\(|\n/)) continue // must have space before it - if (maximumWrittenSection.match(/^everyone\b/) || maximumWrittenSection.match(/^here\b/)) continue // ignore @everyone/@here - - var roomID = roomID ?? select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get() - assert(roomID) - var pjr = pjr ?? findMentions.processJoined(Object.entries((await di.api.getJoinedMembers(roomID)).joined).map(([mxid, ev]) => ({mxid, displayname: ev.display_name}))) - - const found = findMentions.findMention(pjr, maximumWrittenSection, m.index, prefix, content) - if (found) { - addMention(found.mxid) - content = found.newContent + const matches = [...message.content.matchAll(/@ ?([a-z0-9._]+)\b/gi)] + if (options.scanTextForMentions !== false && matches.length && matches.some(m => m[1].match(/[a-z]/i) && m[1] !== "everyone" && m[1] !== "here")) { + const writtenMentionsText = matches.map(m => m[1].toLowerCase()) + const roomID = select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get() + assert(roomID) + const {joined} = await di.api.getJoinedMembers(roomID) + for (const [mxid, member] of Object.entries(joined)) { + if (!userRegex.some(rx => mxid.match(rx))) { + const localpart = mxid.match(/@([^:]*)/) + assert(localpart) + const displayName = member.display_name || localpart[1] + if (writtenMentionsText.includes(localpart[1].toLowerCase()) || writtenMentionsText.includes(displayName.toLowerCase())) addMention(mxid) } } } - // Scan the content for emojihax and replace them with real emojis - content = content.replaceAll(/\[([a-zA-Z0-9_-]{2,32})(?:~[0-9]+)?\]\(https:\/\/cdn\.discordapp\.com\/emojis\/([0-9]+)\.[^ \n)`]+\)/g, (_, name, id) => { - return `<:${name}:${id}>` - }) - - const {body, html} = await transformContent(content) + const {body, html} = await transformContent(message.content) await addTextEvent(body, html, msgtype) } @@ -811,125 +660,8 @@ async function messageToEvent(message, guild, options = {}, di) { // Then attachments if (message.attachments) { - const attachmentEvents = await Promise.all(message.attachments.map(attachment => attachmentToEvent(mentions, attachment))) - - // Try to merge attachment events with the previous event - // This means that if the attachments ended up as a text link, and especially if there were many of them, the events will be joined together. - let prev = events.at(-1) - for (const atch of attachmentEvents) { - if (atch.msgtype === "m.text" && prev?.body && prev?.formatted_body && ["m.text", "m.notice"].includes(prev?.msgtype)) { - prev.body = prev.body + "\n" + atch.body - prev.formatted_body = prev.formatted_body + "
${formatted_body}` - if (stack.msb.body) stack.msb.body += "\n\n" - stack.msb.add(body, formatted_body) - } - else if (component.type === DiscordTypes.ComponentType.Section) { - // May contain text display, possibly more in the future - // Accessory may be button or thumbnail - stack.bump() - for (const innerComponent of component.components) { - await processComponent(innerComponent) - } - if (component.accessory) { - stack.bump() - await processComponent(component.accessory) - const {body, formatted_body} = stack.shift().get() - stack.msb.addLine(body, formatted_body) - } - const {body, formatted_body} = stack.shift().get() - stack.msb.addParagraph(body, formatted_body) - } - else if (component.type === DiscordTypes.ComponentType.ActionRow) { - const linkButtons = component.components.filter(c => c.type === DiscordTypes.ComponentType.Button && c.style === DiscordTypes.ButtonStyle.Link) - if (linkButtons.length) { - stack.msb.addLine("") - for (const linkButton of linkButtons) { - await processComponent(linkButton) - } - } - } - // Components that can only be inside things - else if (component.type === DiscordTypes.ComponentType.Thumbnail) { - // May only be a section accessory - stack.msb.add(`🖼️ ${component.media.url}`, tag`🖼️ ${component.media.url}`) - } - else if (component.type === DiscordTypes.ComponentType.Button) { - // May only be a section accessory or in an action row (up to 5) - if (component.style === DiscordTypes.ButtonStyle.Link) { - if (component.label) { - stack.msb.add(`[${component.label} ${component.url}] `, tag`${component.label} `) - } else { - stack.msb.add(component.url) - } - } - } - - // Not handling file upload or label because they are modal-only components - } - - for (const component of message.components) { - await processComponent(component) - } - - const {body, formatted_body} = stack.msb.get() - if (body.trim().length) { - await addTextEvent(body, formatted_body, "m.text") - } - } - - // Then polls - if (message.poll) { - const pollEvent = await pollToEvent(message.poll) - events.push(pollEvent) + const attachmentEvents = await Promise.all(message.attachments.map(attachmentToEvent.bind(null, mentions))) + events.push(...attachmentEvents) } // Then embeds @@ -943,43 +675,13 @@ async function messageToEvent(message, guild, options = {}, di) { continue // Matrix's own URL previews are fine for images. } - if (embed.type === "video" && embed.video?.url && !embed.title && message.content.includes(embed.video.url)) { - continue // Doesn't add extra information and the direct video URL is already there. - } - - if (embed.type === "poll_result") { - // The code here is only for the message to be bridged to Matrix. Dealing with the Discord-side updates is in d2m/actions/poll-end.js. - } - if (embed.url?.startsWith("https://discord.com/")) { continue // If discord creates an embed preview for a discord channel link, don't copy that embed } - if (embed.url && spoilers.some(sp => sp.match(/\bhttps?:\/\/[a-z]/))) { - // If the original message had spoilered URLs, don't generate any embeds for links. - // This logic is the same as the Discord desktop client. It doesn't match specific embeds to specific spoilered text, it's all or nothing. - // It's not easy to do much better because posting a link like youtu.be generates an embed.url with youtube.com/watch, so you can't match up the text without making at least that a special case. - continue - } - // Start building up a replica ("rep") of the embed in Discord-markdown format, which we will convert into both plaintext and formatted body at once const rep = new mxUtils.MatrixStringBuilder() - if (isKlipyGIF) { - assert(embed.video?.url) - rep.add("[GIF] ", "➿ ") - if (embed.title) { - rep.add(`${embed.title} ${embed.video.url}`, tag`${embed.title}`) - } else { - rep.add(embed.video.url) - } - - let {body, formatted_body: html} = rep.get() - html = `
${html}` - await addTextEvent(body, html, "m.text") - continue - } - // Provider if (embed.provider?.name && embed.provider.name !== "Tenor") { if (embed.provider.url) { @@ -1076,7 +778,7 @@ async function messageToEvent(message, guild, options = {}, di) { } // Rich replies - if (repliedToEventRow && !repliedToEventInDifferentRoom) { + if (repliedToEventRow) { Object.assign(events[0], { "m.relates_to": { "m.in_reply_to": { @@ -1086,16 +788,6 @@ async function messageToEvent(message, guild, options = {}, di) { }) } - // Strip formatted_body where equivalent to body - if (!options.alwaysReturnFormattedBody) { - for (const event of events) { - if (event.$type === "m.room.message" && "msgtype" in event && ["m.text", "m.notice"].includes(event.msgtype) && event.body === event.formatted_body) { - delete event.format - delete event.formatted_body - } - } - } - return events } diff --git a/src/d2m/converters/message-to-event.test.pk.js b/src/d2m/converters/message-to-event.pk.test.js similarity index 72% rename from src/d2m/converters/message-to-event.test.pk.js rename to src/d2m/converters/message-to-event.pk.test.js index 1323280..ce83d54 100644 --- a/src/d2m/converters/message-to-event.test.pk.js +++ b/src/d2m/converters/message-to-event.pk.test.js @@ -50,7 +50,11 @@ test("message2event: pk reply to matrix is converted to native matrix reply", as ] }, msgtype: "m.text", - body: "this is a reply", + body: "> cadence [they]: now for my next experiment:\n\nthis is a reply", + format: "org.matrix.custom.html", + formatted_body: '
In reply to cadence [they]
' + + "now for my next experiment:
In reply to wing
' + + "some text
In reply to Ampflower 🌺
' + + "[Media]
" - + "" - + "Lillith (INX)
" - + "Display name: Lillith (she/her)" - + "
" - + `🖼️ https://files.inx.moe/p/cdn/lillith.webp` - + "
Pronouns: She/Her" - + "
Message count: 3091
" - + "Proxy tags:" - + "
l;text" - + "l:text" - + "l.text" - + "textl." - + "textl;" - + "textl:
System ID: xffgnx ∙ Member ID: pphhoh
"
- + "Created: 2025-12-31 03:16:45 UTC
` - + "System: INX (
" - + `🖼️ https://files.inx.moe/p/cdn/lillith.webp` - + "xffgnx)" - + "
Member: Lillith (pphhoh)" - + "
Sent by: infinidoge1337 (@unknown-user:)" - + "
Account Roles (7)" - + "
§b, !, ‼, Ears Port Ping, Ears Update Ping, Yttr Ping, unsup Ping
" - + "Same hat
" - + `🖼️ Image: image.png
Original Message ID: 1466556003645657118 · <t:1769724599:f>
", - "m.mentions": {}, - msgtype: "m.text", - }]) -}) diff --git a/src/d2m/converters/message-to-event.test.js b/src/d2m/converters/message-to-event.test.js index 1a73aea..ee4ec03 100644 --- a/src/d2m/converters/message-to-event.test.js +++ b/src/d2m/converters/message-to-event.test.js @@ -2,7 +2,6 @@ const {test} = require("supertape") const {messageToEvent} = require("./message-to-event") const {MatrixServerError} = require("../../matrix/mreq") const data = require("../../../test/data") -const {mockGetEffectivePower} = require("../../matrix/utils.test") const Ty = require("../../types") /** @@ -67,7 +66,17 @@ test("message2event: simple room mention", async t => { let called = 0 const events = await messageToEvent(data.message.simple_room_mention, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), + async getStateEvent(roomID, type, key) { + called++ + t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { called++ t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe") @@ -88,14 +97,24 @@ test("message2event: simple room mention", async t => { format: "org.matrix.custom.html", formatted_body: '#worm-farm' }]) - t.equal(called, 1, "should call getJoinedMembers") + t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") }) test("message2event: simple room link", async t => { let called = 0 const events = await messageToEvent(data.message.simple_room_link, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), + async getStateEvent(roomID, type, key) { + called++ + t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { called++ t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe") @@ -116,14 +135,24 @@ test("message2event: simple room link", async t => { format: "org.matrix.custom.html", formatted_body: '#worm-farm' }]) - t.equal(called, 1, "should call getJoinedMembers once") + t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") }) test("message2event: nicked room mention", async t => { let called = 0 const events = await messageToEvent(data.message.nicked_room_mention, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), + async getStateEvent(roomID, type, key) { + called++ + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { called++ t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") @@ -144,7 +173,7 @@ test("message2event: nicked room mention", async t => { format: "org.matrix.custom.html", formatted_body: '#main' }]) - t.equal(called, 1, "should call getJoinedMembers once") + t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") }) test("message2event: unknown room mention", async t => { @@ -195,7 +224,17 @@ test("message2event: simple message link", async t => { let called = 0 const events = await messageToEvent(data.message.simple_message_link, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), + async getStateEvent(roomID, type, key) { + called++ + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { called++ t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") @@ -216,14 +255,13 @@ test("message2event: simple message link", async t => { format: "org.matrix.custom.html", formatted_body: 'https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg?via=cadence.moe&via=super.invalid' }]) - t.equal(called, 1, "should call getJoinedMembers once") + t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") }) test("message2event: message link that OOYE doesn't know about", async t => { let called = 0 const events = await messageToEvent(data.message.message_link_to_before_ooye, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), async getEventForTimestamp(roomID, ts) { called++ t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") @@ -232,6 +270,17 @@ test("message2event: message link that OOYE doesn't know about", async t => { origin_server_ts: 1613287812754 } }, + async getStateEvent(roomID, type, key) { // for ?via calculation + called++ + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { // for ?via calculation called++ t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") @@ -254,7 +303,7 @@ test("message2event: message link that OOYE doesn't know about", async t => { formatted_body: "Me: I'll scroll up to find a certain message I'll sendIn reply to Extremity' + + '
Image
In reply to cadence' + + '
so can you reply to my webhook uwu
In reply to okay 🤍 yay 🤍' + + '
@extremity you owe me $30
In reply to cadence [they]
What about them?
In reply to Ami (she/her)
let me guess they got a lot of bug reports like "empty chest with no loot?"
In reply to Ami (she/her)
let me guess they got a lot of bug reports like "empty chest with no loot?"
📸 Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg (38 KB)` - + `
What's cooking, good looking?`, "m.mentions": {}, - msgtype: "m.text", + msgtype: "m.notice", }, { $type: "m.room.message", @@ -1361,7 +1194,6 @@ test("message2event: constructed forwarded message", async t => { test("message2event: constructed forwarded text", async t => { const events = await messageToEvent(data.message.constructed_forwarded_text, {}, {}, { api: { - getEffectivePower: mockGetEffectivePower(), async getJoinedMembers() { return { joined: { @@ -1378,10 +1210,10 @@ test("message2event: constructed forwarded text", async t => { body: "[🔀 Forwarded from #amanda-spam]" + "\n» What's cooking, good looking?", format: "org.matrix.custom.html", - formatted_body: `🔀 Forwarded from amanda-spam [jump to room]` + formatted_body: `🔀 Forwarded from amanda-spam` + `
What's cooking, good looking?`, "m.mentions": {}, - msgtype: "m.text", + msgtype: "m.notice", }, { $type: "m.room.message", @@ -1404,7 +1236,7 @@ test("message2event: don't scan forwarded messages for mentions", async t => { formatted_body: `🔀 Forwarded message` + `
If some folks have spare bandwidth then helping out ArchiveTeam with archiving soon to be deleted research and government data might be worthwhile https://social.luca.run/@luca/113950834185678114`, "m.mentions": {}, - msgtype: "m.text" + msgtype: "m.notice" } ]) }) @@ -1499,7 +1331,6 @@ test("message2event: vc invite event renders embed", async t => { test("message2event: vc invite event renders embed with room link", async t => { const events = await messageToEvent({content: "https://discord.gg/placeholder?event=1381174024801095751"}, {}, {}, { api: { - getEffectivePower: mockGetEffectivePower(), getJoinedMembers: async () => ({ joined: { "@_ooye_bot:cadence.moe": {display_name: null, avatar_url: null}, @@ -1549,7 +1380,6 @@ test("message2event: channel links are converted even inside lists (parser post- + "\nThis list will probably change in the future" }, data.guild.general, {}, { api: { - getEffectivePower: mockGetEffectivePower(), getJoinedMembers(roomID) { called++ t.equal(roomID, "!qzDBLKlildpzrrOnFZ:cadence.moe") @@ -1581,226 +1411,3 @@ test("message2event: channel links are converted even inside lists (parser post- ]) t.equal(called, 1) }) - -test("message2event: emoji added special message", async t => { - const events = await messageToEvent(data.special_message.emoji_added) - t.deepEqual(events, [ - { - $type: "m.room.message", - msgtype: "m.emote", - body: "added a new emoji, :cx_marvelous: :cx_marvelous:", - format: "org.matrix.custom.html", - formatted_body: `added a new emoji,
In reply to Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆cross-room reply`, - "m.mentions": { - user_ids: [ - "@cadence:cadence.moe" - ] - } - } - ]) -}) - -test("message2event: forwarded message with unreferenced mention", async t => { - const events = await messageToEvent({ - type: 0, - content: "", - attachments: [], - embeds: [], - timestamp: "2026-01-20T14:14:21.281Z", - edited_timestamp: null, - flags: 16384, - components: [], - id: "1463174818823405651", - channel_id: "893634327722721290", - author: { - id: "100031256988766208", - username: "leo60228", - discriminator: "0", - avatar: "8a164f29946f23eb4f45cde71a75e5a6", - avatar_decoration_data: null, - public_flags: 768, - global_name: "leo vriska", - primary_guild: null, - collectibles: null, - display_name_styles: null - }, - bot: false, - pinned: false, - mentions: [], - mention_roles: [], - mention_everyone: false, - tts: false, - message_reference: { - type: 1, - channel_id: "937181373943382036", - message_id: "1032034158261846038", - guild_id: "936370934292549712" - }, - message_snapshots: [ - { - message: { - type: 0, - content: "<@77084495118868480>", - attachments: [ - { - id: "1463174815119704114", - filename: "2022-10-18_16-49-46.mp4", - size: 51238885, - url: "https://cdn.discordapp.com/attachments/893634327722721290/1463174815119704114/2022-10-18_16-49-46.mp4?ex=6970df3c&is=696f8dbc&hm=515d3cbcc8464bdada7f4c3d9ccc8174f671cb75391ce21a46a804fcb1e4befe&", - proxy_url: "https://media.discordapp.net/attachments/893634327722721290/1463174815119704114/2022-10-18_16-49-46.mp4?ex=6970df3c&is=696f8dbc&hm=515d3cbcc8464bdada7f4c3d9ccc8174f671cb75391ce21a46a804fcb1e4befe&", - width: 1920, - height: 1080, - content_type: "video/mp4", - content_scan_version: 3, - spoiler: false - } - ], - embeds: [], - timestamp: "2022-10-18T20:55:17.597Z", - edited_timestamp: null, - flags: 0, - components: [] - } - } - ] - }) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "[🔀 Forwarded message]\n» @unknown-user:\n» 🎞️ Uploaded file: https://bridge.example.org/download/discordcdn/893634327722721290/1463174815119704114/2022-10-18_16-49-46.mp4 (51 MB)", - format: "org.matrix.custom.html", - formatted_body: "🔀 Forwarded message
[Media]
@unknown-user:", - "m.mentions": {} - }]) -}) - -test("message2event: single-choice poll", async t => { - const events = await messageToEvent(data.message.poll_single_choice, data.guild.general, {}) - t.deepEqual(events, [{ - $type: "org.matrix.msc3381.poll.start", - "org.matrix.msc3381.poll.start": { - question: { - "org.matrix.msc1767.text": "only one answer allowed!", - body: "only one answer allowed!", - msgtype: "m.text" - }, - kind: "org.matrix.msc3381.poll.disclosed", // Discord always lets you see results, so keeping this consistent with that. - max_selections: 1, - answers: [{ - id: "1", - "org.matrix.msc1767.text": "[\ud83d\udc4d] answer one" - }, { - id: "2", - "org.matrix.msc1767.text": "[\ud83d\udc4e] answer two" - }, { - id: "3", - "org.matrix.msc1767.text": "answer three" - }] - }, - "org.matrix.msc1767.text": "only one answer allowed!\n1. [\ud83d\udc4d] answer one\n2. [\ud83d\udc4e] answer two\n3. answer three" - }]) -}) - -test("message2event: multiple-choice poll", async t => { - const events = await messageToEvent(data.message.poll_multiple_choice, data.guild.general, {}) - t.deepEqual(events, [{ - $type: "org.matrix.msc3381.poll.start", - "org.matrix.msc3381.poll.start": { - question: { - "org.matrix.msc1767.text": "more than one answer allowed", - body: "more than one answer allowed", - msgtype: "m.text" - }, - kind: "org.matrix.msc3381.poll.disclosed", // Discord always lets you see results, so keeping this consistent with that. - max_selections: 3, - answers: [{ - id: "1", - "org.matrix.msc1767.text": "[😭] no" - }, { - id: "2", - "org.matrix.msc1767.text": "oh no" - }, { - id: "3", - "org.matrix.msc1767.text": "oh noooooo" - }] - }, - "org.matrix.msc1767.text": "more than one answer allowed\n1. [😭] no\n2. oh no\n3. oh noooooo" - }]) -}) - -test("message2event: smalltext from regular user", async t => { - const events = await messageToEvent({ - content: "-# hmm", - author: { - bot: false - } - }) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - "m.mentions": {}, - body: "...hmm" - }]) -}) diff --git a/src/d2m/converters/pins-to-list.js b/src/d2m/converters/pins-to-list.js index 5a33c7c..3e890ea 100644 --- a/src/d2m/converters/pins-to-list.js +++ b/src/d2m/converters/pins-to-list.js @@ -3,11 +3,10 @@ const {select} = require("../../passthrough") /** - * @param {import("discord-api-types/v10").RESTGetAPIChannelMessagesPinsResult} pins + * @param {import("discord-api-types/v10").RESTGetAPIChannelPinsResult} pins * @param {{"m.room.pinned_events/"?: {pinned?: string[]}}} kstate */ function pinsToList(pins, kstate) { - /** Most recent last. */ let alreadyPinned = kstate["m.room.pinned_events/"]?.pinned || [] // If any of the already pinned messages are bridged messages then remove them from the already pinned list. @@ -16,13 +15,13 @@ function pinsToList(pins, kstate) { // * Matrix-only unbridged messages that are pinned will remain pinned. alreadyPinned = alreadyPinned.filter(event_id => { const messageID = select("event_message", "message_id", {event_id}).pluck().get() - return !messageID || pins.items.find(m => m.message.id === messageID) // if it is bridged then remove it from the filter + return !messageID || pins.find(m => m.id === messageID) // if it is bridged then remove it from the filter }) /** @type {string[]} */ const result = [] - for (const pin of pins.items) { - const eventID = select("event_message", "event_id", {message_id: pin.message.id, part: 0}).pluck().get() + for (const message of pins) { + const eventID = select("event_message", "event_id", {message_id: message.id, part: 0}).pluck().get() if (eventID && !alreadyPinned.includes(eventID)) result.push(eventID) } result.reverse() diff --git a/src/d2m/converters/pins-to-list.test.js b/src/d2m/converters/pins-to-list.test.js index 571735e..d0657cb 100644 --- a/src/d2m/converters/pins-to-list.test.js +++ b/src/d2m/converters/pins-to-list.test.js @@ -1,7 +1,6 @@ const {test} = require("supertape") const data = require("../../../test/data") const {pinsToList} = require("./pins-to-list") -const mixin = require("@cloudrac3r/mixin-deep") test("pins2list: converts known IDs, ignores unknown IDs", t => { const result = pinsToList(data.pins.faked, {}) @@ -47,9 +46,7 @@ test("pins2list: already pinned unknown items are not moved", t => { }) test("pins2list: bridged messages can be unpinned", t => { - const shortPins = mixin({}, data.pins.faked) - shortPins.items = shortPins.items.slice(0, -2) - const result = pinsToList(shortPins, { + const result = pinsToList(data.pins.faked.slice(0, -2), { "m.room.pinned_events/": { pinned: [ "$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA", diff --git a/src/d2m/converters/remove-reaction.js b/src/d2m/converters/remove-reaction.js index 4ca22b6..caa96d1 100644 --- a/src/d2m/converters/remove-reaction.js +++ b/src/d2m/converters/remove-reaction.js @@ -5,8 +5,8 @@ const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const {discord, sync, select} = passthrough -/** @type {import("../../matrix/utils")} */ -const utils = sync.require("../../matrix/utils") +/** @type {import("../../m2d/converters/utils")} */ +const utils = sync.require("../../m2d/converters/utils") /** * @typedef ReactionRemoveRequest diff --git a/src/d2m/converters/thread-to-announcement.js b/src/d2m/converters/thread-to-announcement.js index 575b3c5..98b8f12 100644 --- a/src/d2m/converters/thread-to-announcement.js +++ b/src/d2m/converters/thread-to-announcement.js @@ -4,8 +4,8 @@ const assert = require("assert").strict const passthrough = require("../../passthrough") const {discord, sync, db, select} = passthrough -/** @type {import("../../matrix/utils")} */ -const mxUtils = sync.require("../../matrix/utils") +/** @type {import("../../m2d/converters/utils")} */ +const mxUtils = sync.require("../../m2d/converters/utils") const {reg} = require("../../matrix/read-registration.js") const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) diff --git a/src/d2m/converters/thread-to-announcement.test.js b/src/d2m/converters/thread-to-announcement.test.js index 3286f62..3d5d1eb 100644 --- a/src/d2m/converters/thread-to-announcement.test.js +++ b/src/d2m/converters/thread-to-announcement.test.js @@ -2,7 +2,6 @@ const {test} = require("supertape") const {threadToAnnouncement} = require("./thread-to-announcement") const data = require("../../../test/data") const Ty = require("../../types") -const {mockGetEffectivePower} = require("../../matrix/utils.test") /** * @param {string} roomID @@ -31,7 +30,13 @@ function mockGetEvent(t, roomID_in, eventID_in, outer) { } const viaApi = { - getEffectivePower: mockGetEffectivePower(), + async getStateEvent(roomID, type, key) { + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, async getJoinedMembers(roomID) { return { joined: { diff --git a/src/d2m/converters/user-to-mxid.js b/src/d2m/converters/user-to-mxid.js index 7705aff..c011b92 100644 --- a/src/d2m/converters/user-to-mxid.js +++ b/src/d2m/converters/user-to-mxid.js @@ -20,10 +20,10 @@ const SPECIAL_USER_MAPPINGS = new Map([ function downcaseUsername(user) { // First, try to convert the username to the set of allowed characters let downcased = user.username.toLowerCase() - // spaces and slashes to underscores... - .replace(/[ /]/g, "_") + // spaces to underscores... + .replace(/ /g, "_") // remove disallowed characters... - .replace(/[^a-z0-9._=-]*/g, "") + .replace(/[^a-z0-9._=/-]*/g, "") // remove leading and trailing dashes and underscores... .replace(/(?:^[_-]*|[_-]*$)/g, "") // If requested, also make the Discord user ID part of the username @@ -99,16 +99,17 @@ function webhookAuthorToFakeUserID(author) { return `webhook_${downcased}` } -function isWebhookUserID(userID) { - return userID.match(/^webhook_[a-z90-9._=/-]+$/) -} - /** * @param {Ty.WebhookAuthor} author * @returns {string} */ function webhookAuthorToSimName(author) { - assert(!SPECIAL_USER_MAPPINGS.has(author.id), "Special users should have followed the other code path.") + if (SPECIAL_USER_MAPPINGS.has(author.id)) { + const error = new Error("Special users should have followed the other code path.") + // @ts-ignore + error.author = author + throw error + } // 1. Is sim user already registered? const fakeUserID = webhookAuthorToFakeUserID(author) @@ -131,4 +132,3 @@ function webhookAuthorToSimName(author) { module.exports.userToSimName = userToSimName module.exports.webhookAuthorToFakeUserID = webhookAuthorToFakeUserID module.exports.webhookAuthorToSimName = webhookAuthorToSimName -module.exports.isWebhookUserID = isWebhookUserID diff --git a/src/d2m/converters/user-to-mxid.test.js b/src/d2m/converters/user-to-mxid.test.js index f8cf16a..86f151b 100644 --- a/src/d2m/converters/user-to-mxid.test.js +++ b/src/d2m/converters/user-to-mxid.test.js @@ -2,7 +2,7 @@ const {test} = require("supertape") const tryToCatch = require("try-to-catch") const assert = require("assert") const data = require("../../../test/data") -const {userToSimName, webhookAuthorToSimName} = require("./user-to-mxid") +const {userToSimName} = require("./user-to-mxid") test("user2name: cannot create user for a webhook", async t => { const [error] = await tryToCatch(() => userToSimName({discriminator: "0000"})) @@ -21,12 +21,8 @@ test("user2name: works on single emoji at the end", t => { t.equal(userToSimName({username: "Melody 🎵", discriminator: "2192"}), "melody") }) -test("user2name: works on really weird name", t => { - t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7") -}) - -test("user2name: treats slashes", t => { - t.equal(userToSimName({username: "Evil Lillith (she/her)", discriminator: "5892"}), "evil_lillith_she_her") +test("user2name: works on crazy name", t => { + t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7//") }) test("user2name: adds discriminator if name is unavailable (old tag format)", t => { @@ -49,17 +45,10 @@ test("user2name: works on special user", t => { t.equal(userToSimName(data.user.clyde_ai), "clyde_ai") }) -test("webhook author: can generate sim names", t => { - t.equal(webhookAuthorToSimName({ - username: "Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆", - avatar: null, - id: "123" - }), "webhook_cadence_maid_of_creation_eye_of_clarity_empress_of_hope") -}) - test("user2name: includes ID if requested in config", t => { const {reg} = require("../../matrix/read-registration") reg.ooye.include_user_id_in_mxid = true t.equal(userToSimName({username: "Harry Styles!", discriminator: "0001", id: "123456"}), "123456_harry_styles") - t.equal(userToSimName({username: "f***", discriminator: "0001", id: "123456"}), "123456_f") + t.equal(userToSimName({username: "f***", discriminator: "0001", id: "123456"}), "123456_f") + reg.ooye.include_user_id_in_mxid = false }) diff --git a/src/d2m/discord-client.js b/src/d2m/discord-client.js index 7b0fcf8..c84b466 100644 --- a/src/d2m/discord-client.js +++ b/src/d2m/discord-client.js @@ -23,7 +23,7 @@ class DiscordClient { /** @type {import("cloudstorm").IClientOptions["intents"]} */ const intents = [ "DIRECT_MESSAGES", "DIRECT_MESSAGE_REACTIONS", "DIRECT_MESSAGE_TYPING", - "GUILDS", "GUILD_EMOJIS_AND_STICKERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "GUILD_MESSAGE_TYPING", "GUILD_WEBHOOKS", "GUILD_MESSAGE_POLLS", + "GUILDS", "GUILD_EMOJIS_AND_STICKERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "GUILD_MESSAGE_TYPING", "GUILD_WEBHOOKS", "MESSAGE_CONTENT" ] if (reg.ooye.receive_presences !== false) intents.push("GUILD_PRESENCES") @@ -31,6 +31,7 @@ class DiscordClient { this.snow = new SnowTransfer(discordToken) this.cloud = new CloudStorm(discordToken, { shards: [0], + reconnect: true, snowtransferInstance: this.snow, intents, ws: { diff --git a/src/d2m/discord-packets.js b/src/d2m/discord-packets.js index 8cf2fde..017d50e 100644 --- a/src/d2m/discord-packets.js +++ b/src/d2m/discord-packets.js @@ -6,6 +6,10 @@ const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../passthrough") const {sync, db} = passthrough +function populateGuildID(guildID, channelID) { + db.prepare("UPDATE channel_room SET guild_id = ? WHERE channel_id = ?").run(guildID, channelID) +} + const utils = { /** * @param {import("./discord-client")} client @@ -37,17 +41,18 @@ const utils = { channel.guild_id = message.d.id arr.push(channel.id) client.channels.set(channel.id, channel) + populateGuildID(message.d.id, channel.id) } for (const thread of message.d.threads || []) { // @ts-ignore thread.guild_id = message.d.id arr.push(thread.id) client.channels.set(thread.id, thread) + populateGuildID(message.d.id, thread.id) } if (listen === "full") { try { - interactions.registerInteractions() await eventDispatcher.checkMissedExpressions(message.d) await eventDispatcher.checkMissedPins(client, message.d) await eventDispatcher.checkMissedMessages(client, message.d) @@ -107,6 +112,7 @@ const utils = { } else if (message.t === "THREAD_CREATE") { client.channels.set(message.d.id, message.d) if (message.d["guild_id"]) { + populateGuildID(message.d["guild_id"], message.d.id) const channels = client.guildChannelMap.get(message.d["guild_id"]) if (channels && !channels.includes(message.d.id)) channels.push(message.d.id) } @@ -134,6 +140,7 @@ const utils = { } else if (message.t === "CHANNEL_CREATE") { client.channels.set(message.d.id, message.d) if (message.d["guild_id"]) { // obj[prop] notation can be used to access a property without typescript complaining that it doesn't exist on all values something can have + populateGuildID(message.d["guild_id"], message.d.id) const channels = client.guildChannelMap.get(message.d["guild_id"]) if (channels && !channels.includes(message.d.id)) channels.push(message.d.id) } diff --git a/src/d2m/event-dispatcher.js b/src/d2m/event-dispatcher.js index 01bbc67..49352d7 100644 --- a/src/d2m/event-dispatcher.js +++ b/src/d2m/event-dispatcher.js @@ -32,12 +32,8 @@ const speedbump = sync.require("./actions/speedbump") const retrigger = sync.require("./actions/retrigger") /** @type {import("./actions/set-presence")} */ const setPresence = sync.require("./actions/set-presence") -/** @type {import("./actions/poll-vote")} */ -const vote = sync.require("./actions/poll-vote") /** @type {import("../m2d/event-dispatcher")} */ const matrixEventDispatcher = sync.require("../m2d/event-dispatcher") -/** @type {import("../discord/interactions/matrix-info")} */ -const matrixInfoInteraction = sync.require("../discord/interactions/matrix-info") const {Semaphore} = require("@chriscdn/promise-semaphore") const checkMissedPinsSema = new Semaphore() @@ -51,15 +47,13 @@ module.exports = { * @param {import("cloudstorm").IGatewayMessage} gatewayMessage */ async onError(client, e, gatewayMessage) { - if (gatewayMessage.t === "TYPING_START") return - - matrixEventDispatcher.printError(gatewayMessage.t, "Discord", e, gatewayMessage) - const channelID = gatewayMessage.d["channel_id"] if (!channelID) return const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() if (!roomID) return + if (gatewayMessage.t === "TYPING_START") return + await matrixEventDispatcher.sendError(roomID, "Discord", gatewayMessage.t, e, gatewayMessage) }, @@ -73,7 +67,7 @@ module.exports = { async checkMissedMessages(client, guild) { if (guild.unavailable) return const bridgedChannels = select("channel_room", "channel_id").pluck().all() - const preparedExists = from("message_room").join("historical_channel_room", "historical_room_index").pluck("message_id").and("WHERE reference_channel_id = ? LIMIT 1").prepare() + const preparedExists = db.prepare("SELECT channel_id FROM message_channel WHERE channel_id = ? LIMIT 1") const preparedGet = select("event_message", "event_id", {}, "WHERE message_id = ?").pluck() /** @type {(DiscordTypes.APIChannel & {type: DiscordTypes.GuildChannelType})[]} */ let channels = [] @@ -93,7 +87,7 @@ module.exports = { const member = guild.members.find(m => m.user?.id === client.user.id) if (!member) return if (!("permission_overwrites" in channel)) continue - const permissions = dUtils.getPermissions(guild.id, member.roles, guild.roles, client.user.id, channel.permission_overwrites) + const permissions = dUtils.getPermissions(member.roles, guild.roles, client.user.id, channel.permission_overwrites) if (!dUtils.hasAllPermissions(permissions, ["ViewChannel", "ReadMessageHistory"])) continue // We don't have permission to look back in this channel /** More recent messages come first. */ @@ -152,7 +146,7 @@ module.exports = { const lastPin = updatePins.convertTimestamp(channel.last_pin_timestamp) // Permissions check - const permissions = dUtils.getPermissions(guild.id, member.roles, guild.roles, client.user.id, channel.permission_overwrites) + const permissions = dUtils.getPermissions(member.roles, guild.roles, client.user.id, channel.permission_overwrites) if (!dUtils.hasAllPermissions(permissions, ["ViewChannel", "ReadMessageHistory"])) continue // We don't have permission to look up the pins in this channel const row = select("channel_room", ["room_id", "last_bridged_pin_timestamp"], {channel_id: channel.id}).get() @@ -196,21 +190,6 @@ module.exports = { await createSpace.syncSpace(guild) }, - /** - * @param {import("./discord-client")} client - * @param {DiscordTypes.GatewayGuildRoleUpdateDispatchData} data - */ - async GUILD_ROLE_UPDATE(client, data) { - const guild = client.guilds.get(data.guild_id) - if (!guild) return - const spaceID = select("guild_space", "space_id", {guild_id: data.guild_id}).pluck().get() - if (!spaceID) return - - if (data.role.id === data.guild_id) { // @everyone role changed - find a way to do this more efficiently in the future to handle many role updates - await createSpace.syncSpaceFully(guild) - } - }, - /** * @param {import("./discord-client")} client * @param {DiscordTypes.GatewayChannelUpdateDispatchData} channelOrThread @@ -250,7 +229,7 @@ module.exports = { const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() if (!roomID) return // channel wasn't being bridged in the first place // @ts-ignore - await createRoom.unbridgeChannel(channel, guildID) + await createRoom.unbridgeDeletedChannel(channel, guildID) }, /** @@ -291,7 +270,7 @@ module.exports = { // Based on looking at data they've sent me over the gateway, this is the best way to check for meaningful changes. // If the message content is a string then it includes all interesting fields and is meaningful. // Otherwise, if there are embeds, then the system generated URL preview embeds. - if (!(typeof data.content === "string" || "embeds" in data || "components" in data)) return + if (!(typeof data.content === "string" || "embeds" in data)) return if (dUtils.isEphemeralMessage(data)) return // Ephemeral messages are for the eyes of the receiver only! @@ -299,10 +278,8 @@ module.exports = { const {affected, row} = await speedbump.maybeDoSpeedbump(data.channel_id, data.id) if (affected) return - if (!row) { - // Check that the sending-to room exists, and deal with Eventual Consistency(TM) - if (retrigger.eventNotFoundThenRetrigger(data.id, module.exports.MESSAGE_UPDATE, client, data)) return - } + // Check that the sending-to room exists, and deal with Eventual Consistency(TM) + if (retrigger.eventNotFoundThenRetrigger(data.id, module.exports.MESSAGE_UPDATE, client, data)) return /** @type {DiscordTypes.GatewayMessageCreateDispatchData} */ // @ts-ignore @@ -322,16 +299,7 @@ module.exports = { */ async MESSAGE_REACTION_ADD(client, data) { if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. - if (data.emoji.name === "❓" && select("event_message", "message_id", {message_id: data.message_id, source: 0, part: 0}).get()) { // source 0 = matrix - const guild_id = data.guild_id ?? client.channels.get(data.channel_id)?.["guild_id"] - await Promise.all([ - client.snow.channel.deleteReaction(data.channel_id, data.message_id, data.emoji.name).catch(() => {}), - // @ts-ignore - this is all you need for it to do a matrix-side lookup - matrixInfoInteraction.dm({guild_id, data: {target_id: data.message_id}, member: {user: {id: data.user_id}}}) - ]) - } else { - await addReaction.addReaction(data) - } + await addReaction.addReaction(data) }, /** @@ -391,20 +359,6 @@ module.exports = { await createSpace.syncSpaceExpressions(data, false) }, - /** - * @param {import("./discord-client")} client - * @param {DiscordTypes.GatewayMessagePollVoteDispatchData} data - */ - async MESSAGE_POLL_VOTE_ADD(client, data) { - if (retrigger.eventNotFoundThenRetrigger(data.message_id, module.exports.MESSAGE_POLL_VOTE_ADD, client, data)) return - await vote.addVote(data) - }, - - async MESSAGE_POLL_VOTE_REMOVE(client, data) { - if (retrigger.eventNotFoundThenRetrigger(data.message_id, module.exports.MESSAGE_POLL_VOTE_REMOVE, client, data)) return - await vote.removeVote(data) - }, - /** * @param {import("./discord-client")} client * @param {DiscordTypes.GatewayPresenceUpdateDispatchData} data diff --git a/src/db/migrations/0017-analyze.sql b/src/db/migrations/0017-analyze.sql new file mode 100644 index 0000000..802fca2 --- /dev/null +++ b/src/db/migrations/0017-analyze.sql @@ -0,0 +1,225 @@ +-- https://www.sqlite.org/lang_analyze.html + +BEGIN TRANSACTION; + +ANALYZE sqlite_schema; + +DELETE FROM "sqlite_stat1"; +INSERT INTO "sqlite_stat1" ("tbl","idx","stat") VALUES ('sim','sim','625 1'), + ('reaction','reaction','3242 1'), + ('channel_room','channel_room','389 1'), + ('channel_room','sqlite_autoindex_channel_room_1','389 1'), + ('media_proxy','media_proxy','5068 1'), + ('sim_proxy','sim_proxy','36 1'), + ('webhook','webhook','155 1'), + ('member_cache','member_cache','784 3 1'), + ('member_power','member_power','1 1 1'), + ('file','file','21862 1'), + ('message_channel','message_channel','366884 1'), + ('lottie','lottie','19 1'), + ('event_message','event_message','382920 1 1'), + ('migration',NULL,'1'), + ('sim_member','sim_member','2871 7 1'), + ('guild_space','guild_space','32 1'), + ('guild_active','guild_active','34 1'), + ('emoji','emoji','2563 1'), + ('auto_emoji','auto_emoji','3 1'); + +DELETE FROM "sqlite_stat4"; +INSERT INTO "sqlite_stat4" ("tbl","idx","neq","nlt","ndlt","sample") VALUES ('sim','sim','1','69','69',X'0231313137363631373038303932333039353039'), + ('sim','sim','1','139','139',X'0231313530383936363934333439373931323332'), + ('sim','sim','1','209','209',X'0231323231383737363334373737323139303732'), + ('sim','sim','1','279','279',X'0231333039313431353735353334313136383636'), + ('sim','sim','1','349','349',X'0231333935343433383235363034313635363434'), + ('sim','sim','1','419','419',X'0231353335363239373830383338353134373030'), + ('sim','sim','1','489','489',X'0231363930333339333730353930363636383034'), + ('sim','sim','1','559','559',X'0231383535353736303637393137323137383133'), + ('reaction','reaction','1','360','360',X'020699d5faceefb5fb4f'), + ('reaction','reaction','1','721','721',X'0206b61095e98b6b2fb1'), + ('reaction','reaction','1','1082','1082',X'0206d1dcb418603a5eaa'), + ('reaction','reaction','1','1443','1443',X'0206ef9fc42b9df746ad'), + ('reaction','reaction','1','1804','1804',X'02060f38c1f98f130605'), + ('reaction','reaction','1','2165','2165',X'02062b53df6dab7b1067'), + ('reaction','reaction','1','2526','2526',X'020645dd7e7f60c4aac7'), + ('reaction','reaction','1','2887','2887',X'0206658d2fe735805979'), + ('channel_room','channel_room','1','43','43',X'023331313434393131333330393139333231363330'), + ('channel_room','channel_room','1','87','87',X'023331313835343033343830303934303335393738'), + ('channel_room','channel_room','1','131','131',X'023331323139353036353836343139303638393839'), + ('channel_room','channel_room','1','175','175',X'023331323336353538333034323331303334393630'), + ('channel_room','channel_room','1','219','219',X'023331323933373932323135333930323234343235'), + ('channel_room','channel_room','1','263','263',X'023331333333323139363936393333323038303937'), + ('channel_room','channel_room','1','307','307',X'0231343835363635393733363433333738363938'), + ('channel_room','channel_room','1','351','351',X'0231373039303432313039353632323234363731'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','6 6','6 6',X'034b3321416a6c4c49464e6248646474424a6d4d73503a636164656e63652e6d6f6531313531333434383735363139343833373233'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','34 34','34 34',X'034b3321474b4a63424a6b527a47634e4855686c50613a636164656e63652e6d6f6531303237393433323532323237323630343637'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','43 43','43 43',X'034b3121484b50534d62736d694673506d6268414f513a636164656e63652e6d6f65313931343837343839393433343034353434'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','58 58','58 58',X'034b33214a4479425a685545706874784f6e6f6569513a636164656e63652e6d6f6531323937323836373434353633313236333532'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','87 87','87 87',X'034b33214e544d724e686e715271695755654d494d523a636164656e63652e6d6f6531323235323434353738393939373031353536'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','108 108','108 108',X'034b332151444e44796656674e7657565345656876713a636164656e63652e6d6f6531313432333134303935353535363435343830'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','131 131','131 131',X'034b3121544171536b575752654b43506f584c6a75483a636164656e63652e6d6f65383737303730363531343733363631393532'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','175 175','175 175',X'034b3321594249486864714e697255585941587845563a636164656e63652e6d6f6531323335303831373939353936373639333730'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','177 177','177 177',X'034b3321594b46454e79716667696951686956496b533a636164656e63652e6d6f6531323934363237303431343530333933373034'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','186 186','186 186',X'034b3321596f54644f55766a53765349767266716c653a636164656e63652e6d6f6531323734313936373733383435333430323933'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','202 202','202 202',X'034b3121625877616673695372655647676470535a463a636164656e63652e6d6f65373339303137363739373936343336393932'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','208 208','208 208',X'034b3321634a4b6843764943795377717a47634551423a636164656e63652e6d6f6531323732363632303331323238373331343834'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','219 219','219 219',X'034b3121656455786a56647a6755765844554951434b3a636164656e63652e6d6f65343937313631333530393334353630373738'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','242 242','242 242',X'034b31216a4d746e6e6f51414e4278466a486458494d3a636164656e63652e6d6f65373634353135323932303539323731313939'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','263 263','263 263',X'034b31216c7a776870666a5a6e59797468656a7453483a636164656e63652e6d6f65383838343831373132383438343030343534'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','264 264','264 264',X'034b33216d454c5846716a426958726d7558796943723a636164656e63652e6d6f6531313936393134373631303430393234373432'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','268 268','268 268',X'034b33216d557765577571546761574a767769576a653a636164656e63652e6d6f6531323936373131393236333032333830303834'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','291 291','291 291',X'034b3321704761494e45534643587a634e42497a724e3a636164656e63652e6d6f6531303237343531333333333533313532353232'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','306 306','306 306',X'034b3321717646656248564f4b6876454e54494563763a636164656e63652e6d6f6531323737373238383139323232303230313436'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','307 307','307 307',X'034b332171767370666d716f476449634a66794c506c3a636164656e63652e6d6f6531323936393138333638393539343633343735'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','351 351','351 351',X'034b3321774e7a7741724a47796f4c5168426f544e4b3a636164656e63652e6d6f6531303238303436373930333435333739383930'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','368 368','368 368',X'034b3321794e6d504c7765654a69756570725a677a733a636164656e63652e6d6f6531323531393631373233373731313632363234'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','376 376','376 376',X'034b3121796a4879795772466f704c66646878564e423a636164656e63652e6d6f65333336313537353037303734353233313336'), + ('channel_room','sqlite_autoindex_channel_room_1','1 1','379 379','379 379',X'034b31217a4c4f6b62766b44587551465948594555673a636164656e63652e6d6f65393933383838313433373030393330363331'), + ('media_proxy','media_proxy','1','563','563',X'02069e6054680b610946'), + ('media_proxy','media_proxy','1','1127','1127',X'0206bb489b717c9320e4'), + ('media_proxy','media_proxy','1','1691','1691',X'0206d75f602775b7a27c'), + ('media_proxy','media_proxy','1','2255','2255',X'0206f2c705ddca4e2b14'), + ('media_proxy','media_proxy','1','2819','2819',X'02061060db7a5151967b'), + ('media_proxy','media_proxy','1','3383','3383',X'02062cc47366f7550d22'), + ('media_proxy','media_proxy','1','3947','3947',X'020647d275ec0d781fc7'), + ('media_proxy','media_proxy','1','4511','4511',X'02066402024a7ea38249'), + ('sim_proxy','sim_proxy','1','4','4',X'025531316564343731342d636635652d346333372d393331382d376136353266383732636634'), + ('sim_proxy','sim_proxy','1','9','9',X'025533346636333932642d323263372d346337382d393063372d326536323734313535613266'), + ('sim_proxy','sim_proxy','1','14','14',X'025535396662363131392d626133392d346565382d393738612d386432376366303631393633'), + ('sim_proxy','sim_proxy','1','19','19',X'025539373066366536332d646234632d346531342d383063362d336639343938643961363665'), + ('sim_proxy','sim_proxy','1','24','24',X'025561636231613335642d313336662d343362332d626365622d326566646634616265306436'), + ('sim_proxy','sim_proxy','1','29','29',X'025563316635623735392d336136342d343633342d623634632d643461656436316539656632'), + ('sim_proxy','sim_proxy','1','34','34',X'025566323230373135632d633436332d343532622d626233612d373662646662306365353537'), + ('webhook','webhook','1','17','17',X'023331313532383834313435343038373230393936'), + ('webhook','webhook','1','35','35',X'023331313939303936333434333830343631313138'), + ('webhook','webhook','1','53','53',X'023331323331383036353337373032353736313938'), + ('webhook','webhook','1','71','71',X'023331323933373836383939343238383036363536'), + ('webhook','webhook','1','89','89',X'023331333132363031353130363535353537373132'), + ('webhook','webhook','1','107','107',X'0231323937323734313733303636333133373339'), + ('webhook','webhook','1','125','125',X'0231353239313736313536333938363832313137'), + ('webhook','webhook','1','143','143',X'0231363837303238373334333232313437333434'), + ('member_cache','member_cache','4 1','73 74','48 74',X'034b3921496f4866536e67625a6762747061747a494e3a636164656e63652e6d6f65406875636b6c65746f6e3a636164656e63652e6d6f65'), + ('member_cache','member_cache','2 1','86 87','57 87',X'034b2d214b5169714663546e764f6f4f424475746a7a3a636164656e63652e6d6f6540726e6c3a636164656e63652e6d6f65'), + ('member_cache','member_cache','4 1','101 104','68 104',X'034b43214e446249714e704a795076664b526e4e63723a636164656e63652e6d6f6540776f756e6465645f77617272696f723a6d61747269782e6f7267'), + ('member_cache','member_cache','4 1','110 113','73 113',X'034b3b214f485844457370624d485348716c4445614f3a636164656e63652e6d6f6540717561647261646963616c3a6d61747269782e6f7267'), + ('member_cache','member_cache','5 1','171 175','111 175',X'034b3b215450616f6a5454444446444847776c7276743a636164656e63652e6d6f6540766962656973766572796f3a6d61747269782e6f7267'), + ('member_cache','member_cache','39 1','180 208','116 208',X'034b4d2154716c79516d69667847556767456d64424e3a636164656e63652e6d6f6540726f626c6b796f6772653a6372616674696e67636f6d72616465732e6e6574'), + ('member_cache','member_cache','4 1','231 231','126 231',X'034b3b2156624f77675559777146614e4c5345644e413a636164656e63652e6d6f654061666c6f7765723a73796e646963617465642e676179'), + ('member_cache','member_cache','9 1','262 263','141 263',X'034b3b21594b46454e79716667696951686956496b533a636164656e63652e6d6f654062656e6d61633a636861742e62656e6d61632e78797a'), + ('member_cache','member_cache','3 1','283 283','149 283',X'034b35215a615a4d78456f52724d6d4e49554d79446c3a636164656e63652e6d6f6540636164656e63653a636164656e63652e6d6f65'), + ('member_cache','member_cache','88 1','307 351','166 351',X'034b3b2163427874565278446c5a765356684a58564b3a636164656e63652e6d6f65406a61736b617274683a736c656570696e672e746f776e'), + ('member_cache','member_cache','11 1','408 415','177 415',X'034b5121654856655270706e6c6f57587177704a6e553a636164656e63652e6d6f65406a61636b736f6e6368656e3636363a6a61636b736f6e6368656e3636362e636f6d'), + ('member_cache','member_cache','7 1','423 424','181 424',X'034b4b2165724f7079584e465a486a48724568784e583a636164656e63652e6d6f6540616d796973636f6f6c7a3a6d61747269782e6174697573616d792e636f6d'), + ('member_cache','member_cache','96 1','436 439','187 439',X'034b4b21676865544b5a7451666c444e7070684c49673a636164656e63652e6d6f6540616c65783a73706163652e67616d65727374617665726e2e6f6e6c696e65'), + ('member_cache','member_cache','96 1','436 527','187 527',X'034b3121676865544b5a7451666c444e7070684c49673a636164656e63652e6d6f654078796c6f626f6c3a616d6265722e74656c'), + ('member_cache','member_cache','10 1','546 555','197 555',X'0351312169537958674e7851634575586f587073536e3a707573737468656361742e6f726740797562697175653a6e6f70652e63686174'), + ('member_cache','member_cache','13 1','594 601','224 601',X'034b2b216c7570486a715444537a774f744d59476d493a636164656e63652e6d6f6540656c6c69753a68617368692e7265'), + ('member_cache','member_cache','2 1','614 615','229 615',X'034b2f216d584978494644676c4861734e53427371773a636164656e63652e6d6f654077696e673a666561746865722e6f6e6c'), + ('member_cache','member_cache','4 1','616 619','230 619',X'034b2f216d616767455367755a427147425a74536e723a636164656e63652e6d6f654077696e673a666561746865722e6f6e6c'), + ('member_cache','member_cache','4 1','659 660','259 660',X'034b332172454f73706e5971644f414c4149466e69563a636164656e63652e6d6f6540656c797369613a636164656e63652e6d6f65'), + ('member_cache','member_cache','4 1','699 701','284 701',X'034b3521766e717a56767678534a586c5a504f5276533a636164656e63652e6d6f6540636164656e63653a636164656e63652e6d6f65'), + ('member_cache','member_cache','1 1','703 703','285 703',X'034b3521767165714c474851616842464a56566779483a636164656e63652e6d6f654063696465723a6361746769726c2e636c6f7564'), + ('member_cache','member_cache','4 1','705 705','287 705',X'034b35217750454472596b77497a6f744e66706e57503a636164656e63652e6d6f6540636164656e63653a636164656e63652e6d6f65'), + ('member_cache','member_cache','35 1','709 709','288 709',X'034b2d2177574f667376757356486f4e4e567242585a3a636164656e63652e6d6f654061613a6361747669626572732e6d65'), + ('member_cache','member_cache','14 1','747 749','291 749',X'034b3721776c534544496a44676c486d42474b7254703a636164656e63652e6d6f654062616461746e616d65733a62616461742e646576'), + ('member_power','member_power','1 1','0 0','0 0',X'03350f40636164656e63653a636164656e63652e6d6f652a'), + ('file','file','1','2429','2429',X'03815f68747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f313039393033313838373530303033343038382f313333313336303134333238333036303833372f50584c5f32303235303132315f3230323934323137372e6a7067'), + ('file','file','1','4859','4859',X'03817568747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f313134353832313533383832323637323436362f313330323232393131303834373936373331352f53637265656e73686f745f32303234313130325f3034313332365f5265646469742e6a7067'), + ('file','file','1','7289','7289',X'03815968747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f313231393439383932363436363636323433302f313239373634363930353038353636313234362f494d475f32303234313032305f3135323230302e6a7067'), + ('file','file','1','9719','9719',X'03814168747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3135393136353731343139343735393638302f313236383537333933363531343337313635392f494d475f353433362e6a7067'), + ('file','file','1','12149','12149',X'03813b68747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3236363736373539303634313233383032372f313237323430333931313939383730313630392f696d6167652e706e67'), + ('file','file','1','14579','14579',X'03816b68747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3539383730363933323736303434343936392f313237373532343532343330383632373437372f45585445524e414c5f454449545f323032345f4d5f64726166745f322e646f6378'), + ('file','file','1','17009','17009',X'03815768747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3635353231363137333639363238363734362f313333333630333132383634313036303938342f323032352d30312d32375f31372e30312e31352e706e67'), + ('file','file','1','19439','19439',X'027f68747470733a2f2f63646e2e646973636f72646170702e636f6d2f656d6f6a69732f313230323936303730343936313931323836322e706e67'), + ('message_channel','message_channel','1','40764','40764',X'023331313630333434353733303030383232383735'), + ('message_channel','message_channel','1','81529','81529',X'023331313830323437393130343238393837343333'), + ('message_channel','message_channel','1','122294','122294',X'023331313938303533383732383337363131363230'), + ('message_channel','message_channel','1','163059','163059',X'023331323237373739373330333839303738303537'), + ('message_channel','message_channel','1','203824','203824',X'023331323437303438333039303031313538373137'), + ('message_channel','message_channel','1','244589','244589',X'023331323635363939353734333034303830303034'), + ('message_channel','message_channel','1','285354','285354',X'023331323835343637363238323434313732383234'), + ('message_channel','message_channel','1','326119','326119',X'023331333038373932333935313031333736353732'), + ('lottie','lottie','1','2','2',X'0231373439303534363630373639323138363331'), + ('lottie','lottie','1','5','5',X'0231373534313037353339323030363731373635'), + ('lottie','lottie','1','8','8',X'0231373936313430363338303933343433303932'), + ('lottie','lottie','1','11','11',X'0231373936313431373032363935343835353030'), + ('lottie','lottie','1','14','14',X'0231383136303837373932323931323832393434'), + ('lottie','lottie','1','17','17',X'0231383233393736313032393736323930383636'), + ('event_message','event_message','11 1','14788 14796','14356 14796',X'03336531313532303033373639343137303839303434246d44714a474b3530424a715170394273684e534d64365f3768494354776e70362d793130786d6669766563'), + ('event_message','event_message','11 1','33806 33815','32914 33815',X'033365313135373630333638373035373836363736322459526f7139484b376e55677a397668796f6e3053424a49497978497a5750734e4f6e39756f765644664d45'), + ('event_message','event_message','1 1','42546 42546','41335 42546',X'03336531313630363236383737353835363938393237245033436e4f6d6a35462d6939454e4e79586b70796f5679306b74324a654464764276326e746d33566a4355'), + ('event_message','event_message','2 1','85093 85093','81999 85093',X'0333653131383039393939363531323930343831303424496748666562784533746e623047412d534f7176594a354e4e55385735706c68523159676854636a554734'), + ('event_message','event_message','11 1','116157 116165','111525 116165',X'03336531313933363837333730363137333237363536245a32305734766c737079566e387a6e2d526a6f64694a51745f5a7851644f5f33744e415169453755356477'), + ('event_message','event_message','1 1','127640 127640','122328 127640',X'0333653132303132353637313733373633303332323624702d626f415672476a4b45327a7158664f3738387a5a597a376a42624648717431334d386464705467476f'), + ('event_message','event_message','16 1','140270 140281','134379 140281',X'03336531323039333735363534373138343830343235246c374a4f7543526b7756306d627a69356e5843496b4538416a6951374f67473455456c2d7053445a516649'), + ('event_message','event_message','11 1','162065 162071','154933 162071',X'0333653132323434383135393033313937373537373424674c77513179796e4b6d5859496b5a597a4a55627a66557a55552d714c4b5f524f454e4250325f6e44766b'), + ('event_message','event_message','1 1','170187 170187','162530 170187',X'0333653132323937373533363839343532373038333424656c6c76416a544a5847627936767249767470677a555572787231716f5a75536e50525f474b4e35455945'), + ('event_message','event_message','11 1','178736 178736','170762 178736',X'03336531323333353238303533323338333337353537242d39304668552d36455373594b6435484d7237666d6a414a5f6a576149616e356c4776384e655436564959'), + ('event_message','event_message','10 1','180317 180325','172228 180325',X'03336531323334363237303030393333383130323637246a4679416449665a4f54432d2d735971715472473735374c7a50504f34386439657963485477686d797751'), + ('event_message','event_message','1 1','212734 212734','203278 212734',X'03336531323438303131373930303633373637363734246557493950456f4d576b614b4d33416a7030782d6d455f6c4b4a6c495f6d6d44686f6379464b5170534f59'), + ('event_message','event_message','11 1','228100 228103','217856 228103',X'033365313235333734353736363733373132313336322447326a7a746e5977716a3676304a7a4168576e30725950596470667a5f496f76426771574a4876434c516b'), + ('event_message','event_message','11 1','240172 240181','229123 240181',X'033365313235393239383538323233303630313735382472635679454c5a76647453547a37366f3669434a4f614a316a756f683835535778494d546c5072517a676f'), + ('event_message','event_message','10 1','240259 240264','229132 240264',X'0333653132353933303030343035333535373235323024535849376a465f696e71424d714c4f564b347659746852644b31724747502d435a5f355a354f6b774e3751'), + ('event_message','event_message','2 1','255280 255281','243230 255281',X'03336531323636373837343338353137343234313738246e6e4e576f526a54495757723441463770625454746b7a73784c4d336b312d6d6645665031496b43586e59'), + ('event_message','event_message','1 1','297828 297828','283436 297828',X'0333653132383637303937353334363834323032313024544342465970356f39767a2d4f6b2d33654f4e433772426d354966615934476b48536e58445257474f4767'), + ('event_message','event_message','1 1','340375 340375','323489 340375',X'033365313330393336363936343530353934303030392431664536386d2d50546e786d5474345458584c35754847594139353779396c76582d50797150496d395f30'), + ('event_message','event_message','11 1','343791 343799','326730 343799',X'03336531333131353731333337393331363537323737246731767241315269725951592d3933304f6973587142372d6961686e34684e492d6462374952714176616b'), + ('event_message','event_message','10 1','363207 363216','344868 363216',X'0333653133323434393732323633393438393834393524784f78636e4749364269545941734d7a4f7557316f526e68356b675378544e30466442386a3037695f4f67'), + ('event_message','event_message','10 1','363219 363219','344871 363219',X'033365313332343439373532363733323039393732352432586f7938567937643843375a47767472657966326b4c4f4159397546386b4755364c3642435f446b6d77'), + ('event_message','event_message','10 1','363340 363343','344918 363343',X'03336531333234353037313732333634373530393830244f46645731396649534176706d34646c36367a2d5543337236432d436354506e34752d63444f7036733345'), + ('event_message','event_message','11 1','369452 369456','350712 369456',X'0333653133323736353831313733343439323336383024574d774330644574417277375f4562554a534465546532577174506d3747584347774570646c4f79326d30'), + ('event_message','event_message','10 1','372353 372356','353425 372356',X'0333653133323933313737353039313234353035373224366259364d313472667163486d5854476349716d4a4366467471796839794472375a6a487463715a6d4f34'), + ('sim_member','sim_member','225 1','0 12','0 12',X'034b4721414956694e775a64636b4652764c4f4567433a636164656e63652e6d6f65405f6f6f79655f616b6972615f6e6965723a636164656e63652e6d6f65'), + ('sim_member','sim_member','2 1','319 319','14 319',X'034b43214456706f6e54524d56456570486378744c423a636164656e63652e6d6f65405f6f6f79655f656e746f6c6f6d613a636164656e63652e6d6f65'), + ('sim_member','sim_member','68 1','391 440','26 440',X'034b4921457a54624a496c496d45534f746b4e644e4a3a636164656e63652e6d6f65405f6f6f79655f73617475726461797465643a636164656e63652e6d6f65'), + ('sim_member','sim_member','8 1','638 639','59 639',X'034b3f21497a4f675169446e757346516977796d614c3a636164656e63652e6d6f65405f6f6f79655f636f6f6b69653a636164656e63652e6d6f65'), + ('sim_member','sim_member','31 1','743 771','86 771',X'034b49214d5071594e414a62576b72474f544a7461703a636164656e63652e6d6f65405f6f6f79655f746865666f6f6c323239343a636164656e63652e6d6f65'), + ('sim_member','sim_member','26 1','774 787','87 787',X'034b49214d687950614b4250506f496c7365794d6d743a636164656e63652e6d6f65405f6f6f79655f6b79757567727970686f6e3a636164656e63652e6d6f65'), + ('sim_member','sim_member','27 1','877 881','104 881',X'034b45215063734371724f466a48476f41424270414c3a636164656e63652e6d6f65405f6f6f79655f62696c6c795f626f623a636164656e63652e6d6f65'), + ('sim_member','sim_member','16 1','956 959','117 959',X'034b43215158526f4a777a63506d5047546d454b454d3a636164656e63652e6d6f65405f6f6f79655f626f7472616334723a636164656e63652e6d6f65'), + ('sim_member','sim_member','32 1','997 1012','121 1012',X'034b3f215170676c734e587a4c7751594d4c6c734f503a636164656e63652e6d6f65405f6f6f79655f6a75746f6d693a636164656e63652e6d6f65'), + ('sim_member','sim_member','7 1','1274 1279','157 1279',X'034b4121554d6f6e68556765644d47585a78466658753a636164656e63652e6d6f65405f6f6f79655f6d696e696d75733a636164656e63652e6d6f65'), + ('sim_member','sim_member','27 1','1415 1439','188 1439',X'034b3d21595868717249786d586e47736961796a59783a636164656e63652e6d6f65405f6f6f79655f73746161663a636164656e63652e6d6f65'), + ('sim_member','sim_member','16 1','1597 1599','217 1599',X'034b512163466a4479477274466d48796d794c6652453a636164656e63652e6d6f65405f6f6f79655f626f6a61636b5f686f7273656d616e3a636164656e63652e6d6f65'), + ('sim_member','sim_member','27 1','1758 1761','248 1761',X'034b3d2168665a74624d656f5355564e424850736a743a636164656e63652e6d6f65405f6f6f79655f617a7572653a636164656e63652e6d6f65'), + ('sim_member','sim_member','25 1','1865 1886','270 1886',X'034b3f216b4c52714b4b555158636962494d744f706c3a636164656e63652e6d6f65405f6f6f79655f7361796f72693a636164656e63652e6d6f65'), + ('sim_member','sim_member','19 1','1918 1919','276 1919',X'034b3d216b68497350756c465369736d43646c596e493a636164656e63652e6d6f65405f6f6f79655f617a7572653a636164656e63652e6d6f65'), + ('sim_member','sim_member','33 1','1986 2015','286 2015',X'034b3d216d5451744d736a534c4f646c576f7265594d3a636164656e63652e6d6f65405f6f6f79655f73746161663a636164656e63652e6d6f65'), + ('sim_member','sim_member','37 1','2027 2028','289 2028',X'034b4d216d616767455367755a427147425a74536e723a636164656e63652e6d6f65405f6f6f79655f2e7265616c2e706572736f6e2e3a636164656e63652e6d6f65'), + ('sim_member','sim_member','28 1','2117 2130','297 2130',X'034b3f216e4e595a794b6f4e70797859417a50466f733a636164656e63652e6d6f65405f6f6f79655f6a75746f6d693a636164656e63652e6d6f65'), + ('sim_member','sim_member','20 1','2230 2239','310 2239',X'034b41217046504c7270594879487a784e4c69594b413a636164656e63652e6d6f65405f6f6f79655f686578676f61743a636164656e63652e6d6f65'), + ('sim_member','sim_member','30 1','2381 2398','332 2398',X'034b3b21717a44626c4b6c69444c577a52524f6e465a3a636164656e63652e6d6f65405f6f6f79655f6d6e696b3a636164656e63652e6d6f65'), + ('sim_member','sim_member','38 1','2490 2518','344 2518',X'034b3d2173445250714549546e4f4e57474176496b423a636164656e63652e6d6f65405f6f6f79655f727974686d3a636164656e63652e6d6f65'), + ('sim_member','sim_member','11 1','2555 2559','358 2559',X'034b4321746751436d526b426e6474516362687150583a636164656e63652e6d6f65405f6f6f79655f6a6f7365707065793a636164656e63652e6d6f65'), + ('sim_member','sim_member','47 1','2633 2666','377 2666',X'034b4b217750454472596b77497a6f744e66706e57503a636164656e63652e6d6f65405f6f6f79655f6e61706f6c656f6e333038393a636164656e63652e6d6f65'), + ('sim_member','sim_member','52 1','2817 2837','414 2837',X'034b43217a66654e574d744b4f764f48766f727979563a636164656e63652e6d6f65405f6f6f79655f696e736f676e69613a636164656e63652e6d6f65'), + ('guild_space','guild_space','1','3','3',X'0231313132373630363639313738323431303234'), + ('guild_space','guild_space','1','7','7',X'023331313534383638343234373234343633363837'), + ('guild_space','guild_space','1','11','11',X'023331323139303338323637383430393235383138'), + ('guild_space','guild_space','1','15','15',X'023331323839353939363232353930383930313335'), + ('guild_space','guild_space','1','19','19',X'0231323733383737363437323234393935383431'), + ('guild_space','guild_space','1','23','23',X'0231353239313736313536333938363832313135'), + ('guild_space','guild_space','1','27','27',X'0231373535303134333534373334313533383138'), + ('guild_space','guild_space','1','31','31',X'0231393933383838313432343535323130303834'), + ('guild_active','guild_active','1','3','3',X'0231313132373630363639313738323431303234'), + ('guild_active','guild_active','1','7','7',X'023331313534383638343234373234343633363837'), + ('guild_active','guild_active','1','11','11',X'023331323139303338323637383430393235383138'), + ('guild_active','guild_active','1','15','15',X'023331323839353939363232353930383930313335'), + ('guild_active','guild_active','1','19','19',X'023331333333323139363936393333323038303934'), + ('guild_active','guild_active','1','23','23',X'0231343735353939303338353336373434393630'), + ('guild_active','guild_active','1','27','27',X'022f3636313932393535373737343836383438'), + ('guild_active','guild_active','1','31','31',X'0231383737303635303431393930353136373637'), + ('emoji','emoji','1','284','284',X'023331313132323031303430303637303339333332'), + ('emoji','emoji','1','569','569',X'023331323334393032303131393436383630363638'), + ('emoji','emoji','1','854','854',X'0231323735313734373438353034313935303732'), + ('emoji','emoji','1','1139','1139',X'0231333837343730383630303134393737303235'), + ('emoji','emoji','1','1424','1424',X'0231353435363639393734303130383838313932'), + ('emoji','emoji','1','1709','1709',X'0231363339313034333030393139393437323634'), + ('emoji','emoji','1','1994','1994',X'0231373532363932363230303638373832313231'), + ('emoji','emoji','1','2279','2279',X'0231383935343737353331303434363138323430'), + ('auto_emoji','auto_emoji','1','0','0',X'02114c31'), + ('auto_emoji','auto_emoji','1','1','1',X'02114c32'), + ('auto_emoji','auto_emoji','1','2','2',X'020f5f'); + +ANALYZE sqlite_schema; + +COMMIT; diff --git a/src/db/migrations/0026-make-rooms-historical.sql b/src/db/migrations/0026-make-rooms-historical.sql deleted file mode 100644 index ba4775e..0000000 --- a/src/db/migrations/0026-make-rooms-historical.sql +++ /dev/null @@ -1,63 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; - --- *** historical_channel_room *** - -CREATE TABLE "historical_channel_room" ( - "historical_room_index" INTEGER NOT NULL, - "reference_channel_id" TEXT NOT NULL, - "room_id" TEXT NOT NULL UNIQUE, - "upgraded_timestamp" INTEGER NOT NULL, - PRIMARY KEY("historical_room_index" AUTOINCREMENT), - FOREIGN KEY("reference_channel_id") REFERENCES "channel_room"("channel_id") ON DELETE CASCADE -); - -INSERT INTO historical_channel_room (reference_channel_id, room_id, upgraded_timestamp) SELECT channel_id, room_id, 0 FROM channel_room; - --- *** message_channel -> message_room *** - -CREATE TABLE "message_room" ( - "message_id" TEXT NOT NULL, - "historical_room_index" INTEGER NOT NULL, - PRIMARY KEY("message_id"), - FOREIGN KEY("historical_room_index") REFERENCES "historical_channel_room"("historical_room_index") ON DELETE CASCADE -) WITHOUT ROWID; -INSERT INTO message_room (message_id, historical_room_index) SELECT message_id, max(historical_room_index) as historical_room_index FROM message_channel INNER JOIN historical_channel_room ON historical_channel_room.reference_channel_id = message_channel.channel_id GROUP BY message_id; - --- *** event_message *** - -CREATE TABLE "new_event_message" ( - "event_id" TEXT NOT NULL, - "event_type" TEXT, - "event_subtype" TEXT, - "message_id" TEXT NOT NULL, - "part" INTEGER NOT NULL, - "reaction_part" INTEGER NOT NULL, - "source" INTEGER NOT NULL, - PRIMARY KEY("message_id","event_id"), - FOREIGN KEY("message_id") REFERENCES "message_room"("message_id") ON DELETE CASCADE -) WITHOUT ROWID; -INSERT INTO new_event_message (event_id, event_type, event_subtype, message_id, part, reaction_part, source) SELECT event_id, event_type, event_subtype, message_id, part, reaction_part, source from event_message; -DROP TABLE event_message; -ALTER TABLE new_event_message RENAME TO event_message; - --- *** reaction *** - -CREATE TABLE "new_reaction" ( - "hashed_event_id" INTEGER NOT NULL, - "message_id" TEXT NOT NULL, - "encoded_emoji" TEXT NOT NULL, original_encoding TEXT, - PRIMARY KEY("hashed_event_id"), - FOREIGN KEY("message_id") REFERENCES "message_room"("message_id") ON DELETE CASCADE -) WITHOUT ROWID; -INSERT INTO new_reaction (hashed_event_id, message_id, encoded_emoji) SELECT hashed_event_id, message_id, encoded_emoji FROM reaction; -DROP TABLE reaction; -ALTER TABLE new_reaction RENAME TO reaction; - --- *** - -DROP TABLE message_channel; -PRAGMA foreign_key_check; - -COMMIT; -PRAGMA foreign_keys=ON; diff --git a/src/db/migrations/0027-analyze.sql b/src/db/migrations/0027-analyze.sql deleted file mode 100644 index f66e0c1..0000000 --- a/src/db/migrations/0027-analyze.sql +++ /dev/null @@ -1,250 +0,0 @@ --- https://www.sqlite.org/lang_analyze.html - -BEGIN TRANSACTION; - -ANALYZE sqlite_schema; - -DELETE FROM "sqlite_stat1"; -INSERT INTO "sqlite_stat1" ("tbl","idx","stat") VALUES ('reaction','reaction','4741 1'), -('event_message','event_message','537386 1 1'), -('message_room','message_room','510262 1'), -('historical_channel_room','sqlite_autoindex_historical_channel_room_1','991 1'), -('auto_emoji','auto_emoji','2 1'), -('sim','sim','1075 1'), -('webhook','webhook','205 1'), -('channel_room','channel_room','992 1'), -('channel_room','sqlite_autoindex_channel_room_1','992 1'), -('guild_active','guild_active','45 1'), -('media_proxy','media_proxy','19794 1'), -('sim_member','sim_member','5504 6 1'), -('emoji','emoji','3472 1'), -('guild_space','guild_space','43 1'), -('member_power','member_power','1 1 1'), -('sim_proxy','sim_proxy','213 1'), -('migration',NULL,'1'), -('member_cache','member_cache','1117 3 1'), -('file','file','36489 1'), -('lottie','lottie','22 1'); - -DELETE FROM "sqlite_stat4"; -INSERT INTO "sqlite_stat4" ("tbl","idx","neq","nlt","ndlt","sample") VALUES ('reaction','reaction','1','526','526',X'02069c21bd28f26ae025'), - ('reaction','reaction','1','1053','1053',X'0206b8866f4c30c2e1aa'), - ('reaction','reaction','1','1580','1580',X'0206d43fceca129b040e'), - ('reaction','reaction','1','2107','2107',X'0206f121f9a4fe54b557'), - ('reaction','reaction','1','2634','2634',X'020610299199abbd0e9c'), - ('reaction','reaction','1','3161','3161',X'02062be99961e7716037'), - ('reaction','reaction','1','3688','3688',X'020647b48fa5ee5a415c'), - ('reaction','reaction','1','4215','4215',X'020664fdc2d88c77dda3'), - ('event_message','event_message','11 1','14790 14792','14356 14792',X'03336531313532303033373639343137303839303434244a616d4c6d732d4b77454c6b47766866344d524f385576535536336a574a5a4c4474524c4c57664f775873'), - ('event_message','event_message','11 1','33809 33816','32914 33816',X'0333653131353736303336383730353738363637363224544b7141734f58566c6e67506f546f4a427565514e664444756d494a6d38384f486a76766f7949496e7130'), - ('event_message','event_message','1 1','59709 59709','57896 59709',X'033365313136363930353332323132303637353336392442794756564f6767326a416845624267463941755056486178377a34314459514e4459316e34435a4a4455'), - ('event_message','event_message','11 1','116172 116182','111525 116182',X'0333653131393336383733373036313733323736353624786a385f70696e784f624f4349666c70556832305542345973664a547642694b4164675f473168562d5334'), - ('event_message','event_message','1 1','119419 119419','114559 119419',X'03336531313935323132333038393839383730313732244f556670664d5054576c364774734943484d725459556d6464656c636232663374494a662d425769554355'), - ('event_message','event_message','16 1','140286 140287','134379 140287',X'0333653132303933373536353437313834383034323524346b61796e4d68422d336d6967417571347255745f726639353454636b6f657636664c5f3675394f455030'), - ('event_message','event_message','11 1','162080 162086','154932 162086',X'0333653132323434383135393033313937373537373424674c77513179796e4b6d5859496b5a597a4a55627a66557a55552d714c4b5f524f454e4250325f6e44766b'), - ('event_message','event_message','11 1','178659 178659','170672 178659',X'03336531323333353238303533323338333337353537242d39304668552d36455373594b6435484d7237666d6a414a5f6a576149616e356c4776384e655436564959'), - ('event_message','event_message','1 1','179129 179129','171083 179129',X'03336531323333393533373032373637383836343636245a446e5f42385a6b41674c645939495649767445516e47373369706a555a55447943634768697851673859'), - ('event_message','event_message','10 1','180049 180052','171954 180052',X'03336531323334363237303030393333383130323637245171504b7357795254734a49695449744646716a686e506d48764a6e5932584a6c595a506b424e372d766f'), - ('event_message','event_message','11 1','215266 215271','205302 215271',X'03336531323533373435373636373337313231333632244b4936672d57724f5a5757533463534c4c4f353950555176425066754b5f5446504b443233583130504759'), - ('event_message','event_message','11 1','224498 224499','213831 224499',X'0333653132353932393835383232333036303137353824356a573361764d37626d643661756c7367635650506f5257417552476e30503477324939786b5675326f6b'), - ('event_message','event_message','10 1','224519 224523','213833 224523',X'033365313235393239383739353234323635353839352452696c715a6862347a32526b594c596958504375445975546f6b6430544e365a784638737842745670346b'), - ('event_message','event_message','10 1','224615 224616','213843 224616',X'0333653132353933303036363636373831383139323024425a69396d4c73323034344c674a6e56673761557a614467484b4b5545787334587a467954474245585573'), - ('event_message','event_message','1 1','238839 238839','227061 238839',X'0333653132363839343934383836303535393336343224374d3633546d416c526947553847795f416164576f4d4f4e4a334b363441326235385f6e72385961652d51'), - ('event_message','event_message','1 1','298549 298549','283096 298549',X'03336531333037303536353132313931303337343532245830424a3954514e544d3041687554736c7258744b5836383376723749524355747a4b47524a4374493555'), - ('event_message','event_message','11 1','304605 304605','288785 304605',X'03336531333131353731333337393331363537323737242d674e75657465765a426169587949335859717437325743695438396549573269514761416266384f6455'), - ('event_message','event_message','11 1','327028 327037','309699 327037',X'0333653133323736353831313733343439323336383024715055786a61394c36694e756548683046335962304b524b67665730414356394769367a4147464b714973'), - ('event_message','event_message','10 1','329549 329550','312055 329550',X'033365313332393331373735303931323435303537322430325a4779526f33656133786e5356706b52487047325459415464373971684834536632506f4e7a614773'), - ('event_message','event_message','1 1','358259 358259','339179 358259',X'03336531333436303136333531313138313634303539243364757343667558596a506f715a3642774851755a48496e5163504f4e70766c64387476654a4d45685a38'), - ('event_message','event_message','1 1','417969 417969','395237 417969',X'0333653133363831333832343230383333393336363724537a7775656948304b696130376d67304e51322d58627751352d6a7653507649464e645053396464416655'), - ('event_message','event_message','11 1','422263 422270','399248 422270',X'033365313336393833343930353236313034373831382456754f5872464d593547734350377467425f6a763348486f426264666b3859464c4b4f6e48583732497677'), - ('event_message','event_message','10 1','424260 424266','401135 424266',X'03336531333730353132353138353938303939303637246c7268447950715458362d45497a3637552d616a75453839614655394c4151556f5a356d7363725072466f'), - ('event_message','event_message','1 1','477679 477679','451062 477679',X'0333653134303434353430323035313234383133333324524f454b6b5f726b3373344b7451337a75344552774c4b5069484964757676575f514d4b4e66306c385630'), - ('message_room','message_room','1','56695','56695',X'023331313636313031373337333834343630333739'), - ('message_room','message_room','1','113391','113391',X'023331313932353935303036363435363132353434'), - ('message_room','message_room','1','170087','170087',X'023331323331393439393133373937393535363335'), - ('message_room','message_room','1','226783','226783',X'023331323636313430343634333733383239373532'), - ('message_room','message_room','1','283479','283479',X'023331333034303933383132373833373130323539'), - ('message_room','message_room','1','340175','340175',X'023331333434383431363637333537393730343332'), - ('message_room','message_room','1','396871','396871',X'023331333637353035313132313333363638393134'), - ('message_room','message_room','1','453567','453567',X'023331343032363934353234333439373134343833'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','0 0','0 0',X'034b0221414355774c616c64303030303030303030303a636164656e63652e6d6f650288'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','24 24','24 24',X'034b02214255635a694c7a57303030303030303030303a636164656e63652e6d6f6501ab'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','110 110','110 110',X'034b022147486e4d47697875303030303030303030303a636164656e63652e6d6f6500c6'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','193 193','193 193',X'034b02214b4b535575717666303030303030303030303a636164656e63652e6d6f650350'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','221 221','221 221',X'034b02214c51715351594b73303030303030303030303a636164656e63652e6d6f6503af'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','319 319','319 319',X'034b02215170676c734e587a303030303030303030303a636164656e63652e6d6f650366'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','332 332','332 332',X'034b0221525a585a7064554f303030303030303030303a636164656e63652e6d6f65009f'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','351 351','351 351',X'034b0221534b6f6c6f636b77303030303030303030303a636164656e63652e6d6f65035f'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','443 443','443 443',X'034b0221576374435a494d73303030303030303030303a636164656e63652e6d6f650084'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','551 551','551 551',X'034b0221637779454c6c6b55303030303030303030303a636164656e63652e6d6f6501b0'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','554 554','554 554',X'034b0221644965496d615167303030303030303030303a636164656e63652e6d6f6503a0'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','560 560','560 560',X'034b0221645568456f756a71303030303030303030303a636164656e63652e6d6f650090'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','573 573','573 573',X'034b02216552517465644b67303030303030303030303a636164656e63652e6d6f650099'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','593 593','593 593',X'034b0221666764594e526d4e303030303030303030303a636164656e63652e6d6f65016b'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','624 624','624 624',X'034b0221687078416c4c6f71303030303030303030303a636164656e63652e6d6f650297'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','625 625','625 625',X'034b02216873414570464e47303030303030303030303a636164656e63652e6d6f6500be'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','665 665','665 665',X'034b01216a71484b51424476303030303030303030303a636164656e63652e6d6f653b'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','758 758','758 758',X'034b02216f6251554d424b75303030303030303030303a636164656e63652e6d6f6500f7'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','776 776','776 776',X'034b022170566e596b5a4f46303030303030303030303a636164656e63652e6d6f650232'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','781 781','781 781',X'034b01217065766e6542516e303030303030303030303a636164656e63652e6d6f6518'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','857 857','857 857',X'034b02217446564c65724b78303030303030303030303a636164656e63652e6d6f65024e'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','866 866','866 866',X'034b0221745a61474145557a303030303030303030303a636164656e63652e6d6f6501f8'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','887 887','887 887',X'034b022175727a464b754d61303030303030303030303a636164656e63652e6d6f65033b'), - ('historical_channel_room','sqlite_autoindex_historical_channel_room_1','1 1','921 921','921 921',X'034b022177574a5548445a74303030303030303030303a636164656e63652e6d6f65025c'), - ('auto_emoji','auto_emoji','1','0','0',X'02114c31'), - ('auto_emoji','auto_emoji','1','1','1',X'02114c32'), - ('sim','sim','1','119','119',X'025531316564343731342d636635652d346333372d393331382d376136353266383732636634'), - ('sim','sim','1','239','239',X'0231313439363932303632313634333230323536'), - ('sim','sim','1','359','359',X'025532323533323035312d633335332d346638662d383835362d653137383831323435303763'), - ('sim','sim','1','479','479',X'0231333036373839323436333237353836383136'), - ('sim','sim','1','599','599',X'0231343132383438323830343635313738363235'), - ('sim','sim','1','719','719',X'0231353638323430303837363238373735343234'), - ('sim','sim','1','839','839',X'0231373234383037393132373233313835373534'), - ('sim','sim','1','959','959',X'0231393431303333313033353936353835303630'), - ('webhook','webhook','1','22','22',X'023331313630383933333337303239353836393536'), - ('webhook','webhook','1','45','45',X'023331323139343938393236343636363632343330'), - ('webhook','webhook','1','68','68',X'023331323432383939363632343734373131303630'), - ('webhook','webhook','1','91','91',X'023331323937323836383730393534323833313533'), - ('webhook','webhook','1','114','114',X'023331333430353438363133363931393332373133'), - ('webhook','webhook','1','137','137',X'023331343034313334383236303530383436393331'), - ('webhook','webhook','1','160','160',X'0231333639373535303430343638303431373238'), - ('webhook','webhook','1','183','183',X'0231363035353930343336333230333738383930'), - ('channel_room','channel_room','1','110','110',X'023331313939353030313137393834363733393133'), - ('channel_room','channel_room','1','221','221',X'023331323734313935333432323131393430353434'), - ('channel_room','channel_room','1','332','332',X'023331333437303036333637393639343433383430'), - ('channel_room','channel_room','1','443','443',X'023331343035323432323838343138303632333636'), - ('channel_room','channel_room','1','554','554',X'023331343036373736363630393936333935323830'), - ('channel_room','channel_room','1','665','665',X'023331343039363536363537383835323635393830'), - ('channel_room','channel_room','1','776','776',X'023331343139353132333134363234383638343632'), - ('channel_room','channel_room','1','887','887',X'0231333734383732393736313738343133353639'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','23 23','23 23',X'034b3121425167434a4d4c78303030303030303030303a636164656e63652e6d6f65393631373335333036303032393732373432'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','96 96','96 96',X'034b332146514f654f667747303030303030303030303a636164656e63652e6d6f6531323137393638383531303939313839323738'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','110 110','110 110',X'034b332147486e4d47697875303030303030303030303a636164656e63652e6d6f6531323432323436333730303938363739383838'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','138 138','138 138',X'034b3321484a79705a6b6863303030303030303030303a636164656e63652e6d6f6531303237323933303239313633333335373130'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','161 161','161 161',X'034b33214962646466626172303030303030303030303a636164656e63652e6d6f6531323937373538303931373331303039353536'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','221 221','221 221',X'034b31214c51715351594b73303030303030303030303a636164656e63652e6d6f65373039303431393733353332363838343235'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','240 240','240 240',X'034b33214d5071594e414a62303030303030303030303a636164656e63652e6d6f6531323139303338323638323835323539393037'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','250 250','250 250',X'034b33214e414f484c4e444c303030303030303030303a636164656e63652e6d6f6531343037323332343832313338333934363634'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','325 325','325 325',X'034b33215178576669464359303030303030303030303a636164656e63652e6d6f6531343034353739343736363837323934363434'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','332 332','332 332',X'034b33215254735654767542303030303030303030303a636164656e63652e6d6f6531343037323235393932313935333432343237'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','430 430','430 430',X'034b33215673656a6b6b5a71303030303030303030303a636164656e63652e6d6f6531323235323636343030363838333431303833'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','443 443','443 443',X'034b3321576241744a736c6b303030303030303030303a636164656e63652e6d6f6531343230323635333433363931313332393339'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','552 552','552 552',X'034b3321637779454c6c6b55303030303030303030303a636164656e63652e6d6f6531343034393538363332363830303939393931'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','554 554','554 554',X'034b332164484e5378484a47303030303030303030303a636164656e63652e6d6f6531343035363439333331343335393939323932'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','565 565','565 565',X'034b3321646c584f50766944303030303030303030303a636164656e63652e6d6f6531323735353037363433323231343039393033'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','579 579','579 579',X'034b332165656c6c7a6a5370303030303030303030303a636164656e63652e6d6f6531343036373736363630393936333935323830'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','619 619','619 619',X'034b332168525179596e6d4e303030303030303030303a636164656e63652e6d6f6531343237323832333338303035353136343233'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','664 664','664 664',X'034b33216a6c566e54585747303030303030303030303a636164656e63652e6d6f6531323139353034373636333137373536343238'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','665 665','665 665',X'034b33216a6c6c4479666d76303030303030303030303a636164656e63652e6d6f6531303835303935353736383731333837313936'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','776 776','776 776',X'034b332170555653686b7978303030303030303030303a636164656e63652e6d6f6531323139343939353736363137303738383735'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','813 813','813 813',X'034b33217179416246555961303030303030303030303a636164656e63652e6d6f6531343035393130313436363731393732333833'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','887 887','887 887',X'034b33217571697357484575303030303030303030303a636164656e63652e6d6f6531323331383036353337373032353736313938'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','924 924','924 924',X'034b332177585242634d4851303030303030303030303a636164656e63652e6d6f6531343233373338343430363833363232353332'), - ('channel_room','sqlite_autoindex_channel_room_1','1 1','953 953','953 953',X'034b33217954757a6749556f303030303030303030303a636164656e63652e6d6f6531333338353537373531373232323530333130'), - ('guild_active','guild_active','1','5','5',X'023331313433333336323438373631363437313534'), - ('guild_active','guild_active','1','11','11',X'023331313630383933333336333234393331353834'), - ('guild_active','guild_active','1','17','17',X'023331323839353936343835343631323137333430'), - ('guild_active','guild_active','1','23','23',X'023331333338363530383035363233393834333030'), - ('guild_active','guild_active','1','29','29',X'023331343338363132393630393137353836313233'), - ('guild_active','guild_active','1','35','35',X'0231343937313539373236343535343535373534'), - ('guild_active','guild_active','1','41','41',X'0231383730313138363530373638363730373530'), - ('media_proxy','media_proxy','1','2199','2199',X'02069cb7709d83b92e22'), - ('media_proxy','media_proxy','1','4399','4399',X'0206b953cc685f0b68d2'), - ('media_proxy','media_proxy','1','6599','6599',X'0206d546a2d00310b6cc'), - ('media_proxy','media_proxy','1','8799','8799',X'0206f0d029ff71e1dae5'), - ('media_proxy','media_proxy','1','10999','10999',X'02060e4626697605710f'), - ('media_proxy','media_proxy','1','13199','13199',X'02062adc53c43825bc39'), - ('media_proxy','media_proxy','1','15399','15399',X'02064704c4b0f76fa5ff'), - ('media_proxy','media_proxy','1','17599','17599',X'02066338ce2423770613'), - ('sim_member','sim_member','225 1','14 80','4 80',X'034b4721414956694e775a64303030303030303030303a636164656e63652e6d6f65405f6f6f79655f66726f73745f313139323a636164656e63652e6d6f65'), - ('sim_member','sim_member','32 1','483 488','68 488',X'034b3b21455450534d664d69303030303030303030303a636164656e63652e6d6f65405f6f6f79655f653372613a636164656e63652e6d6f65'), - ('sim_member','sim_member','125 1','598 611','85 611',X'034b4921457a54624a496c49303030303030303030303a636164656e63652e6d6f65405f6f6f79655f61726a756e3034323236393a636164656e63652e6d6f65'), - ('sim_member','sim_member','35 1','818 851','107 851',X'034b472147486e4d47697875303030303030303030303a636164656e63652e6d6f65405f6f6f79655f76616e746164656c69613a636164656e63652e6d6f65'), - ('sim_member','sim_member','63 1','945 1005','141 1005',X'034b412148725979716b6f79303030303030303030303a636164656e63652e6d6f65405f6f6f79655f7669686f776c733a636164656e63652e6d6f65'), - ('sim_member','sim_member','48 1','1024 1025','149 1025',X'034b47214943566475566c64303030303030303030303a636164656e63652e6d6f65405f6f6f79655f5f706b5f6172656866723a636164656e63652e6d6f65'), - ('sim_member','sim_member','39 1','1205 1223','175 1223',X'034b41214a48614a71425870303030303030303030303a636164656e63652e6d6f65405f6f6f79655f6c6f6f6e656c613a636164656e63652e6d6f65'), - ('sim_member','sim_member','48 1','1734 1768','289 1768',X'034b47215074796952785161303030303030303030303a636164656e63652e6d6f65405f6f6f79655f6d6f6d6f7473756b692e3a636164656e63652e6d6f65'), - ('sim_member','sim_member','5 1','1832 1835','299 1835',X'034b4b2151544372636e6953303030303030303030303a636164656e63652e6d6f65405f6f6f79655f72616e646f6d6974796775793a636164656e63652e6d6f65'), - ('sim_member','sim_member','64 1','2097 2100','353 2100',X'034b4521536c7664497a734f303030303030303030303a636164656e63652e6d6f65405f6f6f79655f5f706b5f626369736c3a636164656e63652e6d6f65'), - ('sim_member','sim_member','81 1','2213 2240','361 2240',X'034b4721544f61794476734c303030303030303030303a636164656e63652e6d6f65405f6f6f79655f5f706b5f6f7a707a79633a636164656e63652e6d6f65'), - ('sim_member','sim_member','42 1','2368 2409','373 2409',X'034b49215468436b4b585743303030303030303030303a636164656e63652e6d6f65405f6f6f79655f776172736d6974686c69763a636164656e63652e6d6f65'), - ('sim_member','sim_member','36 1','2422 2447','380 2447',X'034b4b2154716c79516d6966303030303030303030303a636164656e63652e6d6f65405f6f6f79655f6a6f6b65726765726d616e793a636164656e63652e6d6f65'), - ('sim_member','sim_member','65 1','2689 2721','438 2721',X'034b472157755a5549494e74303030303030303030303a636164656e63652e6d6f65405f6f6f79655f5f706b5f77797a63686a3a636164656e63652e6d6f65'), - ('sim_member','sim_member','2 1','3058 3059','497 3059',X'034b3921616f764c6d776a67303030303030303030303a636164656e63652e6d6f65405f6f6f79655f726e6c3a636164656e63652e6d6f65'), - ('sim_member','sim_member','8 1','3666 3671','630 3671',X'034b39216966636d75794e6e303030303030303030303a636164656e63652e6d6f65405f6f6f79655f726e6c3a636164656e63652e6d6f65'), - ('sim_member','sim_member','43 1','3849 3874','668 3874',X'034b4f216b6b4b714249664c303030303030303030303a636164656e63652e6d6f65405f6f6f79655f656c656374726f6e6963353339313a636164656e63652e6d6f65'), - ('sim_member','sim_member','8 1','4280 4283','746 4283',X'034b3f216f705748554e6b46303030303030303030303a636164656e63652e6d6f65405f6f6f79655f636f6f6b69653a636164656e63652e6d6f65'), - ('sim_member','sim_member','158 1','4424 4465','770 4465',X'034b452170757146464b5948303030303030303030303a636164656e63652e6d6f65405f6f6f79655f5f706b5f6a666e747a3a636164656e63652e6d6f65'), - ('sim_member','sim_member','44 1','4810 4810','824 4810',X'034b4121734b4c6f784a4e62303030303030303030303a636164656e63652e6d6f65405f6f6f79655f313030626563733a636164656e63652e6d6f65'), - ('sim_member','sim_member','11 1','4892 4895','841 4895',X'034b45217443744769524448303030303030303030303a636164656e63652e6d6f65405f6f6f79655f646f6f74736b7972653a636164656e63652e6d6f65'), - ('sim_member','sim_member','73 1','5072 5089','888 5089',X'034b47217665764462756174303030303030303030303a636164656e63652e6d6f65405f6f6f79655f5f706b5f6b73706a75653a636164656e63652e6d6f65'), - ('sim_member','sim_member','59 1','5182 5236','903 5236',X'034b43217750454472596b77303030303030303030303a636164656e63652e6d6f65405f6f6f79655f74656368323334613a636164656e63652e6d6f65'), - ('sim_member','sim_member','52 1','5441 5469','968 5469',X'034b41217a66654e574d744b303030303030303030303a636164656e63652e6d6f65405f6f6f79655f6e6f766574746f3a636164656e63652e6d6f65'), - ('emoji','emoji','1','385','385',X'023331313035373039393137313237353737363733'), - ('emoji','emoji','1','771','771',X'023331323230353735323436303531303533353638'), - ('emoji','emoji','1','1157','1157',X'023331333530383339313335363836313033303730'), - ('emoji','emoji','1','1543','1543',X'0231333439373232393637383636393938373834'), - ('emoji','emoji','1','1929','1929',X'0231343933383437383237313138353535313436'), - ('emoji','emoji','1','2315','2315',X'0231363432353731303038313337373536373032'), - ('emoji','emoji','1','2701','2701',X'0231373738313036343330333034393434313238'), - ('emoji','emoji','1','3087','3087',X'0231393030383733373535343037303336343738'), - ('guild_space','guild_space','1','4','4',X'023331313333333135333632353636343535333336'), - ('guild_space','guild_space','1','9','9',X'023331313534383638343234373234343633363837'), - ('guild_space','guild_space','1','14','14',X'023331323139303338323637383430393235383138'), - ('guild_space','guild_space','1','19','19',X'023331323839363030383537323437303535383733'), - ('guild_space','guild_space','1','24','24',X'023331333435363431323031393032323838393837'), - ('guild_space','guild_space','1','29','29',X'0231323733383737363437323234393935383431'), - ('guild_space','guild_space','1','34','34',X'0231353239313736313536333938363832313135'), - ('guild_space','guild_space','1','39','39',X'0231383730313138363530373638363730373530'), - ('member_power','member_power','1 1','0 0','0 0',X'03350f40636164656e63653a636164656e63652e6d6f652a'), - ('sim_proxy','sim_proxy','1','23','23',X'025531363733363165392d656137652d343530392d623533302d356531613863613735336237'), - ('sim_proxy','sim_proxy','1','47','47',X'025532653561626332312d326332622d346133352d386237642d366432383162363036653932'), - ('sim_proxy','sim_proxy','1','71','71',X'025534383131393165322d393462302d346534632d623934352d336330323932623135356238'), - ('sim_proxy','sim_proxy','1','95','95',X'025536346331346631642d663834342d346535622d386665332d336162336163363239616230'), - ('sim_proxy','sim_proxy','1','119','119',X'025538376562363463322d363763352d346432352d383161642d666664333235663266303639'), - ('sim_proxy','sim_proxy','1','143','143',X'025561616630313539652d623165312d343231342d396266652d313334613536303738323231'), - ('sim_proxy','sim_proxy','1','167','167',X'025563396534393633372d663061352d343566352d383234382d366436393565643861316434'), - ('sim_proxy','sim_proxy','1','191','191',X'025565333734613634362d386231332d343365392d393635392d653233326366653866626265'), - ('member_cache','member_cache','4 1','98 99','66 99',X'034b35214a48614a71425870303030303030303030303a636164656e63652e6d6f6540657a7261637574653a6d61747269782e6f7267'), - ('member_cache','member_cache','4 1','119 122','80 122',X'034b43214c684978654c4d54303030303030303030303a636164656e63652e6d6f6540737461727368696e656c756e6163793a6d61747269782e6f7267'), - ('member_cache','member_cache','1 1','124 124','82 124',X'034b2d214d5071594e414a62303030303030303030303a636164656e63652e6d6f6540726e6c3a636164656e63652e6d6f65'), - ('member_cache','member_cache','5 1','128 131','85 131',X'034b3b214e446249714e704a303030303030303030303a636164656e63652e6d6f65406761627269656c766f6e643a6d61747269782e6f7267'), - ('member_cache','member_cache','4 1','138 139','90 139',X'034b3d214f48584445737062303030303030303030303a636164656e63652e6d6f6540616d693a7468652d61706f746865636172792e636c7562'), - ('member_cache','member_cache','5 1','207 209','135 209',X'034b51215450616f6a545444303030303030303030303a636164656e63652e6d6f65406a61636b736f6e6368656e3636363a6a61636b736f6e6368656e3636362e636f6d'), - ('member_cache','member_cache','76 1','216 249','140 249',X'034b2d2154716c79516d6966303030303030303030303a636164656e63652e6d6f65406963656d616e3a656e76732e6e6574'), - ('member_cache','member_cache','4 1','345 345','171 345',X'034b3521586f4c466b65786a303030303030303030303a636164656e63652e6d6f6540636164656e63653a636164656e63652e6d6f65'), - ('member_cache','member_cache','10 1','351 354','174 354',X'034b3521594b46454e797166303030303030303030303a636164656e63652e6d6f654066617269656c6c653a6d61747269782e6f7267'), - ('member_cache','member_cache','1 1','374 374','183 374',X'034b2d21596f54644f55766a303030303030303030303a636164656e63652e6d6f6540726e6c3a636164656e63652e6d6f65'), - ('member_cache','member_cache','152 1','405 499','205 499',X'034b45216342787456527844303030303030303030303a636164656e63652e6d6f65406d61726975733835313030303a6d617269757364617669642e6672'), - ('member_cache','member_cache','4 1','562 563','209 563',X'034b35216356514d45455158303030303030303030303a636164656e63652e6d6f6540657a7261637574653a6d61747269782e6f7267'), - ('member_cache','member_cache','8 1','582 586','223 586',X'034b3721654856655270706e303030303030303030303a636164656e63652e6d6f65406563686f3a66757272797265667567652e636f6d'), - ('member_cache','member_cache','7 1','594 600','227 600',X'034b3b2165724f7079584e46303030303030303030303a636164656e63652e6d6f6540766962656973766572796f3a6d61747269782e6f7267'), - ('member_cache','member_cache','165 1','613 624','235 624',X'034b4921676865544b5a7451303030303030303030303a636164656e63652e6d6f6540616d70666c6f7765723a7468652d61706f746865636172792e636c7562'), - ('member_cache','member_cache','165 1','613 749','235 749',X'034b4521676865544b5a7451303030303030303030303a636164656e63652e6d6f654073706c617473756e653a636861742e6e6575726172696f2e636f6d'), - ('member_cache','member_cache','6 1','778 782','236 782',X'034b3321676b6b686f756d42303030303030303030303a636164656e63652e6d6f65406b6162693a6361746769726c2e776f726b73'), - ('member_cache','member_cache','10 1','786 794','239 794',X'034b332168424a766e654e4f303030303030303030303a636164656e63652e6d6f65406d65636879613a636164656e63652e6d6f65'), - ('member_cache','member_cache','13 1','809 819','249 819',X'034b2b2169537958674e7851303030303030303030303a636164656e63652e6d6f65406d69646f753a656e76732e6e6574'), - ('member_cache','member_cache','4 1','856 858','273 858',X'034b3b216b73724f45554666303030303030303030303a636164656e63652e6d6f65406761627269656c766f6e643a6d61747269782e6f7267'), - ('member_cache','member_cache','12 1','865 874','279 874',X'034b35216c7570486a715444303030303030303030303a636164656e63652e6d6f654068656c6c63703a6f70656e737573652e6f7267'), - ('member_cache','member_cache','4 1','886 887','285 887',X'034b2d216d61676745536775303030303030303030303a636164656e63652e6d6f65406361743a6d61756e69756d2e6e6574'), - ('member_cache','member_cache','71 1','999 999','357 999',X'034b2d2177574f6673767573303030303030303030303a636164656e63652e6d6f654061613a6361747669626572732e6d65'), - ('member_cache','member_cache','14 1','1074 1085','361 1085',X'034b3721776c534544496a44303030303030303030303a636164656e63652e6d6f654073646f6d693a6861636b657273706163652e706c'), - ('file','file','1','4054','4054',X'03816568747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3131323736303636393137383234313032342f313135313733303032323332383035373930372f53637265656e73686f745f32303233303931345f3036303333352e6a7067'), - ('file','file','1','8109','8109',X'03814168747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3131393239333936393731353639313532322f313231383430393538323539343838373638302f494d475f343738322e6a7067'), - ('file','file','1','12164','12164',X'03813b68747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3133343037373735333438353033333437322f313139393131323831343931373333333133332f696d6167652e706e67'), - ('file','file','1','16219','16219',X'03814168747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f313337363732343737393830353034383838322f313339323938363435323538343936303032312f707265766965772e706e67'), - ('file','file','1','20274','20274',X'03816568747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3135393136353731343139343735393638302f313236353735383536303531323338313030392f53637265656e73686f745f32303234303732342d3135353232382e706e67'), - ('file','file','1','24329','24329',X'03813b68747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3238383838323935333331343839333832352f313134373538383535363839343738313539332f696d6167652e706e67'), - ('file','file','1','28384','28384',X'03817168747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3635353231363137333639363238363734362f313330383239363937343136333737353530392f31373331393932363837323838343136383737323137393036333831303733392e6a7067'), - ('file','file','1','32439','32439',X'027f68747470733a2f2f63646e2e646973636f72646170702e636f6d2f656d6f6a69732f313034323532383239323539363632313434322e706e67'), - ('lottie','lottie','1','2','2',X'0231373439303532393434363832353832303336'), - ('lottie','lottie','1','5','5',X'0231373531363036333739333430333635383634'), - ('lottie','lottie','1','8','8',X'0231373534313038373731383532323232353634'), - ('lottie','lottie','1','11','11',X'0231373936313430363338303933343433303932'), - ('lottie','lottie','1','14','14',X'0231373936313431373032363935343835353030'), - ('lottie','lottie','1','17','17',X'0231383136303837373932323931323832393434'), - ('lottie','lottie','1','20','20',X'0231383233393736313032393736323930383636'); - -ANALYZE sqlite_schema; - -COMMIT; diff --git a/src/db/migrations/0028-add-room-upgrade.sql b/src/db/migrations/0028-add-room-upgrade.sql deleted file mode 100644 index fed6f21..0000000 --- a/src/db/migrations/0028-add-room-upgrade.sql +++ /dev/null @@ -1,10 +0,0 @@ -BEGIN TRANSACTION; - -CREATE TABLE room_upgrade_pending ( - new_room_id TEXT NOT NULL, - old_room_id TEXT NOT NULL UNIQUE, - PRIMARY KEY (new_room_id), - FOREIGN KEY (old_room_id) REFERENCES channel_room (room_id) ON DELETE CASCADE -) WITHOUT ROWID; - -COMMIT; diff --git a/src/db/migrations/0029-force-guild-ids.js b/src/db/migrations/0029-force-guild-ids.js deleted file mode 100644 index 354bc6b..0000000 --- a/src/db/migrations/0029-force-guild-ids.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - a. If the bridge bot sim already has the correct ID: - - No rows updated. - - b. If the bridge bot sim has the wrong ID but there's no duplicate: - - One row updated. - - c. If the bridge bot sim has the wrong ID and there's a duplicate: - - One row updated (replaces an existing row). -*/ - -const {discord} = require("../../passthrough") - -const ones = "₀₁₂₃₄₅₆₇₈₉" -const tens = "0123456789" - -/* c8 ignore start */ - -module.exports = async function(db) { - /** @type {{name: string, channel_id: string, thread_parent: string | null}[]} */ - const rows = db.prepare("SELECT name, channel_id, thread_parent FROM channel_room WHERE guild_id IS NULL").all() - - /** @type {Map
🎞️ Uploaded file: 2022-10-18_16-49-46.mp4 (51 MB)
${formattedBody}
` + this.formattedBody += formattedBody + } + return this + } + + get() { + return { + msgtype: "m.text", + body: this.body, + format: "org.matrix.custom.html", + formatted_body: this.formattedBody + } + } +} + +/** + * Context: Room IDs are not routable on their own. Room permalinks need a list of servers to try. The client is responsible for coming up with a list of servers. + * ASSUMPTION 1: The bridge bot is a member of the target room and can therefore access its power levels and member list for calculation. + * ASSUMPTION 2: Because the bridge bot is a member of the target room, the target room is bridged. + * https://spec.matrix.org/v1.9/appendices/#routing + * https://gitdab.com/cadence/out-of-your-element/issues/11 + * @param {string} roomID + * @param {{[K in "getStateEvent" | "getJoinedMembers"]: import("../../matrix/api")[K]}} api + */ +async function getViaServers(roomID, api) { + const candidates = [] + const {joined} = await api.getJoinedMembers(roomID) + // Candidate 0: The bot's own server name + candidates.push(reg.ooye.server_name) + // Candidate 1: Highest joined non-sim non-bot power level user in the room + // https://github.com/matrix-org/matrix-react-sdk/blob/552c65db98b59406fb49562e537a2721c8505517/src/utils/permalinks/Permalinks.ts#L172 + try { + /** @type {{users?: {[mxid: string]: number}}} */ + const powerLevels = await api.getStateEvent(roomID, "m.room.power_levels", "") + if (powerLevels.users) { + const sorted = Object.entries(powerLevels.users).sort((a, b) => b[1] - a[1]) // Highest... + for (const power of sorted) { + const mxid = power[0] + if (!(mxid in joined)) continue // joined... + if (userRegex.some(r => mxid.match(r))) continue // non-sim non-bot... + const match = mxid.match(/:(.*)/) + assert(match) + if (!candidates.includes(match[1])) { + candidates.push(match[1]) + break + } + } + } + } catch (e) { + // power levels event not found + } + // Candidates 2-3: Most popular servers in the room + /** @type {Map${stackLines.join("\n")}${util.inspect(gatewayMessage.d, false, 4, false)}Error: Custom error\n at ./m2d/converters/utils.test.js:3:11)
{ display: 'Custom message data' }Line 2
Line 3Line 4
" + }) +}) + +test("getViaServers: returns the server name if the room only has sim users", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => ({}), + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe"]) + }) + t.deepEqual(result, ["cadence.moe"]) +}) + +test("getViaServers: also returns the most popular servers in order", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => ({}), + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid"]) + }) + t.deepEqual(result, ["cadence.moe", "thecollective.invalid", "selfhosted.invalid"]) +}) + +test("getViaServers: does not return IP address servers", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => ({}), + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:45.77.232.172:8443", "@cadence:[::1]:8443", "@cadence:123example.456example.invalid"]) + }) + t.deepEqual(result, ["cadence.moe", "123example.456example.invalid"]) +}) + +test("getViaServers: also returns the highest power level user (100)", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => ({ + users: { + "@moderator:tractor.invalid": 50, + "@singleuser:selfhosted.invalid": 100, + "@_ooye_bot:cadence.moe": 100 + } + }), + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@moderator:tractor.invalid"]) + }) + t.deepEqual(result, ["cadence.moe", "selfhosted.invalid", "thecollective.invalid", "tractor.invalid"]) +}) + +test("getViaServers: also returns the highest power level user (50)", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => ({ + users: { + "@moderator:tractor.invalid": 50, + "@_ooye_bot:cadence.moe": 100 + } + }), + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@singleuser:selfhosted.invalid"]) + }) + t.deepEqual(result, ["cadence.moe", "tractor.invalid", "thecollective.invalid", "selfhosted.invalid"]) +}) + +test("getViaServers: returns at most 4 results", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => ({ + users: { + "@moderator:tractor.invalid": 50, + "@singleuser:selfhosted.invalid": 100, + "@_ooye_bot:cadence.moe": 100 + } + }), + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@cadence:123example.456example.invalid"]) + }) + t.deepEqual(result.length, 4) +}) + +test("getViaServers: returns results even when power levels can't be fetched", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => { + throw new Error("event not found or something") + }, + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@cadence:123example.456example.invalid"]) + }) + t.deepEqual(result.length, 4) +}) + +test("getViaServers: only considers power levels of currently joined members", async t => { + const result = await getViaServers("!baby", { + getStateEvent: async () => ({ + users: { + "@moderator:tractor.invalid": 50, + "@former_moderator:missing.invalid": 100, + "@_ooye_bot:cadence.moe": 100 + } + }), + getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@singleuser:selfhosted.invalid"]) + }) + t.deepEqual(result, ["cadence.moe", "tractor.invalid", "thecollective.invalid", "selfhosted.invalid"]) +}) diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index 70e293b..985036e 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -18,20 +18,14 @@ const addReaction = sync.require("./actions/add-reaction") const redact = sync.require("./actions/redact") /** @type {import("./actions/update-pins")}) */ const updatePins = sync.require("./actions/update-pins") -/** @type {import("./actions/vote")}) */ -const vote = sync.require("./actions/vote") /** @type {import("../matrix/matrix-command-handler")} */ const matrixCommandHandler = sync.require("../matrix/matrix-command-handler") -/** @type {import("../matrix/utils")} */ -const utils = sync.require("../matrix/utils") +/** @type {import("./converters/utils")} */ +const utils = sync.require("./converters/utils") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") /** @type {import("../d2m/actions/create-room")} */ const createRoom = sync.require("../d2m/actions/create-room") -/** @type {import("../matrix/room-upgrade")} */ -const roomUpgrade = require("../matrix/room-upgrade") -/** @type {import("../d2m/actions/retrigger")} */ -const retrigger = sync.require("../d2m/actions/retrigger") const {reg} = require("../matrix/read-registration") let lastReportedEvent = 0 @@ -88,12 +82,6 @@ function stringifyErrorStack(err, depth = 0) { return collapsed; } -function printError(type, source, e, payload) { - console.error(`Error while processing a ${type} ${source} event:`) - console.error(e) - console.dir(payload, {depth: null}) -} - /** * @param {string} roomID * @param {"Discord" | "Matrix"} source @@ -102,9 +90,9 @@ function printError(type, source, e, payload) { * @param {any} payload */ async function sendError(roomID, source, type, e, payload) { - if (source === "Matrix") { - printError(type, source, e, payload) - } + console.error(`Error while processing a ${type} ${source} event:`) + console.error(e) + console.dir(payload, {depth: null}) if (Date.now() - lastReportedEvent < 5000) return null lastReportedEvent = Date.now() @@ -173,7 +161,7 @@ const errorRetrySema = new Semaphore() */ async function onRetryReactionAdd(reactionEvent) { const roomID = reactionEvent.room_id - await errorRetrySema.request(async () => { + errorRetrySema.request(async () => { const event = await api.getEvent(roomID, reactionEvent.content["m.relates_to"]?.event_id) // Check that it's a real error from OOYE @@ -181,10 +169,11 @@ async function onRetryReactionAdd(reactionEvent) { if (event.sender !== `@${reg.sender_localpart}:${reg.ooye.server_name}` || !error) return // To stop people injecting misleading messages, the reaction needs to come from either the original sender or a room moderator - if (reactionEvent.sender !== error.payload.sender) { + if (reactionEvent.sender !== event.sender) { // Check if it's a room moderator - const {powers: {[reactionEvent.sender]: senderPower}, powerLevels} = await utils.getEffectivePower(roomID, [reactionEvent.sender], api) - if (senderPower < (powerLevels.state_default ?? 50)) return + const powerLevelsStateContent = await api.getStateEvent(roomID, "m.room.power_levels", "") + const powerLevel = powerLevelsStateContent.users?.[reactionEvent.sender] || 0 + if (powerLevel < 50) return } // Retry @@ -211,7 +200,6 @@ async event => { // @ts-ignore await matrixCommandHandler.execute(event) } - retrigger.messageFinishedBridging(event.event_id) await api.ackEvent(event) })) @@ -221,55 +209,6 @@ sync.addTemporaryListener(as, "type:m.sticker", guard("m.sticker", */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return - const messageResponses = await sendEvent.sendEvent(event) - retrigger.messageFinishedBridging(event.event_id) - await api.ackEvent(event) -})) - -sync.addTemporaryListener(as, "type:org.matrix.msc3381.poll.start", guard("org.matrix.msc3381.poll.start", -/** - * @param {Ty.Event.Outer_Org_Matrix_Msc3381_Poll_Start} event it is a org.matrix.msc3381.poll.start because that's what this listener is filtering for - */ -async event => { - if (utils.eventSenderIsFromDiscord(event.sender)) return - const messageResponses = await sendEvent.sendEvent(event) - await api.ackEvent(event) -})) - -sync.addTemporaryListener(as, "type:org.matrix.msc3381.poll.response", guard("org.matrix.msc3381.poll.response", -/** - * @param {Ty.Event.Outer_Org_Matrix_Msc3381_Poll_Response} event it is a org.matrix.msc3381.poll.response because that's what this listener is filtering for - */ -async event => { - if (utils.eventSenderIsFromDiscord(event.sender)) return - await vote.updateVote(event) // Matrix votes can't be bridged, so all we do is store it in the database. - await api.ackEvent(event) -})) - -sync.addTemporaryListener(as, "type:org.matrix.msc3381.poll.end", guard("org.matrix.msc3381.poll.end", -/** - * @param {Ty.Event.Outer_Org_Matrix_Msc3381_Poll_End} event it is a org.matrix.msc3381.poll.end because that's what this listener is filtering for - */ -async event => { - if (utils.eventSenderIsFromDiscord(event.sender)) return - const pollEventID = event.content["m.relates_to"]?.event_id - if (!pollEventID) return // Validity check - const messageID = select("event_message", "message_id", {event_id: pollEventID, event_type: "org.matrix.msc3381.poll.start", source: 0}).pluck().get() - if (!messageID) return // Nothing can be done if the parent message was never bridged. Also, Discord-native polls cannot be ended by others, so this only works for polls started on Matrix. - try { - var pollEvent = await api.getEvent(event.room_id, pollEventID) // Poll start event must exist for this to be valid - } catch (e) { - return - } - - // According to the rules, the poll end is only allowed if it was sent by the poll starter, or by someone with redact powers. - if (pollEvent.sender !== event.sender) { - const {powerLevels, powers: {[event.sender]: enderPower}} = await utils.getEffectivePower(event.room_id, [event.sender], api) - if (enderPower < (powerLevels.redact ?? 50)) { - return // Not allowed - } - } - const messageResponses = await sendEvent.sendEvent(event) await api.ackEvent(event) })) @@ -363,7 +302,15 @@ async event => { await api.ackEvent(event) })) - +function getFromInviteRoomState(inviteRoomState, nskey, key) { + if (!Array.isArray(inviteRoomState)) return null + for (const event of inviteRoomState) { + if (event.type === nskey && event.state_key === "") { + return event.content[key] + } + } + return null +} sync.addTemporaryListener(as, "type:m.space.child", guard("m.space.child", /** @@ -371,18 +318,7 @@ sync.addTemporaryListener(as, "type:m.space.child", guard("m.space.child", */ async event => { if (Array.isArray(event.content.via) && event.content.via.length) { // space child is being added - try { - // try to join if able, it's okay if it doesn't want, bot will still respond to invites - await api.joinRoom(event.state_key) - // if autojoined a child space, store it in invite (otherwise the child space will be impossible to use with self-service in the future) - const hierarchy = await api.getHierarchy(event.state_key, {limit: 1}) - const roomProperties = hierarchy.rooms?.[0] - if (roomProperties?.room_id === event.state_key && roomProperties.room_type === "m.space" && roomProperties.name) { - db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)") - .run(event.sender, event.state_key, roomProperties.room_type, roomProperties.name, roomProperties.topic, roomProperties.avatar_url) - await updateMemberCachePowerLevels(event.state_key) // store privileged users in member_cache so they are also allowed to perform self-service - } - } catch (e) {} + await api.joinRoom(event.state_key).catch(() => {}) // try to join if able, it's okay if it doesn't want, bot will still respond to invites } })) @@ -392,59 +328,58 @@ sync.addTemporaryListener(as, "type:m.room.member", guard("m.room.member", */ async event => { if (event.state_key[0] !== "@") return + const bot = `@${reg.sender_localpart}:${reg.ooye.server_name}` - if (event.state_key === utils.bot) { - const upgraded = await roomUpgrade.onBotMembership(event, api, createRoom) - if (upgraded) return - } - - if (event.content.membership === "invite" && event.state_key === utils.bot) { - // Supposed to be here already? - const guildID = select("guild_space", "guild_id", {space_id: event.room_id}).pluck().get() - if (guildID) { - await api.joinRoom(event.room_id) - return - } - + if (event.content.membership === "invite" && event.state_key === bot) { // We were invited to a room. We should join, and register the invite details for future reference in web. - try { - var inviteRoomState = await api.getInviteState(event.room_id, event) - } catch (e) { - console.error(e) - return await api.leaveRoomWithReason(event.room_id, `I wasn't able to find out what this room is. Please report this as a bug. Check console for more details. (${e.toString()})`) + let attemptedApiMessage = "According to unsigned invite data." + let inviteRoomState = event.unsigned?.invite_room_state + if (!Array.isArray(inviteRoomState) || inviteRoomState.length === 0) { + try { + inviteRoomState = await api.getInviteState(event.room_id) + attemptedApiMessage = "According to SSS API." + } catch (e) { + attemptedApiMessage = "According to unsigned invite data. SSS API unavailable: " + e.toString() + } } - if (!inviteRoomState?.name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite.`) + const name = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.name", "name") + const topic = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.topic", "topic") + const avatar = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.avatar", "url") + const creationType = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.create", "type") + if (!name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite! (${attemptedApiMessage})`) await api.joinRoom(event.room_id) - db.prepare("REPLACE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, inviteRoomState.type, inviteRoomState.name, inviteRoomState.topic, inviteRoomState.avatar) - if (inviteRoomState.avatar) utils.getPublicUrlForMxc(inviteRoomState.avatar) // make sure it's available in the media_proxy allowed URLs - await updateMemberCachePowerLevels(event.room_id) // store privileged users in member_cache so they are also allowed to perform self-service + db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, creationType, name, topic, avatar) + if (avatar) utils.getPublicUrlForMxc(avatar) // make sure it's available in the media_proxy allowed URLs } + if (utils.eventSenderIsFromDiscord(event.state_key)) return + if (event.content.membership === "leave" || event.content.membership === "ban") { // Member is gone db.prepare("DELETE FROM member_cache WHERE room_id = ? and mxid = ?").run(event.room_id, event.state_key) - // Unregister room's use as a direct chat and/or an invite target if the bot itself left - if (event.state_key === utils.bot) { + // Unregister room's use as a direct chat if the bot itself left + if (event.state_key === bot) { db.prepare("DELETE FROM direct WHERE room_id = ?").run(event.room_id) - db.prepare("DELETE FROM invite WHERE room_id = ?").run(event.room_id) } } - if (utils.eventSenderIsFromDiscord(event.state_key)) return - const exists = select("channel_room", "room_id", {room_id: event.room_id}) ?? select("guild_space", "space_id", {space_id: event.room_id}) if (!exists) return // don't cache members in unbridged rooms // Member is here - let {powers: {[event.state_key]: memberPower}, tombstone} = await utils.getEffectivePower(event.room_id, [event.state_key], api) - if (memberPower === Infinity) memberPower = tombstone // database storage compatibility + let powerLevel = 0 + try { + /** @type {Ty.Event.M_Power_Levels} */ + const powerLevelsEvent = await api.getStateEvent(event.room_id, "m.room.power_levels", "") + powerLevel = powerLevelsEvent.users?.[event.state_key] ?? powerLevelsEvent.users_default ?? 0 + } catch (e) {} const displayname = event.content.displayname || null const avatar_url = event.content.avatar_url - db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url, power_level) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO UPDATE SET displayname = ?, avatar_url = ?, power_level = ?, missing_profile = NULL").run( + db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url, power_level) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO UPDATE SET displayname = ?, avatar_url = ?, power_level = ?").run( event.room_id, event.state_key, - displayname, avatar_url, memberPower, - displayname, avatar_url, memberPower + displayname, avatar_url, powerLevel, + displayname, avatar_url, powerLevel ) })) @@ -454,35 +389,12 @@ sync.addTemporaryListener(as, "type:m.room.power_levels", guard("m.room.power_le */ async event => { if (event.state_key !== "") return - await updateMemberCachePowerLevels(event.room_id) -})) - -/** - * @param {string} roomID - */ -async function updateMemberCachePowerLevels(roomID) { - const existingPower = select("member_cache", "mxid", {room_id: roomID}).pluck().all() - const {powerLevels, allCreators, tombstone} = await utils.getEffectivePower(roomID, [], api) - const newPower = powerLevels.users || {} - const newPowerUsers = Object.keys(newPower) - const relevantUsers = existingPower.concat(newPowerUsers).concat(allCreators) - for (const mxid of [...new Set(relevantUsers)]) { - const level = allCreators.includes(mxid) ? tombstone : newPower[mxid] ?? powerLevels.users_default ?? 0 - db.prepare("INSERT INTO member_cache (room_id, mxid, power_level, missing_profile) VALUES (?, ?, ?, 1) ON CONFLICT DO UPDATE SET power_level = ?") - .run(roomID, mxid, level, level) + const existingPower = select("member_cache", "mxid", {room_id: event.room_id}).pluck().all() + const newPower = event.content.users || {} + for (const mxid of existingPower) { + db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(newPower[mxid] || 0, event.room_id, mxid) } -} - -sync.addTemporaryListener(as, "type:m.room.tombstone", guard("m.room.tombstone", -/** - * @param {Ty.Event.StateOuter${formattedBody}
` - this.formattedBody += formattedBody - } - return this - } - - get() { - return { - msgtype: "m.text", - body: this.body, - format: "org.matrix.custom.html", - formatted_body: this.formattedBody - } - } -} - -/** - * Context: Room IDs are not routable on their own. Room permalinks need a list of servers to try. The client is responsible for coming up with a list of servers. - * ASSUMPTION 1: The bridge bot is a member of the target room and can therefore access its power levels and member list for calculation. - * ASSUMPTION 2: Because the bridge bot is a member of the target room, the target room is bridged. - * https://spec.matrix.org/v1.9/appendices/#routing - * https://gitdab.com/cadence/out-of-your-element/issues/11 - * @param {string} roomID - * @param {{[K in "getStateEvent" | "getStateEventOuter" | "getJoinedMembers"]: import("./api")[K]} | {getEffectivePower: (roomID: string, mxids: string[], api: any) => Promise<{powers: Record${stackLines.join("\n")}${util.inspect(gatewayMessage.d, false, 4, false)}Error: Custom error\n at ./example.test.js:3:11)
{ display: 'Custom message data' }Line 2
Line 3Line 4
" - }) -}) - -/** - * @param {string[]} [creators] - * @param {{[x: string]: number}} [users] - * @param {string} [roomVersion] - */ -function mockGetEffectivePower(creators = ["@_ooye_bot:cadence.moe"], users = {}, roomVersion = "12") { - return async function getEffectivePower(roomID, mxids) { - return { - allCreators: creators, - powerLevels: {users}, - powers: mxids.reduce((a, mxid) => { - if (creators.includes(mxid) && roomHasAtLeastVersion(roomVersion, 12)) a[mxid] = Infinity - else if (mxid in users) a[mxid] = users[mxid] - else a[mxid] = 0 - return a - }, {}), - roomCreate: { - type: "m.room.create", - state_key: "", - sender: creators[0], - content: { - additional_creators: creators.slice(1), - room_version: roomVersion - }, - room_id: roomID, - origin_server_ts: 0, - event_id: "$create" - }, - tombstone: roomVersion === "12" ? 150 : 100, - } - } -} - -test("getViaServers: returns the server name if the room only has sim users", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe"]) - }) - t.deepEqual(result, ["cadence.moe"]) -}) - -test("getViaServers: also returns the most popular servers in order", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid"]) - }) - t.deepEqual(result, ["cadence.moe", "thecollective.invalid", "selfhosted.invalid"]) -}) - -test("getViaServers: does not return IP address servers", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:45.77.232.172:8443", "@cadence:[::1]:8443", "@cadence:123example.456example.invalid"]) - }) - t.deepEqual(result, ["cadence.moe", "123example.456example.invalid"]) -}) - -test("getViaServers: also returns the highest power level user (v12 creator)", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(["@_ooye_bot:cadence.moe", "@singleuser:selfhosted.invalid"], { - "@moderator:tractor.invalid": 50 - }), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@moderator:tractor.invalid"]) - }) - t.deepEqual(result, ["cadence.moe", "selfhosted.invalid", "thecollective.invalid", "tractor.invalid"]) -}) - -test("getViaServers: also returns the highest power level user (100)", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(["@_ooye_bot:cadence.moe"], { - "@moderator:tractor.invalid": 50, - "@singleuser:selfhosted.invalid": 100 - }), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@moderator:tractor.invalid"]) - }) - t.deepEqual(result, ["cadence.moe", "selfhosted.invalid", "thecollective.invalid", "tractor.invalid"]) -}) - -test("getViaServers: also returns the highest power level user (50)", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(["@_ooye_bot:cadence.moe"], { - "@moderator:tractor.invalid": 50 - }), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@singleuser:selfhosted.invalid"]) - }) - t.deepEqual(result, ["cadence.moe", "tractor.invalid", "thecollective.invalid", "selfhosted.invalid"]) -}) - -test("getViaServers: returns at most 4 results", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(["@_ooye_bot:cadence.moe"], { - "@moderator:tractor.invalid": 50, - "@singleuser:selfhosted.invalid": 100 - }), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@cadence:123example.456example.invalid"]) - }) - t.deepEqual(result.length, 4) -}) - -test("getViaServers: only considers power levels of currently joined members", async t => { - const result = await getViaServers("!baby", { - getEffectivePower: mockGetEffectivePower(["@_ooye_bot:cadence.moe", "@former_moderator:missing.invalid"], { - "@moderator:tractor.invalid": 50 - }), - getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@singleuser:selfhosted.invalid"]) - }) - t.deepEqual(result, ["cadence.moe", "tractor.invalid", "thecollective.invalid", "selfhosted.invalid"]) -}) - -test("roomHasAtLeastVersion: v9 < v11", t => { - t.equal(roomHasAtLeastVersion("9", 11), false) -}) - -test("roomHasAtLeastVersion: v12 >= v11", t => { - t.equal(roomHasAtLeastVersion("12", 11), true) -}) - -test("roomHasAtLeastVersion: v12 >= v12", t => { - t.equal(roomHasAtLeastVersion("12", 12), true) -}) - -test("roomHasAtLeastVersion: custom versions never match", t => { - t.equal(roomHasAtLeastVersion("moe.cadence.silly", 11), false) -}) - -test("removeCreatorsFromPowerLevels: removes the creator from a v12 room", t => { - t.deepEqual(removeCreatorsFromPowerLevels({ - type: "m.room.create", - state_key: "", - sender: "@_ooye_bot:cadence.moe", - room_id: "!example", - event_id: "$create", - origin_server_ts: 0, - content: { - room_version: "12" - } - }, { - users: { - "@_ooye_bot:cadence.moe": 100 - } - }), { - users: { - } - }) -}) - -test("removeCreatorsFromPowerLevels: removes all creators from a v12 room", t => { - t.deepEqual(removeCreatorsFromPowerLevels({ - type: "m.room.create", - state_key: "", - sender: "@_ooye_bot:cadence.moe", - room_id: "!example", - event_id: "$create", - origin_server_ts: 0, - content: { - additional_creators: ["@cadence:cadence.moe"], - room_version: "12" - } - }, { - users: { - "@_ooye_bot:cadence.moe": 100, - "@cadence:cadence.moe": 100 - } - }), { - users: { - } - }) -}) - -test("removeCreatorsFromPowerLevels: doesn't touch a v11 room", t => { - t.deepEqual(removeCreatorsFromPowerLevels({ - type: "m.room.create", - state_key: "", - sender: "@_ooye_bot:cadence.moe", - room_id: "!example", - event_id: "$create", - origin_server_ts: 0, - content: { - additional_creators: ["@cadence:cadence.moe"], - room_version: "11" - } - }, { - users: { - "@_ooye_bot:cadence.moe": 100, - "@cadence:cadence.moe": 100 - } - }), { - users: { - "@_ooye_bot:cadence.moe": 100, - "@cadence:cadence.moe": 100 - } - }) -}) - -test("set user power: no-op", async t => { - let called = 0 - await setUserPower("!room", "@cadence:cadence.moe", 0, { - async getStateEvent(roomID, type, key) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.power_levels") - t.equal(key, "") - return {} - }, - async getStateEventOuter(roomID, type, key) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.create") - t.equal(key, "") - return { - type: "m.room.create", - state_key: "", - sender: "@_ooye_bot:cadence.moe", - room_id: "!room", - origin_server_ts: 0, - event_id: "$create", - content: { - room_version: "11" - } - } - }, - /* c8 ignore next 4 */ - async sendState() { - called++ - throw new Error("should not try to send state") - } - }) - t.equal(called, 2) -}) - -test("set user power: bridge bot must promote unprivileged users", async t => { - let called = 0 - await setUserPower("!room", "@cadence:cadence.moe", 100, { - async getStateEvent(roomID, type, key) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.power_levels") - t.equal(key, "") - return { - users: {"@_ooye_bot:cadence.moe": 100} - } - }, - async getStateEventOuter(roomID, type, key) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.create") - t.equal(key, "") - return { - type: "m.room.create", - state_key: "", - sender: "@_ooye_bot:cadence.moe", - room_id: "!room", - origin_server_ts: 0, - event_id: "$create", - content: { - room_version: "11" - } - } - }, - async sendState(roomID, type, key, content, mxid) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.power_levels") - t.equal(key, "") - t.deepEqual(content, { - users: { - "@_ooye_bot:cadence.moe": 100, - "@cadence:cadence.moe": 100 - } - }) - t.equal(mxid, undefined) - return "$sent" - } - }) - t.equal(called, 3) -}) - -test("set user power: privileged users must demote themselves", async t => { - let called = 0 - await setUserPower("!room", "@cadence:cadence.moe", 0, { - async getStateEvent(roomID, type, key) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.power_levels") - t.equal(key, "") - return { - users: { - "@cadence:cadence.moe": 100, - "@_ooye_bot:cadence.moe": 100 - } - } - }, - async getStateEventOuter(roomID, type, key) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.create") - t.equal(key, "") - return { - type: "m.room.create", - state_key: "", - sender: "@_ooye_bot:cadence.moe", - room_id: "!room", - origin_server_ts: 0, - event_id: "$create", - content: { - room_version: "11" - } - } - }, - async sendState(roomID, type, key, content, mxid) { - called++ - t.equal(roomID, "!room") - t.equal(type, "m.room.power_levels") - t.equal(key, "") - t.deepEqual(content, { - users: {"@_ooye_bot:cadence.moe": 100} - }) - t.equal(mxid, "@cadence:cadence.moe") - return "$sent" - } - }) - t.equal(called, 3) -}) - -module.exports.mockGetEffectivePower = mockGetEffectivePower diff --git a/src/types.d.ts b/src/types.d.ts index a85907d..f9488b9 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,5 +1,3 @@ -import * as DiscordTypes from "discord-api-types/v10" - export type AppServiceRegistrationConfig = { id: string as_token: string @@ -145,6 +143,21 @@ export namespace Event { } } + export type BaseStateEvent = { + type: string + room_id: string + sender: string + content: any + state_key: string + origin_server_ts: number + unsigned?: any + event_id: string + user_id: string + age: number + replaces_state: string + prev_content?: any + } + export type StrippedChildStateEvent = { type: string state_key: string @@ -161,7 +174,7 @@ export namespace Event { } export type M_Room_Create = { - additional_creators?: string[] + additional_creators: string[] "m.federate"?: boolean room_version: string type?: string @@ -208,7 +221,6 @@ export namespace Event { filename?: string url: string info?: any - "page.codeberg.everypizza.msc4193.spoiler"?: boolean "m.relates_to"?: { "m.in_reply_to": { event_id: string @@ -226,7 +238,6 @@ export namespace Event { format?: "org.matrix.custom.html" formatted_body?: string filename?: string - "page.codeberg.everypizza.msc4193.spoiler"?: boolean file: { url: string iv: string @@ -271,49 +282,6 @@ export namespace Event { export type Outer_M_Sticker = Outer