diff --git a/package-lock.json b/package-lock.json index 7fd76a3..1852245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.0.0", - "@cloudrac3r/mixin-deep": "^3.0.1", + "@cloudrac3r/mixin-deep": "^3.0.0", "@cloudrac3r/pngjs": "^7.0.3", "@cloudrac3r/pug": "^4.0.4", "@cloudrac3r/turndown": "^7.1.4", @@ -281,10 +281,9 @@ } }, "node_modules/@cloudrac3r/mixin-deep": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cloudrac3r/mixin-deep/-/mixin-deep-3.0.1.tgz", - "integrity": "sha512-awxfIraHjJ/URNlZ0ROc78Tdjtfk/fM/Gnj1embfrSN08h/HpRtLmPc3xlG3T2vFAy1AkONaebd52u7o6kDaYw==", - "license": "MIT", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@cloudrac3r/mixin-deep/-/mixin-deep-3.0.0.tgz", + "integrity": "sha512-yQz1wHSZbHfbKaGSjrV3wIG0e9MnElKlmekMKJPRdTn2jhF2Mt8wfMPX8U7v6rTyzR/7BTrX8CCUcrJMLgoQqw==", "engines": { "node": ">=6" } @@ -3218,9 +3217,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", - "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", "license": "MIT", "engines": { "node": ">=18.17" diff --git a/package.json b/package.json index 1d2e411..1705c02 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.0.0", - "@cloudrac3r/mixin-deep": "^3.0.1", + "@cloudrac3r/mixin-deep": "^3.0.0", "@cloudrac3r/pngjs": "^7.0.3", "@cloudrac3r/pug": "^4.0.4", "@cloudrac3r/turndown": "^7.1.4", diff --git a/src/d2m/actions/create-room.js b/src/d2m/actions/create-room.js index cf785ac..066d3fa 100644 --- a/src/d2m/actions/create-room.js +++ b/src/d2m/actions/create-room.js @@ -11,8 +11,6 @@ const {discord, sync, db, select, from} = passthrough const file = sync.require("../../matrix/file") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") -/** @type {import("../../matrix/mreq")} */ -const mreq = sync.require("../../matrix/mreq") /** @type {import("../../matrix/kstate")} */ const ks = sync.require("../../matrix/kstate") /** @type {import("../../discord/utils")} */ @@ -89,10 +87,9 @@ async function channelToKState(channel, guild, di) { assert(typeof parentSpaceID === "string") } - const channelRow = select("channel_room", ["nick", "custom_avatar", "custom_topic"], {channel_id: channel.id}).get() + const channelRow = select("channel_room", ["nick", "custom_avatar"], {channel_id: channel.id}).get() const customName = channelRow?.nick const customAvatar = channelRow?.custom_avatar - const hasCustomTopic = channelRow?.custom_topic const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) const avatarEventContent = {} @@ -168,8 +165,6 @@ async function channelToKState(channel, guild, di) { } } - if (hasCustomTopic) delete channelKState["m.room.topic/"] - return {spaceID: parentSpaceID, privacyLevel, channelKState} } @@ -417,20 +412,9 @@ async function unbridgeDeletedChannel(channel, guildID) { const row = from("guild_space").join("guild_active", "guild_id").select("space_id", "autocreate").get() assert.ok(row) - let botInRoom = true - // remove declaration that the room is bridged - try { - await api.sendState(roomID, "uk.half-shot.bridge", `moe.cadence.ooye://discord/${guildID}/${channel.id}`, {}) - } catch (e) { - if (String(e).includes("not in room")) { - botInRoom = false - } else { - throw e - } - } - - if (botInRoom && "topic" in channel) { + await api.sendState(roomID, "uk.half-shot.bridge", `moe.cadence.ooye://discord/${guildID}/${channel.id}`, {}) + if ("topic" in channel) { // previously the Matrix topic would say the channel ID. we should remove that await api.sendState(roomID, "m.room.topic", "", {topic: channel.topic || ""}) } @@ -446,8 +430,6 @@ async function unbridgeDeletedChannel(channel, guildID) { db.prepare("DELETE FROM member_cache WHERE room_id = ?").run(roomID) db.prepare("DELETE FROM channel_room WHERE room_id = ? AND channel_id = ?").run(roomID, channel.id) // cascades to most other tables, like messages - if (!botInRoom) return - // demote admins in room /** @type {Ty.Event.M_Power_Levels} */ const powerLevelContent = await api.getStateEvent(roomID, "m.room.power_levels", "") diff --git a/src/d2m/actions/create-room.test.js b/src/d2m/actions/create-room.test.js index 0376d65..a1766dd 100644 --- a/src/d2m/actions/create-room.test.js +++ b/src/d2m/actions/create-room.test.js @@ -94,26 +94,6 @@ test("channel2room: room where limited people can mention everyone", async t => t.equal(called, 1) }) -test("channel2room: matrix room that already has a custom topic set", async t => { - let called = 0 - async function getStateEvent(roomID, type, key) { // getting power levels from space to apply to room - called++ - t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") - t.equal(type, "m.room.power_levels") - t.equal(key, "") - return {} - } - db.prepare("UPDATE channel_room SET custom_topic = 1 WHERE channel_id = ?").run(testData.channel.general.id) - const expected = mixin({}, testData.room.general, {"m.room.power_levels/": {notifications: {room: 20}}}) - // @ts-ignore - delete expected["m.room.topic/"] - t.deepEqual( - kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general, {api: {getStateEvent}}).then(x => x.channelKState)), - expected - ) - t.equal(called, 1) -}) - test("convertNameAndTopic: custom name and topic", t => { t.deepEqual( _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"), diff --git a/src/db/migrations/0018-add-custom-topic-to-channel-room.sql b/src/db/migrations/0018-add-custom-topic-to-channel-room.sql deleted file mode 100644 index c33d21c..0000000 --- a/src/db/migrations/0018-add-custom-topic-to-channel-room.sql +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN TRANSACTION; - -ALTER TABLE channel_room ADD COLUMN custom_topic INTEGER DEFAULT 0; - -COMMIT; diff --git a/src/db/orm-defs.d.ts b/src/db/orm-defs.d.ts index a272625..c235e99 100644 --- a/src/db/orm-defs.d.ts +++ b/src/db/orm-defs.d.ts @@ -10,8 +10,6 @@ export type Models = { speedbump_id: string | null speedbump_webhook_id: string | null speedbump_checked: number | null - guild_id: string | null - custom_topic: number } event_message: { diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index ac44a19..fc2a558 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -164,17 +164,6 @@ async event => { db.prepare("UPDATE channel_room SET nick = ? WHERE room_id = ?").run(name, event.room_id) })) -sync.addTemporaryListener(as, "type:m.room.topic", guard("m.room.topic", -/** - * @param {Ty.Event.StateOuter} event - */ -async event => { - if (event.state_key !== "") return - if (utils.eventSenderIsFromDiscord(event.sender)) return - const customTopic = +!!event.content.topic - db.prepare("UPDATE channel_room SET custom_topic = ? WHERE room_id = ?").run(customTopic, event.room_id) -})) - sync.addTemporaryListener(as, "type:m.room.pinned_events", guard("m.room.pinned_events", /** * @param {Ty.Event.StateOuter} event diff --git a/src/types.d.ts b/src/types.d.ts index cc33a4a..84aad44 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -243,10 +243,6 @@ export namespace Event { name?: string } - export type M_Room_Topic = { - topic?: string - } - export type M_Room_PinnedEvents = { pinned: string[] } diff --git a/src/web/pug/guild.pug b/src/web/pug/guild.pug index 050e2c1..1d564dc 100644 --- a/src/web/pug/guild.pug +++ b/src/web/pug/guild.pug @@ -56,7 +56,7 @@ block body .fl-grow1 h2.fs-headline1 Invite a Matrix user - form.d-grid.g-af-column.gy4.gx8.jc-start(method="post" action="/api/invite" hx-post="/api/invite" hx-indicator="#invite-button") + form.d-grid.g-af-column.gy4.gx8.jc-start(method="post" action="/api/invite" style="grid-template-rows: repeat(2, auto)") label.s-label(for="mxid") Matrix ID input.fl-grow1.s-input.wmx3#mxid(name="mxid" required placeholder="@user:example.org") label.s-label(for="permissions") Permissions @@ -67,10 +67,12 @@ block body option(value="admin") Admin input(type="hidden" name="guild_id" value=guild_id) .grid--row-start2 - button.s-btn.s-btn__filled#invite-button Invite + button.s-btn.s-btn__filled Invite div != svg + h2.mt48.fs-headline1 Moderation + h2.mt48.fs-headline1 Matrix setup h3.mt32.fs-category Linked channels diff --git a/src/web/pug/invite.pug b/src/web/pug/invite.pug index 8cb977b..7f7ff2e 100644 --- a/src/web/pug/invite.pug +++ b/src/web/pug/invite.pug @@ -13,11 +13,11 @@ block body .s-page-title.mb24 h1.s-page-title--header= guild.name - .d-flex.g16#form-container + .d-flex.g16 .fl-grow1 h2.fs-headline1 Invite a Matrix user - form.d-flex.gy16.fd-column(method="post" action="/api/invite" hx-post="/api/invite" hx-indicator="#invite-button" hx-select="#ok" hx-target="#form-container") + form.d-flex.gy16.fd-column(method="post" action="/api/invite" style="grid-template-rows: repeat(2, auto)") .d-flex.gy4.fd-column label.s-label(for="mxid") Matrix ID input.fl-grow1.s-input.wmx3#mxid(name="mxid" required placeholder="@user:example.org") @@ -30,4 +30,4 @@ block body option(value="admin") Admin input(type="hidden" name="nonce" value=nonce) div - button.s-btn.s-btn__filled#invite-button Invite + button.s-btn.s-btn__filled.htmx-indicator Invite diff --git a/src/web/pug/ok.pug b/src/web/pug/ok.pug index dee4ed8..9aed737 100644 --- a/src/web/pug/ok.pug +++ b/src/web/pug/ok.pug @@ -1,6 +1,6 @@ extends includes/template.pug block body - .ta-center.wmx5.p48.mx-auto#ok + .ta-center.wmx5.p48.mx-auto != icons.Spots.SpotApproveXL p.mt24.fs-body2= msg diff --git a/src/web/routes/guild.js b/src/web/routes/guild.js index 3d0bf6f..8a1cbc2 100644 --- a/src/web/routes/guild.js +++ b/src/web/routes/guild.js @@ -2,7 +2,7 @@ const assert = require("assert/strict") const {z} = require("zod") -const {H3Event, defineEventHandler, sendRedirect, useSession, createError, getValidatedQuery, readValidatedBody, setResponseHeader} = require("h3") +const {H3Event, defineEventHandler, sendRedirect, useSession, createError, getValidatedQuery, readValidatedBody} = require("h3") const {randomUUID} = require("crypto") const {LRUCache} = require("lru-cache") const Ty = require("../../types") @@ -191,10 +191,9 @@ as.router.post("/api/invite", defineEventHandler(async event => { ( parsedBody.permissions === "admin" ? 100 : parsedBody.permissions === "moderator" ? 50 : 0) - if (powerLevel) await api.setUserPowerCascade(spaceID, parsedBody.mxid, powerLevel) + await api.setUserPowerCascade(spaceID, parsedBody.mxid, powerLevel) if (parsedBody.guild_id) { - setResponseHeader(event, "HX-Refresh", true) return sendRedirect(event, `/guild?guild_id=${guild_id}`, 302) } else { return sendRedirect(event, "/ok?msg=User has been invited.", 302) diff --git a/src/web/routes/guild.test.js b/src/web/routes/guild.test.js index a95cbea..3952d14 100644 --- a/src/web/routes/guild.test.js +++ b/src/web/routes/guild.test.js @@ -177,12 +177,16 @@ test("api invite: can invite to a moderated guild", async t => { async inviteToRoom(roomID, mxidToInvite, mxid) { t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") called++ + }, + async setUserPowerCascade(roomID, mxid, power) { + t.equal(power, 0) + called++ } } }) ) t.notOk(error) - t.equal(called, 2) + t.equal(called, 3) }) test("api invite: does not reinvite joined users", async t => { @@ -201,10 +205,14 @@ test("api invite: does not reinvite joined users", async t => { async getStateEvent(roomID, type, key) { called++ return {membership: "join"} + }, + async setUserPowerCascade(roomID, mxid, power) { + t.equal(power, 0) + called++ } } }) ) t.notOk(error) - t.equal(called, 1) + t.equal(called, 2) }) diff --git a/src/web/routes/link.js b/src/web/routes/link.js index 9db39d7..efc474f 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -3,7 +3,6 @@ const {z} = require("zod") const {defineEventHandler, useSession, createError, readValidatedBody, setResponseHeader} = require("h3") const Ty = require("../../types") -const DiscordTypes = require("discord-api-types/v10") const {discord, db, as, sync, select, from} = require("../../passthrough") /** @type {import("../../d2m/actions/create-space")} */ @@ -65,12 +64,10 @@ as.router.post("/api/link", defineEventHandler(async event => { await createRoom.syncRoom(parsedBody.discord) // Send a notification in the room - if (channel.type === DiscordTypes.ChannelType.GuildText) { - await api.sendEvent(parsedBody.matrix, "m.room.message", { - msgtype: "m.notice", - body: "👋 This room is now bridged with Discord. Say hi!" - }) - } + await api.sendEvent(parsedBody.matrix, "m.room.message", { + msgtype: "m.notice", + body: "👋 This room is now bridged with Discord. Say hi!" + }) setResponseHeader(event, "HX-Refresh", "true") return null // 204