Compare commits
No commits in common. "ad510794487cb6c27235f747141d2c21513d597c" and "5b21344a65306c3686bcc6671f98ad56abe03af1" have entirely different histories.
ad51079448
...
5b21344a65
14 changed files with 35 additions and 90 deletions
15
package-lock.json
generated
15
package-lock.json
generated
|
@ -14,7 +14,7 @@
|
||||||
"@cloudrac3r/giframe": "^0.4.3",
|
"@cloudrac3r/giframe": "^0.4.3",
|
||||||
"@cloudrac3r/html-template-tag": "^5.0.1",
|
"@cloudrac3r/html-template-tag": "^5.0.1",
|
||||||
"@cloudrac3r/in-your-element": "^1.0.0",
|
"@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/pngjs": "^7.0.3",
|
||||||
"@cloudrac3r/pug": "^4.0.4",
|
"@cloudrac3r/pug": "^4.0.4",
|
||||||
"@cloudrac3r/turndown": "^7.1.4",
|
"@cloudrac3r/turndown": "^7.1.4",
|
||||||
|
@ -281,10 +281,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cloudrac3r/mixin-deep": {
|
"node_modules/@cloudrac3r/mixin-deep": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@cloudrac3r/mixin-deep/-/mixin-deep-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cloudrac3r/mixin-deep/-/mixin-deep-3.0.0.tgz",
|
||||||
"integrity": "sha512-awxfIraHjJ/URNlZ0ROc78Tdjtfk/fM/Gnj1embfrSN08h/HpRtLmPc3xlG3T2vFAy1AkONaebd52u7o6kDaYw==",
|
"integrity": "sha512-yQz1wHSZbHfbKaGSjrV3wIG0e9MnElKlmekMKJPRdTn2jhF2Mt8wfMPX8U7v6rTyzR/7BTrX8CCUcrJMLgoQqw==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
@ -3218,9 +3217,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "6.21.1",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz",
|
||||||
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
|
"integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17"
|
"node": ">=18.17"
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"@cloudrac3r/giframe": "^0.4.3",
|
"@cloudrac3r/giframe": "^0.4.3",
|
||||||
"@cloudrac3r/html-template-tag": "^5.0.1",
|
"@cloudrac3r/html-template-tag": "^5.0.1",
|
||||||
"@cloudrac3r/in-your-element": "^1.0.0",
|
"@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/pngjs": "^7.0.3",
|
||||||
"@cloudrac3r/pug": "^4.0.4",
|
"@cloudrac3r/pug": "^4.0.4",
|
||||||
"@cloudrac3r/turndown": "^7.1.4",
|
"@cloudrac3r/turndown": "^7.1.4",
|
||||||
|
|
|
@ -11,8 +11,6 @@ const {discord, sync, db, select, from} = passthrough
|
||||||
const file = sync.require("../../matrix/file")
|
const file = sync.require("../../matrix/file")
|
||||||
/** @type {import("../../matrix/api")} */
|
/** @type {import("../../matrix/api")} */
|
||||||
const api = sync.require("../../matrix/api")
|
const api = sync.require("../../matrix/api")
|
||||||
/** @type {import("../../matrix/mreq")} */
|
|
||||||
const mreq = sync.require("../../matrix/mreq")
|
|
||||||
/** @type {import("../../matrix/kstate")} */
|
/** @type {import("../../matrix/kstate")} */
|
||||||
const ks = sync.require("../../matrix/kstate")
|
const ks = sync.require("../../matrix/kstate")
|
||||||
/** @type {import("../../discord/utils")} */
|
/** @type {import("../../discord/utils")} */
|
||||||
|
@ -89,10 +87,9 @@ async function channelToKState(channel, guild, di) {
|
||||||
assert(typeof parentSpaceID === "string")
|
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 customName = channelRow?.nick
|
||||||
const customAvatar = channelRow?.custom_avatar
|
const customAvatar = channelRow?.custom_avatar
|
||||||
const hasCustomTopic = channelRow?.custom_topic
|
|
||||||
const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName)
|
const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName)
|
||||||
|
|
||||||
const avatarEventContent = {}
|
const avatarEventContent = {}
|
||||||
|
@ -168,8 +165,6 @@ async function channelToKState(channel, guild, di) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCustomTopic) delete channelKState["m.room.topic/"]
|
|
||||||
|
|
||||||
return {spaceID: parentSpaceID, privacyLevel, channelKState}
|
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()
|
const row = from("guild_space").join("guild_active", "guild_id").select("space_id", "autocreate").get()
|
||||||
assert.ok(row)
|
assert.ok(row)
|
||||||
|
|
||||||
let botInRoom = true
|
|
||||||
|
|
||||||
// remove declaration that the room is bridged
|
// remove declaration that the room is bridged
|
||||||
try {
|
await api.sendState(roomID, "uk.half-shot.bridge", `moe.cadence.ooye://discord/${guildID}/${channel.id}`, {})
|
||||||
await api.sendState(roomID, "uk.half-shot.bridge", `moe.cadence.ooye://discord/${guildID}/${channel.id}`, {})
|
if ("topic" in channel) {
|
||||||
} catch (e) {
|
|
||||||
if (String(e).includes("not in room")) {
|
|
||||||
botInRoom = false
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botInRoom && "topic" in channel) {
|
|
||||||
// previously the Matrix topic would say the channel ID. we should remove that
|
// previously the Matrix topic would say the channel ID. we should remove that
|
||||||
await api.sendState(roomID, "m.room.topic", "", {topic: channel.topic || ""})
|
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 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
|
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
|
// demote admins in room
|
||||||
/** @type {Ty.Event.M_Power_Levels} */
|
/** @type {Ty.Event.M_Power_Levels} */
|
||||||
const powerLevelContent = await api.getStateEvent(roomID, "m.room.power_levels", "")
|
const powerLevelContent = await api.getStateEvent(roomID, "m.room.power_levels", "")
|
||||||
|
|
|
@ -94,26 +94,6 @@ test("channel2room: room where limited people can mention everyone", async t =>
|
||||||
t.equal(called, 1)
|
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 => {
|
test("convertNameAndTopic: custom name and topic", t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"),
|
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"),
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE channel_room ADD COLUMN custom_topic INTEGER DEFAULT 0;
|
|
||||||
|
|
||||||
COMMIT;
|
|
2
src/db/orm-defs.d.ts
vendored
2
src/db/orm-defs.d.ts
vendored
|
@ -10,8 +10,6 @@ export type Models = {
|
||||||
speedbump_id: string | null
|
speedbump_id: string | null
|
||||||
speedbump_webhook_id: string | null
|
speedbump_webhook_id: string | null
|
||||||
speedbump_checked: number | null
|
speedbump_checked: number | null
|
||||||
guild_id: string | null
|
|
||||||
custom_topic: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event_message: {
|
event_message: {
|
||||||
|
|
|
@ -164,17 +164,6 @@ async event => {
|
||||||
db.prepare("UPDATE channel_room SET nick = ? WHERE room_id = ?").run(name, event.room_id)
|
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<Ty.Event.M_Room_Topic>} 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",
|
sync.addTemporaryListener(as, "type:m.room.pinned_events", guard("m.room.pinned_events",
|
||||||
/**
|
/**
|
||||||
* @param {Ty.Event.StateOuter<Ty.Event.M_Room_PinnedEvents>} event
|
* @param {Ty.Event.StateOuter<Ty.Event.M_Room_PinnedEvents>} event
|
||||||
|
|
4
src/types.d.ts
vendored
4
src/types.d.ts
vendored
|
@ -243,10 +243,6 @@ export namespace Event {
|
||||||
name?: string
|
name?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type M_Room_Topic = {
|
|
||||||
topic?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type M_Room_PinnedEvents = {
|
export type M_Room_PinnedEvents = {
|
||||||
pinned: string[]
|
pinned: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ block body
|
||||||
.fl-grow1
|
.fl-grow1
|
||||||
h2.fs-headline1 Invite a Matrix user
|
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
|
label.s-label(for="mxid") Matrix ID
|
||||||
input.fl-grow1.s-input.wmx3#mxid(name="mxid" required placeholder="@user:example.org")
|
input.fl-grow1.s-input.wmx3#mxid(name="mxid" required placeholder="@user:example.org")
|
||||||
label.s-label(for="permissions") Permissions
|
label.s-label(for="permissions") Permissions
|
||||||
|
@ -67,10 +67,12 @@ block body
|
||||||
option(value="admin") Admin
|
option(value="admin") Admin
|
||||||
input(type="hidden" name="guild_id" value=guild_id)
|
input(type="hidden" name="guild_id" value=guild_id)
|
||||||
.grid--row-start2
|
.grid--row-start2
|
||||||
button.s-btn.s-btn__filled#invite-button Invite
|
button.s-btn.s-btn__filled Invite
|
||||||
div
|
div
|
||||||
!= svg
|
!= svg
|
||||||
|
|
||||||
|
h2.mt48.fs-headline1 Moderation
|
||||||
|
|
||||||
h2.mt48.fs-headline1 Matrix setup
|
h2.mt48.fs-headline1 Matrix setup
|
||||||
|
|
||||||
h3.mt32.fs-category Linked channels
|
h3.mt32.fs-category Linked channels
|
||||||
|
|
|
@ -13,11 +13,11 @@ block body
|
||||||
.s-page-title.mb24
|
.s-page-title.mb24
|
||||||
h1.s-page-title--header= guild.name
|
h1.s-page-title--header= guild.name
|
||||||
|
|
||||||
.d-flex.g16#form-container
|
.d-flex.g16
|
||||||
.fl-grow1
|
.fl-grow1
|
||||||
h2.fs-headline1 Invite a Matrix user
|
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
|
.d-flex.gy4.fd-column
|
||||||
label.s-label(for="mxid") Matrix ID
|
label.s-label(for="mxid") Matrix ID
|
||||||
input.fl-grow1.s-input.wmx3#mxid(name="mxid" required placeholder="@user:example.org")
|
input.fl-grow1.s-input.wmx3#mxid(name="mxid" required placeholder="@user:example.org")
|
||||||
|
@ -30,4 +30,4 @@ block body
|
||||||
option(value="admin") Admin
|
option(value="admin") Admin
|
||||||
input(type="hidden" name="nonce" value=nonce)
|
input(type="hidden" name="nonce" value=nonce)
|
||||||
div
|
div
|
||||||
button.s-btn.s-btn__filled#invite-button Invite
|
button.s-btn.s-btn__filled.htmx-indicator Invite
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
extends includes/template.pug
|
extends includes/template.pug
|
||||||
|
|
||||||
block body
|
block body
|
||||||
.ta-center.wmx5.p48.mx-auto#ok
|
.ta-center.wmx5.p48.mx-auto
|
||||||
!= icons.Spots.SpotApproveXL
|
!= icons.Spots.SpotApproveXL
|
||||||
p.mt24.fs-body2= msg
|
p.mt24.fs-body2= msg
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
const assert = require("assert/strict")
|
const assert = require("assert/strict")
|
||||||
const {z} = require("zod")
|
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 {randomUUID} = require("crypto")
|
||||||
const {LRUCache} = require("lru-cache")
|
const {LRUCache} = require("lru-cache")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
@ -191,10 +191,9 @@ as.router.post("/api/invite", defineEventHandler(async event => {
|
||||||
( parsedBody.permissions === "admin" ? 100
|
( parsedBody.permissions === "admin" ? 100
|
||||||
: parsedBody.permissions === "moderator" ? 50
|
: parsedBody.permissions === "moderator" ? 50
|
||||||
: 0)
|
: 0)
|
||||||
if (powerLevel) await api.setUserPowerCascade(spaceID, parsedBody.mxid, powerLevel)
|
await api.setUserPowerCascade(spaceID, parsedBody.mxid, powerLevel)
|
||||||
|
|
||||||
if (parsedBody.guild_id) {
|
if (parsedBody.guild_id) {
|
||||||
setResponseHeader(event, "HX-Refresh", true)
|
|
||||||
return sendRedirect(event, `/guild?guild_id=${guild_id}`, 302)
|
return sendRedirect(event, `/guild?guild_id=${guild_id}`, 302)
|
||||||
} else {
|
} else {
|
||||||
return sendRedirect(event, "/ok?msg=User has been invited.", 302)
|
return sendRedirect(event, "/ok?msg=User has been invited.", 302)
|
||||||
|
|
|
@ -177,12 +177,16 @@ test("api invite: can invite to a moderated guild", async t => {
|
||||||
async inviteToRoom(roomID, mxidToInvite, mxid) {
|
async inviteToRoom(roomID, mxidToInvite, mxid) {
|
||||||
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe")
|
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe")
|
||||||
called++
|
called++
|
||||||
|
},
|
||||||
|
async setUserPowerCascade(roomID, mxid, power) {
|
||||||
|
t.equal(power, 0)
|
||||||
|
called++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
t.notOk(error)
|
t.notOk(error)
|
||||||
t.equal(called, 2)
|
t.equal(called, 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("api invite: does not reinvite joined users", async t => {
|
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) {
|
async getStateEvent(roomID, type, key) {
|
||||||
called++
|
called++
|
||||||
return {membership: "join"}
|
return {membership: "join"}
|
||||||
|
},
|
||||||
|
async setUserPowerCascade(roomID, mxid, power) {
|
||||||
|
t.equal(power, 0)
|
||||||
|
called++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
t.notOk(error)
|
t.notOk(error)
|
||||||
t.equal(called, 1)
|
t.equal(called, 2)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
const {z} = require("zod")
|
const {z} = require("zod")
|
||||||
const {defineEventHandler, useSession, createError, readValidatedBody, setResponseHeader} = require("h3")
|
const {defineEventHandler, useSession, createError, readValidatedBody, setResponseHeader} = require("h3")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
|
||||||
|
|
||||||
const {discord, db, as, sync, select, from} = require("../../passthrough")
|
const {discord, db, as, sync, select, from} = require("../../passthrough")
|
||||||
/** @type {import("../../d2m/actions/create-space")} */
|
/** @type {import("../../d2m/actions/create-space")} */
|
||||||
|
@ -65,12 +64,10 @@ as.router.post("/api/link", defineEventHandler(async event => {
|
||||||
await createRoom.syncRoom(parsedBody.discord)
|
await createRoom.syncRoom(parsedBody.discord)
|
||||||
|
|
||||||
// Send a notification in the room
|
// Send a notification in the room
|
||||||
if (channel.type === DiscordTypes.ChannelType.GuildText) {
|
await api.sendEvent(parsedBody.matrix, "m.room.message", {
|
||||||
await api.sendEvent(parsedBody.matrix, "m.room.message", {
|
msgtype: "m.notice",
|
||||||
msgtype: "m.notice",
|
body: "👋 This room is now bridged with Discord. Say hi!"
|
||||||
body: "👋 This room is now bridged with Discord. Say hi!"
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setResponseHeader(event, "HX-Refresh", "true")
|
setResponseHeader(event, "HX-Refresh", "true")
|
||||||
return null // 204
|
return null // 204
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue