diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 96a9671..479bb79 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -32,12 +32,13 @@ function applyKStateDiffToRoom(roomID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel - * @param {import("discord-api-types/v10").APIGuild} guild + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuild} guild */ async function channelToKState(channel, guild) { const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) assert.ok(typeof spaceID === "string") + const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) const avatarEventContent = {} if (guild.icon) { @@ -45,9 +46,27 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } + // TODO: Improve nasty nested ifs + let convertedName, convertedTopic + if (customName) { + convertedName = customName + if (channel.topic) { + convertedTopic = `${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } else { + convertedTopic = `${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } + } else { + convertedName = channel.name + if (channel.topic) { + convertedTopic = `${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } else { + convertedTopic = `Channel ID: ${channel.id}\nGuild ID: ${guild.id}` + } + } + const channelKState = { - "m.room.name/": {name: channel.name}, - "m.room.topic/": {$if: channel.topic, topic: channel.topic}, + "m.room.name/": {name: convertedName}, + "m.room.topic/": {topic: convertedTopic}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, @@ -58,7 +77,7 @@ async function channelToKState(channel, guild) { "m.room.join_rules/": { join_rule: "restricted", allow: [{ - type: "m.room.membership", + type: "m.room_membership", room_id: spaceID }] } @@ -69,7 +88,7 @@ async function channelToKState(channel, guild) { /** * Create a bridge room, store the relationship in the database, and add it to the guild's space. - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuildTextChannel} channel * @param guild * @param {string} spaceID * @param {any} kstate @@ -96,7 +115,7 @@ async function createRoom(channel, guild, spaceID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildChannel} channel + * @param {DiscordTypes.APIGuildChannel} channel */ function channelToGuild(channel) { const guildID = channel.guild_id @@ -129,7 +148,7 @@ function channelToGuild(channel) { * @returns {Promise} room ID */ async function _syncRoom(channelID, shouldActuallySync) { - /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + /** @ts-ignore @type {DiscordTypes.APIGuildChannel} */ const channel = discord.channels.get(channelID) assert.ok(channel) const guild = channelToGuild(channel) @@ -179,7 +198,9 @@ async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) for (const channelID of channelIDs) { - await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + if (discord.channels.get(channelID)?.type === DiscordTypes.ChannelType.GuildText) { // TODO: guild sync thread channels and such. maybe make a helper function to check if a given channel is syncable? + await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + } } } diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 738c59a..24a825a 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -15,19 +15,19 @@ const createRoom = sync.require("../actions/create-room") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function sendMessage(message) { - assert.ok(message.member) - +async function sendMessage(message, guild) { const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { + assert(message.member) senderMxid = await registerUser.ensureSimJoined(message.author, roomID) await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message) + const events = await messageToEvent.messageToEvent(message, guild) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 86be8ac..549d104 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -1,16 +1,18 @@ // @ts-check +const assert = require("assert").strict const markdown = require("discord-markdown") const passthrough = require("../../passthrough") -const { sync, db } = passthrough +const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") /** * @param {import("discord-api-types/v10").APIMessage} message + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function messageToEvent(message) { +async function messageToEvent(message, guild) { const events = [] // Text content appears first @@ -87,6 +89,31 @@ async function messageToEvent(message) { events.push(...attachmentEvents) // Then stickers + if (message.sticker_items) { + const stickerEvents = await Promise.all(message.sticker_items.map(async stickerItem => { + const format = file.stickerFormat.get(stickerItem.format_type) + if (format?.mime) { + let body = stickerItem.name + const sticker = guild.stickers.find(sticker => sticker.id === stickerItem.id) + if (sticker && sticker.description) body += ` - ${sticker.description}` + return { + $type: "m.sticker", + body, + info: { + mimetype: format.mime + }, + url: await file.uploadDiscordFileToMxc(file.sticker(stickerItem)) + } + } else { + return { + $type: "m.room.message", + msgtype: "m.text", + body: "Unsupported sticker format. Name: " + stickerItem.name + } + } + })) + events.push(...stickerEvents) + } return events } diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 323dc78..c92cd85 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -4,7 +4,7 @@ const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker) + const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", msgtype: "m.text", @@ -29,6 +29,6 @@ test("message2event: stickers", async t => { // thumbnail_url // thumbnail_info }, - url: "mxc://" + url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" }]) }) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index eeee451..fbcdca0 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,5 +1,4 @@ -// @ts-check - +const assert = require("assert").strict const {sync} = require("../passthrough") /** @type {import("./actions/send-message")}) */ @@ -15,8 +14,11 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = client.channels.get(message.channel_id) + const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024") return // TODO: activate on other servers (requires the space creation flow to be done first) - sendMessage.sendMessage(message) + sendMessage.sendMessage(message, guild) }, /** @@ -24,7 +26,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ onReactionAdd(client, data) { - if (data.emoji.id !== null) return // TOOD: image emoji reactions + if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) } diff --git a/db/ooye.db b/db/ooye.db index e62e0af..5168a0f 100644 Binary files a/db/ooye.db and b/db/ooye.db differ diff --git a/matrix/api.js b/matrix/api.js index 846ea64..3ec014d 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -78,7 +78,7 @@ function getAllState(roomID) { async function sendState(roomID, type, stateKey, content, mxid) { console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) assert.ok(type) - assert.ok(stateKey) + assert.ok(typeof stateKey === "string") /** @type {import("../types").R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) return root.event_id diff --git a/matrix/file.js b/matrix/file.js index a373676..64cd492 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -83,10 +83,11 @@ function emoji(emojiID, animated) { const stickerFormat = new Map([ [1, {label: "PNG", ext: "png", mime: "image/png"}], [2, {label: "APNG", ext: "png", mime: "image/apng"}], - [3, {label: "LOTTIE", ext: "json", mime: "application/json"}], + [3, {label: "LOTTIE", ext: "json", mime: null}], [4, {label: "GIF", ext: "gif", mime: "image/gif"}] ]) +/** @param {{id: string, format_type: number}} sticker */ function sticker(sticker) { const format = stickerFormat.get(sticker.format_type) if (!format) throw new Error(`No such format ${sticker.format_type} for sticker ${JSON.stringify(sticker)}`) diff --git a/package-lock.json b/package-lock.json index 9556331..224e232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -478,9 +478,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/better-sqlite3": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz", - "integrity": "sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.4.0.tgz", + "integrity": "sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==", "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", diff --git a/test/data.js b/test/data.js index 9704c8d..bb70cbe 100644 --- a/test/data.js +++ b/test/data.js @@ -22,8 +22,8 @@ module.exports = { }, room: { general: { - "m.room.name/": {name: "collective-unconscious"}, - "m.room.topic/": {topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:"}, + "m.room.name/": {name: "main"}, + "m.room.topic/": {topic: "collective-unconscious | https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:\n\nChannel ID: 112760669178241024\nGuild ID: 112760669178241024"}, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { @@ -33,7 +33,7 @@ module.exports = { "m.room.join_rules/": { join_rule: "restricted", allow: [{ - type: "m.room.membership", + type: "m.room_membership", room_id: "!jjWAGMeQdNrVZSSfvz:cadence.moe" }] },