support threads

This commit is contained in:
Cadence Ember 2023-08-19 18:39:23 +12:00
parent 213bf0a515
commit 0fc8e68f15
9 changed files with 172 additions and 63 deletions

View file

@ -21,8 +21,8 @@ async function roomToKState(roomID) {
}
/**
* @params {string} roomID
* @params {any} kstate
* @param {string} roomID
* @param {any} kstate
*/
function applyKStateDiffToRoom(roomID, kstate) {
const events = ks.kstateToState(kstate)
@ -51,7 +51,7 @@ function convertNameAndTopic(channel, guild, customName) {
}
/**
* @param {DiscordTypes.APIGuildTextChannel} channel
* @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel
* @param {DiscordTypes.APIGuild} guild
*/
async function channelToKState(channel, guild) {
@ -98,21 +98,27 @@ async function channelToKState(channel, guild) {
* @returns {Promise<string>} room ID
*/
async function createRoom(channel, guild, spaceID, kstate) {
const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null)
const roomID = await api.createRoom({
name: channel.name,
topic: channel.topic || undefined,
name: convertedName,
topic: convertedTopic,
preset: "private_chat",
visibility: "private",
invite: ["@cadence:cadence.moe"], // TODO
initial_state: ks.kstateToState(kstate)
})
db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID)
let threadParent = null
if (channel.type === DiscordTypes.ChannelType.PublicThread) {
/** @type {DiscordTypes.APIThreadChannel} */ // @ts-ignore
const thread = channel
threadParent = thread.parent_id
}
db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent)
// Put the newly created child into the space
await api.sendState(spaceID, "m.space.child", roomID, { // TODO: should I deduplicate with the equivalent code from syncRoom?
via: ["cadence.moe"] // TODO: use the proper server
})
_syncSpaceMember(channel, spaceID, roomID)
return roomID
}
@ -156,14 +162,15 @@ async function _syncRoom(channelID, shouldActuallySync) {
assert.ok(channel)
const guild = channelToGuild(channel)
/** @type {string?} */
const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id)
/** @type {{room_id: string, thread_parent: string?}} */
const existing = db.prepare("SELECT room_id, thread_parent from channel_room WHERE channel_id = ?").get(channelID)
if (!existing) {
const {spaceID, channelKState} = await channelToKState(channel, guild)
return createRoom(channel, guild, spaceID, channelKState)
} else {
if (!shouldActuallySync) {
return existing // only need to ensure room exists, and it does. return the room ID
return existing.room_id // only need to ensure room exists, and it does. return the room ID
}
console.log(`[room sync] to matrix: ${channel.name}`)
@ -171,24 +178,41 @@ async function _syncRoom(channelID, shouldActuallySync) {
const {spaceID, channelKState} = await channelToKState(channel, guild)
// sync channel state to room
const roomKState = await roomToKState(existing)
const roomKState = await roomToKState(existing.room_id)
const roomDiff = ks.diffKState(roomKState, channelKState)
const roomApply = applyKStateDiffToRoom(existing, roomDiff)
const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff)
// sync room as space member
const spaceKState = await roomToKState(spaceID)
const spaceDiff = ks.diffKState(spaceKState, {
[`m.space.child/${existing}`]: {
via: ["cadence.moe"] // TODO: use the proper server
}
})
const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff)
const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id)
await Promise.all([roomApply, spaceApply])
return existing
return existing.room_id
}
}
/**
* @param {DiscordTypes.APIGuildTextChannel} channel
* @param {string} spaceID
* @param {string} roomID
* @returns {Promise<string[]>}
*/
async function _syncSpaceMember(channel, spaceID, roomID) {
const spaceKState = await roomToKState(spaceID)
let spaceEventContent = {}
if (
channel.type !== DiscordTypes.ChannelType.PrivateThread // private threads do not belong in the space (don't offer people something they can't join)
|| channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant)
) {
spaceEventContent = {
via: ["cadence.moe"] // TODO: use the proper server
}
}
const spaceDiff = ks.diffKState(spaceKState, {
[`m.space.child/${roomID}`]: spaceEventContent
})
return applyKStateDiffToRoom(spaceID, spaceDiff)
}
function ensureRoom(channelID) {
return _syncRoom(channelID, false)
}

View file

@ -1,5 +1,6 @@
// @ts-check
const assert = require("assert")
const passthrough = require("../../passthrough")
const { sync, db } = passthrough
/** @type {import("../../matrix/api")} */
@ -9,13 +10,14 @@ const api = sync.require("../../matrix/api")
* @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild
*/
async function createSpace(guild) {
assert(guild.name)
const roomID = await api.createRoom({
name: guild.name,
preset: "private_chat",
preset: "private_chat", // cannot join space unless invited
visibility: "private",
power_level_content_override: {
events_default: 100,
invite: 50
events_default: 100, // space can only be managed by bridge
invite: 0 // any existing member can invite others
},
invite: ["@cadence:cadence.moe"], // TODO
topic: guild.description || undefined,
@ -27,13 +29,13 @@ async function createSpace(guild) {
type: "m.room.guest_access",
state_key: "",
content: {
guest_access: "can_join"
guest_access: "can_join" // guests can join space if other conditions are met
}
},
{
type: "m.room.history_visibility",
content: {
history_visibility: "invited"
history_visibility: "invited" // any events sent after user was invited are visible
}
}
]

View file

@ -35,6 +35,12 @@ const utils = {
arr.push(channel.id)
client.channels.set(channel.id, channel)
}
for (const thread of message.d.threads || []) {
// @ts-ignore
thread.guild_id = message.d.id
arr.push(thread.id)
client.channels.set(thread.id, thread)
}
} else if (message.t === "GUILD_DELETE") {

View file

@ -15,6 +15,10 @@ const api = sync.require("../matrix/api")
let lastReportedEvent = 0
function isGuildAllowed(guildID) {
return ["112760669178241024", "497159726455455754", "1100319549670301727"].includes(guildID)
}
// Grab Discord events we care about for the bridge, check them, and pass them on
module.exports = {
@ -29,31 +33,34 @@ module.exports = {
console.error(`while handling this ${gatewayMessage.t} gateway event:`)
console.dir(gatewayMessage.d, {depth: null})
if (Date.now() - lastReportedEvent > 5000) {
lastReportedEvent = Date.now()
const channelID = gatewayMessage.d.channel_id
if (channelID) {
const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID)
let stackLines = e.stack.split("\n")
let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/"))
if (cloudstormLine !== -1) {
stackLines = stackLines.slice(0, cloudstormLine - 2)
}
api.sendEvent(roomID, "m.room.message", {
msgtype: "m.text",
body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.",
format: "org.matrix.custom.html",
formatted_body: "\u26a0 <strong>Bridged event from Discord not delivered</strong>"
+ `<br>Gateway event: ${gatewayMessage.t}`
+ `<pre>${stackLines.join("\n")}</pre>`
+ `<details><summary>Original payload</summary>`
+ `<pre>${util.inspect(gatewayMessage.d, false, 4, false)}</pre></details>`,
"m.mentions": {
user_ids: ["@cadence:cadence.moe"]
}
})
}
if (Date.now() - lastReportedEvent < 5000) return
lastReportedEvent = Date.now()
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)
if (!roomID) return
let stackLines = e.stack.split("\n")
let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/"))
if (cloudstormLine !== -1) {
stackLines = stackLines.slice(0, cloudstormLine - 2)
}
api.sendEvent(roomID, "m.room.message", {
msgtype: "m.text",
body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.",
format: "org.matrix.custom.html",
formatted_body: "\u26a0 <strong>Bridged event from Discord not delivered</strong>"
+ `<br>Gateway event: ${gatewayMessage.t}`
+ `<br>${e.toString()}`
+ `<details><summary>Error trace</summary>`
+ `<pre>${stackLines.join("\n")}</pre></details>`
+ `<details><summary>Original payload</summary>`
+ `<pre>${util.inspect(gatewayMessage.d, false, 4, false)}</pre></details>`,
"m.mentions": {
user_ids: ["@cadence:cadence.moe"]
}
})
},
/**
@ -72,7 +79,7 @@ module.exports = {
const channel = client.channels.get(message.channel_id)
if (!channel.guild_id) return // Nothing we can do in direct messages.
const guild = client.guilds.get(channel.guild_id)
if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first)
if (!isGuildAllowed(guild.id)) return
await sendMessage.sendMessage(message, guild)
},
@ -97,7 +104,7 @@ module.exports = {
const channel = client.channels.get(message.channel_id)
if (!channel.guild_id) return // Nothing we can do in direct messages.
const guild = client.guilds.get(channel.guild_id)
if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first)
if (!isGuildAllowed(guild.id)) return
await editMessage.editMessage(message, guild)
}
},
@ -109,7 +116,6 @@ module.exports = {
async onReactionAdd(client, data) {
if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix.
if (data.emoji.id !== null) return // TODO: image emoji reactions
console.log(data)
await addReaction.addReaction(data)
},
@ -118,7 +124,6 @@ module.exports = {
* @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data
*/
async onMessageDelete(client, data) {
console.log(data)
await deleteMessage.deleteMessage(data)
}
}