diff --git a/package.json b/package.json index 857e2d2..6b4484d 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,6 @@ "addbot": "node addbot.js", "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot", "test-slow": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap --no-worker test/test.js -- --slow | tap-dot", - "cover": "c8 -o test/coverage --skip-full -x db/migrations -x src/matrix/file.js -x src/matrix/api.js -x src/matrix/mreq.js -x src/d2m/converters/rlottie-wasm.js -r html -r text supertape --no-check-assertions-count --format fail --no-worker test/test.js -- --slow" + "cover": "c8 -o test/coverage --skip-full -x db/migrations -x matrix/file.js -x matrix/api.js -x matrix/mreq.js -x d2m/converters/rlottie-wasm.js -r html -r text supertape --no-check-assertions-count --format fail --no-worker test/test.js -- --slow" } } diff --git a/scripts/setup.js b/scripts/setup.js index 0fae913..2fb2dd0 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -105,8 +105,7 @@ async function validateHomeserverOrigin(serverUrlPrompt, url) { const serverNameResponse = await prompt({ type: "input", name: "server_name", - message: "Homeserver name", - validate: serverName => !!serverName.match(/[a-z][a-z.]+[a-z]/) + message: "Homeserver name" }) console.log("What is the URL of your homeserver?") @@ -177,7 +176,7 @@ async function validateHomeserverOrigin(serverUrlPrompt, url) { message: "Client secret" }) - const template = getTemplateRegistration(serverNameResponse.server_name) + const template = getTemplateRegistration() reg = { ...template, url: bridgeOriginResponse.bridge_origin, diff --git a/src/d2m/actions/create-room.js b/src/d2m/actions/create-room.js index 2d8d1af..75696f2 100644 --- a/src/d2m/actions/create-room.js +++ b/src/d2m/actions/create-room.js @@ -504,5 +504,5 @@ module.exports.postApplyPowerLevels = postApplyPowerLevels module.exports._convertNameAndTopic = convertNameAndTopic module.exports._unbridgeRoom = _unbridgeRoom module.exports.unbridgeDeletedChannel = unbridgeDeletedChannel -module.exports.existsOrAutocreatable = existsOrAutocreatable module.exports.assertExistsOrAutocreatable = assertExistsOrAutocreatable +module.exports.existsOrAutocreatable = existsOrAutocreatable diff --git a/src/d2m/event-dispatcher.js b/src/d2m/event-dispatcher.js index af8eedf..1806ee6 100644 --- a/src/d2m/event-dispatcher.js +++ b/src/d2m/event-dispatcher.js @@ -191,7 +191,7 @@ module.exports = { async onThreadCreate(client, thread) { const channelID = thread.parent_id || undefined const parentRoomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() - if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel (won't autocreate) + 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) }, @@ -249,7 +249,6 @@ module.exports = { if (message.author.username === "Deleted User") return // Nothing we can do for deleted users. const channel = client.channels.get(message.channel_id) if (!channel || !("guild_id" in channel) || !channel.guild_id) return // Nothing we can do in direct messages. - const guild = client.guilds.get(channel.guild_id) assert(guild) @@ -260,13 +259,11 @@ module.exports = { if (dUtils.isEphemeralMessage(message)) return // Ephemeral messages are for the eyes of the receiver only! - if (!createRoom.existsOrAutocreatable(channel, guild.id)) return // Check that the sending-to room exists or is autocreatable - const {affected, row} = await speedbump.maybeDoSpeedbump(message.channel_id, message.id) if (affected) return // @ts-ignore - await sendMessage.sendMessage(message, channel, guild, row) + await sendMessage.sendMessage(message, channel, guild, row), retrigger.messageFinishedBridging(message.id) }, @@ -281,7 +278,7 @@ module.exports = { // Otherwise, if there are embeds, then the system generated URL preview embeds. if (!(typeof data.content === "string" || "embeds" in data)) return - // Check that the sending-to room exists, and deal with Eventual Consistency(TM) + // Deal with Eventual Consistency(TM) if (retrigger.eventNotFoundThenRetrigger(data.id, module.exports.onMessageUpdate, client, data)) return if (data.webhook_id) { @@ -298,11 +295,11 @@ module.exports = { /** @type {DiscordTypes.GatewayMessageCreateDispatchData} */ // @ts-ignore const message = data + const channel = client.channels.get(message.channel_id) if (!channel || !("guild_id" in channel) || !channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) assert(guild) - // @ts-ignore await editMessage.editMessage(message, guild, row) }, diff --git a/src/db/migrations/0002-optimise-profile-content.up.js b/src/db/migrations/0002-optimise-profile-content.up.js index 5b540cb..a8619cf 100644 --- a/src/db/migrations/0002-optimise-profile-content.up.js +++ b/src/db/migrations/0002-optimise-profile-content.up.js @@ -3,7 +3,6 @@ module.exports = async function(db) { const contents = db.prepare("SELECT distinct hashed_profile_content FROM sim_member WHERE hashed_profile_content IS NOT NULL").pluck().all() const stmt = db.prepare("UPDATE sim_member SET hashed_profile_content = ? WHERE hashed_profile_content = ?") db.transaction(() => { - /* c8 ignore next 6 */ for (let s of contents) { let b = Buffer.isBuffer(s) ? Uint8Array.from(s) : Uint8Array.from(Buffer.from(s)) const unsignedHash = hasher.h64Raw(b) diff --git a/src/db/orm-defs.d.ts b/src/db/orm-defs.d.ts index c235e99..b1e6b79 100644 --- a/src/db/orm-defs.d.ts +++ b/src/db/orm-defs.d.ts @@ -124,4 +124,3 @@ export type PickTypeOf> = T extends { [k in K]?: any } ? export type Merge = {[x in AllKeys]: PickTypeOf} export type Nullable = {[k in keyof T]: T[k] | null} export type Numberish = {[k in keyof T]: T[k] extends number ? (number | bigint) : T[k]} -export type ValueOrArray = {[k in keyof T]: T[k][] | T[k]} diff --git a/src/db/orm.js b/src/db/orm.js index 4d9b6f1..646012b 100644 --- a/src/db/orm.js +++ b/src/db/orm.js @@ -8,7 +8,7 @@ const U = require("./orm-defs") * @template {keyof U.Models[Table]} Col * @param {Table} table * @param {Col[] | Col} cols - * @param {Partial>>} where + * @param {Partial>} where * @param {string} [e] */ function select(table, cols, where = {}, e = "") { diff --git a/src/db/orm.test.js b/src/db/orm.test.js index 278723a..a53cc66 100644 --- a/src/db/orm.test.js +++ b/src/db/orm.test.js @@ -30,11 +30,6 @@ test("orm: select: all, where and pluck works on multiple columns", t => { t.deepEqual(names, ["cadence [they]"]) }) -test("orm: select: in array works", t => { - const ids = select("emoji", "emoji_id", {name: ["online", "upstinky"]}).pluck().all() - t.deepEqual(ids, ["288858540888686602", "606664341298872324"]) -}) - 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) diff --git a/src/m2d/actions/redact.js b/src/m2d/actions/redact.js index 5a12d5a..ffbb261 100644 --- a/src/m2d/actions/redact.js +++ b/src/m2d/actions/redact.js @@ -13,7 +13,7 @@ const utils = sync.require("../converters/utils") */ async function deleteMessage(event) { const rows = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").where({event_id: event.redacts}).all() - db.prepare("DELETE FROM event_message WHERE event_id = ?").run(event.redacts) + db.prepare("DELETE FROM event_message WHERE event_id = ?").run(event.event_id) for (const row of rows) { db.prepare("DELETE FROM message_channel WHERE message_id = ?").run(row.message_id) await discord.snow.channel.deleteMessage(row.channel_id, row.message_id, event.content.reason) diff --git a/src/matrix/read-registration.js b/src/matrix/read-registration.js index 9fb0535..8438387 100644 --- a/src/matrix/read-registration.js +++ b/src/matrix/read-registration.js @@ -24,12 +24,8 @@ function writeRegistration(reg) { fs.writeFileSync(registrationFilePath, JSON.stringify(reg, null, 2)) } -/** - * @param {string} serverName - * @returns {import("../types").InitialAppServiceRegistrationConfig} reg - */ -function getTemplateRegistration(serverName) { - const namespace_prefix = "_ooye_" +/** @returns {import("../types").InitialAppServiceRegistrationConfig} reg */ +function getTemplateRegistration() { return { id: "ooye", as_token: crypto.randomBytes(32).toString("hex"), @@ -37,21 +33,21 @@ function getTemplateRegistration(serverName) { namespaces: { users: [{ exclusive: true, - regex: `@${namespace_prefix}.*:${serverName}` + regex: "@_ooye_.*:cadence.moe" }], aliases: [{ exclusive: true, - regex: `#${namespace_prefix}.*:${serverName}` + regex: "#_ooye_.*:cadence.moe" }] }, protocols: [ "discord" ], - sender_localpart: `${namespace_prefix}bot`, + sender_localpart: "_ooye_bot", rate_limited: false, socket: 6693, ooye: { - namespace_prefix, + namespace_prefix: "_ooye_", max_file_size: 5000000, content_length_workaround: false, include_user_id_in_mxid: false, diff --git a/src/web/pug/guild.pug b/src/web/pug/guild.pug index 1e27f2e..2130aff 100644 --- a/src/web/pug/guild.pug +++ b/src/web/pug/guild.pug @@ -136,8 +136,8 @@ block body form.d-flex.ai-center.g8 label.s-label.fl-grow1(for="autocreate") | Create new Matrix rooms automatically - p.s-description If you want, OOYE can automatically create new Matrix rooms and link them when an unlinked Discord channel is spoken in. - - let value = !!select("guild_active", "autocreate", {guild_id}).pluck().get() + p.s-description If you want, OOYE can automatically create new Matrix rooms and link them when a new Discord channel is spoken in. + - let value = select("guild_active", "autocreate", {guild_id}).pluck().get() input(type="hidden" name="guild_id" value=guild_id) input.s-toggle-switch.order-last#autocreate(name="autocreate" type="checkbox" hx-post="/api/autocreate" hx-indicator="#autocreate-loading" hx-disabled-elt="this" hx-swap="none" checked=value) .is-loading#autocreate-loading diff --git a/src/web/pug/includes/template.pug b/src/web/pug/includes/template.pug index 94b5e92..c117056 100644 --- a/src/web/pug/includes/template.pug +++ b/src/web/pug/includes/template.pug @@ -13,7 +13,6 @@ doctype html html(lang="en") head title Out Of Your Element - link(rel="stylesheet" type="text/css" href="/static/stacks.min.css") meta(name="htmx-config" content='{"indicatorClass":"is-loading"}') @@ -53,7 +52,7 @@ html(lang="en") li(role="menuitem") a.s-topbar--item.s-user-card.d-flex.p4(href=`/guild?guild_id=${guild.id}`) +guild(guild) - .mx-auto.w100.wmx9.py24.px8#content + .mx-auto.w100.wmx9.py24#content block body script. document.querySelectorAll("[popovertarget]").forEach(e => { diff --git a/src/web/routes/invite.js b/src/web/routes/invite.js index 94fa367..eec7a3c 100644 --- a/src/web/routes/invite.js +++ b/src/web/routes/invite.js @@ -9,8 +9,6 @@ const {LRUCache} = require("lru-cache") const {discord, as, sync, select} = require("../../passthrough") /** @type {import("../pug-sync")} */ const pugSync = sync.require("../pug-sync") -/** @type {import("../../d2m/actions/create-space")} */ -const createSpace = sync.require("../../d2m/actions/create-space") const {reg} = require("../../matrix/read-registration") /** @type {import("../../matrix/api")} */ @@ -73,21 +71,21 @@ as.router.post("/api/invite", defineEventHandler(async event => { } // Check guild is bridged - const guild = discord.guilds.get(guild_id) - assert(guild) - const spaceID = await createSpace.ensureSpace(guild) + const spaceID = select("guild_space", "space_id", {guild_id: guild_id}).pluck().get() + if (!spaceID) throw createError({status: 428, message: "Server not bridged", data: "You can only invite Matrix users to servers that are bridged to Matrix."}) // Check for existing invite to the space let spaceMember try { spaceMember = await api.getStateEvent(spaceID, "m.room.member", parsedBody.mxid) } catch (e) {} - - if (!spaceMember || spaceMember.membership !== "invite" || spaceMember.membership !== "join") { - // Invite - await api.inviteToRoom(spaceID, parsedBody.mxid) + if (spaceMember && (spaceMember.membership === "invite" || spaceMember.membership === "join")) { + return sendRedirect(event, `/guild?guild_id=${guild_id}`, 302) } + // Invite + await api.inviteToRoom(spaceID, parsedBody.mxid) + // Permissions if (parsedBody.permissions === "moderator") { await api.setUserPowerCascade(spaceID, parsedBody.mxid, 50) diff --git a/src/web/routes/oauth.js b/src/web/routes/oauth.js index 2f12dd1..f3078ad 100644 --- a/src/web/routes/oauth.js +++ b/src/web/routes/oauth.js @@ -7,7 +7,7 @@ const {SnowTransfer} = require("snowtransfer") const DiscordTypes = require("discord-api-types/v10") const fetch = require("node-fetch") -const {as, db} = require("../../passthrough") +const {as} = require("../../passthrough") const {id} = require("../../../addbot") const {reg} = require("../../matrix/read-registration") @@ -77,19 +77,14 @@ as.router.get("/oauth", defineEventHandler(async event => { const client = new SnowTransfer(`Bearer ${parsedToken.data.access_token}`) try { const guilds = await client.user.getGuilds() - var managedGuilds = guilds.filter(g => BigInt(g.permissions) & DiscordTypes.PermissionFlagsBits.ManageGuild).map(g => g.id) + const managedGuilds = guilds.filter(g => BigInt(g.permissions) & DiscordTypes.PermissionFlagsBits.ManageGuild).map(g => g.id) await session.update({managedGuilds}) } catch (e) { throw createError({status: 502, message: "API call failed", data: e.message}) } - // Set auto-create for the guild - // @ts-ignore - if (managedGuilds.includes(parsedQuery.data.guild_id)) { - db.prepare("INSERT OR IGNORE INTO guild_active (guild_id, autocreate) VALUES (?, ?)").run(parsedQuery.data.guild_id, +!session.data.selfService) - } - if (parsedQuery.data.guild_id) { + // TODO: we probably need to create a matrix space and database entry immediately here so that self-service settings apply and so matrix users can be invited return sendRedirect(event, `/guild?guild_id=${parsedQuery.data.guild_id}`, 302) }