diff --git a/scripts/remove-uncached-bridged-users.js b/scripts/remove-uncached-bridged-users.js deleted file mode 100644 index b3ceb8a..0000000 --- a/scripts/remove-uncached-bridged-users.js +++ /dev/null @@ -1,36 +0,0 @@ -// @ts-check - -const HeatSync = require("heatsync") -const sync = new HeatSync({watchFS: false}) - -const sqlite = require("better-sqlite3") -const db = new sqlite("ooye.db", {fileMustExist: true}) - -const passthrough = require("../src/passthrough") -Object.assign(passthrough, {db, sync}) - -const api = require("../src/matrix/api") -const utils = require("../src/matrix/utils") -const {reg} = require("../src/matrix/read-registration") - -const rooms = db.prepare("select room_id, name, nick from channel_room").all() - -;(async () => { - // Search for members starting with @_ooye_ and kick them if they are not in sim_member cache - for (const room of rooms) { - try { - const members = await api.getJoinedMembers(room.room_id) - for (const mxid of Object.keys(members.joined)) { - if (!mxid.startsWith("@" + reg.sender_localpart) && utils.eventSenderIsFromDiscord(mxid) && !db.prepare("select mxid from sim_member where mxid = ? and room_id = ?").get(mxid, room.room_id)) { - await api.leaveRoom(room.room_id, mxid) - } - } - } catch (e) { - if (e.message.includes("Appservice not in room")) { - // ok - } else { - throw e - } - } - } -})() diff --git a/src/d2m/actions/register-user.js b/src/d2m/actions/register-user.js index d475e54..c837ccb 100644 --- a/src/d2m/actions/register-user.js +++ b/src/d2m/actions/register-user.js @@ -206,16 +206,14 @@ function _hashProfileContent(content, powerLevel) { * 3. Calculate the power level the user should get based on their Discord permissions * 4. Compare against the previously known state content, which is helpfully stored in the database * 5. If the state content or power level have changed, send them to Matrix and update them in the database for next time - * 6. If the sim is for a user-installed app, check which user it was added by * @param {DiscordTypes.APIUser} user * @param {Omit | undefined} member * @param {DiscordTypes.APIGuildChannel} channel * @param {DiscordTypes.APIGuild} guild * @param {string} roomID - * @param {DiscordTypes.APIMessageInteractionMetadata} [interactionMetadata] * @returns {Promise} mxid of the updated sim */ -async function syncUser(user, member, channel, guild, roomID, interactionMetadata) { +async function syncUser(user, member, channel, guild, roomID) { const mxid = await ensureSimJoined(user, roomID) const content = await memberToStateContent(user, member, guild.id) const powerLevel = memberToPowerLevel(user, member, guild, channel) @@ -224,12 +222,6 @@ async function syncUser(user, member, channel, guild, roomID, interactionMetadat allowOverwrite: !!member, globalProfile: await userToGlobalProfile(user) }) - - const appInstalledByUser = user.bot && interactionMetadata?.authorizing_integration_owners?.[DiscordTypes.ApplicationIntegrationType.UserInstall] - if (appInstalledByUser) { - db.prepare("INSERT OR IGNORE INTO app_user_install (app_bot_id, user_id, guild_id) VALUES (?, ?, ?)").run(user.id, appInstalledByUser, guild.id) - } - return mxid } diff --git a/src/d2m/actions/remove-member.js b/src/d2m/actions/remove-member.js deleted file mode 100644 index 4dbd5a6..0000000 --- a/src/d2m/actions/remove-member.js +++ /dev/null @@ -1,26 +0,0 @@ -// @ts-check - -const passthrough = require("../../passthrough") -const {sync, db, select, from} = passthrough -/** @type {import("../../matrix/api")} */ -const api = sync.require("../../matrix/api") -/** @type {import("../converters/remove-member-mxids")} */ -const removeMemberMxids = sync.require("../converters/remove-member-mxids") - -/** - * @param {string} userID discord user ID that left - * @param {string} guildID discord guild ID that they left - */ -async function removeMember(userID, guildID) { - const {userAppDeletions, membership} = removeMemberMxids.removeMemberMxids(userID, guildID) - db.transaction(() => { - for (const d of userAppDeletions) { - db.prepare("DELETE FROM app_user_install WHERE guild_id = ? and user_id = ?").run(guildID, d) - } - })() - for (const m of membership) { - await api.leaveRoom(m.room_id, m.mxid) - } -} - -module.exports.removeMember = removeMember diff --git a/src/d2m/actions/send-message.js b/src/d2m/actions/send-message.js index 8550d43..eb919bb 100644 --- a/src/d2m/actions/send-message.js +++ b/src/d2m/actions/send-message.js @@ -51,7 +51,7 @@ async function sendMessage(message, channel, guild, row) { if (message.author.id === discord.application.id) { // no need to sync the bot's own user } else { - senderMxid = await registerUser.syncUser(message.author, message.member, channel, guild, roomID, message.interaction_metadata) + senderMxid = await registerUser.syncUser(message.author, message.member, channel, guild, roomID) } } diff --git a/src/d2m/actions/set-presence.js b/src/d2m/actions/set-presence.js index 0a31038..f26668f 100644 --- a/src/d2m/actions/set-presence.js +++ b/src/d2m/actions/set-presence.js @@ -1,7 +1,5 @@ // @ts-check -const assert = require("assert").strict - const passthrough = require("../../passthrough") const {sync, select} = passthrough /** @type {import("../../matrix/api")} */ @@ -28,7 +26,7 @@ const presenceLoopInterval = 28e3 // Cache the list of enabled guilds rather than accessing it like multiple times per second when any user changes presence const guildPresenceSetting = new class { - /** @private @type {Set} */ guilds = new Set() + /** @private @type {Set} */ guilds constructor() { this.update() } @@ -42,7 +40,7 @@ const guildPresenceSetting = new class { class Presence extends sync.reloadClassMethods(() => Presence) { /** @type {string} */ userID - /** @type {{presence: "online" | "offline" | "unavailable", status_msg?: string} | undefined} */ data + /** @type {{presence: "online" | "offline" | "unavailable", status_msg?: string}} */ data /** @private @type {?string | undefined} */ mxid /** @private @type {number} */ delay = Math.random() @@ -68,7 +66,6 @@ class Presence extends sync.reloadClassMethods(() => Presence) { // I haven't tried, but I assume Synapse explodes if you try to update too many presences at the same time. // This random delay will space them out over the whole 28 second cycle. setTimeout(() => { - assert(this.data) api.setPresence(this.data, mxid).catch(() => {}) }, this.delay * presenceLoopInterval).unref() } diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index 3f598f2..adc56e6 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -35,10 +35,10 @@ function getDiscordParseCallbacks(message, guild, useHTML, spoilers = []) { /** @param {{id: string, type: "discordUser"}} node */ user: node => { const mxid = select("sim", "mxid", {user_id: node.id}).pluck().get() - const interactionMetadata = message.interaction_metadata + const interaction = message.interaction_metadata || message.interaction const username = message.mentions?.find(ment => ment.id === node.id)?.username || message.referenced_message?.mentions?.find(ment => ment.id === node.id)?.username - || (interactionMetadata?.user.id === node.id ? interactionMetadata.user.username : null) + || (interaction?.user.id === node.id ? interaction.user.username : null) || (message.author?.id === node.id ? message.author.username : null) || "unknown-user" if (mxid && useHTML) { @@ -357,8 +357,9 @@ async function messageToEvent(message, guild, options = {}, di) { }] } - let isInteraction = (message.type === DiscordTypes.MessageType.ChatInputCommand || message.type === DiscordTypes.MessageType.ContextMenuCommand) && message.interaction && "name" in message.interaction - let isThinkingInteraction = isInteraction && !!((message.flags || 0) & DiscordTypes.MessageFlags.Loading) + const interaction = message.interaction_metadata || message.interaction + const isInteraction = message.type === DiscordTypes.MessageType.ChatInputCommand && !!interaction && "name" in interaction + const isThinkingInteraction = isInteraction && !!((message.flags || 0) & DiscordTypes.MessageFlags.Loading) /** @type {{room?: boolean, user_ids?: string[]}} @@ -399,16 +400,6 @@ async function messageToEvent(message, guild, options = {}, di) { } else if (message.referenced_message) { repliedToUnknownEvent = true } - } else if (message.type === DiscordTypes.MessageType.ContextMenuCommand && message.interaction && message.message_reference?.message_id) { - // It could be a /plu/ral emulated reply - if (message.interaction.name.startsWith("Reply ") && message.content.startsWith("-# [↪](")) { - const row = await getHistoricalEventRow(message.message_reference?.message_id) - if (row && "event_id" in row) { - repliedToEventRow = Object.assign(row, {channel_id: row.reference_channel_id}) - message.content = message.content.replace(/^.*\n/, "") - isInteraction = false // declutter - } - } } else if (dUtils.isWebhookMessage(message) && message.embeds[0]?.author?.name?.endsWith("↩️")) { // It could be a PluralKit emulated reply, let's see if it has a message link const isEmulatedReplyToText = message.embeds[0].description?.startsWith("**[Reply to:]") @@ -694,8 +685,8 @@ async function messageToEvent(message, guild, options = {}, di) { } } - if (isInteraction && !isThinkingInteraction && message.interaction && events.length === 0) { - const formattedInteraction = getFormattedInteraction(message.interaction, false) + if (isInteraction && !isThinkingInteraction && events.length === 0) { + const formattedInteraction = getFormattedInteraction(interaction, false) body = `${formattedInteraction.body}\n${body}` html = `${formattedInteraction.html}${html}` } @@ -791,8 +782,8 @@ async function messageToEvent(message, guild, options = {}, di) { events.push(...forwardedEvents) } - if (isInteraction && isThinkingInteraction && message.interaction) { - const formattedInteraction = getFormattedInteraction(message.interaction, true) + if (isThinkingInteraction) { + const formattedInteraction = getFormattedInteraction(interaction, true) await addTextEvent(formattedInteraction.body, formattedInteraction.html, "m.notice") } diff --git a/src/d2m/converters/message-to-event.test.components.js b/src/d2m/converters/message-to-event.test.components.js index 137b63b..7d875a6 100644 --- a/src/d2m/converters/message-to-event.test.components.js +++ b/src/d2m/converters/message-to-event.test.components.js @@ -65,7 +65,7 @@ test("message2event components: pk question mark output", async t => { + "
" + "

System: INX (xffgnx)" + "
Member: Lillith (pphhoh)" - + "
Sent by: infinidoge1337 (@unknown-user)" + + "
Sent by: infinidoge1337 (@unknown-user:)" + "

Account Roles (7)" + "
§b, !, ‼, Ears Port Ping, Ears Update Ping, Yttr Ping, unsup Ping

" + `🖼️ https://files.inx.moe/p/cdn/lillith.webp` diff --git a/src/d2m/converters/remove-member-mxids.js b/src/d2m/converters/remove-member-mxids.js deleted file mode 100644 index de26662..0000000 --- a/src/d2m/converters/remove-member-mxids.js +++ /dev/null @@ -1,38 +0,0 @@ -// @ts-check - -const passthrough = require("../../passthrough") -const {db, select, from} = passthrough - -/** - * @param {string} userID discord user ID that left - * @param {string} guildID discord guild ID that they left - */ -function removeMemberMxids(userID, guildID) { - // Get sims for user and remove - let membership = from("sim").join("sim_member", "mxid").join("channel_room", "room_id") - .select("room_id", "mxid").where({user_id: userID, guild_id: guildID}).and("ORDER BY room_id, mxid").all() - membership = membership.concat(from("sim_proxy").join("sim", "user_id").join("sim_member", "mxid").join("channel_room", "room_id") - .select("room_id", "mxid").where({proxy_owner_id: userID, guild_id: guildID}).and("ORDER BY room_id, mxid").all()) - - // Get user installed apps and remove - /** @type {string[]} */ - let userAppDeletions = [] - // 1. Select apps that have 1 user remaining - /** @type {Set} */ - const appsWithOneUser = new Set(db.prepare("SELECT app_bot_id FROM app_user_install WHERE guild_id = ? GROUP BY app_bot_id HAVING count(*) = 1").pluck().all(guildID)) - // 2. Select apps installed by this user - const appsFromThisUser = new Set(select("app_user_install", "app_bot_id", {guild_id: guildID, user_id: userID}).pluck().all()) - if (appsFromThisUser.size) userAppDeletions.push(userID) - // Then remove user installed apps if this was the last user with them - const appsToRemove = appsWithOneUser.intersection(appsFromThisUser) - for (const botID of appsToRemove) { - // Remove sims for user installed app - const appRemoval = removeMemberMxids(botID, guildID) - membership = membership.concat(appRemoval.membership) - userAppDeletions = userAppDeletions.concat(appRemoval.userAppDeletions) - } - - return {membership, userAppDeletions} -} - -module.exports.removeMemberMxids = removeMemberMxids diff --git a/src/d2m/converters/remove-member-mxids.test.js b/src/d2m/converters/remove-member-mxids.test.js deleted file mode 100644 index a880dff..0000000 --- a/src/d2m/converters/remove-member-mxids.test.js +++ /dev/null @@ -1,43 +0,0 @@ -// @ts-check - -const {test} = require("supertape") -const {removeMemberMxids} = require("./remove-member-mxids") - -test("remove member mxids: would remove mxid for all rooms in this server", t => { - t.deepEqual(removeMemberMxids("772659086046658620", "112760669178241024"), { - userAppDeletions: [], - membership: [{ - mxid: "@_ooye_cadence:cadence.moe", - room_id: "!fGgIymcYWOqjbSRUdV:cadence.moe" - }, { - mxid: "@_ooye_cadence:cadence.moe", - room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" - }] - }) -}) - -test("remove member mxids: removes sims too", t => { - t.deepEqual(removeMemberMxids("196188877885538304", "112760669178241024"), { - userAppDeletions: [], - membership: [{ - mxid: '@_ooye_ampflower:cadence.moe', - room_id: '!qzDBLKlildpzrrOnFZ:cadence.moe' - }, { - mxid: '@_ooye__pk_zoego:cadence.moe', - room_id: '!qzDBLKlildpzrrOnFZ:cadence.moe' - }] - }) -}) - -test("remove member mxids: removes apps too", t => { - t.deepEqual(removeMemberMxids("197126718400626689", "66192955777486848"), { - userAppDeletions: ["197126718400626689"], - membership: [{ - mxid: '@_ooye_infinidoge1337:cadence.moe', - room_id: '!BnKuBPCvyfOkhcUjEu:cadence.moe' - }, { - mxid: '@_ooye_evil_lillith_sheher:cadence.moe', - room_id: '!BnKuBPCvyfOkhcUjEu:cadence.moe' - }] - }) -}) diff --git a/src/d2m/discord-packets.js b/src/d2m/discord-packets.js index afea9ea..b1e381e 100644 --- a/src/d2m/discord-packets.js +++ b/src/d2m/discord-packets.js @@ -49,9 +49,8 @@ const utils = { if (listen === "full") { try { await eventDispatcher.checkMissedExpressions(message.d) - await eventDispatcher.checkMissedMessages(client, message.d) await eventDispatcher.checkMissedPins(client, message.d) - await eventDispatcher.checkMissedLeaves(client, message.d) + await eventDispatcher.checkMissedMessages(client, message.d) } catch (e) { console.error("Failed to sync missed events. To retry, please fix this error and restart OOYE:") console.error(e) diff --git a/src/d2m/event-dispatcher.js b/src/d2m/event-dispatcher.js index 7d156a0..01bbc67 100644 --- a/src/d2m/event-dispatcher.js +++ b/src/d2m/event-dispatcher.js @@ -32,8 +32,6 @@ const speedbump = sync.require("./actions/speedbump") const retrigger = sync.require("./actions/retrigger") /** @type {import("./actions/set-presence")} */ const setPresence = sync.require("./actions/set-presence") -/** @type {import("./actions/remove-member")} */ -const removeMember = sync.require("./actions/remove-member") /** @type {import("./actions/poll-vote")} */ const vote = sync.require("./actions/poll-vote") /** @type {import("../m2d/event-dispatcher")} */ @@ -174,31 +172,6 @@ module.exports = { await createSpace.syncSpaceExpressions(data, true) }, - /** - * When logging back in, check if any members left while we were gone. - * Do this by getting the member list from Discord and seeing who we still have locally that isn't there in the response. - * @param {import("./discord-client")} client - * @param {DiscordTypes.GatewayGuildCreateDispatchData} guild - */ - async checkMissedLeaves(client, guild) { - const maxLimit = 1000 - if (guild.member_count >= maxLimit) return // too large to want to scan - const discordMembers = await client.snow.guild.getGuildMembers(guild.id, {limit: maxLimit}) - if (discordMembers.length >= maxLimit) return // response was maxed out, there are guild members that weren't listed, can't act safely - const discordMembersSet = new Set(discordMembers.map(m => m.user.id)) - // no indexes on this one but I'll cope - const membersAddedOnMatrix = new Set(from("sim").join("sim_member", "mxid").join("channel_room", "room_id") - .pluck("user_id").selectUnsafe("DISTINCT user_id").where({guild_id: guild.id}).and("AND user_id not like '%-%' and user_id not like '%\\_%' escape '\\'").all()) - const userInstalledAppIDs = new Set(from("app_user_install").pluck("app_bot_id").selectUnsafe("DISTINCT app_bot_id").where({guild_id: guild.id}).all()) - // loop over members added on matrix and if the member does not exist on discord-side then they should be removed - for (const userID of membersAddedOnMatrix) { - if (userInstalledAppIDs.has(userID)) continue // skip user installed apps here since they're never true members - they'll be removed by removeMember when the associated user is removed - if (!discordMembersSet.has(userID)) { - await removeMember.removeMember(userID, guild.id) - } - } - }, - /** * Announces to the parent room that the thread room has been created. * See notes.md, "Ignore MESSAGE_UPDATE and bridge THREAD_CREATE as the announcement" @@ -238,14 +211,6 @@ module.exports = { } }, - /** - * @param {import("./discord-client")} client - * @param {DiscordTypes.GatewayGuildMemberRemoveDispatchData} data - */ - async GUILD_MEMBER_REMOVE(client, data) { - await removeMember.removeMember(data.user.id, data.guild_id) - }, - /** * @param {import("./discord-client")} client * @param {DiscordTypes.GatewayChannelUpdateDispatchData} channelOrThread diff --git a/src/db/migrations/0035-role-default.sql b/src/db/migrations/0035-role-default.sql index a5ce62d..6c44e7e 100644 --- a/src/db/migrations/0035-role-default.sql +++ b/src/db/migrations/0035-role-default.sql @@ -4,6 +4,6 @@ CREATE TABLE "role_default" ( "guild_id" TEXT NOT NULL, "role_id" TEXT NOT NULL, PRIMARY KEY ("guild_id", "role_id") -) WITHOUT ROWID; +); COMMIT; diff --git a/src/db/migrations/0036-app-user-install.sql b/src/db/migrations/0036-app-user-install.sql deleted file mode 100644 index 087a0ac..0000000 --- a/src/db/migrations/0036-app-user-install.sql +++ /dev/null @@ -1,10 +0,0 @@ -BEGIN TRANSACTION; - -CREATE TABLE "app_user_install" ( - "guild_id" TEXT NOT NULL, - "app_bot_id" TEXT NOT NULL, - "user_id" TEXT NOT NULL, - PRIMARY KEY ("guild_id", "app_bot_id", "user_id") -) WITHOUT ROWID; - -COMMIT; diff --git a/src/db/orm-defs.d.ts b/src/db/orm-defs.d.ts index d95bfc3..f6628f2 100644 --- a/src/db/orm-defs.d.ts +++ b/src/db/orm-defs.d.ts @@ -1,10 +1,4 @@ export type Models = { - app_user_install: { - guild_id: string - app_bot_id: string - user_id: string - } - auto_emoji: { name: string emoji_id: string diff --git a/src/discord/utils.js b/src/discord/utils.js index aed7068..2431246 100644 --- a/src/discord/utils.js +++ b/src/discord/utils.js @@ -114,7 +114,7 @@ function hasAllPermissions(resolvedPermissions, permissionsToCheckFor) { * @param {DiscordTypes.APIMessage} message */ function isWebhookMessage(message) { - return message.webhook_id && message.type !== DiscordTypes.MessageType.ChatInputCommand && message.type !== DiscordTypes.MessageType.ContextMenuCommand + return message.webhook_id && message.type !== DiscordTypes.MessageType.ChatInputCommand } /** diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index c11b696..085c69c 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -423,10 +423,7 @@ async event => { if (event.content.membership === "leave" || event.content.membership === "ban") { // Member is gone - // if Matrix member, data was cached in member_cache db.prepare("DELETE FROM member_cache WHERE room_id = ? and mxid = ?").run(event.room_id, event.state_key) - // if Discord member (so kicked/banned by Matrix user), data was cached in sim_member - db.prepare("DELETE FROM sim_member WHERE room_id = ? and mxid = ?").run(event.room_id, event.state_key) // Unregister room's use as a direct chat and/or an invite target if the bot itself left if (event.state_key === utils.bot) { diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index 07f8c24..1dd9dfe 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -38,28 +38,15 @@ INSERT INTO sim (user_id, username, sim_name, mxid) VALUES ('1109360903096369153', 'Amanda', 'amanda', '@_ooye_amanda:cadence.moe'), ('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '_pk_zoego', '_pk_zoego', '@_ooye__pk_zoego:cadence.moe'), ('320067006521147393', 'papiophidian', 'papiophidian', '@_ooye_papiophidian:cadence.moe'), -('772659086046658620', 'cadence.worm', 'cadence', '@_ooye_cadence:cadence.moe'), -('196188877885538304', 'ampflower', 'ampflower', '@_ooye_ampflower:cadence.moe'), -('1458668878107381800', 'Evil Lillith (she/her)', 'evil_lillith_sheher', '@_ooye_evil_lillith_sheher:cadence.moe'), -('197126718400626689', 'infinidoge1337', 'infinidoge1337', '@_ooye_infinidoge1337:cadence.moe'); - +('772659086046658620', 'cadence.worm', 'cadence', '@_ooye_cadence:cadence.moe'); INSERT INTO sim_member (mxid, room_id, hashed_profile_content) VALUES ('@_ooye_bojack_horseman:cadence.moe', '!hYnGGlPHlbujVVfktC:cadence.moe', NULL), -('@_ooye_cadence:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL), -('@_ooye_cadence:cadence.moe', '!kLRqKKUQXcibIMtOpl:cadence.moe', NULL), -('@_ooye_cadence:cadence.moe', '!fGgIymcYWOqjbSRUdV:cadence.moe', NULL), -('@_ooye_ampflower:cadence.moe', '!qzDBLKlildpzrrOnFZ:cadence.moe', NULL), -('@_ooye__pk_zoego:cadence.moe', '!qzDBLKlildpzrrOnFZ:cadence.moe', NULL), -('@_ooye_infinidoge1337:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL), -('@_ooye_evil_lillith_sheher:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL); +('@_ooye_cadence:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL); INSERT INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES ('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '196188877885538304', 'Azalea &flwr; 🌺'); -INSERT INTO app_user_install (guild_id, app_bot_id, user_id) VALUES -('66192955777486848', '1458668878107381800', '197126718400626689'); - INSERT INTO message_room (message_id, historical_room_index) WITH a (message_id, channel_id) AS (VALUES ('1106366167788044450', '122155380120748034'), diff --git a/test/test.js b/test/test.js index 4cd9627..da6bcba 100644 --- a/test/test.js +++ b/test/test.js @@ -152,7 +152,6 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../src/d2m/converters/message-to-event.test.embeds") require("../src/d2m/converters/message-to-event.test.pk") require("../src/d2m/converters/pins-to-list.test") - require("../src/d2m/converters/remove-member-mxids.test") require("../src/d2m/converters/remove-reaction.test") require("../src/d2m/converters/thread-to-announcement.test") require("../src/d2m/converters/user-to-mxid.test")