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/create-room.js b/src/d2m/actions/create-room.js index c2ec01a..31d3022 100644 --- a/src/d2m/actions/create-room.js +++ b/src/d2m/actions/create-room.js @@ -256,7 +256,7 @@ async function createRoom(channel, guild, spaceID, kstate, privacyLevel) { /** * Handling power levels separately. The spec doesn't specify what happens, Dendrite differs, - * and Synapse does a very poorly thought out *shallow merge* of what I provide on top of what it creates. + * and Synapse does an absolutely insane *shallow merge* of what I provide on top of what it creates. * We don't want the `events` key to be overridden completely. * https://github.com/matrix-org/synapse/blob/develop/synapse/handlers/room.py#L1170-L1210 * https://github.com/matrix-org/matrix-spec/issues/492 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 index 56ac750..f4d18cf 100644 --- a/src/d2m/actions/remove-member.js +++ b/src/d2m/actions/remove-member.js @@ -1,37 +1,124 @@ // @ts-check +const DiscordTypes = require("discord-api-types/v10") + const passthrough = require("../../passthrough") -const {sync, db, select, from} = passthrough +const {discord, 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 + * Make a specific sim leave all rooms in a guild and clean up the database. + * @param {string} mxid + * @param {string} guildID + * @returns {Promise} number of rooms left + */ +async function removeSimFromGuild(mxid, guildID) { + const rooms = from("sim_member") + .join("channel_room", "room_id") + .where({mxid, guild_id: guildID}) + .pluck("room_id") + .all() + + for (const roomID of rooms) { + try { + await api.leaveRoom(roomID, mxid) + } catch (e) { + // Room may no longer exist or sim may already have left + console.log(`[remove member] failed to leave room ${roomID}: ${e}`) + } + db.prepare("DELETE FROM sim_member WHERE mxid = ? AND room_id = ?").run(mxid, roomID) + } + + return rooms.length +} + +/** + * Remove a user's sim and their PluralKit proxy sims from all rooms in a guild. + * Called when a Discord user leaves a guild. + * @param {string} userID Discord user ID + * @param {string} guildID Discord guild ID */ 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) { - try { - await api.leaveRoom(m.room_id, m.mxid) - } catch (e) { - if (String(e).includes("not in room")) { - // no further action needed - } else { - throw e - } - } - // Update cache to say that the member isn't in the room any more - // You'd think this would happen automatically when the leave event arrives at Matrix's event dispatcher, but that isn't 100% reliable. - db.prepare("DELETE FROM sim_member WHERE room_id = ? AND mxid = ?").run(m.room_id, m.mxid) + // Remove the user's own sim + const mxid = select("sim", "mxid", {user_id: userID}).pluck().get() + if (mxid) { + const count = await removeSimFromGuild(mxid, guildID) + if (count) console.log(`[remove member] removed sim for ${userID} from ${count} rooms in guild ${guildID}`) + } + + // Remove PluralKit proxy sims owned by this user + const pkUserIDs = select("sim_proxy", "user_id", {proxy_owner_id: userID}).pluck().all() + for (const pkUserID of pkUserIDs) { + const pkMxid = select("sim", "mxid", {user_id: pkUserID}).pluck().get() + if (!pkMxid) continue + const count = await removeSimFromGuild(pkMxid, guildID) + if (count) console.log(`[remove member] removed pk sim for ${pkUserID} (owner: ${userID}) from ${count} rooms in guild ${guildID}`) } } +/** + * Backfill: check all sims in guild rooms and remove those whose Discord users have left the guild. + * Called on GUILD_CREATE to catch removals that happened while the bridge was offline. + * @param {DiscordTypes.GatewayGuildCreateDispatchData} guild + */ +async function checkMissedMembers(guild) { + if (guild.unavailable) return + + // Find all distinct regular Discord user IDs with sims in this guild's rooms + // Exclude PK sims (UUIDs with dashes) and webhook sims (with underscores) + const rows = from("sim_member") + .join("channel_room", "room_id") + .join("sim", "mxid") + .where({guild_id: guild.id}) + .and("AND user_id NOT LIKE '%-%' AND user_id NOT LIKE '%\\_%' ESCAPE '\\'") + .pluck("user_id") + .all() + + const userIDs = [...new Set(rows)] + + for (const userID of userIDs) { + try { + await discord.snow.guild.getGuildMember(guild.id, userID) + } catch (e) { + if (String(e).includes("10007") || String(e).includes("Unknown Member")) { + console.log(`[remove member] backfill: user ${userID} is no longer in guild ${guild.id}`) + await removeMember(userID, guild.id) + } + // Other errors (rate limits, network issues) - skip this user + } + } + + // Also check PK proxy owners who may not have a regular sim but whose PK sims are in guild rooms + const pkRows = from("sim_member") + .join("channel_room", "room_id") + .join("sim", "mxid") + .join("sim_proxy", "user_id") + .where({guild_id: guild.id}) + .pluck("proxy_owner_id") + .all() + + const pkOwnerIDs = [...new Set(pkRows)].filter(id => !userIDs.includes(id)) + + for (const ownerID of pkOwnerIDs) { + try { + await discord.snow.guild.getGuildMember(guild.id, ownerID) + } catch (e) { + if (String(e).includes("10007") || String(e).includes("Unknown Member")) { + console.log(`[remove member] backfill: pk owner ${ownerID} is no longer in guild ${guild.id}`) + // Only remove PK sims for this owner, not their own sim (they don't have one in this guild) + const pkUserIDs = select("sim_proxy", "user_id", {proxy_owner_id: ownerID}).pluck().all() + for (const pkUserID of pkUserIDs) { + const pkMxid = select("sim", "mxid", {user_id: pkUserID}).pluck().get() + if (!pkMxid) continue + const count = await removeSimFromGuild(pkMxid, guild.id) + if (count) console.log(`[remove member] backfill: removed pk sim for ${pkUserID} (owner: ${ownerID}) from ${count} rooms in guild ${guild.id}`) + } + } + } + } +} + +module.exports.removeSimFromGuild = removeSimFromGuild module.exports.removeMember = removeMember +module.exports.checkMissedMembers = checkMissedMembers diff --git a/src/d2m/actions/remove-member.test.js b/src/d2m/actions/remove-member.test.js new file mode 100644 index 0000000..383ba7d --- /dev/null +++ b/src/d2m/actions/remove-member.test.js @@ -0,0 +1,202 @@ +// @ts-check + +const {test} = require("supertape") +const {removeSimFromGuild, removeMember, checkMissedMembers} = require("./remove-member") +const passthrough = require("../../passthrough") +const {db, sync, discord} = passthrough + +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") + +function setupTestSim(userID, simName, mxid) { + db.prepare("INSERT OR IGNORE INTO sim (user_id, username, sim_name, mxid) VALUES (?, ?, ?, ?)").run(userID, simName, simName, mxid) +} + +function setupTestSimMember(mxid, roomID) { + db.prepare("INSERT OR IGNORE INTO sim_member (mxid, room_id, hashed_profile_content) VALUES (?, ?, NULL)").run(mxid, roomID) +} + +function cleanupTestSim(userID) { + const mxid = db.prepare("SELECT mxid FROM sim WHERE user_id = ?").pluck().get(userID) + if (mxid) { + db.prepare("DELETE FROM sim_member WHERE mxid = ?").run(mxid) + } + db.prepare("DELETE FROM sim WHERE user_id = ?").run(userID) +} + +test("remove-member: removeSimFromGuild removes sim from all guild rooms", async t => { + const mxid = "@_ooye_testrm1:cadence.moe" + const guildID = "112760669178241024" + const roomID1 = "!kLRqKKUQXcibIMtOpl:cadence.moe" + const roomID2 = "!fGgIymcYWOqjbSRUdV:cadence.moe" + + setupTestSim("999999990", "testrm1", mxid) + setupTestSimMember(mxid, roomID1) + setupTestSimMember(mxid, roomID2) + + const leftRooms = [] + const originalLeaveRoom = api.leaveRoom + api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) } + + try { + const count = await removeSimFromGuild(mxid, guildID) + + t.equal(count, 2) + t.equal(leftRooms.length, 2) + t.ok(leftRooms.some(r => r.roomID === roomID1)) + t.ok(leftRooms.some(r => r.roomID === roomID2)) + + const remaining = db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid) + t.equal(remaining, 0) + } finally { + api.leaveRoom = originalLeaveRoom + cleanupTestSim("999999990") + } +}) + +test("remove-member: removeSimFromGuild only affects rooms in the specified guild", async t => { + const mxid = "@_ooye_testrm2:cadence.moe" + const guildID = "112760669178241024" + const guildRoom = "!kLRqKKUQXcibIMtOpl:cadence.moe" // guild 112760669178241024 + const otherGuildRoom = "!BnKuBPCvyfOkhcUjEu:cadence.moe" // guild 66192955777486848 + + setupTestSim("999999991", "testrm2", mxid) + setupTestSimMember(mxid, guildRoom) + setupTestSimMember(mxid, otherGuildRoom) + + const leftRooms = [] + const originalLeaveRoom = api.leaveRoom + api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) } + + try { + const count = await removeSimFromGuild(mxid, guildID) + + t.equal(count, 1) + t.equal(leftRooms[0].roomID, guildRoom) + + const otherRemaining = db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ? AND room_id = ?").pluck().get(mxid, otherGuildRoom) + t.equal(otherRemaining, 1, "other guild's room should be untouched") + } finally { + api.leaveRoom = originalLeaveRoom + cleanupTestSim("999999991") + } +}) + +test("remove-member: removeSimFromGuild handles leaveRoom errors gracefully", async t => { + const mxid = "@_ooye_testrm3:cadence.moe" + const guildID = "112760669178241024" + const roomID = "!kLRqKKUQXcibIMtOpl:cadence.moe" + + setupTestSim("999999992", "testrm3", mxid) + setupTestSimMember(mxid, roomID) + + const originalLeaveRoom = api.leaveRoom + api.leaveRoom = async () => { throw new Error("not in room") } + + try { + const count = await removeSimFromGuild(mxid, guildID) + t.equal(count, 1) + + const remaining = db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid) + t.equal(remaining, 0, "sim_member should be deleted even if leaveRoom fails") + } finally { + api.leaveRoom = originalLeaveRoom + cleanupTestSim("999999992") + } +}) + +test("remove-member: removeMember removes user sim and their PK proxy sims", async t => { + const userID = "999999993" + const mxid = "@_ooye_testrm4:cadence.moe" + const pkUserID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + const pkMxid = "@_ooye__pk_testrmpk:cadence.moe" + const guildID = "112760669178241024" + const roomID = "!kLRqKKUQXcibIMtOpl:cadence.moe" + + setupTestSim(userID, "testrm4", mxid) + setupTestSimMember(mxid, roomID) + setupTestSim(pkUserID, "_pk_testrmpk", pkMxid) + setupTestSimMember(pkMxid, roomID) + db.prepare("INSERT OR IGNORE INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES (?, ?, ?)").run(pkUserID, userID, "Test PK") + + const leftRooms = [] + const originalLeaveRoom = api.leaveRoom + api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) } + + try { + await removeMember(userID, guildID) + + t.equal(leftRooms.length, 2) + t.ok(leftRooms.some(r => r.mxid === mxid), "user sim should leave") + t.ok(leftRooms.some(r => r.mxid === pkMxid), "pk sim should leave") + + t.equal(db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid), 0) + t.equal(db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(pkMxid), 0) + } finally { + api.leaveRoom = originalLeaveRoom + db.prepare("DELETE FROM sim_proxy WHERE user_id = ?").run(pkUserID) + cleanupTestSim(userID) + cleanupTestSim(pkUserID) + } +}) + +test("remove-member: removeMember does nothing for unknown user", async t => { + const leftRooms = [] + const originalLeaveRoom = api.leaveRoom + api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) } + + try { + await removeMember("000000000000000000", "112760669178241024") + t.equal(leftRooms.length, 0) + } finally { + api.leaveRoom = originalLeaveRoom + } +}) + +test("remove-member: checkMissedMembers removes sims for departed users", async t => { + const userID = "999999994" + const mxid = "@_ooye_testrm5:cadence.moe" + const guildID = "112760669178241024" + const roomID = "!kLRqKKUQXcibIMtOpl:cadence.moe" + + setupTestSim(userID, "testrm5", mxid) + setupTestSimMember(mxid, roomID) + + const leftRooms = [] + const originalLeaveRoom = api.leaveRoom + api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) } + + const originalSnow = discord.snow + discord.snow = { + guild: { + getGuildMember: async (gid, uid) => { + if (uid === userID) throw new Error('{"message": "Unknown Member", "code": 10007}') + return {} + } + } + } + + try { + await checkMissedMembers({id: guildID}) + + t.ok(leftRooms.some(r => r.mxid === mxid), "departed user's sim should be removed") + t.equal(db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid), 0) + } finally { + api.leaveRoom = originalLeaveRoom + discord.snow = originalSnow + cleanupTestSim(userID) + } +}) + +test("remove-member: checkMissedMembers skips unavailable guilds", async t => { + const leftRooms = [] + const originalLeaveRoom = api.leaveRoom + api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) } + + try { + await checkMissedMembers({id: "112760669178241024", unavailable: true}) + t.equal(leftRooms.length, 0, "should not process unavailable guilds") + } finally { + api.leaveRoom = originalLeaveRoom + } +}) 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/message-to-event.test.embeds.js b/src/d2m/converters/message-to-event.test.embeds.js index fdb0807..73244d3 100644 --- a/src/d2m/converters/message-to-event.test.embeds.js +++ b/src/d2m/converters/message-to-event.test.embeds.js @@ -125,8 +125,8 @@ test("message2event embeds: blockquote in embed", async t => { t.equal(called, 1, "should call getJoinedMembers once") }) -test("message2event embeds: extreme html is all escaped", async t => { - const events = await messageToEvent(data.message_with_embeds.extreme_html_escaping, data.guild.general) +test("message2event embeds: crazy html is all escaped", async t => { + const events = await messageToEvent(data.message_with_embeds.escaping_crazy_html_tags, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", msgtype: "m.notice", 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-client.js b/src/d2m/discord-client.js index c2a0549..88d74d0 100644 --- a/src/d2m/discord-client.js +++ b/src/d2m/discord-client.js @@ -23,7 +23,7 @@ class DiscordClient { /** @type {import("cloudstorm").IClientOptions["intents"]} */ const intents = [ "DIRECT_MESSAGES", "DIRECT_MESSAGE_REACTIONS", "DIRECT_MESSAGE_TYPING", - "GUILDS", "GUILD_EMOJIS_AND_STICKERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "GUILD_MESSAGE_TYPING", "GUILD_WEBHOOKS", "GUILD_MESSAGE_POLLS", + "GUILDS", "GUILD_EMOJIS_AND_STICKERS", "GUILD_MEMBERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "GUILD_MESSAGE_TYPING", "GUILD_WEBHOOKS", "GUILD_MESSAGE_POLLS", "MESSAGE_CONTENT" ] if (reg.ooye.receive_presences !== false) intents.push("GUILD_PRESENCES") diff --git a/src/d2m/discord-packets.js b/src/d2m/discord-packets.js index afea9ea..df1c122 100644 --- a/src/d2m/discord-packets.js +++ b/src/d2m/discord-packets.js @@ -49,9 +49,9 @@ 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) + await eventDispatcher.checkMissedMembers(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 c86cc13..8194ab1 100644 --- a/src/d2m/event-dispatcher.js +++ b/src/d2m/event-dispatcher.js @@ -32,14 +32,14 @@ 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")} */ const matrixEventDispatcher = sync.require("../m2d/event-dispatcher") /** @type {import("../discord/interactions/matrix-info")} */ const matrixInfoInteraction = sync.require("../discord/interactions/matrix-info") +/** @type {import("./actions/remove-member")} */ +const removeMember = sync.require("./actions/remove-member") const {Semaphore} = require("@chriscdn/promise-semaphore") const checkMissedPinsSema = new Semaphore() @@ -125,7 +125,6 @@ module.exports = { // Send in order for (let i = Math.min(messages.length, latestBridgedMessageIndex)-1; i >= 0; i--) { const message = messages[i] - if (message.type === DiscordTypes.MessageType.UserJoin) continue // since join announcements don't become events, it would be a repetition to act on them during backfill if (!members.has(message.author.id)) members.set(message.author.id, await client.snow.guild.getGuildMember(guild.id, message.author.id).catch(() => undefined)) await module.exports.MESSAGE_CREATE(client, { @@ -175,31 +174,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" @@ -239,14 +213,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 @@ -449,5 +415,24 @@ module.exports = { const status = data.status if (!status) return setPresence.presenceTracker.incomingPresence(data.user.id, data.guild_id, status) + }, + + /** + * When a Discord user leaves (or is kicked/banned from) the guild, make their sims leave the bridged Matrix rooms. + * Also removes PluralKit proxy sims owned by the departing user. + * @param {import("./discord-client")} client + * @param {DiscordTypes.GatewayGuildMemberRemoveDispatchData} data + */ + async GUILD_MEMBER_REMOVE(client, data) { + if (data.user.id === client.user.id) return // Don't process if the bot itself is removed + await removeMember.removeMember(data.user.id, data.guild_id) + }, + + /** + * When logging back in, check for sims whose Discord users have left the guild while the bridge was offline. + * @param {DiscordTypes.GatewayGuildCreateDispatchData} guild + */ + async checkMissedMembers(guild) { + await removeMember.checkMissedMembers(guild) } } 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/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index 1b23787..458924d 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -471,8 +471,7 @@ async function checkWrittenMentions(content, senderMxid, roomID, guild, di) { // @ts-ignore - typescript doesn't know about indices yet content: content.slice(0, writtenMentionMatch.indices[1][0]-1) + `@everyone` + content.slice(writtenMentionMatch.indices[1][1]), ensureJoined: [], - allowedMentionsParse: ["everyone"], - allowedMentionsUsers: [] + allowedMentionsParse: ["everyone"] } } } else if (writtenMentionMatch[1].length < 40) { // the API supports up to 100 characters, but really if you're searching more than 40, something messed up @@ -483,8 +482,7 @@ async function checkWrittenMentions(content, senderMxid, roomID, guild, di) { // @ts-ignore - typescript doesn't know about indices yet content: content.slice(0, writtenMentionMatch.indices[1][0]-1) + `<@${results[0].user.id}>` + content.slice(writtenMentionMatch.indices[1][1]), ensureJoined: [results[0].user], - allowedMentionsParse: [], - allowedMentionsUsers: [results[0].user.id] + allowedMentionsParse: [] } } } @@ -546,7 +544,6 @@ async function eventToMessage(event, guild, channel, di) { let displayName = event.sender let avatarURL = undefined const allowedMentionsParse = ["users", "roles"] - const allowedMentionsUsers = [] /** @type {string[]} */ let messageIDsToEdit = [] let replyLine = "" @@ -766,7 +763,7 @@ async function eventToMessage(event, guild, channel, di) { // Generate a reply preview for a standard message repliedToContent = repliedToContent.replace(/.*<\/mx-reply>/s, "") // Remove everything before replies, so just use the actual message body repliedToContent = repliedToContent.replace(/^\s*
.*?<\/blockquote>(.....)/s, "$1") // If the message starts with a blockquote, don't count it and use the message body afterwards - repliedToContent = repliedToContent.replace(/(?:\n|
)+/g, " ") // Should all be on one line + repliedToContent = repliedToContent.replace(/(?:\n|
)+/g, " ") // Should all be on one line repliedToContent = repliedToContent.replace(/]*data-mx-spoiler\b[^>]*>.*?<\/span>/g, "[spoiler]") // Good enough method of removing spoiler content. (I don't want to break out the HTML parser unless I have to.) repliedToContent = repliedToContent.replace(/]*)>/g, (_, att) => { // Convert Matrix emoji images into Discord emoji markdown const mxcUrlMatch = att.match(/\bsrc="(mxc:\/\/[^"]+)"/) @@ -989,34 +986,16 @@ async function eventToMessage(event, guild, channel, di) { } } - // Complete content content = displayNameRunoff + replyLine + content + // Split into 2000 character chunks const chunks = chunk(content, 2000) - - // If m.mentions is specified and valid, overwrite allowedMentionsParse with a converted m.mentions - let allowed_mentions = {parse: allowedMentionsParse} - if (event.content["m.mentions"]) { - // Combine requested mentions with detected written mentions to get the full list - if (Array.isArray(event.content["m.mentions"].user_ids)) { - for (const mxid of event.content["m.mentions"].user_ids) { - const user_id = select("sim", "user_id", {mxid}).pluck().get() - if (!user_id) continue - allowedMentionsUsers.push( - select("sim_proxy", "proxy_owner_id", {user_id}).pluck().get() || user_id - ) - } - } - // Specific mentions were requested, so do not parse users - allowed_mentions.parse = allowed_mentions.parse.filter(x => x !== "users") - allowed_mentions.users = allowedMentionsUsers - } - - // Assemble chunks into Discord messages content /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | stream.Readable}[]})[]} */ const messages = chunks.map(content => ({ content, - allowed_mentions, + allowed_mentions: { + parse: allowedMentionsParse + }, username: displayNameShortened, avatar_url: avatarURL })) diff --git a/src/m2d/converters/event-to-message.test.js b/src/m2d/converters/event-to-message.test.js index 1c263b4..aa426cd 100644 --- a/src/m2d/converters/event-to-message.test.js +++ b/src/m2d/converters/event-to-message.test.js @@ -266,8 +266,7 @@ test("event2message: markdown in link text does not attempt to be escaped becaus content: "hey [@mario sports mix [she/her]](), is it possible to listen on a unix socket?", avatar_url: undefined, allowed_mentions: { - parse: ["roles"], - users: [] + parse: ["users", "roles"] } }] } @@ -548,8 +547,7 @@ test("event2message: links don't have angle brackets added by accident", async t content: "Wanted to automate WG→AWG config enrichment and ended up basically coding a batch INI processor.\nhttps://github.com/Erquint/wgcbp", avatar_url: undefined, allowed_mentions: { - parse: ["roles"], - users: [] + parse: ["users", "roles"] } }] } @@ -1298,8 +1296,7 @@ test("event2message: lists have appropriate line breaks", async t => { content: `i am not certain what you mean by "already exists with as discord". my goals are\n\n* bridgeing specific channels with existing matrix rooms\n * optionally maybe entire "servers"\n* offering the bridge as a public service`, avatar_url: undefined, allowed_mentions: { - parse: ["roles"], - users: [] + parse: ["users", "roles"] } }] } @@ -1340,8 +1337,7 @@ test("event2message: ordered list start attribute works", async t => { content: `i am not certain what you mean by "already exists with as discord". my goals are\n\n1. bridgeing specific channels with existing matrix rooms\n 2. optionally maybe entire "servers"\n2. offering the bridge as a public service`, avatar_url: undefined, allowed_mentions: { - parse: ["roles"], - users: [] + parse: ["users", "roles"] } }] } @@ -1467,118 +1463,6 @@ test("event2message: rich reply to a sim user", async t => { ) }) -test("event2message: rich reply to a sim user, explicitly enabling mentions in client", async t => { - t.deepEqual( - await eventToMessage({ - "type": "m.room.message", - "sender": "@cadence:cadence.moe", - "content": { - "msgtype": "m.text", - "body": "> <@_ooye_kyuugryphon:cadence.moe> Slow news day.\n\nTesting this reply, ignore", - "format": "org.matrix.custom.html", - "formatted_body": "
In reply to @_ooye_kyuugryphon:cadence.moe
Slow news day.
Testing this reply, ignore", - "m.relates_to": { - "m.in_reply_to": { - "event_id": "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04" - } - }, - "m.mentions": { - user_ids: ["@_ooye_kyuugryphon:cadence.moe"] - } - }, - "origin_server_ts": 1693029683016, - "unsigned": { - "age": 91, - "transaction_id": "m1693029682894.510" - }, - "event_id": "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8", - "room_id": "!fGgIymcYWOqjbSRUdV:cadence.moe" - }, data.guild.general, data.channel.general, { - api: { - getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04", { - type: "m.room.message", - content: { - msgtype: "m.text", - body: "Slow news day." - }, - sender: "@_ooye_kyuugryphon:cadence.moe" - }) - } - }), - { - ensureJoined: [], - messagesToDelete: [], - messagesToEdit: [], - messagesToSend: [{ - username: "cadence [they]", - content: "-# > <:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 <@111604486476181504>:" - + " Slow news day." - + "\nTesting this reply, ignore", - avatar_url: "https://bridge.example.org/download/matrix/cadence.moe/azCAhThKTojXSZJRoWwZmhvU", - allowed_mentions: { - parse: ["roles"], - users: ["111604486476181504"] - } - }] - } - ) -}) - -test("event2message: rich reply to a sim user, explicitly disabling mentions in client", async t => { - t.deepEqual( - await eventToMessage({ - "type": "m.room.message", - "sender": "@cadence:cadence.moe", - "content": { - "msgtype": "m.text", - "body": "> <@_ooye_kyuugryphon:cadence.moe> Slow news day.\n\nTesting this reply, ignore", - "format": "org.matrix.custom.html", - "formatted_body": "
In reply to @_ooye_kyuugryphon:cadence.moe
Slow news day.
Testing this reply, ignore", - "m.relates_to": { - "m.in_reply_to": { - "event_id": "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04" - } - }, - "m.mentions": {} - }, - "origin_server_ts": 1693029683016, - "unsigned": { - "age": 91, - "transaction_id": "m1693029682894.510" - }, - "event_id": "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8", - "room_id": "!fGgIymcYWOqjbSRUdV:cadence.moe" - }, data.guild.general, data.channel.general, { - api: { - getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04", { - type: "m.room.message", - content: { - msgtype: "m.text", - body: "Slow news day." - }, - sender: "@_ooye_kyuugryphon:cadence.moe" - }) - } - }), - { - ensureJoined: [], - messagesToDelete: [], - messagesToEdit: [], - messagesToSend: [{ - username: "cadence [they]", - content: "-# > <:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 <@111604486476181504>:" - + " Slow news day." - + "\nTesting this reply, ignore", - avatar_url: "https://bridge.example.org/download/matrix/cadence.moe/azCAhThKTojXSZJRoWwZmhvU", - allowed_mentions: { - parse: ["roles"], - users: [] - } - }] - } - ) -}) - test("event2message: rich reply to a rich reply to a multi-line message should correctly strip reply fallback", async t => { t.deepEqual( await eventToMessage({ @@ -1943,9 +1827,9 @@ test("event2message: should suppress embeds for links in reply preview", async t sender: "@rnl:cadence.moe", content: { msgtype: "m.text", - body: `> <@cadence:cadence.moe> https://www.youtube.com/watch?v=uX32idb1jMw\n\nEveryone in the comments is like "you're so ahead of the curve" and "can't believe this came out before the scandal" but I thought Mr Beast sucking was a common sentiment`, + body: `> <@cadence:cadence.moe> https://www.youtube.com/watch?v=uX32idb1jMw\n\nEveryone in the comments is like "you're so ahead of the curve" and "crazy that this came out before the scandal" but I thought Mr Beast sucking was a common sentiment`, format: "org.matrix.custom.html", - formatted_body: `
In reply to @cadence:cadence.moe
https://www.youtube.com/watch?v=uX32idb1jMw
Everyone in the comments is like "you're so ahead of the curve" and "can't believe this came out before the scandal" but I thought Mr Beast sucking was a common sentiment`, + formatted_body: `
In reply to @cadence:cadence.moe
https://www.youtube.com/watch?v=uX32idb1jMw
Everyone in the comments is like "you're so ahead of the curve" and "crazy that this came out before the scandal" but I thought Mr Beast sucking was a common sentiment`, "m.relates_to": { "m.in_reply_to": { event_id: "$qmyjr-ISJtnOM5WTWLI0fT7uSlqRLgpyin2d2NCglCU" @@ -1975,7 +1859,7 @@ test("event2message: should suppress embeds for links in reply preview", async t username: "RNL", content: "-# > <:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1273204543739396116 **Ⓜcadence [they]**:" + " " - + `\nEveryone in the comments is like "you're so ahead of the curve" and "can't believe this came out before the scandal" but I thought Mr Beast sucking was a common sentiment`, + + `\nEveryone in the comments is like "you're so ahead of the curve" and "crazy that this came out before the scandal" but I thought Mr Beast sucking was a common sentiment`, avatar_url: undefined, allowed_mentions: { parse: ["users", "roles"] 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/src/web/pug/includes/template.pug b/src/web/pug/includes/template.pug index 86680eb..278a16a 100644 --- a/src/web/pug/includes/template.pug +++ b/src/web/pug/includes/template.pug @@ -88,12 +88,6 @@ html(lang="en") --_ts-multiple-bg: var(--green-400); --_ts-multiple-fc: var(--white); } - .s-avatar { - --_av-bg: var(--white); - } - .s-avatar .s-avatar--letter { - color: var(--white); - } .s-btn__dropdown:has(+ :popover-open) { background-color: var(--theme-topbar-item-background-hover, var(--black-200)) !important; } diff --git a/src/web/server.js b/src/web/server.js index 837e14d..dc13cf0 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -83,13 +83,7 @@ function tryStatic(event, fallthrough) { // Everything else else { const mime = mimeTypes.lookup(id) - if (typeof mime === "string") { - if (mime.startsWith("text/")) { - defaultContentType(event, mime + "; charset=utf-8") // usually wise - } else { - defaultContentType(event, mime) - } - } + if (typeof mime === "string") defaultContentType(event, mime) return { size: stats.size } @@ -100,7 +94,7 @@ function tryStatic(event, fallthrough) { const path = join(publicDir, id) return pugSync.renderPath(event, path, {}) } else { - return fs.createReadStream(join(publicDir, id)) + return fs.promises.readFile(join(publicDir, id)) } } }) diff --git a/test/data.js b/test/data.js index 45e0388..46e8b0f 100644 --- a/test/data.js +++ b/test/data.js @@ -4617,7 +4617,7 @@ module.exports = { flags: 0, components: [] }, - extreme_html_escaping: { + escaping_crazy_html_tags: { id: "1158894131322552391", type: 0, content: "", 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..82fa937 100644 --- a/test/test.js +++ b/test/test.js @@ -143,6 +143,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../src/d2m/actions/create-room.test") require("../src/d2m/actions/create-space.test") require("../src/d2m/actions/register-user.test") + require("../src/d2m/actions/remove-member.test") require("../src/d2m/converters/edit-to-changes.test") require("../src/d2m/converters/emoji-to-key.test") require("../src/d2m/converters/find-mentions.test") @@ -152,7 +153,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")