From 79bd0254f03a1e7dd324956a0487c7f845cce50e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 18 Sep 2023 22:51:59 +1200 Subject: [PATCH] Finish moving from SQL to New Funny ORM --- d2m/actions/add-reaction.js | 4 ++-- d2m/actions/announce-thread.js | 5 ++--- d2m/actions/create-room.js | 13 ++++++------- d2m/actions/create-space.js | 12 +++++------- d2m/actions/delete-message.js | 9 +++------ d2m/actions/register-user.js | 15 ++++++++------- d2m/converters/edit-to-changes.js | 12 +++++------- d2m/converters/lottie.js | 4 ++-- d2m/converters/thread-to-announcement.js | 7 +++---- d2m/converters/user-to-mxid.js | 7 +++---- d2m/event-dispatcher.js | 22 +++++++++++----------- db/orm-utils.d.ts | 7 ++++++- discord/discord-command-handler.js | 8 ++++---- m2d/actions/add-reaction.js | 6 +++--- m2d/actions/channel-webhook.js | 11 +++++++---- m2d/actions/send-event.js | 5 +++-- m2d/converters/event-to-message.js | 14 +++++++------- m2d/converters/event-to-message.test.js | 9 +++++---- matrix/file.js | 4 ++-- 19 files changed, 87 insertions(+), 87 deletions(-) diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index 2eab8f0..b8fb38e 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -3,7 +3,7 @@ const assert = require("assert").strict const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("./register-user")} */ @@ -17,7 +17,7 @@ const createRoom = sync.require("../actions/create-room") async function addReaction(data) { const user = data.member?.user assert.ok(user && user.username) - const parentID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ? AND part = 0").pluck().get(data.message_id) // 0 = primary + const parentID = select("event_message", "event_id", "WHERE message = ? AND part = 0").pluck().get(data.message_id) // 0 = primary if (!parentID) return // Nothing can be done if the parent message was never bridged. assert.equal(typeof parentID, "string") const roomID = await createRoom.ensureRoom(data.channel_id) diff --git a/d2m/actions/announce-thread.js b/d2m/actions/announce-thread.js index aa6def2..760905a 100644 --- a/d2m/actions/announce-thread.js +++ b/d2m/actions/announce-thread.js @@ -3,7 +3,7 @@ const assert = require("assert") const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** @type {import("../converters/thread-to-announcement")} */ const threadToAnnouncement = sync.require("../converters/thread-to-announcement") /** @type {import("../../matrix/api")} */ @@ -15,8 +15,7 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async function announceThread(parentRoomID, threadRoomID, thread) { - /** @type {string?} */ - const creatorMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(thread.owner_id) + const creatorMxid = select("sim", "mxid", "WHERE discord_id = ?").pluck().get(thread.owner_id) 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 638d89c..a207586 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -5,7 +5,7 @@ const DiscordTypes = require("discord-api-types/v10") const reg = require("../../matrix/read-registration") const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") /** @type {import("../../matrix/api")} */ @@ -41,7 +41,7 @@ function applyKStateDiffToRoom(roomID, kstate) { /** * @param {{id: string, name: string, topic?: string?, type: number}} channel * @param {{id: string}} guild - * @param {string?} customName + * @param {string | null | undefined} customName */ function convertNameAndTopic(channel, guild, customName) { let channelPrefix = @@ -71,7 +71,7 @@ async function channelToKState(channel, guild) { const spaceID = await createSpace.ensureSpace(guild.id) assert.ok(typeof spaceID === "string") - const row = db.prepare("SELECT nick, custom_avatar FROM channel_room WHERE channel_id = ?").get(channel.id) + const row = select("channel_room", ["nick", "custom_avatar"], "WHERE channel_id = ?").get(channel.id) const customName = row?.nick const customAvatar = row?.custom_avatar const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) @@ -248,8 +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 } - /** @type {{room_id: string, thread_parent: string?}} */ - const existing = db.prepare("SELECT room_id, thread_parent from channel_room WHERE channel_id = ?").get(channelID) + const existing = select("channel_room", ["room_id", "thread_parent"], "WHERE channel_id = ?").get(channelID) if (!existing) { const creation = (async () => { @@ -309,9 +308,9 @@ async function _unbridgeRoom(channelID) { } async function unbridgeDeletedChannel(channelID, guildID) { - const roomID = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channelID) + const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channelID) assert.ok(roomID) - const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guildID) + const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guildID) 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 102b2a0..8628dcb 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -5,7 +5,7 @@ const DiscordTypes = require("discord-api-types/v10") const reg = require("../../matrix/read-registration") const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ @@ -86,8 +86,7 @@ async function _syncSpace(guildID, shouldActuallySync) { await inflightSpaceCreate.get(guildID) // just waiting, and then doing a new db query afterwards, is the simplest way of doing it } - /** @type {string?} */ - const spaceID = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").pluck().get(guildID) + const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guildID) if (!spaceID) { const creation = (async () => { @@ -118,7 +117,7 @@ async function _syncSpace(guildID, shouldActuallySync) { const newAvatarState = spaceDiff["m.room.avatar/"] if (guild.icon && newAvatarState?.url) { // don't try to update rooms with custom avatars though - const roomsWithCustomAvatars = db.prepare("SELECT room_id FROM channel_room 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) @@ -154,8 +153,7 @@ async function syncSpaceFully(guildID) { const guild = discord.guilds.get(guildID) assert.ok(guild) - /** @type {string?} */ - const spaceID = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").pluck().get(guildID) + const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guildID) const guildKState = await guildToKState(guild) @@ -176,7 +174,7 @@ async function syncSpaceFully(guildID) { }).map(({state_key}) => state_key) for (const roomID of childRooms) { - const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(roomID) if (!channelID) continue if (discord.channels.has(channelID)) { await createRoom.syncRoom(channelID) diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js index 01de993..ebbbbe4 100644 --- a/d2m/actions/delete-message.js +++ b/d2m/actions/delete-message.js @@ -1,7 +1,7 @@ // @ts-check const passthrough = require("../../passthrough") -const { sync, db } = passthrough +const {sync, db, select} = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") @@ -9,13 +9,10 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data */ async function deleteMessage(data) { - /** @type {string?} */ - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id) if (!roomID) return - /** @type {string[]} */ - const eventsToRedact = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").pluck().all(data.id) - + const eventsToRedact = select("event_message", "event_id", "WHERE message_id = ?").pluck().all(data.id) 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 7d7fb02..a23513e 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -4,7 +4,7 @@ const assert = require("assert") const reg = require("../../matrix/read-registration") const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ @@ -47,7 +47,7 @@ async function createSim(user) { */ async function ensureSim(user) { let mxid = null - const existing = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(user.id) + const existing = select("sim", "mxid", "WHERE discord_id = ?").pluck().get(user.id) if (existing) { mxid = existing } else { @@ -70,7 +70,7 @@ async function ensureSimJoined(user, roomID) { const mxid = await ensureSim(user) // Ensure joined - const existing = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, mxid) + const existing = select("sim_member", "mxid", "WHERE room_id = ? AND mxid = ?").pluck().get(roomID, mxid) if (!existing) { try { await api.inviteToRoom(roomID, mxid) @@ -137,7 +137,7 @@ async function syncUser(user, member, guildID, roomID) { const mxid = await ensureSimJoined(user, roomID) const content = await memberToStateContent(user, member, guildID) const profileEventContentHash = calculateProfileEventContentHash(content) - const existingHash = db.prepare("SELECT profile_event_content_hash FROM sim_member WHERE room_id = ? AND mxid = ?").pluck().get(roomID, mxid) + const existingHash = select("sim_member", "profile_event_content_hash", "WHERE room_id = ? AND mxid = ?").pluck().get(roomID, mxid) // only do the actual sync if the hash has changed since we last looked if (existingHash !== profileEventContentHash) { await api.sendState(roomID, "m.room.member", mxid, content, mxid) @@ -147,17 +147,18 @@ async function syncUser(user, member, guildID, roomID) { } async function syncAllUsersInRoom(roomID) { - const mxids = db.prepare("SELECT mxid FROM sim_member WHERE room_id = ?").pluck().all(roomID) + const mxids = select("sim_member", "mxid", "WHERE room_id = ?").pluck().all(roomID) - const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(roomID) assert.ok(typeof channelID === "string") + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ const channel = discord.channels.get(channelID) const guildID = channel.guild_id assert.ok(typeof guildID === "string") for (const mxid of mxids) { - const userID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(mxid) + const userID = select("sim", "discord_id", "WHERE mxid = ?").pluck().get(mxid) assert.ok(typeof userID === "string") /** @ts-ignore @type {Required} */ diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 814bb97..ac072f8 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -3,7 +3,7 @@ const assert = require("assert") const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** @type {import("./message-to-event")} */ const messageToEvent = sync.require("../converters/message-to-event") /** @type {import("../actions/register-user")} */ @@ -21,17 +21,15 @@ const createRoom = sync.require("../actions/create-room") async function editToChanges(message, guild, api) { // Figure out what events we will be replacing - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) - /** @type {string?} */ - let senderMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(message.author.id) || null + const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(message.channel_id) + let senderMxid = select("sim", "mxid", "WHERE discord_id = ?").pluck().get(message.author.id) || null if (senderMxid) { - const senderIsInRoom = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, senderMxid) + const senderIsInRoom = select("sim_member", "mxid", "WHERE room_id = ? AND mxid = ?").get(roomID, senderMxid) if (!senderIsInRoom) { senderMxid = null // just send as ooye bot } } - /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ - const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) + const oldEventRows = select("event_message", ["event_id", "event_type", "event_subtype", "part"], "WHERE message_id = ?").all(message.id) // Figure out what we will be replacing them with diff --git a/d2m/converters/lottie.js b/d2m/converters/lottie.js index 5115f8b..1d200a4 100644 --- a/d2m/converters/lottie.js +++ b/d2m/converters/lottie.js @@ -6,7 +6,7 @@ const assert = require("assert").strict const {PNG} = require("pngjs") const passthrough = require("../../passthrough") -const { sync, db, discord } = passthrough +const {sync, db, discord, select} = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") //** @type {import("../../matrix/mreq")} */ @@ -38,7 +38,7 @@ const Rlottie = (async () => { * @returns {Promise<{mxc: string, info: typeof INFO}>} */ async function convert(stickerItem) { - const existingMxc = db.prepare("SELECT mxc FROM lottie WHERE id = ?").pluck().get(stickerItem.id) + const existingMxc = select("lottie", "mxc", "WHERE id = ?").pluck().get(stickerItem.id) if (existingMxc) return {mxc: existingMxc, info: INFO} const r = await Rlottie const res = await fetch(file.DISCORD_IMAGES_BASE + file.sticker(stickerItem)) diff --git a/d2m/converters/thread-to-announcement.js b/d2m/converters/thread-to-announcement.js index 405f7e9..003134b 100644 --- a/d2m/converters/thread-to-announcement.js +++ b/d2m/converters/thread-to-announcement.js @@ -1,9 +1,9 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** @type {import("../../matrix/read-registration")} */ const reg = sync.require("../../matrix/read-registration.js") @@ -17,8 +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) { - /** @type {string?} */ - const branchedFromEventID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").pluck().get(thread.id) + const branchedFromEventID = select("event_message", "event_id", "WHERE message_id = ?").pluck().get(thread.id) /** @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 1fe8ffc..4537966 100644 --- a/d2m/converters/user-to-mxid.js +++ b/d2m/converters/user-to-mxid.js @@ -3,7 +3,7 @@ const assert = require("assert") const passthrough = require("../../passthrough") -const { sync, db } = passthrough +const {select} = passthrough /** * Downcased and stripped username. Can only include a basic set of characters. @@ -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 = db.prepare("SELECT sim_name FROM sim WHERE discord_id = ?").pluck().get(user.id) + const existing = select("sim", "sim_name", "WHERE discord_id = ?").pluck().get(user.id) if (existing) return existing // 2. Register based on username (could be new or old format) @@ -64,8 +64,7 @@ function userToSimName(user) { } // Check for conflicts with already registered sims - /** @type {string[]} */ - const matches = db.prepare("SELECT sim_name FROM sim 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 5f864f6..1301bca 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,6 +1,6 @@ const assert = require("assert").strict const util = require("util") -const {sync, db} = require("../passthrough") +const {sync, db, select, from} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") @@ -42,7 +42,7 @@ module.exports = { const channelID = gatewayMessage.d.channel_id if (!channelID) return - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) + const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channelID) if (!roomID) return let stackLines = e.stack.split("\n") @@ -80,8 +80,8 @@ module.exports = { */ async checkMissedMessages(client, guild) { if (guild.unavailable) return - const bridgedChannels = db.prepare("SELECT channel_id FROM channel_room").pluck().all() - const prepared = db.prepare("SELECT 1 FROM event_message WHERE message_id = ?").pluck() + const bridgedChannels = select("channel_room", "channel_id").pluck().all() + const prepared = select("event_message", "1", "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 @@ -125,7 +125,7 @@ module.exports = { * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async onThreadCreate(client, thread) { - const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(thread.parent_id) + const parentRoomID = select("channel_room", "room_id", "WHERE channel_room = ?").pluck().get(thread.parent_id) 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) @@ -136,7 +136,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayGuildUpdateDispatchData} guild */ async onGuildUpdate(client, guild) { - const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) + const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guild.id) if (!spaceID) return await createSpace.syncSpace(guild.id) }, @@ -147,7 +147,7 @@ module.exports = { * @param {boolean} isThread */ async onChannelOrThreadUpdate(client, channelOrThread, isThread) { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(channelOrThread.id) + const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channelOrThread.id) if (!roomID) return // No target room to update the data on await createRoom.syncRoom(channelOrThread.id) }, @@ -158,7 +158,7 @@ module.exports = { */ async onMessageCreate(client, message) { if (message.webhook_id) { - const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + const row = select("webhook", "1", "WHERE webhook_id = ?").pluck().get(message.webhook_id) 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 @@ -179,7 +179,7 @@ module.exports = { */ async onMessageUpdate(client, data) { if (data.webhook_id) { - const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(data.webhook_id) + const row = select("webhook", "1", "WHERE webhook_id = ?").pluck().get(message.webhook_id) 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 @@ -222,9 +222,9 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayTypingStartDispatchData} data */ async onTypingStart(client, data) { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id) if (!roomID) return - const mxid = db.prepare("SELECT mxid FROM sim INNER JOIN sim_member USING (mxid) WHERE discord_id = ? AND room_id = ?").pluck().get(data.user_id, roomID) + const mxid = from("sim").join("sim_member", "mxid").and("WHERE discord_id = ? AND room_id = ?").pluck("mxid").get(data.user_id, roomID) 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/orm-utils.d.ts b/db/orm-utils.d.ts index 5eb87c4..1c6b3a6 100644 --- a/db/orm-utils.d.ts +++ b/db/orm-utils.d.ts @@ -27,6 +27,11 @@ export type Models = { space_id: string } + lottie: { + id: string + mxc: string + } + member_cache: { room_id: string mxid: string @@ -49,7 +54,7 @@ export type Models = { sim_member: { mxid: string room_id: string - profile_event_content_hash: string + profile_event_content_hash: any } webhook: { diff --git a/discord/discord-command-handler.js b/discord/discord-command-handler.js index 089286a..f364f63 100644 --- a/discord/discord-command-handler.js +++ b/discord/discord-command-handler.js @@ -5,7 +5,7 @@ const util = require("util") const DiscordTypes = require("discord-api-types/v10") const reg = require("../matrix/read-registration") -const {discord, sync, db} = require("../passthrough") +const {discord, sync, db, select} = require("../passthrough") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") /** @type {import("../matrix/file")} */ @@ -80,7 +80,7 @@ const commands = [{ execute: replyctx( async (message, channel, guild, ctx) => { // Guard - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(channel.id) if (!roomID) return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "This channel isn't bridged to the other side." @@ -124,8 +124,8 @@ const commands = [{ execute: replyctx( async (message, channel, guild, ctx) => { // Check guild is bridged - const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + 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) 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 8752602..fbee20f 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -4,15 +4,15 @@ const assert = require("assert").strict const Ty = require("../../types") const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough +const {discord, sync, db, select} = passthrough /** * @param {Ty.Event.Outer} event */ async function addReaction(event) { - const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(event.room_id) if (!channelID) return // We just assume the bridge has already been created - const messageID = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? AND part = 0").pluck().get(event.content["m.relates_to"].event_id) // 0 = primary + const messageID = select("event_message", "message_id", "WHERE event_id = ? AND part = 0").pluck().get(event.content["m.relates_to"].event_id) // 0 = primary if (!messageID) return // Nothing can be done if the parent message was never bridged. // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index eabec52..0fde77a 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -3,7 +3,7 @@ const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") -const {discord, db} = passthrough +const {discord, db, select} = passthrough /** * Look in the database to find webhook credentials for a channel. @@ -14,10 +14,13 @@ const {discord, db} = passthrough */ async function ensureWebhook(channelID, forceCreate = false) { if (!forceCreate) { - /** @type {{id: string, token: string} | null} */ - const row = db.prepare("SELECT webhook_id as id, webhook_token as token FROM webhook WHERE channel_id = ?").get(channelID) + const row = select("webhook", ["webhook_id", "webhook_token"], "WHERE channel_id = ?").get(channelID) if (row) { - return {created: false, ...row} + return { + id: row.webhook_id, + token: row.webhook_token, + created: false + } } } diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 08670a9..005b031 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -7,7 +7,7 @@ const {promisify} = require("util") const Ty = require("../../types") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") -const {sync, discord, db} = passthrough +const {sync, discord, db, select} = passthrough /** @type {import("./channel-webhook")} */ const channelWebhook = sync.require("./channel-webhook") @@ -53,7 +53,8 @@ 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) { // TODO: we just assume the bridge has already been created, is that really ok? - const row = db.prepare("SELECT channel_id, thread_parent FROM channel_room WHERE room_id = ?").get(event.room_id) + const row = select("channel_room", ["channel_id", "thread_parent"], "WHERE room_id = ?").get(event.room_id) + assert(row) let channelID = row.channel_id let threadID = undefined if (row.thread_parent) { diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 5abfd49..c9b0133 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -7,7 +7,7 @@ const TurndownService = require("turndown") const assert = require("assert").strict const passthrough = require("../../passthrough") -const { sync, db, discord } = passthrough +const {sync, db, discord, select, from} = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") /** @type {import("../converters/utils")} */ @@ -148,7 +148,7 @@ turndownService.addRule("fencedCodeBlock", { * @returns {Promise<{displayname?: string?, avatar_url?: string?}>} */ async function getMemberFromCacheOrHomeserver(roomID, mxid, api) { - const row = db.prepare("SELECT displayname, avatar_url FROM member_cache WHERE room_id = ? AND mxid = ?").get(roomID, mxid) + const row = select("member_cache", ["displayname", "avatar_url"], "WHERE room_id = ? AND mxid = ?").get(roomID, mxid) 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) @@ -230,7 +230,7 @@ async function eventToMessage(event, guild, di) { if (relType !== "m.replace") return const originalEventId = relatesTo.event_id if (!originalEventId) return - messageIDsToEdit = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? ORDER BY part").pluck().all(originalEventId) + messageIDsToEdit = select("event_message", "message_id", "WHERE event_id = ? ORDER BY part").pluck().all(originalEventId) if (!messageIDsToEdit.length) return // Ok, it's an edit. @@ -261,7 +261,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 = db.prepare("SELECT channel_id, message_id FROM event_message INNER JOIN message_channel USING (message_id) WHERE event_id = ? ORDER BY part").get(repliedToEventId) + const row = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").and("WHERE event_id = ? ORDER BY part").get(repliedToEventId) if (row) { replyLine = `<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/${guild.id}/${row.channel_id}/${row.message_id} ` } else { @@ -269,7 +269,7 @@ async function eventToMessage(event, guild, di) { } const sender = repliedToEvent.sender const senderName = sender.match(/@([^:]*)/)?.[1] || sender - const authorID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(repliedToEvent.sender) + const authorID = select("sim", "discord_id", "WHERE mxid = ?").pluck().get(repliedToEvent.sender) if (authorID) { replyLine += `<@${authorID}>` } else { @@ -312,14 +312,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 = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(mxid) + const userID = select("sim", "discord_id", "WHERE mxid = ?").pluck().get(mxid) 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 = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(roomID) 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 00c64ed..3b40f90 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -2,7 +2,7 @@ const {test} = require("supertape") const {eventToMessage} = require("./event-to-message") const data = require("../../test/data") const {MatrixServerError} = require("../../matrix/mreq") -const {db} = require("../../passthrough") +const {db, select} = require("../../passthrough") /** * @param {string} roomID @@ -1414,7 +1414,8 @@ test("event2message: caches the member if the member is not known", async t => { }] } ) - t.deepEqual(db.prepare("SELECT avatar_url, displayname, mxid FROM member_cache WHERE room_id = '!should_be_newly_cached:cadence.moe'").all(), [ + + t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], "WHERE 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") @@ -1457,7 +1458,7 @@ test("event2message: skips caching the member if the member does not exist, some }] } ) - t.deepEqual(db.prepare("SELECT avatar_url, displayname, mxid FROM member_cache WHERE room_id = '!not_real:cadence.moe'").all(), []) + t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], "WHERE room_id = '!not_real:cadence.moe'").all(), []) t.equal(called, 1, "getStateEvent should be called once") }) @@ -1500,7 +1501,7 @@ test("event2message: overly long usernames are shifted into the message content" }] } ) - t.deepEqual(db.prepare("SELECT avatar_url, displayname, mxid FROM member_cache WHERE room_id = '!should_be_newly_cached_2:cadence.moe'").all(), [ + t.deepEqual(select("member_cache", ["avatar_url", "displayname", "mxid"], "WHERE 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 ec26711..bf5a588 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -3,7 +3,7 @@ const fetch = require("node-fetch").default const passthrough = require("../passthrough") -const { sync, db } = passthrough +const {sync, db, select} = passthrough /** @type {import("./mreq")} */ const mreq = sync.require("./mreq") @@ -33,7 +33,7 @@ async function uploadDiscordFileToMxc(path) { } // Has this file already been uploaded in the past? Grab the existing copy from the database. - const existingFromDb = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) + const existingFromDb = select("file", "mxc_url", "WHERE discord_url = ?").pluck().get(url) if (typeof existingFromDb === "string") { return existingFromDb }