From f48c1f3f31ee123212bfa64c36a5a0a6ce76b7cb Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 31 Jan 2024 13:09:39 +1300 Subject: [PATCH] PK: Fix mentions/replies using UUID --- d2m/actions/register-pk-user.js | 4 ++++ db/migrations/0010-add-sim-proxy.sql | 5 ++++ db/orm-defs.d.ts | 6 +++++ db/orm.js | 9 +++++-- db/orm.test.js | 9 +++++++ m2d/converters/event-to-message.js | 16 ++++++++++--- m2d/converters/event-to-message.test.js | 32 ++++++++++++++++++++++++- test/ooye-test-data.sql | 6 ++++- types.d.ts | 1 + 9 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 db/migrations/0010-add-sim-proxy.sql diff --git a/d2m/actions/register-pk-user.js b/d2m/actions/register-pk-user.js index 002bfb9..003453d 100644 --- a/d2m/actions/register-pk-user.js +++ b/d2m/actions/register-pk-user.js @@ -122,10 +122,14 @@ async function memberToStateContent(pkMessage, author) { * 4. If the state content has changed, send it to Matrix and update it in the database for next time * @param {WebhookAuthor} author * @param {Ty.PkMessage} pkMessage + * @param {string} roomID * @returns {Promise} mxid of the updated sim */ async function syncUser(author, pkMessage, roomID) { const mxid = await ensureSimJoined(pkMessage, roomID) + // Update the sim_proxy table, so mentions can look up the original sender later + db.prepare("INSERT OR IGNORE INTO sim_proxy (user_id, proxy_owner_id) VALUES (?, ?)").run(pkMessage.member.id, pkMessage.sender) + // Sync the member state const content = await memberToStateContent(pkMessage, author) const currentHash = registerUser._hashProfileContent(content) const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get() diff --git a/db/migrations/0010-add-sim-proxy.sql b/db/migrations/0010-add-sim-proxy.sql new file mode 100644 index 0000000..d7dd0a1 --- /dev/null +++ b/db/migrations/0010-add-sim-proxy.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS sim_proxy ( + user_id TEXT NOT NULL, + proxy_owner_id TEXT NOT NULL, + PRIMARY KEY(user_id) +) WITHOUT ROWID; diff --git a/db/orm-defs.d.ts b/db/orm-defs.d.ts index 540c7a6..35fea51 100644 --- a/db/orm-defs.d.ts +++ b/db/orm-defs.d.ts @@ -63,6 +63,11 @@ export type Models = { hashed_profile_content: number } + sim_proxy: { + user_id: string + proxy_owner_id: string + } + webhook: { channel_id: string webhook_id: string @@ -100,3 +105,4 @@ export type Prepared = { export type AllKeys = U extends any ? keyof U : never export type PickTypeOf> = T extends { [k in K]?: any } ? T[K] : never export type Merge = {[x in AllKeys]: PickTypeOf} +export type Nullable = {[k in keyof T]: T[k] | null} diff --git a/db/orm.js b/db/orm.js index d9cc1e8..c6cab96 100644 --- a/db/orm.js +++ b/db/orm.js @@ -38,6 +38,8 @@ class From { /** @private @type {Table[]} */ this.tables = [table] /** @private */ + this.directions = [] + /** @private */ this.sql = "" /** @private */ this.cols = [] @@ -53,12 +55,14 @@ class From { * @template {keyof U.Models} Table2 * @param {Table2} table * @param {Col & (keyof U.Models[Table2])} col + * @param {"inner" | "left"} [direction] */ - join(table, col) { + join(table, col, direction = "inner") { /** @type {From>} */ // @ts-ignore const r = this r.tables.push(table) + r.directions.push(direction.toUpperCase()) r.using.push(col) return r } @@ -112,7 +116,8 @@ class From { for (let i = 1; i < this.tables.length; i++) { const table = this.tables[i] const col = this.using[i-1] - sql += `INNER JOIN ${table} USING (${col}) ` + const direction = this.directions[i-1] + sql += `${direction} JOIN ${table} USING (${col}) ` } sql += this.sql /** @type {U.Prepared, Col>>} */ diff --git a/db/orm.test.js b/db/orm.test.js index 36e95c2..066eabb 100644 --- a/db/orm.test.js +++ b/db/orm.test.js @@ -44,3 +44,12 @@ test("orm: from: where and pluck works", t => { const subtypes = from("event_message").where({message_id: "1141501302736695316"}).pluck("event_subtype").all() t.deepEqual(subtypes.sort(), ["m.image", "m.text"]) }) + +test("orm: from: join direction works", t => { + const hasOwner = from("sim").join("sim_proxy", "user_id", "left").select("user_id", "proxy_owner_id").where({sim_name: "_pk_zoego"}).get() + t.deepEqual(hasOwner, {user_id: "43d378d5-1183-47dc-ab3c-d14e21c3fe58", proxy_owner_id: "196188877885538304"}) + const hasNoOwner = from("sim").join("sim_proxy", "user_id", "left").select("user_id", "proxy_owner_id").where({sim_name: "crunch_god"}).get() + t.deepEqual(hasNoOwner, {user_id: "820865262526005258", proxy_owner_id: null}) + const hasNoOwnerInner = from("sim").join("sim_proxy", "user_id", "inner").select("user_id", "proxy_owner_id").where({sim_name: "crunch_god"}).get() + t.deepEqual(hasNoOwnerInner, null) +}) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 6d0232c..a48fa75 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -238,6 +238,16 @@ function splitDisplayName(displayName) { } } +/** + * Convert a Matrix user ID into a Discord user ID for mentioning, where if the user is a PK proxy, it will mention the proxy owner. + * @param {string} mxid + */ +function getUserOrProxyOwnerID(mxid) { + const row = from("sim").join("sim_proxy", "user_id", "left").select("user_id", "proxy_owner_id").where({mxid}).get() + if (!row) return null + return row.proxy_owner_id || row.user_id +} + /** * At the time of this executing, we know what the end of message emojis are, and we know that at least one of them is unknown. * This function will strip them from the content and generate the correct pending file of the sprite sheet. @@ -444,11 +454,11 @@ async function eventToMessage(event, guild, di) { replyLine += `https://discord.com/channels/${guild.id}/${row.channel_id}/${row.message_id} ` } const sender = repliedToEvent.sender - const authorID = select("sim", "user_id", {mxid: repliedToEvent.sender}).pluck().get() + const authorID = getUserOrProxyOwnerID(sender) if (authorID) { replyLine += `<@${authorID}>` } else { - let senderName = select("member_cache", "displayname", {mxid: repliedToEvent.sender}).pluck().get() + let senderName = select("member_cache", "displayname", {mxid: sender}).pluck().get() if (!senderName) { const match = sender.match(/@([^:]*)/) assert(match) @@ -497,7 +507,7 @@ async function eventToMessage(event, guild, di) { mxid = decodeURIComponent(mxid) if (mxUtils.eventSenderIsFromDiscord(mxid)) { // Handle mention of an OOYE sim user by their mxid - const userID = select("sim", "user_id", {mxid: mxid}).pluck().get() + const userID = getUserOrProxyOwnerID(mxid) if (!userID) return whole return `${attributeValue} data-user-id="${userID}">` } else { diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index ddb5c58..378e9f0 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -1869,7 +1869,6 @@ test("event2message: mentioning discord users works", async t => { ) }) - test("event2message: mentioning discord users works when URL encoded", async t => { t.deepEqual( await eventToMessage({ @@ -1901,6 +1900,37 @@ test("event2message: mentioning discord users works when URL encoded", async t = ) }) +test("event2message: mentioning PK discord users works", async t => { + t.deepEqual( + await eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: `I'm just Azalea testing mentions` + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "cadence [they]", + content: "I'm just <@196188877885538304> testing mentions", + avatar_url: undefined + }] + } + ) +}) + test("event2message: mentioning matrix users works", async t => { t.deepEqual( await eventToMessage({ diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index 8da0128..f1b720f 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -21,7 +21,11 @@ INSERT INTO sim (user_id, sim_name, localpart, mxid) VALUES ('112890272819507200', '.wing.', '_ooye_.wing.', '@_ooye_.wing.:cadence.moe'), ('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'), ('111604486476181504', 'kyuugryphon', '_ooye_kyuugryphon', '@_ooye_kyuugryphon:cadence.moe'), -('1109360903096369153', 'amanda', '_ooye_amanda', '@_ooye_amanda:cadence.moe'); +('1109360903096369153', 'amanda', '_ooye_amanda', '@_ooye_amanda:cadence.moe'), +('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '_pk_zoego', '_ooye__pk_zoego', '@_ooye__pk_zoego:cadence.moe'); + +INSERT INTO sim_proxy (user_id, proxy_owner_id) VALUES +('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '196188877885538304'); INSERT INTO sim_member (mxid, room_id, hashed_profile_content) VALUES ('@_ooye_bojack_horseman:cadence.moe', '!hYnGGlPHlbujVVfktC:cadence.moe', NULL); diff --git a/types.d.ts b/types.d.ts index a01241c..e9946d7 100644 --- a/types.d.ts +++ b/types.d.ts @@ -70,6 +70,7 @@ export type PkMember = { export type PkMessage = { system: PkSystem member: PkMember + sender: string } export namespace Event {