From 475cd5b7243558d1e8d3821d8c1e8a898693b3ed Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 6 Oct 2023 12:31:10 +1300 Subject: [PATCH] Add new WHERE feature to my funny orm --- d2m/actions/add-reaction.js | 2 +- d2m/actions/announce-thread.js | 2 +- d2m/actions/create-room.js | 8 ++-- d2m/actions/create-space.js | 10 ++--- d2m/actions/delete-message.js | 4 +- d2m/actions/register-user.js | 12 +++--- d2m/actions/remove-reaction.js | 14 +++---- d2m/converters/edit-to-changes.js | 19 ++++----- d2m/converters/emoji-to-key.js | 2 +- d2m/converters/lottie.js | 2 +- d2m/converters/message-to-event.js | 20 ++++----- d2m/converters/thread-to-announcement.js | 2 +- d2m/converters/user-to-mxid.js | 4 +- d2m/event-dispatcher.js | 18 ++++---- .../0003-distinguish-column-names.sql | 4 ++ db/{orm-utils.d.ts => orm-defs.d.ts} | 2 +- db/{orm-utils.js => orm-defs.js} | 0 db/orm.js | 42 +++++++++++++++---- db/orm.test.js | 19 ++++++++- discord/discord-command-handler.js | 6 +-- m2d/actions/add-reaction.js | 4 +- m2d/actions/channel-webhook.js | 2 +- m2d/actions/redact.js | 5 ++- m2d/actions/send-event.js | 2 +- m2d/converters/emoji.js | 18 ++++---- m2d/converters/event-to-message.js | 16 +++---- m2d/converters/event-to-message.test.js | 6 +-- matrix/file.js | 2 +- matrix/matrix-command-handler.js | 2 +- test/ooye-test-data.sql | 5 ++- 30 files changed, 149 insertions(+), 105 deletions(-) rename db/{orm-utils.d.ts => orm-defs.d.ts} (98%) rename db/{orm-utils.js => orm-defs.js} (100%) diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index 1c2380fb..f962a737 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -21,7 +21,7 @@ async function addReaction(data) { const user = data.member?.user assert.ok(user && user.username) - const parentID = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id) // 0 = primary + const parentID = select("event_message", "event_id", {message_id: data.message_id, part: 0}).pluck().get() // 0 = primary if (!parentID) return // Nothing can be done if the parent message was never bridged. assert.equal(typeof parentID, "string") diff --git a/d2m/actions/announce-thread.js b/d2m/actions/announce-thread.js index e52a2a38..86c64125 100644 --- a/d2m/actions/announce-thread.js +++ b/d2m/actions/announce-thread.js @@ -15,7 +15,7 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async function announceThread(parentRoomID, threadRoomID, thread) { - const creatorMxid = select("sim", "mxid", "WHERE user_id = ?").pluck().get(thread.owner_id) + const creatorMxid = select("sim", "mxid", {user_id: thread.owner_id}).pluck().get() const content = await threadToAnnouncement.threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thread, {api}) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 58488e79..51926ee9 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -71,7 +71,7 @@ async function channelToKState(channel, guild) { const spaceID = await createSpace.ensureSpace(guild) assert.ok(typeof spaceID === "string") - const row = select("channel_room", ["nick", "custom_avatar"], "WHERE channel_id = ?").get(channel.id) + const row = select("channel_room", ["nick", "custom_avatar"], {channel_id: channel.id}).get() const customName = row?.nick const customAvatar = row?.custom_avatar const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) @@ -248,7 +248,7 @@ async function _syncRoom(channelID, shouldActuallySync) { await inflightRoomCreate.get(channelID) // just waiting, and then doing a new db query afterwards, is the simplest way of doing it } - const existing = select("channel_room", ["room_id", "thread_parent"], "WHERE channel_id = ?").get(channelID) + const existing = select("channel_room", ["room_id", "thread_parent"], {channel_id: channelID}).get() if (!existing) { const creation = (async () => { @@ -308,9 +308,9 @@ async function _unbridgeRoom(channelID) { } async function unbridgeDeletedChannel(channelID, guildID) { - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channelID) + const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() assert.ok(roomID) - const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guildID) + const spaceID = select("guild_space", "space_id", {guild_id: guildID}).pluck().get() assert.ok(spaceID) // remove room from being a space member diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index f90c1c3c..da877e05 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -86,7 +86,7 @@ async function _syncSpace(guild, shouldActuallySync) { await inflightSpaceCreate.get(guild.id) // just waiting, and then doing a new db query afterwards, is the simplest way of doing it } - const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guild.id) + const spaceID = select("guild_space", "space_id", {guild_id: guild.id}).pluck().get() if (!spaceID) { const creation = (async () => { @@ -117,7 +117,7 @@ async function _syncSpace(guild, shouldActuallySync) { const newAvatarState = spaceDiff["m.room.avatar/"] if (guild.icon && newAvatarState?.url) { // don't try to update rooms with custom avatars though - const roomsWithCustomAvatars = select("channel_room", "room_id", "WHERE custom_avatar IS NOT NULL").pluck().all() + const roomsWithCustomAvatars = select("channel_room", "room_id", {}, "WHERE custom_avatar IS NOT NULL").pluck().all() const childRooms = ks.kstateToState(spaceKState).filter(({type, state_key, content}) => { return type === "m.space.child" && "via" in content && !roomsWithCustomAvatars.includes(state_key) @@ -159,7 +159,7 @@ async function syncSpaceFully(guildID) { const guild = discord.guilds.get(guildID) assert.ok(guild) - const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guildID) + const spaceID = select("guild_space", "space_id", {guild_id: guildID}).pluck().get() const guildKState = await guildToKState(guild) @@ -180,7 +180,7 @@ async function syncSpaceFully(guildID) { }).map(({state_key}) => state_key) for (const roomID of childRooms) { - const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(roomID) + const channelID = select("channel_room", "channel_id", {room_id: roomID}).pluck().get() if (!channelID) continue if (discord.channels.has(channelID)) { await createRoom.syncRoom(channelID) @@ -198,7 +198,7 @@ async function syncSpaceFully(guildID) { async function syncSpaceExpressions(data) { // No need for kstate here. Each of these maps to a single state event, which will always overwrite what was there before. I can just send the state event. - const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(data.guild_id) + const spaceID = select("guild_space", "space_id", {guild_id: data.guild_id}).pluck().get() if (!spaceID) return if ("emojis" in data && data.emojis.length) { diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js index ebbbbe45..30c31d47 100644 --- a/d2m/actions/delete-message.js +++ b/d2m/actions/delete-message.js @@ -9,10 +9,10 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data */ async function deleteMessage(data) { - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get() if (!roomID) return - const eventsToRedact = select("event_message", "event_id", "WHERE message_id = ?").pluck().all(data.id) + const eventsToRedact = select("event_message", "event_id", {message_id: data.id}).pluck().all() for (const eventID of eventsToRedact) { // Unfortunately, we can't specify a sender to do the redaction as, unless we find out that info via the audit logs await api.redactEvent(roomID, eventID) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index dc3995dd..ca8fb8ee 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -51,7 +51,7 @@ async function createSim(user) { */ async function ensureSim(user) { let mxid = null - const existing = select("sim", "mxid", "WHERE user_id = ?").pluck().get(user.id) + const existing = select("sim", "mxid", {user_id: user.id}).pluck().get() if (existing) { mxid = existing } else { @@ -74,7 +74,7 @@ async function ensureSimJoined(user, roomID) { const mxid = await ensureSim(user) // Ensure joined - const existing = select("sim_member", "mxid", "WHERE room_id = ? AND mxid = ?").pluck().get(roomID, mxid) + const existing = select("sim_member", "mxid", {room_id: roomID, mxid}).pluck().get() if (!existing) { try { await api.inviteToRoom(roomID, mxid) @@ -143,7 +143,7 @@ async function syncUser(user, member, guildID, roomID) { const mxid = await ensureSimJoined(user, roomID) const content = await memberToStateContent(user, member, guildID) const currentHash = hashProfileContent(content) - const existingHash = select("sim_member", "hashed_profile_content", "WHERE room_id = ? AND mxid = ?").safeIntegers().pluck().get(roomID, mxid) + const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get() // only do the actual sync if the hash has changed since we last looked if (existingHash !== currentHash) { await api.sendState(roomID, "m.room.member", mxid, content, mxid) @@ -153,9 +153,9 @@ async function syncUser(user, member, guildID, roomID) { } async function syncAllUsersInRoom(roomID) { - const mxids = select("sim_member", "mxid", "WHERE room_id = ?").pluck().all(roomID) + const mxids = select("sim_member", "mxid", {room_id: roomID}).pluck().all() - const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(roomID) + const channelID = select("channel_room", "channel_id", {room_id: roomID}).pluck().get() assert.ok(typeof channelID === "string") /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ @@ -164,7 +164,7 @@ async function syncAllUsersInRoom(roomID) { assert.ok(typeof guildID === "string") for (const mxid of mxids) { - const userID = select("sim", "user_id", "WHERE mxid = ?").pluck().get(mxid) + const userID = select("sim", "user_id", {mxid}).pluck().get() assert.ok(typeof userID === "string") /** @ts-ignore @type {Required} */ diff --git a/d2m/actions/remove-reaction.js b/d2m/actions/remove-reaction.js index d84531e4..eec93a4d 100644 --- a/d2m/actions/remove-reaction.js +++ b/d2m/actions/remove-reaction.js @@ -18,9 +18,9 @@ const emoji = sync.require("../../m2d/converters/emoji") * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveDispatchData} data */ async function removeReaction(data) { - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get() if (!roomID) return - const eventIDForMessage = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id) + const eventIDForMessage = select("event_message", "event_id", {message_id: data.message_id, part: 0}).pluck().get() if (!eventIDForMessage) return /** @type {Ty.Pagination>} */ @@ -42,7 +42,7 @@ async function removeReaction(data) { } if (!lookingAtMatrixReaction && !wantToRemoveMatrixReaction) { // We are removing a Discord user's reaction, so we just make the sim user remove it. - const mxid = select("sim", "mxid", "WHERE user_id = ?").pluck().get(data.user_id) + const mxid = select("sim", "mxid", {user_id: data.user_id}).pluck().get() if (mxid === event.sender) { await api.redactEvent(roomID, event.event_id, mxid) } @@ -55,9 +55,9 @@ async function removeReaction(data) { * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveEmojiDispatchData} data */ async function removeEmojiReaction(data) { - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get() if (!roomID) return - const eventIDForMessage = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id) + const eventIDForMessage = select("event_message", "event_id", {message_id: data.message_id, part: 0}).pluck().get() if (!eventIDForMessage) return /** @type {Ty.Pagination>} */ @@ -79,9 +79,9 @@ async function removeEmojiReaction(data) { * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveAllDispatchData} data */ async function removeAllReactions(data) { - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get() if (!roomID) return - const eventIDForMessage = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id) + const eventIDForMessage = select("event_message", "event_id", {message_id: data.message_id, part: 0}).pluck().get() if (!eventIDForMessage) return /** @type {Ty.Pagination>} */ diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 9144ccba..8df39798 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -1,9 +1,9 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict const passthrough = require("../../passthrough") -const {discord, sync, db, select} = passthrough +const {discord, sync, db, select, from} = passthrough /** @type {import("./message-to-event")} */ const messageToEvent = sync.require("../converters/message-to-event") /** @type {import("../actions/register-user")} */ @@ -21,15 +21,12 @@ const createRoom = sync.require("../actions/create-room") async function editToChanges(message, guild, api) { // Figure out what events we will be replacing - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(message.channel_id) - let senderMxid = select("sim", "mxid", "WHERE user_id = ?").pluck().get(message.author.id) || null - if (senderMxid) { - const senderIsInRoom = select("sim_member", "mxid", "WHERE room_id = ? AND mxid = ?").get(roomID, senderMxid) - if (!senderIsInRoom) { - senderMxid = null // just send as ooye bot - } - } - const oldEventRows = select("event_message", ["event_id", "event_type", "event_subtype", "part"], "WHERE message_id = ?").all(message.id) + const roomID = select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get() + assert(roomID) + /** @type {string?} Null if we don't have a sender in the room, which will happen if it's a webhook's message. The bridge bot will do the edit instead. */ + const senderMxid = from("sim").join("sim_member", "mxid").where({user_id: message.author.id}).pluck("mxid").get() || null + + const oldEventRows = select("event_message", ["event_id", "event_type", "event_subtype", "part"], {message_id: message.id}).all() // Figure out what we will be replacing them with diff --git a/d2m/converters/emoji-to-key.js b/d2m/converters/emoji-to-key.js index 7d04f970..bfd05c23 100644 --- a/d2m/converters/emoji-to-key.js +++ b/d2m/converters/emoji-to-key.js @@ -14,7 +14,7 @@ async function emojiToKey(emoji) { let key if (emoji.id) { // Custom emoji - const mxc = select("emoji", "mxc_url", "WHERE id = ?").pluck().get(emoji.id) + const mxc = select("emoji", "mxc_url", {emoji_id: emoji.id}).pluck().get(emoji.id) if (mxc) { // The custom emoji is registered and we should send it key = mxc diff --git a/d2m/converters/lottie.js b/d2m/converters/lottie.js index a00f98f1..a0d1cd18 100644 --- a/d2m/converters/lottie.js +++ b/d2m/converters/lottie.js @@ -38,7 +38,7 @@ const Rlottie = (async () => { * @returns {Promise<{mxc_url: string, info: typeof INFO}>} */ async function convert(stickerItem) { - const existingMxc = select("lottie", "mxc_url", "WHERE sticker_id = ?").pluck().get(stickerItem.id) + const existingMxc = select("lottie", "mxc_url", {sticker_id: stickerItem.id}).pluck().get() if (existingMxc) return {mxc_url: existingMxc, info: INFO} const r = await Rlottie const res = await fetch(file.DISCORD_IMAGES_BASE + file.sticker(stickerItem)) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 62a032f9..e3b932f2 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -19,7 +19,7 @@ function getDiscordParseCallbacks(message, useHTML) { return { /** @param {{id: string, type: "discordUser"}} node */ user: node => { - const mxid = select("sim", "mxid", "WHERE user_id = ?").pluck().get(node.id) + const mxid = select("sim", "mxid", {user_id: node.id}).pluck().get() const username = message.mentions.find(ment => ment.id === node.id)?.username || node.id if (mxid && useHTML) { return `@${username}` @@ -29,7 +29,7 @@ function getDiscordParseCallbacks(message, useHTML) { }, /** @param {{id: string, type: "discordChannel"}} node */ channel: node => { - const row = select("channel_room", ["room_id", "name", "nick"], "WHERE channel_id = ?").get(node.id) + const row = select("channel_room", ["room_id", "name", "nick"], {channel_id: node.id}).get() if (!row) { return `<#${node.id}>` // fallback for when this channel is not bridged } else if (useHTML) { @@ -41,7 +41,7 @@ function getDiscordParseCallbacks(message, useHTML) { /** @param {{animated: boolean, name: string, id: string, type: "discordEmoji"}} node */ emoji: node => { if (useHTML) { - const mxc = select("emoji", "mxc_url", "WHERE id = ?").pluck().get(node.id) + const mxc = select("emoji", "mxc_url", {emoji_id: node.id}).pluck().get() if (mxc) { return `:${node.name}:` } else { // We shouldn't get here since all emojis should have been added ahead of time in the messageToEvent function. @@ -85,8 +85,8 @@ async function messageToEvent(message, guild, options = {}, di) { const ref = message.message_reference assert(ref) assert(ref.message_id) - const eventID = select("event_message", "event_id", "WHERE message_id = ?").pluck().get(ref.message_id) - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(ref.channel_id) + const eventID = select("event_message", "event_id", {message_id: ref.message_id}).pluck().get() + const roomID = select("channel_room", "room_id", {channel_id: ref.channel_id}).pluck().get() if (!eventID || !roomID) return [] const event = await di.api.getEvent(roomID, eventID) return [{ @@ -138,8 +138,8 @@ async function messageToEvent(message, guild, options = {}, di) { async function addTextEvent(content, msgtype, {scanMentions}) { content = content.replace(/https:\/\/(?:ptb\.|canary\.|www\.)?discord(?:app)?\.com\/channels\/([0-9]+)\/([0-9]+)\/([0-9]+)/, (whole, guildID, channelID, messageID) => { - const eventID = select("event_message", "event_id", "WHERE message_id = ?").pluck().get(messageID) - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channelID) + const eventID = select("event_message", "event_id", {message_id: messageID}).pluck().get() + const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() if (eventID && roomID) { return `https://matrix.to/#/${roomID}/${eventID}` } else { @@ -155,8 +155,8 @@ async function messageToEvent(message, guild, options = {}, di) { const id = match[3] const name = match[2] const animated = +!!match[1] - const row = select("emoji", "id", "WHERE id = ?").pluck().get(id) - if (!row) { + const exists = select("emoji", "emoji_id", {emoji_id: id}).pluck().get() + if (!exists) { // The custom emoji is not registered. We will register it and then add it. emojiDownloads.push( file.uploadDiscordFileToMxc(file.emoji(id, animated)).then(mxc => { @@ -182,7 +182,7 @@ async function messageToEvent(message, guild, options = {}, di) { const matches = [...content.matchAll(/@ ?([a-z0-9._]+)\b/gi)] if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { const writtenMentionsText = matches.map(m => m[1].toLowerCase()) - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(message.channel_id) + 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)) { diff --git a/d2m/converters/thread-to-announcement.js b/d2m/converters/thread-to-announcement.js index 003134b2..8ef3a673 100644 --- a/d2m/converters/thread-to-announcement.js +++ b/d2m/converters/thread-to-announcement.js @@ -17,7 +17,7 @@ const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API */ async function threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thread, di) { - const branchedFromEventID = select("event_message", "event_id", "WHERE message_id = ?").pluck().get(thread.id) + const branchedFromEventID = select("event_message", "event_id", {message_id: thread.id}).pluck().get() /** @type {{"m.mentions"?: any, "m.in_reply_to"?: any}} */ const context = {} if (branchedFromEventID) { diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js index 8514fc9f..35a3b5ed 100644 --- a/d2m/converters/user-to-mxid.js +++ b/d2m/converters/user-to-mxid.js @@ -53,7 +53,7 @@ function userToSimName(user) { assert.notEqual(user.discriminator, "0000", "cannot create user for a webhook") // 1. Is sim user already registered? - const existing = select("sim", "sim_name", "WHERE user_id = ?").pluck().get(user.id) + const existing = select("sim", "sim_name", {user_id: user.id}).pluck().get() if (existing) return existing // 2. Register based on username (could be new or old format) @@ -64,7 +64,7 @@ function userToSimName(user) { } // Check for conflicts with already registered sims - const matches = select("sim", "sim_name", "WHERE sim_name LIKE ? ESCAPE '@'").pluck().all(downcased + "%") + const matches = select("sim", "sim_name", {}, "WHERE sim_name LIKE ? ESCAPE '@'").pluck().all(downcased + "%") // Keep generating until we get a suggestion that doesn't conflict for (const suggestion of generateLocalpartAlternatives(preferences)) { if (!matches.includes(suggestion)) return suggestion diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 0fd1da50..525b7353 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -46,7 +46,7 @@ module.exports = { const channelID = gatewayMessage.d.channel_id if (!channelID) return - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channelID) + const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() if (!roomID) return let stackLines = e.stack.split("\n") @@ -85,7 +85,7 @@ module.exports = { async checkMissedMessages(client, guild) { if (guild.unavailable) return const bridgedChannels = select("channel_room", "channel_id").pluck().all() - const prepared = select("event_message", "event_id", "WHERE message_id = ?").pluck() + const prepared = select("event_message", "event_id", {}, "WHERE message_id = ?").pluck() for (const channel of guild.channels.concat(guild.threads)) { if (!bridgedChannels.includes(channel.id)) continue if (!channel.last_message_id) continue @@ -129,7 +129,7 @@ module.exports = { * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async onThreadCreate(client, thread) { - const parentRoomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(thread.parent_id) + const parentRoomID = select("channel_room", "room_id", {channel_id: thread.parent_id}).pluck().get() if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel const threadRoomID = await createRoom.syncRoom(thread.id) // Create room (will share the same inflight as the initial message to the thread) await announceThread.announceThread(parentRoomID, threadRoomID, thread) @@ -140,7 +140,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayGuildUpdateDispatchData} guild */ async onGuildUpdate(client, guild) { - const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guild.id) + const spaceID = select("guild_space", "space_id", {guild_id: guild.id}).pluck().get() if (!spaceID) return await createSpace.syncSpace(guild) }, @@ -151,7 +151,7 @@ module.exports = { * @param {boolean} isThread */ async onChannelOrThreadUpdate(client, channelOrThread, isThread) { - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channelOrThread.id) + const roomID = select("channel_room", "room_id", {channel_id: channelOrThread.id}).pluck().get() if (!roomID) return // No target room to update the data on await createRoom.syncRoom(channelOrThread.id) }, @@ -162,7 +162,7 @@ module.exports = { */ async onMessageCreate(client, message) { if (message.webhook_id) { - const row = select("webhook", "webhook_id", "WHERE webhook_id = ?").pluck().get(message.webhook_id) + const row = select("webhook", "webhook_id", {webhook_id: message.webhook_id}).pluck().get() if (row) { // The message was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. return @@ -183,7 +183,7 @@ module.exports = { */ async onMessageUpdate(client, data) { if (data.webhook_id) { - const row = select("webhook", "webhook_id", "WHERE webhook_id = ?").pluck().get(data.webhook_id) + const row = select("webhook", "webhook_id", {webhook_id: data.webhook_id}).pluck().get() if (row) { // The update was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. return @@ -249,9 +249,9 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayTypingStartDispatchData} data */ async onTypingStart(client, data) { - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get() if (!roomID) return - const mxid = from("sim").join("sim_member", "mxid").and("WHERE user_id = ? AND room_id = ?").pluck("mxid").get(data.user_id, roomID) + const mxid = from("sim").join("sim_member", "mxid").where({user_id: data.user_id, room_id: roomID}).pluck("mxid").get() if (!mxid) return // Each Discord user triggers the notification every 8 seconds as long as they remain typing. // Discord does not send typing stopped events, so typing only stops if the timeout is reached or if the user sends their message. diff --git a/db/migrations/0003-distinguish-column-names.sql b/db/migrations/0003-distinguish-column-names.sql index 4ffe0ac8..f94720be 100644 --- a/db/migrations/0003-distinguish-column-names.sql +++ b/db/migrations/0003-distinguish-column-names.sql @@ -12,4 +12,8 @@ ALTER TABLE lottie RENAME COLUMN id TO sticker_id; ALTER TABLE sim RENAME COLUMN discord_id TO user_id; +-- Rename id to emoji_id so joins make sense in the future + +ALTER TABLE emoji RENAME COLUMN id TO emoji_id; + COMMIT; diff --git a/db/orm-utils.d.ts b/db/orm-defs.d.ts similarity index 98% rename from db/orm-utils.d.ts rename to db/orm-defs.d.ts index a0cd8174..9524f205 100644 --- a/db/orm-utils.d.ts +++ b/db/orm-defs.d.ts @@ -64,7 +64,7 @@ export type Models = { } emoji: { - id: string + emoji_id: string name: string animated: number mxc_url: string diff --git a/db/orm-utils.js b/db/orm-defs.js similarity index 100% rename from db/orm-utils.js rename to db/orm-defs.js diff --git a/db/orm.js b/db/orm.js index 61957537..9325bc0b 100644 --- a/db/orm.js +++ b/db/orm.js @@ -1,19 +1,28 @@ // @ts-check const {db} = require("../passthrough") -const U = require("./orm-utils") +const U = require("./orm-defs") /** * @template {keyof U.Models} Table * @template {keyof U.Models[Table]} Col * @param {Table} table * @param {Col[] | Col} cols + * @param {Partial} where * @param {string} [e] */ -function select(table, cols, e = "") { +function select(table, cols, where = {}, e = "") { if (!Array.isArray(cols)) cols = [cols] + const parameters = [] + const wheres = Object.entries(where).map(([col, value]) => { + parameters.push(value) + return `"${col}" = ?` + }) + const whereString = wheres.length ? " WHERE " + wheres.join(" AND ") : "" /** @type {U.Prepared>} */ - const prepared = db.prepare(`SELECT ${cols.map(k => `"${String(k)}"`).join(", ")} FROM ${table} ${e}`) + const prepared = db.prepare(`SELECT ${cols.map(k => `"${String(k)}"`).join(", ")} FROM ${table} ${whereString} ${e}`) + prepared.get = prepared.get.bind(prepared, ...parameters) + prepared.all = prepared.all.bind(prepared, ...parameters) return prepared } @@ -26,13 +35,18 @@ class From { * @param {Table} table */ constructor(table) { - /** @type {Table[]} */ + /** @private @type {Table[]} */ this.tables = [table] - + /** @private */ this.sql = "" + /** @private */ this.cols = [] + /** @private */ this.using = [] + /** @private */ this.isPluck = false + /** @private */ + this.parameters = [] } /** @@ -78,7 +92,19 @@ class From { * @param {string} sql */ and(sql) { - this.sql = sql + this.sql += " " + sql + return this + } + + /** + * @param {Partial} conditions + */ + where(conditions) { + const wheres = Object.entries(conditions).map(([col, value]) => { + this.parameters.push(value) + return `"${col}" = ?` + }) + this.sql += " WHERE " + wheres.join(" AND ") return this } @@ -98,12 +124,12 @@ class From { get(..._) { const prepared = this.prepare() - return prepared.get(..._) + return prepared.get(...this.parameters, ..._) } all(..._) { const prepared = this.prepare() - return prepared.all(..._) + return prepared.all(...this.parameters, ..._) } } diff --git a/db/orm.test.js b/db/orm.test.js index 7b8237b9..36e95c25 100644 --- a/db/orm.test.js +++ b/db/orm.test.js @@ -6,7 +6,7 @@ const data = require("../test/data") const {db, select, from} = require("../passthrough") test("orm: select: get works", t => { - const row = select("guild_space", "guild_id", "WHERE space_id = ?").get("!jjWAGMeQdNrVZSSfvz:cadence.moe") + const row = select("guild_space", "guild_id", {}, "WHERE space_id = ?").get("!jjWAGMeQdNrVZSSfvz:cadence.moe") t.equal(row?.guild_id, data.guild.general.id) }) @@ -16,10 +16,20 @@ test("orm: from: get works", t => { }) test("orm: select: get pluck works", t => { - const guildID = select("guild_space", "guild_id", "WHERE space_id = ?").pluck().get("!jjWAGMeQdNrVZSSfvz:cadence.moe") + const guildID = select("guild_space", "guild_id", {}, "WHERE space_id = ?").pluck().get("!jjWAGMeQdNrVZSSfvz:cadence.moe") t.equal(guildID, data.guild.general.id) }) +test("orm: select: get, where and pluck works", t => { + const channelID = select("message_channel", "channel_id", {message_id: "1128118177155526666"}).pluck().get() + t.equal(channelID, "112760669178241024") +}) + +test("orm: select: all, where and pluck works on multiple columns", t => { + const names = select("member_cache", "displayname", {room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", mxid: "@cadence:cadence.moe"}).pluck().all() + t.deepEqual(names, ["cadence [they]"]) +}) + test("orm: from: get pluck works", t => { const guildID = from("guild_space").pluck("guild_id").and("WHERE space_id = ?").get("!jjWAGMeQdNrVZSSfvz:cadence.moe") t.equal(guildID, data.guild.general.id) @@ -29,3 +39,8 @@ test("orm: from: join and pluck works", t => { const mxid = from("sim").join("sim_member", "mxid").and("WHERE user_id = ? AND room_id = ?").pluck("mxid").get("771520384671416320", "!hYnGGlPHlbujVVfktC:cadence.moe") t.equal(mxid, "@_ooye_bojack_horseman:cadence.moe") }) + +test("orm: from: where and pluck works", t => { + const subtypes = from("event_message").where({message_id: "1141501302736695316"}).pluck("event_subtype").all() + t.deepEqual(subtypes.sort(), ["m.image", "m.text"]) +}) diff --git a/discord/discord-command-handler.js b/discord/discord-command-handler.js index 8fb6aec8..57115617 100644 --- a/discord/discord-command-handler.js +++ b/discord/discord-command-handler.js @@ -81,7 +81,7 @@ const commands = [{ execute: replyctx( async (message, channel, guild, ctx) => { // Guard - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channel.id) + const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() if (!roomID) return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "This channel isn't bridged to the other side." @@ -125,8 +125,8 @@ const commands = [{ execute: replyctx( async (message, channel, guild, ctx) => { // Check guild is bridged - const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guild.id) - const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channel.id) + const spaceID = select("guild_space", "space_id", {guild_id: guild.id}).pluck().get() + const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() if (!spaceID || !roomID) return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "This server isn't bridged to Matrix, so you can't invite Matrix users." diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js index bd8eeffe..eb12bd43 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -14,9 +14,9 @@ const emoji = sync.require("../converters/emoji") * @param {Ty.Event.Outer} event */ async function addReaction(event) { - const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(event.room_id) + const channelID = select("channel_room", "channel_id", {room_id: event.room_id}).pluck().get() if (!channelID) return // We just assume the bridge has already been created - const messageID = select("event_message", "message_id", "WHERE event_id = ? AND part = 0").pluck().get(event.content["m.relates_to"].event_id) // 0 = primary + const messageID = select("event_message", "message_id", {event_id: event.content["m.relates_to"].event_id, part: 0}).pluck().get() // 0 = primary if (!messageID) return // Nothing can be done if the parent message was never bridged. const key = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index 0fde77a7..3bb728d2 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -14,7 +14,7 @@ const {discord, db, select} = passthrough */ async function ensureWebhook(channelID, forceCreate = false) { if (!forceCreate) { - const row = select("webhook", ["webhook_id", "webhook_token"], "WHERE channel_id = ?").get(channelID) + const row = select("webhook", ["webhook_id", "webhook_token"], {channel_id: channelID}).get() if (row) { return { id: row.webhook_id, diff --git a/m2d/actions/redact.js b/m2d/actions/redact.js index 74b92b89..316b4665 100644 --- a/m2d/actions/redact.js +++ b/m2d/actions/redact.js @@ -12,7 +12,7 @@ const utils = sync.require("../converters/utils") * @param {Ty.Event.Outer_M_Room_Redaction} event */ async function deleteMessage(event) { - const rows = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").and("WHERE event_id = ?").all(event.redacts) + const rows = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").where({event_id: event.redacts}).all() for (const row of rows) { discord.snow.channel.deleteMessage(row.channel_id, row.message_id, event.content.reason) } @@ -23,7 +23,8 @@ async function deleteMessage(event) { */ async function removeReaction(event) { const hash = utils.getEventIDHash(event.redacts) - const row = from("reaction").join("message_channel", "message_id").select("channel_id", "message_id", "encoded_emoji").and("WHERE hashed_event_id = ?").get(hash) + // TODO: this works but fix the type + const row = from("reaction").join("message_channel", "message_id").select("channel_id", "message_id", "encoded_emoji").where({hashed_event_id: hash}).get() if (!row) return await discord.snow.channel.deleteReactionSelf(row.channel_id, row.message_id, row.encoded_emoji) db.prepare("DELETE FROM reaction WHERE hashed_event_id = ?").run(hash) diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index d5797b94..13966fb5 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -58,7 +58,7 @@ async function resolvePendingFiles(message) { /** @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File | Ty.Event.Outer_M_Sticker} event */ async function sendEvent(event) { - const row = select("channel_room", ["channel_id", "thread_parent"], "WHERE room_id = ?").get(event.room_id) + const row = select("channel_room", ["channel_id", "thread_parent"], {room_id: event.room_id}).get() if (!row) return // allow the bot to exist in unbridged rooms, just don't do anything with it let channelID = row.channel_id let threadID = undefined diff --git a/m2d/converters/emoji.js b/m2d/converters/emoji.js index a505cd19..e17367ca 100644 --- a/m2d/converters/emoji.js +++ b/m2d/converters/emoji.js @@ -7,19 +7,19 @@ const passthrough = require("../../passthrough") const {sync, select} = passthrough /** - * @param {string} emoji + * @param {string} input * @param {string | null | undefined} shortcode * @returns {string?} */ -function encodeEmoji(emoji, shortcode) { +function encodeEmoji(input, shortcode) { let discordPreferredEncoding - if (emoji.startsWith("mxc://")) { + if (input.startsWith("mxc://")) { // Custom emoji - let row = select("emoji", ["id", "name"], "WHERE mxc_url = ?").get(emoji) + let row = select("emoji", ["emoji_id", "name"], {mxc_url: input}).get() if (!row && shortcode) { // Use the name to try to find a known emoji with the same name. const name = shortcode.replace(/^:|:$/g, "") - row = select("emoji", ["id", "name"], "WHERE name = ?").get(name) + row = select("emoji", ["emoji_id", "name"], {name: name}).get() } if (!row) { // We don't have this emoji and there's no realistic way to just-in-time upload a new emoji somewhere. @@ -27,11 +27,11 @@ function encodeEmoji(emoji, shortcode) { return null } // Cool, we got an exact or a candidate emoji. - discordPreferredEncoding = encodeURIComponent(`${row.name}:${row.id}`) + discordPreferredEncoding = encodeURIComponent(`${row.name}:${row.emoji_id}`) } else { // Default emoji // https://github.com/discord/discord-api-docs/issues/2723#issuecomment-807022205 ???????????? - const encoded = encodeURIComponent(emoji) + const encoded = encodeURIComponent(input) const encodedTrimmed = encoded.replace(/%EF%B8%8F/g, "") const forceTrimmedList = [ @@ -42,10 +42,10 @@ function encodeEmoji(emoji, shortcode) { discordPreferredEncoding = ( forceTrimmedList.includes(encodedTrimmed) ? encodedTrimmed - : encodedTrimmed !== encoded && [...emoji].length === 2 ? encoded + : encodedTrimmed !== encoded && [...input].length === 2 ? encoded : encodedTrimmed) - console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding) + console.log("add reaction from matrix:", input, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding) } return discordPreferredEncoding } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index ae676457..bd9c37a3 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -121,7 +121,7 @@ turndownService.addRule("emoji", { replacement: function (content, node) { const mxcUrl = node.getAttribute("src") // Get the known emoji from the database. (We may not be able to actually use this if it was from another server.) - const row = select("emoji", ["id", "name", "animated"], "WHERE mxc_url = ?").get(mxcUrl) + const row = select("emoji", ["emoji_id", "name", "animated"], {mxc_url: mxcUrl}).get() // Also guess a suitable emoji based on the ID (if available) or name let guess = null const guessedName = node.getAttribute("title").replace(/^:|:$/g, "") @@ -129,7 +129,7 @@ turndownService.addRule("emoji", { /** @type {{name: string, id: string, animated: number}[]} */ // @ts-ignore const emojis = guild.emojis - const match = emojis.find(e => e.id === row?.id) || emojis.find(e => e.name === guessedName) || emojis.find(e => e.name?.toLowerCase() === guessedName.toLowerCase()) + const match = emojis.find(e => e.id === row?.emoji_id) || emojis.find(e => e.name === guessedName) || emojis.find(e => e.name?.toLowerCase() === guessedName.toLowerCase()) if (match) { guess = match break @@ -180,7 +180,7 @@ turndownService.addRule("fencedCodeBlock", { * @returns {Promise<{displayname?: string?, avatar_url?: string?}>} */ async function getMemberFromCacheOrHomeserver(roomID, mxid, api) { - const row = select("member_cache", ["displayname", "avatar_url"], "WHERE room_id = ? AND mxid = ?").get(roomID, mxid) + const row = select("member_cache", ["displayname", "avatar_url"], {room_id: roomID, mxid}).get() if (row) return row return api.getStateEvent(roomID, "m.room.member", mxid).then(event => { db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(roomID, mxid, event?.displayname || null, event?.avatar_url || null) @@ -285,7 +285,7 @@ async function eventToMessage(event, guild, di) { if (relType !== "m.replace") return const originalEventId = relatesTo.event_id if (!originalEventId) return - messageIDsToEdit = select("event_message", "message_id", "WHERE event_id = ? ORDER BY part").pluck().all(originalEventId) + messageIDsToEdit = select("event_message", "message_id", {event_id: originalEventId}, "ORDER BY part").pluck().all() if (!messageIDsToEdit.length) return // Ok, it's an edit. @@ -316,7 +316,7 @@ async function eventToMessage(event, guild, di) { if (!repliedToEventId) return let repliedToEvent = await di.api.getEvent(event.room_id, repliedToEventId) if (!repliedToEvent) return - const row = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").and("WHERE event_id = ? ORDER BY part").get(repliedToEventId) + const row = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").where({event_id: repliedToEventId}).and("ORDER BY part").get() if (row) { replyLine = `<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/${guild.id}/${row.channel_id}/${row.message_id} ` } else { @@ -324,7 +324,7 @@ async function eventToMessage(event, guild, di) { } const sender = repliedToEvent.sender const senderName = sender.match(/@([^:]*)/)?.[1] || sender - const authorID = select("sim", "user_id", "WHERE mxid = ?").pluck().get(repliedToEvent.sender) + const authorID = select("sim", "user_id", {mxid: repliedToEvent.sender}).pluck().get() if (authorID) { replyLine += `<@${authorID}>` } else { @@ -367,14 +367,14 @@ async function eventToMessage(event, guild, di) { // Handling mentions of Discord users input = input.replace(/("https:\/\/matrix.to\/#\/(@[^"]+)")>/g, (whole, attributeValue, mxid) => { if (!utils.eventSenderIsFromDiscord(mxid)) return whole - const userID = select("sim", "user_id", "WHERE mxid = ?").pluck().get(mxid) + const userID = select("sim", "user_id", {mxid: mxid}).pluck().get() if (!userID) return whole return `${attributeValue} data-user-id="${userID}">` }) // Handling mentions of Discord rooms input = input.replace(/("https:\/\/matrix.to\/#\/(![^"]+)")>/g, (whole, attributeValue, roomID) => { - const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(roomID) + const channelID = select("channel_room", "channel_id", {room_id: roomID}).pluck().get() if (!channelID) return whole return `${attributeValue} data-channel-id="${channelID}">` }) diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index 6fc8123d..ef7f65d5 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -1508,7 +1508,7 @@ test("event2message: caches the member if the member is not known", async t => { } ) - t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], "WHERE room_id = '!should_be_newly_cached:cadence.moe'").all(), [ + t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], {room_id: "!should_be_newly_cached:cadence.moe"}).all(), [ {avatar_url: "mxc://cadence.moe/this_is_the_avatar", displayname: null, mxid: "@should_be_newly_cached:cadence.moe"} ]) t.equal(called, 1, "getStateEvent should be called once") @@ -1551,7 +1551,7 @@ test("event2message: skips caching the member if the member does not exist, some }] } ) - t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], "WHERE room_id = '!not_real:cadence.moe'").all(), []) + t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], {room_id: "!not_real:cadence.moe"}).all(), []) t.equal(called, 1, "getStateEvent should be called once") }) @@ -1594,7 +1594,7 @@ test("event2message: overly long usernames are shifted into the message content" }] } ) - t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], "WHERE room_id = '!should_be_newly_cached_2:cadence.moe'").all(), [ + t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], {room_id: "!should_be_newly_cached_2:cadence.moe"}).all(), [ {avatar_url: null, displayname: "I am BLACK I am WHITE I am SHORT I am LONG I am EVERYTHING YOU THINK IS IMPORTANT and I DON'T MATTER", mxid: "@should_be_newly_cached_2:cadence.moe"} ]) t.equal(called, 1, "getStateEvent should be called once") diff --git a/matrix/file.js b/matrix/file.js index 2dd64f14..626a1adb 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -43,7 +43,7 @@ async function uploadDiscordFileToMxc(path) { } // Has this file already been uploaded in the past? Grab the existing copy from the database. - const existingFromDb = select("file", "mxc_url", "WHERE discord_url = ?").pluck().get(urlNoExpiry) + const existingFromDb = select("file", "mxc_url", {discord_url: urlNoExpiry}).pluck().get() if (typeof existingFromDb === "string") { return existingFromDb } diff --git a/matrix/matrix-command-handler.js b/matrix/matrix-command-handler.js index 0f90bc63..a22d2b13 100644 --- a/matrix/matrix-command-handler.js +++ b/matrix/matrix-command-handler.js @@ -151,7 +151,7 @@ const commands = [{ async (event, realBody, ctx) => { // Guard /** @type {string} */ // @ts-ignore - const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(event.room_id) + const channelID = select("channel_room", "channel_id", {room_id: event.room_id}).pluck().get() const guildID = discord.channels.get(channelID)?.["guild_id"] let matrixOnlyReason = null const matrixOnlyConclusion = "So the emoji will be uploaded on Matrix-side only. It will still be usable over the bridge, but may have degraded functionality." diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index 681ec915..88dabcd0 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -8,7 +8,8 @@ INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent, custom ('497161350934560778', '!CzvdIdUQXgUjDVKxeU:cadence.moe', 'amanda-spam', NULL, NULL, NULL), ('160197704226439168', '!hYnGGlPHlbujVVfktC:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL, NULL), ('1100319550446252084', '!BnKuBPCvyfOkhcUjEu:cadence.moe', 'worm-farm', NULL, NULL, NULL), -('297272183716052993', '!rEOspnYqdOalaIFniV:cadence.moe', 'general', NULL, NULL, NULL); +('297272183716052993', '!rEOspnYqdOalaIFniV:cadence.moe', 'general', NULL, NULL, NULL), +('122155380120748034', '!cqeGDbPiMFAhLsqqqq:cadence.moe', 'cadences-mind', 'coding', NULL, NULL); INSERT INTO sim (user_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), @@ -72,7 +73,7 @@ INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/emojis/393635038903926784.gif', 'mxc://cadence.moe/WbYqNlACRuicynBfdnPYtmvc'), ('https://cdn.discordapp.com/attachments/176333891320283136/1157854643037163610/Screenshot_20231001_034036.jpg', 'mxc://cadence.moe/zAXdQriaJuLZohDDmacwWWDR'); -INSERT INTO emoji (id, name, animated, mxc_url) VALUES +INSERT INTO emoji (emoji_id, name, animated, mxc_url) VALUES ('230201364309868544', 'hippo', 0, 'mxc://cadence.moe/qWmbXeRspZRLPcjseyLmeyXC'), ('393635038903926784', 'hipposcope', 1, 'mxc://cadence.moe/WbYqNlACRuicynBfdnPYtmvc'), ('362741439211503616', 'bn_re', 0, 'mxc://cadence.moe/OIpqpfxTnHKokcsYqDusxkBT'),