From 725f00a98a6d3aefb116c6e248ffdb392abee107 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 15:29:46 +1200 Subject: [PATCH 1/2] send sim messages to the proper rooms --- d2m/actions/register-user.js | 10 +++++++++- d2m/actions/send-message.js | 2 +- d2m/converters/user-to-mxid.js | 1 + d2m/converters/user-to-mxid.test.js | 4 ++++ d2m/discord-packets.js | 7 ++++--- d2m/event-dispatcher.js | 3 --- db/ooye.db | Bin 90112 -> 90112 bytes matrix/api.js | 4 ++++ matrix/mreq.js | 5 +++-- matrix/read-registration.test.js | 11 +++++++++++ test/test.js | 1 + 11 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 matrix/read-registration.test.js diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index a5fd0ef..508dd39 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -24,10 +24,18 @@ async function createSim(user) { const mxid = "@" + localpart + ":cadence.moe" // Save chosen name in the database forever + // Making this database change right away so that in a concurrent registration, the 2nd registration will already have generated a different localpart because it can see this row when it generates db.prepare("INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES (?, ?, ?, ?)").run(user.id, simName, localpart, mxid) // Register matrix user with that name - await api.register(localpart) + try { + await api.register(localpart) + } catch (e) { + // If user creation fails, manually undo the database change. Still isn't perfect, but should help. + // (A transaction would be preferable, but I don't think it's safe to leave transaction open across event loop ticks.) + db.prepare("DELETE FROM sim WHERE discord_id = ?").run(user.id) + throw e + } return mxid } diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 630cf48..f828134 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -28,4 +28,4 @@ async function sendMessage(message) { return eventID } -module.exports.sendMessage = sendMessage +module.exports.sendMessage = sendMessage \ No newline at end of file diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js index 35d9368..89e47a4 100644 --- a/d2m/converters/user-to-mxid.js +++ b/d2m/converters/user-to-mxid.js @@ -44,6 +44,7 @@ function* generateLocalpartAlternatives(preferences) { /** * Whole process for checking the database and generating the right sim name. + * It is very important this is not an async function: once the name has been chosen, the calling function should be able to immediately claim that name into the database in the same event loop tick. * @param {import("discord-api-types/v10").APIUser} user * @returns {string} */ diff --git a/d2m/converters/user-to-mxid.test.js b/d2m/converters/user-to-mxid.test.js index 4c721fc..8c4c430 100644 --- a/d2m/converters/user-to-mxid.test.js +++ b/d2m/converters/user-to-mxid.test.js @@ -31,3 +31,7 @@ test("user2name: adds number suffix if name is unavailable (new username format) test("user2name: uses ID if name becomes too short", t => { t.equal(userToSimName({username: "f***", discriminator: "0001", id: "9"}), "9") }) + +test("user2name: uses ID when name has only disallowed characters", t => { + t.equal(userToSimName({username: "!@#$%^&*", discriminator: "0001", id: "9"}), "9") +}) \ No newline at end of file diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 0d16cdc..3786393 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -6,15 +6,16 @@ const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../passthrough") const { sync } = passthrough -/** @type {typeof import("./event-dispatcher")} */ -const eventDispatcher = sync.require("./event-dispatcher") - const utils = { /** * @param {import("./discord-client")} client * @param {import("cloudstorm").IGatewayMessage} message */ onPacket(client, message) { + // requiring this later so that the client is already constructed by the time event-dispatcher is loaded + /** @type {typeof import("./event-dispatcher")} */ + const eventDispatcher = sync.require("./event-dispatcher") + if (message.t === "READY") { if (client.ready) return client.ready = true diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 8539044..83a1a70 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -2,9 +2,6 @@ const {sync} = require("../passthrough") -/** @type {import("./actions/create-space")}) */ -const createSpace = sync.require("./actions/create-space") - /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") diff --git a/db/ooye.db b/db/ooye.db index c9803f8c5b23166716dfaf9fc2e2ef32cd8010f0..8ef07bdeb2c6c642965975defc772b7ffde0712a 100644 GIT binary patch delta 1905 zcma)-O>7%g5XblBd1J5l?VBW3KK-#xok~<7vd>=cdK1iAaaA>n)Cz7iAX9?XG$FB@ zl7nffQz0bULPaphm%^!~qBm5PTndV+Rz;P9IDy0k2_d+_flE;dRqrn%*}3k$~| zD`#U~6~{YyNcCG44OY0hJ1ydDBcpIKUo7TJtvS~>{}T}>cR?Wg$!$0TFVZvgp?p_a zfjjaAWk0=1Gvus-lv_liA}5!Ved9h3?xP$UaKNFS>O+cR4!Cgln9rI*+mnlSz2HkX z0=7gq0u#Q;M#v8AqI7314sS$x8ZCz&c~lZNk?fYl>5T-RMuOuDOFPxh`Kf`l&u$d9 zHNZ}PEv7+mxYPD!ALVR=5Q%G?fQTZrTad|p0e*r{U;=#f3Y}K&(thPb#Ul4Px?f1y zCL-&Pr4vlojHF>0Oi!AoWwJyKhP!gLzjcUhj9!vO78{*pj2W7)rwrXRSc)Yzy$O{o z)8wd&&6mnsuA&tb*)G-Mk|s;Vbt`F@TGFyioiBgmEwJ@e9Sn#pvM-xtagC?(UoD~Y z`^1xT^=7&Go|Uhwcy{wP2!hZ#`0RuvvcbJJV{y|k6LBk{n@rsvhWS`!Uu3_(yakuwYcHOP*2|Dif(gSXE zYXOquP9zhimPlCDPe)O4NQP4aoPt~MFWi8?;8#95pTjkHA3o#L@*0duxF6LffB=r6 zCZMt(??RkTkl+aNni&Y_R_etGAKoEs)?CxpDEr%t8t z$`2)9yfJmUC6qhbb}EEE-xSQ4@ZxkvDB61Lln6(BkTB++o>Pmcxs+(=HMAZM5A^yU DP$WcD diff --git a/matrix/api.js b/matrix/api.js index 04b7cd1..dbc39bc 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -28,6 +28,7 @@ function path(p, mxid) { * @returns {Promise} */ function register(username) { + console.log(`[api] register: ${username}`) return mreq.mreq("POST", "/client/v3/register", { type: "m.login.application_service", username @@ -38,6 +39,7 @@ function register(username) { * @returns {Promise} room ID */ async function createRoom(content) { + console.log(`[api] create room:`, content) /** @type {import("../types").R.RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", content) return root.room_id @@ -74,6 +76,7 @@ function getAllState(roomID) { * @returns {Promise} event ID */ async function sendState(roomID, type, stateKey, content, mxid) { + console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) assert.ok(type) assert.ok(stateKey) /** @type {import("../types").R.EventSent} */ @@ -82,6 +85,7 @@ async function sendState(roomID, type, stateKey, content, mxid) { } async function sendEvent(roomID, type, content, mxid) { + console.log(`[api] event to ${roomID} as ${mxid || "default sim"}`) /** @type {import("../types").R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid), content) return root.event_id diff --git a/matrix/mreq.js b/matrix/mreq.js index 6c4eaa3..df34d91 100644 --- a/matrix/mreq.js +++ b/matrix/mreq.js @@ -11,11 +11,12 @@ const reg = sync.require("./read-registration.js") const baseUrl = "https://matrix.cadence.moe/_matrix" class MatrixServerError extends Error { - constructor(data) { + constructor(data, opts) { super(data.error || data.errcode) this.data = data /** @type {string} */ this.errcode = data.errcode + this.opts = opts } } @@ -38,7 +39,7 @@ async function mreq(method, url, body, extra = {}) { const res = await fetch(baseUrl + url, opts) const root = await res.json() - if (!res.ok || root.errcode) throw new MatrixServerError(root) + if (!res.ok || root.errcode) throw new MatrixServerError(root, opts) return root } diff --git a/matrix/read-registration.test.js b/matrix/read-registration.test.js new file mode 100644 index 0000000..9c7f828 --- /dev/null +++ b/matrix/read-registration.test.js @@ -0,0 +1,11 @@ +const {test} = require("supertape") +const assert = require("assert") +const reg = require("./read-registration") + +test("reg: has necessary parameters", t => { + const propertiesToCheck = ["sender_localpart", "id", "as_token", "namespace_prefix"] + t.deepEqual( + propertiesToCheck.filter(p => p in reg), + propertiesToCheck + ) +}) \ No newline at end of file diff --git a/test/test.js b/test/test.js index 1068136..4e01708 100644 --- a/test/test.js +++ b/test/test.js @@ -12,6 +12,7 @@ const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) +require("../matrix/read-registration.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../matrix/api.test") From 6df728dcc73b48bd2153dca12d17f4861e942a29 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 17:13:59 +1200 Subject: [PATCH 2/2] adding basic reactions to discord messages --- d2m/actions/add-reaction.js | 36 +++++++++++++++++++++++++++++++++++ d2m/actions/register-user.js | 2 ++ d2m/actions/send-message.js | 8 +++----- d2m/event-dispatcher.js | 5 ++++- db/ooye.db | Bin 90112 -> 94208 bytes 5 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 d2m/actions/add-reaction.js diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js new file mode 100644 index 0000000..82449cd --- /dev/null +++ b/d2m/actions/add-reaction.js @@ -0,0 +1,36 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") +/** @type {import("./register-user")} */ +const registerUser = sync.require("./register-user") +/** @type {import("../actions/create-room")} */ +const createRoom = sync.require("../actions/create-room") + +/** + * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data + */ +async function addReaction(data) { + const user = data.member?.user + assert.ok(user && user.username) + // TODO: should add my own sent messages to event_message so they can be reacted to? + const parentID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ? AND part = 0").pluck().get(data.message_id) // 0 = primary + if (!parentID) return // TODO: how to handle reactions for unbridged messages? is there anything I can do? + assert.equal(typeof parentID, "string") + const roomID = await createRoom.ensureRoom(data.channel_id) + const senderMxid = await registerUser.ensureSimJoined(user, roomID) + const eventID = api.sendEvent(roomID, "m.reaction", { + "m.relates_to": { + rel_type: "m.annotation", + event_id: parentID, + key: data.emoji.name + } + }, senderMxid) + return eventID +} + +module.exports.addReaction = addReaction diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 508dd39..04b0998 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -42,6 +42,7 @@ async function createSim(user) { /** * Ensure a sim is registered for the user. * If there is already a sim, use that one. If there isn't one yet, register a new sim. + * @param {import("discord-api-types/v10").APIUser} user * @returns mxid */ async function ensureSim(user) { @@ -57,6 +58,7 @@ async function ensureSim(user) { /** * Ensure a sim is registered for the user and is joined to the room. + * @param {import("discord-api-types/v10").APIUser} user * @returns mxid */ async function ensureSimJoined(user, roomID) { diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index f828134..fb181d2 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -1,8 +1,5 @@ // @ts-check -const fetch = require("node-fetch").default -const reg = require("../../matrix/read-registration.js") - const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough /** @type {import("../converters/message-to-event")} */ @@ -24,8 +21,9 @@ async function sendMessage(message) { if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) } - const eventID = api.sendEvent(roomID, "m.room.message", event, senderMxid) + const eventID = await api.sendEvent(roomID, "m.room.message", event, senderMxid) + db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, 0) // 0 is primary, 1 is supporting return eventID } -module.exports.sendMessage = sendMessage \ No newline at end of file +module.exports.sendMessage = sendMessage diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 83a1a70..eeee451 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -4,6 +4,8 @@ const {sync} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") +/** @type {import("./actions/add-reaction")}) */ +const addReaction = sync.require("./actions/add-reaction") // Grab Discord events we care about for the bridge, check them, and pass them on @@ -22,7 +24,8 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ onReactionAdd(client, data) { + if (data.emoji.id !== null) return // TOOD: image emoji reactions console.log(data) - return {} + addReaction.addReaction(data) } } diff --git a/db/ooye.db b/db/ooye.db index 8ef07bdeb2c6c642965975defc772b7ffde0712a..4408064ae46001d0d87d8c66327d9f76c3d50cec 100644 GIT binary patch delta 1712 zcmb7DZ%i9y9KPd9q3!j4{0XvH)Y5EWQy9JXu6I3=IVc4R{R1sfsLL+ba#T3lLR%;> z9dn7<6h91`g~Y^|CF&AC%@8Hb#27VhU#5Q2m?h2_|0KpZ6a6qHTYR~(Ohk-)c<;U6 zz4tu7=l48!r#>h>^-bwHTjd#oAZoW(3EpkXjU0W3I6U@~m98SHOzX>p0m^lQ%W}PF!_Q5-Y#$09t;iGgM)s5quJCK_66F) zBlceJ$icg4d3@Ay$b1OPK3ROm{HP7d?2cxPRBx;eG!##o_oeif^1W{Cwvu^HvX|91 zG?1rReXebX+lqgTx9#jdM?-rbZS|5lNtPS!MjRADtUf}2zgl5r;sm@0v%oSxGZ&fo zYNa#CEP9Lvl`G$`=6QcM8%CWuWip!dMdMR3H9F^yB>LFNpfDtO3ob35M=?#yu&mR? z39Kl%S%f5B5?SOa)k)%+bY?oz?^iqhKK?t!T=XMH$6L~0|Bq{rlER6;`0%HRiX(}XjB!y^!A4`U!dG|shJLnzZq;^+& zF{XNT7MH}kCFDXPa=S&&Q=+r97ueo3H_^v+C3C$hJD(SYKyEDIPj@HUqN%Zz$C>R) zP7jNt9&L*R2`n%0NEBJ@3aOJ!Yq4y!M~!Goyu;HicFfEn@5J!JTqqw-_POHP^lW0p z@0t%v^TEIt3o9W(6a~rU;w3W<(8ydTpcP&Nnz;^X_#8)fg;{h`D!G^7j5=TsA(iTY z-SSxcmp}|U;{TeDy{B}*9(*oorc~4ZO;5#LP2KLPb@Laf*t`%7Ue?vhqn|N~Ee{-)&p*&y9yyzuagtU{L6M z8Hqt5463)RELlj}aPMtnb&H{;mn>e`5Y|4ikVg!4G<}(XFW_Zp2Ltm4^OW@pQ)_+6 zD$%#-%b4I5`p6U3QnsePg%u5vJig@W8vw9jJk`5X0ZhHQeTFruG4fC3p6)uGBP>H|uD~5^w@Ogg@XN z_yulY#jnC!@I0J{EQW`{R8G~9n}Vz>C!Qoq{1b7{OgJt*@uEF~-0nb7w9D@c7 zC<9=qdh)({>xvK}KgBYaDCQz0;0G-7D!d6VLJ2sSkC`8tB@ELTyNzlgcNx`MN41hG c^$(Hz);>B;wvx1-DM65U2b}6xuzbn(7l0l1QUCw| delta 585 zcmZXPPiPZC6vk(=iD`Cc=gS|{#H1P<=&f~&TLL}QN(I4FbMfN8#cEZwN{hXvg{ml4 zJ(#X#E{Y&(#ZzF>;KhoHdWqf?yi~+0^k78~Mi<4GAc$m!Z{VBvn{VEmc^8^_6uO*^ zO);;Ci=SCHxqoDg+)Bfp zo^H6&lM`bj&gAIvai?h=yXC^|6A#u<@bXT}U5upNE0Ji{cKTYlc4BV)$Fyc8i8=R= z(QRk*-yE-^ALea|1V2?=8%}vSvFzzAN<$vRMpS5!IMiH@p9s^y`OY4ucI0?g4dr-I z>U7maeSAR8i)~$ms#)Bo*Uc56Q<6+7y@I>6+NH&ft8Vy97sZYnf&RE#{FQZo`Ww7A zeov5!7w5ygQFIJ7Y;fV;@vZ7JgKr6D3{huzjX4YFSexe1QJ}fU2qh