From 5716366b3f4e414cee9fd1687757db811ca68279 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 5 May 2023 08:25:00 +1200 Subject: [PATCH] space and room creation --- d2m/actions/create-room.js | 113 ++++++++++++++++++++++++++++++------ d2m/actions/create-space.js | 46 +++++++++++++++ d2m/actions/send-message.js | 4 +- d2m/discord-packets.js | 3 + d2m/event-dispatcher.js | 15 +++-- db/ooye.db | Bin 0 -> 40960 bytes db/ooye.sql | 81 ++++++++++++++++++++++++++ index.js | 4 +- matrix/file.js | 63 ++++++++++++++++++++ matrix/mreq.js | 47 +++++++++++++++ matrix/read-registration.js | 8 +-- notes.md | 25 +++++++- package-lock.json | 9 +++ package.json | 1 + passthrough.js | 1 + stdin.js | 7 ++- types.d.ts | 18 ++++++ 17 files changed, 409 insertions(+), 36 deletions(-) create mode 100644 d2m/actions/create-space.js create mode 100644 db/ooye.db create mode 100644 db/ooye.sql create mode 100644 matrix/file.js create mode 100644 matrix/mreq.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7e00651..b4934dc 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -1,22 +1,99 @@ // @ts-check -const reg = require("../../matrix/read-registration.js") -const fetch = require("node-fetch") +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") -fetch("https://matrix.cadence.moe/_matrix/client/v3/createRoom?user_id=@_ooye_example:cadence.moe", { - method: "POST", - body: JSON.stringify({ - invite: ["@cadence:cadence.moe"], - is_direct: false, - name: "New Bot User Room", - preset: "trusted_private_chat" - }), - headers: { - Authorization: `Bearer ${reg.as_token}` +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +/** + * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + */ +async function createRoom(channel) { + const guildID = channel.guild_id + assert.ok(guildID) + const guild = discord.guilds.get(guildID) + assert.ok(guild) + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guildID) + assert.ok(typeof spaceID === "string") + + const avatarEventContent = {} + if (guild.icon) { + avatarEventContent.url = await file.uploadDiscordFileToMxc(file.guildIcon(guild)) } -}).then(res => res.text()).then(text => { - // {"room_id":"!aAVaqeAKwChjWbsywj:cadence.moe"} - console.log(text) -}).catch(err => { - console.log(err) -}) + + /** @type {import("../../types").R_RoomCreated} */ + const root = await mreq.mreq("POST", "/client/v3/createRoom", { + name: channel.name, + topic: channel.topic || undefined, + preset: "private_chat", + visibility: "private", + invite: ["@cadence:cadence.moe"], // TODO + initial_state: [ + { + type: "m.room.avatar", + state_key: "", + content: avatarEventContent + }, + { + type: "m.room.guest_access", + state_key: "", + content: { + guest_access: "can_join" + } + }, + { + type: "m.room.history_visibility", + state_key: "", + content: { + history_visibility: "invited" + } + }, + { + type: "m.space.parent", + state_key: spaceID, + content: { + via: ["cadence.moe"], // TODO: put the proper server here + canonical: true + } + }, + { + type: "m.room.join_rules", + content: { + join_rule: "restricted", + allow: [{ + type: "m.room.membership", + room_id: spaceID + }] + } + } + ] + }) + + db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) + + // Put the newly created child into the space + await mreq.mreq("PUT", `/client/v3/rooms/${spaceID}/state/m.space.child/${root.room_id}`, { + via: ["cadence.moe"] // TODO: use the proper server + }) +} + +async function createAllForGuild(guildID) { + const channelIDs = discord.guildChannelMap.get(guildID) + assert.ok(channelIDs) + for (const channelID of channelIDs) { + const channel = discord.channels.get(channelID) + assert.ok(channel) + const existing = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + if (channel.type === DiscordTypes.ChannelType.GuildText && !existing) { + await createRoom(channel) + } + } +} + +module.exports.createRoom = createRoom +module.exports.createAllForGuild = createAllForGuild diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js new file mode 100644 index 0000000..6d3c327 --- /dev/null +++ b/d2m/actions/create-space.js @@ -0,0 +1,46 @@ +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") + +/** + * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild + */ +function createSpace(guild) { + return mreq.mreq("POST", "/client/v3/createRoom", { + name: guild.name, + preset: "private_chat", + visibility: "private", + power_level_content_override: { + events_default: 100, + invite: 50 + }, + invite: ["@cadence:cadence.moe"], // TODO + topic: guild.description || undefined, + creation_content: { + type: "m.space" + }, + initial_state: [ + { + type: "m.room.guest_access", + state_key: "", + content: { + guest_access: "can_join" + } + }, + { + type: "m.room.history_visibility", + content: { + history_visibility: "invited" + } + } + ] + }).then(/** @param {import("../../types").R_RoomCreated} root */ root => { + db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) + return root + }) +} + +module.exports.createSpace = createSpace diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index a4dd9a2..b736a50 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -10,7 +10,7 @@ const messageToEvent = require("../converters/message-to-event.js") */ function sendMessage(message) { const event = messageToEvent(message) - fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { + return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { method: "PUT", body: JSON.stringify(event), headers: { @@ -24,4 +24,4 @@ function sendMessage(message) { }) } -module.exports = sendMessage +module.exports.sendMessage = sendMessage diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index fe97d20..0d16cdc 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -2,6 +2,7 @@ // Discord library internals type beat +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../passthrough") const { sync } = passthrough @@ -27,6 +28,8 @@ const utils = { const arr = [] client.guildChannelMap.set(message.d.id, arr) for (const channel of message.d.channels || []) { + // @ts-ignore + channel.guild_id = message.d.id arr.push(channel.id) client.channels.set(channel.id, channel) } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 13626f4..e4de37e 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,8 +1,14 @@ // @ts-check -// Grab Discord events we care about for the bridge, check them, and pass them on +const {sync} = require("../passthrough") -const sendMessage = require("./actions/send-message") +/** @type {import("./actions/create-space")}) */ +const createSpace = sync.require("./actions/create-space") + +/** @type {import("./actions/send-message")}) */ +const sendMessage = sync.require("./actions/send-message") + +// Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { /** @@ -10,10 +16,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { - console.log(message) - console.log(message.guild_id) - console.log(message.member) - sendMessage(message) + sendMessage.sendMessage(message) }, /** diff --git a/db/ooye.db b/db/ooye.db new file mode 100644 index 0000000000000000000000000000000000000000..dea5853e5cb352179d82280dc355fe84c149f6d0 GIT binary patch literal 40960 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 a0ipm=fG9u|APT%I6}YikwZHTG|NjL>} */ +const inflight = new Map() + +/** + * @param {string} path + */ +async function uploadDiscordFileToMxc(path) { + const url = DISCORD_IMAGES_BASE + path + + // Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution + let existing = inflight.get(url) + if (typeof existing === "string") { + return existing + } + + // Has this file already been uploaded in the past? Grab the existing copy from the database. + existing = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) + if (typeof existing === "string") { + return existing + } + + // Download from Discord + const promise = fetch(url, {}).then(/** @param {import("node-fetch").Response} res */ async res => { + /** @ts-ignore @type {import("stream").Readable} body */ + const body = res.body + + // Upload to Matrix + /** @type {import("../types").R_FileUploaded} */ + const root = await mreq.mreq("POST", "/media/v3/upload", body, { + headers: { + "Content-Type": res.headers.get("content-type") + } + }) + + // Store relationship in database + db.prepare("INSERT INTO file (discord_url, mxc_url) VALUES (?, ?)").run(url, root.content_uri) + inflight.delete(url) + + return root.content_uri + }) + inflight.set(url, promise) + + return promise +} + +function guildIcon(guild) { + return `/icons/${guild.id}/${guild.icon}?size=${IMAGE_SIZE}` +} + +module.exports.guildIcon = guildIcon +module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/matrix/mreq.js b/matrix/mreq.js new file mode 100644 index 0000000..ad7fa46 --- /dev/null +++ b/matrix/mreq.js @@ -0,0 +1,47 @@ +// @ts-check + +const fetch = require("node-fetch") +const mixin = require("mixin-deep") + +const passthrough = require("../passthrough") +const { sync } = passthrough +/** @type {import("./read-registration")} */ +const reg = sync.require("./read-registration.js") + +const baseUrl = "https://matrix.cadence.moe/_matrix" + +class MatrixServerError { + constructor(data) { + this.data = data + /** @type {string} */ + this.errcode = data.errcode + /** @type {string} */ + this.error = data.error + } +} + +/** + * @param {string} method + * @param {string} url + * @param {any} [body] + * @param {any} [extra] + */ +function mreq(method, url, body, extra = {}) { + const opts = mixin({ + method, + body: (body == undefined || Object.is(body.constructor, Object)) ? JSON.stringify(body) : body, + headers: { + Authorization: `Bearer ${reg.as_token}` + } + }, extra) + console.log(baseUrl + url, opts) + return fetch(baseUrl + url, opts).then(res => { + return res.json().then(root => { + if (!res.ok || root.errcode) throw new MatrixServerError(root) + return root + }) + }) +} + +module.exports.MatrixServerError = MatrixServerError +module.exports.mreq = mreq diff --git a/matrix/read-registration.js b/matrix/read-registration.js index ee17f28..cc3fed8 100644 --- a/matrix/read-registration.js +++ b/matrix/read-registration.js @@ -3,11 +3,5 @@ const fs = require("fs") const yaml = require("js-yaml") -/** - * @typedef AppServiceRegistrationConfig - * @property {string} id - * @property {string} as_token - * @property {string} hs_token - */ - +/** @type {import("../types").AppServiceRegistrationConfig} */ module.exports = yaml.load(fs.readFileSync("registration.yaml", "utf8")) diff --git a/notes.md b/notes.md index eed5990..e63d9e5 100644 --- a/notes.md +++ b/notes.md @@ -36,6 +36,13 @@ Public channels in that server should then use the following settings, so that t - Find & join access: Space members (so users must have been invited to the space already, even if they find out the room ID to join) - Who can read history: Anyone (so that people can see messages during the preview before joining) +Step by step process: + +1. Create a space room for the guild. Store the guild-space ID relationship in the database. Configure the space room to act like a space. + - `{"name":"NAME","preset":"private_chat","visibility":"private","power_level_content_override":{"events_default":100,"invite":50},"topic":"TOPIC","creation_content":{"type":"m.space"},"initial_state":[{"type":"m.room.guest_access","state_key":"","content":{"guest_access":"can_join"}},{"type":"m.room.history_visibility","content":{"history_visibility":"invited"}}]}` +2. Create channel rooms for the channels. Store the channel-room ID relationship in the database. (Probably no need to store parent-child relationships in the database?) +3. Send state events to put the channel rooms in the space. + ### Private channels Discord **channels** that disallow view permission to @everyone should instead have the following **room** settings in Matrix: @@ -58,6 +65,13 @@ The context-sensitive /invite command will invite Matrix users to the correspond 1. Transform content. 2. Send to matrix. +## Webhook message sent + +- Consider using the _ooye_bot account to send all webhook messages to prevent extraneous joins? + - Downside: the profile information from the most recently sent message would stick around in the member list. This is toleable. +- Otherwise, could use an account per webhook ID, but if webhook IDs are often deleted and re-created, this could still end up leaving too many accounts in the room. +- The original bridge uses an account per webhook display name, which does the most sense in terms of canonical accounts, but leaves too many accounts in the room. + ## Message deleted 1. Look up equivalents on matrix. @@ -91,4 +105,13 @@ The context-sensitive /invite command will invite Matrix users to the correspond 1. Create the corresponding room. 2. Add to database. 3. Update room details to match. -4. Add to space. +4. Make sure the permissions are correct according to the rules above! +5. Add to space. + +## Emojis updated + +1. Upload any newly added images to msc. +2. Create or replace state event for the bridged pack. (Can just use key "ooye" and display name "Discord", or something, for this pack.) +3. The emojis may now be sent by Matrix users! + +TOSPEC: m2d emoji uploads?? diff --git a/package-lock.json b/package-lock.json index a653203..31df4c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", + "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", "snowtransfer": "^0.7.0", "supertape": "^8.3.0" @@ -1696,6 +1697,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mixin-deep": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", + "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==", + "engines": { + "node": ">=6" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", diff --git a/package.json b/package.json index 7a7de4a..6755f9f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", + "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", "snowtransfer": "^0.7.0", "supertape": "^8.3.0" diff --git a/passthrough.js b/passthrough.js index b6bc56f..8ef75db 100644 --- a/passthrough.js +++ b/passthrough.js @@ -6,6 +6,7 @@ * @property {typeof import("./config")} config * @property {import("./d2m/discord-client")} discord * @property {import("heatsync")} sync + * @property {import("better-sqlite3/lib/database")} db */ /** @type {Passthrough} */ // @ts-ignore diff --git a/stdin.js b/stdin.js index be38b06..fb95809 100644 --- a/stdin.js +++ b/stdin.js @@ -4,7 +4,12 @@ const repl = require("repl") const util = require("util") const passthrough = require("./passthrough") -const { discord, config, sync } = passthrough +const { discord, config, sync, db } = passthrough + +const createSpace = sync.require("./d2m/actions/create-space.js") +const createRoom = sync.require("./d2m/actions/create-room.js") +const mreq = sync.require("./matrix/mreq.js") +const guildID = "112760669178241024" const extraContext = {} diff --git a/types.d.ts b/types.d.ts index 180a559..d571359 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,6 +1,24 @@ +export type AppServiceRegistrationConfig = { + id: string + as_token: string + hs_token: string + url: string + sender_localpart: string + protocols: [string] + rate_limited: boolean +} + export type M_Room_Message_content = { msgtype: "m.text" body: string formatted_body?: "org.matrix.custom.html" format?: string } + +export type R_RoomCreated = { + room_id: string +} + +export type R_FileUploaded = { + content_uri: string +}