From 5716366b3f4e414cee9fd1687757db811ca68279 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 5 May 2023 08:25:00 +1200 Subject: [PATCH 001/200] space and room creation --- d2m/actions/create-room.js | 113 ++++++++++++++++++++++++++++++------ d2m/actions/create-space.js | 46 +++++++++++++++ d2m/actions/send-message.js | 4 +- d2m/discord-packets.js | 3 + d2m/event-dispatcher.js | 15 +++-- db/ooye.db | Bin 0 -> 40960 bytes db/ooye.sql | 81 ++++++++++++++++++++++++++ index.js | 4 +- matrix/file.js | 63 ++++++++++++++++++++ matrix/mreq.js | 47 +++++++++++++++ matrix/read-registration.js | 8 +-- notes.md | 25 +++++++- package-lock.json | 9 +++ package.json | 1 + passthrough.js | 1 + stdin.js | 7 ++- types.d.ts | 18 ++++++ 17 files changed, 409 insertions(+), 36 deletions(-) create mode 100644 d2m/actions/create-space.js create mode 100644 db/ooye.db create mode 100644 db/ooye.sql create mode 100644 matrix/file.js create mode 100644 matrix/mreq.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7e00651..b4934dc 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -1,22 +1,99 @@ // @ts-check -const reg = require("../../matrix/read-registration.js") -const fetch = require("node-fetch") +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") -fetch("https://matrix.cadence.moe/_matrix/client/v3/createRoom?user_id=@_ooye_example:cadence.moe", { - method: "POST", - body: JSON.stringify({ - invite: ["@cadence:cadence.moe"], - is_direct: false, - name: "New Bot User Room", - preset: "trusted_private_chat" - }), - headers: { - Authorization: `Bearer ${reg.as_token}` +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +/** + * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + */ +async function createRoom(channel) { + const guildID = channel.guild_id + assert.ok(guildID) + const guild = discord.guilds.get(guildID) + assert.ok(guild) + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guildID) + assert.ok(typeof spaceID === "string") + + const avatarEventContent = {} + if (guild.icon) { + avatarEventContent.url = await file.uploadDiscordFileToMxc(file.guildIcon(guild)) } -}).then(res => res.text()).then(text => { - // {"room_id":"!aAVaqeAKwChjWbsywj:cadence.moe"} - console.log(text) -}).catch(err => { - console.log(err) -}) + + /** @type {import("../../types").R_RoomCreated} */ + const root = await mreq.mreq("POST", "/client/v3/createRoom", { + name: channel.name, + topic: channel.topic || undefined, + preset: "private_chat", + visibility: "private", + invite: ["@cadence:cadence.moe"], // TODO + initial_state: [ + { + type: "m.room.avatar", + state_key: "", + content: avatarEventContent + }, + { + type: "m.room.guest_access", + state_key: "", + content: { + guest_access: "can_join" + } + }, + { + type: "m.room.history_visibility", + state_key: "", + content: { + history_visibility: "invited" + } + }, + { + type: "m.space.parent", + state_key: spaceID, + content: { + via: ["cadence.moe"], // TODO: put the proper server here + canonical: true + } + }, + { + type: "m.room.join_rules", + content: { + join_rule: "restricted", + allow: [{ + type: "m.room.membership", + room_id: spaceID + }] + } + } + ] + }) + + db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) + + // Put the newly created child into the space + await mreq.mreq("PUT", `/client/v3/rooms/${spaceID}/state/m.space.child/${root.room_id}`, { + via: ["cadence.moe"] // TODO: use the proper server + }) +} + +async function createAllForGuild(guildID) { + const channelIDs = discord.guildChannelMap.get(guildID) + assert.ok(channelIDs) + for (const channelID of channelIDs) { + const channel = discord.channels.get(channelID) + assert.ok(channel) + const existing = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + if (channel.type === DiscordTypes.ChannelType.GuildText && !existing) { + await createRoom(channel) + } + } +} + +module.exports.createRoom = createRoom +module.exports.createAllForGuild = createAllForGuild diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js new file mode 100644 index 0000000..6d3c327 --- /dev/null +++ b/d2m/actions/create-space.js @@ -0,0 +1,46 @@ +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") + +/** + * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild + */ +function createSpace(guild) { + return mreq.mreq("POST", "/client/v3/createRoom", { + name: guild.name, + preset: "private_chat", + visibility: "private", + power_level_content_override: { + events_default: 100, + invite: 50 + }, + invite: ["@cadence:cadence.moe"], // TODO + topic: guild.description || undefined, + creation_content: { + type: "m.space" + }, + initial_state: [ + { + type: "m.room.guest_access", + state_key: "", + content: { + guest_access: "can_join" + } + }, + { + type: "m.room.history_visibility", + content: { + history_visibility: "invited" + } + } + ] + }).then(/** @param {import("../../types").R_RoomCreated} root */ root => { + db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) + return root + }) +} + +module.exports.createSpace = createSpace diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index a4dd9a2..b736a50 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -10,7 +10,7 @@ const messageToEvent = require("../converters/message-to-event.js") */ function sendMessage(message) { const event = messageToEvent(message) - fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { + return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { method: "PUT", body: JSON.stringify(event), headers: { @@ -24,4 +24,4 @@ function sendMessage(message) { }) } -module.exports = sendMessage +module.exports.sendMessage = sendMessage diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index fe97d20..0d16cdc 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -2,6 +2,7 @@ // Discord library internals type beat +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../passthrough") const { sync } = passthrough @@ -27,6 +28,8 @@ const utils = { const arr = [] client.guildChannelMap.set(message.d.id, arr) for (const channel of message.d.channels || []) { + // @ts-ignore + channel.guild_id = message.d.id arr.push(channel.id) client.channels.set(channel.id, channel) } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 13626f4..e4de37e 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,8 +1,14 @@ // @ts-check -// Grab Discord events we care about for the bridge, check them, and pass them on +const {sync} = require("../passthrough") -const sendMessage = require("./actions/send-message") +/** @type {import("./actions/create-space")}) */ +const createSpace = sync.require("./actions/create-space") + +/** @type {import("./actions/send-message")}) */ +const sendMessage = sync.require("./actions/send-message") + +// Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { /** @@ -10,10 +16,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { - console.log(message) - console.log(message.guild_id) - console.log(message.member) - sendMessage(message) + sendMessage.sendMessage(message) }, /** diff --git a/db/ooye.db b/db/ooye.db new file mode 100644 index 0000000000000000000000000000000000000000..dea5853e5cb352179d82280dc355fe84c149f6d0 GIT binary patch literal 40960 zcmeHQX^33eb*`RXYOk72)3a*&wWN_WGg7~Okw!SRyxRArtE!3JseOI5S9Mo`9m`Gv zA=qGJ6C5Xy{4h@Ng8jpR1c%@x2FDN*kFmi95e)VZ4;k=;EF_SSb8mZ+etDxe1BqbF z+>)wQb^80>z4v?Po_pVUrBnHAqC0QywMJ8|I^W|g4_h`iEWfn3XR%mRcsk(u=3@t5 zY`ysg{@pnD`hG7}%gxqnZqH9GcGv3`@3Hrq=MDGYdA{N~alP*Ty!%7JxUg~eSLfB*pxNr4J#B%U>MN*w1)o2m?(HM< z{@yi*eZSG2)ko9D@nSmIw-?lMVK0@2XEBl3E2g5^qWUs`w9~pNAd`zGeYwhBT&-Nw z9r3#3`c>!7?(GX3mhQ08Je^Gja4B|NUCc-L{W0puF^hgWt~%Zp$RMEo?&Tfp?v*PW zA93O#);rbVusJxMjz-$~*AMB3eEwHwLx0oKZQOt4=%B|Y0Nu#X9S{aC*>-m0SME3P zH?g<@_WV1mWPQg(<~(r|T*rpoN7&xkjb6H6%k4#X&^Vrrt94NR+?yR?>z&e=bF}Td z7dNfDmo9C5WD}o`bH6(Ga!Y?Q&i&@>T$~w9gx){vun9tUD*pR;a3b6w$(_yTE#7Z? zKjr-ZT%Z4==j)!|@MJtsyMOHdvisESce`BQcm2Mr=i;6J>HL=Slg?juzTo(o<4+yG z=14i7wExKd1^dGOKD*8Ks_l1eEgNn9ck5qSKW;5s_jmqb=Z|+jyc64bZ2R8!=eH-@ zx3{;q{&wp#TlK9QoBz7`jm_WMEJD-dK@@nmD!|{_Wa13VGJ?R#G{e#oFVh@Pb(4vC zGC7EhOR1G_SZy3FZq=)e=CIzpsg0WaE5PCeMFA))07noRn&qizxO-Yz#S@LCHuSC6 z>9AkNx(zHzlv$An9>+=&%QGBHh1AZd6`3zf>3k}%SQbx9I@T@J7RPYV7{|#B%}Js} z^Hjg#ODy8yW-8yW1lzez^hn225K97vz;P_kiHa!k(8GGD7hHv>^N zKR*tn=hb@sDBtm~bSx3EWO%R&^pW9Zg_Bu<>ZJV9NWYa!PN%Cysg;@5bSwc_g2FH| z$S*UZD8bNZnMx(KXuYrXhwbQLvyjkANgay^mZ-?G!m=OC2rAM%FUzzH zDpIS}I+2?u@>(UlNQL{g#aPE;fF;qif;@tUv#dzyd z1B-{T2s}t3%K~^Z&C4*ZNisAVIg0Zq$VxgBdA9Zedcfu{MIm3&XfdJ$N%#Igdo zq6E#z91F9Jil+RLV!RRU29l@a?z%hIkLxgj!aP^6^SK@0*@X zN~>z@NGCmi4OlQHnw2FK0$7HJe|f4EZbw&Iy;4d~dbND6*f`Yn@G2S?bV{aSIN-bB zRxop@Y<$x5Wn)_TWY}B|6-T}=#gkeqZn4r)QLnNx`E0@zfwH>X@Vb{D|AL*6f2NtJ* zbTCy|S>zZ7o(iQ-Bhg7&9nKCL%SbKcE2hu)P@u5WgSRV!#0VV3Lr$Otk!ojJhkqXpy#srRrdBw62EI z(XpOJEDoB2m4$_=0&^5RnNq9u@Tpo)EKjv*K65lm>*aU~SUh;WD1#RZq6ABfq`(pu zZx=xG>a-UdhJ)R4QNM&e2`rIiA-KsrxCg7i993j07sxHkT5251_ePRq&svF}N;V)|SD1Wxa~&$Mpnap~)zL=YvlPq6}+0Rjv9Xvs$Fk z>C8iZ=w)xFQ{w`#z#kY0@icT%?FXY){)^VTkrI{(b&4x zs|<3w9zF^zk>(YMZi>JP0t}C5VXDlMgF&tnQj3SxLti(R)w3Q!ZK3E(iz37f88#3o z`t}pKNjzT6mh0VGG&xU?2fDT%1{U}qN7Ev#|C|Jq89b5-7y3;tHdv=}nR=}p1!Z(B zFRM1ZD*nfd@320N)N(I$k ze;y1+OTNq?S*qwaDsEtb?tyH^liD&xWMN* zTIP8PqAzR}Wrgb3*IK0)EO$DE!(|{B3G2PriQ5uk1w+9QW(&l8R-&TobUWJ(4i~df zwtKSDstMh=9KeEE&r1?4=-6{bh*Q*}xb~MDrEG1`KTTF`5)o>tuOZ z8|qkAV8K=hmVVFzMPASY)M!kWq2hE9$*Jv>TGILp{q|-DSg;1cDui|osILsH)0B3y z4rD5mOvD#SH=6NGva4%r8@0tl%zzjJEy0Qc@ld8>)02E#tNQDm(dcj(nAG*_-WISV z*kr?OkrbF*XwR&$R6QImCVZ#8!&t5vIvUMtaa~)Rz!DW6Z)?DHX-0k&GOz56jJRW_sA{Xql*{Yik2o5MyYN zj|D0?K15%IinNNs$Wf!ly*7g2yHKolSf5Cw<=L;<3}15yC4k~fSg_B+UI%%Q)6%*G`7ZHzW#!Ea%+ zA_uN4Y|e3*lb81UP5MLc5wepLSy<5!HxO7J$!IO0`GZbHfHE9o*mqf zf_n}h#gJor7MYDnv}dr{kR^K>nT=_%r;yp0=Xw&Ejft#XY&K-7o_ez2r?V`RIjm*Z}!xl0dlM9>p*oN%ELy%Uq8`A^m z{{M#e+i?H?0Y!ImSfT(?fG9u|APNu#hyp|bq5x5VC_oe-3J?YU9~I!y{r|W4d`+)c z@`aPlY$G*2Jj&-=OWmhD(Ea}n?`szLOCCf4q5x5VC_oe-3J?W|0z?6#08xM_KolSf z{K6}M?*ALp183L&_blFfzwiSj10V_z1&9Jf0ipm=fG9u|APNu#hyp|bqQHAof&I<6 zoqGHG|97mK#r9RZ&;A`(#i>}o=KhBFp7j%sr#uU9(#_aD=6b~O>$dMXUw8e1>zmFC zHp-^Dero?`=Usc#C0PH%J8^x`yX~lY?Czh~pL4$7y|Q(!n)7q+-}7XxKk#06{E_`P zoqz2vdOz)W!}DjJAKAa;_#3y|`zM~yIPTc~&2!_ud1}b0i2_6cq5x6gJ*2>;&G^RK zln%XvG3JFrU%?pjvY@vy#=HpVEsQZQ`Ke%xd4W$EW6aBWN*H5ayi>#&^HQAx#=K2o zPK+@x!^vTcdC^T4W6VozGC%K+SwT%2W6Y~(9$<`lt<0MkV_q5a2F94z!F>6wKc-bL zU&0vk8ka9(jCn=N>lkBRzw#Q!m{+U3iZSN3DPO=C^GcLgFvh&@+qEReT&{%xfqVr0nF|Rdf z#TfI-f;$*vUPo{nW6Y}tZefghjlfNeF|P>t5WfC5um5-U|Nlo8@8~_W4|k~@&z#g+>F8|P%8yEcQf3++t|x=D&;Nhc;{EJ{9XL5UQGh5w6d(!^1&9Jf z0ipm=fG9u|APNu#hyrh^Km#%b_6MCQIJ^FT)#82iE&D)>L;<1zQGh5w6d(!^1&9Jf a0ipm=fG9u|APT%I6}YikwZHTG|NjL>} */ +const inflight = new Map() + +/** + * @param {string} path + */ +async function uploadDiscordFileToMxc(path) { + const url = DISCORD_IMAGES_BASE + path + + // Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution + let existing = inflight.get(url) + if (typeof existing === "string") { + return existing + } + + // Has this file already been uploaded in the past? Grab the existing copy from the database. + existing = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) + if (typeof existing === "string") { + return existing + } + + // Download from Discord + const promise = fetch(url, {}).then(/** @param {import("node-fetch").Response} res */ async res => { + /** @ts-ignore @type {import("stream").Readable} body */ + const body = res.body + + // Upload to Matrix + /** @type {import("../types").R_FileUploaded} */ + const root = await mreq.mreq("POST", "/media/v3/upload", body, { + headers: { + "Content-Type": res.headers.get("content-type") + } + }) + + // Store relationship in database + db.prepare("INSERT INTO file (discord_url, mxc_url) VALUES (?, ?)").run(url, root.content_uri) + inflight.delete(url) + + return root.content_uri + }) + inflight.set(url, promise) + + return promise +} + +function guildIcon(guild) { + return `/icons/${guild.id}/${guild.icon}?size=${IMAGE_SIZE}` +} + +module.exports.guildIcon = guildIcon +module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/matrix/mreq.js b/matrix/mreq.js new file mode 100644 index 0000000..ad7fa46 --- /dev/null +++ b/matrix/mreq.js @@ -0,0 +1,47 @@ +// @ts-check + +const fetch = require("node-fetch") +const mixin = require("mixin-deep") + +const passthrough = require("../passthrough") +const { sync } = passthrough +/** @type {import("./read-registration")} */ +const reg = sync.require("./read-registration.js") + +const baseUrl = "https://matrix.cadence.moe/_matrix" + +class MatrixServerError { + constructor(data) { + this.data = data + /** @type {string} */ + this.errcode = data.errcode + /** @type {string} */ + this.error = data.error + } +} + +/** + * @param {string} method + * @param {string} url + * @param {any} [body] + * @param {any} [extra] + */ +function mreq(method, url, body, extra = {}) { + const opts = mixin({ + method, + body: (body == undefined || Object.is(body.constructor, Object)) ? JSON.stringify(body) : body, + headers: { + Authorization: `Bearer ${reg.as_token}` + } + }, extra) + console.log(baseUrl + url, opts) + return fetch(baseUrl + url, opts).then(res => { + return res.json().then(root => { + if (!res.ok || root.errcode) throw new MatrixServerError(root) + return root + }) + }) +} + +module.exports.MatrixServerError = MatrixServerError +module.exports.mreq = mreq diff --git a/matrix/read-registration.js b/matrix/read-registration.js index ee17f28..cc3fed8 100644 --- a/matrix/read-registration.js +++ b/matrix/read-registration.js @@ -3,11 +3,5 @@ const fs = require("fs") const yaml = require("js-yaml") -/** - * @typedef AppServiceRegistrationConfig - * @property {string} id - * @property {string} as_token - * @property {string} hs_token - */ - +/** @type {import("../types").AppServiceRegistrationConfig} */ module.exports = yaml.load(fs.readFileSync("registration.yaml", "utf8")) diff --git a/notes.md b/notes.md index eed5990..e63d9e5 100644 --- a/notes.md +++ b/notes.md @@ -36,6 +36,13 @@ Public channels in that server should then use the following settings, so that t - Find & join access: Space members (so users must have been invited to the space already, even if they find out the room ID to join) - Who can read history: Anyone (so that people can see messages during the preview before joining) +Step by step process: + +1. Create a space room for the guild. Store the guild-space ID relationship in the database. Configure the space room to act like a space. + - `{"name":"NAME","preset":"private_chat","visibility":"private","power_level_content_override":{"events_default":100,"invite":50},"topic":"TOPIC","creation_content":{"type":"m.space"},"initial_state":[{"type":"m.room.guest_access","state_key":"","content":{"guest_access":"can_join"}},{"type":"m.room.history_visibility","content":{"history_visibility":"invited"}}]}` +2. Create channel rooms for the channels. Store the channel-room ID relationship in the database. (Probably no need to store parent-child relationships in the database?) +3. Send state events to put the channel rooms in the space. + ### Private channels Discord **channels** that disallow view permission to @everyone should instead have the following **room** settings in Matrix: @@ -58,6 +65,13 @@ The context-sensitive /invite command will invite Matrix users to the correspond 1. Transform content. 2. Send to matrix. +## Webhook message sent + +- Consider using the _ooye_bot account to send all webhook messages to prevent extraneous joins? + - Downside: the profile information from the most recently sent message would stick around in the member list. This is toleable. +- Otherwise, could use an account per webhook ID, but if webhook IDs are often deleted and re-created, this could still end up leaving too many accounts in the room. +- The original bridge uses an account per webhook display name, which does the most sense in terms of canonical accounts, but leaves too many accounts in the room. + ## Message deleted 1. Look up equivalents on matrix. @@ -91,4 +105,13 @@ The context-sensitive /invite command will invite Matrix users to the correspond 1. Create the corresponding room. 2. Add to database. 3. Update room details to match. -4. Add to space. +4. Make sure the permissions are correct according to the rules above! +5. Add to space. + +## Emojis updated + +1. Upload any newly added images to msc. +2. Create or replace state event for the bridged pack. (Can just use key "ooye" and display name "Discord", or something, for this pack.) +3. The emojis may now be sent by Matrix users! + +TOSPEC: m2d emoji uploads?? diff --git a/package-lock.json b/package-lock.json index a653203..31df4c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", + "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", "snowtransfer": "^0.7.0", "supertape": "^8.3.0" @@ -1696,6 +1697,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mixin-deep": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", + "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==", + "engines": { + "node": ">=6" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", diff --git a/package.json b/package.json index 7a7de4a..6755f9f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", + "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", "snowtransfer": "^0.7.0", "supertape": "^8.3.0" diff --git a/passthrough.js b/passthrough.js index b6bc56f..8ef75db 100644 --- a/passthrough.js +++ b/passthrough.js @@ -6,6 +6,7 @@ * @property {typeof import("./config")} config * @property {import("./d2m/discord-client")} discord * @property {import("heatsync")} sync + * @property {import("better-sqlite3/lib/database")} db */ /** @type {Passthrough} */ // @ts-ignore diff --git a/stdin.js b/stdin.js index be38b06..fb95809 100644 --- a/stdin.js +++ b/stdin.js @@ -4,7 +4,12 @@ const repl = require("repl") const util = require("util") const passthrough = require("./passthrough") -const { discord, config, sync } = passthrough +const { discord, config, sync, db } = passthrough + +const createSpace = sync.require("./d2m/actions/create-space.js") +const createRoom = sync.require("./d2m/actions/create-room.js") +const mreq = sync.require("./matrix/mreq.js") +const guildID = "112760669178241024" const extraContext = {} diff --git a/types.d.ts b/types.d.ts index 180a559..d571359 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,6 +1,24 @@ +export type AppServiceRegistrationConfig = { + id: string + as_token: string + hs_token: string + url: string + sender_localpart: string + protocols: [string] + rate_limited: boolean +} + export type M_Room_Message_content = { msgtype: "m.text" body: string formatted_body?: "org.matrix.custom.html" format?: string } + +export type R_RoomCreated = { + room_id: string +} + +export type R_FileUploaded = { + content_uri: string +} From c7868e9dbb605d5c58b69542f5b88f3b86737757 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 5 May 2023 08:25:00 +1200 Subject: [PATCH 002/200] space and room creation --- d2m/actions/create-room.js | 113 ++++++++++++++++++++++++++++++------ d2m/actions/create-space.js | 46 +++++++++++++++ d2m/actions/send-message.js | 4 +- d2m/discord-packets.js | 3 + d2m/event-dispatcher.js | 15 +++-- index.js | 4 +- matrix/file.js | 63 ++++++++++++++++++++ matrix/mreq.js | 47 +++++++++++++++ matrix/read-registration.js | 8 +-- notes.md | 25 +++++++- package-lock.json | 9 +++ package.json | 1 + passthrough.js | 1 + stdin.js | 7 ++- types.d.ts | 18 ++++++ 15 files changed, 328 insertions(+), 36 deletions(-) create mode 100644 d2m/actions/create-space.js create mode 100644 matrix/file.js create mode 100644 matrix/mreq.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7e00651..b4934dc 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -1,22 +1,99 @@ // @ts-check -const reg = require("../../matrix/read-registration.js") -const fetch = require("node-fetch") +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") -fetch("https://matrix.cadence.moe/_matrix/client/v3/createRoom?user_id=@_ooye_example:cadence.moe", { - method: "POST", - body: JSON.stringify({ - invite: ["@cadence:cadence.moe"], - is_direct: false, - name: "New Bot User Room", - preset: "trusted_private_chat" - }), - headers: { - Authorization: `Bearer ${reg.as_token}` +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +/** + * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + */ +async function createRoom(channel) { + const guildID = channel.guild_id + assert.ok(guildID) + const guild = discord.guilds.get(guildID) + assert.ok(guild) + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guildID) + assert.ok(typeof spaceID === "string") + + const avatarEventContent = {} + if (guild.icon) { + avatarEventContent.url = await file.uploadDiscordFileToMxc(file.guildIcon(guild)) } -}).then(res => res.text()).then(text => { - // {"room_id":"!aAVaqeAKwChjWbsywj:cadence.moe"} - console.log(text) -}).catch(err => { - console.log(err) -}) + + /** @type {import("../../types").R_RoomCreated} */ + const root = await mreq.mreq("POST", "/client/v3/createRoom", { + name: channel.name, + topic: channel.topic || undefined, + preset: "private_chat", + visibility: "private", + invite: ["@cadence:cadence.moe"], // TODO + initial_state: [ + { + type: "m.room.avatar", + state_key: "", + content: avatarEventContent + }, + { + type: "m.room.guest_access", + state_key: "", + content: { + guest_access: "can_join" + } + }, + { + type: "m.room.history_visibility", + state_key: "", + content: { + history_visibility: "invited" + } + }, + { + type: "m.space.parent", + state_key: spaceID, + content: { + via: ["cadence.moe"], // TODO: put the proper server here + canonical: true + } + }, + { + type: "m.room.join_rules", + content: { + join_rule: "restricted", + allow: [{ + type: "m.room.membership", + room_id: spaceID + }] + } + } + ] + }) + + db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) + + // Put the newly created child into the space + await mreq.mreq("PUT", `/client/v3/rooms/${spaceID}/state/m.space.child/${root.room_id}`, { + via: ["cadence.moe"] // TODO: use the proper server + }) +} + +async function createAllForGuild(guildID) { + const channelIDs = discord.guildChannelMap.get(guildID) + assert.ok(channelIDs) + for (const channelID of channelIDs) { + const channel = discord.channels.get(channelID) + assert.ok(channel) + const existing = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + if (channel.type === DiscordTypes.ChannelType.GuildText && !existing) { + await createRoom(channel) + } + } +} + +module.exports.createRoom = createRoom +module.exports.createAllForGuild = createAllForGuild diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js new file mode 100644 index 0000000..6d3c327 --- /dev/null +++ b/d2m/actions/create-space.js @@ -0,0 +1,46 @@ +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") + +/** + * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild + */ +function createSpace(guild) { + return mreq.mreq("POST", "/client/v3/createRoom", { + name: guild.name, + preset: "private_chat", + visibility: "private", + power_level_content_override: { + events_default: 100, + invite: 50 + }, + invite: ["@cadence:cadence.moe"], // TODO + topic: guild.description || undefined, + creation_content: { + type: "m.space" + }, + initial_state: [ + { + type: "m.room.guest_access", + state_key: "", + content: { + guest_access: "can_join" + } + }, + { + type: "m.room.history_visibility", + content: { + history_visibility: "invited" + } + } + ] + }).then(/** @param {import("../../types").R_RoomCreated} root */ root => { + db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) + return root + }) +} + +module.exports.createSpace = createSpace diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index a4dd9a2..b736a50 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -10,7 +10,7 @@ const messageToEvent = require("../converters/message-to-event.js") */ function sendMessage(message) { const event = messageToEvent(message) - fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { + return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { method: "PUT", body: JSON.stringify(event), headers: { @@ -24,4 +24,4 @@ function sendMessage(message) { }) } -module.exports = sendMessage +module.exports.sendMessage = sendMessage diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index fe97d20..0d16cdc 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -2,6 +2,7 @@ // Discord library internals type beat +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../passthrough") const { sync } = passthrough @@ -27,6 +28,8 @@ const utils = { const arr = [] client.guildChannelMap.set(message.d.id, arr) for (const channel of message.d.channels || []) { + // @ts-ignore + channel.guild_id = message.d.id arr.push(channel.id) client.channels.set(channel.id, channel) } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 13626f4..e4de37e 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,8 +1,14 @@ // @ts-check -// Grab Discord events we care about for the bridge, check them, and pass them on +const {sync} = require("../passthrough") -const sendMessage = require("./actions/send-message") +/** @type {import("./actions/create-space")}) */ +const createSpace = sync.require("./actions/create-space") + +/** @type {import("./actions/send-message")}) */ +const sendMessage = sync.require("./actions/send-message") + +// Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { /** @@ -10,10 +16,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { - console.log(message) - console.log(message.guild_id) - console.log(message.member) - sendMessage(message) + sendMessage.sendMessage(message) }, /** diff --git a/index.js b/index.js index c6340f8..d1c721c 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,15 @@ // @ts-check +const sqlite = require("better-sqlite3") const HeatSync = require("heatsync") const config = require("./config") const passthrough = require("./passthrough") +const db = new sqlite("db/ooye.db") const sync = new HeatSync() -Object.assign(passthrough, { config, sync }) +Object.assign(passthrough, { config, sync, db }) const DiscordClient = require("./d2m/discord-client") diff --git a/matrix/file.js b/matrix/file.js new file mode 100644 index 0000000..f5e81ce --- /dev/null +++ b/matrix/file.js @@ -0,0 +1,63 @@ +// @ts-check + +const fetch = require("node-fetch") + +const passthrough = require("../passthrough") +const { sync, db } = passthrough +/** @type {import("./mreq")} */ +const mreq = sync.require("./mreq") + +const DISCORD_IMAGES_BASE = "https://cdn.discordapp.com" +const IMAGE_SIZE = 1024 + +/** @type {Map>} */ +const inflight = new Map() + +/** + * @param {string} path + */ +async function uploadDiscordFileToMxc(path) { + const url = DISCORD_IMAGES_BASE + path + + // Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution + let existing = inflight.get(url) + if (typeof existing === "string") { + return existing + } + + // Has this file already been uploaded in the past? Grab the existing copy from the database. + existing = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) + if (typeof existing === "string") { + return existing + } + + // Download from Discord + const promise = fetch(url, {}).then(/** @param {import("node-fetch").Response} res */ async res => { + /** @ts-ignore @type {import("stream").Readable} body */ + const body = res.body + + // Upload to Matrix + /** @type {import("../types").R_FileUploaded} */ + const root = await mreq.mreq("POST", "/media/v3/upload", body, { + headers: { + "Content-Type": res.headers.get("content-type") + } + }) + + // Store relationship in database + db.prepare("INSERT INTO file (discord_url, mxc_url) VALUES (?, ?)").run(url, root.content_uri) + inflight.delete(url) + + return root.content_uri + }) + inflight.set(url, promise) + + return promise +} + +function guildIcon(guild) { + return `/icons/${guild.id}/${guild.icon}?size=${IMAGE_SIZE}` +} + +module.exports.guildIcon = guildIcon +module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/matrix/mreq.js b/matrix/mreq.js new file mode 100644 index 0000000..ad7fa46 --- /dev/null +++ b/matrix/mreq.js @@ -0,0 +1,47 @@ +// @ts-check + +const fetch = require("node-fetch") +const mixin = require("mixin-deep") + +const passthrough = require("../passthrough") +const { sync } = passthrough +/** @type {import("./read-registration")} */ +const reg = sync.require("./read-registration.js") + +const baseUrl = "https://matrix.cadence.moe/_matrix" + +class MatrixServerError { + constructor(data) { + this.data = data + /** @type {string} */ + this.errcode = data.errcode + /** @type {string} */ + this.error = data.error + } +} + +/** + * @param {string} method + * @param {string} url + * @param {any} [body] + * @param {any} [extra] + */ +function mreq(method, url, body, extra = {}) { + const opts = mixin({ + method, + body: (body == undefined || Object.is(body.constructor, Object)) ? JSON.stringify(body) : body, + headers: { + Authorization: `Bearer ${reg.as_token}` + } + }, extra) + console.log(baseUrl + url, opts) + return fetch(baseUrl + url, opts).then(res => { + return res.json().then(root => { + if (!res.ok || root.errcode) throw new MatrixServerError(root) + return root + }) + }) +} + +module.exports.MatrixServerError = MatrixServerError +module.exports.mreq = mreq diff --git a/matrix/read-registration.js b/matrix/read-registration.js index ee17f28..cc3fed8 100644 --- a/matrix/read-registration.js +++ b/matrix/read-registration.js @@ -3,11 +3,5 @@ const fs = require("fs") const yaml = require("js-yaml") -/** - * @typedef AppServiceRegistrationConfig - * @property {string} id - * @property {string} as_token - * @property {string} hs_token - */ - +/** @type {import("../types").AppServiceRegistrationConfig} */ module.exports = yaml.load(fs.readFileSync("registration.yaml", "utf8")) diff --git a/notes.md b/notes.md index eed5990..e63d9e5 100644 --- a/notes.md +++ b/notes.md @@ -36,6 +36,13 @@ Public channels in that server should then use the following settings, so that t - Find & join access: Space members (so users must have been invited to the space already, even if they find out the room ID to join) - Who can read history: Anyone (so that people can see messages during the preview before joining) +Step by step process: + +1. Create a space room for the guild. Store the guild-space ID relationship in the database. Configure the space room to act like a space. + - `{"name":"NAME","preset":"private_chat","visibility":"private","power_level_content_override":{"events_default":100,"invite":50},"topic":"TOPIC","creation_content":{"type":"m.space"},"initial_state":[{"type":"m.room.guest_access","state_key":"","content":{"guest_access":"can_join"}},{"type":"m.room.history_visibility","content":{"history_visibility":"invited"}}]}` +2. Create channel rooms for the channels. Store the channel-room ID relationship in the database. (Probably no need to store parent-child relationships in the database?) +3. Send state events to put the channel rooms in the space. + ### Private channels Discord **channels** that disallow view permission to @everyone should instead have the following **room** settings in Matrix: @@ -58,6 +65,13 @@ The context-sensitive /invite command will invite Matrix users to the correspond 1. Transform content. 2. Send to matrix. +## Webhook message sent + +- Consider using the _ooye_bot account to send all webhook messages to prevent extraneous joins? + - Downside: the profile information from the most recently sent message would stick around in the member list. This is toleable. +- Otherwise, could use an account per webhook ID, but if webhook IDs are often deleted and re-created, this could still end up leaving too many accounts in the room. +- The original bridge uses an account per webhook display name, which does the most sense in terms of canonical accounts, but leaves too many accounts in the room. + ## Message deleted 1. Look up equivalents on matrix. @@ -91,4 +105,13 @@ The context-sensitive /invite command will invite Matrix users to the correspond 1. Create the corresponding room. 2. Add to database. 3. Update room details to match. -4. Add to space. +4. Make sure the permissions are correct according to the rules above! +5. Add to space. + +## Emojis updated + +1. Upload any newly added images to msc. +2. Create or replace state event for the bridged pack. (Can just use key "ooye" and display name "Discord", or something, for this pack.) +3. The emojis may now be sent by Matrix users! + +TOSPEC: m2d emoji uploads?? diff --git a/package-lock.json b/package-lock.json index a653203..31df4c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", + "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", "snowtransfer": "^0.7.0", "supertape": "^8.3.0" @@ -1696,6 +1697,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mixin-deep": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", + "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==", + "engines": { + "node": ">=6" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", diff --git a/package.json b/package.json index 7a7de4a..6755f9f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", + "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", "snowtransfer": "^0.7.0", "supertape": "^8.3.0" diff --git a/passthrough.js b/passthrough.js index b6bc56f..8ef75db 100644 --- a/passthrough.js +++ b/passthrough.js @@ -6,6 +6,7 @@ * @property {typeof import("./config")} config * @property {import("./d2m/discord-client")} discord * @property {import("heatsync")} sync + * @property {import("better-sqlite3/lib/database")} db */ /** @type {Passthrough} */ // @ts-ignore diff --git a/stdin.js b/stdin.js index be38b06..fb95809 100644 --- a/stdin.js +++ b/stdin.js @@ -4,7 +4,12 @@ const repl = require("repl") const util = require("util") const passthrough = require("./passthrough") -const { discord, config, sync } = passthrough +const { discord, config, sync, db } = passthrough + +const createSpace = sync.require("./d2m/actions/create-space.js") +const createRoom = sync.require("./d2m/actions/create-room.js") +const mreq = sync.require("./matrix/mreq.js") +const guildID = "112760669178241024" const extraContext = {} diff --git a/types.d.ts b/types.d.ts index 180a559..d571359 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,6 +1,24 @@ +export type AppServiceRegistrationConfig = { + id: string + as_token: string + hs_token: string + url: string + sender_localpart: string + protocols: [string] + rate_limited: boolean +} + export type M_Room_Message_content = { msgtype: "m.text" body: string formatted_body?: "org.matrix.custom.html" format?: string } + +export type R_RoomCreated = { + room_id: string +} + +export type R_FileUploaded = { + content_uri: string +} From d21617e2d3069a5c54453370985e1e6965438a52 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 5 May 2023 17:29:08 +1200 Subject: [PATCH 003/200] add tests, implement kstate and state diffing --- d2m/actions/create-room.js | 186 ++- d2m/actions/send-message.js | 4 +- d2m/converters/message-to-event.js | 4 +- matrix/file.js | 5 +- matrix/mreq.js | 16 +- matrix/read-registration.js | 5 +- package-lock.json | 2397 +++++++++++++++++++++++++++- package.json | 3 + test/data.js | 85 + test/test.js | 15 + 10 files changed, 2656 insertions(+), 64 deletions(-) create mode 100644 test/data.js create mode 100644 test/test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index b4934dc..5af26f5 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -1,6 +1,8 @@ // @ts-check const assert = require("assert").strict +const {test} = require("supertape") +const testData = require("../../test/data") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -10,22 +12,134 @@ const mreq = sync.require("../../matrix/mreq") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +function kstateToState(kstate) { + return Object.entries(kstate).map(([k, content]) => { + console.log(k) + const [type, state_key] = k.split("/") + assert.ok(typeof type === "string") + assert.ok(typeof state_key === "string") + return {type, state_key, content} + }) +} + +test("kstate2state: general", t => { + t.deepEqual(kstateToState({ + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }), [ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]) +}) + +function diffKState(actual, target) { + const diff = {} + // go through each key that it should have + for (const key of Object.keys(target)) { + if (key in actual) { + // diff + try { + assert.deepEqual(actual[key], target[key]) + } catch (e) { + // they differ. reassign the target + diff[key] = target[key] + } + } else { + // not present, needs to be added + diff[key] = target[key] + } + } + return diff +} + +test("diffKState: detects edits", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + "same/": {a: 2} + }, { + "m.room.name/": {name: "edited name"}, + "same/": {a: 2} + }), + { + "m.room.name/": {name: "edited name"} + } + ) +}) + +test("diffKState: detects new properties", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + }, { + "m.room.name/": {name: "test name"}, + "new/": {a: 2} + }), + { + "new/": {a: 2} + } + ) +}) + /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function createRoom(channel) { - const guildID = channel.guild_id - assert.ok(guildID) - const guild = discord.guilds.get(guildID) - assert.ok(guild) - const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guildID) +async function channelToKState(channel, guild) { + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) assert.ok(typeof spaceID === "string") const avatarEventContent = {} if (guild.icon) { - avatarEventContent.url = await file.uploadDiscordFileToMxc(file.guildIcon(guild)) + avatarEventContent.discord_path = file.guildIcon(guild) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) } + const kstate = { + "m.room.name/": {name: channel.name}, + "m.room.topic/": {topic: channel.topic || undefined}, + "m.room.avatar/": avatarEventContent, + "m.room.guest_access/": {guest_access: "can_join"}, + "m.room.history_visibility/": {history_visibility: "invited"}, + [`m.space.parent/${spaceID}`]: { // TODO: put the proper server here + via: ["cadence.moe"], + canonical: true + }, + "m.room.join_rules/": { + join_rule: "restricted", + allow: [{ + type: "m.room.membership", + room_id: spaceID + }] + } + } + + return {spaceID, kstate} +} + +test("channel2room: general", async t => { + t.deepEqual(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.kstate), {expected: true, ...testData.room.general}) +}) + +/** + * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param guild + * @param {string} spaceID + * @param {any} kstate + */ +async function createRoom(channel, guild, spaceID, kstate) { /** @type {import("../../types").R_RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", { name: channel.name, @@ -33,45 +147,7 @@ async function createRoom(channel) { preset: "private_chat", visibility: "private", invite: ["@cadence:cadence.moe"], // TODO - initial_state: [ - { - type: "m.room.avatar", - state_key: "", - content: avatarEventContent - }, - { - type: "m.room.guest_access", - state_key: "", - content: { - guest_access: "can_join" - } - }, - { - type: "m.room.history_visibility", - state_key: "", - content: { - history_visibility: "invited" - } - }, - { - type: "m.space.parent", - state_key: spaceID, - content: { - via: ["cadence.moe"], // TODO: put the proper server here - canonical: true - } - }, - { - type: "m.room.join_rules", - content: { - join_rule: "restricted", - allow: [{ - type: "m.room.membership", - room_id: spaceID - }] - } - } - ] + initial_state: kstateToState(kstate) }) db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) @@ -82,6 +158,24 @@ async function createRoom(channel) { }) } +/** + * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + */ +async function syncRoom(channel) { + const guildID = channel.guild_id + assert(guildID) + const guild = discord.guilds.get(guildID) + assert(guild) + + const {spaceID, kstate} = await channelToKState(channel, guild) + + /** @type {string?} */ + const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) + if (!existing) { + createRoom(channel, guild, spaceID, kstate) + } +} + async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index b736a50..1f71a66 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -2,14 +2,14 @@ const reg = require("../../matrix/read-registration.js") const makeTxnId = require("../../matrix/txnid.js") -const fetch = require("node-fetch") +const fetch = require("node-fetch").default const messageToEvent = require("../converters/message-to-event.js") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ function sendMessage(message) { - const event = messageToEvent(message) + const event = messageToEvent.messageToEvent(message) return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { method: "PUT", body: JSON.stringify(event), diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index c9d158e..6f9bc37 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -6,7 +6,7 @@ const markdown = require("discord-markdown") * @param {import("discord-api-types/v10").APIMessage} message * @returns {import("../../types").M_Room_Message_content} */ -module.exports = function messageToEvent(message) { +function messageToEvent(message) { const body = message.content const html = markdown.toHTML(body, { /* discordCallback: { @@ -24,3 +24,5 @@ module.exports = function messageToEvent(message) { formatted_body: html } } + +module.exports.messageToEvent = messageToEvent \ No newline at end of file diff --git a/matrix/file.js b/matrix/file.js index f5e81ce..d242f52 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -1,6 +1,6 @@ // @ts-check -const fetch = require("node-fetch") +const fetch = require("node-fetch").default const passthrough = require("../passthrough") const { sync, db } = passthrough @@ -33,7 +33,6 @@ async function uploadDiscordFileToMxc(path) { // Download from Discord const promise = fetch(url, {}).then(/** @param {import("node-fetch").Response} res */ async res => { - /** @ts-ignore @type {import("stream").Readable} body */ const body = res.body // Upload to Matrix @@ -56,7 +55,7 @@ async function uploadDiscordFileToMxc(path) { } function guildIcon(guild) { - return `/icons/${guild.id}/${guild.icon}?size=${IMAGE_SIZE}` + return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } module.exports.guildIcon = guildIcon diff --git a/matrix/mreq.js b/matrix/mreq.js index ad7fa46..b3e12aa 100644 --- a/matrix/mreq.js +++ b/matrix/mreq.js @@ -1,6 +1,6 @@ // @ts-check -const fetch = require("node-fetch") +const fetch = require("node-fetch").default const mixin = require("mixin-deep") const passthrough = require("../passthrough") @@ -26,7 +26,7 @@ class MatrixServerError { * @param {any} [body] * @param {any} [extra] */ -function mreq(method, url, body, extra = {}) { +async function mreq(method, url, body, extra = {}) { const opts = mixin({ method, body: (body == undefined || Object.is(body.constructor, Object)) ? JSON.stringify(body) : body, @@ -34,13 +34,13 @@ function mreq(method, url, body, extra = {}) { Authorization: `Bearer ${reg.as_token}` } }, extra) + console.log(baseUrl + url, opts) - return fetch(baseUrl + url, opts).then(res => { - return res.json().then(root => { - if (!res.ok || root.errcode) throw new MatrixServerError(root) - return root - }) - }) + const res = await fetch(baseUrl + url, opts) + const root = await res.json() + + if (!res.ok || root.errcode) throw new MatrixServerError(root) + return root } module.exports.MatrixServerError = MatrixServerError diff --git a/matrix/read-registration.js b/matrix/read-registration.js index cc3fed8..a1d920d 100644 --- a/matrix/read-registration.js +++ b/matrix/read-registration.js @@ -3,5 +3,6 @@ const fs = require("fs") const yaml = require("js-yaml") -/** @type {import("../types").AppServiceRegistrationConfig} */ -module.exports = yaml.load(fs.readFileSync("registration.yaml", "utf8")) +/** @ts-ignore @type {import("../types").AppServiceRegistrationConfig} */ +const reg = yaml.load(fs.readFileSync("registration.yaml", "utf8")) +module.exports = reg \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 31df4c7..654d022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "out-of-your-element", "version": "1.0.0", - "lockfileVersion": 3, + "lockfileVersion": 2, "requires": true, "packages": { "": { @@ -22,7 +22,8 @@ "supertape": "^8.3.0" }, "devDependencies": { - "@types/node": "^18.16.0" + "@types/node": "^18.16.0", + "tap-dot": "github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf" } }, "node_modules/@babel/runtime": { @@ -656,6 +657,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -834,6 +841,15 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1127,6 +1143,27 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -1951,6 +1988,12 @@ "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2022,6 +2065,12 @@ "rc": "cli.js" } }, + "node_modules/re-emitter": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.4.tgz", + "integrity": "sha512-C0SIXdXDSus2yqqvV7qifnb4NoWP7mEBXJq3axci301mXHCZb8Djwm4hrEZo4UeXRaEnfjH98uQ8EBppk2oNWA==", + "dev": true + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -2276,6 +2325,18 @@ "node": ">=0.10.0" } }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/stacktracey": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", @@ -2424,6 +2485,131 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tap-dot": { + "version": "2.0.0", + "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.1", + "tap-out": "github:alandelaney-whs/tap-out", + "through2": "^2.0.0" + }, + "bin": { + "tap-dot": "bin/dot" + } + }, + "node_modules/tap-dot/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tap-out": { + "name": "tap-in", + "version": "3.2.1", + "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "dev": true, + "license": "MIT", + "dependencies": { + "re-emitter": "1.1.4", + "readable-stream": "2.3.7", + "split": "1.0.1", + "trim": "1.0.1" + }, + "bin": { + "tap-in": "bin/tap-in.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tap-out/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/tap-out/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/tap-out/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/tap-out/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -2450,6 +2636,58 @@ "node": ">=6" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2463,6 +2701,13 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/trim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", + "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", + "deprecated": "Use String.prototype.trim() instead", + "dev": true + }, "node_modules/try-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", @@ -2608,6 +2853,15 @@ "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -2621,5 +2875,2144 @@ "node": ">=12" } } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@cloudcmd/stub": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", + "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.0.6", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" + }, + "jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + } + }, + "jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@matrix-org/matrix-sdk-crypto-js": { + "version": "0.1.0-alpha.7", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz", + "integrity": "sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==" + }, + "@putout/cli-keypress": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", + "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", + "requires": { + "ci-info": "^3.1.1", + "fullstore": "^3.0.0" + } + }, + "@putout/cli-validate-args": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", + "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", + "requires": { + "fastest-levenshtein": "^1.0.12", + "just-kebab-case": "^1.1.0" + } + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + }, + "@supertape/engine-loader": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", + "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", + "requires": { + "try-catch": "^3.0.0" + } + }, + "@supertape/formatter-fail": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", + "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", + "requires": { + "@supertape/formatter-tap": "^3.0.3", + "fullstore": "^3.0.0" + } + }, + "@supertape/formatter-json-lines": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", + "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", + "requires": { + "fullstore": "^3.0.0" + } + }, + "@supertape/formatter-progress-bar": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", + "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", + "requires": { + "chalk": "^4.1.0", + "ci-info": "^3.1.1", + "cli-progress": "^3.8.2", + "fullstore": "^3.0.0", + "once": "^1.4.0" + } + }, + "@supertape/formatter-short": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", + "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==" + }, + "@supertape/formatter-tap": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", + "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==" + }, + "@supertape/operator-stub": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", + "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", + "requires": { + "@cloudcmd/stub": "^4.0.0" + } + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "@types/node": { + "version": "18.16.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz", + "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "@types/react": { + "version": "18.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.38.tgz", + "integrity": "sha512-ExsidLLSzYj4cvaQjGnQCk4HFfVT9+EZ9XZsQ8Hsrcn8QNgXtpZ3m9vSIC2MWtx7jHictK6wYhQgGh6ic58oOw==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "another-json": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", + "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==" + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "requires": { + "printable-characters": "^1.0.42" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "backtracker": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.1.tgz", + "integrity": "sha512-bQTxQ/JL9nm8/mNFP/bkiOJN0w9OOK6LQDqa+Jt9YnnFGQzAplYwi2TDmzuEwHoAtuUso5StoyKvZazkPO4q4g==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "better-sqlite3": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz", + "integrity": "sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w==", + "requires": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.0" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "requires": { + "base-x": "^4.0.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "centra": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.6.0.tgz", + "integrity": "sha512-dgh+YleemrT8u85QL11Z6tYhegAs3MMxsaWAq/oXeAmYJ7VxL3SI9TZtnfaEvNDMAPolj25FXIb3S+HCI4wQaQ==" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" + }, + "cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "requires": { + "string-width": "^4.2.3" + } + }, + "cloudstorm": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.0.tgz", + "integrity": "sha512-k+1u1kTdtlz3L6lnflAKMhkkZPoBl/2Du2czNvad2pYNOMBs8e0XZpSuCazC50Q29tzi08latn4SxtLbkws50A==", + "requires": { + "snowtransfer": "0.7.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==" + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "requires": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==" + }, + "discord-api-types": { + "version": "0.37.39", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.39.tgz", + "integrity": "sha512-hkhQsQyzsTJITp311WXvHZh9j4RAMfIk2hPmsWeOTN50QTpg6zqmJNfel9D/8lYNvsU01wzw9281Yke8NhYyHg==" + }, + "discord-markdown": { + "version": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "from": "discord-markdown@git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "requires": { + "simple-markdown": "^0.7.2" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fullstore": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", + "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "requires": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + } + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "heatsync": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.0.tgz", + "integrity": "sha512-3avAZvdWohjVNhx/P1lHGEUriGP8VlbdFKrMsiBVbXzOGuEEKnC9840Qu4SyUWxgs0V1D3RIpNS3898NFgQkng==", + "requires": { + "backtracker": "3.3.1" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "just-kebab-case": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", + "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==" + }, + "loglevel": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "matrix-appservice": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-2.0.0.tgz", + "integrity": "sha512-HCIuJ5i0YuO8b0dMyGe5dqlsE4f3RzHU0MuMg/2gGAZ4HL3r7aSWOFbyIWStSSUrk1qCa9Eml0i4EnEi0pOtdA==", + "requires": { + "body-parser": "^1.19.0", + "express": "^4.18.1", + "js-yaml": "^4.1.0", + "morgan": "^1.10.0" + } + }, + "matrix-events-sdk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", + "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" + }, + "matrix-js-sdk": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-24.1.0.tgz", + "integrity": "sha512-xEx2ZoNsS56dwgqLJ3rIv2SUpFxdQLrLKmJCpMatMUKCAg+NGuZfpQ3QXblIbGaqFNQZCH7fC7S48AeTMZp1Jw==", + "requires": { + "@babel/runtime": "^7.12.5", + "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.5", + "another-json": "^0.2.0", + "bs58": "^5.0.0", + "content-type": "^1.0.4", + "loglevel": "^1.7.1", + "matrix-events-sdk": "0.0.1", + "matrix-widget-api": "^1.3.1", + "p-retry": "4", + "sdp-transform": "^2.14.1", + "unhomoglyph": "^1.0.6", + "uuid": "9" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + } + } + }, + "matrix-widget-api": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz", + "integrity": "sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==", + "requires": { + "@types/events": "^3.0.0", + "events": "^3.2.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mixin-deep": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", + "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==" + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-abi": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.40.0.tgz", + "integrity": "sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA==", + "requires": { + "semver": "^7.3.5" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + } + } + }, + "printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "re-emitter": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.4.tgz", + "integrity": "sha512-C0SIXdXDSus2yqqvV7qifnb4NoWP7mEBXJq3axci301mXHCZb8Djwm4hrEZo4UeXRaEnfjH98uQ8EBppk2oNWA==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sdp-transform": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz", + "integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==" + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "simple-markdown": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.7.3.tgz", + "integrity": "sha512-uGXIc13NGpqfPeFJIt/7SHHxd6HekEJYtsdoCM06mEBPL9fQH/pSD7LRM6PZ7CKchpSvxKL4tvwMamqAaNDAyg==", + "requires": { + "@types/react": ">=16.0.0" + } + }, + "snowtransfer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.0.tgz", + "integrity": "sha512-vc7B46tO4QeK99z/pN8ISd8QvO9QB3Oo4qP7nYYhriIMOtjYkHMi8t6kUBPIJLbeX+h0NpfwxaGJfXNLm1ZQ5A==", + "requires": { + "centra": "^2.6.0", + "discord-api-types": "^0.37.31", + "form-data": "^4.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "stacktracey": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", + "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "requires": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "requires": { + "internal-slot": "^1.0.4" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + }, + "supertape": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", + "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", + "requires": { + "@cloudcmd/stub": "^4.0.0", + "@putout/cli-keypress": "^1.0.0", + "@putout/cli-validate-args": "^1.0.1", + "@supertape/engine-loader": "^1.0.0", + "@supertape/formatter-fail": "^3.0.0", + "@supertape/formatter-json-lines": "^2.0.0", + "@supertape/formatter-progress-bar": "^3.0.0", + "@supertape/formatter-short": "^2.0.0", + "@supertape/formatter-tap": "^3.0.0", + "@supertape/operator-stub": "^3.0.0", + "cli-progress": "^3.8.2", + "deep-equal": "^2.0.3", + "fullstore": "^3.0.0", + "glob": "^8.0.3", + "jest-diff": "^29.0.1", + "once": "^1.4.0", + "resolve": "^1.17.0", + "stacktracey": "^2.1.7", + "strip-ansi": "^7.0.0", + "try-to-catch": "^3.0.0", + "wraptile": "^3.0.0", + "yargs-parser": "^21.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "tap-dot": { + "version": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "dev": true, + "from": "tap-dot@github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "requires": { + "chalk": "^1.1.1", + "tap-out": "github:alandelaney-whs/tap-out", + "through2": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "tap-out": { + "version": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "dev": true, + "from": "tap-out@github:alandelaney-whs/tap-out", + "requires": { + "re-emitter": "1.1.4", + "readable-stream": "2.3.7", + "split": "1.0.1", + "trim": "1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "trim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", + "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", + "dev": true + }, + "try-catch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", + "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==" + }, + "try-to-catch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", + "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unhomoglyph": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", + "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "wraptile": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", + "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } } } diff --git a/package.json b/package.json index 6755f9f..f8917e7 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,8 @@ }, "devDependencies": { "@types/node": "^18.16.0" + }, + "scripts": { + "test": "supertape --format short test/test.js" } } diff --git a/test/data.js b/test/data.js new file mode 100644 index 0000000..42af177 --- /dev/null +++ b/test/data.js @@ -0,0 +1,85 @@ +// @ts-check + +const DiscordTypes = require("discord-api-types/v10") + +module.exports = { + channel: { + general: { + type: 0, + topic: 'https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:', + rate_limit_per_user: 0, + position: 0, + permission_overwrites: [], + parent_id: null, + nsfw: false, + name: 'collective-unconscious' , + last_pin_timestamp: '2023-04-06T09:51:57+00:00', + last_message_id: '1103832925784514580', + id: '112760669178241024', + default_thread_rate_limit_per_user: 0, + guild_id: '112760669178241024' + } + }, + room: { + general: { + "m.room.name/": {name: "collective-unconscious"}, + "m.room.topic/": {topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:"}, + "m.room.guest_access/": {guest_access: "can_join"}, + "m.room.history_visibility/": {history_visibility: "invited"}, + "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { + via: ["cadence.moe"], // TODO: put the proper server here + canonical: true + }, + "m.room.join_rules/": { + join_rule: "restricted", + allow: [{ + type: "m.room.membership", + room_id: "!jjWAGMeQdNrVZSSfvz:cadence.moe" + }] + }, + "m.room.avatar/": { + discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", + url: "mxc://cadence.moe/sZtPwbfOIsvfSoWCWPrGnzql" + } + } + }, + guild: { + general: { + owner_id: '112760500130975744', + premium_tier: 3, + stickers: [], + max_members: 500000, + splash: '86a34ed02524b972918bef810087f8e7', + explicit_content_filter: 0, + afk_channel_id: null, + nsfw_level: 0, + description: null, + preferred_locale: 'en-US', + system_channel_id: '112760669178241024', + mfa_level: 0, + /** @type {300} */ + afk_timeout: 300, + id: '112760669178241024', + icon: 'a_f83622e09ead74f0c5c527fe241f8f8c', + emojis: [], + premium_subscription_count: 14, + roles: [], + discovery_splash: null, + default_message_notifications: 1, + region: 'deprecated', + max_video_channel_users: 25, + verification_level: 0, + application_id: null, + premium_progress_bar_enabled: false, + banner: 'a_a666ae551605a2d8cda0afd591c0af3a', + features: [], + vanity_url_code: null, + hub_type: null, + public_updates_channel_id: null, + rules_channel_id: null, + name: 'Psychonauts 3', + max_stage_video_channel_users: 300, + system_channel_flags: 0|0 + } + } +} \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..cb6fe5d --- /dev/null +++ b/test/test.js @@ -0,0 +1,15 @@ +// @ts-check + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const config = require("../config") +const passthrough = require("../passthrough") +const db = new sqlite("db/ooye.db") + +// @ts-ignore +const sync = new HeatSync({persistent: false}) + +Object.assign(passthrough, { config, sync, db }) + +require("../d2m/actions/create-room") \ No newline at end of file From f09eeccef32d1ea8d0fe58a3ac3c905b063cd855 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 5 May 2023 17:29:08 +1200 Subject: [PATCH 004/200] add tests, implement kstate and state diffing --- d2m/actions/create-room.js | 186 ++- d2m/actions/send-message.js | 4 +- d2m/converters/message-to-event.js | 4 +- matrix/file.js | 5 +- matrix/mreq.js | 16 +- matrix/read-registration.js | 5 +- package-lock.json | 2397 +++++++++++++++++++++++++++- package.json | 3 + test/data.js | 85 + test/test.js | 15 + 10 files changed, 2656 insertions(+), 64 deletions(-) create mode 100644 test/data.js create mode 100644 test/test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index b4934dc..5af26f5 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -1,6 +1,8 @@ // @ts-check const assert = require("assert").strict +const {test} = require("supertape") +const testData = require("../../test/data") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -10,22 +12,134 @@ const mreq = sync.require("../../matrix/mreq") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +function kstateToState(kstate) { + return Object.entries(kstate).map(([k, content]) => { + console.log(k) + const [type, state_key] = k.split("/") + assert.ok(typeof type === "string") + assert.ok(typeof state_key === "string") + return {type, state_key, content} + }) +} + +test("kstate2state: general", t => { + t.deepEqual(kstateToState({ + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }), [ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]) +}) + +function diffKState(actual, target) { + const diff = {} + // go through each key that it should have + for (const key of Object.keys(target)) { + if (key in actual) { + // diff + try { + assert.deepEqual(actual[key], target[key]) + } catch (e) { + // they differ. reassign the target + diff[key] = target[key] + } + } else { + // not present, needs to be added + diff[key] = target[key] + } + } + return diff +} + +test("diffKState: detects edits", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + "same/": {a: 2} + }, { + "m.room.name/": {name: "edited name"}, + "same/": {a: 2} + }), + { + "m.room.name/": {name: "edited name"} + } + ) +}) + +test("diffKState: detects new properties", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + }, { + "m.room.name/": {name: "test name"}, + "new/": {a: 2} + }), + { + "new/": {a: 2} + } + ) +}) + /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function createRoom(channel) { - const guildID = channel.guild_id - assert.ok(guildID) - const guild = discord.guilds.get(guildID) - assert.ok(guild) - const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guildID) +async function channelToKState(channel, guild) { + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) assert.ok(typeof spaceID === "string") const avatarEventContent = {} if (guild.icon) { - avatarEventContent.url = await file.uploadDiscordFileToMxc(file.guildIcon(guild)) + avatarEventContent.discord_path = file.guildIcon(guild) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) } + const kstate = { + "m.room.name/": {name: channel.name}, + "m.room.topic/": {topic: channel.topic || undefined}, + "m.room.avatar/": avatarEventContent, + "m.room.guest_access/": {guest_access: "can_join"}, + "m.room.history_visibility/": {history_visibility: "invited"}, + [`m.space.parent/${spaceID}`]: { // TODO: put the proper server here + via: ["cadence.moe"], + canonical: true + }, + "m.room.join_rules/": { + join_rule: "restricted", + allow: [{ + type: "m.room.membership", + room_id: spaceID + }] + } + } + + return {spaceID, kstate} +} + +test("channel2room: general", async t => { + t.deepEqual(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.kstate), {expected: true, ...testData.room.general}) +}) + +/** + * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param guild + * @param {string} spaceID + * @param {any} kstate + */ +async function createRoom(channel, guild, spaceID, kstate) { /** @type {import("../../types").R_RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", { name: channel.name, @@ -33,45 +147,7 @@ async function createRoom(channel) { preset: "private_chat", visibility: "private", invite: ["@cadence:cadence.moe"], // TODO - initial_state: [ - { - type: "m.room.avatar", - state_key: "", - content: avatarEventContent - }, - { - type: "m.room.guest_access", - state_key: "", - content: { - guest_access: "can_join" - } - }, - { - type: "m.room.history_visibility", - state_key: "", - content: { - history_visibility: "invited" - } - }, - { - type: "m.space.parent", - state_key: spaceID, - content: { - via: ["cadence.moe"], // TODO: put the proper server here - canonical: true - } - }, - { - type: "m.room.join_rules", - content: { - join_rule: "restricted", - allow: [{ - type: "m.room.membership", - room_id: spaceID - }] - } - } - ] + initial_state: kstateToState(kstate) }) db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) @@ -82,6 +158,24 @@ async function createRoom(channel) { }) } +/** + * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + */ +async function syncRoom(channel) { + const guildID = channel.guild_id + assert(guildID) + const guild = discord.guilds.get(guildID) + assert(guild) + + const {spaceID, kstate} = await channelToKState(channel, guild) + + /** @type {string?} */ + const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) + if (!existing) { + createRoom(channel, guild, spaceID, kstate) + } +} + async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index b736a50..1f71a66 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -2,14 +2,14 @@ const reg = require("../../matrix/read-registration.js") const makeTxnId = require("../../matrix/txnid.js") -const fetch = require("node-fetch") +const fetch = require("node-fetch").default const messageToEvent = require("../converters/message-to-event.js") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ function sendMessage(message) { - const event = messageToEvent(message) + const event = messageToEvent.messageToEvent(message) return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { method: "PUT", body: JSON.stringify(event), diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index c9d158e..6f9bc37 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -6,7 +6,7 @@ const markdown = require("discord-markdown") * @param {import("discord-api-types/v10").APIMessage} message * @returns {import("../../types").M_Room_Message_content} */ -module.exports = function messageToEvent(message) { +function messageToEvent(message) { const body = message.content const html = markdown.toHTML(body, { /* discordCallback: { @@ -24,3 +24,5 @@ module.exports = function messageToEvent(message) { formatted_body: html } } + +module.exports.messageToEvent = messageToEvent \ No newline at end of file diff --git a/matrix/file.js b/matrix/file.js index f5e81ce..d242f52 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -1,6 +1,6 @@ // @ts-check -const fetch = require("node-fetch") +const fetch = require("node-fetch").default const passthrough = require("../passthrough") const { sync, db } = passthrough @@ -33,7 +33,6 @@ async function uploadDiscordFileToMxc(path) { // Download from Discord const promise = fetch(url, {}).then(/** @param {import("node-fetch").Response} res */ async res => { - /** @ts-ignore @type {import("stream").Readable} body */ const body = res.body // Upload to Matrix @@ -56,7 +55,7 @@ async function uploadDiscordFileToMxc(path) { } function guildIcon(guild) { - return `/icons/${guild.id}/${guild.icon}?size=${IMAGE_SIZE}` + return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } module.exports.guildIcon = guildIcon diff --git a/matrix/mreq.js b/matrix/mreq.js index ad7fa46..b3e12aa 100644 --- a/matrix/mreq.js +++ b/matrix/mreq.js @@ -1,6 +1,6 @@ // @ts-check -const fetch = require("node-fetch") +const fetch = require("node-fetch").default const mixin = require("mixin-deep") const passthrough = require("../passthrough") @@ -26,7 +26,7 @@ class MatrixServerError { * @param {any} [body] * @param {any} [extra] */ -function mreq(method, url, body, extra = {}) { +async function mreq(method, url, body, extra = {}) { const opts = mixin({ method, body: (body == undefined || Object.is(body.constructor, Object)) ? JSON.stringify(body) : body, @@ -34,13 +34,13 @@ function mreq(method, url, body, extra = {}) { Authorization: `Bearer ${reg.as_token}` } }, extra) + console.log(baseUrl + url, opts) - return fetch(baseUrl + url, opts).then(res => { - return res.json().then(root => { - if (!res.ok || root.errcode) throw new MatrixServerError(root) - return root - }) - }) + const res = await fetch(baseUrl + url, opts) + const root = await res.json() + + if (!res.ok || root.errcode) throw new MatrixServerError(root) + return root } module.exports.MatrixServerError = MatrixServerError diff --git a/matrix/read-registration.js b/matrix/read-registration.js index cc3fed8..a1d920d 100644 --- a/matrix/read-registration.js +++ b/matrix/read-registration.js @@ -3,5 +3,6 @@ const fs = require("fs") const yaml = require("js-yaml") -/** @type {import("../types").AppServiceRegistrationConfig} */ -module.exports = yaml.load(fs.readFileSync("registration.yaml", "utf8")) +/** @ts-ignore @type {import("../types").AppServiceRegistrationConfig} */ +const reg = yaml.load(fs.readFileSync("registration.yaml", "utf8")) +module.exports = reg \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 31df4c7..654d022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "out-of-your-element", "version": "1.0.0", - "lockfileVersion": 3, + "lockfileVersion": 2, "requires": true, "packages": { "": { @@ -22,7 +22,8 @@ "supertape": "^8.3.0" }, "devDependencies": { - "@types/node": "^18.16.0" + "@types/node": "^18.16.0", + "tap-dot": "github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf" } }, "node_modules/@babel/runtime": { @@ -656,6 +657,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -834,6 +841,15 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1127,6 +1143,27 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -1951,6 +1988,12 @@ "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2022,6 +2065,12 @@ "rc": "cli.js" } }, + "node_modules/re-emitter": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.4.tgz", + "integrity": "sha512-C0SIXdXDSus2yqqvV7qifnb4NoWP7mEBXJq3axci301mXHCZb8Djwm4hrEZo4UeXRaEnfjH98uQ8EBppk2oNWA==", + "dev": true + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -2276,6 +2325,18 @@ "node": ">=0.10.0" } }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/stacktracey": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", @@ -2424,6 +2485,131 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tap-dot": { + "version": "2.0.0", + "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.1", + "tap-out": "github:alandelaney-whs/tap-out", + "through2": "^2.0.0" + }, + "bin": { + "tap-dot": "bin/dot" + } + }, + "node_modules/tap-dot/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tap-dot/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tap-out": { + "name": "tap-in", + "version": "3.2.1", + "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "dev": true, + "license": "MIT", + "dependencies": { + "re-emitter": "1.1.4", + "readable-stream": "2.3.7", + "split": "1.0.1", + "trim": "1.0.1" + }, + "bin": { + "tap-in": "bin/tap-in.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tap-out/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/tap-out/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/tap-out/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/tap-out/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -2450,6 +2636,58 @@ "node": ">=6" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2463,6 +2701,13 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/trim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", + "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", + "deprecated": "Use String.prototype.trim() instead", + "dev": true + }, "node_modules/try-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", @@ -2608,6 +2853,15 @@ "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -2621,5 +2875,2144 @@ "node": ">=12" } } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@cloudcmd/stub": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", + "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.0.6", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" + }, + "jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + } + }, + "jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@matrix-org/matrix-sdk-crypto-js": { + "version": "0.1.0-alpha.7", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz", + "integrity": "sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==" + }, + "@putout/cli-keypress": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", + "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", + "requires": { + "ci-info": "^3.1.1", + "fullstore": "^3.0.0" + } + }, + "@putout/cli-validate-args": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", + "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", + "requires": { + "fastest-levenshtein": "^1.0.12", + "just-kebab-case": "^1.1.0" + } + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + }, + "@supertape/engine-loader": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", + "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", + "requires": { + "try-catch": "^3.0.0" + } + }, + "@supertape/formatter-fail": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", + "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", + "requires": { + "@supertape/formatter-tap": "^3.0.3", + "fullstore": "^3.0.0" + } + }, + "@supertape/formatter-json-lines": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", + "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", + "requires": { + "fullstore": "^3.0.0" + } + }, + "@supertape/formatter-progress-bar": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", + "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", + "requires": { + "chalk": "^4.1.0", + "ci-info": "^3.1.1", + "cli-progress": "^3.8.2", + "fullstore": "^3.0.0", + "once": "^1.4.0" + } + }, + "@supertape/formatter-short": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", + "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==" + }, + "@supertape/formatter-tap": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", + "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==" + }, + "@supertape/operator-stub": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", + "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", + "requires": { + "@cloudcmd/stub": "^4.0.0" + } + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "@types/node": { + "version": "18.16.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz", + "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "@types/react": { + "version": "18.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.38.tgz", + "integrity": "sha512-ExsidLLSzYj4cvaQjGnQCk4HFfVT9+EZ9XZsQ8Hsrcn8QNgXtpZ3m9vSIC2MWtx7jHictK6wYhQgGh6ic58oOw==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "another-json": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", + "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==" + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "requires": { + "printable-characters": "^1.0.42" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "backtracker": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.1.tgz", + "integrity": "sha512-bQTxQ/JL9nm8/mNFP/bkiOJN0w9OOK6LQDqa+Jt9YnnFGQzAplYwi2TDmzuEwHoAtuUso5StoyKvZazkPO4q4g==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "better-sqlite3": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz", + "integrity": "sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w==", + "requires": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.0" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "requires": { + "base-x": "^4.0.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "centra": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.6.0.tgz", + "integrity": "sha512-dgh+YleemrT8u85QL11Z6tYhegAs3MMxsaWAq/oXeAmYJ7VxL3SI9TZtnfaEvNDMAPolj25FXIb3S+HCI4wQaQ==" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" + }, + "cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "requires": { + "string-width": "^4.2.3" + } + }, + "cloudstorm": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.0.tgz", + "integrity": "sha512-k+1u1kTdtlz3L6lnflAKMhkkZPoBl/2Du2czNvad2pYNOMBs8e0XZpSuCazC50Q29tzi08latn4SxtLbkws50A==", + "requires": { + "snowtransfer": "0.7.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==" + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "requires": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==" + }, + "discord-api-types": { + "version": "0.37.39", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.39.tgz", + "integrity": "sha512-hkhQsQyzsTJITp311WXvHZh9j4RAMfIk2hPmsWeOTN50QTpg6zqmJNfel9D/8lYNvsU01wzw9281Yke8NhYyHg==" + }, + "discord-markdown": { + "version": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "from": "discord-markdown@git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "requires": { + "simple-markdown": "^0.7.2" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fullstore": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", + "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "requires": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + } + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "heatsync": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.0.tgz", + "integrity": "sha512-3avAZvdWohjVNhx/P1lHGEUriGP8VlbdFKrMsiBVbXzOGuEEKnC9840Qu4SyUWxgs0V1D3RIpNS3898NFgQkng==", + "requires": { + "backtracker": "3.3.1" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "just-kebab-case": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", + "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==" + }, + "loglevel": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "matrix-appservice": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-2.0.0.tgz", + "integrity": "sha512-HCIuJ5i0YuO8b0dMyGe5dqlsE4f3RzHU0MuMg/2gGAZ4HL3r7aSWOFbyIWStSSUrk1qCa9Eml0i4EnEi0pOtdA==", + "requires": { + "body-parser": "^1.19.0", + "express": "^4.18.1", + "js-yaml": "^4.1.0", + "morgan": "^1.10.0" + } + }, + "matrix-events-sdk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", + "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" + }, + "matrix-js-sdk": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-24.1.0.tgz", + "integrity": "sha512-xEx2ZoNsS56dwgqLJ3rIv2SUpFxdQLrLKmJCpMatMUKCAg+NGuZfpQ3QXblIbGaqFNQZCH7fC7S48AeTMZp1Jw==", + "requires": { + "@babel/runtime": "^7.12.5", + "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.5", + "another-json": "^0.2.0", + "bs58": "^5.0.0", + "content-type": "^1.0.4", + "loglevel": "^1.7.1", + "matrix-events-sdk": "0.0.1", + "matrix-widget-api": "^1.3.1", + "p-retry": "4", + "sdp-transform": "^2.14.1", + "unhomoglyph": "^1.0.6", + "uuid": "9" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + } + } + }, + "matrix-widget-api": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz", + "integrity": "sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==", + "requires": { + "@types/events": "^3.0.0", + "events": "^3.2.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mixin-deep": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", + "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==" + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-abi": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.40.0.tgz", + "integrity": "sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA==", + "requires": { + "semver": "^7.3.5" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + } + } + }, + "printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "re-emitter": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.4.tgz", + "integrity": "sha512-C0SIXdXDSus2yqqvV7qifnb4NoWP7mEBXJq3axci301mXHCZb8Djwm4hrEZo4UeXRaEnfjH98uQ8EBppk2oNWA==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sdp-transform": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz", + "integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==" + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "simple-markdown": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.7.3.tgz", + "integrity": "sha512-uGXIc13NGpqfPeFJIt/7SHHxd6HekEJYtsdoCM06mEBPL9fQH/pSD7LRM6PZ7CKchpSvxKL4tvwMamqAaNDAyg==", + "requires": { + "@types/react": ">=16.0.0" + } + }, + "snowtransfer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.0.tgz", + "integrity": "sha512-vc7B46tO4QeK99z/pN8ISd8QvO9QB3Oo4qP7nYYhriIMOtjYkHMi8t6kUBPIJLbeX+h0NpfwxaGJfXNLm1ZQ5A==", + "requires": { + "centra": "^2.6.0", + "discord-api-types": "^0.37.31", + "form-data": "^4.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "stacktracey": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", + "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "requires": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "requires": { + "internal-slot": "^1.0.4" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + }, + "supertape": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", + "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", + "requires": { + "@cloudcmd/stub": "^4.0.0", + "@putout/cli-keypress": "^1.0.0", + "@putout/cli-validate-args": "^1.0.1", + "@supertape/engine-loader": "^1.0.0", + "@supertape/formatter-fail": "^3.0.0", + "@supertape/formatter-json-lines": "^2.0.0", + "@supertape/formatter-progress-bar": "^3.0.0", + "@supertape/formatter-short": "^2.0.0", + "@supertape/formatter-tap": "^3.0.0", + "@supertape/operator-stub": "^3.0.0", + "cli-progress": "^3.8.2", + "deep-equal": "^2.0.3", + "fullstore": "^3.0.0", + "glob": "^8.0.3", + "jest-diff": "^29.0.1", + "once": "^1.4.0", + "resolve": "^1.17.0", + "stacktracey": "^2.1.7", + "strip-ansi": "^7.0.0", + "try-to-catch": "^3.0.0", + "wraptile": "^3.0.0", + "yargs-parser": "^21.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "tap-dot": { + "version": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "dev": true, + "from": "tap-dot@github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "requires": { + "chalk": "^1.1.1", + "tap-out": "github:alandelaney-whs/tap-out", + "through2": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "tap-out": { + "version": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "dev": true, + "from": "tap-out@github:alandelaney-whs/tap-out", + "requires": { + "re-emitter": "1.1.4", + "readable-stream": "2.3.7", + "split": "1.0.1", + "trim": "1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "trim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", + "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", + "dev": true + }, + "try-catch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", + "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==" + }, + "try-to-catch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", + "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unhomoglyph": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", + "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "wraptile": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", + "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } } } diff --git a/package.json b/package.json index 6755f9f..f8917e7 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,8 @@ }, "devDependencies": { "@types/node": "^18.16.0" + }, + "scripts": { + "test": "supertape --format short test/test.js" } } diff --git a/test/data.js b/test/data.js new file mode 100644 index 0000000..42af177 --- /dev/null +++ b/test/data.js @@ -0,0 +1,85 @@ +// @ts-check + +const DiscordTypes = require("discord-api-types/v10") + +module.exports = { + channel: { + general: { + type: 0, + topic: 'https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:', + rate_limit_per_user: 0, + position: 0, + permission_overwrites: [], + parent_id: null, + nsfw: false, + name: 'collective-unconscious' , + last_pin_timestamp: '2023-04-06T09:51:57+00:00', + last_message_id: '1103832925784514580', + id: '112760669178241024', + default_thread_rate_limit_per_user: 0, + guild_id: '112760669178241024' + } + }, + room: { + general: { + "m.room.name/": {name: "collective-unconscious"}, + "m.room.topic/": {topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:"}, + "m.room.guest_access/": {guest_access: "can_join"}, + "m.room.history_visibility/": {history_visibility: "invited"}, + "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { + via: ["cadence.moe"], // TODO: put the proper server here + canonical: true + }, + "m.room.join_rules/": { + join_rule: "restricted", + allow: [{ + type: "m.room.membership", + room_id: "!jjWAGMeQdNrVZSSfvz:cadence.moe" + }] + }, + "m.room.avatar/": { + discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", + url: "mxc://cadence.moe/sZtPwbfOIsvfSoWCWPrGnzql" + } + } + }, + guild: { + general: { + owner_id: '112760500130975744', + premium_tier: 3, + stickers: [], + max_members: 500000, + splash: '86a34ed02524b972918bef810087f8e7', + explicit_content_filter: 0, + afk_channel_id: null, + nsfw_level: 0, + description: null, + preferred_locale: 'en-US', + system_channel_id: '112760669178241024', + mfa_level: 0, + /** @type {300} */ + afk_timeout: 300, + id: '112760669178241024', + icon: 'a_f83622e09ead74f0c5c527fe241f8f8c', + emojis: [], + premium_subscription_count: 14, + roles: [], + discovery_splash: null, + default_message_notifications: 1, + region: 'deprecated', + max_video_channel_users: 25, + verification_level: 0, + application_id: null, + premium_progress_bar_enabled: false, + banner: 'a_a666ae551605a2d8cda0afd591c0af3a', + features: [], + vanity_url_code: null, + hub_type: null, + public_updates_channel_id: null, + rules_channel_id: null, + name: 'Psychonauts 3', + max_stage_video_channel_users: 300, + system_channel_flags: 0|0 + } + } +} \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..cb6fe5d --- /dev/null +++ b/test/test.js @@ -0,0 +1,15 @@ +// @ts-check + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const config = require("../config") +const passthrough = require("../passthrough") +const db = new sqlite("db/ooye.db") + +// @ts-ignore +const sync = new HeatSync({persistent: false}) + +Object.assign(passthrough, { config, sync, db }) + +require("../d2m/actions/create-room") \ No newline at end of file From 9395a85e9b4ddcb19c33afa070b43521d81b10b4 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 6 May 2023 01:25:15 +1200 Subject: [PATCH 005/200] Finish room diffing and syncing. All tests pass --- d2m/actions/create-room.js | 170 ++++--- d2m/actions/create-room.test.js | 83 ++++ d2m/actions/create-space.js | 2 +- d2m/actions/register-user.js | 2 +- db/ooye.db | Bin 40960 -> 49152 bytes index.js | 3 - matrix/file.js | 2 +- matrix/mreq.js | 5 +- package-lock.json | 857 ++++++++++++++++---------------- package.json | 10 +- stdin.js | 2 +- test/data.js | 4 +- test/test.js | 2 +- types.d.ts | 45 +- 14 files changed, 658 insertions(+), 529 deletions(-) create mode 100644 d2m/actions/create-room.test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 5af26f5..214d026 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -1,8 +1,6 @@ // @ts-check const assert = require("assert").strict -const {test} = require("supertape") -const testData = require("../../test/data") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -12,37 +10,62 @@ const mreq = sync.require("../../matrix/mreq") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +function kstateStripConditionals(kstate) { + for (const [k, content] of Object.entries(kstate)) { + if ("$if" in content) { + if (content.$if) delete content.$if + else delete kstate[k] + } + } + return kstate +} + function kstateToState(kstate) { - return Object.entries(kstate).map(([k, content]) => { - console.log(k) + const events = [] + for (const [k, content] of Object.entries(kstate)) { + // conditional for whether a key is even part of the kstate (doing this declaratively on json is hard, so represent it as a property instead.) + if ("$if" in content && !content.$if) continue + delete content.$if + const [type, state_key] = k.split("/") assert.ok(typeof type === "string") assert.ok(typeof state_key === "string") - return {type, state_key, content} - }) + events.push({type, state_key, content}) + } + return events } -test("kstate2state: general", t => { - t.deepEqual(kstateToState({ - "m.room.name/": {name: "test name"}, - "m.room.member/@cadence:cadence.moe": {membership: "join"} - }), [ - { - type: "m.room.name", - state_key: "", - content: { - name: "test name" - } - }, - { - type: "m.room.member", - state_key: "@cadence:cadence.moe", - content: { - membership: "join" - } - } - ]) -}) +/** + * @param {import("../../types").Event.BaseStateEvent[]} events + * @returns {any} + */ +function stateToKState(events) { + const kstate = {} + for (const event of events) { + kstate[event.type + "/" + event.state_key] = event.content + } + return kstate +} + +/** + * @param {string} roomID + */ +async function roomToKState(roomID) { + /** @type {import("../../types").Event.BaseStateEvent[]} */ + const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) + return stateToKState(root) +} + +/** + * @params {string} roomID + * @params {any} kstate + */ +function applyKStateDiffToRoom(roomID, kstate) { + const events = kstateToState(kstate) + return Promise.all(events.map(({type, state_key, content}) => + mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${state_key}`, content) + )) +} function diffKState(actual, target) { const diff = {} @@ -60,39 +83,11 @@ function diffKState(actual, target) { // not present, needs to be added diff[key] = target[key] } + // keys that are missing in "actual" will not be deleted on "target" (no action) } return diff } -test("diffKState: detects edits", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - "same/": {a: 2} - }, { - "m.room.name/": {name: "edited name"}, - "same/": {a: 2} - }), - { - "m.room.name/": {name: "edited name"} - } - ) -}) - -test("diffKState: detects new properties", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - }, { - "m.room.name/": {name: "test name"}, - "new/": {a: 2} - }), - { - "new/": {a: 2} - } - ) -}) - /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param {import("discord-api-types/v10").APIGuild} guild @@ -107,14 +102,14 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) } - const kstate = { + const channelKState = { "m.room.name/": {name: channel.name}, - "m.room.topic/": {topic: channel.topic || undefined}, + "m.room.topic/": {$if: channel.topic, topic: channel.topic}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, - [`m.space.parent/${spaceID}`]: { // TODO: put the proper server here - via: ["cadence.moe"], + [`m.space.parent/${spaceID}`]: { + via: ["cadence.moe"], // TODO: put the proper server here canonical: true }, "m.room.join_rules/": { @@ -126,13 +121,9 @@ async function channelToKState(channel, guild) { } } - return {spaceID, kstate} + return {spaceID, channelKState} } -test("channel2room: general", async t => { - t.deepEqual(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.kstate), {expected: true, ...testData.room.general}) -}) - /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param guild @@ -140,7 +131,7 @@ test("channel2room: general", async t => { * @param {any} kstate */ async function createRoom(channel, guild, spaceID, kstate) { - /** @type {import("../../types").R_RoomCreated} */ + /** @type {import("../../types").R.RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", { name: channel.name, topic: channel.topic || undefined, @@ -159,20 +150,46 @@ async function createRoom(channel, guild, spaceID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param {import("discord-api-types/v10").APIGuildChannel} channel */ -async function syncRoom(channel) { +function channelToGuild(channel) { const guildID = channel.guild_id assert(guildID) const guild = discord.guilds.get(guildID) assert(guild) + return guild +} - const {spaceID, kstate} = await channelToKState(channel, guild) +/** + * @param {string} channelID + */ +async function syncRoom(channelID) { + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = discord.channels.get(channelID) + assert.ok(channel) + const guild = channelToGuild(channel) + + const {spaceID, channelKState} = await channelToKState(channel, guild) /** @type {string?} */ const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) if (!existing) { - createRoom(channel, guild, spaceID, kstate) + return createRoom(channel, guild, spaceID, channelKState) + } else { + // sync channel state to room + const roomKState = await roomToKState(existing) + const roomDiff = diffKState(roomKState, channelKState) + const roomApply = applyKStateDiffToRoom(existing, roomDiff) + + // sync room as space member + const spaceKState = await roomToKState(spaceID) + const spaceDiff = diffKState(spaceKState, { + [`m.space.child/${existing}`]: { + via: ["cadence.moe"] // TODO: use the proper server + } + }) + const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff) + return Promise.all([roomApply, spaceApply]) } } @@ -180,14 +197,15 @@ async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) for (const channelID of channelIDs) { - const channel = discord.channels.get(channelID) - assert.ok(channel) - const existing = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) - if (channel.type === DiscordTypes.ChannelType.GuildText && !existing) { - await createRoom(channel) - } + await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) } } module.exports.createRoom = createRoom +module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild +module.exports.kstateToState = kstateToState +module.exports.stateToKState = stateToKState +module.exports.diffKState = diffKState +module.exports.channelToKState = channelToKState +module.exports.kstateStripConditionals = kstateStripConditionals diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js new file mode 100644 index 0000000..5ce52e8 --- /dev/null +++ b/d2m/actions/create-room.test.js @@ -0,0 +1,83 @@ +const {kstateToState, stateToKState, diffKState, channelToKState, kstateStripConditionals} = require("./create-room") +const {test} = require("supertape") +const testData = require("../../test/data") + +test("kstate2state: general", t => { + t.deepEqual(kstateToState({ + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }), [ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]) +}) + +test("state2kstate: general", t => { + t.deepEqual(stateToKState([ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]), { + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }) +}) + +test("diffKState: detects edits", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + "same/": {a: 2} + }, { + "m.room.name/": {name: "edited name"}, + "same/": {a: 2} + }), + { + "m.room.name/": {name: "edited name"} + } + ) +}) + +test("diffKState: detects new properties", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + }, { + "m.room.name/": {name: "test name"}, + "new/": {a: 2} + }), + { + "new/": {a: 2} + } + ) +}) + +test("channel2room: general", async t => { + t.deepEqual( + kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), + testData.room.general + ) +}) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 6d3c327..b3d7e95 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -37,7 +37,7 @@ function createSpace(guild) { } } ] - }).then(/** @param {import("../../types").R_RoomCreated} root */ root => { + }).then(/** @param {import("../../types").R.RoomCreated} root */ root => { db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) return root }) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 14dda55..325a3d0 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -1,7 +1,7 @@ // @ts-check const reg = require("../../matrix/read-registration.js") -const fetch = require("node-fetch") +const fetch = require("node-fetch").default fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { method: "POST", diff --git a/db/ooye.db b/db/ooye.db index dea5853e5cb352179d82280dc355fe84c149f6d0..10a99ddd7695a2831d809699bba20bc3ac701d53 100644 GIT binary patch delta 2972 zcmeH}TWB0r7{~XLz06)_nu@ieV1`<13rXj4&Rhy6+1>1QcCy)|+1)HC*;~@RWp}f? z-6W!uJm{OoxGkj?p;81v5K8pH2k`=es1;Q7p;}6PQz+O+>5FHEChlZ$`{q;UVPHP~ zzw>cs{^$SA=I5r(P4k;iSrP_=!L>6oVN6_Kyh`8}^k^jRqr1UH-0$LtT1QNOn08Gs zn}VicA~Ry-Y9T8iX+@sivK>ZkysSdM}m zY9lZV;DEx@oIozDOxMc3`l>V&suYtY>Fl}=^CL{47>;HH5nU9^iX4FCbfQ^HFUd3B zR@ApqnU8z2I%;aa83#FrgAB{CA|(CkYGGq#y(D!!Id4eG&M&2Om=9q*FVF%@K?X83 zx}eCA=bE1AxwT9wnme1%m!;giUq^Wn#qgpiKtW(3r@j*wvShNoP)!w^?T&wWb*(;E z&L?%0$E+YsguEaEnie2OL%=bBoXtjOrC6z&@n@E%S}~6&qQfTlcE;1HoMl9o7FBAs zxRRf1$0}J*$P<|jG<4Jiq6D-j^cqE4qy(BmUo63pGoGN_sMq7+ax^nN+bP$lb=bJt zjDZ4T7}SpCS%Kw5GTe+v>1w7^Y?TA;)m*8w&`a$;^Vn}L0vXU5a-h469X4Q-UHb@i z#&O8~m-AW23Fj?G#Wmyj+Wi6k84ew9W4~h`VJESgV;oC4HyyWK_lQ5;7x4F;AG*IJ z-XIpS*Sar`96f^ljUC3mb<7h#;XC+UqT>>YF!s*%t-4(~Vy2DAC4ixL9tx_<1d0c; zTGl)VX}}8*09F7r6Me$W8fl8<1VHgfEFx>LlmMiCIS(j`fdH~R&xn!zGK3;Wvn+BG zfMQrJKR7%}&*=%N_5R}#++Sti|Em21YPsLv{(sfI2A)#O{RZ`E`|r1)SC#i$%(K6^ z&O?)Gxz8lbgr@E@!MIwc8HR?C6?V5zwc)gE88z6iV;;+8><4SZwcr$OAG^OGZrOGm zPg*UuFy1C)H*J62DqB9a9d`{oUbX-1ylWYCy=%Q?Gg{iNubhwDNy{DUCw9N}WS3hU zF_A zn<>v_n+vHxBVC?N_`+$qR}i`Y1e@gj1U4 zVD2gU;PYvs!7=C6G=nqE^FT8w!6sGB)I;)WLR)BXI*spEmoM4vCj6#>sNpwvhpF#z z+ty*z(AIpWdv!Z8G*(|(?tZ5{eRM-g1=HuM;i?>J2jl5v#2=6%*=C?yQ!d&~1Z^N_ z;tJxA6P6;PjVev3^hC3`QFxw4ab)UV*)AgOH3RV@aSdrN5$u>bW2~;_&YV8oGgD9h zS2NS7IfHe7QBK+1C`&_z8(rI>mmZDuM#h0_Zo%&vh;N7s#0$g;{2ns& zZCnAe_H%J?_&y}3L3Hm&MmL+u!O0CJGb`ywx1!B%a&Vf1$X++yXeaggelXrac#qOe Meo`0s!+nYW4{+y9<^TWy delta 315 zcmV-B0mS}*fCGTQ0+1U8MF0Q*3Xvc`0Y$N3pbriW0000z10Mhn$`6zeUXuQ(f|+Avk^eZ N4+Jd(VF{Dge=4nHTgm_c diff --git a/index.js b/index.js index d1c721c..807b49f 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,3 @@ passthrough.discord = discord require("./stdin") })() - -// process.on("unhandledRejection", console.error) -// process.on("uncaughtException", console.error) diff --git a/matrix/file.js b/matrix/file.js index d242f52..137a096 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -36,7 +36,7 @@ async function uploadDiscordFileToMxc(path) { const body = res.body // Upload to Matrix - /** @type {import("../types").R_FileUploaded} */ + /** @type {import("../types").R.FileUploaded} */ const root = await mreq.mreq("POST", "/media/v3/upload", body, { headers: { "Content-Type": res.headers.get("content-type") diff --git a/matrix/mreq.js b/matrix/mreq.js index b3e12aa..1345f78 100644 --- a/matrix/mreq.js +++ b/matrix/mreq.js @@ -10,8 +10,9 @@ const reg = sync.require("./read-registration.js") const baseUrl = "https://matrix.cadence.moe/_matrix" -class MatrixServerError { +class MatrixServerError extends Error { constructor(data) { + super(data.error || data.errcode) this.data = data /** @type {string} */ this.errcode = data.errcode @@ -35,7 +36,7 @@ async function mreq(method, url, body, extra = {}) { } }, extra) - console.log(baseUrl + url, opts) + // console.log(baseUrl + url, opts) const res = await fetch(baseUrl + url, opts) const root = await res.json() diff --git a/package-lock.json b/package-lock.json index 654d022..b845abf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,13 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", - "supertape": "^8.3.0" + "snowtransfer": "^0.7.0" }, "devDependencies": { "@types/node": "^18.16.0", - "tap-dot": "github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf" + "@types/node-fetch": "^2.6.3", + "supertape": "^8.3.0", + "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" } }, "node_modules/@babel/runtime": { @@ -41,6 +42,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", + "dev": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^27.0.6", @@ -54,6 +56,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -62,6 +65,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -73,6 +77,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } @@ -81,6 +86,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^27.5.1", @@ -95,6 +101,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } @@ -103,6 +110,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -115,12 +123,14 @@ "node_modules/@cloudcmd/stub/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "node_modules/@cloudcmd/stub/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -132,6 +142,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, "dependencies": { "@sinclair/typebox": "^0.25.16" }, @@ -151,6 +162,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", + "dev": true, "dependencies": { "ci-info": "^3.1.1", "fullstore": "^3.0.0" @@ -163,6 +175,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", + "dev": true, "dependencies": { "fastest-levenshtein": "^1.0.12", "just-kebab-case": "^1.1.0" @@ -174,12 +187,14 @@ "node_modules/@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true }, "node_modules/@supertape/engine-loader": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", + "dev": true, "dependencies": { "try-catch": "^3.0.0" }, @@ -191,6 +206,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", + "dev": true, "dependencies": { "@supertape/formatter-tap": "^3.0.3", "fullstore": "^3.0.0" @@ -203,6 +219,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", + "dev": true, "dependencies": { "fullstore": "^3.0.0" }, @@ -214,6 +231,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", + "dev": true, "dependencies": { "chalk": "^4.1.0", "ci-info": "^3.1.1", @@ -229,6 +247,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==", + "dev": true, "engines": { "node": ">=16" } @@ -237,6 +256,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==", + "dev": true, "engines": { "node": ">=16" } @@ -245,6 +265,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", + "dev": true, "dependencies": { "@cloudcmd/stub": "^4.0.0" }, @@ -263,6 +284,30 @@ "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", + "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -288,6 +333,18 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -309,6 +366,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, "engines": { "node": ">=12" }, @@ -320,6 +378,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -344,6 +403,7 @@ "version": "1.0.55", "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, "dependencies": { "printable-characters": "^1.0.42" } @@ -357,6 +417,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -372,7 +433,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base-x": { "version": "4.0.0", @@ -482,6 +544,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -546,6 +609,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -566,6 +630,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, "funding": [ { "type": "github", @@ -580,6 +645,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, "dependencies": { "string-width": "^4.2.3" }, @@ -602,6 +668,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -612,7 +679,14 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -657,12 +731,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -671,7 +739,8 @@ "node_modules/data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==" + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true }, "node_modules/decompress-response": { "version": "6.0.0", @@ -691,6 +760,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-get-iterator": "^1.1.2", @@ -726,6 +796,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -774,6 +845,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -799,7 +871,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/encodeurl": { "version": "1.0.2", @@ -821,6 +894,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -841,15 +915,6 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -858,6 +923,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -969,6 +1043,7 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, "engines": { "node": ">= 4.9.1" } @@ -1012,6 +1087,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -1053,12 +1129,14 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fullstore": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==", + "dev": true, "engines": { "node": ">=4" } @@ -1072,6 +1150,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1093,6 +1172,7 @@ "version": "2.0.12", "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" @@ -1107,6 +1187,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1125,6 +1206,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -1143,31 +1225,11 @@ "node": ">= 0.4.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1176,6 +1238,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -1184,6 +1247,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -1206,6 +1270,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -1273,6 +1338,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1292,6 +1358,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -1313,6 +1380,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -1328,6 +1396,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -1341,6 +1410,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -1352,6 +1422,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -1367,6 +1438,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -1378,6 +1450,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -1389,6 +1462,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1403,6 +1477,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -1411,6 +1486,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1419,6 +1495,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1433,6 +1510,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -1448,6 +1526,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1456,6 +1535,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -1467,6 +1547,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1481,6 +1562,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -1495,6 +1577,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -1513,6 +1596,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1521,6 +1605,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -1532,12 +1617,14 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", @@ -1552,6 +1639,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -1570,7 +1658,8 @@ "node_modules/just-kebab-case": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", - "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==" + "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", + "dev": true }, "node_modules/loglevel": { "version": "1.8.1", @@ -1719,6 +1808,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1841,6 +1931,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -1856,6 +1947,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -1864,6 +1956,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -1927,7 +2020,8 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-to-regexp": { "version": "0.1.7", @@ -1963,6 +2057,7 @@ "version": "29.5.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, "dependencies": { "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", @@ -1976,6 +2071,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -1986,14 +2082,18 @@ "node_modules/printable-characters": { "version": "1.0.42", "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", "dev": true }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2074,7 +2174,8 @@ "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, "node_modules/readable-stream": { "version": "3.6.2", @@ -2098,6 +2199,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -2114,6 +2216,7 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -2321,6 +2424,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2341,6 +2445,7 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" @@ -2358,6 +2463,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, "dependencies": { "internal-slot": "^1.0.4" }, @@ -2377,6 +2483,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2390,6 +2497,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -2398,6 +2506,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2409,6 +2518,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -2431,6 +2541,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", + "dev": true, "dependencies": { "@cloudcmd/stub": "^4.0.0", "@putout/cli-keypress": "^1.0.0", @@ -2467,6 +2578,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2478,6 +2590,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -2487,85 +2600,28 @@ }, "node_modules/tap-dot": { "version": "2.0.0", - "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", - "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", + "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^1.1.1", - "tap-out": "github:alandelaney-whs/tap-out", - "through2": "^2.0.0" + "colorette": "^1.0.5", + "tap-out": "github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771" }, "bin": { "tap-dot": "bin/dot" } }, - "node_modules/tap-dot/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/tap-out": { - "name": "tap-in", "version": "3.2.1", - "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "resolved": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", + "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", "dev": true, "license": "MIT", "dependencies": { "re-emitter": "1.1.4", - "readable-stream": "2.3.7", - "split": "1.0.1", - "trim": "1.0.1" + "readable-stream": "^4.3.0", + "split": "^1.0.1" }, "bin": { "tap-in": "bin/tap-in.js" @@ -2574,40 +2630,43 @@ "node": ">=8.0.0" } }, - "node_modules/tap-out/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/tap-out/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "node_modules/tap-out/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/tap-out/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/tap-out/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/tap-out/node_modules/readable-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/tar-fs": { @@ -2642,52 +2701,6 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/through2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2701,17 +2714,11 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/trim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", - "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", - "deprecated": "Use String.prototype.trim() instead", - "dev": true - }, "node_modules/try-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==", + "dev": true, "engines": { "node": ">=6" } @@ -2720,6 +2727,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", + "dev": true, "engines": { "node": ">=6" } @@ -2799,6 +2807,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -2814,6 +2823,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "dependencies": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -2828,6 +2838,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -2851,16 +2862,8 @@ "node_modules/wraptile": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", - "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } + "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", + "dev": true }, "node_modules/yallist": { "version": "4.0.0", @@ -2871,6 +2874,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "engines": { "node": ">=12" } @@ -2889,6 +2893,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", + "dev": true, "requires": { "chalk": "^4.0.0", "jest-diff": "^27.0.6", @@ -2898,22 +2903,26 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true }, "jest-diff": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^27.5.1", @@ -2924,12 +2933,14 @@ "jest-get-type": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true }, "pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "requires": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -2939,12 +2950,14 @@ "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -2955,6 +2968,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, "requires": { "@sinclair/typebox": "^0.25.16" } @@ -2968,6 +2982,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", + "dev": true, "requires": { "ci-info": "^3.1.1", "fullstore": "^3.0.0" @@ -2977,6 +2992,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", + "dev": true, "requires": { "fastest-levenshtein": "^1.0.12", "just-kebab-case": "^1.1.0" @@ -2985,12 +3001,14 @@ "@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true }, "@supertape/engine-loader": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", + "dev": true, "requires": { "try-catch": "^3.0.0" } @@ -2999,6 +3017,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", + "dev": true, "requires": { "@supertape/formatter-tap": "^3.0.3", "fullstore": "^3.0.0" @@ -3008,6 +3027,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", + "dev": true, "requires": { "fullstore": "^3.0.0" } @@ -3016,6 +3036,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", + "dev": true, "requires": { "chalk": "^4.1.0", "ci-info": "^3.1.1", @@ -3027,17 +3048,20 @@ "@supertape/formatter-short": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", - "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==" + "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==", + "dev": true }, "@supertape/formatter-tap": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", - "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==" + "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==", + "dev": true }, "@supertape/operator-stub": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", + "dev": true, "requires": { "@cloudcmd/stub": "^4.0.0" } @@ -3053,6 +3077,29 @@ "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", "dev": true }, + "@types/node-fetch": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", + "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -3078,6 +3125,15 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3095,12 +3151,14 @@ "ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -3119,6 +3177,7 @@ "version": "1.0.55", "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, "requires": { "printable-characters": "^1.0.42" } @@ -3131,7 +3190,8 @@ "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true }, "backtracker": { "version": "3.3.1", @@ -3141,7 +3201,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "base-x": { "version": "4.0.0", @@ -3233,6 +3294,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "requires": { "balanced-match": "^1.0.0" } @@ -3277,6 +3339,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3290,12 +3353,14 @@ "ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true }, "cli-progress": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, "requires": { "string-width": "^4.2.3" } @@ -3312,6 +3377,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -3319,7 +3385,14 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true }, "combined-stream": { "version": "1.0.8", @@ -3352,12 +3425,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, "csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -3366,7 +3433,8 @@ "data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==" + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true }, "decompress-response": { "version": "6.0.0", @@ -3380,6 +3448,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "es-get-iterator": "^1.1.2", @@ -3409,6 +3478,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -3437,7 +3507,8 @@ "diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==" + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true }, "discord-api-types": { "version": "0.37.39", @@ -3459,7 +3530,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "encodeurl": { "version": "1.0.2", @@ -3478,6 +3550,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -3495,17 +3568,17 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -3602,7 +3675,8 @@ "fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==" + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true }, "file-uri-to-path": { "version": "1.0.0", @@ -3642,6 +3716,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -3674,12 +3749,14 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "fullstore": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", - "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==" + "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==", + "dev": true }, "function-bind": { "version": "1.1.1", @@ -3689,7 +3766,8 @@ "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true }, "get-intrinsic": { "version": "1.2.0", @@ -3705,6 +3783,7 @@ "version": "2.0.12", "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, "requires": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" @@ -3719,6 +3798,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3731,6 +3811,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -3743,37 +3824,23 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - } - } - }, "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "has-property-descriptors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -3787,6 +3854,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -3828,6 +3896,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3847,6 +3916,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "requires": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -3862,6 +3932,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3871,6 +3942,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -3881,6 +3953,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "requires": { "has-bigints": "^1.0.1" } @@ -3889,6 +3962,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3897,12 +3971,14 @@ "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true }, "is-core-module": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, "requires": { "has": "^1.0.3" } @@ -3911,6 +3987,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -3918,17 +3995,20 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true }, "is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -3937,6 +4017,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3945,12 +4026,14 @@ "is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true }, "is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -3959,6 +4042,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -3967,6 +4051,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -3975,6 +4060,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -3986,12 +4072,14 @@ "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true }, "is-weakset": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -4000,12 +4088,14 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", @@ -4016,7 +4106,8 @@ "jest-get-type": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==" + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true }, "js-yaml": { "version": "4.1.0", @@ -4029,7 +4120,8 @@ "just-kebab-case": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", - "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==" + "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", + "dev": true }, "loglevel": { "version": "1.8.1", @@ -4137,6 +4229,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -4226,6 +4319,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -4234,12 +4328,14 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -4285,7 +4381,8 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "path-to-regexp": { "version": "0.1.7", @@ -4315,6 +4412,7 @@ "version": "29.5.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, "requires": { "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", @@ -4324,19 +4422,21 @@ "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true } } }, "printable-characters": { "version": "1.0.42", "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, "proxy-addr": { @@ -4401,7 +4501,8 @@ "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, "readable-stream": { "version": "3.6.2", @@ -4422,6 +4523,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -4432,6 +4534,7 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, "requires": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -4570,7 +4673,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "split": { "version": "1.0.1", @@ -4585,6 +4689,7 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, "requires": { "as-table": "^1.0.36", "get-source": "^2.0.12" @@ -4599,6 +4704,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, "requires": { "internal-slot": "^1.0.4" } @@ -4615,6 +4721,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4624,12 +4731,14 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -4640,6 +4749,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, "requires": { "ansi-regex": "^6.0.1" } @@ -4653,6 +4763,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", + "dev": true, "requires": { "@cloudcmd/stub": "^4.0.0", "@putout/cli-keypress": "^1.0.0", @@ -4682,6 +4793,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -4689,106 +4801,50 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true }, "tap-dot": { - "version": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", - "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "version": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", + "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", "dev": true, - "from": "tap-dot@github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "from": "tap-dot@github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4", "requires": { - "chalk": "^1.1.1", - "tap-out": "github:alandelaney-whs/tap-out", - "through2": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - } + "colorette": "^1.0.5", + "tap-out": "github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771" } }, "tap-out": { - "version": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "version": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", + "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", "dev": true, - "from": "tap-out@github:alandelaney-whs/tap-out", + "from": "tap-out@github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", "requires": { "re-emitter": "1.1.4", - "readable-stream": "2.3.7", - "split": "1.0.1", - "trim": "1.0.1" + "readable-stream": "^4.3.0", + "split": "^1.0.1" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "readable-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" } } } @@ -4822,54 +4878,6 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4880,21 +4888,17 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "trim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", - "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", - "dev": true - }, "try-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", - "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==" + "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==", + "dev": true }, "try-to-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", - "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==" + "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", + "dev": true }, "tunnel-agent": { "version": "0.6.0", @@ -4956,6 +4960,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "requires": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -4968,6 +4973,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "requires": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -4979,6 +4985,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -4996,12 +5003,7 @@ "wraptile": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", - "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", "dev": true }, "yallist": { @@ -5012,7 +5014,8 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true } } } diff --git a/package.json b/package.json index f8917e7..e3ca70f 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,15 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", - "supertape": "^8.3.0" + "snowtransfer": "^0.7.0" }, "devDependencies": { - "@types/node": "^18.16.0" + "@types/node": "^18.16.0", + "@types/node-fetch": "^2.6.3", + "supertape": "^8.3.0", + "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "supertape --format short test/test.js" + "test": "FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" } } diff --git a/stdin.js b/stdin.js index fb95809..a57c044 100644 --- a/stdin.js +++ b/stdin.js @@ -43,7 +43,7 @@ async function customEval(input, _context, _filename, callback) { const output = util.inspect(result, false, depth, true) return callback(null, output) } catch (e) { - return callback(null, util.inspect(e, true, 100, true)) + return callback(null, util.inspect(e, false, 100, true)) } } diff --git a/test/data.js b/test/data.js index 42af177..e6a49c8 100644 --- a/test/data.js +++ b/test/data.js @@ -39,7 +39,7 @@ module.exports = { }, "m.room.avatar/": { discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", - url: "mxc://cadence.moe/sZtPwbfOIsvfSoWCWPrGnzql" + url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF" } } }, @@ -82,4 +82,4 @@ module.exports = { system_channel_flags: 0|0 } } -} \ No newline at end of file +} diff --git a/test/test.js b/test/test.js index cb6fe5d..de399f3 100644 --- a/test/test.js +++ b/test/test.js @@ -12,4 +12,4 @@ const sync = new HeatSync({persistent: false}) Object.assign(passthrough, { config, sync, db }) -require("../d2m/actions/create-room") \ No newline at end of file +require("../d2m/actions/create-room.test") diff --git a/types.d.ts b/types.d.ts index d571359..1f00836 100644 --- a/types.d.ts +++ b/types.d.ts @@ -8,17 +8,42 @@ export type AppServiceRegistrationConfig = { rate_limited: boolean } -export type M_Room_Message_content = { - msgtype: "m.text" - body: string - formatted_body?: "org.matrix.custom.html" - format?: string +namespace Event { + export type BaseStateEvent = { + type: string + room_id: string + sender: string + content: any + state_key: string + origin_server_ts: number + unsigned: any + event_id: string + user_id: string + age: number + replaces_state: string + prev_content?: any + } + + export type M_Room_Message = { + msgtype: "m.text" + body: string + formatted_body?: "org.matrix.custom.html" + format?: string + } + + export type M_Room_Member = { + membership: string + display_name?: string + avatar_url?: string + } } -export type R_RoomCreated = { - room_id: string -} +namespace R { + export type RoomCreated = { + room_id: string + } -export type R_FileUploaded = { - content_uri: string + export type FileUploaded = { + content_uri: string + } } From 3fbe7eed6e83732e2cc0c0d66ef133deb2f6f821 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 6 May 2023 01:25:15 +1200 Subject: [PATCH 006/200] Finish room diffing and syncing. All tests pass --- d2m/actions/create-room.js | 170 ++++--- d2m/actions/create-room.test.js | 83 ++++ d2m/actions/create-space.js | 2 +- d2m/actions/register-user.js | 2 +- index.js | 3 - matrix/file.js | 2 +- matrix/mreq.js | 5 +- package-lock.json | 857 ++++++++++++++++---------------- package.json | 10 +- stdin.js | 2 +- test/data.js | 4 +- test/test.js | 2 +- types.d.ts | 45 +- 13 files changed, 658 insertions(+), 529 deletions(-) create mode 100644 d2m/actions/create-room.test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 5af26f5..214d026 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -1,8 +1,6 @@ // @ts-check const assert = require("assert").strict -const {test} = require("supertape") -const testData = require("../../test/data") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -12,37 +10,62 @@ const mreq = sync.require("../../matrix/mreq") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +function kstateStripConditionals(kstate) { + for (const [k, content] of Object.entries(kstate)) { + if ("$if" in content) { + if (content.$if) delete content.$if + else delete kstate[k] + } + } + return kstate +} + function kstateToState(kstate) { - return Object.entries(kstate).map(([k, content]) => { - console.log(k) + const events = [] + for (const [k, content] of Object.entries(kstate)) { + // conditional for whether a key is even part of the kstate (doing this declaratively on json is hard, so represent it as a property instead.) + if ("$if" in content && !content.$if) continue + delete content.$if + const [type, state_key] = k.split("/") assert.ok(typeof type === "string") assert.ok(typeof state_key === "string") - return {type, state_key, content} - }) + events.push({type, state_key, content}) + } + return events } -test("kstate2state: general", t => { - t.deepEqual(kstateToState({ - "m.room.name/": {name: "test name"}, - "m.room.member/@cadence:cadence.moe": {membership: "join"} - }), [ - { - type: "m.room.name", - state_key: "", - content: { - name: "test name" - } - }, - { - type: "m.room.member", - state_key: "@cadence:cadence.moe", - content: { - membership: "join" - } - } - ]) -}) +/** + * @param {import("../../types").Event.BaseStateEvent[]} events + * @returns {any} + */ +function stateToKState(events) { + const kstate = {} + for (const event of events) { + kstate[event.type + "/" + event.state_key] = event.content + } + return kstate +} + +/** + * @param {string} roomID + */ +async function roomToKState(roomID) { + /** @type {import("../../types").Event.BaseStateEvent[]} */ + const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) + return stateToKState(root) +} + +/** + * @params {string} roomID + * @params {any} kstate + */ +function applyKStateDiffToRoom(roomID, kstate) { + const events = kstateToState(kstate) + return Promise.all(events.map(({type, state_key, content}) => + mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${state_key}`, content) + )) +} function diffKState(actual, target) { const diff = {} @@ -60,39 +83,11 @@ function diffKState(actual, target) { // not present, needs to be added diff[key] = target[key] } + // keys that are missing in "actual" will not be deleted on "target" (no action) } return diff } -test("diffKState: detects edits", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - "same/": {a: 2} - }, { - "m.room.name/": {name: "edited name"}, - "same/": {a: 2} - }), - { - "m.room.name/": {name: "edited name"} - } - ) -}) - -test("diffKState: detects new properties", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - }, { - "m.room.name/": {name: "test name"}, - "new/": {a: 2} - }), - { - "new/": {a: 2} - } - ) -}) - /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param {import("discord-api-types/v10").APIGuild} guild @@ -107,14 +102,14 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) } - const kstate = { + const channelKState = { "m.room.name/": {name: channel.name}, - "m.room.topic/": {topic: channel.topic || undefined}, + "m.room.topic/": {$if: channel.topic, topic: channel.topic}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, - [`m.space.parent/${spaceID}`]: { // TODO: put the proper server here - via: ["cadence.moe"], + [`m.space.parent/${spaceID}`]: { + via: ["cadence.moe"], // TODO: put the proper server here canonical: true }, "m.room.join_rules/": { @@ -126,13 +121,9 @@ async function channelToKState(channel, guild) { } } - return {spaceID, kstate} + return {spaceID, channelKState} } -test("channel2room: general", async t => { - t.deepEqual(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.kstate), {expected: true, ...testData.room.general}) -}) - /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param guild @@ -140,7 +131,7 @@ test("channel2room: general", async t => { * @param {any} kstate */ async function createRoom(channel, guild, spaceID, kstate) { - /** @type {import("../../types").R_RoomCreated} */ + /** @type {import("../../types").R.RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", { name: channel.name, topic: channel.topic || undefined, @@ -159,20 +150,46 @@ async function createRoom(channel, guild, spaceID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param {import("discord-api-types/v10").APIGuildChannel} channel */ -async function syncRoom(channel) { +function channelToGuild(channel) { const guildID = channel.guild_id assert(guildID) const guild = discord.guilds.get(guildID) assert(guild) + return guild +} - const {spaceID, kstate} = await channelToKState(channel, guild) +/** + * @param {string} channelID + */ +async function syncRoom(channelID) { + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = discord.channels.get(channelID) + assert.ok(channel) + const guild = channelToGuild(channel) + + const {spaceID, channelKState} = await channelToKState(channel, guild) /** @type {string?} */ const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) if (!existing) { - createRoom(channel, guild, spaceID, kstate) + return createRoom(channel, guild, spaceID, channelKState) + } else { + // sync channel state to room + const roomKState = await roomToKState(existing) + const roomDiff = diffKState(roomKState, channelKState) + const roomApply = applyKStateDiffToRoom(existing, roomDiff) + + // sync room as space member + const spaceKState = await roomToKState(spaceID) + const spaceDiff = diffKState(spaceKState, { + [`m.space.child/${existing}`]: { + via: ["cadence.moe"] // TODO: use the proper server + } + }) + const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff) + return Promise.all([roomApply, spaceApply]) } } @@ -180,14 +197,15 @@ async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) for (const channelID of channelIDs) { - const channel = discord.channels.get(channelID) - assert.ok(channel) - const existing = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) - if (channel.type === DiscordTypes.ChannelType.GuildText && !existing) { - await createRoom(channel) - } + await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) } } module.exports.createRoom = createRoom +module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild +module.exports.kstateToState = kstateToState +module.exports.stateToKState = stateToKState +module.exports.diffKState = diffKState +module.exports.channelToKState = channelToKState +module.exports.kstateStripConditionals = kstateStripConditionals diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js new file mode 100644 index 0000000..5ce52e8 --- /dev/null +++ b/d2m/actions/create-room.test.js @@ -0,0 +1,83 @@ +const {kstateToState, stateToKState, diffKState, channelToKState, kstateStripConditionals} = require("./create-room") +const {test} = require("supertape") +const testData = require("../../test/data") + +test("kstate2state: general", t => { + t.deepEqual(kstateToState({ + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }), [ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]) +}) + +test("state2kstate: general", t => { + t.deepEqual(stateToKState([ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]), { + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }) +}) + +test("diffKState: detects edits", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + "same/": {a: 2} + }, { + "m.room.name/": {name: "edited name"}, + "same/": {a: 2} + }), + { + "m.room.name/": {name: "edited name"} + } + ) +}) + +test("diffKState: detects new properties", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + }, { + "m.room.name/": {name: "test name"}, + "new/": {a: 2} + }), + { + "new/": {a: 2} + } + ) +}) + +test("channel2room: general", async t => { + t.deepEqual( + kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), + testData.room.general + ) +}) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 6d3c327..b3d7e95 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -37,7 +37,7 @@ function createSpace(guild) { } } ] - }).then(/** @param {import("../../types").R_RoomCreated} root */ root => { + }).then(/** @param {import("../../types").R.RoomCreated} root */ root => { db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) return root }) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 14dda55..325a3d0 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -1,7 +1,7 @@ // @ts-check const reg = require("../../matrix/read-registration.js") -const fetch = require("node-fetch") +const fetch = require("node-fetch").default fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { method: "POST", diff --git a/index.js b/index.js index d1c721c..807b49f 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,3 @@ passthrough.discord = discord require("./stdin") })() - -// process.on("unhandledRejection", console.error) -// process.on("uncaughtException", console.error) diff --git a/matrix/file.js b/matrix/file.js index d242f52..137a096 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -36,7 +36,7 @@ async function uploadDiscordFileToMxc(path) { const body = res.body // Upload to Matrix - /** @type {import("../types").R_FileUploaded} */ + /** @type {import("../types").R.FileUploaded} */ const root = await mreq.mreq("POST", "/media/v3/upload", body, { headers: { "Content-Type": res.headers.get("content-type") diff --git a/matrix/mreq.js b/matrix/mreq.js index b3e12aa..1345f78 100644 --- a/matrix/mreq.js +++ b/matrix/mreq.js @@ -10,8 +10,9 @@ const reg = sync.require("./read-registration.js") const baseUrl = "https://matrix.cadence.moe/_matrix" -class MatrixServerError { +class MatrixServerError extends Error { constructor(data) { + super(data.error || data.errcode) this.data = data /** @type {string} */ this.errcode = data.errcode @@ -35,7 +36,7 @@ async function mreq(method, url, body, extra = {}) { } }, extra) - console.log(baseUrl + url, opts) + // console.log(baseUrl + url, opts) const res = await fetch(baseUrl + url, opts) const root = await res.json() diff --git a/package-lock.json b/package-lock.json index 654d022..b845abf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,13 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", - "supertape": "^8.3.0" + "snowtransfer": "^0.7.0" }, "devDependencies": { "@types/node": "^18.16.0", - "tap-dot": "github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf" + "@types/node-fetch": "^2.6.3", + "supertape": "^8.3.0", + "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" } }, "node_modules/@babel/runtime": { @@ -41,6 +42,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", + "dev": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^27.0.6", @@ -54,6 +56,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -62,6 +65,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -73,6 +77,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } @@ -81,6 +86,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^27.5.1", @@ -95,6 +101,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } @@ -103,6 +110,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -115,12 +123,14 @@ "node_modules/@cloudcmd/stub/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "node_modules/@cloudcmd/stub/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -132,6 +142,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, "dependencies": { "@sinclair/typebox": "^0.25.16" }, @@ -151,6 +162,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", + "dev": true, "dependencies": { "ci-info": "^3.1.1", "fullstore": "^3.0.0" @@ -163,6 +175,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", + "dev": true, "dependencies": { "fastest-levenshtein": "^1.0.12", "just-kebab-case": "^1.1.0" @@ -174,12 +187,14 @@ "node_modules/@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true }, "node_modules/@supertape/engine-loader": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", + "dev": true, "dependencies": { "try-catch": "^3.0.0" }, @@ -191,6 +206,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", + "dev": true, "dependencies": { "@supertape/formatter-tap": "^3.0.3", "fullstore": "^3.0.0" @@ -203,6 +219,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", + "dev": true, "dependencies": { "fullstore": "^3.0.0" }, @@ -214,6 +231,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", + "dev": true, "dependencies": { "chalk": "^4.1.0", "ci-info": "^3.1.1", @@ -229,6 +247,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==", + "dev": true, "engines": { "node": ">=16" } @@ -237,6 +256,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==", + "dev": true, "engines": { "node": ">=16" } @@ -245,6 +265,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", + "dev": true, "dependencies": { "@cloudcmd/stub": "^4.0.0" }, @@ -263,6 +284,30 @@ "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", + "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -288,6 +333,18 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -309,6 +366,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, "engines": { "node": ">=12" }, @@ -320,6 +378,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -344,6 +403,7 @@ "version": "1.0.55", "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, "dependencies": { "printable-characters": "^1.0.42" } @@ -357,6 +417,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -372,7 +433,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base-x": { "version": "4.0.0", @@ -482,6 +544,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -546,6 +609,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -566,6 +630,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, "funding": [ { "type": "github", @@ -580,6 +645,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, "dependencies": { "string-width": "^4.2.3" }, @@ -602,6 +668,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -612,7 +679,14 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -657,12 +731,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -671,7 +739,8 @@ "node_modules/data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==" + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true }, "node_modules/decompress-response": { "version": "6.0.0", @@ -691,6 +760,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-get-iterator": "^1.1.2", @@ -726,6 +796,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -774,6 +845,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -799,7 +871,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/encodeurl": { "version": "1.0.2", @@ -821,6 +894,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -841,15 +915,6 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -858,6 +923,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -969,6 +1043,7 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, "engines": { "node": ">= 4.9.1" } @@ -1012,6 +1087,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -1053,12 +1129,14 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fullstore": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==", + "dev": true, "engines": { "node": ">=4" } @@ -1072,6 +1150,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1093,6 +1172,7 @@ "version": "2.0.12", "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" @@ -1107,6 +1187,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1125,6 +1206,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -1143,31 +1225,11 @@ "node": ">= 0.4.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1176,6 +1238,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -1184,6 +1247,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -1206,6 +1270,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -1273,6 +1338,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1292,6 +1358,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -1313,6 +1380,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -1328,6 +1396,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -1341,6 +1410,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -1352,6 +1422,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -1367,6 +1438,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -1378,6 +1450,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -1389,6 +1462,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1403,6 +1477,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -1411,6 +1486,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1419,6 +1495,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1433,6 +1510,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -1448,6 +1526,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1456,6 +1535,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -1467,6 +1547,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1481,6 +1562,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -1495,6 +1577,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -1513,6 +1596,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1521,6 +1605,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -1532,12 +1617,14 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", @@ -1552,6 +1639,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -1570,7 +1658,8 @@ "node_modules/just-kebab-case": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", - "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==" + "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", + "dev": true }, "node_modules/loglevel": { "version": "1.8.1", @@ -1719,6 +1808,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1841,6 +1931,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -1856,6 +1947,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -1864,6 +1956,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -1927,7 +2020,8 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-to-regexp": { "version": "0.1.7", @@ -1963,6 +2057,7 @@ "version": "29.5.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, "dependencies": { "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", @@ -1976,6 +2071,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -1986,14 +2082,18 @@ "node_modules/printable-characters": { "version": "1.0.42", "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", "dev": true }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2074,7 +2174,8 @@ "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, "node_modules/readable-stream": { "version": "3.6.2", @@ -2098,6 +2199,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -2114,6 +2216,7 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -2321,6 +2424,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2341,6 +2445,7 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" @@ -2358,6 +2463,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, "dependencies": { "internal-slot": "^1.0.4" }, @@ -2377,6 +2483,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2390,6 +2497,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -2398,6 +2506,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2409,6 +2518,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -2431,6 +2541,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", + "dev": true, "dependencies": { "@cloudcmd/stub": "^4.0.0", "@putout/cli-keypress": "^1.0.0", @@ -2467,6 +2578,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2478,6 +2590,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -2487,85 +2600,28 @@ }, "node_modules/tap-dot": { "version": "2.0.0", - "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", - "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", + "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^1.1.1", - "tap-out": "github:alandelaney-whs/tap-out", - "through2": "^2.0.0" + "colorette": "^1.0.5", + "tap-out": "github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771" }, "bin": { "tap-dot": "bin/dot" } }, - "node_modules/tap-dot/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tap-dot/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/tap-out": { - "name": "tap-in", "version": "3.2.1", - "resolved": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "resolved": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", + "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", "dev": true, "license": "MIT", "dependencies": { "re-emitter": "1.1.4", - "readable-stream": "2.3.7", - "split": "1.0.1", - "trim": "1.0.1" + "readable-stream": "^4.3.0", + "split": "^1.0.1" }, "bin": { "tap-in": "bin/tap-in.js" @@ -2574,40 +2630,43 @@ "node": ">=8.0.0" } }, - "node_modules/tap-out/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/tap-out/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "node_modules/tap-out/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/tap-out/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/tap-out/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/tap-out/node_modules/readable-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/tar-fs": { @@ -2642,52 +2701,6 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/through2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2701,17 +2714,11 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/trim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", - "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", - "deprecated": "Use String.prototype.trim() instead", - "dev": true - }, "node_modules/try-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==", + "dev": true, "engines": { "node": ">=6" } @@ -2720,6 +2727,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", + "dev": true, "engines": { "node": ">=6" } @@ -2799,6 +2807,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -2814,6 +2823,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "dependencies": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -2828,6 +2838,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -2851,16 +2862,8 @@ "node_modules/wraptile": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", - "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } + "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", + "dev": true }, "node_modules/yallist": { "version": "4.0.0", @@ -2871,6 +2874,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "engines": { "node": ">=12" } @@ -2889,6 +2893,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", + "dev": true, "requires": { "chalk": "^4.0.0", "jest-diff": "^27.0.6", @@ -2898,22 +2903,26 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true }, "jest-diff": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^27.5.1", @@ -2924,12 +2933,14 @@ "jest-get-type": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true }, "pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "requires": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -2939,12 +2950,14 @@ "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -2955,6 +2968,7 @@ "version": "29.4.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, "requires": { "@sinclair/typebox": "^0.25.16" } @@ -2968,6 +2982,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", + "dev": true, "requires": { "ci-info": "^3.1.1", "fullstore": "^3.0.0" @@ -2977,6 +2992,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", + "dev": true, "requires": { "fastest-levenshtein": "^1.0.12", "just-kebab-case": "^1.1.0" @@ -2985,12 +3001,14 @@ "@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true }, "@supertape/engine-loader": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", + "dev": true, "requires": { "try-catch": "^3.0.0" } @@ -2999,6 +3017,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", + "dev": true, "requires": { "@supertape/formatter-tap": "^3.0.3", "fullstore": "^3.0.0" @@ -3008,6 +3027,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", + "dev": true, "requires": { "fullstore": "^3.0.0" } @@ -3016,6 +3036,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", + "dev": true, "requires": { "chalk": "^4.1.0", "ci-info": "^3.1.1", @@ -3027,17 +3048,20 @@ "@supertape/formatter-short": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", - "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==" + "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==", + "dev": true }, "@supertape/formatter-tap": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", - "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==" + "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==", + "dev": true }, "@supertape/operator-stub": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", + "dev": true, "requires": { "@cloudcmd/stub": "^4.0.0" } @@ -3053,6 +3077,29 @@ "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", "dev": true }, + "@types/node-fetch": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", + "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -3078,6 +3125,15 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3095,12 +3151,14 @@ "ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -3119,6 +3177,7 @@ "version": "1.0.55", "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, "requires": { "printable-characters": "^1.0.42" } @@ -3131,7 +3190,8 @@ "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true }, "backtracker": { "version": "3.3.1", @@ -3141,7 +3201,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "base-x": { "version": "4.0.0", @@ -3233,6 +3294,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "requires": { "balanced-match": "^1.0.0" } @@ -3277,6 +3339,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3290,12 +3353,14 @@ "ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true }, "cli-progress": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, "requires": { "string-width": "^4.2.3" } @@ -3312,6 +3377,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -3319,7 +3385,14 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true }, "combined-stream": { "version": "1.0.8", @@ -3352,12 +3425,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, "csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -3366,7 +3433,8 @@ "data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==" + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true }, "decompress-response": { "version": "6.0.0", @@ -3380,6 +3448,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "es-get-iterator": "^1.1.2", @@ -3409,6 +3478,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -3437,7 +3507,8 @@ "diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==" + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true }, "discord-api-types": { "version": "0.37.39", @@ -3459,7 +3530,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "encodeurl": { "version": "1.0.2", @@ -3478,6 +3550,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -3495,17 +3568,17 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -3602,7 +3675,8 @@ "fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==" + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true }, "file-uri-to-path": { "version": "1.0.0", @@ -3642,6 +3716,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -3674,12 +3749,14 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "fullstore": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", - "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==" + "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==", + "dev": true }, "function-bind": { "version": "1.1.1", @@ -3689,7 +3766,8 @@ "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true }, "get-intrinsic": { "version": "1.2.0", @@ -3705,6 +3783,7 @@ "version": "2.0.12", "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, "requires": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" @@ -3719,6 +3798,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3731,6 +3811,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -3743,37 +3824,23 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - } - } - }, "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "has-property-descriptors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -3787,6 +3854,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -3828,6 +3896,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3847,6 +3916,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "requires": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -3862,6 +3932,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3871,6 +3942,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -3881,6 +3953,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "requires": { "has-bigints": "^1.0.1" } @@ -3889,6 +3962,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3897,12 +3971,14 @@ "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true }, "is-core-module": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, "requires": { "has": "^1.0.3" } @@ -3911,6 +3987,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -3918,17 +3995,20 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true }, "is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -3937,6 +4017,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3945,12 +4026,14 @@ "is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true }, "is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -3959,6 +4042,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -3967,6 +4051,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -3975,6 +4060,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -3986,12 +4072,14 @@ "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true }, "is-weakset": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -4000,12 +4088,14 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", @@ -4016,7 +4106,8 @@ "jest-get-type": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==" + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true }, "js-yaml": { "version": "4.1.0", @@ -4029,7 +4120,8 @@ "just-kebab-case": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", - "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==" + "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", + "dev": true }, "loglevel": { "version": "1.8.1", @@ -4137,6 +4229,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -4226,6 +4319,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -4234,12 +4328,14 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -4285,7 +4381,8 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "path-to-regexp": { "version": "0.1.7", @@ -4315,6 +4412,7 @@ "version": "29.5.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, "requires": { "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", @@ -4324,19 +4422,21 @@ "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true } } }, "printable-characters": { "version": "1.0.42", "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==" + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, "proxy-addr": { @@ -4401,7 +4501,8 @@ "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, "readable-stream": { "version": "3.6.2", @@ -4422,6 +4523,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -4432,6 +4534,7 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, "requires": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -4570,7 +4673,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "split": { "version": "1.0.1", @@ -4585,6 +4689,7 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, "requires": { "as-table": "^1.0.36", "get-source": "^2.0.12" @@ -4599,6 +4704,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, "requires": { "internal-slot": "^1.0.4" } @@ -4615,6 +4721,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4624,12 +4731,14 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -4640,6 +4749,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, "requires": { "ansi-regex": "^6.0.1" } @@ -4653,6 +4763,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", + "dev": true, "requires": { "@cloudcmd/stub": "^4.0.0", "@putout/cli-keypress": "^1.0.0", @@ -4682,6 +4793,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -4689,106 +4801,50 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true }, "tap-dot": { - "version": "git+ssh://git@github.com/alandelaney-whs/tap-dot.git#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", - "integrity": "sha512-r3EdqKvdl8qy4OxB0oy3YuibtCGy0hbNKUITLfMlblCVnTgWZEDmlhxsV0Dfn5B3xwcZQTokjvaygKDUGIe+WA==", + "version": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", + "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", "dev": true, - "from": "tap-dot@github:alandelaney-whs/tap-dot#32d909760fc177c83a6738fecf0c8c7eb3a7b2bf", + "from": "tap-dot@github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4", "requires": { - "chalk": "^1.1.1", - "tap-out": "github:alandelaney-whs/tap-out", - "through2": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - } + "colorette": "^1.0.5", + "tap-out": "github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771" } }, "tap-out": { - "version": "git+ssh://git@github.com/alandelaney-whs/tap-out.git#c93556c36b8c7013d38ff12f9ce156eb06f734cb", + "version": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", + "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", "dev": true, - "from": "tap-out@github:alandelaney-whs/tap-out", + "from": "tap-out@github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", "requires": { "re-emitter": "1.1.4", - "readable-stream": "2.3.7", - "split": "1.0.1", - "trim": "1.0.1" + "readable-stream": "^4.3.0", + "split": "^1.0.1" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "readable-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" } } } @@ -4822,54 +4878,6 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4880,21 +4888,17 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "trim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-1.0.1.tgz", - "integrity": "sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w==", - "dev": true - }, "try-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", - "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==" + "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==", + "dev": true }, "try-to-catch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", - "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==" + "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", + "dev": true }, "tunnel-agent": { "version": "0.6.0", @@ -4956,6 +4960,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "requires": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -4968,6 +4973,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "requires": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -4979,6 +4985,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -4996,12 +5003,7 @@ "wraptile": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", - "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", "dev": true }, "yallist": { @@ -5012,7 +5014,8 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true } } } diff --git a/package.json b/package.json index f8917e7..e3ca70f 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,15 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", - "supertape": "^8.3.0" + "snowtransfer": "^0.7.0" }, "devDependencies": { - "@types/node": "^18.16.0" + "@types/node": "^18.16.0", + "@types/node-fetch": "^2.6.3", + "supertape": "^8.3.0", + "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "supertape --format short test/test.js" + "test": "FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" } } diff --git a/stdin.js b/stdin.js index fb95809..a57c044 100644 --- a/stdin.js +++ b/stdin.js @@ -43,7 +43,7 @@ async function customEval(input, _context, _filename, callback) { const output = util.inspect(result, false, depth, true) return callback(null, output) } catch (e) { - return callback(null, util.inspect(e, true, 100, true)) + return callback(null, util.inspect(e, false, 100, true)) } } diff --git a/test/data.js b/test/data.js index 42af177..e6a49c8 100644 --- a/test/data.js +++ b/test/data.js @@ -39,7 +39,7 @@ module.exports = { }, "m.room.avatar/": { discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", - url: "mxc://cadence.moe/sZtPwbfOIsvfSoWCWPrGnzql" + url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF" } } }, @@ -82,4 +82,4 @@ module.exports = { system_channel_flags: 0|0 } } -} \ No newline at end of file +} diff --git a/test/test.js b/test/test.js index cb6fe5d..de399f3 100644 --- a/test/test.js +++ b/test/test.js @@ -12,4 +12,4 @@ const sync = new HeatSync({persistent: false}) Object.assign(passthrough, { config, sync, db }) -require("../d2m/actions/create-room") \ No newline at end of file +require("../d2m/actions/create-room.test") diff --git a/types.d.ts b/types.d.ts index d571359..1f00836 100644 --- a/types.d.ts +++ b/types.d.ts @@ -8,17 +8,42 @@ export type AppServiceRegistrationConfig = { rate_limited: boolean } -export type M_Room_Message_content = { - msgtype: "m.text" - body: string - formatted_body?: "org.matrix.custom.html" - format?: string +namespace Event { + export type BaseStateEvent = { + type: string + room_id: string + sender: string + content: any + state_key: string + origin_server_ts: number + unsigned: any + event_id: string + user_id: string + age: number + replaces_state: string + prev_content?: any + } + + export type M_Room_Message = { + msgtype: "m.text" + body: string + formatted_body?: "org.matrix.custom.html" + format?: string + } + + export type M_Room_Member = { + membership: string + display_name?: string + avatar_url?: string + } } -export type R_RoomCreated = { - room_id: string -} +namespace R { + export type RoomCreated = { + room_id: string + } -export type R_FileUploaded = { - content_uri: string + export type FileUploaded = { + content_uri: string + } } From 6ddf19e8cd9f69e49d0a5f315a5f210245def1ff Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 May 2023 08:27:42 +1200 Subject: [PATCH 007/200] preparations for creating users --- d2m/actions/register-user.js | 54 +++++++++++++++++++++++++----------- types.d.ts | 8 ++++++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 325a3d0..a23147d 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -1,20 +1,42 @@ // @ts-check -const reg = require("../../matrix/read-registration.js") -const fetch = require("node-fetch").default +const assert = require("assert") -fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { - method: "POST", - body: JSON.stringify({ +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +async function registerUser(username) { + assert.ok(username.startsWith("_ooye_")) + /** @type {import("../../types").R.Registered} */ + const res = await mreq.mreq("POST", "/client/v3/register", { type: "m.login.application_service", - username: "_ooye_example" - }), - headers: { - Authorization: `Bearer ${reg.as_token}` - } -}).then(res => res.text()).then(text => { - // {"user_id":"@_ooye_example:cadence.moe","home_server":"cadence.moe","access_token":"XXX","device_id":"XXX"} - console.log(text) -}).catch(err => { - console.log(err) -}) + username + }) + return res +} + +/** + * A sim is an account that is being simulated by the bridge to copy events from the other side. + * @param {import("discord-api-types/v10").APIUser} user + */ +async function createSim(user) { + assert.notEqual(user.discriminator, "0000", "user is not a webhook") + fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { + method: "POST", + body: JSON.stringify({ + type: "m.login.application_service", + username: "_ooye_example" + }), + headers: { + Authorization: `Bearer ${reg.as_token}` + } + }).then(res => res.text()).then(text => { + + console.log(text) + }).catch(err => { + console.log(err) + }) diff --git a/types.d.ts b/types.d.ts index 1f00836..8d15d6b 100644 --- a/types.d.ts +++ b/types.d.ts @@ -46,4 +46,12 @@ namespace R { export type FileUploaded = { content_uri: string } + + export type Registered = { + /** "@localpart:domain.tld" */ + user_id: string + home_server: string + access_token: string + device_id: string + } } From 48c2ef76f5244b6df053092d2bf1b4e7a736c16f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 May 2023 08:27:42 +1200 Subject: [PATCH 008/200] preparations for creating users --- d2m/actions/register-user.js | 54 +++++++++++++++++++++++++----------- types.d.ts | 8 ++++++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 325a3d0..a23147d 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -1,20 +1,42 @@ // @ts-check -const reg = require("../../matrix/read-registration.js") -const fetch = require("node-fetch").default +const assert = require("assert") -fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { - method: "POST", - body: JSON.stringify({ +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +async function registerUser(username) { + assert.ok(username.startsWith("_ooye_")) + /** @type {import("../../types").R.Registered} */ + const res = await mreq.mreq("POST", "/client/v3/register", { type: "m.login.application_service", - username: "_ooye_example" - }), - headers: { - Authorization: `Bearer ${reg.as_token}` - } -}).then(res => res.text()).then(text => { - // {"user_id":"@_ooye_example:cadence.moe","home_server":"cadence.moe","access_token":"XXX","device_id":"XXX"} - console.log(text) -}).catch(err => { - console.log(err) -}) + username + }) + return res +} + +/** + * A sim is an account that is being simulated by the bridge to copy events from the other side. + * @param {import("discord-api-types/v10").APIUser} user + */ +async function createSim(user) { + assert.notEqual(user.discriminator, "0000", "user is not a webhook") + fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { + method: "POST", + body: JSON.stringify({ + type: "m.login.application_service", + username: "_ooye_example" + }), + headers: { + Authorization: `Bearer ${reg.as_token}` + } + }).then(res => res.text()).then(text => { + + console.log(text) + }).catch(err => { + console.log(err) + }) diff --git a/types.d.ts b/types.d.ts index 1f00836..8d15d6b 100644 --- a/types.d.ts +++ b/types.d.ts @@ -46,4 +46,12 @@ namespace R { export type FileUploaded = { content_uri: string } + + export type Registered = { + /** "@localpart:domain.tld" */ + user_id: string + home_server: string + access_token: string + device_id: string + } } From ec3c1a61ffeb8fcf76d3da773cb26b4406d36df0 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 May 2023 17:22:20 +1200 Subject: [PATCH 009/200] username sanitisation for registration --- .vscode/tasks.json | 24 + d2m/actions/register-user.js | 31 +- d2m/converters/user-to-mxid.js | 74 + d2m/converters/user-to-mxid.test.js | 33 + db/ooye.db | Bin 49152 -> 61440 bytes matrix/api.js | 20 + package-lock.json | 2559 +++------------------------ package.json | 8 +- test/test.js | 3 +- 9 files changed, 402 insertions(+), 2350 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 d2m/converters/user-to-mxid.js create mode 100644 d2m/converters/user-to-mxid.test.js create mode 100644 matrix/api.js diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..bb95546 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "test", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "label": "npm: test", + "detail": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot", + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + } + } + ] +} \ No newline at end of file diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index a23147d..8f31f23 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -4,39 +4,16 @@ const assert = require("assert") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough -/** @type {import("../../matrix/mreq")} */ -const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") -async function registerUser(username) { - assert.ok(username.startsWith("_ooye_")) - /** @type {import("../../types").R.Registered} */ - const res = await mreq.mreq("POST", "/client/v3/register", { - type: "m.login.application_service", - username - }) - return res -} - /** * A sim is an account that is being simulated by the bridge to copy events from the other side. * @param {import("discord-api-types/v10").APIUser} user */ async function createSim(user) { assert.notEqual(user.discriminator, "0000", "user is not a webhook") - fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { - method: "POST", - body: JSON.stringify({ - type: "m.login.application_service", - username: "_ooye_example" - }), - headers: { - Authorization: `Bearer ${reg.as_token}` - } - }).then(res => res.text()).then(text => { - - console.log(text) - }).catch(err => { - console.log(err) - }) + api.register("_ooye_example") +} diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js new file mode 100644 index 0000000..15b997f --- /dev/null +++ b/d2m/converters/user-to-mxid.js @@ -0,0 +1,74 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough + +/** + * Downcased and stripped username. Can only include a basic set of characters. + * https://spec.matrix.org/v1.6/appendices/#user-identifiers + * @param {import("discord-api-types/v10").APIUser} user + * @returns {string} localpart + */ +function downcaseUsername(user) { + // First, try to convert the username to the set of allowed characters + let downcased = user.username.toLowerCase() + // spaces to underscores... + .replace(/ /g, "_") + // remove disallowed characters... + .replace(/[^a-z0-9._=/-]*/g, "") + // remove leading and trailing dashes and underscores... + .replace(/(?:^[_-]*|[_-]*$)/g, "") + // The new length must be at least 2 characters (in other words, it should have some content) + if (downcased.length < 2) { + downcased = user.id + } + return downcased +} + +/** @param {string[]} preferences */ +function* generateLocalpartAlternatives(preferences) { + const best = preferences[0] + assert.ok(best) + // First, suggest the preferences... + for (const localpart of preferences) { + yield localpart + } + // ...then fall back to generating number suffixes... + let i = 2 + while (true) { + yield best + (i++) + } +} + +/** + * @param {import("discord-api-types/v10").APIUser} user + * @returns {string} + */ +function userToSimName(user) { + assert.notEqual(user.discriminator, "0000", "cannot create user for a webhook") + + // 1. Is sim user already registered? + const existing = db.prepare("SELECT sim_name FROM sim WHERE discord_id = ?").pluck().get(user.id) + if (existing) return existing + + // 2. Register based on username (could be new or old format) + const downcased = downcaseUsername(user) + const preferences = [downcased] + if (user.discriminator.length === 4) { // Old style tag? If user.username is unavailable, try the full tag next + preferences.push(downcased + user.discriminator) + } + + // Check for conflicts with already registered sims + /** @type {string[]} */ + const matches = db.prepare("SELECT sim_name FROM sim WHERE sim_name LIKE ? ESCAPE '@'").pluck().all(downcased + "%") + // Keep generating until we get a suggestion that doesn't conflict + for (const suggestion of generateLocalpartAlternatives(preferences)) { + if (!matches.includes(suggestion)) return suggestion + } + + throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`) +} + +module.exports.userToSimName = userToSimName \ No newline at end of file diff --git a/d2m/converters/user-to-mxid.test.js b/d2m/converters/user-to-mxid.test.js new file mode 100644 index 0000000..7cda6d7 --- /dev/null +++ b/d2m/converters/user-to-mxid.test.js @@ -0,0 +1,33 @@ +const {test} = require("supertape") +const tryToCatch = require("try-to-catch") +const assert = require("assert") +const {userToSimName} = require("./user-to-mxid") + +test("user2name: cannot create user for a webhook", async t => { + const [error] = await tryToCatch(() => userToSimName({discriminator: "0000"})) + t.ok(error instanceof assert.AssertionError, error.message) +}) + +test("user2name: works on normal name", t => { + t.equal(userToSimName({username: "Harry Styles!", discriminator: "0001"}), "harry_styles") +}) + +test("user2name: works on emojis", t => { + t.equal(userToSimName({username: "Cookie 🍪", discriminator: "0001"}), "cookie") +}) + +test("user2name: works on crazy name", t => { + t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7//") +}) + +test("user2name: adds discriminator if name is unavailable (old tag format)", t => { + t.equal(userToSimName({username: "BOT$", discriminator: "1234"}), "bot1234") +}) + +test("user2name: adds number suffix if name is unavailable (new username format)", t => { + t.equal(userToSimName({username: "bot", discriminator: "0"}), "bot2") +}) + +test("user2name: uses ID if name becomes too short", t => { + t.equal(userToSimName({username: "f***", discriminator: "0001", id: "9"}), "9") +}) diff --git a/db/ooye.db b/db/ooye.db index 10a99ddd7695a2831d809699bba20bc3ac701d53..bb571ed8e1eb7be214c5f0378822d6fd873aded8 100644 GIT binary patch delta 277 zcmZo@U~YK8JV9D8f`Ng7ABbUqeWH%BY6OFxe=RTHO9mbuAqM_Pibq;cM3~^Nmadh%=RZs$o zC@E-gaVn)`7ANNyrA&6
  • u3#^)vGrcOS`uQoZKmxCuWMM;xOQDgX+AChZmkgXdZyEUG`8D`H@?PNEz*o-m UmUlUC-DW|9e4fp3d4x9u04I(TQ2+n{ diff --git a/matrix/api.js b/matrix/api.js new file mode 100644 index 0000000..4335585 --- /dev/null +++ b/matrix/api.js @@ -0,0 +1,20 @@ +// @ts-check + +const passthrough = require("../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("./mreq")} */ +const mreq = sync.require("./mreq") +/** @type {import("./file")} */ +const file = sync.require("./file") + +/** + * @returns {Promise} + */ +function register(username) { + return mreq.mreq("POST", "/client/v3/register", { + type: "m.login.application_service", + username + }) +} + +module.exports.register = register \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b845abf..9556331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "out-of-your-element", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -12,25 +12,27 @@ "better-sqlite3": "^8.3.0", "cloudstorm": "^0.7.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "heatsync": "^2.4.0", + "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0" + "snowtransfer": "^0.7.0", + "try-to-catch": "^3.0.1" }, "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" } }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -151,9 +153,9 @@ } }, "node_modules/@matrix-org/matrix-sdk-crypto-js": { - "version": "0.1.0-alpha.7", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz", - "integrity": "sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==", + "version": "0.1.0-alpha.8", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.8.tgz", + "integrity": "sha512-hdmbbGXKrN6JNo3wdBaR5Zs3lXlzllT3U43ViNTlabB3nKkOZQnEAN/Isv+4EQSgz1+8897veI9Q8sqlQX22oA==", "engines": { "node": ">= 10" } @@ -279,9 +281,9 @@ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, "node_modules/@types/node": { - "version": "18.16.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz", - "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", + "version": "18.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", + "integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==", "dev": true }, "node_modules/@types/node-fetch": { @@ -294,29 +296,15 @@ "form-data": "^3.0.0" } }, - "node_modules/@types/node-fetch/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { - "version": "18.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.38.tgz", - "integrity": "sha512-ExsidLLSzYj4cvaQjGnQCk4HFfVT9+EZ9XZsQ8Hsrcn8QNgXtpZ3m9vSIC2MWtx7jHictK6wYhQgGh6ic58oOw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz", + "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -394,6 +382,19 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -426,9 +427,9 @@ } }, "node_modules/backtracker": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.1.tgz", - "integrity": "sha512-bQTxQ/JL9nm8/mNFP/bkiOJN0w9OOK6LQDqa+Jt9YnnFGQzAplYwi2TDmzuEwHoAtuUso5StoyKvZazkPO4q4g==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.2.tgz", + "integrity": "sha512-bXosLBp95xGE1kcWRnbG+e+Sw1xCKTT1GMdvaqEk9cGfBQoFPEvdI78fepKIJWFejV4NCl7OLUt8SNvYom/D/w==" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -504,6 +505,42 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -527,19 +564,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -558,9 +582,10 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, "funding": [ { "type": "github", @@ -577,7 +602,7 @@ ], "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "ieee754": "^1.2.1" } }, "node_modules/bytes": { @@ -731,6 +756,38 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -742,6 +799,14 @@ "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", "dev": true }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -757,16 +822,17 @@ } }, "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", "dev": true, "dependencies": { + "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", + "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", @@ -774,7 +840,7 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.0", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", @@ -851,9 +917,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.39", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.39.tgz", - "integrity": "sha512-hkhQsQyzsTJITp311WXvHZh9j4RAMfIk2hPmsWeOTN50QTpg6zqmJNfel9D/8lYNvsU01wzw9281Yke8NhYyHg==" + "version": "0.37.41", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.41.tgz", + "integrity": "sha512-FaPGBK9hx3zqSRX1x3KQWj+OElAJKmcyyfcdCy+U4AKv+gYuIkRySM7zd1So2sE4gc1DikkghkSBgBgKh6pe4Q==" }, "node_modules/discord-markdown": { "version": "2.4.1", @@ -1012,19 +1078,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/express/node_modules/raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -1070,19 +1123,6 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1093,9 +1133,10 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -1282,11 +1323,11 @@ } }, "node_modules/heatsync": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.0.tgz", - "integrity": "sha512-3avAZvdWohjVNhx/P1lHGEUriGP8VlbdFKrMsiBVbXzOGuEEKnC9840Qu4SyUWxgs0V1D3RIpNS3898NFgQkng==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.1.tgz", + "integrity": "sha512-cRzLwnKnJ5O4dQWXiJyFp4myKY8lGfK+49/SbPsvnr3pf2PNG1Xh8pPono303cjJeFpaPSTs609mQH1xhPVyzA==", "dependencies": { - "backtracker": "3.3.1" + "backtracker": "3.3.2" } }, "node_modules/http-errors": { @@ -1620,6 +1661,12 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", @@ -1725,18 +1772,10 @@ "node": ">=16.0.0" } }, - "node_modules/matrix-js-sdk/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/matrix-widget-api": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz", - "integrity": "sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.4.0.tgz", + "integrity": "sha512-dw0dRylGQzDUoiaY/g5xx1tBbS7aoov31PRtFMAvG58/4uerYllV9Gfou7w+I1aglwB6hihTREzKltVjARWV6A==", "dependencies": { "@types/events": "^3.0.0", "events": "^3.2.0" @@ -1852,19 +1891,6 @@ "node": ">= 0.8.0" } }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/morgan/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -1876,6 +1902,11 @@ "node": ">= 0.8" } }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -1901,9 +1932,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2017,6 +2048,15 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2178,16 +2218,18 @@ "dev": true }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", + "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/regenerator-runtime": { @@ -2306,19 +2348,6 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2343,6 +2372,27 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -2420,6 +2470,19 @@ "node": ">=12.0.0" } }, + "node_modules/snowtransfer/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2601,7 +2664,7 @@ "node_modules/tap-dot": { "version": "2.0.0", "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", + "integrity": "sha512-nhpVoX/s4IJJdm7OymbZ1rdZNlqt3l/yQ9Z9if06jcgRNto6QAZOrLIvdCILYQ6GE0mu+cyVA8s24amdwbvHiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2615,7 +2678,7 @@ "node_modules/tap-out": { "version": "3.2.1", "resolved": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", + "integrity": "sha512-hyMMeN6jagEyeEOq7Xyg3GNIAR3iUDDocaoK5QRPjnEGbFZOYJ39Dkn7BsFUXyGVl+s4b3zPkDcTS38+6KTXCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2630,45 +2693,6 @@ "node": ">=8.0.0" } }, - "node_modules/tap-out/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/tap-out/node_modules/readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -2695,6 +2719,19 @@ "node": ">=6" } }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -2727,7 +2764,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", - "dev": true, "engines": { "node": ">=6" } @@ -2781,6 +2817,14 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2803,6 +2847,21 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-boxed-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", @@ -2879,2143 +2938,5 @@ "node": ">=12" } } - }, - "dependencies": { - "@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@cloudcmd/stub": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", - "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.0.6", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "dev": true - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.25.16" - } - }, - "@matrix-org/matrix-sdk-crypto-js": { - "version": "0.1.0-alpha.7", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz", - "integrity": "sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==" - }, - "@putout/cli-keypress": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", - "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", - "dev": true, - "requires": { - "ci-info": "^3.1.1", - "fullstore": "^3.0.0" - } - }, - "@putout/cli-validate-args": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", - "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", - "dev": true, - "requires": { - "fastest-levenshtein": "^1.0.12", - "just-kebab-case": "^1.1.0" - } - }, - "@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", - "dev": true - }, - "@supertape/engine-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", - "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", - "dev": true, - "requires": { - "try-catch": "^3.0.0" - } - }, - "@supertape/formatter-fail": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", - "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", - "dev": true, - "requires": { - "@supertape/formatter-tap": "^3.0.3", - "fullstore": "^3.0.0" - } - }, - "@supertape/formatter-json-lines": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", - "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", - "dev": true, - "requires": { - "fullstore": "^3.0.0" - } - }, - "@supertape/formatter-progress-bar": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", - "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "ci-info": "^3.1.1", - "cli-progress": "^3.8.2", - "fullstore": "^3.0.0", - "once": "^1.4.0" - } - }, - "@supertape/formatter-short": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", - "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==", - "dev": true - }, - "@supertape/formatter-tap": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", - "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==", - "dev": true - }, - "@supertape/operator-stub": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", - "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", - "dev": true, - "requires": { - "@cloudcmd/stub": "^4.0.0" - } - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" - }, - "@types/node": { - "version": "18.16.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz", - "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", - "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - }, - "dependencies": { - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } - }, - "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/react": { - "version": "18.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.38.tgz", - "integrity": "sha512-ExsidLLSzYj4cvaQjGnQCk4HFfVT9+EZ9XZsQ8Hsrcn8QNgXtpZ3m9vSIC2MWtx7jHictK6wYhQgGh6ic58oOw==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, - "@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "another-json": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", - "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==" - }, - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "requires": { - "printable-characters": "^1.0.42" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "backtracker": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.1.tgz", - "integrity": "sha512-bQTxQ/JL9nm8/mNFP/bkiOJN0w9OOK6LQDqa+Jt9YnnFGQzAplYwi2TDmzuEwHoAtuUso5StoyKvZazkPO4q4g==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "better-sqlite3": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz", - "integrity": "sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w==", - "requires": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.0" - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", - "requires": { - "base-x": "^4.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "centra": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/centra/-/centra-2.6.0.tgz", - "integrity": "sha512-dgh+YleemrT8u85QL11Z6tYhegAs3MMxsaWAq/oXeAmYJ7VxL3SI9TZtnfaEvNDMAPolj25FXIb3S+HCI4wQaQ==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true - }, - "cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "dev": true, - "requires": { - "string-width": "^4.2.3" - } - }, - "cloudstorm": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.0.tgz", - "integrity": "sha512-k+1u1kTdtlz3L6lnflAKMhkkZPoBl/2Du2czNvad2pYNOMBs8e0XZpSuCazC50Q29tzi08latn4SxtLbkws50A==", - "requires": { - "snowtransfer": "0.7.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" - }, - "data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" - }, - "diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", - "dev": true - }, - "discord-api-types": { - "version": "0.37.39", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.39.tgz", - "integrity": "sha512-hkhQsQyzsTJITp311WXvHZh9j4RAMfIk2hPmsWeOTN50QTpg6zqmJNfel9D/8lYNvsU01wzw9281Yke8NhYyHg==" - }, - "discord-markdown": { - "version": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "from": "discord-markdown@git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "requires": { - "simple-markdown": "^0.7.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - } - } - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fullstore": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", - "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "heatsync": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.0.tgz", - "integrity": "sha512-3avAZvdWohjVNhx/P1lHGEUriGP8VlbdFKrMsiBVbXzOGuEEKnC9840Qu4SyUWxgs0V1D3RIpNS3898NFgQkng==", - "requires": { - "backtracker": "3.3.1" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - } - }, - "jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "just-kebab-case": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", - "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", - "dev": true - }, - "loglevel": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", - "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "matrix-appservice": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-2.0.0.tgz", - "integrity": "sha512-HCIuJ5i0YuO8b0dMyGe5dqlsE4f3RzHU0MuMg/2gGAZ4HL3r7aSWOFbyIWStSSUrk1qCa9Eml0i4EnEi0pOtdA==", - "requires": { - "body-parser": "^1.19.0", - "express": "^4.18.1", - "js-yaml": "^4.1.0", - "morgan": "^1.10.0" - } - }, - "matrix-events-sdk": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", - "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" - }, - "matrix-js-sdk": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-24.1.0.tgz", - "integrity": "sha512-xEx2ZoNsS56dwgqLJ3rIv2SUpFxdQLrLKmJCpMatMUKCAg+NGuZfpQ3QXblIbGaqFNQZCH7fC7S48AeTMZp1Jw==", - "requires": { - "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.5", - "another-json": "^0.2.0", - "bs58": "^5.0.0", - "content-type": "^1.0.4", - "loglevel": "^1.7.1", - "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.3.1", - "p-retry": "4", - "sdp-transform": "^2.14.1", - "unhomoglyph": "^1.0.6", - "uuid": "9" - }, - "dependencies": { - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - } - } - }, - "matrix-widget-api": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz", - "integrity": "sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==", - "requires": { - "@types/events": "^3.0.0", - "events": "^3.2.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "mixin-deep": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", - "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==" - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "requires": { - "ee-first": "1.1.1" - } - } - } - }, - "napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "node-abi": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.40.0.tgz", - "integrity": "sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA==", - "requires": { - "semver": "^7.3.5" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "requires": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "requires": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - } - }, - "pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "re-emitter": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.4.tgz", - "integrity": "sha512-C0SIXdXDSus2yqqvV7qifnb4NoWP7mEBXJq3axci301mXHCZb8Djwm4hrEZo4UeXRaEnfjH98uQ8EBppk2oNWA==", - "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - } - }, - "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sdp-transform": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz", - "integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==" - }, - "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "simple-markdown": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.7.3.tgz", - "integrity": "sha512-uGXIc13NGpqfPeFJIt/7SHHxd6HekEJYtsdoCM06mEBPL9fQH/pSD7LRM6PZ7CKchpSvxKL4tvwMamqAaNDAyg==", - "requires": { - "@types/react": ">=16.0.0" - } - }, - "snowtransfer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.0.tgz", - "integrity": "sha512-vc7B46tO4QeK99z/pN8ISd8QvO9QB3Oo4qP7nYYhriIMOtjYkHMi8t6kUBPIJLbeX+h0NpfwxaGJfXNLm1ZQ5A==", - "requires": { - "centra": "^2.6.0", - "discord-api-types": "^0.37.31", - "form-data": "^4.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "requires": { - "through": "2" - } - }, - "stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "requires": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "requires": { - "internal-slot": "^1.0.4" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" - }, - "supertape": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", - "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", - "dev": true, - "requires": { - "@cloudcmd/stub": "^4.0.0", - "@putout/cli-keypress": "^1.0.0", - "@putout/cli-validate-args": "^1.0.1", - "@supertape/engine-loader": "^1.0.0", - "@supertape/formatter-fail": "^3.0.0", - "@supertape/formatter-json-lines": "^2.0.0", - "@supertape/formatter-progress-bar": "^3.0.0", - "@supertape/formatter-short": "^2.0.0", - "@supertape/formatter-tap": "^3.0.0", - "@supertape/operator-stub": "^3.0.0", - "cli-progress": "^3.8.2", - "deep-equal": "^2.0.3", - "fullstore": "^3.0.0", - "glob": "^8.0.3", - "jest-diff": "^29.0.1", - "once": "^1.4.0", - "resolve": "^1.17.0", - "stacktracey": "^2.1.7", - "strip-ansi": "^7.0.0", - "try-to-catch": "^3.0.0", - "wraptile": "^3.0.0", - "yargs-parser": "^21.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tap-dot": { - "version": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", - "dev": true, - "from": "tap-dot@github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "requires": { - "colorette": "^1.0.5", - "tap-out": "github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771" - } - }, - "tap-out": { - "version": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", - "dev": true, - "from": "tap-out@github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "requires": { - "re-emitter": "1.1.4", - "readable-stream": "^4.3.0", - "split": "^1.0.1" - }, - "dependencies": { - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - } - } - } - }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "try-catch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", - "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==", - "dev": true - }, - "try-to-catch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", - "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "unhomoglyph": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", - "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "wraptile": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", - "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } } } diff --git a/package.json b/package.json index e3ca70f..f483842 100644 --- a/package.json +++ b/package.json @@ -18,21 +18,23 @@ "better-sqlite3": "^8.3.0", "cloudstorm": "^0.7.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "heatsync": "^2.4.0", + "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0" + "snowtransfer": "^0.7.0", + "try-to-catch": "^3.0.1" }, "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" + "test": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" } } diff --git a/test/test.js b/test/test.js index de399f3..3008abf 100644 --- a/test/test.js +++ b/test/test.js @@ -8,8 +8,9 @@ const passthrough = require("../passthrough") const db = new sqlite("db/ooye.db") // @ts-ignore -const sync = new HeatSync({persistent: false}) +const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) require("../d2m/actions/create-room.test") +require("../d2m/converters/user-to-mxid.test") \ No newline at end of file From 7ee04d085ff69fab41cb7abc30131e7a9f8de53a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 May 2023 17:22:20 +1200 Subject: [PATCH 010/200] username sanitisation for registration --- .vscode/tasks.json | 24 + d2m/actions/register-user.js | 31 +- d2m/converters/user-to-mxid.js | 74 + d2m/converters/user-to-mxid.test.js | 33 + matrix/api.js | 20 + package-lock.json | 2559 +++------------------------ package.json | 8 +- test/test.js | 3 +- 8 files changed, 402 insertions(+), 2350 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 d2m/converters/user-to-mxid.js create mode 100644 d2m/converters/user-to-mxid.test.js create mode 100644 matrix/api.js diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..bb95546 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "test", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "label": "npm: test", + "detail": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot", + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + } + } + ] +} \ No newline at end of file diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index a23147d..8f31f23 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -4,39 +4,16 @@ const assert = require("assert") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough -/** @type {import("../../matrix/mreq")} */ -const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") -async function registerUser(username) { - assert.ok(username.startsWith("_ooye_")) - /** @type {import("../../types").R.Registered} */ - const res = await mreq.mreq("POST", "/client/v3/register", { - type: "m.login.application_service", - username - }) - return res -} - /** * A sim is an account that is being simulated by the bridge to copy events from the other side. * @param {import("discord-api-types/v10").APIUser} user */ async function createSim(user) { assert.notEqual(user.discriminator, "0000", "user is not a webhook") - fetch("https://matrix.cadence.moe/_matrix/client/v3/register", { - method: "POST", - body: JSON.stringify({ - type: "m.login.application_service", - username: "_ooye_example" - }), - headers: { - Authorization: `Bearer ${reg.as_token}` - } - }).then(res => res.text()).then(text => { - - console.log(text) - }).catch(err => { - console.log(err) - }) + api.register("_ooye_example") +} diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js new file mode 100644 index 0000000..15b997f --- /dev/null +++ b/d2m/converters/user-to-mxid.js @@ -0,0 +1,74 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough + +/** + * Downcased and stripped username. Can only include a basic set of characters. + * https://spec.matrix.org/v1.6/appendices/#user-identifiers + * @param {import("discord-api-types/v10").APIUser} user + * @returns {string} localpart + */ +function downcaseUsername(user) { + // First, try to convert the username to the set of allowed characters + let downcased = user.username.toLowerCase() + // spaces to underscores... + .replace(/ /g, "_") + // remove disallowed characters... + .replace(/[^a-z0-9._=/-]*/g, "") + // remove leading and trailing dashes and underscores... + .replace(/(?:^[_-]*|[_-]*$)/g, "") + // The new length must be at least 2 characters (in other words, it should have some content) + if (downcased.length < 2) { + downcased = user.id + } + return downcased +} + +/** @param {string[]} preferences */ +function* generateLocalpartAlternatives(preferences) { + const best = preferences[0] + assert.ok(best) + // First, suggest the preferences... + for (const localpart of preferences) { + yield localpart + } + // ...then fall back to generating number suffixes... + let i = 2 + while (true) { + yield best + (i++) + } +} + +/** + * @param {import("discord-api-types/v10").APIUser} user + * @returns {string} + */ +function userToSimName(user) { + assert.notEqual(user.discriminator, "0000", "cannot create user for a webhook") + + // 1. Is sim user already registered? + const existing = db.prepare("SELECT sim_name FROM sim WHERE discord_id = ?").pluck().get(user.id) + if (existing) return existing + + // 2. Register based on username (could be new or old format) + const downcased = downcaseUsername(user) + const preferences = [downcased] + if (user.discriminator.length === 4) { // Old style tag? If user.username is unavailable, try the full tag next + preferences.push(downcased + user.discriminator) + } + + // Check for conflicts with already registered sims + /** @type {string[]} */ + const matches = db.prepare("SELECT sim_name FROM sim WHERE sim_name LIKE ? ESCAPE '@'").pluck().all(downcased + "%") + // Keep generating until we get a suggestion that doesn't conflict + for (const suggestion of generateLocalpartAlternatives(preferences)) { + if (!matches.includes(suggestion)) return suggestion + } + + throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`) +} + +module.exports.userToSimName = userToSimName \ No newline at end of file diff --git a/d2m/converters/user-to-mxid.test.js b/d2m/converters/user-to-mxid.test.js new file mode 100644 index 0000000..7cda6d7 --- /dev/null +++ b/d2m/converters/user-to-mxid.test.js @@ -0,0 +1,33 @@ +const {test} = require("supertape") +const tryToCatch = require("try-to-catch") +const assert = require("assert") +const {userToSimName} = require("./user-to-mxid") + +test("user2name: cannot create user for a webhook", async t => { + const [error] = await tryToCatch(() => userToSimName({discriminator: "0000"})) + t.ok(error instanceof assert.AssertionError, error.message) +}) + +test("user2name: works on normal name", t => { + t.equal(userToSimName({username: "Harry Styles!", discriminator: "0001"}), "harry_styles") +}) + +test("user2name: works on emojis", t => { + t.equal(userToSimName({username: "Cookie 🍪", discriminator: "0001"}), "cookie") +}) + +test("user2name: works on crazy name", t => { + t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7//") +}) + +test("user2name: adds discriminator if name is unavailable (old tag format)", t => { + t.equal(userToSimName({username: "BOT$", discriminator: "1234"}), "bot1234") +}) + +test("user2name: adds number suffix if name is unavailable (new username format)", t => { + t.equal(userToSimName({username: "bot", discriminator: "0"}), "bot2") +}) + +test("user2name: uses ID if name becomes too short", t => { + t.equal(userToSimName({username: "f***", discriminator: "0001", id: "9"}), "9") +}) diff --git a/matrix/api.js b/matrix/api.js new file mode 100644 index 0000000..4335585 --- /dev/null +++ b/matrix/api.js @@ -0,0 +1,20 @@ +// @ts-check + +const passthrough = require("../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("./mreq")} */ +const mreq = sync.require("./mreq") +/** @type {import("./file")} */ +const file = sync.require("./file") + +/** + * @returns {Promise} + */ +function register(username) { + return mreq.mreq("POST", "/client/v3/register", { + type: "m.login.application_service", + username + }) +} + +module.exports.register = register \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b845abf..9556331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "out-of-your-element", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -12,25 +12,27 @@ "better-sqlite3": "^8.3.0", "cloudstorm": "^0.7.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "heatsync": "^2.4.0", + "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0" + "snowtransfer": "^0.7.0", + "try-to-catch": "^3.0.1" }, "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" } }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -151,9 +153,9 @@ } }, "node_modules/@matrix-org/matrix-sdk-crypto-js": { - "version": "0.1.0-alpha.7", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz", - "integrity": "sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==", + "version": "0.1.0-alpha.8", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.8.tgz", + "integrity": "sha512-hdmbbGXKrN6JNo3wdBaR5Zs3lXlzllT3U43ViNTlabB3nKkOZQnEAN/Isv+4EQSgz1+8897veI9Q8sqlQX22oA==", "engines": { "node": ">= 10" } @@ -279,9 +281,9 @@ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, "node_modules/@types/node": { - "version": "18.16.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz", - "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", + "version": "18.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", + "integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==", "dev": true }, "node_modules/@types/node-fetch": { @@ -294,29 +296,15 @@ "form-data": "^3.0.0" } }, - "node_modules/@types/node-fetch/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { - "version": "18.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.38.tgz", - "integrity": "sha512-ExsidLLSzYj4cvaQjGnQCk4HFfVT9+EZ9XZsQ8Hsrcn8QNgXtpZ3m9vSIC2MWtx7jHictK6wYhQgGh6ic58oOw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz", + "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -394,6 +382,19 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -426,9 +427,9 @@ } }, "node_modules/backtracker": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.1.tgz", - "integrity": "sha512-bQTxQ/JL9nm8/mNFP/bkiOJN0w9OOK6LQDqa+Jt9YnnFGQzAplYwi2TDmzuEwHoAtuUso5StoyKvZazkPO4q4g==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.2.tgz", + "integrity": "sha512-bXosLBp95xGE1kcWRnbG+e+Sw1xCKTT1GMdvaqEk9cGfBQoFPEvdI78fepKIJWFejV4NCl7OLUt8SNvYom/D/w==" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -504,6 +505,42 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -527,19 +564,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -558,9 +582,10 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, "funding": [ { "type": "github", @@ -577,7 +602,7 @@ ], "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "ieee754": "^1.2.1" } }, "node_modules/bytes": { @@ -731,6 +756,38 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -742,6 +799,14 @@ "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", "dev": true }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -757,16 +822,17 @@ } }, "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", "dev": true, "dependencies": { + "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", + "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", @@ -774,7 +840,7 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.0", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", @@ -851,9 +917,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.39", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.39.tgz", - "integrity": "sha512-hkhQsQyzsTJITp311WXvHZh9j4RAMfIk2hPmsWeOTN50QTpg6zqmJNfel9D/8lYNvsU01wzw9281Yke8NhYyHg==" + "version": "0.37.41", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.41.tgz", + "integrity": "sha512-FaPGBK9hx3zqSRX1x3KQWj+OElAJKmcyyfcdCy+U4AKv+gYuIkRySM7zd1So2sE4gc1DikkghkSBgBgKh6pe4Q==" }, "node_modules/discord-markdown": { "version": "2.4.1", @@ -1012,19 +1078,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/express/node_modules/raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -1070,19 +1123,6 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1093,9 +1133,10 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -1282,11 +1323,11 @@ } }, "node_modules/heatsync": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.0.tgz", - "integrity": "sha512-3avAZvdWohjVNhx/P1lHGEUriGP8VlbdFKrMsiBVbXzOGuEEKnC9840Qu4SyUWxgs0V1D3RIpNS3898NFgQkng==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.1.tgz", + "integrity": "sha512-cRzLwnKnJ5O4dQWXiJyFp4myKY8lGfK+49/SbPsvnr3pf2PNG1Xh8pPono303cjJeFpaPSTs609mQH1xhPVyzA==", "dependencies": { - "backtracker": "3.3.1" + "backtracker": "3.3.2" } }, "node_modules/http-errors": { @@ -1620,6 +1661,12 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", @@ -1725,18 +1772,10 @@ "node": ">=16.0.0" } }, - "node_modules/matrix-js-sdk/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/matrix-widget-api": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz", - "integrity": "sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.4.0.tgz", + "integrity": "sha512-dw0dRylGQzDUoiaY/g5xx1tBbS7aoov31PRtFMAvG58/4uerYllV9Gfou7w+I1aglwB6hihTREzKltVjARWV6A==", "dependencies": { "@types/events": "^3.0.0", "events": "^3.2.0" @@ -1852,19 +1891,6 @@ "node": ">= 0.8.0" } }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/morgan/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -1876,6 +1902,11 @@ "node": ">= 0.8" } }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -1901,9 +1932,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2017,6 +2048,15 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2178,16 +2218,18 @@ "dev": true }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", + "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/regenerator-runtime": { @@ -2306,19 +2348,6 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2343,6 +2372,27 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -2420,6 +2470,19 @@ "node": ">=12.0.0" } }, + "node_modules/snowtransfer/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2601,7 +2664,7 @@ "node_modules/tap-dot": { "version": "2.0.0", "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", + "integrity": "sha512-nhpVoX/s4IJJdm7OymbZ1rdZNlqt3l/yQ9Z9if06jcgRNto6QAZOrLIvdCILYQ6GE0mu+cyVA8s24amdwbvHiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2615,7 +2678,7 @@ "node_modules/tap-out": { "version": "3.2.1", "resolved": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", + "integrity": "sha512-hyMMeN6jagEyeEOq7Xyg3GNIAR3iUDDocaoK5QRPjnEGbFZOYJ39Dkn7BsFUXyGVl+s4b3zPkDcTS38+6KTXCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2630,45 +2693,6 @@ "node": ">=8.0.0" } }, - "node_modules/tap-out/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/tap-out/node_modules/readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -2695,6 +2719,19 @@ "node": ">=6" } }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -2727,7 +2764,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", - "dev": true, "engines": { "node": ">=6" } @@ -2781,6 +2817,14 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2803,6 +2847,21 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-boxed-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", @@ -2879,2143 +2938,5 @@ "node": ">=12" } } - }, - "dependencies": { - "@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@cloudcmd/stub": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", - "integrity": "sha512-7x7tVxJZOdQowHv/VKwHLo9aoNNoVRc6PdKYqyKcDHX+xrF78jSXnqEWrOplnD/gF+tCnyFafu1Is+lFfWCILw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.0.6", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "dev": true - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.25.16" - } - }, - "@matrix-org/matrix-sdk-crypto-js": { - "version": "0.1.0-alpha.7", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz", - "integrity": "sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==" - }, - "@putout/cli-keypress": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@putout/cli-keypress/-/cli-keypress-1.0.0.tgz", - "integrity": "sha512-w+lRVGZodRM4K214R4jvyOsmCUGA3OnaYDOJ2rpXj6a+O6n91zLlkb7JYsw6I0QCNmXjpNLJSoLgzGWTue6YIg==", - "dev": true, - "requires": { - "ci-info": "^3.1.1", - "fullstore": "^3.0.0" - } - }, - "@putout/cli-validate-args": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@putout/cli-validate-args/-/cli-validate-args-1.1.1.tgz", - "integrity": "sha512-AczBS98YyvsDVxvvYjHGyIygFu3i/EJ0xsruU6MlytTuUiCFQIE/QQPDy1bcN5J2Y75BzSYncaYnVrEGcBjeeQ==", - "dev": true, - "requires": { - "fastest-levenshtein": "^1.0.12", - "just-kebab-case": "^1.1.0" - } - }, - "@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", - "dev": true - }, - "@supertape/engine-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@supertape/engine-loader/-/engine-loader-1.1.3.tgz", - "integrity": "sha512-5ilgEng0WBvMQjNJWQ/bnAA6HKgbLKxTya2C0RxFH0LYSN5faBVtgxjLDvTQ+5L+ZxjK/7ooQDDaRS1Mo0ga5Q==", - "dev": true, - "requires": { - "try-catch": "^3.0.0" - } - }, - "@supertape/formatter-fail": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@supertape/formatter-fail/-/formatter-fail-3.0.2.tgz", - "integrity": "sha512-mSBnNprfLFmGvZkP+ODGroPLFCIN5BWE/06XaD5ghiTVWqek7eH8IDqvKyEduvuQu1O5tvQiaTwQsyxvikF+2w==", - "dev": true, - "requires": { - "@supertape/formatter-tap": "^3.0.3", - "fullstore": "^3.0.0" - } - }, - "@supertape/formatter-json-lines": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@supertape/formatter-json-lines/-/formatter-json-lines-2.0.1.tgz", - "integrity": "sha512-9LWOCu4yOF9orf4QJseS8lP3hXkYn24qn57VqYt/3r2aRJv4vWTPfaL1ot5JRHCZs0qXrV1sqPmN6E05rRLDYA==", - "dev": true, - "requires": { - "fullstore": "^3.0.0" - } - }, - "@supertape/formatter-progress-bar": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@supertape/formatter-progress-bar/-/formatter-progress-bar-3.0.0.tgz", - "integrity": "sha512-rVFAQ21eApq3TQV8taFLNcCxcGZvvOPxQC63swdmHFCp+07Dt3tvC/aFxF35NLobc3rySasGSEuPucpyoPrjfg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "ci-info": "^3.1.1", - "cli-progress": "^3.8.2", - "fullstore": "^3.0.0", - "once": "^1.4.0" - } - }, - "@supertape/formatter-short": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@supertape/formatter-short/-/formatter-short-2.0.1.tgz", - "integrity": "sha512-zxFrZfCccFV+bf6A7MCEqT/Xsf0Elc3qa0P3jShfdEfrpblEcpSo0T/Wd9jFwc7uHA3ABgxgcHy7LNIpyrFTCg==", - "dev": true - }, - "@supertape/formatter-tap": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@supertape/formatter-tap/-/formatter-tap-3.0.3.tgz", - "integrity": "sha512-U5OuMotfYhGo9cZ8IgdAXRTH5Yy8yfLDZzYo1upTPTwlJJquKwtvuz7ptiB7BN3OFr5YakkDYlFxOYPcLo7urg==", - "dev": true - }, - "@supertape/operator-stub": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@supertape/operator-stub/-/operator-stub-3.0.0.tgz", - "integrity": "sha512-LZ6E4nSMDMbLOhvEZyeXo8wS5EBiAAffWrohb7yaVHDVTHr+xkczzPxinkvcOBhNuAtC0kVARdMbHg+HULmozA==", - "dev": true, - "requires": { - "@cloudcmd/stub": "^4.0.0" - } - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" - }, - "@types/node": { - "version": "18.16.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz", - "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", - "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - }, - "dependencies": { - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } - }, - "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/react": { - "version": "18.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.38.tgz", - "integrity": "sha512-ExsidLLSzYj4cvaQjGnQCk4HFfVT9+EZ9XZsQ8Hsrcn8QNgXtpZ3m9vSIC2MWtx7jHictK6wYhQgGh6ic58oOw==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, - "@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "another-json": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", - "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==" - }, - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "requires": { - "printable-characters": "^1.0.42" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "backtracker": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/backtracker/-/backtracker-3.3.1.tgz", - "integrity": "sha512-bQTxQ/JL9nm8/mNFP/bkiOJN0w9OOK6LQDqa+Jt9YnnFGQzAplYwi2TDmzuEwHoAtuUso5StoyKvZazkPO4q4g==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "better-sqlite3": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz", - "integrity": "sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w==", - "requires": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.0" - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", - "requires": { - "base-x": "^4.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "centra": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/centra/-/centra-2.6.0.tgz", - "integrity": "sha512-dgh+YleemrT8u85QL11Z6tYhegAs3MMxsaWAq/oXeAmYJ7VxL3SI9TZtnfaEvNDMAPolj25FXIb3S+HCI4wQaQ==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true - }, - "cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "dev": true, - "requires": { - "string-width": "^4.2.3" - } - }, - "cloudstorm": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.0.tgz", - "integrity": "sha512-k+1u1kTdtlz3L6lnflAKMhkkZPoBl/2Du2czNvad2pYNOMBs8e0XZpSuCazC50Q29tzi08latn4SxtLbkws50A==", - "requires": { - "snowtransfer": "0.7.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" - }, - "data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" - }, - "diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", - "dev": true - }, - "discord-api-types": { - "version": "0.37.39", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.39.tgz", - "integrity": "sha512-hkhQsQyzsTJITp311WXvHZh9j4RAMfIk2hPmsWeOTN50QTpg6zqmJNfel9D/8lYNvsU01wzw9281Yke8NhYyHg==" - }, - "discord-markdown": { - "version": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "from": "discord-markdown@git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "requires": { - "simple-markdown": "^0.7.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - } - } - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fullstore": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fullstore/-/fullstore-3.0.0.tgz", - "integrity": "sha512-EEIdG+HWpyygWRwSLIZy+x4u0xtghjHNfhQb0mI5825Mmjq6oFESFUY0hoZigEgd3KH8GX+ZOCK9wgmOiS7VBQ==", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "heatsync": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/heatsync/-/heatsync-2.4.0.tgz", - "integrity": "sha512-3avAZvdWohjVNhx/P1lHGEUriGP8VlbdFKrMsiBVbXzOGuEEKnC9840Qu4SyUWxgs0V1D3RIpNS3898NFgQkng==", - "requires": { - "backtracker": "3.3.1" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - } - }, - "jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "just-kebab-case": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", - "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", - "dev": true - }, - "loglevel": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", - "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "matrix-appservice": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-2.0.0.tgz", - "integrity": "sha512-HCIuJ5i0YuO8b0dMyGe5dqlsE4f3RzHU0MuMg/2gGAZ4HL3r7aSWOFbyIWStSSUrk1qCa9Eml0i4EnEi0pOtdA==", - "requires": { - "body-parser": "^1.19.0", - "express": "^4.18.1", - "js-yaml": "^4.1.0", - "morgan": "^1.10.0" - } - }, - "matrix-events-sdk": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", - "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" - }, - "matrix-js-sdk": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-24.1.0.tgz", - "integrity": "sha512-xEx2ZoNsS56dwgqLJ3rIv2SUpFxdQLrLKmJCpMatMUKCAg+NGuZfpQ3QXblIbGaqFNQZCH7fC7S48AeTMZp1Jw==", - "requires": { - "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.5", - "another-json": "^0.2.0", - "bs58": "^5.0.0", - "content-type": "^1.0.4", - "loglevel": "^1.7.1", - "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.3.1", - "p-retry": "4", - "sdp-transform": "^2.14.1", - "unhomoglyph": "^1.0.6", - "uuid": "9" - }, - "dependencies": { - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - } - } - }, - "matrix-widget-api": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz", - "integrity": "sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==", - "requires": { - "@types/events": "^3.0.0", - "events": "^3.2.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "mixin-deep": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", - "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==" - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "requires": { - "ee-first": "1.1.1" - } - } - } - }, - "napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "node-abi": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.40.0.tgz", - "integrity": "sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA==", - "requires": { - "semver": "^7.3.5" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "requires": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "requires": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - } - }, - "pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "re-emitter": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.4.tgz", - "integrity": "sha512-C0SIXdXDSus2yqqvV7qifnb4NoWP7mEBXJq3axci301mXHCZb8Djwm4hrEZo4UeXRaEnfjH98uQ8EBppk2oNWA==", - "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - } - }, - "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sdp-transform": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz", - "integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==" - }, - "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "simple-markdown": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.7.3.tgz", - "integrity": "sha512-uGXIc13NGpqfPeFJIt/7SHHxd6HekEJYtsdoCM06mEBPL9fQH/pSD7LRM6PZ7CKchpSvxKL4tvwMamqAaNDAyg==", - "requires": { - "@types/react": ">=16.0.0" - } - }, - "snowtransfer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.0.tgz", - "integrity": "sha512-vc7B46tO4QeK99z/pN8ISd8QvO9QB3Oo4qP7nYYhriIMOtjYkHMi8t6kUBPIJLbeX+h0NpfwxaGJfXNLm1ZQ5A==", - "requires": { - "centra": "^2.6.0", - "discord-api-types": "^0.37.31", - "form-data": "^4.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "requires": { - "through": "2" - } - }, - "stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "requires": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "requires": { - "internal-slot": "^1.0.4" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" - }, - "supertape": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/supertape/-/supertape-8.3.0.tgz", - "integrity": "sha512-dcMylmkr1Mctr5UBCrlvZynuBRuLvlkWJLGXdL/PcI41BERnObO+kV0PeZhH5n6lwVnvK2xfvZyN32WIAPf/tw==", - "dev": true, - "requires": { - "@cloudcmd/stub": "^4.0.0", - "@putout/cli-keypress": "^1.0.0", - "@putout/cli-validate-args": "^1.0.1", - "@supertape/engine-loader": "^1.0.0", - "@supertape/formatter-fail": "^3.0.0", - "@supertape/formatter-json-lines": "^2.0.0", - "@supertape/formatter-progress-bar": "^3.0.0", - "@supertape/formatter-short": "^2.0.0", - "@supertape/formatter-tap": "^3.0.0", - "@supertape/operator-stub": "^3.0.0", - "cli-progress": "^3.8.2", - "deep-equal": "^2.0.3", - "fullstore": "^3.0.0", - "glob": "^8.0.3", - "jest-diff": "^29.0.1", - "once": "^1.4.0", - "resolve": "^1.17.0", - "stacktracey": "^2.1.7", - "strip-ansi": "^7.0.0", - "try-to-catch": "^3.0.0", - "wraptile": "^3.0.0", - "yargs-parser": "^21.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tap-dot": { - "version": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "integrity": "sha512-tHte0Cqt0Unnfz3zbhtk8ByNoh9KA7xXKWIC6/UUNJcyueR9DBlTx1YCH6TH7rIKaz8aBNqCV9HCCpAWilOOAQ==", - "dev": true, - "from": "tap-dot@github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "requires": { - "colorette": "^1.0.5", - "tap-out": "github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771" - } - }, - "tap-out": { - "version": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", - "dev": true, - "from": "tap-out@github:cloudrac3r/tap-out#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "requires": { - "re-emitter": "1.1.4", - "readable-stream": "^4.3.0", - "split": "^1.0.1" - }, - "dependencies": { - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - } - } - } - }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "try-catch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz", - "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==", - "dev": true - }, - "try-to-catch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", - "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "unhomoglyph": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", - "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "wraptile": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/wraptile/-/wraptile-3.0.0.tgz", - "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } } } diff --git a/package.json b/package.json index e3ca70f..f483842 100644 --- a/package.json +++ b/package.json @@ -18,21 +18,23 @@ "better-sqlite3": "^8.3.0", "cloudstorm": "^0.7.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", - "heatsync": "^2.4.0", + "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0" + "snowtransfer": "^0.7.0", + "try-to-catch": "^3.0.1" }, "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" + "test": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" } } diff --git a/test/test.js b/test/test.js index de399f3..3008abf 100644 --- a/test/test.js +++ b/test/test.js @@ -8,8 +8,9 @@ const passthrough = require("../passthrough") const db = new sqlite("db/ooye.db") // @ts-ignore -const sync = new HeatSync({persistent: false}) +const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) require("../d2m/actions/create-room.test") +require("../d2m/converters/user-to-mxid.test") \ No newline at end of file From 672fe7c2fb23ed45a317fd87ddc7d1257ce46d27 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 May 2023 23:37:51 +1200 Subject: [PATCH 011/200] switch to using api functions over mreq --- .vscode/tasks.json | 12 ++-------- d2m/actions/create-room.js | 14 +++++------- d2m/actions/create-space.js | 8 +++---- d2m/actions/register-user.js | 8 +++++-- d2m/converters/message-to-event.js | 4 ++-- d2m/converters/user-to-mxid.js | 3 ++- matrix/api.js | 35 +++++++++++++++++++++++++++++- stdin.js | 7 +++--- types.d.ts | 4 ++++ 9 files changed, 64 insertions(+), 31 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bb95546..9a830be 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,15 +10,7 @@ }, "problemMatcher": [], "label": "npm: test", - "detail": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot", - "presentation": { - "echo": false, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": false, - "clear": true - } + "detail": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" } ] -} \ No newline at end of file +} diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 214d026..7e9abed 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -5,10 +5,10 @@ const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough -/** @type {import("../../matrix/mreq")} */ -const mreq = sync.require("../../matrix/mreq") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") function kstateStripConditionals(kstate) { for (const [k, content] of Object.entries(kstate)) { @@ -51,8 +51,7 @@ function stateToKState(events) { * @param {string} roomID */ async function roomToKState(roomID) { - /** @type {import("../../types").Event.BaseStateEvent[]} */ - const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) + const root = await api.getAllState(roomID) return stateToKState(root) } @@ -63,7 +62,7 @@ async function roomToKState(roomID) { function applyKStateDiffToRoom(roomID, kstate) { const events = kstateToState(kstate) return Promise.all(events.map(({type, state_key, content}) => - mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${state_key}`, content) + api.sendState(roomID, type, state_key, content) )) } @@ -131,8 +130,7 @@ async function channelToKState(channel, guild) { * @param {any} kstate */ async function createRoom(channel, guild, spaceID, kstate) { - /** @type {import("../../types").R.RoomCreated} */ - const root = await mreq.mreq("POST", "/client/v3/createRoom", { + const root = await api.createRoom({ name: channel.name, topic: channel.topic || undefined, preset: "private_chat", @@ -144,7 +142,7 @@ async function createRoom(channel, guild, spaceID, kstate) { db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) // Put the newly created child into the space - await mreq.mreq("PUT", `/client/v3/rooms/${spaceID}/state/m.space.child/${root.room_id}`, { + await api.sendState(spaceID, "m.space.child", root.room_id, { via: ["cadence.moe"] // TODO: use the proper server }) } diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index b3d7e95..517dad4 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -2,14 +2,14 @@ const passthrough = require("../../passthrough") const { sync, db } = passthrough -/** @type {import("../../matrix/mreq")} */ -const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ function createSpace(guild) { - return mreq.mreq("POST", "/client/v3/createRoom", { + return api.createRoom({ name: guild.name, preset: "private_chat", visibility: "private", @@ -37,7 +37,7 @@ function createSpace(guild) { } } ] - }).then(/** @param {import("../../types").R.RoomCreated} root */ root => { + }).then(root => { db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) return root }) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 8f31f23..f970218 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -8,12 +8,16 @@ const { discord, sync, db } = passthrough const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +/** @type {import("../converters/user-to-mxid")} */ +const userToMxid = sync.require("../converters/user-to-mxid") /** * A sim is an account that is being simulated by the bridge to copy events from the other side. * @param {import("discord-api-types/v10").APIUser} user */ async function createSim(user) { - assert.notEqual(user.discriminator, "0000", "user is not a webhook") - api.register("_ooye_example") + const simName = userToMxid.userToSimName(user) + const appservicePrefix = "_ooye_" + const localpart = appservicePrefix + simName + await api.register(localpart) } diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 6f9bc37..dd6aabc 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -4,7 +4,7 @@ const markdown = require("discord-markdown") /** * @param {import("discord-api-types/v10").APIMessage} message - * @returns {import("../../types").M_Room_Message_content} + * @returns {import("../../types").Event.M_Room_Message} */ function messageToEvent(message) { const body = message.content @@ -25,4 +25,4 @@ function messageToEvent(message) { } } -module.exports.messageToEvent = messageToEvent \ No newline at end of file +module.exports.messageToEvent = messageToEvent diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js index 15b997f..35d9368 100644 --- a/d2m/converters/user-to-mxid.js +++ b/d2m/converters/user-to-mxid.js @@ -43,6 +43,7 @@ function* generateLocalpartAlternatives(preferences) { } /** + * Whole process for checking the database and generating the right sim name. * @param {import("discord-api-types/v10").APIUser} user * @returns {string} */ @@ -71,4 +72,4 @@ function userToSimName(user) { throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`) } -module.exports.userToSimName = userToSimName \ No newline at end of file +module.exports.userToSimName = userToSimName diff --git a/matrix/api.js b/matrix/api.js index 4335585..88c62d8 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -1,5 +1,7 @@ // @ts-check +const assert = require("assert") + const passthrough = require("../passthrough") const { discord, sync, db } = passthrough /** @type {import("./mreq")} */ @@ -8,6 +10,7 @@ const mreq = sync.require("./mreq") const file = sync.require("./file") /** + * @param {string} username * @returns {Promise} */ function register(username) { @@ -17,4 +20,34 @@ function register(username) { }) } -module.exports.register = register \ No newline at end of file +/** + * @returns {Promise} + */ +function createRoom(content) { + return mreq.mreq("POST", "/client/v3/createRoom", content) +} + +/** + * @param {string} roomID + * @returns {Promise} + */ +function getAllState(roomID) { + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) +} + +/** + * @param {string} roomID + * @param {string} type + * @param {string} stateKey + * @returns {Promise} + */ +function sendState(roomID, type, stateKey, content) { + assert.ok(type) + assert.ok(stateKey) + return mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, content) +} + +module.exports.register = register +module.exports.createRoom = createRoom +module.exports.getAllState = getAllState +module.exports.sendState = sendState diff --git a/stdin.js b/stdin.js index a57c044..551d7d2 100644 --- a/stdin.js +++ b/stdin.js @@ -6,9 +6,10 @@ const util = require("util") const passthrough = require("./passthrough") const { discord, config, sync, db } = passthrough -const createSpace = sync.require("./d2m/actions/create-space.js") -const createRoom = sync.require("./d2m/actions/create-room.js") -const mreq = sync.require("./matrix/mreq.js") +const createSpace = sync.require("./d2m/actions/create-space") +const createRoom = sync.require("./d2m/actions/create-room") +const mreq = sync.require("./matrix/mreq") +const api = sync.require("./matrix/api") const guildID = "112760669178241024" const extraContext = {} diff --git a/types.d.ts b/types.d.ts index 8d15d6b..b3a9acc 100644 --- a/types.d.ts +++ b/types.d.ts @@ -54,4 +54,8 @@ namespace R { access_token: string device_id: string } + + export type EventSent = { + event_id: string + } } From 3bc29def419911d016ea7d42fcdcc31257d7048a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 May 2023 23:37:51 +1200 Subject: [PATCH 012/200] switch to using api functions over mreq --- .vscode/tasks.json | 12 ++-------- d2m/actions/create-room.js | 14 +++++------- d2m/actions/create-space.js | 8 +++---- d2m/actions/register-user.js | 8 +++++-- d2m/converters/message-to-event.js | 4 ++-- d2m/converters/user-to-mxid.js | 3 ++- matrix/api.js | 35 +++++++++++++++++++++++++++++- stdin.js | 7 +++--- types.d.ts | 4 ++++ 9 files changed, 64 insertions(+), 31 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bb95546..9a830be 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,15 +10,7 @@ }, "problemMatcher": [], "label": "npm: test", - "detail": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot", - "presentation": { - "echo": false, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": false, - "clear": true - } + "detail": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" } ] -} \ No newline at end of file +} diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 214d026..7e9abed 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -5,10 +5,10 @@ const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough -/** @type {import("../../matrix/mreq")} */ -const mreq = sync.require("../../matrix/mreq") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") function kstateStripConditionals(kstate) { for (const [k, content] of Object.entries(kstate)) { @@ -51,8 +51,7 @@ function stateToKState(events) { * @param {string} roomID */ async function roomToKState(roomID) { - /** @type {import("../../types").Event.BaseStateEvent[]} */ - const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) + const root = await api.getAllState(roomID) return stateToKState(root) } @@ -63,7 +62,7 @@ async function roomToKState(roomID) { function applyKStateDiffToRoom(roomID, kstate) { const events = kstateToState(kstate) return Promise.all(events.map(({type, state_key, content}) => - mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${state_key}`, content) + api.sendState(roomID, type, state_key, content) )) } @@ -131,8 +130,7 @@ async function channelToKState(channel, guild) { * @param {any} kstate */ async function createRoom(channel, guild, spaceID, kstate) { - /** @type {import("../../types").R.RoomCreated} */ - const root = await mreq.mreq("POST", "/client/v3/createRoom", { + const root = await api.createRoom({ name: channel.name, topic: channel.topic || undefined, preset: "private_chat", @@ -144,7 +142,7 @@ async function createRoom(channel, guild, spaceID, kstate) { db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) // Put the newly created child into the space - await mreq.mreq("PUT", `/client/v3/rooms/${spaceID}/state/m.space.child/${root.room_id}`, { + await api.sendState(spaceID, "m.space.child", root.room_id, { via: ["cadence.moe"] // TODO: use the proper server }) } diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index b3d7e95..517dad4 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -2,14 +2,14 @@ const passthrough = require("../../passthrough") const { sync, db } = passthrough -/** @type {import("../../matrix/mreq")} */ -const mreq = sync.require("../../matrix/mreq") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ function createSpace(guild) { - return mreq.mreq("POST", "/client/v3/createRoom", { + return api.createRoom({ name: guild.name, preset: "private_chat", visibility: "private", @@ -37,7 +37,7 @@ function createSpace(guild) { } } ] - }).then(/** @param {import("../../types").R.RoomCreated} root */ root => { + }).then(root => { db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) return root }) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 8f31f23..f970218 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -8,12 +8,16 @@ const { discord, sync, db } = passthrough const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +/** @type {import("../converters/user-to-mxid")} */ +const userToMxid = sync.require("../converters/user-to-mxid") /** * A sim is an account that is being simulated by the bridge to copy events from the other side. * @param {import("discord-api-types/v10").APIUser} user */ async function createSim(user) { - assert.notEqual(user.discriminator, "0000", "user is not a webhook") - api.register("_ooye_example") + const simName = userToMxid.userToSimName(user) + const appservicePrefix = "_ooye_" + const localpart = appservicePrefix + simName + await api.register(localpart) } diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 6f9bc37..dd6aabc 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -4,7 +4,7 @@ const markdown = require("discord-markdown") /** * @param {import("discord-api-types/v10").APIMessage} message - * @returns {import("../../types").M_Room_Message_content} + * @returns {import("../../types").Event.M_Room_Message} */ function messageToEvent(message) { const body = message.content @@ -25,4 +25,4 @@ function messageToEvent(message) { } } -module.exports.messageToEvent = messageToEvent \ No newline at end of file +module.exports.messageToEvent = messageToEvent diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js index 15b997f..35d9368 100644 --- a/d2m/converters/user-to-mxid.js +++ b/d2m/converters/user-to-mxid.js @@ -43,6 +43,7 @@ function* generateLocalpartAlternatives(preferences) { } /** + * Whole process for checking the database and generating the right sim name. * @param {import("discord-api-types/v10").APIUser} user * @returns {string} */ @@ -71,4 +72,4 @@ function userToSimName(user) { throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`) } -module.exports.userToSimName = userToSimName \ No newline at end of file +module.exports.userToSimName = userToSimName diff --git a/matrix/api.js b/matrix/api.js index 4335585..88c62d8 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -1,5 +1,7 @@ // @ts-check +const assert = require("assert") + const passthrough = require("../passthrough") const { discord, sync, db } = passthrough /** @type {import("./mreq")} */ @@ -8,6 +10,7 @@ const mreq = sync.require("./mreq") const file = sync.require("./file") /** + * @param {string} username * @returns {Promise} */ function register(username) { @@ -17,4 +20,34 @@ function register(username) { }) } -module.exports.register = register \ No newline at end of file +/** + * @returns {Promise} + */ +function createRoom(content) { + return mreq.mreq("POST", "/client/v3/createRoom", content) +} + +/** + * @param {string} roomID + * @returns {Promise} + */ +function getAllState(roomID) { + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) +} + +/** + * @param {string} roomID + * @param {string} type + * @param {string} stateKey + * @returns {Promise} + */ +function sendState(roomID, type, stateKey, content) { + assert.ok(type) + assert.ok(stateKey) + return mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, content) +} + +module.exports.register = register +module.exports.createRoom = createRoom +module.exports.getAllState = getAllState +module.exports.sendState = sendState diff --git a/stdin.js b/stdin.js index a57c044..551d7d2 100644 --- a/stdin.js +++ b/stdin.js @@ -6,9 +6,10 @@ const util = require("util") const passthrough = require("./passthrough") const { discord, config, sync, db } = passthrough -const createSpace = sync.require("./d2m/actions/create-space.js") -const createRoom = sync.require("./d2m/actions/create-room.js") -const mreq = sync.require("./matrix/mreq.js") +const createSpace = sync.require("./d2m/actions/create-space") +const createRoom = sync.require("./d2m/actions/create-room") +const mreq = sync.require("./matrix/mreq") +const api = sync.require("./matrix/api") const guildID = "112760669178241024" const extraContext = {} diff --git a/types.d.ts b/types.d.ts index 8d15d6b..b3a9acc 100644 --- a/types.d.ts +++ b/types.d.ts @@ -54,4 +54,8 @@ namespace R { access_token: string device_id: string } + + export type EventSent = { + event_id: string + } } From 427e82f2d43721e55911b0ea67897086c47db946 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 00:58:46 +1200 Subject: [PATCH 013/200] register -> invite -> join -> send flow --- d2m/actions/create-space.js | 9 +++-- d2m/actions/register-user.js | 53 ++++++++++++++++++++++++++-- d2m/actions/send-message.js | 34 +++++++++--------- d2m/converters/user-to-mxid.test.js | 2 +- db/ooye.db | Bin 61440 -> 90112 bytes matrix/api.js | 50 ++++++++++++++++++++++---- matrix/api.test.js | 23 ++++++++++++ matrix/mreq.js | 2 -- matrix/txnid.js | 2 +- stdin.js | 1 + test/test.js | 3 +- types.d.ts | 5 +++ 12 files changed, 150 insertions(+), 34 deletions(-) create mode 100644 matrix/api.test.js diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 517dad4..4218c1f 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -8,8 +8,8 @@ const api = sync.require("../../matrix/api") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ -function createSpace(guild) { - return api.createRoom({ +async function createSpace(guild) { + const roomID = api.createRoom({ name: guild.name, preset: "private_chat", visibility: "private", @@ -37,10 +37,9 @@ function createSpace(guild) { } } ] - }).then(root => { - db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) - return root }) + db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID) + return roomID } module.exports.createSpace = createSpace diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index f970218..a5fd0ef 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -1,6 +1,7 @@ // @ts-check const assert = require("assert") +const reg = require("../../matrix/read-registration") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough @@ -14,10 +15,58 @@ const userToMxid = sync.require("../converters/user-to-mxid") /** * A sim is an account that is being simulated by the bridge to copy events from the other side. * @param {import("discord-api-types/v10").APIUser} user + * @returns mxid */ async function createSim(user) { + // Choose sim name const simName = userToMxid.userToSimName(user) - const appservicePrefix = "_ooye_" - const localpart = appservicePrefix + simName + const localpart = reg.namespace_prefix + simName + const mxid = "@" + localpart + ":cadence.moe" + + // Save chosen name in the database forever + 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) + return mxid } + +/** + * 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. + * @returns mxid + */ +async function ensureSim(user) { + let mxid = null + const existing = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(user.id) + if (existing) { + mxid = existing + } else { + mxid = await createSim(user) + } + return mxid +} + +/** + * Ensure a sim is registered for the user and is joined to the room. + * @returns mxid + */ +async function ensureSimJoined(user, roomID) { + // Ensure room ID is really an ID, not an alias + assert.ok(roomID[0] === "!") + + // Ensure user + const mxid = await ensureSim(user) + + // Ensure joined + const existing = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, mxid) + if (!existing) { + await api.inviteToRoom(roomID, mxid) + await api.joinRoom(roomID, mxid) + db.prepare("INSERT INTO sim_member (room_id, mxid) VALUES (?, ?)").run(roomID, mxid) + } + return mxid +} + +module.exports.ensureSim = ensureSim +module.exports.ensureSimJoined = ensureSimJoined diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 1f71a66..0a425ee 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -1,27 +1,29 @@ // @ts-check -const reg = require("../../matrix/read-registration.js") -const makeTxnId = require("../../matrix/txnid.js") const fetch = require("node-fetch").default -const messageToEvent = require("../converters/message-to-event.js") +const reg = require("../../matrix/read-registration.js") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../converters/message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") +/** @type {import("./register-user")} */ +const registerUser = sync.require("./register-user") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ -function sendMessage(message) { +async function sendMessage(message) { const event = messageToEvent.messageToEvent(message) - return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { - method: "PUT", - body: JSON.stringify(event), - headers: { - Authorization: `Bearer ${reg.as_token}` - } - }).then(res => res.text()).then(text => { - // {"event_id":"$4Zxs0fMmYlbo-sTlMmSEvwIs9b4hcg6yORzK0Ems84Q"} - console.log(text) - }).catch(err => { - console.log(err) - }) + const roomID = "!VwVlIAjOjejUpDhlbA:cadence.moe" + let senderMxid = null + if (!message.webhook_id) { + senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + } + const eventID = api.sendEvent(roomID, "m.room.message", event, senderMxid) + return eventID } module.exports.sendMessage = sendMessage diff --git a/d2m/converters/user-to-mxid.test.js b/d2m/converters/user-to-mxid.test.js index 7cda6d7..4c721fc 100644 --- a/d2m/converters/user-to-mxid.test.js +++ b/d2m/converters/user-to-mxid.test.js @@ -13,7 +13,7 @@ test("user2name: works on normal name", t => { }) test("user2name: works on emojis", t => { - t.equal(userToSimName({username: "Cookie 🍪", discriminator: "0001"}), "cookie") + t.equal(userToSimName({username: "🍪 Cookie Monster 🍪", discriminator: "0001"}), "cookie_monster") }) test("user2name: works on crazy name", t => { diff --git a/db/ooye.db b/db/ooye.db index bb571ed8e1eb7be214c5f0378822d6fd873aded8..c9803f8c5b23166716dfaf9fc2e2ef32cd8010f0 100644 GIT binary patch delta 2608 zcmb_d+i%-s6u0x)Y2zk7ZPPZ(O4TGHC{=I1C2`uZHqAv;bxAu7 z9yYHap@}C-{s1(@<+Z39kARRS@iO3PJRuMQBqjy|UO-G6pW`@jG2L1q(I=hTch2uz zzT+!jqbncLuXVRA5(LqO58#9F@ox`^;vzBh+-;w?ooHot2v8u#{^I+b-C-|!@A%&J zU2d6f`O@3l5@qf&UobnKUyc8J+e^*k%st}w!0<5na&R-ZP{iDAgL~X%W+%(IBQDMIF&2 zhD_(N%)iA#Qy?5Bw-(JpESHp$T3S%cSXrZ5GqaQFxyjI6dSZGq6lo|Tp%~-AHNdHR z<|dz>3!OTR&;0cC02Y@ut)%>u=FIHLr_!_MLOoNH=VIpVfx0y=q1fsUWWz=YM;f6N z18faX8~X#{3;6xy)|rl`z}8ft$zZHbE|;+7!@A`*yS01A8`x!tRy<+_2*J*78`lo{ zgp1~b(J`JEMTz4QGM`Gy@sudYc}-hgRuxTqQB_QO+|G>Ta|LxRuMU+oHAd3l?@e(F z+NNoOiJCE{?3Q@0BjG_p7C1Q(7ZO5T6A~O37vgfhd|@rWq%3Nu5Y2}Nvkeo6MWKen zx>2xz)dlvD431e%`cL>4Ztk{?e+Zc_#FNed$PhaV;i3?DIKvD6x$t4}@nJ`w0nef^ zSw~(#3<{EFtz2&8_u%Ax*p_1N6L14`7-#R}qW{e5V-(6xJrH&H?8URilj)VyE9%Pp z`iZ6DLfW3x;|OIe2RKSOmzTIoN`Y207Q?4Wzjm~J;*CBRrZ z@55Di!&p7sCg{eFakkHGh@HKls~gEtLdp;ya(|hKuKNi182*6k@DqFmw=kq@pm%4|>LiV3Q4d{rW|VCL-h$s?8@_>?@Bt2T3G{ZuFGiGuOAMfP*JbX;5 ze3Q6UnE6$CCvhvV8Um&HHoxT&-^|9y|DA#V`({ChoBR_uhzPSYGV==?B;}WI0o}sL i|C@pTH&jl7pM{y9(*Pp=lY##yR9uXmjairzr~?32qBMN~ diff --git a/matrix/api.js b/matrix/api.js index 88c62d8..e3a2600 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -8,6 +8,15 @@ const { discord, sync, db } = passthrough const mreq = sync.require("./mreq") /** @type {import("./file")} */ const file = sync.require("./file") +/** @type {import("./txnid")} */ +const makeTxnId = sync.require("./txnid") + +function path(p, mxid = null) { + if (!mxid) return p + const u = new URL(p, "http://localhost") + u.searchParams.set("user_id", mxid) + return u.pathname + "?" + u.searchParams.toString() +} /** * @param {string} username @@ -21,10 +30,27 @@ function register(username) { } /** - * @returns {Promise} + * @returns {Promise} room ID */ -function createRoom(content) { - return mreq.mreq("POST", "/client/v3/createRoom", content) +async function createRoom(content) { + /** @type {import("../types").R.RoomCreated} */ + const root = await mreq.mreq("POST", "/client/v3/createRoom", content) + return root.room_id +} + +/** + * @returns {Promise} room ID + */ +async function joinRoom(roomIDOrAlias, mxid) { + /** @type {import("../types").R.RoomJoined} */ + const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) + return root.room_id +} + +async function inviteToRoom(roomID, mxidToInvite, mxid) { + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/invite`, mxid), { + user_id: mxidToInvite + }) } /** @@ -39,15 +65,27 @@ function getAllState(roomID) { * @param {string} roomID * @param {string} type * @param {string} stateKey - * @returns {Promise} + * @returns {Promise} event ID */ -function sendState(roomID, type, stateKey, content) { +async function sendState(roomID, type, stateKey, content, mxid) { assert.ok(type) assert.ok(stateKey) - return mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, content) + /** @type {import("../types").R.EventSent} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) + return root.event_id } +async function sendEvent(roomID, type, content, mxid) { + /** @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 +} + +module.exports.path = path module.exports.register = register module.exports.createRoom = createRoom +module.exports.joinRoom = joinRoom +module.exports.inviteToRoom = inviteToRoom module.exports.getAllState = getAllState module.exports.sendState = sendState +module.exports.sendEvent = sendEvent diff --git a/matrix/api.test.js b/matrix/api.test.js new file mode 100644 index 0000000..f54c665 --- /dev/null +++ b/matrix/api.test.js @@ -0,0 +1,23 @@ +const {test} = require("supertape") +const assert = require("assert") +const {path} = require("./api") + +test("api path: no change for plain path", t => { + t.equal(path("/hello/world"), "/hello/world") +}) + +test("api path: add mxid to the URL", t => { + t.equal(path("/hello/world", "12345"), "/hello/world?user_id=12345") +}) + +test("api path: empty path with mxid", t => { + t.equal(path("", "12345"), "/?user_id=12345") +}) + +test("api path: existing query parameters with mxid", t => { + t.equal(path("/hello/world?foo=bar&baz=qux", "12345"), "/hello/world?foo=bar&baz=qux&user_id=12345") +}) + +test("api path: real world mxid", t => { + t.equal(path("/hello/world", "@cookie_monster:cadence.moe"), "/hello/world?user_id=%40cookie_monster%3Acadence.moe") +}) diff --git a/matrix/mreq.js b/matrix/mreq.js index 1345f78..6c4eaa3 100644 --- a/matrix/mreq.js +++ b/matrix/mreq.js @@ -16,8 +16,6 @@ class MatrixServerError extends Error { this.data = data /** @type {string} */ this.errcode = data.errcode - /** @type {string} */ - this.error = data.error } } diff --git a/matrix/txnid.js b/matrix/txnid.js index 1e26378..a3568df 100644 --- a/matrix/txnid.js +++ b/matrix/txnid.js @@ -2,6 +2,6 @@ let now = Date.now() -module.exports = function makeTxnId() { +module.exports.makeTxnId = function makeTxnId() { return now++ } diff --git a/stdin.js b/stdin.js index 551d7d2..99345ab 100644 --- a/stdin.js +++ b/stdin.js @@ -8,6 +8,7 @@ const { discord, config, sync, db } = passthrough const createSpace = sync.require("./d2m/actions/create-space") const createRoom = sync.require("./d2m/actions/create-room") +const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") const guildID = "112760669178241024" diff --git a/test/test.js b/test/test.js index 3008abf..1068136 100644 --- a/test/test.js +++ b/test/test.js @@ -13,4 +13,5 @@ const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) require("../d2m/actions/create-room.test") -require("../d2m/converters/user-to-mxid.test") \ No newline at end of file +require("../d2m/converters/user-to-mxid.test") +require("../matrix/api.test") diff --git a/types.d.ts b/types.d.ts index b3a9acc..bc24329 100644 --- a/types.d.ts +++ b/types.d.ts @@ -4,6 +4,7 @@ export type AppServiceRegistrationConfig = { hs_token: string url: string sender_localpart: string + namespace_prefix: string protocols: [string] rate_limited: boolean } @@ -43,6 +44,10 @@ namespace R { room_id: string } + export type RoomJoined = { + room_id: string + } + export type FileUploaded = { content_uri: string } From 1e7e66dc31a6c6a4d1c83399f9e7551d86913f93 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 00:58:46 +1200 Subject: [PATCH 014/200] register -> invite -> join -> send flow --- d2m/actions/create-space.js | 9 +++-- d2m/actions/register-user.js | 53 +++++++++++++++++++++++++++-- d2m/actions/send-message.js | 34 +++++++++--------- d2m/converters/user-to-mxid.test.js | 2 +- matrix/api.js | 50 +++++++++++++++++++++++---- matrix/api.test.js | 23 +++++++++++++ matrix/mreq.js | 2 -- matrix/txnid.js | 2 +- stdin.js | 1 + test/test.js | 3 +- types.d.ts | 5 +++ 11 files changed, 150 insertions(+), 34 deletions(-) create mode 100644 matrix/api.test.js diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 517dad4..4218c1f 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -8,8 +8,8 @@ const api = sync.require("../../matrix/api") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ -function createSpace(guild) { - return api.createRoom({ +async function createSpace(guild) { + const roomID = api.createRoom({ name: guild.name, preset: "private_chat", visibility: "private", @@ -37,10 +37,9 @@ function createSpace(guild) { } } ] - }).then(root => { - db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id) - return root }) + db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID) + return roomID } module.exports.createSpace = createSpace diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index f970218..a5fd0ef 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -1,6 +1,7 @@ // @ts-check const assert = require("assert") +const reg = require("../../matrix/read-registration") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough @@ -14,10 +15,58 @@ const userToMxid = sync.require("../converters/user-to-mxid") /** * A sim is an account that is being simulated by the bridge to copy events from the other side. * @param {import("discord-api-types/v10").APIUser} user + * @returns mxid */ async function createSim(user) { + // Choose sim name const simName = userToMxid.userToSimName(user) - const appservicePrefix = "_ooye_" - const localpart = appservicePrefix + simName + const localpart = reg.namespace_prefix + simName + const mxid = "@" + localpart + ":cadence.moe" + + // Save chosen name in the database forever + 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) + return mxid } + +/** + * 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. + * @returns mxid + */ +async function ensureSim(user) { + let mxid = null + const existing = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(user.id) + if (existing) { + mxid = existing + } else { + mxid = await createSim(user) + } + return mxid +} + +/** + * Ensure a sim is registered for the user and is joined to the room. + * @returns mxid + */ +async function ensureSimJoined(user, roomID) { + // Ensure room ID is really an ID, not an alias + assert.ok(roomID[0] === "!") + + // Ensure user + const mxid = await ensureSim(user) + + // Ensure joined + const existing = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, mxid) + if (!existing) { + await api.inviteToRoom(roomID, mxid) + await api.joinRoom(roomID, mxid) + db.prepare("INSERT INTO sim_member (room_id, mxid) VALUES (?, ?)").run(roomID, mxid) + } + return mxid +} + +module.exports.ensureSim = ensureSim +module.exports.ensureSimJoined = ensureSimJoined diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 1f71a66..0a425ee 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -1,27 +1,29 @@ // @ts-check -const reg = require("../../matrix/read-registration.js") -const makeTxnId = require("../../matrix/txnid.js") const fetch = require("node-fetch").default -const messageToEvent = require("../converters/message-to-event.js") +const reg = require("../../matrix/read-registration.js") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../converters/message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") +/** @type {import("./register-user")} */ +const registerUser = sync.require("./register-user") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ -function sendMessage(message) { +async function sendMessage(message) { const event = messageToEvent.messageToEvent(message) - return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, { - method: "PUT", - body: JSON.stringify(event), - headers: { - Authorization: `Bearer ${reg.as_token}` - } - }).then(res => res.text()).then(text => { - // {"event_id":"$4Zxs0fMmYlbo-sTlMmSEvwIs9b4hcg6yORzK0Ems84Q"} - console.log(text) - }).catch(err => { - console.log(err) - }) + const roomID = "!VwVlIAjOjejUpDhlbA:cadence.moe" + let senderMxid = null + if (!message.webhook_id) { + senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + } + const eventID = api.sendEvent(roomID, "m.room.message", event, senderMxid) + return eventID } module.exports.sendMessage = sendMessage diff --git a/d2m/converters/user-to-mxid.test.js b/d2m/converters/user-to-mxid.test.js index 7cda6d7..4c721fc 100644 --- a/d2m/converters/user-to-mxid.test.js +++ b/d2m/converters/user-to-mxid.test.js @@ -13,7 +13,7 @@ test("user2name: works on normal name", t => { }) test("user2name: works on emojis", t => { - t.equal(userToSimName({username: "Cookie 🍪", discriminator: "0001"}), "cookie") + t.equal(userToSimName({username: "🍪 Cookie Monster 🍪", discriminator: "0001"}), "cookie_monster") }) test("user2name: works on crazy name", t => { diff --git a/matrix/api.js b/matrix/api.js index 88c62d8..e3a2600 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -8,6 +8,15 @@ const { discord, sync, db } = passthrough const mreq = sync.require("./mreq") /** @type {import("./file")} */ const file = sync.require("./file") +/** @type {import("./txnid")} */ +const makeTxnId = sync.require("./txnid") + +function path(p, mxid = null) { + if (!mxid) return p + const u = new URL(p, "http://localhost") + u.searchParams.set("user_id", mxid) + return u.pathname + "?" + u.searchParams.toString() +} /** * @param {string} username @@ -21,10 +30,27 @@ function register(username) { } /** - * @returns {Promise} + * @returns {Promise} room ID */ -function createRoom(content) { - return mreq.mreq("POST", "/client/v3/createRoom", content) +async function createRoom(content) { + /** @type {import("../types").R.RoomCreated} */ + const root = await mreq.mreq("POST", "/client/v3/createRoom", content) + return root.room_id +} + +/** + * @returns {Promise} room ID + */ +async function joinRoom(roomIDOrAlias, mxid) { + /** @type {import("../types").R.RoomJoined} */ + const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) + return root.room_id +} + +async function inviteToRoom(roomID, mxidToInvite, mxid) { + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/invite`, mxid), { + user_id: mxidToInvite + }) } /** @@ -39,15 +65,27 @@ function getAllState(roomID) { * @param {string} roomID * @param {string} type * @param {string} stateKey - * @returns {Promise} + * @returns {Promise} event ID */ -function sendState(roomID, type, stateKey, content) { +async function sendState(roomID, type, stateKey, content, mxid) { assert.ok(type) assert.ok(stateKey) - return mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, content) + /** @type {import("../types").R.EventSent} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) + return root.event_id } +async function sendEvent(roomID, type, content, mxid) { + /** @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 +} + +module.exports.path = path module.exports.register = register module.exports.createRoom = createRoom +module.exports.joinRoom = joinRoom +module.exports.inviteToRoom = inviteToRoom module.exports.getAllState = getAllState module.exports.sendState = sendState +module.exports.sendEvent = sendEvent diff --git a/matrix/api.test.js b/matrix/api.test.js new file mode 100644 index 0000000..f54c665 --- /dev/null +++ b/matrix/api.test.js @@ -0,0 +1,23 @@ +const {test} = require("supertape") +const assert = require("assert") +const {path} = require("./api") + +test("api path: no change for plain path", t => { + t.equal(path("/hello/world"), "/hello/world") +}) + +test("api path: add mxid to the URL", t => { + t.equal(path("/hello/world", "12345"), "/hello/world?user_id=12345") +}) + +test("api path: empty path with mxid", t => { + t.equal(path("", "12345"), "/?user_id=12345") +}) + +test("api path: existing query parameters with mxid", t => { + t.equal(path("/hello/world?foo=bar&baz=qux", "12345"), "/hello/world?foo=bar&baz=qux&user_id=12345") +}) + +test("api path: real world mxid", t => { + t.equal(path("/hello/world", "@cookie_monster:cadence.moe"), "/hello/world?user_id=%40cookie_monster%3Acadence.moe") +}) diff --git a/matrix/mreq.js b/matrix/mreq.js index 1345f78..6c4eaa3 100644 --- a/matrix/mreq.js +++ b/matrix/mreq.js @@ -16,8 +16,6 @@ class MatrixServerError extends Error { this.data = data /** @type {string} */ this.errcode = data.errcode - /** @type {string} */ - this.error = data.error } } diff --git a/matrix/txnid.js b/matrix/txnid.js index 1e26378..a3568df 100644 --- a/matrix/txnid.js +++ b/matrix/txnid.js @@ -2,6 +2,6 @@ let now = Date.now() -module.exports = function makeTxnId() { +module.exports.makeTxnId = function makeTxnId() { return now++ } diff --git a/stdin.js b/stdin.js index 551d7d2..99345ab 100644 --- a/stdin.js +++ b/stdin.js @@ -8,6 +8,7 @@ const { discord, config, sync, db } = passthrough const createSpace = sync.require("./d2m/actions/create-space") const createRoom = sync.require("./d2m/actions/create-room") +const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") const guildID = "112760669178241024" diff --git a/test/test.js b/test/test.js index 3008abf..1068136 100644 --- a/test/test.js +++ b/test/test.js @@ -13,4 +13,5 @@ const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) require("../d2m/actions/create-room.test") -require("../d2m/converters/user-to-mxid.test") \ No newline at end of file +require("../d2m/converters/user-to-mxid.test") +require("../matrix/api.test") diff --git a/types.d.ts b/types.d.ts index b3a9acc..bc24329 100644 --- a/types.d.ts +++ b/types.d.ts @@ -4,6 +4,7 @@ export type AppServiceRegistrationConfig = { hs_token: string url: string sender_localpart: string + namespace_prefix: string protocols: [string] rate_limited: boolean } @@ -43,6 +44,10 @@ namespace R { room_id: string } + export type RoomJoined = { + room_id: string + } + export type FileUploaded = { content_uri: string } From 646d91c0437b889a75ca09c7b47378f5b2912bf5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 01:05:37 +1200 Subject: [PATCH 015/200] database sync --- db/ooye.sql | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/db/ooye.sql b/db/ooye.sql index 25ad5f1..23c5aee 100644 --- a/db/ooye.sql +++ b/db/ooye.sql @@ -72,10 +72,49 @@ INSERT INTO channel_room VALUES('331390333810376704','!kdALuKGeNSkYDgRhIZ:cadenc INSERT INTO channel_room VALUES('768264034327724132','!FEhofHtvWOSNCuvUxW:cadence.moe'); INSERT INTO channel_room VALUES('359903425074561024','!IGixYyKLdvmnAzzOGB:cadence.moe'); INSERT INTO channel_room VALUES('122155380120748034','!iMLtMMlHpWNyAnadZu:cadence.moe'); +INSERT INTO channel_room VALUES('325891921463738369','!IsJKZmawitkFurcjwY:cadence.moe'); +INSERT INTO channel_room VALUES('805261291908104252','!PAGMqppQIkLaNRwkpN:cadence.moe'); +INSERT INTO channel_room VALUES('360567146243293194','!RbrindSuOlbAHAJRFq:cadence.moe'); +INSERT INTO channel_room VALUES('295789411856154624','!RbLRKOjmaEafDtOAAJ:cadence.moe'); +INSERT INTO channel_room VALUES('279985883560804353','!WuYmXisuwEgrxpSkdW:cadence.moe'); +INSERT INTO channel_room VALUES('778183052521111592','!hsALhxajLcTddkKcUE:cadence.moe'); +INSERT INTO channel_room VALUES('360564656265232395','!EZmeznyjKwAcBHbUfX:cadence.moe'); +INSERT INTO channel_room VALUES('802612899990339645','!NVsoZfMPBtLCzlUQAb:cadence.moe'); +INSERT INTO channel_room VALUES('360564868224647168','!YnNokCprKPHliWjKTy:cadence.moe'); +INSERT INTO channel_room VALUES('920171008047079425','!IgtetQZJffJcCQjMqG:cadence.moe'); +INSERT INTO channel_room VALUES('494913643448631296','!WZdAlxVcydGBNdTuoL:cadence.moe'); +INSERT INTO channel_room VALUES('360565596133523459','!OPkSbsYXFqZkRVCIZM:cadence.moe'); +INSERT INTO channel_room VALUES('360567400254537729','!BoXSlmwqtovnOCybXt:cadence.moe'); +INSERT INTO channel_room VALUES('1036840786093953084','!SMEmWBWGgRXeVyiwHN:cadence.moe'); +INSERT INTO channel_room VALUES('360565147854438412','!vwVOQfBJDkaHdiVLUF:cadence.moe'); +INSERT INTO channel_room VALUES('802420775860568074','!QsljgtSqHOtGwMYuIc:cadence.moe'); CREATE TABLE IF NOT EXISTS "file" ( "discord_url" TEXT NOT NULL UNIQUE, "mxc_url" TEXT NOT NULL UNIQUE, PRIMARY KEY("discord_url") ); INSERT INTO file VALUES('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c?size=1024','mxc://cadence.moe/sZtPwbfOIsvfSoWCWPrGnzql'); +INSERT INTO file VALUES('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024','mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'); +CREATE TABLE IF NOT EXISTS "sim_member" ( + "mxid" TEXT NOT NULL, + "room_id" TEXT NOT NULL, + PRIMARY KEY("mxid","room_id") +); +INSERT INTO sim_member VALUES('@_ooye_huck:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); +INSERT INTO sim_member VALUES('@_ooye_bojack_horseman:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); +INSERT INTO sim_member VALUES('@_ooye_botrac4r:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); +INSERT INTO sim_member VALUES('@_ooye_crunch_god:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); +CREATE TABLE IF NOT EXISTS "sim" ( + "discord_id" TEXT NOT NULL UNIQUE, + "sim_name" TEXT NOT NULL UNIQUE, + "localpart" TEXT NOT NULL UNIQUE, + "mxid" TEXT NOT NULL UNIQUE, + PRIMARY KEY("discord_id") +); +INSERT INTO sim VALUES('0','bot','_ooye_bot','@_ooye_bot:cadence.moe'); +INSERT INTO sim VALUES('113340068197859328','cookie','_ooye_cookie','@_ooye_cookie:cadence.moe'); +INSERT INTO sim VALUES('820865262526005258','crunch_god','_ooye_crunch_god','@_ooye_crunch_god:cadence.moe'); +INSERT INTO sim VALUES('142843483923677184','huck','_ooye_huck','@_ooye_huck:cadence.moe'); +INSERT INTO sim VALUES('771520384671416320','bojack_horseman','_ooye_bojack_horseman','@_ooye_bojack_horseman:cadence.moe'); +INSERT INTO sim VALUES('353703396483661824','botrac4r','_ooye_botrac4r','@_ooye_botrac4r:cadence.moe'); COMMIT; From eee5b001bc8ce076fa33fbb6a67eec25cea7013a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 08:03:57 +1200 Subject: [PATCH 016/200] get createRoom and ensureRoom interface working --- d2m/actions/create-room.js | 53 ++++++++++++++++++++++++++++++++----- d2m/actions/send-message.js | 4 ++- d2m/event-dispatcher.js | 1 + matrix/api.js | 8 +++++- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7e9abed..b254951 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -124,13 +124,15 @@ async function channelToKState(channel, guild) { } /** + * Create a bridge room, store the relationship in the database, and add it to the guild's space. * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param guild * @param {string} spaceID * @param {any} kstate + * @returns {Promise} room ID */ async function createRoom(channel, guild, spaceID, kstate) { - const root = await api.createRoom({ + const roomID = await api.createRoom({ name: channel.name, topic: channel.topic || undefined, preset: "private_chat", @@ -139,12 +141,14 @@ async function createRoom(channel, guild, spaceID, kstate) { initial_state: kstateToState(kstate) }) - db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) + db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID) // Put the newly created child into the space - await api.sendState(spaceID, "m.space.child", root.room_id, { + await api.sendState(spaceID, "m.space.child", roomID, { // TODO: should I deduplicate with the equivalent code from syncRoom? via: ["cadence.moe"] // TODO: use the proper server }) + + return roomID } /** @@ -158,22 +162,46 @@ function channelToGuild(channel) { return guild } +/* + Ensure flow: + 1. Get IDs + 2. Does room exist? If so great! + (it doesn't, so it needs to be created) + 3. Get kstate for channel + 4. Create room, return new ID + + New combined flow with ensure / sync: + 1. Get IDs + 2. Does room exist? + 2.5: If room does exist AND don't need to sync: return here + 3. Get kstate for channel + 4. Create room with kstate if room doesn't exist + 5. Get and update room state with kstate if room does exist +*/ + /** * @param {string} channelID + * @param {boolean} shouldActuallySync false if just need to ensure room exists (which is a quick database check), true if also want to sync room data when it does exist (slow) + * @returns {Promise} room ID */ -async function syncRoom(channelID) { +async function _syncRoom(channelID, shouldActuallySync) { /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ const channel = discord.channels.get(channelID) assert.ok(channel) const guild = channelToGuild(channel) - const {spaceID, channelKState} = await channelToKState(channel, guild) - /** @type {string?} */ const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) if (!existing) { + const {spaceID, channelKState} = await channelToKState(channel, guild) return createRoom(channel, guild, spaceID, channelKState) } else { + if (!shouldActuallySync) { + return existing // only need to ensure room exists, and it does. return the room ID + } + + const {spaceID, channelKState} = await channelToKState(channel, guild) + // sync channel state to room const roomKState = await roomToKState(existing) const roomDiff = diffKState(roomKState, channelKState) @@ -187,10 +215,20 @@ async function syncRoom(channelID) { } }) const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff) - return Promise.all([roomApply, spaceApply]) + await Promise.all([roomApply, spaceApply]) + + return existing } } +function ensureRoom(channelID) { + return _syncRoom(channelID, false) +} + +function syncRoom(channelID) { + return _syncRoom(channelID, true) +} + async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) @@ -200,6 +238,7 @@ async function createAllForGuild(guildID) { } module.exports.createRoom = createRoom +module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.kstateToState = kstateToState diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 0a425ee..630cf48 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -11,13 +11,15 @@ const messageToEvent = sync.require("../converters/message-to-event") 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").GatewayMessageCreateDispatchData} message */ async function sendMessage(message) { const event = messageToEvent.messageToEvent(message) - const roomID = "!VwVlIAjOjejUpDhlbA:cadence.moe" + const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index e4de37e..8539044 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -16,6 +16,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { + if (message.guild_id !== "112760669178241024") return // TODO: activate on other servers (requires the space creation flow to be done first) sendMessage.sendMessage(message) }, diff --git a/matrix/api.js b/matrix/api.js index e3a2600..04b7cd1 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -11,7 +11,12 @@ const file = sync.require("./file") /** @type {import("./txnid")} */ const makeTxnId = sync.require("./txnid") -function path(p, mxid = null) { +/** + * @param {string} p endpoint to access + * @param {string} [mxid] optional: user to act as, for the ?user_id parameter + * @returns {string} the new endpoint + */ +function path(p, mxid) { if (!mxid) return p const u = new URL(p, "http://localhost") u.searchParams.set("user_id", mxid) @@ -65,6 +70,7 @@ function getAllState(roomID) { * @param {string} roomID * @param {string} type * @param {string} stateKey + * @param {string} [mxid] * @returns {Promise} event ID */ async function sendState(roomID, type, stateKey, content, mxid) { From 7526d63690f5ab64450e82e95b246ae5d0a3f1ad Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 08:03:57 +1200 Subject: [PATCH 017/200] get createRoom and ensureRoom interface working --- d2m/actions/create-room.js | 53 ++++++++++++++++++++++++++++++++----- d2m/actions/send-message.js | 4 ++- d2m/event-dispatcher.js | 1 + matrix/api.js | 8 +++++- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7e9abed..b254951 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -124,13 +124,15 @@ async function channelToKState(channel, guild) { } /** + * Create a bridge room, store the relationship in the database, and add it to the guild's space. * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param guild * @param {string} spaceID * @param {any} kstate + * @returns {Promise} room ID */ async function createRoom(channel, guild, spaceID, kstate) { - const root = await api.createRoom({ + const roomID = await api.createRoom({ name: channel.name, topic: channel.topic || undefined, preset: "private_chat", @@ -139,12 +141,14 @@ async function createRoom(channel, guild, spaceID, kstate) { initial_state: kstateToState(kstate) }) - db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, root.room_id) + db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID) // Put the newly created child into the space - await api.sendState(spaceID, "m.space.child", root.room_id, { + await api.sendState(spaceID, "m.space.child", roomID, { // TODO: should I deduplicate with the equivalent code from syncRoom? via: ["cadence.moe"] // TODO: use the proper server }) + + return roomID } /** @@ -158,22 +162,46 @@ function channelToGuild(channel) { return guild } +/* + Ensure flow: + 1. Get IDs + 2. Does room exist? If so great! + (it doesn't, so it needs to be created) + 3. Get kstate for channel + 4. Create room, return new ID + + New combined flow with ensure / sync: + 1. Get IDs + 2. Does room exist? + 2.5: If room does exist AND don't need to sync: return here + 3. Get kstate for channel + 4. Create room with kstate if room doesn't exist + 5. Get and update room state with kstate if room does exist +*/ + /** * @param {string} channelID + * @param {boolean} shouldActuallySync false if just need to ensure room exists (which is a quick database check), true if also want to sync room data when it does exist (slow) + * @returns {Promise} room ID */ -async function syncRoom(channelID) { +async function _syncRoom(channelID, shouldActuallySync) { /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ const channel = discord.channels.get(channelID) assert.ok(channel) const guild = channelToGuild(channel) - const {spaceID, channelKState} = await channelToKState(channel, guild) - /** @type {string?} */ const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) if (!existing) { + const {spaceID, channelKState} = await channelToKState(channel, guild) return createRoom(channel, guild, spaceID, channelKState) } else { + if (!shouldActuallySync) { + return existing // only need to ensure room exists, and it does. return the room ID + } + + const {spaceID, channelKState} = await channelToKState(channel, guild) + // sync channel state to room const roomKState = await roomToKState(existing) const roomDiff = diffKState(roomKState, channelKState) @@ -187,10 +215,20 @@ async function syncRoom(channelID) { } }) const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff) - return Promise.all([roomApply, spaceApply]) + await Promise.all([roomApply, spaceApply]) + + return existing } } +function ensureRoom(channelID) { + return _syncRoom(channelID, false) +} + +function syncRoom(channelID) { + return _syncRoom(channelID, true) +} + async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) @@ -200,6 +238,7 @@ async function createAllForGuild(guildID) { } module.exports.createRoom = createRoom +module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.kstateToState = kstateToState diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 0a425ee..630cf48 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -11,13 +11,15 @@ const messageToEvent = sync.require("../converters/message-to-event") 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").GatewayMessageCreateDispatchData} message */ async function sendMessage(message) { const event = messageToEvent.messageToEvent(message) - const roomID = "!VwVlIAjOjejUpDhlbA:cadence.moe" + const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index e4de37e..8539044 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -16,6 +16,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { + if (message.guild_id !== "112760669178241024") return // TODO: activate on other servers (requires the space creation flow to be done first) sendMessage.sendMessage(message) }, diff --git a/matrix/api.js b/matrix/api.js index e3a2600..04b7cd1 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -11,7 +11,12 @@ const file = sync.require("./file") /** @type {import("./txnid")} */ const makeTxnId = sync.require("./txnid") -function path(p, mxid = null) { +/** + * @param {string} p endpoint to access + * @param {string} [mxid] optional: user to act as, for the ?user_id parameter + * @returns {string} the new endpoint + */ +function path(p, mxid) { if (!mxid) return p const u = new URL(p, "http://localhost") u.searchParams.set("user_id", mxid) @@ -65,6 +70,7 @@ function getAllState(roomID) { * @param {string} roomID * @param {string} type * @param {string} stateKey + * @param {string} [mxid] * @returns {Promise} event ID */ async function sendState(roomID, type, stateKey, content, mxid) { From 725f00a98a6d3aefb116c6e248ffdb392abee107 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 15:29:46 +1200 Subject: [PATCH 018/200] 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 da6603d258d882ec9a72ac7c7403d2e5504ddca5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 15:29:46 +1200 Subject: [PATCH 019/200] 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 --- matrix/api.js | 4 ++++ matrix/mreq.js | 5 +++-- matrix/read-registration.test.js | 11 +++++++++++ test/test.js | 1 + 10 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/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 020/200] 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 From 4d8b74f61f194e3084e40e7693acbf314717a4ef Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 9 May 2023 17:13:59 +1200 Subject: [PATCH 021/200] 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 ++++- 4 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) } } From cb09c70e488f37ffed3c6354e2ff77e221b5e106 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 10 May 2023 17:40:31 +1200 Subject: [PATCH 022/200] refactor kstate, add stub user syncing function --- d2m/actions/create-room.js | 76 +++----------------------- d2m/actions/create-room.test.js | 76 +------------------------- d2m/actions/register-user.js | 10 ++++ db/ooye.db | Bin 94208 -> 94208 bytes matrix/kstate.js | 65 ++++++++++++++++++++++ matrix/kstate.test.js | 94 ++++++++++++++++++++++++++++++++ test/test.js | 3 +- 7 files changed, 181 insertions(+), 143 deletions(-) create mode 100644 matrix/kstate.js create mode 100644 matrix/kstate.test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index b254951..7f2c799 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -9,50 +9,15 @@ const { discord, sync, db } = passthrough const file = sync.require("../../matrix/file") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") - -function kstateStripConditionals(kstate) { - for (const [k, content] of Object.entries(kstate)) { - if ("$if" in content) { - if (content.$if) delete content.$if - else delete kstate[k] - } - } - return kstate -} - -function kstateToState(kstate) { - const events = [] - for (const [k, content] of Object.entries(kstate)) { - // conditional for whether a key is even part of the kstate (doing this declaratively on json is hard, so represent it as a property instead.) - if ("$if" in content && !content.$if) continue - delete content.$if - - const [type, state_key] = k.split("/") - assert.ok(typeof type === "string") - assert.ok(typeof state_key === "string") - events.push({type, state_key, content}) - } - return events -} - -/** - * @param {import("../../types").Event.BaseStateEvent[]} events - * @returns {any} - */ -function stateToKState(events) { - const kstate = {} - for (const event of events) { - kstate[event.type + "/" + event.state_key] = event.content - } - return kstate -} +/** @type {import("../../matrix/kstate")} */ +const ks = sync.require("../../matrix/kstate") /** * @param {string} roomID */ async function roomToKState(roomID) { const root = await api.getAllState(roomID) - return stateToKState(root) + return ks.stateToKState(root) } /** @@ -60,33 +25,12 @@ async function roomToKState(roomID) { * @params {any} kstate */ function applyKStateDiffToRoom(roomID, kstate) { - const events = kstateToState(kstate) + const events = ks.kstateToState(kstate) return Promise.all(events.map(({type, state_key, content}) => api.sendState(roomID, type, state_key, content) )) } -function diffKState(actual, target) { - const diff = {} - // go through each key that it should have - for (const key of Object.keys(target)) { - if (key in actual) { - // diff - try { - assert.deepEqual(actual[key], target[key]) - } catch (e) { - // they differ. reassign the target - diff[key] = target[key] - } - } else { - // not present, needs to be added - diff[key] = target[key] - } - // keys that are missing in "actual" will not be deleted on "target" (no action) - } - return diff -} - /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param {import("discord-api-types/v10").APIGuild} guild @@ -98,7 +42,7 @@ async function channelToKState(channel, guild) { const avatarEventContent = {} if (guild.icon) { avatarEventContent.discord_path = file.guildIcon(guild) - avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } const channelKState = { @@ -138,7 +82,7 @@ async function createRoom(channel, guild, spaceID, kstate) { preset: "private_chat", visibility: "private", invite: ["@cadence:cadence.moe"], // TODO - initial_state: kstateToState(kstate) + initial_state: ks.kstateToState(kstate) }) db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID) @@ -204,12 +148,12 @@ async function _syncRoom(channelID, shouldActuallySync) { // sync channel state to room const roomKState = await roomToKState(existing) - const roomDiff = diffKState(roomKState, channelKState) + const roomDiff = ks.diffKState(roomKState, channelKState) const roomApply = applyKStateDiffToRoom(existing, roomDiff) // sync room as space member const spaceKState = await roomToKState(spaceID) - const spaceDiff = diffKState(spaceKState, { + const spaceDiff = ks.diffKState(spaceKState, { [`m.space.child/${existing}`]: { via: ["cadence.moe"] // TODO: use the proper server } @@ -241,8 +185,4 @@ module.exports.createRoom = createRoom module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild -module.exports.kstateToState = kstateToState -module.exports.stateToKState = stateToKState -module.exports.diffKState = diffKState module.exports.channelToKState = channelToKState -module.exports.kstateStripConditionals = kstateStripConditionals diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js index 5ce52e8..ab390fc 100644 --- a/d2m/actions/create-room.test.js +++ b/d2m/actions/create-room.test.js @@ -1,80 +1,8 @@ -const {kstateToState, stateToKState, diffKState, channelToKState, kstateStripConditionals} = require("./create-room") +const {channelToKState} = require("./create-room") +const {kstateStripConditionals} = require("../../matrix/kstate") const {test} = require("supertape") const testData = require("../../test/data") -test("kstate2state: general", t => { - t.deepEqual(kstateToState({ - "m.room.name/": {name: "test name"}, - "m.room.member/@cadence:cadence.moe": {membership: "join"} - }), [ - { - type: "m.room.name", - state_key: "", - content: { - name: "test name" - } - }, - { - type: "m.room.member", - state_key: "@cadence:cadence.moe", - content: { - membership: "join" - } - } - ]) -}) - -test("state2kstate: general", t => { - t.deepEqual(stateToKState([ - { - type: "m.room.name", - state_key: "", - content: { - name: "test name" - } - }, - { - type: "m.room.member", - state_key: "@cadence:cadence.moe", - content: { - membership: "join" - } - } - ]), { - "m.room.name/": {name: "test name"}, - "m.room.member/@cadence:cadence.moe": {membership: "join"} - }) -}) - -test("diffKState: detects edits", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - "same/": {a: 2} - }, { - "m.room.name/": {name: "edited name"}, - "same/": {a: 2} - }), - { - "m.room.name/": {name: "edited name"} - } - ) -}) - -test("diffKState: detects new properties", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - }, { - "m.room.name/": {name: "test name"}, - "new/": {a: 2} - }), - { - "new/": {a: 2} - } - ) -}) - test("channel2room: general", async t => { t.deepEqual( kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 04b0998..89bac2c 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -78,5 +78,15 @@ async function ensureSimJoined(user, roomID) { return mxid } +/** + * @param {import("discord-api-types/v10").APIUser} user + * @param {Required>} member + */ +async function memberToStateContent(user, member) { + return { + displayname: member.nick || user.username + } +} + module.exports.ensureSim = ensureSim module.exports.ensureSimJoined = ensureSimJoined diff --git a/db/ooye.db b/db/ooye.db index 4408064ae46001d0d87d8c66327d9f76c3d50cec..d878291da827fa7e2c5e35eeadf505bc34867d54 100644 GIT binary patch delta 921 zcmb7>Jxmi}9EUjpX|MMI35^2?F)@OPP43>i_r1FTlJ@#V58HaQKv_hX`hp^2T z!!owH`*8c?FhT5h(I4m<%~Ic}w^W+CNq#0%!iud>wg;SWubaEyUO9z)=Y{kEp)r`2xmRXj80uTfSau9M7WF#QFY)C0$#?o_9 zp90k7N~3Ohik0}ZlGO7LGKNpL(lLF}w1hYx@iM<5s41|Jhb#c1fMndJh(6C_sZm8) z!H7j7kwiXV*40S4vB2tV-n%fD%ciV=AqP|5&wZ#_RB*H8z#uTE^3XQ zpl(n*^lNgDCa5DSOqa;7^rueJS7ejv&SF;yO4x%d#BB{J7QkeXPedgdcbO?clu=?rvGxdNzU$wVs@fUh@yO zp8mMgT0a|Yr_bK>^tvC hEIh*8{tP@WJV*HJ`0cieFmB { + t.deepEqual(kstateStripConditionals({ + a: {$if: false, value: 2}, + b: {value: 4} + }), { + b: {value: 4} + }) +}) + +test("kstate strip: keeps true conditions while removing $if", t => { + t.deepEqual(kstateStripConditionals({ + a: {$if: true, value: 2}, + b: {value: 4} + }), { + a: {value: 2}, + b: {value: 4} + }) +}) + +test("kstate2state: general", t => { + t.deepEqual(kstateToState({ + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }), [ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]) +}) + +test("state2kstate: general", t => { + t.deepEqual(stateToKState([ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]), { + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }) +}) + +test("diffKState: detects edits", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + "same/": {a: 2} + }, { + "m.room.name/": {name: "edited name"}, + "same/": {a: 2} + }), + { + "m.room.name/": {name: "edited name"} + } + ) +}) + +test("diffKState: detects new properties", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + }, { + "m.room.name/": {name: "test name"}, + "new/": {a: 2} + }), + { + "new/": {a: 2} + } + ) +}) diff --git a/test/test.js b/test/test.js index 4e01708..bf0023e 100644 --- a/test/test.js +++ b/test/test.js @@ -12,7 +12,8 @@ const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) +require("../matrix/kstate.test") +require("../matrix/api.test") require("../matrix/read-registration.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") -require("../matrix/api.test") From f418d51e555ee80681932e6b4f51b2cf618ec5e6 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 10 May 2023 17:40:31 +1200 Subject: [PATCH 023/200] refactor kstate, add stub user syncing function --- d2m/actions/create-room.js | 76 +++----------------------- d2m/actions/create-room.test.js | 76 +------------------------- d2m/actions/register-user.js | 10 ++++ matrix/kstate.js | 65 +++++++++++++++++++++++ matrix/kstate.test.js | 94 +++++++++++++++++++++++++++++++++ test/test.js | 3 +- 6 files changed, 181 insertions(+), 143 deletions(-) create mode 100644 matrix/kstate.js create mode 100644 matrix/kstate.test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index b254951..7f2c799 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -9,50 +9,15 @@ const { discord, sync, db } = passthrough const file = sync.require("../../matrix/file") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") - -function kstateStripConditionals(kstate) { - for (const [k, content] of Object.entries(kstate)) { - if ("$if" in content) { - if (content.$if) delete content.$if - else delete kstate[k] - } - } - return kstate -} - -function kstateToState(kstate) { - const events = [] - for (const [k, content] of Object.entries(kstate)) { - // conditional for whether a key is even part of the kstate (doing this declaratively on json is hard, so represent it as a property instead.) - if ("$if" in content && !content.$if) continue - delete content.$if - - const [type, state_key] = k.split("/") - assert.ok(typeof type === "string") - assert.ok(typeof state_key === "string") - events.push({type, state_key, content}) - } - return events -} - -/** - * @param {import("../../types").Event.BaseStateEvent[]} events - * @returns {any} - */ -function stateToKState(events) { - const kstate = {} - for (const event of events) { - kstate[event.type + "/" + event.state_key] = event.content - } - return kstate -} +/** @type {import("../../matrix/kstate")} */ +const ks = sync.require("../../matrix/kstate") /** * @param {string} roomID */ async function roomToKState(roomID) { const root = await api.getAllState(roomID) - return stateToKState(root) + return ks.stateToKState(root) } /** @@ -60,33 +25,12 @@ async function roomToKState(roomID) { * @params {any} kstate */ function applyKStateDiffToRoom(roomID, kstate) { - const events = kstateToState(kstate) + const events = ks.kstateToState(kstate) return Promise.all(events.map(({type, state_key, content}) => api.sendState(roomID, type, state_key, content) )) } -function diffKState(actual, target) { - const diff = {} - // go through each key that it should have - for (const key of Object.keys(target)) { - if (key in actual) { - // diff - try { - assert.deepEqual(actual[key], target[key]) - } catch (e) { - // they differ. reassign the target - diff[key] = target[key] - } - } else { - // not present, needs to be added - diff[key] = target[key] - } - // keys that are missing in "actual" will not be deleted on "target" (no action) - } - return diff -} - /** * @param {import("discord-api-types/v10").APIGuildTextChannel} channel * @param {import("discord-api-types/v10").APIGuild} guild @@ -98,7 +42,7 @@ async function channelToKState(channel, guild) { const avatarEventContent = {} if (guild.icon) { avatarEventContent.discord_path = file.guildIcon(guild) - avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } const channelKState = { @@ -138,7 +82,7 @@ async function createRoom(channel, guild, spaceID, kstate) { preset: "private_chat", visibility: "private", invite: ["@cadence:cadence.moe"], // TODO - initial_state: kstateToState(kstate) + initial_state: ks.kstateToState(kstate) }) db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID) @@ -204,12 +148,12 @@ async function _syncRoom(channelID, shouldActuallySync) { // sync channel state to room const roomKState = await roomToKState(existing) - const roomDiff = diffKState(roomKState, channelKState) + const roomDiff = ks.diffKState(roomKState, channelKState) const roomApply = applyKStateDiffToRoom(existing, roomDiff) // sync room as space member const spaceKState = await roomToKState(spaceID) - const spaceDiff = diffKState(spaceKState, { + const spaceDiff = ks.diffKState(spaceKState, { [`m.space.child/${existing}`]: { via: ["cadence.moe"] // TODO: use the proper server } @@ -241,8 +185,4 @@ module.exports.createRoom = createRoom module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild -module.exports.kstateToState = kstateToState -module.exports.stateToKState = stateToKState -module.exports.diffKState = diffKState module.exports.channelToKState = channelToKState -module.exports.kstateStripConditionals = kstateStripConditionals diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js index 5ce52e8..ab390fc 100644 --- a/d2m/actions/create-room.test.js +++ b/d2m/actions/create-room.test.js @@ -1,80 +1,8 @@ -const {kstateToState, stateToKState, diffKState, channelToKState, kstateStripConditionals} = require("./create-room") +const {channelToKState} = require("./create-room") +const {kstateStripConditionals} = require("../../matrix/kstate") const {test} = require("supertape") const testData = require("../../test/data") -test("kstate2state: general", t => { - t.deepEqual(kstateToState({ - "m.room.name/": {name: "test name"}, - "m.room.member/@cadence:cadence.moe": {membership: "join"} - }), [ - { - type: "m.room.name", - state_key: "", - content: { - name: "test name" - } - }, - { - type: "m.room.member", - state_key: "@cadence:cadence.moe", - content: { - membership: "join" - } - } - ]) -}) - -test("state2kstate: general", t => { - t.deepEqual(stateToKState([ - { - type: "m.room.name", - state_key: "", - content: { - name: "test name" - } - }, - { - type: "m.room.member", - state_key: "@cadence:cadence.moe", - content: { - membership: "join" - } - } - ]), { - "m.room.name/": {name: "test name"}, - "m.room.member/@cadence:cadence.moe": {membership: "join"} - }) -}) - -test("diffKState: detects edits", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - "same/": {a: 2} - }, { - "m.room.name/": {name: "edited name"}, - "same/": {a: 2} - }), - { - "m.room.name/": {name: "edited name"} - } - ) -}) - -test("diffKState: detects new properties", t => { - t.deepEqual( - diffKState({ - "m.room.name/": {name: "test name"}, - }, { - "m.room.name/": {name: "test name"}, - "new/": {a: 2} - }), - { - "new/": {a: 2} - } - ) -}) - test("channel2room: general", async t => { t.deepEqual( kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 04b0998..89bac2c 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -78,5 +78,15 @@ async function ensureSimJoined(user, roomID) { return mxid } +/** + * @param {import("discord-api-types/v10").APIUser} user + * @param {Required>} member + */ +async function memberToStateContent(user, member) { + return { + displayname: member.nick || user.username + } +} + module.exports.ensureSim = ensureSim module.exports.ensureSimJoined = ensureSimJoined diff --git a/matrix/kstate.js b/matrix/kstate.js new file mode 100644 index 0000000..398b1b6 --- /dev/null +++ b/matrix/kstate.js @@ -0,0 +1,65 @@ +// @ts-check + +const assert = require("assert") + +/** Mutates the input. */ +function kstateStripConditionals(kstate) { + for (const [k, content] of Object.entries(kstate)) { + // conditional for whether a key is even part of the kstate (doing this declaratively on json is hard, so represent it as a property instead.) + if ("$if" in content) { + if (content.$if) delete content.$if + else delete kstate[k] + } + } + return kstate +} + +function kstateToState(kstate) { + const events = [] + kstateStripConditionals(kstate) + for (const [k, content] of Object.entries(kstate)) { + const [type, state_key] = k.split("/") + assert.ok(typeof type === "string") + assert.ok(typeof state_key === "string") + events.push({type, state_key, content}) + } + return events +} + +/** + * @param {import("../types").Event.BaseStateEvent[]} events + * @returns {any} + */ +function stateToKState(events) { + const kstate = {} + for (const event of events) { + kstate[event.type + "/" + event.state_key] = event.content + } + return kstate +} + +function diffKState(actual, target) { + const diff = {} + // go through each key that it should have + for (const key of Object.keys(target)) { + if (key in actual) { + // diff + try { + assert.deepEqual(actual[key], target[key]) + } catch (e) { + // they differ. reassign the target + diff[key] = target[key] + } + } else { + // not present, needs to be added + diff[key] = target[key] + } + // keys that are missing in "actual" will not be deleted on "target" (no action) + } + return diff +} + +module.exports.kstateStripConditionals = kstateStripConditionals +module.exports.kstateToState = kstateToState +module.exports.stateToKState = stateToKState +module.exports.diffKState = diffKState diff --git a/matrix/kstate.test.js b/matrix/kstate.test.js new file mode 100644 index 0000000..ed59e9d --- /dev/null +++ b/matrix/kstate.test.js @@ -0,0 +1,94 @@ +const {kstateToState, stateToKState, diffKState, kstateStripConditionals} = require("./kstate") +const {test} = require("supertape") + +test("kstate strip: strips false conditions", t => { + t.deepEqual(kstateStripConditionals({ + a: {$if: false, value: 2}, + b: {value: 4} + }), { + b: {value: 4} + }) +}) + +test("kstate strip: keeps true conditions while removing $if", t => { + t.deepEqual(kstateStripConditionals({ + a: {$if: true, value: 2}, + b: {value: 4} + }), { + a: {value: 2}, + b: {value: 4} + }) +}) + +test("kstate2state: general", t => { + t.deepEqual(kstateToState({ + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }), [ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]) +}) + +test("state2kstate: general", t => { + t.deepEqual(stateToKState([ + { + type: "m.room.name", + state_key: "", + content: { + name: "test name" + } + }, + { + type: "m.room.member", + state_key: "@cadence:cadence.moe", + content: { + membership: "join" + } + } + ]), { + "m.room.name/": {name: "test name"}, + "m.room.member/@cadence:cadence.moe": {membership: "join"} + }) +}) + +test("diffKState: detects edits", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + "same/": {a: 2} + }, { + "m.room.name/": {name: "edited name"}, + "same/": {a: 2} + }), + { + "m.room.name/": {name: "edited name"} + } + ) +}) + +test("diffKState: detects new properties", t => { + t.deepEqual( + diffKState({ + "m.room.name/": {name: "test name"}, + }, { + "m.room.name/": {name: "test name"}, + "new/": {a: 2} + }), + { + "new/": {a: 2} + } + ) +}) diff --git a/test/test.js b/test/test.js index 4e01708..bf0023e 100644 --- a/test/test.js +++ b/test/test.js @@ -12,7 +12,8 @@ const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) +require("../matrix/kstate.test") +require("../matrix/api.test") require("../matrix/read-registration.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") -require("../matrix/api.test") From a8e1f95897135542433454ba78ac5dea1ee6ec7e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 10 May 2023 22:15:20 +1200 Subject: [PATCH 024/200] completed user syncing. it occurs on message send --- d2m/actions/create-room.js | 2 + d2m/actions/register-user.js | 76 +++++++++++++++++++++++++++++++++-- d2m/actions/send-message.js | 5 +++ db/ooye.db | Bin 94208 -> 94208 bytes matrix/file.js | 11 +++++ 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7f2c799..96a9671 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -144,6 +144,8 @@ async function _syncRoom(channelID, shouldActuallySync) { return existing // only need to ensure room exists, and it does. return the room ID } + console.log(`[room sync] to matrix: ${channel.name}`) + const {spaceID, channelKState} = await channelToKState(channel, guild) // sync channel state to room diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 89bac2c..cc5f515 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -80,13 +80,81 @@ async function ensureSimJoined(user, roomID) { /** * @param {import("discord-api-types/v10").APIUser} user - * @param {Required>} member + * @param {Omit} member */ -async function memberToStateContent(user, member) { - return { - displayname: member.nick || user.username +async function memberToStateContent(user, member, guildID) { + let displayname = user.username + if (member.nick && member.nick !== displayname) displayname = member.nick + " | " + displayname // prepend nick if present + + const content = { + displayname, + membership: "join", + "moe.cadence.ooye.member": { + }, + "uk.half-shot.discord.member": { + bot: !!user.bot, + displayColor: user.accent_color, + id: user.id, + username: user.discriminator.length === 4 ? `${user.username}#${user.discriminator}` : `@${user.username}` + } + } + + if (member.avatar || user.avatar) { + // const avatarPath = file.userAvatar(user) // the user avatar only + const avatarPath = file.memberAvatar(guildID, user, member) // the member avatar or the user avatar + content["moe.cadence.ooye.member"].avatar = avatarPath + content.avatar_url = await file.uploadDiscordFileToMxc(avatarPath) + } + + return content +} + +function calculateProfileEventContentHash(content) { + return `${content.displayname}\u0000${content.avatar_url}` +} + +/** + * @param {import("discord-api-types/v10").APIUser} user + * @param {Omit} member + */ +async function syncUser(user, member, guildID, roomID) { + const mxid = await ensureSimJoined(user, roomID) + const content = await memberToStateContent(user, member, guildID) + const profileEventContentHash = calculateProfileEventContentHash(content) + const existingHash = db.prepare("SELECT profile_event_content_hash FROM sim_member WHERE room_id = ? AND mxid = ?").pluck().get(roomID, mxid) + // only do the actual sync if the hash has changed since we last looked + if (existingHash !== profileEventContentHash) { + await api.sendState(roomID, "m.room.member", mxid, content, mxid) + db.prepare("UPDATE sim_member SET profile_event_content_hash = ? WHERE room_id = ? AND mxid = ?").run(profileEventContentHash, roomID, mxid) + } +} + +async function syncAllUsersInRoom(roomID) { + const mxids = db.prepare("SELECT mxid FROM sim_member WHERE room_id = ?").pluck().all(roomID) + + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + assert.ok(typeof channelID === "string") + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = discord.channels.get(channelID) + const guildID = channel.guild_id + assert.ok(typeof guildID === "string") + + for (const mxid of mxids) { + const userID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(mxid) + assert.ok(typeof userID === "string") + + /** @ts-ignore @type {Required} */ + const member = await discord.snow.guild.getGuildMember(guildID, userID) + /** @ts-ignore @type {Required} user */ + const user = member.user + assert.ok(user) + + console.log(`[user sync] to matrix: ${user.username} in ${channel.name}`) + await syncUser(user, member, guildID, roomID) } } module.exports.ensureSim = ensureSim module.exports.ensureSimJoined = ensureSimJoined +module.exports.syncUser = syncUser +module.exports.syncAllUsersInRoom = syncAllUsersInRoom diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index fb181d2..2630430 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -1,5 +1,7 @@ // @ts-check +const assert = require("assert") + const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough /** @type {import("../converters/message-to-event")} */ @@ -15,11 +17,14 @@ const createRoom = sync.require("../actions/create-room") * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ async function sendMessage(message) { + assert.ok(message.member) + const event = messageToEvent.messageToEvent(message) const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } 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 diff --git a/db/ooye.db b/db/ooye.db index d878291da827fa7e2c5e35eeadf505bc34867d54..bae53f6c0cabf69cfac4dce2dd27882fc055d96a 100644 GIT binary patch delta 5650 zcmeHKYiwI*8TM%$-;U2YN!lj8=F;va)HLUQNt(8{|?=RTX2?Ky0jti4AE2p~29g{V`w@W2ywwwqD@#Y1}9*W=(|n zMWmyA-^qEO_x3#R_vPG`&U05fzjV0!qDrOe15Y=2_JHT!FQ1^8i>i~ccM#aA>d~uJ zSQvW%y@tGoK8;q4?;=kjYsLZiUc)WJ>xQTGf7d^!SHthX=i&1gFFdIwVJB4U8ySHf z950HICApAWiiC@DdO56Y!^B5kVD&4yeu$_)0<-aB%lT|9xg>|>vYaV~rEI3y{GAXB ziDP=R(_=ni(9ihnE|V`b=&*(!IF?>XMnRM>DIQ0fF4oEWw0%QE(1XfOE+o_8w49E} z`OOtpUxwX7AM30BJbdiZbNV`@M&DL_yFLS(_nMAt`T`@6Dw&DOE2jIKoBB`m2kKwZ zcMTBR_aAK5_qp~b8G(nS`9F1}@p5R28j4{&5 zRr8!V2g2Cw#nU-XVAX!cl8Iq0?LPX|qndRvyEr~Ia zX!+U zwNF4jTI8q-{UrJ`YC(~JZq|io3n(aStCyCD1=W|=( z+tFLy`hM%@?MT{j>522=Ab&`AONBj;l`#VSEBX`~L60K8Me2wf(Hk!tYsOLdAMn%g zB7C3Ws^M|NyrD<`OZ_8yi|!Vv&QEnU%OSN)>FxPjDwHeB#Thc6 zw+20-C7V1moS%p@nS^hKCkUM3nxjr&d7hwgo}w%V6%cnio06D>(??8B%u@d0s>Nd~ zO|Ni)+%RXedaIRbS{AZ4E0u_EfDko?qbME>IiOz40R;sdE#(!`?F>!(=hAs@(&8%3 zSE{ChJz^?NNi)GJ6ZD&KcE)MNTPV07@U%elq`+GG6p(bPnlsPZlEI31ITb1{%r7jJ z9hODbZ%xdVDx!Oyj%1v6*L?R-PR-_v!&Q4M>s>78yc3jZ1B7KLkPQnaJWpEoDj?x}xWqf>NCc&Gv)9| z*qR9X{LG|l$?OeA93D16@qs|f8|V0G>unUA7Z`!(S&nB}*3zY*cyj4vTrP*_i#~59 zWnEw#mb5+R3I;+8j=7SYl9wk_R+luLEN^rfX9b2~d7S16mbYN)T9f=~*MuBm^So>; z@T7}0tuB>U9qEE=a;j?2Q$?vL#8>FaNV#B=HbMbs7A4R$APmJ@P$d+?F=5O2WAd=u z5yy+d(p-3gwala>->jTkjRu?xL0>juw&%U^4X)rUM+q(lL_!DzZ9x>2KrvVp+^Z4$ z@@lXccG|3=1;QFz6qhIFxj9F0k{p)VU?!EI^8qCkzFAiRv=|(4EPd(nt7kj-L$Fqb zt|GJ8INE0%Hoky<4?B~@fVE8p?o}*yptc^xU_)7VBdl)4V2g-q|5-&{ip35DgDD1gFAlDPDi%8s z0@5_7|KimXm)?5w$R0|UQT1wPRoF||7qK(Cx6wy*W#o0_tH>&8!MufgAi zQ}F%TS%cqjQ2z(RGZ!xmXm#s{dphlo^)vPEo8zAec>;sefm#2c#pHBaCk9>CL7T_r znX!4ycFWMv(BS6=9jm3%RDN|ikjSjsq-UF8+FhH3 z{c6FXs(0UFG{U}(Wnbf7Bh;@k?N!^Gf4VtP|J%)e9sC#70BG59@S;zX?NyICDtDZY z0jD5|f|%bf@^-#W-J1?>^lMIasm}m^j$6FuGzJQ+YP^U-qZ)L-s^qA(dU$4Q54Xv* zUGqi}5V8v3u$och#_~Y@sekrsM&Uc#^xl5WS%Z2CIEOH(zws>;6ymJ@&Wrko*TG)b z+Mm*#(l&!`{CEI5*mxX;1~ll)s={q<2)p3M>CR^2fLTWxVc=f_{&}uA(oLqY3RAYp zw;eC69cnLt8oYj7NpftDdK?5gbQC&JfA_|KW(?ZgXKxJ9Nb?`{F4cyV4g1h%^%u}l zED1)HMs&Vhf z4T5d5Zzuf%3ZnWiux3;0yJ=@h4SKM<_QB^=r$E0pWIfns4rA5t|b1nQ}|OM$I4 zJ+%a;`CsYeebrfo>7$x$NDl4nk;e z141A()YE)phPoSPQD_|MYWxU=&SnvPRI n(9ov9_l$wP{`MXS{K+n8=4*qH8t*kcs=_W~5C6YDTk7*Km3OZ) delta 1072 zcmZWnT}%{L6ux(7?yxiWZ+8`!c3~FMYJDKZT_W8dwz~?^+R@gU_`DH=4+ejlQiu=T zLVZ$GDY{vW7-BIF{UNHVEyIAMp-nP^a*kG3@enxNzUAR&Ufzl z?)T*m%#ndP@=Yu}gb<3piU6#!;pWz@L+IT@^PELc#GFL3C^w6zd`66ke)fou@)F%m z@31)CXjQCPYs~!5oHYB)K5xA7&0rD^Oh;Q<@K@>H16@7cpMBcXRqX9Pa=f^l7L%_P zxatYoIH}TBzX6P;{VnPVeM4QMGL|eRUTYWWCL_A%9q6g^X3fvBJ}6|3+Do^q56x!f zT3n5qkw9xmEzqP0i-(9ujYb26A>_r`{e!`PxQXO3anlQL`AcYnk$@WiBJM4ff;bqE z2_zHp2Y@z8)$nzrS}N^Q-wz!I>Gkp5*u{tpQV?u83~OZQ$LHzhgbwbpGUFUv1wLA(&Zi<4r9VEigS1+@zuYKaErw_J~^g1o;sNu!;^oYM1S*TNYmu!*` zYdalPd2TOfsgRa--0=pyLD|pZBqOz)<2E(mI<<8^P6|TX1t;gDYVO%4lHseMQwEUz z4+d3k(I#oSLKJ!cMAiz?>9S1<=GyKP^EOEZSE&~t#!0qD%RB0LIYqLDzbjNyBu9K% zzm_v3uUCh)lbq()&OQdIfvVKERDA`=ah1F+X)T*LjaVm}k|i3H?_1NNB$ntEJ|HjC zGrUt=6a_ZOG5?d9g3yn_#P2NN=y0%aBCd4{5^wf38SArp7OKOGZFrsg2g6w-vQ6I! zl~)cL;f$Vk@T!GIs9n!NzTzR4?jXf)tKDVW5NjZC5OW{e?xr}l6*%Vha@q{)uQCKn66J@B-R?3E{mA R?D0PgA>r-)BZtRgF9EgAKbZgk diff --git a/matrix/file.js b/matrix/file.js index 137a096..077d527 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -58,5 +58,16 @@ function guildIcon(guild) { return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } +function userAvatar(user) { + return `/avatars/${user.id}/${user.avatar}.png?size=${IMAGE_SIZE}` +} + +function memberAvatar(guildID, user, member) { + if (!member.avatar) return userAvatar(user) + return `/guilds/${guildID}/users/${user.id}/avatars/${member.avatar}.png?size=${IMAGE_SIZE}` +} + module.exports.guildIcon = guildIcon +module.exports.userAvatar = userAvatar +module.exports.memberAvatar = memberAvatar module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc From 22dde9faf7bf2f88eab06164d66fd34dd106f8ae Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 10 May 2023 22:15:20 +1200 Subject: [PATCH 025/200] completed user syncing. it occurs on message send --- d2m/actions/create-room.js | 2 + d2m/actions/register-user.js | 76 ++++++++++++++++++++++++++++++++++-- d2m/actions/send-message.js | 5 +++ matrix/file.js | 11 ++++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7f2c799..96a9671 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -144,6 +144,8 @@ async function _syncRoom(channelID, shouldActuallySync) { return existing // only need to ensure room exists, and it does. return the room ID } + console.log(`[room sync] to matrix: ${channel.name}`) + const {spaceID, channelKState} = await channelToKState(channel, guild) // sync channel state to room diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 89bac2c..cc5f515 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -80,13 +80,81 @@ async function ensureSimJoined(user, roomID) { /** * @param {import("discord-api-types/v10").APIUser} user - * @param {Required>} member + * @param {Omit} member */ -async function memberToStateContent(user, member) { - return { - displayname: member.nick || user.username +async function memberToStateContent(user, member, guildID) { + let displayname = user.username + if (member.nick && member.nick !== displayname) displayname = member.nick + " | " + displayname // prepend nick if present + + const content = { + displayname, + membership: "join", + "moe.cadence.ooye.member": { + }, + "uk.half-shot.discord.member": { + bot: !!user.bot, + displayColor: user.accent_color, + id: user.id, + username: user.discriminator.length === 4 ? `${user.username}#${user.discriminator}` : `@${user.username}` + } + } + + if (member.avatar || user.avatar) { + // const avatarPath = file.userAvatar(user) // the user avatar only + const avatarPath = file.memberAvatar(guildID, user, member) // the member avatar or the user avatar + content["moe.cadence.ooye.member"].avatar = avatarPath + content.avatar_url = await file.uploadDiscordFileToMxc(avatarPath) + } + + return content +} + +function calculateProfileEventContentHash(content) { + return `${content.displayname}\u0000${content.avatar_url}` +} + +/** + * @param {import("discord-api-types/v10").APIUser} user + * @param {Omit} member + */ +async function syncUser(user, member, guildID, roomID) { + const mxid = await ensureSimJoined(user, roomID) + const content = await memberToStateContent(user, member, guildID) + const profileEventContentHash = calculateProfileEventContentHash(content) + const existingHash = db.prepare("SELECT profile_event_content_hash FROM sim_member WHERE room_id = ? AND mxid = ?").pluck().get(roomID, mxid) + // only do the actual sync if the hash has changed since we last looked + if (existingHash !== profileEventContentHash) { + await api.sendState(roomID, "m.room.member", mxid, content, mxid) + db.prepare("UPDATE sim_member SET profile_event_content_hash = ? WHERE room_id = ? AND mxid = ?").run(profileEventContentHash, roomID, mxid) + } +} + +async function syncAllUsersInRoom(roomID) { + const mxids = db.prepare("SELECT mxid FROM sim_member WHERE room_id = ?").pluck().all(roomID) + + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + assert.ok(typeof channelID === "string") + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = discord.channels.get(channelID) + const guildID = channel.guild_id + assert.ok(typeof guildID === "string") + + for (const mxid of mxids) { + const userID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(mxid) + assert.ok(typeof userID === "string") + + /** @ts-ignore @type {Required} */ + const member = await discord.snow.guild.getGuildMember(guildID, userID) + /** @ts-ignore @type {Required} user */ + const user = member.user + assert.ok(user) + + console.log(`[user sync] to matrix: ${user.username} in ${channel.name}`) + await syncUser(user, member, guildID, roomID) } } module.exports.ensureSim = ensureSim module.exports.ensureSimJoined = ensureSimJoined +module.exports.syncUser = syncUser +module.exports.syncAllUsersInRoom = syncAllUsersInRoom diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index fb181d2..2630430 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -1,5 +1,7 @@ // @ts-check +const assert = require("assert") + const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough /** @type {import("../converters/message-to-event")} */ @@ -15,11 +17,14 @@ const createRoom = sync.require("../actions/create-room") * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ async function sendMessage(message) { + assert.ok(message.member) + const event = messageToEvent.messageToEvent(message) const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } 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 diff --git a/matrix/file.js b/matrix/file.js index 137a096..077d527 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -58,5 +58,16 @@ function guildIcon(guild) { return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } +function userAvatar(user) { + return `/avatars/${user.id}/${user.avatar}.png?size=${IMAGE_SIZE}` +} + +function memberAvatar(guildID, user, member) { + if (!member.avatar) return userAvatar(user) + return `/guilds/${guildID}/users/${user.id}/avatars/${member.avatar}.png?size=${IMAGE_SIZE}` +} + module.exports.guildIcon = guildIcon +module.exports.userAvatar = userAvatar +module.exports.memberAvatar = memberAvatar module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc From 28455ba4c8ee1154d6311e5bbd1dddc527d65222 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 10 May 2023 23:17:37 +1200 Subject: [PATCH 026/200] seed initial setup --- db/ooye.db | Bin 94208 -> 94208 bytes matrix/api.js | 14 ++++++++++++++ matrix/file.js | 9 ++++++++- seed.js | 35 +++++++++++++++++++++++++++++++++++ test/test.js | 1 - 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 seed.js diff --git a/db/ooye.db b/db/ooye.db index bae53f6c0cabf69cfac4dce2dd27882fc055d96a..064c32b6606e9b33b2d031f1b55017cf70430d0a 100644 GIT binary patch delta 306 zcmZp8z}oPDb%HeGrin7njGHzlEXfz);BIH&&*!_v=ghl~SCXfN`#pF2W26t(fgV+9PG$Zei?XnB@Z4wMxxbmC z;VO@vEpt61*%ot5{@ANAS+d4)b6am66CVd(Cj-AL-wyr~elfnz%>on3`6lm}Cnk%_ UH5_`(iHwu?ztGzJY@XCb0CJUX { + // ensure registration is correctly set... + + // test connection to homeserver... + + // upload initial images... + const avatarUrl = await file.uploadDiscordFileToMxc("https://cadence.moe/friends/out_of_your_element_rev_2.jpg") + + // set profile data on homeserver... + await api.profileSetDisplayname(`@${reg.sender_localpart}:cadence.moe`, "Out Of Your Element") + await api.profileSetAvatarUrl(`@${reg.sender_localpart}:cadence.moe`, avatarUrl) + + // database ddl... + + // add initial rows to database, like adding the bot to sim... + +})() diff --git a/test/test.js b/test/test.js index bf0023e..2c44bb3 100644 --- a/test/test.js +++ b/test/test.js @@ -7,7 +7,6 @@ const config = require("../config") const passthrough = require("../passthrough") const db = new sqlite("db/ooye.db") -// @ts-ignore const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) From 38d7db5071f52c1bd4db5fbf4e6a955b38d355df Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 10 May 2023 23:17:37 +1200 Subject: [PATCH 027/200] seed initial setup --- matrix/api.js | 14 ++++++++++++++ matrix/file.js | 9 ++++++++- seed.js | 35 +++++++++++++++++++++++++++++++++++ test/test.js | 1 - 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 seed.js diff --git a/matrix/api.js b/matrix/api.js index dbc39bc..846ea64 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -91,6 +91,18 @@ async function sendEvent(roomID, type, content, mxid) { return root.event_id } +async function profileSetDisplayname(mxid, displayname) { + await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), { + displayname + }) +} + +async function profileSetAvatarUrl(mxid, avatar_url) { + await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid), { + avatar_url + }) +} + module.exports.path = path module.exports.register = register module.exports.createRoom = createRoom @@ -99,3 +111,5 @@ module.exports.inviteToRoom = inviteToRoom module.exports.getAllState = getAllState module.exports.sendState = sendState module.exports.sendEvent = sendEvent +module.exports.profileSetDisplayname = profileSetDisplayname +module.exports.profileSetAvatarUrl = profileSetAvatarUrl diff --git a/matrix/file.js b/matrix/file.js index 077d527..4578d4a 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -17,7 +17,14 @@ const inflight = new Map() * @param {string} path */ async function uploadDiscordFileToMxc(path) { - const url = DISCORD_IMAGES_BASE + path + let url + if (path.startsWith("http")) { + // TODO: this is cheating to make seed.js easier. due a refactor or a name change since it's not soley for discord? + // possibly could be good to save non-discord external URLs under a user-specified key rather than simply using the url? + url = path + } else { + url = DISCORD_IMAGES_BASE + path + } // Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution let existing = inflight.get(url) diff --git a/seed.js b/seed.js new file mode 100644 index 0000000..d84ca8d --- /dev/null +++ b/seed.js @@ -0,0 +1,35 @@ +// @ts-check + +const assert = require("assert") +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const config = require("./config") +const passthrough = require("./passthrough") +const db = new sqlite("db/ooye.db") + +const sync = new HeatSync({watchFS: false}) + +Object.assign(passthrough, { config, sync, db }) + +const api = require("./matrix/api") +const file = require("./matrix/file") +const reg = require("./matrix/read-registration") + +;(async () => { + // ensure registration is correctly set... + + // test connection to homeserver... + + // upload initial images... + const avatarUrl = await file.uploadDiscordFileToMxc("https://cadence.moe/friends/out_of_your_element_rev_2.jpg") + + // set profile data on homeserver... + await api.profileSetDisplayname(`@${reg.sender_localpart}:cadence.moe`, "Out Of Your Element") + await api.profileSetAvatarUrl(`@${reg.sender_localpart}:cadence.moe`, avatarUrl) + + // database ddl... + + // add initial rows to database, like adding the bot to sim... + +})() diff --git a/test/test.js b/test/test.js index bf0023e..2c44bb3 100644 --- a/test/test.js +++ b/test/test.js @@ -7,7 +7,6 @@ const config = require("../config") const passthrough = require("../passthrough") const db = new sqlite("db/ooye.db") -// @ts-ignore const sync = new HeatSync({watchFS: false}) Object.assign(passthrough, { config, sync, db }) From 63e5ae3e0e86b1e10d14a15e1ebb356ea4fe6027 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 12 May 2023 17:35:37 +1200 Subject: [PATCH 028/200] begin converting discord attachments and stickers --- d2m/actions/send-message.js | 23 +++++- d2m/converters/message-to-event.js | 94 ++++++++++++++++++++---- d2m/converters/message-to-event.test.js | 28 +++++++ db/ooye.db | Bin 94208 -> 94208 bytes matrix/file.js | 7 ++ test/data.js | 74 +++++++++++++++---- test/test.js | 1 + 7 files changed, 194 insertions(+), 33 deletions(-) create mode 100644 d2m/converters/message-to-event.test.js diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 2630430..738c59a 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -19,16 +19,31 @@ const createRoom = sync.require("../actions/create-room") async function sendMessage(message) { assert.ok(message.member) - const event = messageToEvent.messageToEvent(message) const roomID = await createRoom.ensureRoom(message.channel_id) + let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - 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 + + const events = await messageToEvent.messageToEvent(message) + const eventIDs = [] + let eventPart = 0 // 0 is primary, 1 is supporting + for (const event of events) { + const eventType = event.$type + /** @type {Pick> & { $type?: string }} */ + const eventWithoutType = {...event} + delete eventWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, eventPart) + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventIDs.push(eventID) + } + + return eventIDs } module.exports.sendMessage = sendMessage diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index dd6aabc..86be8ac 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,27 +2,93 @@ const markdown = require("discord-markdown") +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + /** * @param {import("discord-api-types/v10").APIMessage} message - * @returns {import("../../types").Event.M_Room_Message} */ -function messageToEvent(message) { +async function messageToEvent(message) { + const events = [] + + // Text content appears first const body = message.content const html = markdown.toHTML(body, { - /* discordCallback: { - user: Function, - channel: Function, - role: Function, - everyone: Function, - here: Function - } */ + discordCallback: { + user: node => { + const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) + if (mxid) { + return "https://matrix.to/#/" + mxid + } else { + return "@" + node.id + } + }, + channel: node => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) + if (roomID) { + return "https://matrix.to/#/" + roomID + } else { + return "#" + node.id + } + }, + role: node => + "@&" + node.id, + everyone: node => + "@room", + here: node => + "@here" + } }, null, null) - return { - msgtype: "m.text", - body: body, - format: "org.matrix.custom.html", - formatted_body: html + const isPlaintext = body === html + if (isPlaintext) { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body + }) + } else { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body, + format: "org.matrix.custom.html", + formatted_body: html + }) } + + // Then attachments + const attachmentEvents = await Promise.all(message.attachments.map(async attachment => { + // TODO: handle large files differently - link them instead of uploading + if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { + return { + $type: "m.room.message", + msgtype: "m.image", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.filename, + // TODO: filename: attachment.filename and then use body as the caption + info: { + mimetype: attachment.content_type, + w: attachment.width, + h: attachment.height, + size: attachment.size + } + } + } else { + return { + $type: "m.room.message", + msgtype: "m.text", + body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) + } + } + })) + events.push(...attachmentEvents) + + // Then stickers + + return events } module.exports.messageToEvent = messageToEvent diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js new file mode 100644 index 0000000..07a45f7 --- /dev/null +++ b/d2m/converters/message-to-event.test.js @@ -0,0 +1,28 @@ +const {test} = require("supertape") +const assert = require("assert") +const {messageToEvent} = require("./message-to-event") +const data = require("../../test/data") + +test("message2event: stickers", async t => { + const events = await messageToEvent(data.message.sticker) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "can have attachments too" + }, { + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + info: { + mimetype: "image/png", + w: 333, + h: 287, + size: 127373, + }, + }, { + $type: "m.sticker", + todo: "todo" + }]) +}) diff --git a/db/ooye.db b/db/ooye.db index 064c32b6606e9b33b2d031f1b55017cf70430d0a..924e5995e795ae11ae409e01ad15cfcfed6e76d8 100644 GIT binary patch delta 329 zcmZp8z}oPDb%HeG=7}=SjGH$mEXfz+e(f|Me delta 94 zcmV-k0HObY;01u-1&|v7vXLA^0kW}Rq;C`m3x@y?Zw}fHLk+GC84QFA?+b^s5ioBH zvxj?q1O^BU-vA8XvkZWC43V&3v)_y*0|p2Vi2x5pvkZ_<4w0}ilUkk@v!9+}(ffxX AS^xk5 diff --git a/matrix/file.js b/matrix/file.js index 4578d4a..62a4550 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -74,7 +74,14 @@ function memberAvatar(guildID, user, member) { return `/guilds/${guildID}/users/${user.id}/avatars/${member.avatar}.png?size=${IMAGE_SIZE}` } +function emoji(emojiID, animated) { + const base = `/emojis/${emojiID}` + if (animated) return base + ".gif" + else return base + ".png" +} + module.exports.guildIcon = guildIcon module.exports.userAvatar = userAvatar module.exports.memberAvatar = memberAvatar +module.exports.emoji = emoji module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/test/data.js b/test/data.js index e6a49c8..c94d132 100644 --- a/test/data.js +++ b/test/data.js @@ -6,18 +6,18 @@ module.exports = { channel: { general: { type: 0, - topic: 'https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:', + topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:", rate_limit_per_user: 0, position: 0, permission_overwrites: [], parent_id: null, nsfw: false, - name: 'collective-unconscious' , - last_pin_timestamp: '2023-04-06T09:51:57+00:00', - last_message_id: '1103832925784514580', - id: '112760669178241024', + name: "collective-unconscious" , + last_pin_timestamp: "2023-04-06T09:51:57+00:00", + last_message_id: "1103832925784514580", + id: "112760669178241024", default_thread_rate_limit_per_user: 0, - guild_id: '112760669178241024' + guild_id: "112760669178241024" } }, room: { @@ -45,41 +45,85 @@ module.exports = { }, guild: { general: { - owner_id: '112760500130975744', + owner_id: "112760500130975744", premium_tier: 3, stickers: [], max_members: 500000, - splash: '86a34ed02524b972918bef810087f8e7', + splash: "86a34ed02524b972918bef810087f8e7", explicit_content_filter: 0, afk_channel_id: null, nsfw_level: 0, description: null, - preferred_locale: 'en-US', - system_channel_id: '112760669178241024', + preferred_locale: "en-US", + system_channel_id: "112760669178241024", mfa_level: 0, /** @type {300} */ afk_timeout: 300, - id: '112760669178241024', - icon: 'a_f83622e09ead74f0c5c527fe241f8f8c', + id: "112760669178241024", + icon: "a_f83622e09ead74f0c5c527fe241f8f8c", emojis: [], premium_subscription_count: 14, roles: [], discovery_splash: null, default_message_notifications: 1, - region: 'deprecated', + region: "deprecated", max_video_channel_users: 25, verification_level: 0, application_id: null, premium_progress_bar_enabled: false, - banner: 'a_a666ae551605a2d8cda0afd591c0af3a', + banner: "a_a666ae551605a2d8cda0afd591c0af3a", features: [], vanity_url_code: null, hub_type: null, public_updates_channel_id: null, rules_channel_id: null, - name: 'Psychonauts 3', + name: "Psychonauts 3", max_stage_video_channel_users: 300, system_channel_flags: 0|0 } + }, + message: { + // Display order is text content, attachments, then stickers + sticker: { + id: "1106366167788044450", + type: 0, + content: "can have attachments too", + channel_id: "122155380120748034", + author: { + id: "113340068197859328", + username: "Cookie 🍪", + global_name: null, + display_name: null, + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "7766", + public_flags: 128, + avatar_decoration: null + }, + attachments: [{ + id: "1106366167486038016", + filename: "image.png", + size: 127373, + url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", + width: 333, + height: 287, + content_type: "image/png" + }], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-05-11T23:44:09.690000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + sticker_items: [{ + id: "1106323941183717586", + format_type: 1, + name: "pomu puff" + }] + } } } diff --git a/test/test.js b/test/test.js index 2c44bb3..ae6aea6 100644 --- a/test/test.js +++ b/test/test.js @@ -14,5 +14,6 @@ Object.assign(passthrough, { config, sync, db }) require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") +require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") From e1d7ced87d5f7f10be83c1b2cf723b1d65bbef3f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 12 May 2023 17:35:37 +1200 Subject: [PATCH 029/200] begin converting discord attachments and stickers --- d2m/actions/send-message.js | 23 ++++-- d2m/converters/message-to-event.js | 94 +++++++++++++++++++++---- d2m/converters/message-to-event.test.js | 28 ++++++++ matrix/file.js | 7 ++ test/data.js | 74 +++++++++++++++---- test/test.js | 1 + 6 files changed, 194 insertions(+), 33 deletions(-) create mode 100644 d2m/converters/message-to-event.test.js diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 2630430..738c59a 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -19,16 +19,31 @@ const createRoom = sync.require("../actions/create-room") async function sendMessage(message) { assert.ok(message.member) - const event = messageToEvent.messageToEvent(message) const roomID = await createRoom.ensureRoom(message.channel_id) + let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - 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 + + const events = await messageToEvent.messageToEvent(message) + const eventIDs = [] + let eventPart = 0 // 0 is primary, 1 is supporting + for (const event of events) { + const eventType = event.$type + /** @type {Pick> & { $type?: string }} */ + const eventWithoutType = {...event} + delete eventWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, eventPart) + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventIDs.push(eventID) + } + + return eventIDs } module.exports.sendMessage = sendMessage diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index dd6aabc..86be8ac 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,27 +2,93 @@ const markdown = require("discord-markdown") +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + /** * @param {import("discord-api-types/v10").APIMessage} message - * @returns {import("../../types").Event.M_Room_Message} */ -function messageToEvent(message) { +async function messageToEvent(message) { + const events = [] + + // Text content appears first const body = message.content const html = markdown.toHTML(body, { - /* discordCallback: { - user: Function, - channel: Function, - role: Function, - everyone: Function, - here: Function - } */ + discordCallback: { + user: node => { + const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) + if (mxid) { + return "https://matrix.to/#/" + mxid + } else { + return "@" + node.id + } + }, + channel: node => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) + if (roomID) { + return "https://matrix.to/#/" + roomID + } else { + return "#" + node.id + } + }, + role: node => + "@&" + node.id, + everyone: node => + "@room", + here: node => + "@here" + } }, null, null) - return { - msgtype: "m.text", - body: body, - format: "org.matrix.custom.html", - formatted_body: html + const isPlaintext = body === html + if (isPlaintext) { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body + }) + } else { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body, + format: "org.matrix.custom.html", + formatted_body: html + }) } + + // Then attachments + const attachmentEvents = await Promise.all(message.attachments.map(async attachment => { + // TODO: handle large files differently - link them instead of uploading + if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { + return { + $type: "m.room.message", + msgtype: "m.image", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.filename, + // TODO: filename: attachment.filename and then use body as the caption + info: { + mimetype: attachment.content_type, + w: attachment.width, + h: attachment.height, + size: attachment.size + } + } + } else { + return { + $type: "m.room.message", + msgtype: "m.text", + body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) + } + } + })) + events.push(...attachmentEvents) + + // Then stickers + + return events } module.exports.messageToEvent = messageToEvent diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js new file mode 100644 index 0000000..07a45f7 --- /dev/null +++ b/d2m/converters/message-to-event.test.js @@ -0,0 +1,28 @@ +const {test} = require("supertape") +const assert = require("assert") +const {messageToEvent} = require("./message-to-event") +const data = require("../../test/data") + +test("message2event: stickers", async t => { + const events = await messageToEvent(data.message.sticker) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "can have attachments too" + }, { + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + info: { + mimetype: "image/png", + w: 333, + h: 287, + size: 127373, + }, + }, { + $type: "m.sticker", + todo: "todo" + }]) +}) diff --git a/matrix/file.js b/matrix/file.js index 4578d4a..62a4550 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -74,7 +74,14 @@ function memberAvatar(guildID, user, member) { return `/guilds/${guildID}/users/${user.id}/avatars/${member.avatar}.png?size=${IMAGE_SIZE}` } +function emoji(emojiID, animated) { + const base = `/emojis/${emojiID}` + if (animated) return base + ".gif" + else return base + ".png" +} + module.exports.guildIcon = guildIcon module.exports.userAvatar = userAvatar module.exports.memberAvatar = memberAvatar +module.exports.emoji = emoji module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/test/data.js b/test/data.js index e6a49c8..c94d132 100644 --- a/test/data.js +++ b/test/data.js @@ -6,18 +6,18 @@ module.exports = { channel: { general: { type: 0, - topic: 'https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:', + topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:", rate_limit_per_user: 0, position: 0, permission_overwrites: [], parent_id: null, nsfw: false, - name: 'collective-unconscious' , - last_pin_timestamp: '2023-04-06T09:51:57+00:00', - last_message_id: '1103832925784514580', - id: '112760669178241024', + name: "collective-unconscious" , + last_pin_timestamp: "2023-04-06T09:51:57+00:00", + last_message_id: "1103832925784514580", + id: "112760669178241024", default_thread_rate_limit_per_user: 0, - guild_id: '112760669178241024' + guild_id: "112760669178241024" } }, room: { @@ -45,41 +45,85 @@ module.exports = { }, guild: { general: { - owner_id: '112760500130975744', + owner_id: "112760500130975744", premium_tier: 3, stickers: [], max_members: 500000, - splash: '86a34ed02524b972918bef810087f8e7', + splash: "86a34ed02524b972918bef810087f8e7", explicit_content_filter: 0, afk_channel_id: null, nsfw_level: 0, description: null, - preferred_locale: 'en-US', - system_channel_id: '112760669178241024', + preferred_locale: "en-US", + system_channel_id: "112760669178241024", mfa_level: 0, /** @type {300} */ afk_timeout: 300, - id: '112760669178241024', - icon: 'a_f83622e09ead74f0c5c527fe241f8f8c', + id: "112760669178241024", + icon: "a_f83622e09ead74f0c5c527fe241f8f8c", emojis: [], premium_subscription_count: 14, roles: [], discovery_splash: null, default_message_notifications: 1, - region: 'deprecated', + region: "deprecated", max_video_channel_users: 25, verification_level: 0, application_id: null, premium_progress_bar_enabled: false, - banner: 'a_a666ae551605a2d8cda0afd591c0af3a', + banner: "a_a666ae551605a2d8cda0afd591c0af3a", features: [], vanity_url_code: null, hub_type: null, public_updates_channel_id: null, rules_channel_id: null, - name: 'Psychonauts 3', + name: "Psychonauts 3", max_stage_video_channel_users: 300, system_channel_flags: 0|0 } + }, + message: { + // Display order is text content, attachments, then stickers + sticker: { + id: "1106366167788044450", + type: 0, + content: "can have attachments too", + channel_id: "122155380120748034", + author: { + id: "113340068197859328", + username: "Cookie 🍪", + global_name: null, + display_name: null, + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "7766", + public_flags: 128, + avatar_decoration: null + }, + attachments: [{ + id: "1106366167486038016", + filename: "image.png", + size: 127373, + url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", + width: 333, + height: 287, + content_type: "image/png" + }], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-05-11T23:44:09.690000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + sticker_items: [{ + id: "1106323941183717586", + format_type: 1, + name: "pomu puff" + }] + } } } diff --git a/test/test.js b/test/test.js index 2c44bb3..ae6aea6 100644 --- a/test/test.js +++ b/test/test.js @@ -14,5 +14,6 @@ Object.assign(passthrough, { config, sync, db }) require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") +require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") From ae640a8fde8f38c3d5bd7f4242fca4c4f4b7cd38 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 15 May 2023 17:25:05 +1200 Subject: [PATCH 030/200] continue on stickers --- d2m/converters/message-to-event.test.js | 8 +++++++- db/ooye.db | Bin 94208 -> 94208 bytes matrix/file.js | 16 ++++++++++++++++ notes.md | 16 ++++++++++++++++ test/data.js | 13 ++++++++++++- 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 07a45f7..323dc78 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -23,6 +23,12 @@ test("message2event: stickers", async t => { }, }, { $type: "m.sticker", - todo: "todo" + body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", + info: { + mimetype: "image/png" + // thumbnail_url + // thumbnail_info + }, + url: "mxc://" }]) }) diff --git a/db/ooye.db b/db/ooye.db index 924e5995e795ae11ae409e01ad15cfcfed6e76d8..e62e0afe3a346783156dfc1c0677d063c496ed73 100644 GIT binary patch delta 399 zcmZp8z}oPDb%HeGwuv&%jN3LQEZNU2!%;Gs{eUS8$4-ut%^MX=IO?5cSW=BSR4fC6 z9YZ6`b23sA)4f6s)4eR?BYizQeGQE>e9~OflZ#5+yfTVYoGgo64Gj&>4b6<4rNM?6mzzgegeN;&<~SNTnPyg$nj89f2Il$}=D7Jfc?6_Y<|g@j8RrxP z_(wtwG3GWgF)%kUHa9o1G}`>{1RJ}E4A)WyzDnL;ep{|up%<2sClNbDup1kcz1?z%{cGFv1A4{>TOg5E=^y01kN# zQx8237Yr>7z_S?8jS7*lX0r#|{saOCQTv==5tBdrBeP8Fw1h=1IFkSX diff --git a/matrix/file.js b/matrix/file.js index 62a4550..a373676 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -80,8 +80,24 @@ function emoji(emojiID, animated) { else return base + ".png" } +const stickerFormat = new Map([ + [1, {label: "PNG", ext: "png", mime: "image/png"}], + [2, {label: "APNG", ext: "png", mime: "image/apng"}], + [3, {label: "LOTTIE", ext: "json", mime: "application/json"}], + [4, {label: "GIF", ext: "gif", mime: "image/gif"}] +]) + +function sticker(sticker) { + const format = stickerFormat.get(sticker.format_type) + if (!format) throw new Error(`No such format ${sticker.format_type} for sticker ${JSON.stringify(sticker)}`) + const ext = format.ext + return `/stickers/${sticker.id}.${ext}` +} + module.exports.guildIcon = guildIcon module.exports.userAvatar = userAvatar module.exports.memberAvatar = memberAvatar module.exports.emoji = emoji +module.exports.stickerFormat = stickerFormat +module.exports.sticker = sticker module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/notes.md b/notes.md index e63d9e5..3491682 100644 --- a/notes.md +++ b/notes.md @@ -60,6 +60,22 @@ The context-sensitive /invite command will invite Matrix users to the correspond # d2m events +## Login - backfill + +Need to backfill any messages that were missed while offline. + +After logging in, check last_message_id on each channel and compare against database to see if anything has been missed. However, mustn't interpret old channels from before the bridge was created as being "new". So, something has been missed if: + +- The last_message_id is not in the table of bridged messages +- The channel is already set up with a bridged room +- A message has been bridged in that channel before + +(If either of the last two conditions is false, that means the channel predates the bridge and we haven't actually missed anything there.) + +For channels that have missed messages, use the getChannelMessages function, and bridge each in turn. + +Can use custom transaction ID (?) to send the original timestamps to Matrix. See appservice docs for details. + ## Message sent 1. Transform content. diff --git a/test/data.js b/test/data.js index c94d132..9704c8d 100644 --- a/test/data.js +++ b/test/data.js @@ -47,7 +47,18 @@ module.exports = { general: { owner_id: "112760500130975744", premium_tier: 3, - stickers: [], + stickers: [{ + version: 1683838696974, + type: 2, + tags: "sunglasses", + name: "pomu puff", + id: "1106323941183717586", + guild_id: "112760669178241024", + format_type: 1, + description: "damn that tiny lil bitch really chuffing. puffing that fat ass dart", + available: true, + asset: "" + }], max_members: 500000, splash: "86a34ed02524b972918bef810087f8e7", explicit_content_filter: 0, From 512f61422d868235d0bf143df86157da14a5e6ed Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 15 May 2023 17:25:05 +1200 Subject: [PATCH 031/200] continue on stickers --- d2m/converters/message-to-event.test.js | 8 +++++++- matrix/file.js | 16 ++++++++++++++++ notes.md | 16 ++++++++++++++++ test/data.js | 13 ++++++++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 07a45f7..323dc78 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -23,6 +23,12 @@ test("message2event: stickers", async t => { }, }, { $type: "m.sticker", - todo: "todo" + body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", + info: { + mimetype: "image/png" + // thumbnail_url + // thumbnail_info + }, + url: "mxc://" }]) }) diff --git a/matrix/file.js b/matrix/file.js index 62a4550..a373676 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -80,8 +80,24 @@ function emoji(emojiID, animated) { else return base + ".png" } +const stickerFormat = new Map([ + [1, {label: "PNG", ext: "png", mime: "image/png"}], + [2, {label: "APNG", ext: "png", mime: "image/apng"}], + [3, {label: "LOTTIE", ext: "json", mime: "application/json"}], + [4, {label: "GIF", ext: "gif", mime: "image/gif"}] +]) + +function sticker(sticker) { + const format = stickerFormat.get(sticker.format_type) + if (!format) throw new Error(`No such format ${sticker.format_type} for sticker ${JSON.stringify(sticker)}`) + const ext = format.ext + return `/stickers/${sticker.id}.${ext}` +} + module.exports.guildIcon = guildIcon module.exports.userAvatar = userAvatar module.exports.memberAvatar = memberAvatar module.exports.emoji = emoji +module.exports.stickerFormat = stickerFormat +module.exports.sticker = sticker module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/notes.md b/notes.md index e63d9e5..3491682 100644 --- a/notes.md +++ b/notes.md @@ -60,6 +60,22 @@ The context-sensitive /invite command will invite Matrix users to the correspond # d2m events +## Login - backfill + +Need to backfill any messages that were missed while offline. + +After logging in, check last_message_id on each channel and compare against database to see if anything has been missed. However, mustn't interpret old channels from before the bridge was created as being "new". So, something has been missed if: + +- The last_message_id is not in the table of bridged messages +- The channel is already set up with a bridged room +- A message has been bridged in that channel before + +(If either of the last two conditions is false, that means the channel predates the bridge and we haven't actually missed anything there.) + +For channels that have missed messages, use the getChannelMessages function, and bridge each in turn. + +Can use custom transaction ID (?) to send the original timestamps to Matrix. See appservice docs for details. + ## Message sent 1. Transform content. diff --git a/test/data.js b/test/data.js index c94d132..9704c8d 100644 --- a/test/data.js +++ b/test/data.js @@ -47,7 +47,18 @@ module.exports = { general: { owner_id: "112760500130975744", premium_tier: 3, - stickers: [], + stickers: [{ + version: 1683838696974, + type: 2, + tags: "sunglasses", + name: "pomu puff", + id: "1106323941183717586", + guild_id: "112760669178241024", + format_type: 1, + description: "damn that tiny lil bitch really chuffing. puffing that fat ass dart", + available: true, + asset: "" + }], max_members: 500000, splash: "86a34ed02524b972918bef810087f8e7", explicit_content_filter: 0, From d8b83759091cd9314ed9b73719fe35ebb8e115f4 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 28 Jun 2023 23:38:58 +1200 Subject: [PATCH 032/200] fix a variety of bugs that snuck in --- d2m/actions/create-room.js | 6 ++++-- d2m/actions/send-message.js | 6 +++--- d2m/event-dispatcher.js | 2 +- db/ooye.db | Bin 94208 -> 106496 bytes matrix/api.js | 2 +- package-lock.json | 6 +++--- test/data.js | 2 +- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 96a9671..c4baa2d 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -58,7 +58,7 @@ async function channelToKState(channel, guild) { "m.room.join_rules/": { join_rule: "restricted", allow: [{ - type: "m.room.membership", + type: "m.room_membership", room_id: spaceID }] } @@ -179,7 +179,9 @@ async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) for (const channelID of channelIDs) { - await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + if (discord.channels.get(channelID)?.type === DiscordTypes.ChannelType.GuildText) { // TODO: guild sync thread channels and such. maybe make a helper function to check if a given channel is syncable? + await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + } } } diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 738c59a..897f7c0 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -15,14 +15,14 @@ const createRoom = sync.require("../actions/create-room") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function sendMessage(message) { - assert.ok(message.member) - +async function sendMessage(message, guild) { const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { + assert(message.member) senderMxid = await registerUser.ensureSimJoined(message.author, roomID) await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index eeee451..baf19a8 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -24,7 +24,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ onReactionAdd(client, data) { - if (data.emoji.id !== null) return // TOOD: image emoji reactions + if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) } diff --git a/db/ooye.db b/db/ooye.db index e62e0afe3a346783156dfc1c0677d063c496ed73..7d323c81fa70762b9aba35f54f9fda885d6c6bac 100644 GIT binary patch delta 19330 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 E2jqTg2mk;8 delta 3183 zcmeH}drXv97QpYF@BQ8T-OEsA08x+@v5LGLR746mRl)JmZe#jVrqt3^R1n0Jx?5n3 zudeJ?yt^Ii3rSW@+oX$`C`y|~t4&RVE$EINV^^2o<6AD4X6S~GH|NSpa;pF`%MSW87bz{j(Q zbB=Ye*XVc7L8p->lQetC{)^pVeQzDJRMJP znwV`>GFk0pbRnz0;^mcL)hcz_>>AqZ89NpSe<_%ifMdjXE0vc#s(Th?VYIyZmCA;a zbWu(cvl;?xYFEA-sIOeLIsoaFft*0je<_cwS131u!hNI%+rh_l!;_YV*AwxA*(}ac zF7B92pK9tJf10>S;zc)cM&vGCR#9DDxiU~!Q?tsLYCN!*XeqLo8Lb#$95-naF~0|b z@_X`FZn2NpCiWCdbiQ^za2lMM4yEVmK3YY+WQd$5+sR@w&c11P*&FNycD!}fdf!@W z6kZc&mm6Qe`*97<#|k>z+McI*OO+gdsJjj4Xx=%1pN9Zv1L7Tkvm}b_ zh@>Y$)iwJ zHpnv|^Rkr5e$n?suI9}IIeiaAP6DirVg@8_yCda9ke8!!0>~kdkAs}Q7m~({!g8PmD!M@i?n{aRdK-uXodse*>Q9m!paLKYR0l+W-UYIOt^ip; zo*_vl&;lR>Xd{pg^bwE-bQ?$onlLO$0jdCU0qp?7K;6Sxv82?NmvFuni>zYDbSlZG zhhuTC+1ioV8EfHwJ&d!5v$hwj%6x57$XJbE3pc*BiN{&?Fyb}rE9R5eHGys*{baK} z9Cl}aV~2;=X+tFe{0}cPFWs#y= z>;=SUh2ysk(nB53V&UEs7hb)08*XxBbEW7k;-B;1^YVv-;^Cl>|F;hZ#s7Cugu36t zx$t76vB=LiaS2an5qX&}GtE0+j*`%a(J?;{@D^a6AExi)Z7_Z3f~|=DoR$IZkZ8cg zPBQC-(UC9Tcf4k}{%DI)46`W7-jCSp&NIBwKF>UK5;b%h^B- zT27X8$}X@{UQO;hsbr_q!_Uc6i7mE9R6T_FTm0_LqBA*}q zD@s)Ttx;zq(OQ3#XuBRSVtO-#uXkd2$@QaHXQ$B`|C&%^HJ&bx^iB?MxzT{Vuv-*^ z5I@N`@gjQ!J8HGF>CS+2#Hn|t(fjncl|i4RIyq_XCWXXhe`e;&V;M5^WpQeOe|f02 z3QzfGPioMK)x@s8QP8p(T2+O|h`T*07>R_&3Hll)6aK9)y;fI!545DORn@2ct3;%C zwiwYrQ76r4h95;gLR`9bOs5+j-0jaFpqIGSvi~>&l{KIlqT+g@Jb*F(*!%!0mZhSb z-*UsJ%#kUP-V#yNelI*#PDJ^$d0ksS%EX?bUJIDeEJhTK{wJ_)F7uwkUY^j z<0ZVsJk)U>ceYgFdM(9aZbWGp;^+BxK9j3#KYP*njCq_EnLI$d=DyDO6x_~QJN%tPnOMh3l-D8tEB*-|`W+q{+km7nDw^D6!ZpUoHZbQxywQ2IiAF^RIR3OmS`@*eKvr}+W? r8}8z%>=gT!ZIxjwm^w-+z@e*2*wcBS9GBw>adr#hr}*ao{Sf{RZUCG= diff --git a/matrix/api.js b/matrix/api.js index 846ea64..3ec014d 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -78,7 +78,7 @@ function getAllState(roomID) { async function sendState(roomID, type, stateKey, content, mxid) { console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) assert.ok(type) - assert.ok(stateKey) + assert.ok(typeof stateKey === "string") /** @type {import("../types").R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) return root.event_id diff --git a/package-lock.json b/package-lock.json index 9556331..224e232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -478,9 +478,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/better-sqlite3": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz", - "integrity": "sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.4.0.tgz", + "integrity": "sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==", "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", diff --git a/test/data.js b/test/data.js index 9704c8d..6a65b5f 100644 --- a/test/data.js +++ b/test/data.js @@ -33,7 +33,7 @@ module.exports = { "m.room.join_rules/": { join_rule: "restricted", allow: [{ - type: "m.room.membership", + type: "m.room_membership", room_id: "!jjWAGMeQdNrVZSSfvz:cadence.moe" }] }, From 584f19a01140ba1fde2ec35fd26aac28961a94a4 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 28 Jun 2023 23:38:58 +1200 Subject: [PATCH 033/200] fix a variety of bugs that snuck in --- d2m/actions/create-room.js | 6 ++++-- d2m/actions/send-message.js | 6 +++--- d2m/event-dispatcher.js | 2 +- matrix/api.js | 2 +- package-lock.json | 6 +++--- test/data.js | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 96a9671..c4baa2d 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -58,7 +58,7 @@ async function channelToKState(channel, guild) { "m.room.join_rules/": { join_rule: "restricted", allow: [{ - type: "m.room.membership", + type: "m.room_membership", room_id: spaceID }] } @@ -179,7 +179,9 @@ async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) for (const channelID of channelIDs) { - await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + if (discord.channels.get(channelID)?.type === DiscordTypes.ChannelType.GuildText) { // TODO: guild sync thread channels and such. maybe make a helper function to check if a given channel is syncable? + await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + } } } diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 738c59a..897f7c0 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -15,14 +15,14 @@ const createRoom = sync.require("../actions/create-room") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function sendMessage(message) { - assert.ok(message.member) - +async function sendMessage(message, guild) { const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { + assert(message.member) senderMxid = await registerUser.ensureSimJoined(message.author, roomID) await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index eeee451..baf19a8 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -24,7 +24,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ onReactionAdd(client, data) { - if (data.emoji.id !== null) return // TOOD: image emoji reactions + if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) } diff --git a/matrix/api.js b/matrix/api.js index 846ea64..3ec014d 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -78,7 +78,7 @@ function getAllState(roomID) { async function sendState(roomID, type, stateKey, content, mxid) { console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) assert.ok(type) - assert.ok(stateKey) + assert.ok(typeof stateKey === "string") /** @type {import("../types").R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) return root.event_id diff --git a/package-lock.json b/package-lock.json index 9556331..224e232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -478,9 +478,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/better-sqlite3": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz", - "integrity": "sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.4.0.tgz", + "integrity": "sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==", "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", diff --git a/test/data.js b/test/data.js index 9704c8d..6a65b5f 100644 --- a/test/data.js +++ b/test/data.js @@ -33,7 +33,7 @@ module.exports = { "m.room.join_rules/": { join_rule: "restricted", allow: [{ - type: "m.room.membership", + type: "m.room_membership", room_id: "!jjWAGMeQdNrVZSSfvz:cadence.moe" }] }, From 5b15f710d7c811c7c1f4f684234b85f87bab10bd Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 29 Jun 2023 00:06:56 +1200 Subject: [PATCH 034/200] support d->m stickers --- d2m/actions/create-room.js | 33 +++++++++++++++++++----- d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.js | 31 ++++++++++++++++++++-- d2m/converters/message-to-event.test.js | 4 +-- d2m/event-dispatcher.js | 8 +++--- db/ooye.db | Bin 106496 -> 106496 bytes matrix/file.js | 3 ++- test/data.js | 4 +-- 8 files changed, 67 insertions(+), 18 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index c4baa2d..479bb79 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -32,12 +32,13 @@ function applyKStateDiffToRoom(roomID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel - * @param {import("discord-api-types/v10").APIGuild} guild + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuild} guild */ async function channelToKState(channel, guild) { const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) assert.ok(typeof spaceID === "string") + const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) const avatarEventContent = {} if (guild.icon) { @@ -45,9 +46,27 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } + // TODO: Improve nasty nested ifs + let convertedName, convertedTopic + if (customName) { + convertedName = customName + if (channel.topic) { + convertedTopic = `${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } else { + convertedTopic = `${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } + } else { + convertedName = channel.name + if (channel.topic) { + convertedTopic = `${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } else { + convertedTopic = `Channel ID: ${channel.id}\nGuild ID: ${guild.id}` + } + } + const channelKState = { - "m.room.name/": {name: channel.name}, - "m.room.topic/": {$if: channel.topic, topic: channel.topic}, + "m.room.name/": {name: convertedName}, + "m.room.topic/": {topic: convertedTopic}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, @@ -69,7 +88,7 @@ async function channelToKState(channel, guild) { /** * Create a bridge room, store the relationship in the database, and add it to the guild's space. - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuildTextChannel} channel * @param guild * @param {string} spaceID * @param {any} kstate @@ -96,7 +115,7 @@ async function createRoom(channel, guild, spaceID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildChannel} channel + * @param {DiscordTypes.APIGuildChannel} channel */ function channelToGuild(channel) { const guildID = channel.guild_id @@ -129,7 +148,7 @@ function channelToGuild(channel) { * @returns {Promise} room ID */ async function _syncRoom(channelID, shouldActuallySync) { - /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + /** @ts-ignore @type {DiscordTypes.APIGuildChannel} */ const channel = discord.channels.get(channelID) assert.ok(channel) const guild = channelToGuild(channel) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 897f7c0..24a825a 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message) + const events = await messageToEvent.messageToEvent(message, guild) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 86be8ac..549d104 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -1,16 +1,18 @@ // @ts-check +const assert = require("assert").strict const markdown = require("discord-markdown") const passthrough = require("../../passthrough") -const { sync, db } = passthrough +const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") /** * @param {import("discord-api-types/v10").APIMessage} message + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function messageToEvent(message) { +async function messageToEvent(message, guild) { const events = [] // Text content appears first @@ -87,6 +89,31 @@ async function messageToEvent(message) { events.push(...attachmentEvents) // Then stickers + if (message.sticker_items) { + const stickerEvents = await Promise.all(message.sticker_items.map(async stickerItem => { + const format = file.stickerFormat.get(stickerItem.format_type) + if (format?.mime) { + let body = stickerItem.name + const sticker = guild.stickers.find(sticker => sticker.id === stickerItem.id) + if (sticker && sticker.description) body += ` - ${sticker.description}` + return { + $type: "m.sticker", + body, + info: { + mimetype: format.mime + }, + url: await file.uploadDiscordFileToMxc(file.sticker(stickerItem)) + } + } else { + return { + $type: "m.room.message", + msgtype: "m.text", + body: "Unsupported sticker format. Name: " + stickerItem.name + } + } + })) + events.push(...stickerEvents) + } return events } diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 323dc78..c92cd85 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -4,7 +4,7 @@ const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker) + const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", msgtype: "m.text", @@ -29,6 +29,6 @@ test("message2event: stickers", async t => { // thumbnail_url // thumbnail_info }, - url: "mxc://" + url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" }]) }) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index baf19a8..fbcdca0 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,5 +1,4 @@ -// @ts-check - +const assert = require("assert").strict const {sync} = require("../passthrough") /** @type {import("./actions/send-message")}) */ @@ -15,8 +14,11 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = client.channels.get(message.channel_id) + const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024") return // TODO: activate on other servers (requires the space creation flow to be done first) - sendMessage.sendMessage(message) + sendMessage.sendMessage(message, guild) }, /** diff --git a/db/ooye.db b/db/ooye.db index 7d323c81fa70762b9aba35f54f9fda885d6c6bac..5168a0f57405b2bfa3d220e31089a5c75bca3e6c 100644 GIT binary patch delta 1757 zcmb7^%}?8Q9L8}7F#(d0c0xj+X-sTllfwM+b8Ks`iOD zJ8S}3RaI5ca+x-1(u%Tr>)YoKVXMmHfhGN!?aSt$*1S@{rdMj&v*No zY5SRJcMh7_n`g#mMwY8T9_9CKlZNVu1G+h4?HL}b6VpqtK|k9b!6)#&@g>9emZ#9o zu>kai`pNX%ke_CtO=PqHEw0r$xsvX;Hd9K3ZI>BwMcnc^gUVJEaI0Xg9jv=EfoM!n zgi=NqxRv#4Fdp|uwx|q3`zj)%5TfjGb8FIimI=gAr0WezF1eTxQ|kDQ0)(o_*Vu*w z^h?EXcq{3a`~|t{$~#<12kY{+y=EipFpn2h4^Jk@Be) zFZ#TW0`KyslPu~8)ciHkFLZMqvg%B9+M=VIDN3Xu093uN1a~2B=51cXBGOL8K7)mjY-SLjZvg6vC|Qlxui#4#5D0 zq1rau;~*51)giC+XYgNxbP7omDDK2)gdkBAFd3b4jrd(av_ByU3?l%7;7nSBm=_CT zt5@yv7-;%9&7U}#8bpwqD;vv|GNpEeQ2IzIPy0*$iaT~e!EuTpfD@seG+yrs z)YJaV>mH%iOG~kGijOzCY*rLjwNI;8*_oa;?!?Xz>R)$fhaYuL#z*aDS%tS33}$&~ zM|@`E$7e$pKAV5z6+@P#M#(-r`ZQshvQIr9v3V?Zi#YXsXx;s3O11CL%wD#DeQ4kR z)9^N8Ivuh7U^{?MY<1fQwn^(R)~~ExtIs-P`5k^?I<-+a6{4I3ux=HFo4 z{KPykhvED%`dBdy^b2#1<8Vh8T;mXI=z_~SKX^&$Z|jPy7F_H1EnV>c>|YxFv@SS{ z`Bihy>WXWmF*#iN^4C8Mlg;CaUFgF}XnF7OUDNwZMl>|53NKJoUKHzv=J7WVA1OjRRVv}L4GLziVF_R4vU;&epFsw3@pwcV^1783!lhD$SvtJXX3g!U(heC4tNen4i*jN4YUX-lPaJj3JMVk;R&z_e+f>rUZ8UZv9M(UvnZ(G=K>CX ow+fH}JqrPAk)da|ubTm30+&dR0XUaPo&h$KX7@O^ww?h>0h8NdzyJUM diff --git a/matrix/file.js b/matrix/file.js index a373676..64cd492 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -83,10 +83,11 @@ function emoji(emojiID, animated) { const stickerFormat = new Map([ [1, {label: "PNG", ext: "png", mime: "image/png"}], [2, {label: "APNG", ext: "png", mime: "image/apng"}], - [3, {label: "LOTTIE", ext: "json", mime: "application/json"}], + [3, {label: "LOTTIE", ext: "json", mime: null}], [4, {label: "GIF", ext: "gif", mime: "image/gif"}] ]) +/** @param {{id: string, format_type: number}} sticker */ function sticker(sticker) { const format = stickerFormat.get(sticker.format_type) if (!format) throw new Error(`No such format ${sticker.format_type} for sticker ${JSON.stringify(sticker)}`) diff --git a/test/data.js b/test/data.js index 6a65b5f..bb70cbe 100644 --- a/test/data.js +++ b/test/data.js @@ -22,8 +22,8 @@ module.exports = { }, room: { general: { - "m.room.name/": {name: "collective-unconscious"}, - "m.room.topic/": {topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:"}, + "m.room.name/": {name: "main"}, + "m.room.topic/": {topic: "collective-unconscious | https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:\n\nChannel ID: 112760669178241024\nGuild ID: 112760669178241024"}, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { From 740ddc36d12a222d84f52b913f1f6cf5bd0f664d Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 29 Jun 2023 00:06:56 +1200 Subject: [PATCH 035/200] support d->m stickers --- d2m/actions/create-room.js | 33 +++++++++++++++++++------ d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.js | 31 +++++++++++++++++++++-- d2m/converters/message-to-event.test.js | 4 +-- d2m/event-dispatcher.js | 8 +++--- matrix/file.js | 3 ++- test/data.js | 4 +-- 7 files changed, 67 insertions(+), 18 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index c4baa2d..479bb79 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -32,12 +32,13 @@ function applyKStateDiffToRoom(roomID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel - * @param {import("discord-api-types/v10").APIGuild} guild + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuild} guild */ async function channelToKState(channel, guild) { const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) assert.ok(typeof spaceID === "string") + const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) const avatarEventContent = {} if (guild.icon) { @@ -45,9 +46,27 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } + // TODO: Improve nasty nested ifs + let convertedName, convertedTopic + if (customName) { + convertedName = customName + if (channel.topic) { + convertedTopic = `${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } else { + convertedTopic = `${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } + } else { + convertedName = channel.name + if (channel.topic) { + convertedTopic = `${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + } else { + convertedTopic = `Channel ID: ${channel.id}\nGuild ID: ${guild.id}` + } + } + const channelKState = { - "m.room.name/": {name: channel.name}, - "m.room.topic/": {$if: channel.topic, topic: channel.topic}, + "m.room.name/": {name: convertedName}, + "m.room.topic/": {topic: convertedTopic}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, @@ -69,7 +88,7 @@ async function channelToKState(channel, guild) { /** * Create a bridge room, store the relationship in the database, and add it to the guild's space. - * @param {import("discord-api-types/v10").APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuildTextChannel} channel * @param guild * @param {string} spaceID * @param {any} kstate @@ -96,7 +115,7 @@ async function createRoom(channel, guild, spaceID, kstate) { } /** - * @param {import("discord-api-types/v10").APIGuildChannel} channel + * @param {DiscordTypes.APIGuildChannel} channel */ function channelToGuild(channel) { const guildID = channel.guild_id @@ -129,7 +148,7 @@ function channelToGuild(channel) { * @returns {Promise} room ID */ async function _syncRoom(channelID, shouldActuallySync) { - /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + /** @ts-ignore @type {DiscordTypes.APIGuildChannel} */ const channel = discord.channels.get(channelID) assert.ok(channel) const guild = channelToGuild(channel) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 897f7c0..24a825a 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message) + const events = await messageToEvent.messageToEvent(message, guild) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 86be8ac..549d104 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -1,16 +1,18 @@ // @ts-check +const assert = require("assert").strict const markdown = require("discord-markdown") const passthrough = require("../../passthrough") -const { sync, db } = passthrough +const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") /** * @param {import("discord-api-types/v10").APIMessage} message + * @param {import("discord-api-types/v10").APIGuild} guild */ -async function messageToEvent(message) { +async function messageToEvent(message, guild) { const events = [] // Text content appears first @@ -87,6 +89,31 @@ async function messageToEvent(message) { events.push(...attachmentEvents) // Then stickers + if (message.sticker_items) { + const stickerEvents = await Promise.all(message.sticker_items.map(async stickerItem => { + const format = file.stickerFormat.get(stickerItem.format_type) + if (format?.mime) { + let body = stickerItem.name + const sticker = guild.stickers.find(sticker => sticker.id === stickerItem.id) + if (sticker && sticker.description) body += ` - ${sticker.description}` + return { + $type: "m.sticker", + body, + info: { + mimetype: format.mime + }, + url: await file.uploadDiscordFileToMxc(file.sticker(stickerItem)) + } + } else { + return { + $type: "m.room.message", + msgtype: "m.text", + body: "Unsupported sticker format. Name: " + stickerItem.name + } + } + })) + events.push(...stickerEvents) + } return events } diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 323dc78..c92cd85 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -4,7 +4,7 @@ const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker) + const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", msgtype: "m.text", @@ -29,6 +29,6 @@ test("message2event: stickers", async t => { // thumbnail_url // thumbnail_info }, - url: "mxc://" + url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" }]) }) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index baf19a8..fbcdca0 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,5 +1,4 @@ -// @ts-check - +const assert = require("assert").strict const {sync} = require("../passthrough") /** @type {import("./actions/send-message")}) */ @@ -15,8 +14,11 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = client.channels.get(message.channel_id) + const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024") return // TODO: activate on other servers (requires the space creation flow to be done first) - sendMessage.sendMessage(message) + sendMessage.sendMessage(message, guild) }, /** diff --git a/matrix/file.js b/matrix/file.js index a373676..64cd492 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -83,10 +83,11 @@ function emoji(emojiID, animated) { const stickerFormat = new Map([ [1, {label: "PNG", ext: "png", mime: "image/png"}], [2, {label: "APNG", ext: "png", mime: "image/apng"}], - [3, {label: "LOTTIE", ext: "json", mime: "application/json"}], + [3, {label: "LOTTIE", ext: "json", mime: null}], [4, {label: "GIF", ext: "gif", mime: "image/gif"}] ]) +/** @param {{id: string, format_type: number}} sticker */ function sticker(sticker) { const format = stickerFormat.get(sticker.format_type) if (!format) throw new Error(`No such format ${sticker.format_type} for sticker ${JSON.stringify(sticker)}`) diff --git a/test/data.js b/test/data.js index 6a65b5f..bb70cbe 100644 --- a/test/data.js +++ b/test/data.js @@ -22,8 +22,8 @@ module.exports = { }, room: { general: { - "m.room.name/": {name: "collective-unconscious"}, - "m.room.topic/": {topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:"}, + "m.room.name/": {name: "main"}, + "m.room.topic/": {topic: "collective-unconscious | https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:\n\nChannel ID: 112760669178241024\nGuild ID: 112760669178241024"}, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { From 109b3d256018aa6291268c0607d8dc2d017ccad2 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 29 Jun 2023 08:08:17 +1200 Subject: [PATCH 036/200] add appservice listener --- db/ooye.db | Bin 106496 -> 131072 bytes index.js | 7 ++++++- m2d/appservice.js | 8 ++++++++ m2d/event-dispatcher.js | 8 ++++++++ passthrough.js | 1 + 5 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 m2d/appservice.js create mode 100644 m2d/event-dispatcher.js diff --git a/db/ooye.db b/db/ooye.db index 5168a0f57405b2bfa3d220e31089a5c75bca3e6c..9c7d480b489cab00620de78d776874b511700f41 100644 GIT binary patch delta 22266 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 YYneHQn7QDl#}jL=dVJnZkL$qv4~bBlCIA2c delta 1447 zcmYLHeNYuu6yNjSeY^Yi;~t&}BJ%M>6U_x#Y;ZJ35R@buDyeC-KnJsg1VP2p(m==W z2Biz_P$NP)pbRSb!k00L7BWui1S()@keZ_Lp-g``idy$M)3h^Z_IJe4oLf{R z7o8IKj?)7LK?vq1af8yyGa#^W>DM?qM~DJ{fv&fG1%)=*c^&5I>CCS^Y&3M^eyL3= zmnYlK>rLSANP!wQyFHj?wHFbXCzw|K5X7>e4wLnUnQV2q$tnX)Rv2uuxg5{AXb}4n z!DKP@vFsb}2pwxO+ocRbwhFDRBFeCe20>)ymkggLi+yq=mOZ#sLP#IaDL5L-nlBr~ zv4KZUPcs~|I5JsCXC@JK?%LG3hlo`ixVrWbX*vfT$D&wAR~C^T@I#fyV9UD8h&;fP zM!JWIGmV4$S2Bqz35;3C4aWqIve@Axv7V-17XneUIYGV336HtEhxfAoY6j6V1@rT3 znM9A^(FER8*|id)-{x1R_aKU;|IP?qz(e?h!2)|?3BAe>uk`F8n8#Imd!h)RwaNT? zGYPhHFr&p__v%G9FWB%!V%X^xL((~VPL!DBPHn7ia(JXk4`&8(-+!Nt1C^WaZ?l5( z;XQ9rKI|l=4U@c4g>YH%3An@x0awxAXcD9OA&u3mM8^sjDF!xvO^EmVHb{1cDL$IB zK+t-XE!t-KwlY@jrf2kFJyhSW{NYJ1dLBIPm?^TSU?&qNy0jYt&evV~D(!~;Sg+JC z=m~TxT};2jwHQcu;zio$d2R1)xv}B0oyAoIvi@~J9>0CVw)c6R7_mdZU$9bY!(5z= zDsSu%{fNFq1NB?d1F=K@R-P=C>s$1N`grZW)}obY8JeMvs6VR}>I!v+Dk`1I0cDMn zDA^R3^SU(M+2H)tnI^sK43!7uW_g#qsIfX*WF0ke*2FCou;%VloQ&4o2b_PHv;# zfiwu>(sYWQ*5Wd}jY)VGYv>f*g*J?!t@LHuM|aaa&bCv2m-%27)If+gB@>!qRH)o4 uU@LCN1p1JkpzGyUD|#tJC<;*)sCMP7@)^(DEL$(;LHM(kcp)49iTf8qX36pZ diff --git a/index.js b/index.js index 807b49f..f4c59c6 100644 --- a/index.js +++ b/index.js @@ -9,13 +9,18 @@ const db = new sqlite("db/ooye.db") const sync = new HeatSync() -Object.assign(passthrough, { config, sync, db }) +Object.assign(passthrough, {config, sync, db}) const DiscordClient = require("./d2m/discord-client") const discord = new DiscordClient(config.discordToken) passthrough.discord = discord +const as = require("./m2d/appservice") +passthrough.as = as + +sync.require("./m2d/event-dispatcher") + ;(async () => { await discord.cloud.connect() console.log("Discord gateway started") diff --git a/m2d/appservice.js b/m2d/appservice.js new file mode 100644 index 0000000..e99a822 --- /dev/null +++ b/m2d/appservice.js @@ -0,0 +1,8 @@ +const reg = require("../matrix/read-registration") +const AppService = require("matrix-appservice").AppService +const as = new AppService({ + homeserverToken: reg.hs_token +}) +as.listen(+(new URL(reg.url).port)) + +module.exports = as diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js new file mode 100644 index 0000000..136aae0 --- /dev/null +++ b/m2d/event-dispatcher.js @@ -0,0 +1,8 @@ +const assert = require("assert").strict +const {sync, as} = require("../passthrough") + +// Grab Matrix events we care about for the bridge, check them, and pass them on + +sync.addTemporaryListener(as, "type:m.room.message", event => { + console.log(event) +}) diff --git a/passthrough.js b/passthrough.js index 8ef75db..16de1bb 100644 --- a/passthrough.js +++ b/passthrough.js @@ -7,6 +7,7 @@ * @property {import("./d2m/discord-client")} discord * @property {import("heatsync")} sync * @property {import("better-sqlite3/lib/database")} db + * @property {import("matrix-appservice").AppService} as */ /** @type {Passthrough} */ // @ts-ignore From d70199f890f546d3f8e5da2809708f6f6ee2f68b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 29 Jun 2023 08:08:17 +1200 Subject: [PATCH 037/200] add appservice listener --- index.js | 7 ++++++- m2d/appservice.js | 8 ++++++++ m2d/event-dispatcher.js | 8 ++++++++ passthrough.js | 1 + 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 m2d/appservice.js create mode 100644 m2d/event-dispatcher.js diff --git a/index.js b/index.js index 807b49f..f4c59c6 100644 --- a/index.js +++ b/index.js @@ -9,13 +9,18 @@ const db = new sqlite("db/ooye.db") const sync = new HeatSync() -Object.assign(passthrough, { config, sync, db }) +Object.assign(passthrough, {config, sync, db}) const DiscordClient = require("./d2m/discord-client") const discord = new DiscordClient(config.discordToken) passthrough.discord = discord +const as = require("./m2d/appservice") +passthrough.as = as + +sync.require("./m2d/event-dispatcher") + ;(async () => { await discord.cloud.connect() console.log("Discord gateway started") diff --git a/m2d/appservice.js b/m2d/appservice.js new file mode 100644 index 0000000..e99a822 --- /dev/null +++ b/m2d/appservice.js @@ -0,0 +1,8 @@ +const reg = require("../matrix/read-registration") +const AppService = require("matrix-appservice").AppService +const as = new AppService({ + homeserverToken: reg.hs_token +}) +as.listen(+(new URL(reg.url).port)) + +module.exports = as diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js new file mode 100644 index 0000000..136aae0 --- /dev/null +++ b/m2d/event-dispatcher.js @@ -0,0 +1,8 @@ +const assert = require("assert").strict +const {sync, as} = require("../passthrough") + +// Grab Matrix events we care about for the bridge, check them, and pass them on + +sync.addTemporaryListener(as, "type:m.room.message", event => { + console.log(event) +}) diff --git a/passthrough.js b/passthrough.js index 8ef75db..16de1bb 100644 --- a/passthrough.js +++ b/passthrough.js @@ -7,6 +7,7 @@ * @property {import("./d2m/discord-client")} discord * @property {import("heatsync")} sync * @property {import("better-sqlite3/lib/database")} db + * @property {import("matrix-appservice").AppService} as */ /** @type {Passthrough} */ // @ts-ignore From cd4d7770901b039d5d62cc9070150e69d3774da3 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 30 Jun 2023 15:15:23 +1200 Subject: [PATCH 038/200] add test for member state content --- d2m/actions/register-user.js | 1 + d2m/actions/register-user.test.js | 24 +++++++++++++++++ test/data.js | 45 +++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 d2m/actions/register-user.test.js diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index cc5f515..1d1eb3d 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -154,6 +154,7 @@ async function syncAllUsersInRoom(roomID) { } } +module.exports._memberToStateContent = memberToStateContent module.exports.ensureSim = ensureSim module.exports.ensureSimJoined = ensureSimJoined module.exports.syncUser = syncUser diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js new file mode 100644 index 0000000..7e23450 --- /dev/null +++ b/d2m/actions/register-user.test.js @@ -0,0 +1,24 @@ +const {channelToKState} = require("./create-room") +const {_memberToStateContent} = require("./register-user") +const {test} = require("supertape") +const testData = require("../../test/data") + +test("member2state: general", async t => { + t.deepEqual( + await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), + { + avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", + displayname: "The Expert's Submarine | aprilsong", + membership: "join", + "moe.cadence.ooye.member": { + avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" + }, + "uk.half-shot.discord.member": { + bot: false, + displayColor: null, + id: "134826546694193153", + username: "@aprilsong" + } + } + ) +}) diff --git a/test/data.js b/test/data.js index bb70cbe..85b3cd4 100644 --- a/test/data.js +++ b/test/data.js @@ -93,6 +93,51 @@ module.exports = { system_channel_flags: 0|0 } }, + member: { + sheep: { + avatar: "38dd359aa12bcd52dd3164126c587f8c", + communication_disabled_until: null, + flags: 0, + joined_at: "2020-10-14T22:08:37.804000+00:00", + nick: "The Expert's Submarine", + pending: false, + premium_since: "2022-05-04T00:28:44.326000+00:00", + roles: [ + "112767366235959296", "118924814567211009", + "118923488755974146", "199995902742626304", + "204427286542417920", "217013981053845504", + "222168467627835392", "260993819204386816", + "265239342648131584", "271173313575780353", + "225744901915148298", "287733611912757249", + "318243902521868288", "348651574924541953", + "352291384021090304", "378402925128712193", + "392141548932038658", "393912152173576203", + "1123460940935991296", "872274377150980116", + "373336013109461013", "530220455085473813", + "768280323829137430", "842343433452257310", + "454567553738473472", "920107226528612383", + "1123528381514911745", "1040735082610167858", + "585531096071012409", "849737964090556488", + "660272211449479249" + ], + user: { + id: "134826546694193153", + username: "aprilsong", + avatar: "c754c120bce07ae3b3130e2b0e61d9dd", + discriminator: "0", + public_flags: 640, + flags: 640, + banner: "a3ad0693213f9dbf793b4159dbae0717", + accent_color: null, + global_name: "sheep", + avatar_decoration: null, + display_name: "sheep", + banner_color: null + }, + mute: false, + deaf: false + } + }, message: { // Display order is text content, attachments, then stickers sticker: { From cc1210729f36b252f097abbcdde10868b7872367 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 30 Jun 2023 15:15:23 +1200 Subject: [PATCH 039/200] add test for member state content --- d2m/actions/register-user.js | 1 + d2m/actions/register-user.test.js | 24 +++++++++++++++++ test/data.js | 45 +++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 d2m/actions/register-user.test.js diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index cc5f515..1d1eb3d 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -154,6 +154,7 @@ async function syncAllUsersInRoom(roomID) { } } +module.exports._memberToStateContent = memberToStateContent module.exports.ensureSim = ensureSim module.exports.ensureSimJoined = ensureSimJoined module.exports.syncUser = syncUser diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js new file mode 100644 index 0000000..7e23450 --- /dev/null +++ b/d2m/actions/register-user.test.js @@ -0,0 +1,24 @@ +const {channelToKState} = require("./create-room") +const {_memberToStateContent} = require("./register-user") +const {test} = require("supertape") +const testData = require("../../test/data") + +test("member2state: general", async t => { + t.deepEqual( + await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), + { + avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", + displayname: "The Expert's Submarine | aprilsong", + membership: "join", + "moe.cadence.ooye.member": { + avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" + }, + "uk.half-shot.discord.member": { + bot: false, + displayColor: null, + id: "134826546694193153", + username: "@aprilsong" + } + } + ) +}) diff --git a/test/data.js b/test/data.js index bb70cbe..85b3cd4 100644 --- a/test/data.js +++ b/test/data.js @@ -93,6 +93,51 @@ module.exports = { system_channel_flags: 0|0 } }, + member: { + sheep: { + avatar: "38dd359aa12bcd52dd3164126c587f8c", + communication_disabled_until: null, + flags: 0, + joined_at: "2020-10-14T22:08:37.804000+00:00", + nick: "The Expert's Submarine", + pending: false, + premium_since: "2022-05-04T00:28:44.326000+00:00", + roles: [ + "112767366235959296", "118924814567211009", + "118923488755974146", "199995902742626304", + "204427286542417920", "217013981053845504", + "222168467627835392", "260993819204386816", + "265239342648131584", "271173313575780353", + "225744901915148298", "287733611912757249", + "318243902521868288", "348651574924541953", + "352291384021090304", "378402925128712193", + "392141548932038658", "393912152173576203", + "1123460940935991296", "872274377150980116", + "373336013109461013", "530220455085473813", + "768280323829137430", "842343433452257310", + "454567553738473472", "920107226528612383", + "1123528381514911745", "1040735082610167858", + "585531096071012409", "849737964090556488", + "660272211449479249" + ], + user: { + id: "134826546694193153", + username: "aprilsong", + avatar: "c754c120bce07ae3b3130e2b0e61d9dd", + discriminator: "0", + public_flags: 640, + flags: 640, + banner: "a3ad0693213f9dbf793b4159dbae0717", + accent_color: null, + global_name: "sheep", + avatar_decoration: null, + display_name: "sheep", + banner_color: null + }, + mute: false, + deaf: false + } + }, message: { // Display order is text content, attachments, then stickers sticker: { From ed9ea57f12548e5889e77af579a7b14e5fab69f0 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 30 Jun 2023 15:15:34 +1200 Subject: [PATCH 040/200] add webhook creation utility --- db/ooye.db | Bin 131072 -> 143360 bytes m2d/actions/register-webhook.js | 50 ++++++++++++++++++++++++++++++++ m2d/actions/send-message.js | 23 +++++++++++++++ test/test.js | 1 + types.d.ts | 5 ++++ 5 files changed, 79 insertions(+) create mode 100644 m2d/actions/register-webhook.js create mode 100644 m2d/actions/send-message.js diff --git a/db/ooye.db b/db/ooye.db index 9c7d480b489cab00620de78d776874b511700f41..be5650515d1dd449ecdae32f120a2e170559af4d 100644 GIT binary patch delta 623 zcmZo@;AnWjF+p1J00RSqG7!Un)eHcVJJSS$WvT=Cw@$%l=ENJkJ zZE_F$UgJg!ZFX^KX~rh^lEkE()biA%jQsp;D5dBe( zV;|s<664~8NyV4sXQ$>VafY}?giP+{lHB}_ql$@-O`8quCUY$|ac|YhAGjqZpXcV? zyqMd7Nsv!mRh6-c8)UhzE}OWcG)P(;YI?j8l%cGv1CtYk$r(Z!N}Fx?P82e$F+82j z-r&dtq&ymCvo>J~W*eEkOyIaxI*u=uf%+$op%+kcr(%8_{SU<5Wu_UpmSl`$p zCB@j(GBMH6C@DF`)CkBkG&3a!fXxDJEHzmYVC4>sXRpP?Y84ni-JkT3Qv5QIMlHS#g$Fv(L=! zJ~J72m`QPS9%SIJ;QPRr!TW&Mn`asiA9owqKdv&)gTOdh%*obhqy}=hVlmj^R^&T; sa$=c+G%iOqvZ_vJyv^vo{lG0o2URv+Q1l3F7Ie7DKXHR18y7w<0F)fLnE(I) delta 297 zcmZp8z|qjaF+o~z9|Hq}0uaN1=0qK1o_!2@Sr>S@eHer|Ste$yvT?_N;ATOCZ)}r$ z*!NEU%fYSzl-(k(s>;};T9TNQlbW27n3tED6JL~{pNq_9nw-Ly#HcX&1fS++7S0@| z$^W=H*vz%q#JyD~f8dswe4d+k^I~oTCe<9UVcY}_v*K#B(O?(X*Jo@}-)zHoqL5jY zv0yTLgX3hchS?K0^lmO_oWjSf%4s&4eS+g=uL*lOCT=jC%sx}B*=Oc Promise} callback + * @returns Promise + * @template T + */ +async function withWebhook(channelID, callback) { + const webhook = await ensureWebhook(channelID, false) + return callback(webhook).catch(e => { + console.error(e) + // TODO: check if the error was webhook-related and if webhook.created === false, then: const webhook = ensureWebhook(channelID, true); return callback(webhook) + throw new Error(e) + }) +} + +module.exports.ensureWebhook = ensureWebhook +module.exports.withWebhook = withWebhook diff --git a/m2d/actions/send-message.js b/m2d/actions/send-message.js new file mode 100644 index 0000000..9c61107 --- /dev/null +++ b/m2d/actions/send-message.js @@ -0,0 +1,23 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") +const passthrough = require("../../passthrough") +const {sync, discord, db} = passthrough + +/** @type {import("./register-webhook")} */ +const registerWebhook = sync.require("./register-webhook") + +/** + * @param {string} channelID + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {name: string, file: Buffer}[]} data + */ +// param {DiscordTypes.RESTPostAPIWebhookWithTokenQuery & {wait: true, disableEveryone?: boolean}} options +async function sendMessage(channelID, data) { + const result = await registerWebhook.withWebhook(channelID, async webhook => { + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) + }) + return result +} + +module.exports.sendMessage = sendMessage diff --git a/test/test.js b/test/test.js index ae6aea6..5f06ae4 100644 --- a/test/test.js +++ b/test/test.js @@ -17,3 +17,4 @@ require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") +require("../d2m/actions/register-user.test") diff --git a/types.d.ts b/types.d.ts index bc24329..aaa8db1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -9,6 +9,11 @@ export type AppServiceRegistrationConfig = { rate_limited: boolean } +export type WebhookCreds = { + id: string + token: string +} + namespace Event { export type BaseStateEvent = { type: string From 5bd1bc9a5bea0db9a2dbe2579c2c6a0b1fe783d2 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 30 Jun 2023 15:15:34 +1200 Subject: [PATCH 041/200] add webhook creation utility --- m2d/actions/register-webhook.js | 50 +++++++++++++++++++++++++++++++++ m2d/actions/send-message.js | 23 +++++++++++++++ test/test.js | 1 + types.d.ts | 5 ++++ 4 files changed, 79 insertions(+) create mode 100644 m2d/actions/register-webhook.js create mode 100644 m2d/actions/send-message.js diff --git a/m2d/actions/register-webhook.js b/m2d/actions/register-webhook.js new file mode 100644 index 0000000..511029b --- /dev/null +++ b/m2d/actions/register-webhook.js @@ -0,0 +1,50 @@ +// @ts-check + +const assert = require("assert").strict +const passthrough = require("../../passthrough") +const {discord, db} = passthrough + +/** + * Look in the database to find webhook credentials for a channel. + * (Note that the credentials may be invalid and need to be re-created if the webhook was interfered with from outside.) + * @param {string} channelID + * @param {boolean} forceCreate create a new webhook no matter what the database says about the state + * @returns id and token for a webhook for that channel + */ +async function ensureWebhook(channelID, forceCreate = false) { + if (!forceCreate) { + /** @type {{id: string, token: string} | null} */ + const row = db.prepare("SELECT webhook_id as id, webhook_token as token FROM webhook WHERE channel_id = ?").get(channelID) + if (row) { + return {created: false, ...row} + } + } + + // If we got here, we need to create a new webhook. + const webhook = await discord.snow.webhook.createWebhook(channelID, {name: "Out Of Your Element: Matrix Bridge"}) + assert(webhook.token) + db.prepare("REPLACE INTO webhook (channel_id, webhook_id, webhook_token) VALUES (?, ?, ?)").run(channelID, webhook.id, webhook.token) + return { + id: webhook.id, + token: webhook.token, + created: true + } +} + +/** + * @param {string} channelID + * @param {(webhook: import("../../types").WebhookCreds) => Promise} callback + * @returns Promise + * @template T + */ +async function withWebhook(channelID, callback) { + const webhook = await ensureWebhook(channelID, false) + return callback(webhook).catch(e => { + console.error(e) + // TODO: check if the error was webhook-related and if webhook.created === false, then: const webhook = ensureWebhook(channelID, true); return callback(webhook) + throw new Error(e) + }) +} + +module.exports.ensureWebhook = ensureWebhook +module.exports.withWebhook = withWebhook diff --git a/m2d/actions/send-message.js b/m2d/actions/send-message.js new file mode 100644 index 0000000..9c61107 --- /dev/null +++ b/m2d/actions/send-message.js @@ -0,0 +1,23 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") +const passthrough = require("../../passthrough") +const {sync, discord, db} = passthrough + +/** @type {import("./register-webhook")} */ +const registerWebhook = sync.require("./register-webhook") + +/** + * @param {string} channelID + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {name: string, file: Buffer}[]} data + */ +// param {DiscordTypes.RESTPostAPIWebhookWithTokenQuery & {wait: true, disableEveryone?: boolean}} options +async function sendMessage(channelID, data) { + const result = await registerWebhook.withWebhook(channelID, async webhook => { + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) + }) + return result +} + +module.exports.sendMessage = sendMessage diff --git a/test/test.js b/test/test.js index ae6aea6..5f06ae4 100644 --- a/test/test.js +++ b/test/test.js @@ -17,3 +17,4 @@ require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") +require("../d2m/actions/register-user.test") diff --git a/types.d.ts b/types.d.ts index bc24329..aaa8db1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -9,6 +9,11 @@ export type AppServiceRegistrationConfig = { rate_limited: boolean } +export type WebhookCreds = { + id: string + token: string +} + namespace Event { export type BaseStateEvent = { type: string From e1072de53da9053b268f69409da89fc888012976 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 1 Jul 2023 17:02:03 +1200 Subject: [PATCH 042/200] updating discord libraries per recommendation --- package-lock.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 224e232..e4baa2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -679,11 +679,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.0.tgz", - "integrity": "sha512-k+1u1kTdtlz3L6lnflAKMhkkZPoBl/2Du2czNvad2pYNOMBs8e0XZpSuCazC50Q29tzi08latn4SxtLbkws50A==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.4.tgz", + "integrity": "sha512-EBeLY+VFP21lObPm3EHAhmtnch7irBD/AfRd6p+glXpYMU4vaCXzA+e7TI4UA2GCoN6LnX/skw4tk6GKyT0gIg==", "dependencies": { - "snowtransfer": "0.7.0" + "snowtransfer": "0.7.2" }, "engines": { "node": ">=12.0.0" @@ -917,9 +917,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.41", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.41.tgz", - "integrity": "sha512-FaPGBK9hx3zqSRX1x3KQWj+OElAJKmcyyfcdCy+U4AKv+gYuIkRySM7zd1So2sE4gc1DikkghkSBgBgKh6pe4Q==" + "version": "0.37.47", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.47.tgz", + "integrity": "sha512-rNif8IAv6duS2z47BMXq/V9kkrLfkAoiwpFY3sLxxbyKprk065zqf3HLTg4bEoxRSmi+Lhc7yqGDrG8C3j8GFA==" }, "node_modules/discord-markdown": { "version": "2.4.1", @@ -2458,12 +2458,12 @@ } }, "node_modules/snowtransfer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.0.tgz", - "integrity": "sha512-vc7B46tO4QeK99z/pN8ISd8QvO9QB3Oo4qP7nYYhriIMOtjYkHMi8t6kUBPIJLbeX+h0NpfwxaGJfXNLm1ZQ5A==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.2.tgz", + "integrity": "sha512-EYFanl0T4yNul2WdBY/69+eo36eaKGkY4wEtMED+7wOFI70gdEsy4VQlToWb0WVm5/gvSiCMK4aLgA3EQGObAQ==", "dependencies": { "centra": "^2.6.0", - "discord-api-types": "^0.37.31", + "discord-api-types": "^0.37.46", "form-data": "^4.0.0" }, "engines": { From 07b9bab5eca3c3f3b070ef3d5df5a09d861d94cb Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 1 Jul 2023 17:02:03 +1200 Subject: [PATCH 043/200] updating discord libraries per recommendation --- package-lock.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 224e232..e4baa2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -679,11 +679,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.0.tgz", - "integrity": "sha512-k+1u1kTdtlz3L6lnflAKMhkkZPoBl/2Du2czNvad2pYNOMBs8e0XZpSuCazC50Q29tzi08latn4SxtLbkws50A==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.4.tgz", + "integrity": "sha512-EBeLY+VFP21lObPm3EHAhmtnch7irBD/AfRd6p+glXpYMU4vaCXzA+e7TI4UA2GCoN6LnX/skw4tk6GKyT0gIg==", "dependencies": { - "snowtransfer": "0.7.0" + "snowtransfer": "0.7.2" }, "engines": { "node": ">=12.0.0" @@ -917,9 +917,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.41", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.41.tgz", - "integrity": "sha512-FaPGBK9hx3zqSRX1x3KQWj+OElAJKmcyyfcdCy+U4AKv+gYuIkRySM7zd1So2sE4gc1DikkghkSBgBgKh6pe4Q==" + "version": "0.37.47", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.47.tgz", + "integrity": "sha512-rNif8IAv6duS2z47BMXq/V9kkrLfkAoiwpFY3sLxxbyKprk065zqf3HLTg4bEoxRSmi+Lhc7yqGDrG8C3j8GFA==" }, "node_modules/discord-markdown": { "version": "2.4.1", @@ -2458,12 +2458,12 @@ } }, "node_modules/snowtransfer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.0.tgz", - "integrity": "sha512-vc7B46tO4QeK99z/pN8ISd8QvO9QB3Oo4qP7nYYhriIMOtjYkHMi8t6kUBPIJLbeX+h0NpfwxaGJfXNLm1ZQ5A==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.2.tgz", + "integrity": "sha512-EYFanl0T4yNul2WdBY/69+eo36eaKGkY4wEtMED+7wOFI70gdEsy4VQlToWb0WVm5/gvSiCMK4aLgA3EQGObAQ==", "dependencies": { "centra": "^2.6.0", - "discord-api-types": "^0.37.31", + "discord-api-types": "^0.37.46", "form-data": "^4.0.0" }, "engines": { From b727a55862e8ab6ad26c9589faa12afb1f83e4e5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sun, 2 Jul 2023 01:40:54 +1200 Subject: [PATCH 044/200] fix webhooks sending (upstream bug) --- d2m/actions/create-space.js | 2 +- d2m/event-dispatcher.js | 2 +- package-lock.json | 59 ++++++++++++++++++++++++++----------- package.json | 4 +-- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 4218c1f..e3b6da7 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -9,7 +9,7 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ async function createSpace(guild) { - const roomID = api.createRoom({ + const roomID = await api.createRoom({ name: guild.name, preset: "private_chat", visibility: "private", diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index fbcdca0..1a1e30a 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -17,7 +17,7 @@ module.exports = { /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ const channel = client.channels.get(message.channel_id) const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) sendMessage.sendMessage(message, guild) }, diff --git a/package-lock.json b/package-lock.json index e4baa2e..fecb682 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", - "cloudstorm": "^0.7.0", + "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", @@ -18,7 +18,7 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", + "snowtransfer": "^0.8.0", "try-to-catch": "^3.0.1" }, "devDependencies": { @@ -605,6 +605,17 @@ "ieee754": "^1.2.1" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -625,11 +636,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/centra": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/centra/-/centra-2.6.0.tgz", - "integrity": "sha512-dgh+YleemrT8u85QL11Z6tYhegAs3MMxsaWAq/oXeAmYJ7VxL3SI9TZtnfaEvNDMAPolj25FXIb3S+HCI4wQaQ==" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -679,11 +685,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.4.tgz", - "integrity": "sha512-EBeLY+VFP21lObPm3EHAhmtnch7irBD/AfRd6p+glXpYMU4vaCXzA+e7TI4UA2GCoN6LnX/skw4tk6GKyT0gIg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.0.tgz", + "integrity": "sha512-CT5/RKvSz1I0wmsf0SmZ2Jg9fPvqY67t9e2Y8n92vU0uEK5WmfPUyPOLZoYPMJwmktmsVCj4N6Pvka9gBIsY4g==", "dependencies": { - "snowtransfer": "0.7.2" + "snowtransfer": "0.8.0" }, "engines": { "node": ">=12.0.0" @@ -2458,13 +2464,13 @@ } }, "node_modules/snowtransfer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.2.tgz", - "integrity": "sha512-EYFanl0T4yNul2WdBY/69+eo36eaKGkY4wEtMED+7wOFI70gdEsy4VQlToWb0WVm5/gvSiCMK4aLgA3EQGObAQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.0.tgz", + "integrity": "sha512-ang6qQsET4VX4u9mdZq6ynJvcm8HQfV6iZOHBh8Y3T0QkJLr6GAjzcv1et7BOXl1HDR/6NhD+j+ZGr8+imTclg==", "dependencies": { - "centra": "^2.6.0", - "discord-api-types": "^0.37.46", - "form-data": "^4.0.0" + "discord-api-types": "^0.37.47", + "form-data": "^4.0.0", + "undici": "^5.22.1" }, "engines": { "node": ">=12.0.0" @@ -2534,6 +2540,14 @@ "node": ">= 0.4" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2791,6 +2805,17 @@ "node": ">= 0.6" } }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/unhomoglyph": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", diff --git a/package.json b/package.json index f483842..b3e19eb 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", - "cloudstorm": "^0.7.0", + "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", @@ -24,7 +24,7 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", + "snowtransfer": "^0.8.0", "try-to-catch": "^3.0.1" }, "devDependencies": { From 1591bfc578ac69a19a76d421387d22ea2a865218 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sun, 2 Jul 2023 01:40:54 +1200 Subject: [PATCH 045/200] fix webhooks sending (upstream bug) --- d2m/actions/create-space.js | 2 +- d2m/event-dispatcher.js | 2 +- package-lock.json | 59 ++++++++++++++++++++++++++----------- package.json | 4 +-- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 4218c1f..e3b6da7 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -9,7 +9,7 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ async function createSpace(guild) { - const roomID = api.createRoom({ + const roomID = await api.createRoom({ name: guild.name, preset: "private_chat", visibility: "private", diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index fbcdca0..1a1e30a 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -17,7 +17,7 @@ module.exports = { /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ const channel = client.channels.get(message.channel_id) const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) sendMessage.sendMessage(message, guild) }, diff --git a/package-lock.json b/package-lock.json index e4baa2e..fecb682 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", - "cloudstorm": "^0.7.0", + "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", @@ -18,7 +18,7 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", + "snowtransfer": "^0.8.0", "try-to-catch": "^3.0.1" }, "devDependencies": { @@ -605,6 +605,17 @@ "ieee754": "^1.2.1" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -625,11 +636,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/centra": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/centra/-/centra-2.6.0.tgz", - "integrity": "sha512-dgh+YleemrT8u85QL11Z6tYhegAs3MMxsaWAq/oXeAmYJ7VxL3SI9TZtnfaEvNDMAPolj25FXIb3S+HCI4wQaQ==" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -679,11 +685,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.7.4.tgz", - "integrity": "sha512-EBeLY+VFP21lObPm3EHAhmtnch7irBD/AfRd6p+glXpYMU4vaCXzA+e7TI4UA2GCoN6LnX/skw4tk6GKyT0gIg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.0.tgz", + "integrity": "sha512-CT5/RKvSz1I0wmsf0SmZ2Jg9fPvqY67t9e2Y8n92vU0uEK5WmfPUyPOLZoYPMJwmktmsVCj4N6Pvka9gBIsY4g==", "dependencies": { - "snowtransfer": "0.7.2" + "snowtransfer": "0.8.0" }, "engines": { "node": ">=12.0.0" @@ -2458,13 +2464,13 @@ } }, "node_modules/snowtransfer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.7.2.tgz", - "integrity": "sha512-EYFanl0T4yNul2WdBY/69+eo36eaKGkY4wEtMED+7wOFI70gdEsy4VQlToWb0WVm5/gvSiCMK4aLgA3EQGObAQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.0.tgz", + "integrity": "sha512-ang6qQsET4VX4u9mdZq6ynJvcm8HQfV6iZOHBh8Y3T0QkJLr6GAjzcv1et7BOXl1HDR/6NhD+j+ZGr8+imTclg==", "dependencies": { - "centra": "^2.6.0", - "discord-api-types": "^0.37.46", - "form-data": "^4.0.0" + "discord-api-types": "^0.37.47", + "form-data": "^4.0.0", + "undici": "^5.22.1" }, "engines": { "node": ">=12.0.0" @@ -2534,6 +2540,14 @@ "node": ">= 0.4" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2791,6 +2805,17 @@ "node": ">= 0.6" } }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/unhomoglyph": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", diff --git a/package.json b/package.json index f483842..b3e19eb 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", - "cloudstorm": "^0.7.0", + "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", @@ -24,7 +24,7 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", - "snowtransfer": "^0.7.0", + "snowtransfer": "^0.8.0", "try-to-catch": "^3.0.1" }, "devDependencies": { From 883a05303c6010c78a2700078a63726dbc52b223 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sun, 2 Jul 2023 01:41:31 +1200 Subject: [PATCH 046/200] don't send "" for attachments without content --- d2m/converters/message-to-event.js | 84 ++++++++++++------------ d2m/converters/message-to-event.test.js | 17 +++++ db/ooye.db | Bin 143360 -> 184320 bytes matrix/api.js | 5 ++ stdin.js | 1 + test/data.js | 39 ++++++++++- 6 files changed, 104 insertions(+), 42 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 549d104..382e970 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -16,48 +16,50 @@ async function messageToEvent(message, guild) { const events = [] // Text content appears first - const body = message.content - const html = markdown.toHTML(body, { - discordCallback: { - user: node => { - const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) - if (mxid) { - return "https://matrix.to/#/" + mxid - } else { - return "@" + node.id - } - }, - channel: node => { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) - if (roomID) { - return "https://matrix.to/#/" + roomID - } else { - return "#" + node.id - } - }, - role: node => - "@&" + node.id, - everyone: node => - "@room", - here: node => - "@here" + if (message.content) { + const body = message.content + const html = markdown.toHTML(body, { + discordCallback: { + user: node => { + const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) + if (mxid) { + return "https://matrix.to/#/" + mxid + } else { + return "@" + node.id + } + }, + channel: node => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) + if (roomID) { + return "https://matrix.to/#/" + roomID + } else { + return "#" + node.id + } + }, + role: node => + "@&" + node.id, + everyone: node => + "@room", + here: node => + "@here" + } + }, null, null) + const isPlaintext = body === html + if (isPlaintext) { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body + }) + } else { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body, + format: "org.matrix.custom.html", + formatted_body: html + }) } - }, null, null) - const isPlaintext = body === html - if (isPlaintext) { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body - }) - } else { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body, - format: "org.matrix.custom.html", - formatted_body: html - }) } // Then attachments diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index c92cd85..c318389 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -3,6 +3,23 @@ const assert = require("assert") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") +test("message2event: attachment with no content", async t => { + const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", + info: { + mimetype: "image/png", + w: 466, + h: 85, + size: 12919, + }, + }]) +}) + test("message2event: stickers", async t => { const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ diff --git a/db/ooye.db b/db/ooye.db index be5650515d1dd449ecdae32f120a2e170559af4d..c91e41f261aca43181eb223f01ae6028a4dcefcd 100644 GIT binary patch delta 36750 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 NkMGn^m&eaS{{WZ09TWfn delta 2186 zcmaJ?dsr3M5#KrcenS!w@BxSc%H50X)$Wa;KpL&p^fSiTrfNuxpn_t6G^P>1h*)cD z6NBUwj!1ZEQx#GR-s;*K3#3+4OuPy)DZb*f8mx-;D@nvh&qe*C|MZWYZ)VQSo|)O- zZ)a75y6TLyAzFZThGF6>VlzTo%EI2(+*-2~^)pkMBa9g|>)^Jz)SO_d#&Ki2@rE(Q z7@}X`C+Y!xwf-zD)MK^l+F!KwTsYsT&DFkxQn;;Xp8Ab?kXO|zb%8ogWtA?aQ7KX~ z`EI_DTc+spN$8ZD1s*oaB`{8&Do0BHly*sL(0=JTX_RJgiS>K6^+ScLofKr;jh4l-)-{Z!bm^WkPcyVx>k7F1ZM&3Szym-wihb%vgEb_%+G@k^6D1i$lk(UR+ z5}3V%`cVA@5##Wbp%|xh zJ6MUb?pM&Z$AV$_P?U}5Mf>sXCCP{>!*{wI{P^$(5m_Hvc<2ZpKHKBqDI+bs?5KlB zC3^6CF}|I#T$li6Cu4R7!n4{<+?ko?a$%^~#MhpQpeLso^OPGNhPutbsh8qo4Ta(= zfw{T4eaoUq|aYbVVsh@`43KW%%GDylaB$Jv==8rHClrCn*19xLajE|$$wRL$fEWm zv&*=oer#?Dgr^21ysRpMym%ZU1K+LwL@1SJGf#n^hkW={MQl0yB5R=mbs;*)#dDXq z-P|wu5}{DY5Oc+`B2ov`PIVkV)eH){StlO2PXo5&ArX`#Mg$+JUlhL<8k9OEO*qOo z>wknAbtB4=lK7KirT%kdD+O>*pQ*=zU+YWI+O&62C3};brDbatn=Ox!{zWgpB<&S4 zq+dfmUn0#G3M(4DByBnJk(EoSIJOb_@Szhn|K1>!Pv=sI)sX)4U_&*rH=!qR#zOib ztH@h5$WMw(X^MUwEOx!klHhf)N%c93(;FvkGVeV2$kj?@6StZT>%Hb5bxph2JzbvZ^0r`@G*J_&s`QIh4}Ko{&utl^GQ@K zvj2xt4!)7-f#=C`@-VP1$%0vAP7Spw3(u0KJmjSsyBVMMTDZMiApBxV$Mz*?KCbC< z$gZWd#?~V?nOKNslWDcI&Ad&t%|I;`o9k)vAV*gWvhM>@-bvlH*J-c&YN*!HN&5-E zLzz>d0db$*4xzHsZ1yv>6pGBV9HVfclNqM9d2Lv%p9F$?odu zu$ubyVj|86`DcJg-!sWA&IXLwaqMU!&V$SdZL%h+`_(cvgWE2=z+DzLDxC5IH+LR< z2(2v|@M+xmr-8JNfCx7;2mYq9(|MVQMG!-hiXg+yD}vcvMavIec^P~Y%_*NViG<$( z&$c#x%e&81!7n+aP9m%7D1?m~a5#N4lS0>l`)UoOB4mUO9^sGr9XG5FZa`>m350Ke zM5N3nDGe}G4|3`3UN#;59X$`{+?fp!27%f$`y>+61Zg<<)g-sJ2^R63AsuE&WD5+Z zKa?iPJ-i2;q0y?`!I*CteMX*sMt@qX(hPO2a#wj>?x0ayWVfRSvV@oErCxE?;1;Dn zk91QpdW>zx&y4Sjd*~KDukX~$^ci}Lc8zY(x3#&_bZNMjOn2u2wNlMdeJUtHu~(^8 z3Y2t3mj5BHmAA^T%ai3t#aYrhsa0AQ(hw@2QJ_XcP@mEj+&=4L!*JI<4#(2p1 zc{x7d*!A<}G diff --git a/matrix/api.js b/matrix/api.js index 3ec014d..ec85795 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -60,6 +60,10 @@ async function inviteToRoom(roomID, mxidToInvite, mxid) { }) } +async function leaveRoom(roomID, mxid) { + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) +} + /** * @param {string} roomID * @returns {Promise} @@ -108,6 +112,7 @@ module.exports.register = register module.exports.createRoom = createRoom module.exports.joinRoom = joinRoom module.exports.inviteToRoom = inviteToRoom +module.exports.leaveRoom = leaveRoom module.exports.getAllState = getAllState module.exports.sendState = sendState module.exports.sendEvent = sendEvent diff --git a/stdin.js b/stdin.js index 99345ab..1a5b8d1 100644 --- a/stdin.js +++ b/stdin.js @@ -11,6 +11,7 @@ const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") +const sendMessage = sync.require("./m2d/actions/send-message") const guildID = "112760669178241024" const extraContext = {} diff --git a/test/data.js b/test/data.js index 85b3cd4..5efea36 100644 --- a/test/data.js +++ b/test/data.js @@ -140,6 +140,43 @@ module.exports = { }, message: { // Display order is text content, attachments, then stickers + attachment_no_content: { + id: "1124628646670389348", + type: 0, + content: "", + channel_id: "497161332244742154", + author: { + id: "320067006521147393", + username: "papiophidian", + global_name: "PapiOphidian", + avatar: "fb2b4535f7a108619e3edae12fcb16c5", + discriminator: "0", + public_flags: 4194880, + avatar_decoration: null + }, + attachments: [ + { + id: "1124628646431297546", + filename: "image.png", + size: 12919, + url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", + proxy_url: "https://media.discordapp.net/attachments/497161332244742154/1124628646431297546/image.png", + width: 466, + height: 85, + content_type: "image/png" + } + ], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-01T09:12:43.956000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, sticker: { id: "1106366167788044450", type: 0, @@ -180,6 +217,6 @@ module.exports = { format_type: 1, name: "pomu puff" }] - } + } } } From d592a3c82eb8d771fe399dc0260784b1b198e293 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sun, 2 Jul 2023 01:41:31 +1200 Subject: [PATCH 047/200] don't send "" for attachments without content --- d2m/converters/message-to-event.js | 84 +++++++++++++------------ d2m/converters/message-to-event.test.js | 17 +++++ matrix/api.js | 5 ++ stdin.js | 1 + test/data.js | 39 +++++++++++- 5 files changed, 104 insertions(+), 42 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 549d104..382e970 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -16,48 +16,50 @@ async function messageToEvent(message, guild) { const events = [] // Text content appears first - const body = message.content - const html = markdown.toHTML(body, { - discordCallback: { - user: node => { - const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) - if (mxid) { - return "https://matrix.to/#/" + mxid - } else { - return "@" + node.id - } - }, - channel: node => { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) - if (roomID) { - return "https://matrix.to/#/" + roomID - } else { - return "#" + node.id - } - }, - role: node => - "@&" + node.id, - everyone: node => - "@room", - here: node => - "@here" + if (message.content) { + const body = message.content + const html = markdown.toHTML(body, { + discordCallback: { + user: node => { + const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) + if (mxid) { + return "https://matrix.to/#/" + mxid + } else { + return "@" + node.id + } + }, + channel: node => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) + if (roomID) { + return "https://matrix.to/#/" + roomID + } else { + return "#" + node.id + } + }, + role: node => + "@&" + node.id, + everyone: node => + "@room", + here: node => + "@here" + } + }, null, null) + const isPlaintext = body === html + if (isPlaintext) { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body + }) + } else { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body, + format: "org.matrix.custom.html", + formatted_body: html + }) } - }, null, null) - const isPlaintext = body === html - if (isPlaintext) { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body - }) - } else { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body, - format: "org.matrix.custom.html", - formatted_body: html - }) } // Then attachments diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index c92cd85..c318389 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -3,6 +3,23 @@ const assert = require("assert") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") +test("message2event: attachment with no content", async t => { + const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", + info: { + mimetype: "image/png", + w: 466, + h: 85, + size: 12919, + }, + }]) +}) + test("message2event: stickers", async t => { const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ diff --git a/matrix/api.js b/matrix/api.js index 3ec014d..ec85795 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -60,6 +60,10 @@ async function inviteToRoom(roomID, mxidToInvite, mxid) { }) } +async function leaveRoom(roomID, mxid) { + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) +} + /** * @param {string} roomID * @returns {Promise} @@ -108,6 +112,7 @@ module.exports.register = register module.exports.createRoom = createRoom module.exports.joinRoom = joinRoom module.exports.inviteToRoom = inviteToRoom +module.exports.leaveRoom = leaveRoom module.exports.getAllState = getAllState module.exports.sendState = sendState module.exports.sendEvent = sendEvent diff --git a/stdin.js b/stdin.js index 99345ab..1a5b8d1 100644 --- a/stdin.js +++ b/stdin.js @@ -11,6 +11,7 @@ const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") +const sendMessage = sync.require("./m2d/actions/send-message") const guildID = "112760669178241024" const extraContext = {} diff --git a/test/data.js b/test/data.js index 85b3cd4..5efea36 100644 --- a/test/data.js +++ b/test/data.js @@ -140,6 +140,43 @@ module.exports = { }, message: { // Display order is text content, attachments, then stickers + attachment_no_content: { + id: "1124628646670389348", + type: 0, + content: "", + channel_id: "497161332244742154", + author: { + id: "320067006521147393", + username: "papiophidian", + global_name: "PapiOphidian", + avatar: "fb2b4535f7a108619e3edae12fcb16c5", + discriminator: "0", + public_flags: 4194880, + avatar_decoration: null + }, + attachments: [ + { + id: "1124628646431297546", + filename: "image.png", + size: 12919, + url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", + proxy_url: "https://media.discordapp.net/attachments/497161332244742154/1124628646431297546/image.png", + width: 466, + height: 85, + content_type: "image/png" + } + ], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-01T09:12:43.956000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, sticker: { id: "1106366167788044450", type: 0, @@ -180,6 +217,6 @@ module.exports = { format_type: 1, name: "pomu puff" }] - } + } } } From 78144279c579f722d940c2fd2478bccb4ff35f89 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 3 Jul 2023 01:06:05 +1200 Subject: [PATCH 048/200] preparing for m2d --- db/ooye.db | Bin 184320 -> 184320 bytes m2d/actions/send-message.js | 2 +- m2d/converters/event-to-message.js | 30 ++++++++++++++++++++++ m2d/converters/event-to-message.test.js | 32 ++++++++++++++++++++++++ m2d/event-dispatcher.js | 4 +++ test/test.js | 1 + types.d.ts | 12 +++++++++ 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 m2d/converters/event-to-message.js create mode 100644 m2d/converters/event-to-message.test.js diff --git a/db/ooye.db b/db/ooye.db index c91e41f261aca43181eb223f01ae6028a4dcefcd..e0b3a33f48a915e3e722489bdb7f33be174e7023 100644 GIT binary patch delta 1520 zcmb8vUuauZ90%~8n?E-ilUr92M(Y@^+pM;7?>+bA-V-RSy=l@kNt-`SNHQ@^(%Yo@ zpCwJwh-Nb&_#jpSy@GD6s9l{3t9`gS1@%Stq##UTOq|eB#=t)>K8O#dw;jGzv`^>d z{63uDkMH@Nzid2z*|^dVZoV-ff}5Lj?^2IAdT#en-%uB+oQCO`dtcdF?G*tyyLIc) z=KwQ;swq#}7YT+%M0+PCpOki1%cx4nlMF7V(}{+!kl>3JnNlOkM*>OPmI)n`mrO`R8aM1VVA?VM5-`OhsdqkR4` zm>dD4vhEI8GLTi{b>pkxJLTm&U_%2ua_I&kDJ$FH9>L7Ai*+FrspsR#V7eIzglb|g zmz&1o%L$s5E#Rs*8*p zWh!A84oJyF3`e6d%N3ILY9PJfZG^*_EMIX1!Bw@QnJ7cn9+7;xsKk1k;$$dQ%uhK3 zdB)@R=UKSioSzO?&Ov_zkAW*{NjdD|ePKzAPfm~7^7(+1_5~{oB1p9N>;q?f@g7x zkEO1`h!-42x(^zzFde3Q%AWBesWCvdj_A@GTh+OPHMqv5ws~DbxkQLictIGe$N^3tX`wZlp>BDLLbTc*$OIAWNcJsAFGg5sJ~eHyiWqDHfjT5;W#82YIpml&bto23iM& zV4tzj3_=XXw1AMfHS%_(5;aE;=pHh{77F_E;H{1gTK@M_Vie|9b zKB+Q?_$Ln(hJiE>S&S?eu|24jlzX2L{iJ!wVA%aV7pW|T4F;w6OX3f5FW`rmK6qcT J-XOjv{|5P{)^-2@ delta 353 zcmV-n0iOPVpbLPY3y>QD$dMdF0m!jnqz?*40Nel%&JUdrY?BZWh5`dIvz8D;0RfG( z+7VmhWC9Zl4m1Vn01wX(o)2sfIu8lA6mSBg z1(C2c1Th0;0+(UC0xY)~b^_}e5I_T%00*iEc?0|hM+X)L<+m1)0#E~yur#-rlLBlU z0~!eAmk00xHM0>gs0g>@xB?&t3@8XX01wX(oeygdIkytP0`>=yur#+i$O6U(%mj8* diff --git a/m2d/actions/send-message.js b/m2d/actions/send-message.js index 9c61107..3505b60 100644 --- a/m2d/actions/send-message.js +++ b/m2d/actions/send-message.js @@ -10,7 +10,7 @@ const registerWebhook = sync.require("./register-webhook") /** * @param {string} channelID - * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {name: string, file: Buffer}[]} data + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}[]} data */ // param {DiscordTypes.RESTPostAPIWebhookWithTokenQuery & {wait: true, disableEveryone?: boolean}} options async function sendMessage(channelID, data) { diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js new file mode 100644 index 0000000..f48b5da --- /dev/null +++ b/m2d/converters/event-to-message.js @@ -0,0 +1,30 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") +const markdown = require("discord-markdown") + +const passthrough = require("../../passthrough") +const { sync, db, discord } = passthrough +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +/** + * @param {import("../../types").Event.Outer} event + */ +function eventToMessage(event) { + /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ + const messages = [] + + if (event.content.msgtype === "m.text") { + messages.push({ + content: event.content.body, + username: event.sender.replace(/^@/, ""), + avatar_url: undefined, // TODO: provide the URL to the avatar from the homeserver's content repo + }) + } + + return messages +} + +module.exports.eventToMessage = eventToMessage diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js new file mode 100644 index 0000000..a41beef --- /dev/null +++ b/m2d/converters/event-to-message.test.js @@ -0,0 +1,32 @@ +// @ts-check + +const {test} = require("supertape") +const assert = require("assert") +const {eventToMessage} = require("./event-to-message") +const data = require("../../test/data") + +test("event2message: janky test", t => { + t.deepEqual( + eventToMessage({ + age: 405299, + content: { + body: "test", + msgtype: "m.text" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + }, + user_id: "@cadence:cadence.moe" + }), + [{ + username: "cadence:cadence.moe", + content: "test", + avatar_url: undefined + }] + ) +}) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 136aae0..789d9ed 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -1,3 +1,5 @@ +// @ts-check + const assert = require("assert").strict const {sync, as} = require("../passthrough") @@ -5,4 +7,6 @@ const {sync, as} = require("../passthrough") sync.addTemporaryListener(as, "type:m.room.message", event => { console.log(event) + // TODO: filter out events that were bridged discord messages (i.e. sent by OOYE) + // TODO: call sendMessage }) diff --git a/test/test.js b/test/test.js index 5f06ae4..f2f0912 100644 --- a/test/test.js +++ b/test/test.js @@ -18,3 +18,4 @@ require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") +require("../m2d/converters/event-to-message.test") diff --git a/types.d.ts b/types.d.ts index aaa8db1..2181793 100644 --- a/types.d.ts +++ b/types.d.ts @@ -15,6 +15,18 @@ export type WebhookCreds = { } namespace Event { + export type Outer = { + type: string + room_id: string + sender: string + content: T + origin_server_ts: number + unsigned: any + event_id: string + user_id: string + age: number + } + export type BaseStateEvent = { type: string room_id: string From 6e55e6d1b358707821f08b251940c2385757b204 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 3 Jul 2023 01:06:05 +1200 Subject: [PATCH 049/200] preparing for m2d --- m2d/actions/send-message.js | 2 +- m2d/converters/event-to-message.js | 30 +++++++++++++++++++++++ m2d/converters/event-to-message.test.js | 32 +++++++++++++++++++++++++ m2d/event-dispatcher.js | 4 ++++ test/test.js | 1 + types.d.ts | 12 ++++++++++ 6 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 m2d/converters/event-to-message.js create mode 100644 m2d/converters/event-to-message.test.js diff --git a/m2d/actions/send-message.js b/m2d/actions/send-message.js index 9c61107..3505b60 100644 --- a/m2d/actions/send-message.js +++ b/m2d/actions/send-message.js @@ -10,7 +10,7 @@ const registerWebhook = sync.require("./register-webhook") /** * @param {string} channelID - * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {name: string, file: Buffer}[]} data + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}[]} data */ // param {DiscordTypes.RESTPostAPIWebhookWithTokenQuery & {wait: true, disableEveryone?: boolean}} options async function sendMessage(channelID, data) { diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js new file mode 100644 index 0000000..f48b5da --- /dev/null +++ b/m2d/converters/event-to-message.js @@ -0,0 +1,30 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") +const markdown = require("discord-markdown") + +const passthrough = require("../../passthrough") +const { sync, db, discord } = passthrough +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +/** + * @param {import("../../types").Event.Outer} event + */ +function eventToMessage(event) { + /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ + const messages = [] + + if (event.content.msgtype === "m.text") { + messages.push({ + content: event.content.body, + username: event.sender.replace(/^@/, ""), + avatar_url: undefined, // TODO: provide the URL to the avatar from the homeserver's content repo + }) + } + + return messages +} + +module.exports.eventToMessage = eventToMessage diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js new file mode 100644 index 0000000..a41beef --- /dev/null +++ b/m2d/converters/event-to-message.test.js @@ -0,0 +1,32 @@ +// @ts-check + +const {test} = require("supertape") +const assert = require("assert") +const {eventToMessage} = require("./event-to-message") +const data = require("../../test/data") + +test("event2message: janky test", t => { + t.deepEqual( + eventToMessage({ + age: 405299, + content: { + body: "test", + msgtype: "m.text" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + }, + user_id: "@cadence:cadence.moe" + }), + [{ + username: "cadence:cadence.moe", + content: "test", + avatar_url: undefined + }] + ) +}) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 136aae0..789d9ed 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -1,3 +1,5 @@ +// @ts-check + const assert = require("assert").strict const {sync, as} = require("../passthrough") @@ -5,4 +7,6 @@ const {sync, as} = require("../passthrough") sync.addTemporaryListener(as, "type:m.room.message", event => { console.log(event) + // TODO: filter out events that were bridged discord messages (i.e. sent by OOYE) + // TODO: call sendMessage }) diff --git a/test/test.js b/test/test.js index 5f06ae4..f2f0912 100644 --- a/test/test.js +++ b/test/test.js @@ -18,3 +18,4 @@ require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") +require("../m2d/converters/event-to-message.test") diff --git a/types.d.ts b/types.d.ts index aaa8db1..2181793 100644 --- a/types.d.ts +++ b/types.d.ts @@ -15,6 +15,18 @@ export type WebhookCreds = { } namespace Event { + export type Outer = { + type: string + room_id: string + sender: string + content: T + origin_server_ts: number + unsigned: any + event_id: string + user_id: string + age: number + } + export type BaseStateEvent = { type: string room_id: string From 4ec2e6bc63f580bb3aca3c111128d7a6cd206bf5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 3 Jul 2023 17:20:24 +1200 Subject: [PATCH 050/200] glue --- ...register-webhook.js => channel-webhook.js} | 13 ++++++ m2d/actions/send-event.js | 43 +++++++++++++++++++ m2d/actions/send-message.js | 23 ---------- m2d/event-dispatcher.js | 31 +++++++++++-- types.d.ts | 12 +++++- 5 files changed, 94 insertions(+), 28 deletions(-) rename m2d/actions/{register-webhook.js => channel-webhook.js} (77%) create mode 100644 m2d/actions/send-event.js delete mode 100644 m2d/actions/send-message.js diff --git a/m2d/actions/register-webhook.js b/m2d/actions/channel-webhook.js similarity index 77% rename from m2d/actions/register-webhook.js rename to m2d/actions/channel-webhook.js index 511029b..5e56859 100644 --- a/m2d/actions/register-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -1,6 +1,7 @@ // @ts-check const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const {discord, db} = passthrough @@ -46,5 +47,17 @@ async function withWebhook(channelID, callback) { }) } +/** + * @param {string} channelID + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}[]} data + */ +async function sendMessageWithWebhook(channelID, data) { + const result = await withWebhook(channelID, async webhook => { + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) + }) + return result +} + module.exports.ensureWebhook = ensureWebhook module.exports.withWebhook = withWebhook +module.exports.sendMessageWithWebhook = sendMessageWithWebhook diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js new file mode 100644 index 0000000..5eb8c04 --- /dev/null +++ b/m2d/actions/send-event.js @@ -0,0 +1,43 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") +const passthrough = require("../../passthrough") +const {sync, discord, db} = passthrough + +/** @type {import("./channel-webhook")} */ +const channelWebhook = sync.require("./channel-webhook") +/** @type {import("../converters/event-to-message")} */ +const eventToMessage = sync.require("../converters/event-to-message") + +/** @param {import("../../types").Event.Outer} event */ +async function sendEvent(event) { + // TODO: matrix equivalents... + const roomID = await createRoom.ensureRoom(message.channel_id) + // TODO: no need to sync the member to the other side... right? + let senderMxid = null + if (!message.webhook_id) { + assert(message.member) + senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + } + + const messages = eventToMessage.eventToMessage(event) + assert(Array.isArray(messages)) + + /** @type {DiscordTypes.APIMessage[]} */ + const messageResponses = [] + let eventPart = 0 // 0 is primary, 1 is supporting + for (const message of messages) { + const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) + // TODO: are you sure about that? many to many? and we don't need to store which side it originated from? + db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(event.event_id, messageResponse.id, eventPart) + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + messageResponses.push(messageResponse) + } + + return messageResponses +} + +module.exports.sendEvent = sendEvent diff --git a/m2d/actions/send-message.js b/m2d/actions/send-message.js deleted file mode 100644 index 3505b60..0000000 --- a/m2d/actions/send-message.js +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check - -const assert = require("assert").strict -const DiscordTypes = require("discord-api-types/v10") -const passthrough = require("../../passthrough") -const {sync, discord, db} = passthrough - -/** @type {import("./register-webhook")} */ -const registerWebhook = sync.require("./register-webhook") - -/** - * @param {string} channelID - * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}[]} data - */ -// param {DiscordTypes.RESTPostAPIWebhookWithTokenQuery & {wait: true, disableEveryone?: boolean}} options -async function sendMessage(channelID, data) { - const result = await registerWebhook.withWebhook(channelID, async webhook => { - return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) - }) - return result -} - -module.exports.sendMessage = sendMessage diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 789d9ed..b8bfacf 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -1,12 +1,37 @@ // @ts-check +/** + * Grab Matrix events we care about, check them, and bridge them. + */ + const assert = require("assert").strict const {sync, as} = require("../passthrough") +const reg = require("../matrix/read-registration") +/** @type {import("./actions/send-event")} */ +const sendEvent = sync.require("./actions/send-event") -// Grab Matrix events we care about for the bridge, check them, and pass them on +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) +/** + * Determine whether an event is the bridged representation of a discord message. + * Such messages shouldn't be bridged again. + * @param {import("../types").Event.Outer} event + */ +function eventOriginatedFromDiscord(event) { + if ( + // If it's from a user in the bridge's namespace... + userRegex.some(x => event.sender.match(x)) + // ...not counting the appservice's own user... + && !event.sender.startsWith(`@${reg.sender_localpart}:`) + ) { + // ...then it originated from discord + return true + } + + return false +} sync.addTemporaryListener(as, "type:m.room.message", event => { console.log(event) - // TODO: filter out events that were bridged discord messages (i.e. sent by OOYE) - // TODO: call sendMessage + if (eventOriginatedFromDiscord(event)) return + const messageResponses = sendEvent.sendEvent(event) }) diff --git a/types.d.ts b/types.d.ts index 2181793..19ef1f2 100644 --- a/types.d.ts +++ b/types.d.ts @@ -5,6 +5,16 @@ export type AppServiceRegistrationConfig = { url: string sender_localpart: string namespace_prefix: string + namespaces: { + users: { + exclusive: boolean + regex: string + }[] + aliases: { + exclusive: boolean + regex: string + }[] + } protocols: [string] rate_limited: boolean } @@ -23,8 +33,6 @@ namespace Event { origin_server_ts: number unsigned: any event_id: string - user_id: string - age: number } export type BaseStateEvent = { From 3578ca28b5e43fd8aeddf1086cd4f49bc8cecb7e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 3 Jul 2023 17:20:24 +1200 Subject: [PATCH 051/200] glue --- ...register-webhook.js => channel-webhook.js} | 13 ++++++ m2d/actions/send-event.js | 43 +++++++++++++++++++ m2d/actions/send-message.js | 23 ---------- m2d/event-dispatcher.js | 31 +++++++++++-- types.d.ts | 12 +++++- 5 files changed, 94 insertions(+), 28 deletions(-) rename m2d/actions/{register-webhook.js => channel-webhook.js} (77%) create mode 100644 m2d/actions/send-event.js delete mode 100644 m2d/actions/send-message.js diff --git a/m2d/actions/register-webhook.js b/m2d/actions/channel-webhook.js similarity index 77% rename from m2d/actions/register-webhook.js rename to m2d/actions/channel-webhook.js index 511029b..5e56859 100644 --- a/m2d/actions/register-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -1,6 +1,7 @@ // @ts-check const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const {discord, db} = passthrough @@ -46,5 +47,17 @@ async function withWebhook(channelID, callback) { }) } +/** + * @param {string} channelID + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}[]} data + */ +async function sendMessageWithWebhook(channelID, data) { + const result = await withWebhook(channelID, async webhook => { + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) + }) + return result +} + module.exports.ensureWebhook = ensureWebhook module.exports.withWebhook = withWebhook +module.exports.sendMessageWithWebhook = sendMessageWithWebhook diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js new file mode 100644 index 0000000..5eb8c04 --- /dev/null +++ b/m2d/actions/send-event.js @@ -0,0 +1,43 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") +const passthrough = require("../../passthrough") +const {sync, discord, db} = passthrough + +/** @type {import("./channel-webhook")} */ +const channelWebhook = sync.require("./channel-webhook") +/** @type {import("../converters/event-to-message")} */ +const eventToMessage = sync.require("../converters/event-to-message") + +/** @param {import("../../types").Event.Outer} event */ +async function sendEvent(event) { + // TODO: matrix equivalents... + const roomID = await createRoom.ensureRoom(message.channel_id) + // TODO: no need to sync the member to the other side... right? + let senderMxid = null + if (!message.webhook_id) { + assert(message.member) + senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + } + + const messages = eventToMessage.eventToMessage(event) + assert(Array.isArray(messages)) + + /** @type {DiscordTypes.APIMessage[]} */ + const messageResponses = [] + let eventPart = 0 // 0 is primary, 1 is supporting + for (const message of messages) { + const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) + // TODO: are you sure about that? many to many? and we don't need to store which side it originated from? + db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(event.event_id, messageResponse.id, eventPart) + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + messageResponses.push(messageResponse) + } + + return messageResponses +} + +module.exports.sendEvent = sendEvent diff --git a/m2d/actions/send-message.js b/m2d/actions/send-message.js deleted file mode 100644 index 3505b60..0000000 --- a/m2d/actions/send-message.js +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check - -const assert = require("assert").strict -const DiscordTypes = require("discord-api-types/v10") -const passthrough = require("../../passthrough") -const {sync, discord, db} = passthrough - -/** @type {import("./register-webhook")} */ -const registerWebhook = sync.require("./register-webhook") - -/** - * @param {string} channelID - * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}[]} data - */ -// param {DiscordTypes.RESTPostAPIWebhookWithTokenQuery & {wait: true, disableEveryone?: boolean}} options -async function sendMessage(channelID, data) { - const result = await registerWebhook.withWebhook(channelID, async webhook => { - return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) - }) - return result -} - -module.exports.sendMessage = sendMessage diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 789d9ed..b8bfacf 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -1,12 +1,37 @@ // @ts-check +/** + * Grab Matrix events we care about, check them, and bridge them. + */ + const assert = require("assert").strict const {sync, as} = require("../passthrough") +const reg = require("../matrix/read-registration") +/** @type {import("./actions/send-event")} */ +const sendEvent = sync.require("./actions/send-event") -// Grab Matrix events we care about for the bridge, check them, and pass them on +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) +/** + * Determine whether an event is the bridged representation of a discord message. + * Such messages shouldn't be bridged again. + * @param {import("../types").Event.Outer} event + */ +function eventOriginatedFromDiscord(event) { + if ( + // If it's from a user in the bridge's namespace... + userRegex.some(x => event.sender.match(x)) + // ...not counting the appservice's own user... + && !event.sender.startsWith(`@${reg.sender_localpart}:`) + ) { + // ...then it originated from discord + return true + } + + return false +} sync.addTemporaryListener(as, "type:m.room.message", event => { console.log(event) - // TODO: filter out events that were bridged discord messages (i.e. sent by OOYE) - // TODO: call sendMessage + if (eventOriginatedFromDiscord(event)) return + const messageResponses = sendEvent.sendEvent(event) }) diff --git a/types.d.ts b/types.d.ts index 2181793..19ef1f2 100644 --- a/types.d.ts +++ b/types.d.ts @@ -5,6 +5,16 @@ export type AppServiceRegistrationConfig = { url: string sender_localpart: string namespace_prefix: string + namespaces: { + users: { + exclusive: boolean + regex: string + }[] + aliases: { + exclusive: boolean + regex: string + }[] + } protocols: [string] rate_limited: boolean } @@ -23,8 +33,6 @@ namespace Event { origin_server_ts: number unsigned: any event_id: string - user_id: string - age: number } export type BaseStateEvent = { From 8144e6abb80cf73841f8679a3e1163c0977be70e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 4 Jul 2023 00:39:42 +1200 Subject: [PATCH 052/200] bridge both ways and prevent reflections --- d2m/actions/add-reaction.js | 1 - d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.test.js | 1 - d2m/event-dispatcher.js | 9 ++++++- db/ooye.db | Bin 184320 -> 258048 bytes m2d/actions/channel-webhook.js | 2 +- m2d/actions/send-event.js | 20 +++++--------- m2d/converters/event-to-message.js | 1 - m2d/converters/event-to-message.test.js | 5 +--- m2d/converters/utils.js | 21 +++++++++++++++ m2d/converters/utils.test.js | 16 ++++++++++++ m2d/event-dispatcher.js | 33 +++++++----------------- matrix/api.test.js | 1 - matrix/read-registration.test.js | 3 +-- stdin.js | 2 +- test/test.js | 1 + 16 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 m2d/converters/utils.js create mode 100644 m2d/converters/utils.test.js diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index 82449cd..cd3d296 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -17,7 +17,6 @@ const createRoom = sync.require("../actions/create-room") 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") diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 24a825a..4f111b0 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -37,7 +37,7 @@ async function sendMessage(message, guild) { delete eventWithoutType.$type const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, eventPart) + db.prepare("INSERT INTO event_message (event_id, message_id, part, source) VALUES (?, ?, ?, 1)").run(eventID, message.id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting eventIDs.push(eventID) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index c318389..26cf1f1 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -1,5 +1,4 @@ const {test} = require("supertape") -const assert = require("assert") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 1a1e30a..99c7792 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,5 +1,5 @@ const assert = require("assert").strict -const {sync} = require("../passthrough") +const {sync, db} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") @@ -18,6 +18,13 @@ module.exports = { const channel = client.channels.get(message.channel_id) const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (message.webhook_id) { + const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + if (row) { + // The message was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. + return + } + } sendMessage.sendMessage(message, guild) }, diff --git a/db/ooye.db b/db/ooye.db index e0b3a33f48a915e3e722489bdb7f33be174e7023..df5e6d199d8c0d1918dcbb6d2f18f403e9c8b19d 100644 GIT binary patch delta 28964 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
    In reply to ${repliedToUserHtml}` + + `
    ${repliedToHtml}
    ` + + html + body = (`${repliedToDisplayName}: ` // scenario 1 part B for mentions + + repliedToBody).split("\n").map(line => "> " + line).join("\n") + + "\n\n" + body + } + + const newTextMessageEvent = { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.text", + body: body + } + const isPlaintext = body === html - if (isPlaintext) { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body - }) - } else { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body, + if (!isPlaintext) { + Object.assign(newTextMessageEvent, { format: "org.matrix.custom.html", formatted_body: html }) } + + events.push(newTextMessageEvent) } // Then attachments @@ -90,6 +167,7 @@ async function messageToEvent(message, guild) { if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.image", url: await file.uploadDiscordFileToMxc(attachment.url), external_url: attachment.url, @@ -105,6 +183,7 @@ async function messageToEvent(message, guild) { } else { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.text", body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) } @@ -122,6 +201,7 @@ async function messageToEvent(message, guild) { if (sticker && sticker.description) body += ` - ${sticker.description}` return { $type: "m.sticker", + "m.mentions": mentions, body, info: { mimetype: format.mime @@ -131,6 +211,7 @@ async function messageToEvent(message, guild) { } else { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.text", body: "Unsupported sticker format. Name: " + stickerItem.name } @@ -139,6 +220,17 @@ async function messageToEvent(message, guild) { events.push(...stickerEvents) } + // Rich replies + if (repliedToEventId) { + Object.assign(events[0], { + "m.relates_to": { + "m.in_reply_to": { + event_id: repliedToEventId + } + } + }) + } + return events } diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index e3fcb06..5fa16f8 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -1,11 +1,39 @@ const {test} = require("supertape") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") +const Ty = require("../../types") + +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} test("message2event: simple plaintext", async t => { const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "ayy lmao" }]) @@ -15,6 +43,7 @@ test("message2event: simple user mention", async t => { const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", format: "org.matrix.custom.html", @@ -26,6 +55,7 @@ test("message2event: simple room mention", async t => { const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "#main", format: "org.matrix.custom.html", @@ -37,6 +67,7 @@ test("message2event: simple message link", async t => { const events = await messageToEvent(data.message.simple_message_link, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", format: "org.matrix.custom.html", @@ -48,6 +79,7 @@ test("message2event: attachment with no content", async t => { const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.image", url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", body: "image.png", @@ -65,10 +97,12 @@ test("message2event: stickers", async t => { const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "can have attachments too" }, { $type: "m.room.message", + "m.mentions": {}, msgtype: "m.image", url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", body: "image.png", @@ -81,6 +115,7 @@ test("message2event: stickers", async t => { }, }, { $type: "m.sticker", + "m.mentions": {}, body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", info: { mimetype: "image/png" @@ -90,3 +125,94 @@ test("message2event: stickers", async t => { url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" }]) }) + +test("message2event: skull webp attachment with content", async t => { + const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "Image" + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.image", + body: "skull.webp", + info: { + w: 1200, + h: 628, + mimetype: "image/webp", + size: 74290 + }, + external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + url: "mxc://cadence.moe/sDxWmDErBhYBxtDcJQgBETes" + }]) +}) + +test("message2event: reply to skull webp attachment with content", async t => { + const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q" + } + }, + "m.mentions": {}, + msgtype: "m.text", + body: "> Extremity: Image\n\nReply", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to Extremity' + + '
    Image
    ' + + 'Reply' + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.image", + body: "RDT_20230704_0936184915846675925224905.jpg", + info: { + w: 2048, + h: 1536, + mimetype: "image/jpeg", + size: 85906 + }, + external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + url: "mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa" + }]) +}) + +test("message2event: simple reply to matrix user", async t => { + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + }, + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe" + ] + }, + msgtype: "m.text", + body: "> cadence: so can you reply to my webhook uwu\n\nReply", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to cadence' + + '
    so can you reply to my webhook uwu
    ' + + 'Reply' + }]) +}) + +// TODO: read "edits of replies" in the spec diff --git a/db/ooye.db b/db/ooye.db index 560fa72042d091d7c598c89ce57ae3556dd5c227..8856af2938f5c59d796cdae940a01b59bbfe66d9 100644 GIT binary patch delta 2781 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 mB|eZx} */ + const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/event/${eventID}`) + return root +} + /** * @param {string} roomID * @returns {Promise} @@ -73,6 +84,15 @@ function getAllState(roomID) { return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) } +/** + * "Any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than /members as it can be implemented more efficiently on the server." + * @param {string} roomID + * @returns {Promise<{joined: {[mxid: string]: Ty.R.RoomMember}}>} + */ +function getJoinedMembers(roomID) { + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`) +} + /** * @param {string} roomID * @param {string} type @@ -114,7 +134,9 @@ module.exports.createRoom = createRoom module.exports.joinRoom = joinRoom module.exports.inviteToRoom = inviteToRoom module.exports.leaveRoom = leaveRoom +module.exports.getEvent = getEvent module.exports.getAllState = getAllState +module.exports.getJoinedMembers = getJoinedMembers module.exports.sendState = sendState module.exports.sendEvent = sendEvent module.exports.profileSetDisplayname = profileSetDisplayname diff --git a/package.json b/package.json index 7fb8cc6..8604330 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,6 @@ "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" + "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot" } } diff --git a/test/data.js b/test/data.js index d2c586a..7c8fadc 100644 --- a/test/data.js +++ b/test/data.js @@ -423,6 +423,434 @@ module.exports = { flags: 0, components: [] }, + skull_webp_attachment_with_content: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + referenced_message: null, + pinned: false, + nonce: "1128084721398448128", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ], + guild_id: "112760669178241024" + }, + reply_to_skull_webp_attachment_with_content: { + type: 19, + tts: false, + timestamp: "2023-07-10T22:06:27.348000+00:00", + referenced_message: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ] + }, + pinned: false, + nonce: "1128084845403045888", + message_reference: { + message_id: "1128084748338741392", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [ + { + username: "extremity", + public_flags: 768, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + } + ], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084851279536279", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Reply", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 2048, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + size: 85906, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + id: "1128084851023675515", + height: 1536, + filename: "RDT_20230704_0936184915846675925224905.jpg", + content_type: "image/jpeg" + } + ], + guild_id: "112760669178241024" + }, + simple_reply_to_matrix_user: { + type: 19, + tts: false, + timestamp: "2023-07-11T00:19:04.358000+00:00", + referenced_message: { + webhook_id: "703458020193206272", + type: 0, + tts: false, + timestamp: "2023-07-11T00:18:52.856000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128118177155526666", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "so can you reply to my webhook uwu", + components: [], + channel_id: "112760669178241024", + author: { + username: "cadence", + id: "703458020193206272", + discriminator: "0000", + bot: true, + avatar: "ea5413d310c85eb9edaa9db865e80155" + }, + attachments: [], + application_id: "684280192553844747" + }, + pinned: false, + nonce: "1128118222315323392", + message_reference: { + message_id: "1128118177155526666", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1128118225398407228", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Reply", + components: [], + channel_id: "112760669178241024", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" + }, + edit_of_reply_to_skull_webp_attachment_with_content: { + type: 19, + tts: false, + timestamp: "2023-07-10T22:06:27.348000+00:00", + referenced_message: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ] + }, + pinned: false, + message_reference: { + message_id: "1128084748338741392", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [ + { + username: "extremity", + public_flags: 768, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + } + ], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084851279536279", + flags: 0, + embeds: [], + edited_timestamp: "2023-07-10T22:08:57.442417+00:00", + content: "Edit", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 2048, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + size: 85906, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + id: "1128084851023675515", + height: 1536, + filename: "RDT_20230704_0936184915846675925224905.jpg", + content_type: "image/jpeg" + } + ], + guild_id: "112760669178241024" + }, sticker: { id: "1106366167788044450", type: 0, diff --git a/types.d.ts b/types.d.ts index 01ff6a1..3ed3975 100644 --- a/types.d.ts +++ b/types.d.ts @@ -81,6 +81,11 @@ namespace R { room_id: string } + export type RoomMember = { + avatar_url: string + display_name: string + } + export type FileUploaded = { content_uri: string } From 328ae74b61033dcbd95432465d699270f98face1 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 16:51:30 +1200 Subject: [PATCH 071/200] support rich replies, support basic m.mentions --- d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.js | 120 ++++++- d2m/converters/message-to-event.test.js | 126 +++++++ matrix/api.js | 22 ++ package.json | 2 +- test/data.js | 428 ++++++++++++++++++++++++ types.d.ts | 5 + 7 files changed, 689 insertions(+), 16 deletions(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index f5fe5ef..ff3c1de 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message, guild) + const events = await messageToEvent.messageToEvent(message, guild, api) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 9999df9..0e94a70 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const markdown = require("discord-markdown") +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough @@ -39,10 +40,56 @@ function getDiscordParseCallbacks(message, useHTML) { /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild + * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API */ -async function messageToEvent(message, guild) { +async function messageToEvent(message, guild, api) { const events = [] + /** + @type {{room?: boolean, user_ids?: string[]}} + We should consider the following scenarios for mentions: + 1. TODO A discord user rich-replies to a matrix user with a text post + + The matrix user needs to be m.mentioned in the text event + + The matrix user needs to have their name/mxid/link in the text event (notification fallback) + - So prepend their `@name:` to the start of the plaintext body + 2. TODO A discord user rich-replies to a matrix user with an image event only + + The matrix user needs to be m.mentioned in the image event + + The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) + - So append their name to the filename body, I guess!!! + 3. TODO A discord user `@`s a matrix user in the text body of their text box + + The matrix user needs to be m.mentioned in the text event + + No change needed to the text event content: it already has their name + - So make sure we don't do anything in this case. + */ + const mentions = {} + let repliedToEventId = null + let repliedToEventRoomId = null + let repliedToEventSenderMxid = null + let repliedToEventOriginallyFromMatrix = false + + function addMention(mxid) { + if (!mentions.user_ids) mentions.user_ids = [] + mentions.user_ids.push(mxid) + } + + // Mentions scenarios 1 and 2, part A. i.e. translate relevant message.mentions to m.mentions + // (Still need to do scenarios 1 and 2 part B, and scenario 3.) + if (message.type === DiscordTypes.MessageType.Reply && message.message_reference?.message_id) { + const row = db.prepare("SELECT event_id, room_id, source FROM event_message INNER JOIN channel_room USING (channel_id) WHERE message_id = ? AND part = 0").get(message.message_reference.message_id) + if (row) { + repliedToEventId = row.event_id + repliedToEventRoomId = row.room_id + repliedToEventOriginallyFromMatrix = row.source === 0 // source 0 = matrix + } + } + if (repliedToEventOriginallyFromMatrix) { + // Need to figure out who sent that event... + const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) + repliedToEventSenderMxid = event.sender + // Need to add the sender to m.mentions + addMention(repliedToEventSenderMxid) + } + // Text content appears first if (message.content) { let content = message.content @@ -55,33 +102,63 @@ async function messageToEvent(message, guild) { } }) - const html = markdown.toHTML(content, { + let html = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) - const body = markdown.toHTML(content, { + let body = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, false), discordOnly: true, escapeHTML: false, }, null, null) + // Fallback body/formatted_body for replies + if (repliedToEventId) { + let repliedToDisplayName + let repliedToUserHtml + if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { + const match = repliedToEventSenderMxid.match(/^@([^:]*)/) + assert(match) + repliedToDisplayName = match[1] || "a Matrix user" // grab the localpart as the display name, whatever + repliedToUserHtml = `${repliedToDisplayName}` + } else { + repliedToDisplayName = message.referenced_message?.author.global_name || message.referenced_message?.author.username || "a Discord user" + repliedToUserHtml = repliedToDisplayName + } + const repliedToContent = message.referenced_message?.content || "[Replied-to message content wasn't provided by Discord]" + const repliedToHtml = markdown.toHTML(repliedToContent, { + discordCallback: getDiscordParseCallbacks(message, true) + }, null, null) + const repliedToBody = markdown.toHTML(repliedToContent, { + discordCallback: getDiscordParseCallbacks(message, false), + discordOnly: true, + escapeHTML: false, + }, null, null) + html = `
    In reply to ${repliedToUserHtml}` + + `
    ${repliedToHtml}
    ` + + html + body = (`${repliedToDisplayName}: ` // scenario 1 part B for mentions + + repliedToBody).split("\n").map(line => "> " + line).join("\n") + + "\n\n" + body + } + + const newTextMessageEvent = { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.text", + body: body + } + const isPlaintext = body === html - if (isPlaintext) { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body - }) - } else { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body, + if (!isPlaintext) { + Object.assign(newTextMessageEvent, { format: "org.matrix.custom.html", formatted_body: html }) } + + events.push(newTextMessageEvent) } // Then attachments @@ -90,6 +167,7 @@ async function messageToEvent(message, guild) { if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.image", url: await file.uploadDiscordFileToMxc(attachment.url), external_url: attachment.url, @@ -105,6 +183,7 @@ async function messageToEvent(message, guild) { } else { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.text", body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) } @@ -122,6 +201,7 @@ async function messageToEvent(message, guild) { if (sticker && sticker.description) body += ` - ${sticker.description}` return { $type: "m.sticker", + "m.mentions": mentions, body, info: { mimetype: format.mime @@ -131,6 +211,7 @@ async function messageToEvent(message, guild) { } else { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.text", body: "Unsupported sticker format. Name: " + stickerItem.name } @@ -139,6 +220,17 @@ async function messageToEvent(message, guild) { events.push(...stickerEvents) } + // Rich replies + if (repliedToEventId) { + Object.assign(events[0], { + "m.relates_to": { + "m.in_reply_to": { + event_id: repliedToEventId + } + } + }) + } + return events } diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index e3fcb06..5fa16f8 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -1,11 +1,39 @@ const {test} = require("supertape") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") +const Ty = require("../../types") + +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} test("message2event: simple plaintext", async t => { const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "ayy lmao" }]) @@ -15,6 +43,7 @@ test("message2event: simple user mention", async t => { const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", format: "org.matrix.custom.html", @@ -26,6 +55,7 @@ test("message2event: simple room mention", async t => { const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "#main", format: "org.matrix.custom.html", @@ -37,6 +67,7 @@ test("message2event: simple message link", async t => { const events = await messageToEvent(data.message.simple_message_link, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", format: "org.matrix.custom.html", @@ -48,6 +79,7 @@ test("message2event: attachment with no content", async t => { const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.image", url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", body: "image.png", @@ -65,10 +97,12 @@ test("message2event: stickers", async t => { const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "can have attachments too" }, { $type: "m.room.message", + "m.mentions": {}, msgtype: "m.image", url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", body: "image.png", @@ -81,6 +115,7 @@ test("message2event: stickers", async t => { }, }, { $type: "m.sticker", + "m.mentions": {}, body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", info: { mimetype: "image/png" @@ -90,3 +125,94 @@ test("message2event: stickers", async t => { url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" }]) }) + +test("message2event: skull webp attachment with content", async t => { + const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "Image" + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.image", + body: "skull.webp", + info: { + w: 1200, + h: 628, + mimetype: "image/webp", + size: 74290 + }, + external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + url: "mxc://cadence.moe/sDxWmDErBhYBxtDcJQgBETes" + }]) +}) + +test("message2event: reply to skull webp attachment with content", async t => { + const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q" + } + }, + "m.mentions": {}, + msgtype: "m.text", + body: "> Extremity: Image\n\nReply", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to Extremity' + + '
    Image
    ' + + 'Reply' + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.image", + body: "RDT_20230704_0936184915846675925224905.jpg", + info: { + w: 2048, + h: 1536, + mimetype: "image/jpeg", + size: 85906 + }, + external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + url: "mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa" + }]) +}) + +test("message2event: simple reply to matrix user", async t => { + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + }, + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe" + ] + }, + msgtype: "m.text", + body: "> cadence: so can you reply to my webhook uwu\n\nReply", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to cadence' + + '
    so can you reply to my webhook uwu
    ' + + 'Reply' + }]) +}) + +// TODO: read "edits of replies" in the spec diff --git a/matrix/api.js b/matrix/api.js index cf22933..ed9980b 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -65,6 +65,17 @@ async function leaveRoom(roomID, mxid) { await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) } +/** + * @param {string} roomID + * @param {string} eventID + * @template T + */ +async function getEvent(roomID, eventID) { + /** @type {Ty.Event.Outer} */ + const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/event/${eventID}`) + return root +} + /** * @param {string} roomID * @returns {Promise} @@ -73,6 +84,15 @@ function getAllState(roomID) { return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) } +/** + * "Any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than /members as it can be implemented more efficiently on the server." + * @param {string} roomID + * @returns {Promise<{joined: {[mxid: string]: Ty.R.RoomMember}}>} + */ +function getJoinedMembers(roomID) { + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`) +} + /** * @param {string} roomID * @param {string} type @@ -114,7 +134,9 @@ module.exports.createRoom = createRoom module.exports.joinRoom = joinRoom module.exports.inviteToRoom = inviteToRoom module.exports.leaveRoom = leaveRoom +module.exports.getEvent = getEvent module.exports.getAllState = getAllState +module.exports.getJoinedMembers = getJoinedMembers module.exports.sendState = sendState module.exports.sendEvent = sendEvent module.exports.profileSetDisplayname = profileSetDisplayname diff --git a/package.json b/package.json index 7fb8cc6..8604330 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,6 @@ "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "cross-env FORCE_COLOR=true supertape --format tap test/test.js | tap-dot" + "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot" } } diff --git a/test/data.js b/test/data.js index d2c586a..7c8fadc 100644 --- a/test/data.js +++ b/test/data.js @@ -423,6 +423,434 @@ module.exports = { flags: 0, components: [] }, + skull_webp_attachment_with_content: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + referenced_message: null, + pinned: false, + nonce: "1128084721398448128", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ], + guild_id: "112760669178241024" + }, + reply_to_skull_webp_attachment_with_content: { + type: 19, + tts: false, + timestamp: "2023-07-10T22:06:27.348000+00:00", + referenced_message: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ] + }, + pinned: false, + nonce: "1128084845403045888", + message_reference: { + message_id: "1128084748338741392", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [ + { + username: "extremity", + public_flags: 768, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + } + ], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084851279536279", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Reply", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 2048, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + size: 85906, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + id: "1128084851023675515", + height: 1536, + filename: "RDT_20230704_0936184915846675925224905.jpg", + content_type: "image/jpeg" + } + ], + guild_id: "112760669178241024" + }, + simple_reply_to_matrix_user: { + type: 19, + tts: false, + timestamp: "2023-07-11T00:19:04.358000+00:00", + referenced_message: { + webhook_id: "703458020193206272", + type: 0, + tts: false, + timestamp: "2023-07-11T00:18:52.856000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128118177155526666", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "so can you reply to my webhook uwu", + components: [], + channel_id: "112760669178241024", + author: { + username: "cadence", + id: "703458020193206272", + discriminator: "0000", + bot: true, + avatar: "ea5413d310c85eb9edaa9db865e80155" + }, + attachments: [], + application_id: "684280192553844747" + }, + pinned: false, + nonce: "1128118222315323392", + message_reference: { + message_id: "1128118177155526666", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1128118225398407228", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Reply", + components: [], + channel_id: "112760669178241024", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" + }, + edit_of_reply_to_skull_webp_attachment_with_content: { + type: 19, + tts: false, + timestamp: "2023-07-10T22:06:27.348000+00:00", + referenced_message: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ] + }, + pinned: false, + message_reference: { + message_id: "1128084748338741392", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [ + { + username: "extremity", + public_flags: 768, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + } + ], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084851279536279", + flags: 0, + embeds: [], + edited_timestamp: "2023-07-10T22:08:57.442417+00:00", + content: "Edit", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 2048, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + size: 85906, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + id: "1128084851023675515", + height: 1536, + filename: "RDT_20230704_0936184915846675925224905.jpg", + content_type: "image/jpeg" + } + ], + guild_id: "112760669178241024" + }, sticker: { id: "1106366167788044450", type: 0, diff --git a/types.d.ts b/types.d.ts index 01ff6a1..3ed3975 100644 --- a/types.d.ts +++ b/types.d.ts @@ -81,6 +81,11 @@ namespace R { room_id: string } + export type RoomMember = { + avatar_url: string + display_name: string + } + export type FileUploaded = { content_uri: string } From 3baf97be8aae24b95ebb3617e935a753da61bb88 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 17:27:40 +1200 Subject: [PATCH 072/200] support written @mentions (scenario 3) --- d2m/converters/message-to-event.js | 21 +++++++++++- d2m/converters/message-to-event.test.js | 43 +++++++++++++++++++++++++ test/data.js | 31 ++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 0e94a70..daa0a3c 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -8,6 +8,9 @@ const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +const reg = require("../../matrix/read-registration") + +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) function getDiscordParseCallbacks(message, useHTML) { return { @@ -69,7 +72,7 @@ async function messageToEvent(message, guild, api) { function addMention(mxid) { if (!mentions.user_ids) mentions.user_ids = [] - mentions.user_ids.push(mxid) + if (!mentions.user_ids.includes(mxid)) mentions.user_ids.push(mxid) } // Mentions scenarios 1 and 2, part A. i.e. translate relevant message.mentions to m.mentions @@ -106,12 +109,28 @@ async function messageToEvent(message, guild, api) { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) + // TODO: add a string return type to my discord-markdown library let body = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, false), discordOnly: true, escapeHTML: false, }, null, null) + // Mentions scenario 3: scan the message content for written @mentions of matrix users + const matches = [...content.matchAll(/@([a-z0-9._]+)\b/gi)] + if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { + const writtenMentionsText = matches.map(m => m[1].toLowerCase()) + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) + const {joined} = await api.getJoinedMembers(roomID) + for (const [mxid, member] of Object.entries(joined)) { + if (!userRegex.some(rx => mxid.match(rx))) { + const localpart = mxid.match(/@([^:]*)/) + assert(localpart) + if (writtenMentionsText.includes(localpart[1].toLowerCase()) || writtenMentionsText.includes(member.display_name.toLowerCase())) addMention(mxid) + } + } + } + // Fallback body/formatted_body for replies if (repliedToEventId) { let repliedToDisplayName diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 5fa16f8..58093ca 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -215,4 +215,47 @@ test("message2event: simple reply to matrix user", async t => { }]) }) +test("message2event: simple written @mention for matrix user", async t => { + const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, { + async getJoinedMembers(roomID) { + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@huckleton:cadence.moe": { + display_name: "huck", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + }, + "@_ooye_bot:cadence.moe": { + display_name: "Out Of Your Element", + avatar_url: "whatever" + } + } + }) + }) + }) + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe", + "@huckleton:cadence.moe" + ] + }, + msgtype: "m.text", + body: "@Cadence, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and @huck" + }]) +}) + // TODO: read "edits of replies" in the spec diff --git a/test/data.js b/test/data.js index 7c8fadc..a5ca95e 100644 --- a/test/data.js +++ b/test/data.js @@ -304,6 +304,37 @@ module.exports = { flags: 0, components: [] }, + simple_written_at_mention_for_matrix: { + id: "1126739682080858234", + type: 0, + content: "@Cadence, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and @huck", + channel_id: "112760669178241024", + author: { + id: "114147806469554185", + username: "extremity", + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6", + discriminator: "0", + public_flags: 768, + flags: 768, + banner: null, + accent_color: null, + global_name: "Extremity", + avatar_decoration: null, + display_name: "Extremity", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T05:01:14.019000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, simple_reply: { id: "1126604870762369124", type: 19, From 437f04682c0490b3fe94e679195f6466f2045abe Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 17:27:40 +1200 Subject: [PATCH 073/200] support written @mentions (scenario 3) --- d2m/converters/message-to-event.js | 21 +++++++++++- d2m/converters/message-to-event.test.js | 43 +++++++++++++++++++++++++ test/data.js | 31 ++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 0e94a70..daa0a3c 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -8,6 +8,9 @@ const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +const reg = require("../../matrix/read-registration") + +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) function getDiscordParseCallbacks(message, useHTML) { return { @@ -69,7 +72,7 @@ async function messageToEvent(message, guild, api) { function addMention(mxid) { if (!mentions.user_ids) mentions.user_ids = [] - mentions.user_ids.push(mxid) + if (!mentions.user_ids.includes(mxid)) mentions.user_ids.push(mxid) } // Mentions scenarios 1 and 2, part A. i.e. translate relevant message.mentions to m.mentions @@ -106,12 +109,28 @@ async function messageToEvent(message, guild, api) { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) + // TODO: add a string return type to my discord-markdown library let body = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, false), discordOnly: true, escapeHTML: false, }, null, null) + // Mentions scenario 3: scan the message content for written @mentions of matrix users + const matches = [...content.matchAll(/@([a-z0-9._]+)\b/gi)] + if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { + const writtenMentionsText = matches.map(m => m[1].toLowerCase()) + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) + const {joined} = await api.getJoinedMembers(roomID) + for (const [mxid, member] of Object.entries(joined)) { + if (!userRegex.some(rx => mxid.match(rx))) { + const localpart = mxid.match(/@([^:]*)/) + assert(localpart) + if (writtenMentionsText.includes(localpart[1].toLowerCase()) || writtenMentionsText.includes(member.display_name.toLowerCase())) addMention(mxid) + } + } + } + // Fallback body/formatted_body for replies if (repliedToEventId) { let repliedToDisplayName diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 5fa16f8..58093ca 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -215,4 +215,47 @@ test("message2event: simple reply to matrix user", async t => { }]) }) +test("message2event: simple written @mention for matrix user", async t => { + const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, { + async getJoinedMembers(roomID) { + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@huckleton:cadence.moe": { + display_name: "huck", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + }, + "@_ooye_bot:cadence.moe": { + display_name: "Out Of Your Element", + avatar_url: "whatever" + } + } + }) + }) + }) + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe", + "@huckleton:cadence.moe" + ] + }, + msgtype: "m.text", + body: "@Cadence, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and @huck" + }]) +}) + // TODO: read "edits of replies" in the spec diff --git a/test/data.js b/test/data.js index 7c8fadc..a5ca95e 100644 --- a/test/data.js +++ b/test/data.js @@ -304,6 +304,37 @@ module.exports = { flags: 0, components: [] }, + simple_written_at_mention_for_matrix: { + id: "1126739682080858234", + type: 0, + content: "@Cadence, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and @huck", + channel_id: "112760669178241024", + author: { + id: "114147806469554185", + username: "extremity", + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6", + discriminator: "0", + public_flags: 768, + flags: 768, + banner: null, + accent_color: null, + global_name: "Extremity", + avatar_decoration: null, + display_name: "Extremity", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T05:01:14.019000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, simple_reply: { id: "1126604870762369124", type: 19, From 3ffd9086e26798f99a260556f760c043b3cdadc9 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 17:28:42 +1200 Subject: [PATCH 074/200] only need to do mentions scenario 2 part B now --- d2m/converters/message-to-event.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index daa0a3c..6705b6e 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -51,15 +51,15 @@ async function messageToEvent(message, guild, api) { /** @type {{room?: boolean, user_ids?: string[]}} We should consider the following scenarios for mentions: - 1. TODO A discord user rich-replies to a matrix user with a text post + 1. A discord user rich-replies to a matrix user with a text post + The matrix user needs to be m.mentioned in the text event + The matrix user needs to have their name/mxid/link in the text event (notification fallback) - So prepend their `@name:` to the start of the plaintext body - 2. TODO A discord user rich-replies to a matrix user with an image event only + 2. A discord user rich-replies to a matrix user with an image event only + The matrix user needs to be m.mentioned in the image event - + The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) + + TODO The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) - So append their name to the filename body, I guess!!! - 3. TODO A discord user `@`s a matrix user in the text body of their text box + 3. A discord user `@`s a matrix user in the text body of their text box + The matrix user needs to be m.mentioned in the text event + No change needed to the text event content: it already has their name - So make sure we don't do anything in this case. From 011f9c5ecbf73580de9b9121a252a195811c27c6 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 17:28:42 +1200 Subject: [PATCH 075/200] only need to do mentions scenario 2 part B now --- d2m/converters/message-to-event.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index daa0a3c..6705b6e 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -51,15 +51,15 @@ async function messageToEvent(message, guild, api) { /** @type {{room?: boolean, user_ids?: string[]}} We should consider the following scenarios for mentions: - 1. TODO A discord user rich-replies to a matrix user with a text post + 1. A discord user rich-replies to a matrix user with a text post + The matrix user needs to be m.mentioned in the text event + The matrix user needs to have their name/mxid/link in the text event (notification fallback) - So prepend their `@name:` to the start of the plaintext body - 2. TODO A discord user rich-replies to a matrix user with an image event only + 2. A discord user rich-replies to a matrix user with an image event only + The matrix user needs to be m.mentioned in the image event - + The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) + + TODO The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) - So append their name to the filename body, I guess!!! - 3. TODO A discord user `@`s a matrix user in the text body of their text box + 3. A discord user `@`s a matrix user in the text body of their text box + The matrix user needs to be m.mentioned in the text event + No change needed to the text event content: it already has their name - So make sure we don't do anything in this case. From e123baa2264d3f7cf7a62ef87334ea574e047f97 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 12 Jul 2023 14:33:38 +1200 Subject: [PATCH 076/200] Include fallback text for replies to media --- d2m/converters/message-to-event.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 6705b6e..300d154 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -144,7 +144,9 @@ async function messageToEvent(message, guild, api) { repliedToDisplayName = message.referenced_message?.author.global_name || message.referenced_message?.author.username || "a Discord user" repliedToUserHtml = repliedToDisplayName } - const repliedToContent = message.referenced_message?.content || "[Replied-to message content wasn't provided by Discord]" + let repliedToContent = message.referenced_message?.content + if (repliedToContent == "") repliedToContent = "[Media]" + else if (!repliedToContent) repliedToContent = "[Replied-to message content wasn't provided by Discord]" const repliedToHtml = markdown.toHTML(repliedToContent, { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) From f4bfe54850b86280c3e3d0b12108d353d6a7dc7d Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 12 Jul 2023 14:33:38 +1200 Subject: [PATCH 077/200] Include fallback text for replies to media --- d2m/converters/message-to-event.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 6705b6e..300d154 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -144,7 +144,9 @@ async function messageToEvent(message, guild, api) { repliedToDisplayName = message.referenced_message?.author.global_name || message.referenced_message?.author.username || "a Discord user" repliedToUserHtml = repliedToDisplayName } - const repliedToContent = message.referenced_message?.content || "[Replied-to message content wasn't provided by Discord]" + let repliedToContent = message.referenced_message?.content + if (repliedToContent == "") repliedToContent = "[Media]" + else if (!repliedToContent) repliedToContent = "[Replied-to message content wasn't provided by Discord]" const repliedToHtml = markdown.toHTML(repliedToContent, { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) From 8a50959064a0f319355289b082091882dae39c22 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 13 Jul 2023 17:11:24 +1200 Subject: [PATCH 078/200] move namespace_prefix into a sub-object --- d2m/actions/register-user.js | 2 +- matrix/read-registration.test.js | 2 +- types.d.ts | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 1d1eb3d..ef6045a 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -20,7 +20,7 @@ const userToMxid = sync.require("../converters/user-to-mxid") async function createSim(user) { // Choose sim name const simName = userToMxid.userToSimName(user) - const localpart = reg.namespace_prefix + simName + const localpart = reg.ooye.namespace_prefix + simName const mxid = "@" + localpart + ":cadence.moe" // Save chosen name in the database forever diff --git a/matrix/read-registration.test.js b/matrix/read-registration.test.js index c5b3ac8..d402cfb 100644 --- a/matrix/read-registration.test.js +++ b/matrix/read-registration.test.js @@ -2,7 +2,7 @@ const {test} = require("supertape") const reg = require("./read-registration") test("reg: has necessary parameters", t => { - const propertiesToCheck = ["sender_localpart", "id", "as_token", "namespace_prefix"] + const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] t.deepEqual( propertiesToCheck.filter(p => p in reg), propertiesToCheck diff --git a/types.d.ts b/types.d.ts index 3ed3975..32aa21f 100644 --- a/types.d.ts +++ b/types.d.ts @@ -4,7 +4,6 @@ export type AppServiceRegistrationConfig = { hs_token: string url: string sender_localpart: string - namespace_prefix: string namespaces: { users: { exclusive: boolean @@ -17,6 +16,9 @@ export type AppServiceRegistrationConfig = { } protocols: [string] rate_limited: boolean + ooye: { + namespace_prefix: string + } } export type WebhookCreds = { From 3a59d66626fac9f0a5b70f72a478ee8f0f3f77f0 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 13 Jul 2023 17:11:24 +1200 Subject: [PATCH 079/200] move namespace_prefix into a sub-object --- d2m/actions/register-user.js | 2 +- matrix/read-registration.test.js | 2 +- types.d.ts | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 1d1eb3d..ef6045a 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -20,7 +20,7 @@ const userToMxid = sync.require("../converters/user-to-mxid") async function createSim(user) { // Choose sim name const simName = userToMxid.userToSimName(user) - const localpart = reg.namespace_prefix + simName + const localpart = reg.ooye.namespace_prefix + simName const mxid = "@" + localpart + ":cadence.moe" // Save chosen name in the database forever diff --git a/matrix/read-registration.test.js b/matrix/read-registration.test.js index c5b3ac8..d402cfb 100644 --- a/matrix/read-registration.test.js +++ b/matrix/read-registration.test.js @@ -2,7 +2,7 @@ const {test} = require("supertape") const reg = require("./read-registration") test("reg: has necessary parameters", t => { - const propertiesToCheck = ["sender_localpart", "id", "as_token", "namespace_prefix"] + const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] t.deepEqual( propertiesToCheck.filter(p => p in reg), propertiesToCheck diff --git a/types.d.ts b/types.d.ts index 3ed3975..32aa21f 100644 --- a/types.d.ts +++ b/types.d.ts @@ -4,7 +4,6 @@ export type AppServiceRegistrationConfig = { hs_token: string url: string sender_localpart: string - namespace_prefix: string namespaces: { users: { exclusive: boolean @@ -17,6 +16,9 @@ export type AppServiceRegistrationConfig = { } protocols: [string] rate_limited: boolean + ooye: { + namespace_prefix: string + } } export type WebhookCreds = { From 24bafaf41fe5472d7900bc54f1027bc82d48d64b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 13 Jul 2023 17:36:20 +1200 Subject: [PATCH 080/200] large files are now linked instead of uploaded --- d2m/converters/message-to-event.js | 23 +++++++++++++++++++--- d2m/converters/message-to-event.test.js | 25 ++++++++++++++++++++++++ db/ooye.db | Bin 303104 -> 303104 bytes matrix/api.js | 2 +- package-lock.json | 12 +++++++++--- package.json | 1 + types.d.ts | 1 + 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 300d154..49a387a 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const markdown = require("discord-markdown") +const pb = require("prettier-bytes") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -184,8 +185,24 @@ async function messageToEvent(message, guild, api) { // Then attachments const attachmentEvents = await Promise.all(message.attachments.map(async attachment => { - // TODO: handle large files differently - link them instead of uploading - if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { + const emoji = + attachment.content_type?.startsWith("image/jp") ? "📸" + : attachment.content_type?.startsWith("image/") ? "🖼️" + : attachment.content_type?.startsWith("video/") ? "🎞️" + : attachment.content_type?.startsWith("text/") ? "📝" + : attachment.content_type?.startsWith("audio/") ? "🎶" + : "📄" + // for large files, always link them instead of uploading so I don't use up all the space in the content repo + if (attachment.size > reg.ooye.max_file_size) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.text", + body: `${emoji} Uploaded file: ${attachment.url} (${pb(attachment.size)})`, + format: "org.matrix.custom.html", + formatted_body: `${emoji} Uploaded file: ${attachment.filename} (${pb(attachment.size)})` + } + } else if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { return { $type: "m.room.message", "m.mentions": mentions, @@ -206,7 +223,7 @@ async function messageToEvent(message, guild, api) { $type: "m.room.message", "m.mentions": mentions, msgtype: "m.text", - body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) + body: `Unsupported attachment:\n${JSON.stringify(attachment, null, 2)}\n${attachment.url}` } } })) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 58093ca..17079e5 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -258,4 +258,29 @@ test("message2event: simple written @mention for matrix user", async t => { }]) }) +test("message2event: very large attachment is linked instead of being uploaded", async t => { + const events = await messageToEvent({ + content: "hey", + attachments: [{ + filename: "hey.jpg", + url: "https://discord.com/404/hey.jpg", + content_type: "application/i-made-it-up", + size: 100e6 + }] + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "hey" + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "📄 Uploaded file: https://discord.com/404/hey.jpg (100 MB)", + format: "org.matrix.custom.html", + formatted_body: '📄 Uploaded file: hey.jpg (100 MB)' + }]) +}) + // TODO: read "edits of replies" in the spec diff --git a/db/ooye.db b/db/ooye.db index 8856af2938f5c59d796cdae940a01b59bbfe66d9..a22816bad5426477af017e68fc08af75a4b99f42 100644 GIT binary patch delta 3111 zcmbuBYiwKP8OP6g&#@ibiJgQ9wsz}MK?j@a=Dg>AB-+Hc6FYW%iJdq$D2@|5PHZQ> zBz9uA#qDTdjJC8h@S;f|Qqh9c58IRv9=$;LFirb{%&KZ-g)(V_ZgiSDP17b#+IDE1 z5vftRejb0>sKzi%#4gq}?GNhk ze*HIeztJ7X@fzL!-Cg(z)mc?k^-b~$h>|Bs2MLHvLwlf(aGR1fED$q!mNcd|*L+Gs&y zD1oL~o&`^=Ug>an2%F*B>aKChfZn)4^wmzz1<}m~qED;nMcw%i>fp%U)l~K;K0?6W ztZCLB2FkH-O;(Q6E9P}^xafjyXmV*(6^iChO$zpnKRq z5D(d#;?R(Wf+UJGC2-)_>VMZ%#vKs5fa5axENR!DF&2$IhEH`rGQ4IO(=}>48Whmq zUmIxbTunC~Z6WZF0qPi20{io|6}%~{RQ9osXgn039Z3g<2jt3-EYD}eqP@UI`g3mI zOuvOOd50^p3Gx3yP#nkeB29A?cye7bJ2L!nkHUqJFz>xQ+i* zt=doax^VQ~d3*;N`xm|o_oAa|d=L8BOW@n}-hbl1e2M5(KZZ1?z=J^j*lGvWqt+bu zs(M{V+dlsY{rEN93_3iul|SyP|NBLt4XauFT@1X6T5f|~?fAQ(%Xq=KVBBZ;li>{b z1W~uaBkdrfvugjN{e`xm-P`g>%d7R=Z7{Z>|A>z+I^2^nX(~J8nG4uL6O275nTBlg zw0&{ZopYAMQ-gD*oNQa%(4S>#ju9ZwQ#5#bUAU1%JQzs2LfLehE@$H5N$4KQz@lX4 zUCD&U>Kv8L!D2o>N(cQLlq%#{P7)a?K?yvyjtPh4(dd+Gjw{G>KGW#JOmcoGmd(zT z%Ztf;G#2Nk;+)UIMatq}GmQ)s6gx@+1t9~Du2X`Q1q%n+z)~gXuIsGUkgf}_q@ zI_r0vX8S;dLoSB*rn!E!ZziGfyR9v5qO70nG zbA@7a6~XP0ro)n{)btc12`tOdj3_{g0f*~*{|>HQdZbf(9y6Xbx(t6aAjAH)KeRp5 z*3){m^Y_T8_Pmnus zK+K=Bdov+d){+|vu<>S6IT|YEDT;zb^C11zO4ixn&f-6v+5W3v#rpe ziT)ZrZc~e6B)SS&$k57EVjxr=DYT3?(c7y#1$`G8Ymzrl&FWVln_HldRh#H9SLU0l zyvlAa^R3WNG|^vUN^cs{Tagbd-0dK*lFyM&k{~VE3HWR*=>({R){0eX?+cfM#yOAb>g?k^C4kJ zmI8SI^?X2_K<_jNH`07a^r62d2z%{=IazgQfS_MLkN4IeXdyq;+rsK2E@rytkv)Lqp*r*kfZogv=o99o=qmYiX<)F8Zl z+iG1bSVor*5psR-J>p}Hs_hE?06;56<(A6T-+ytLIDpbc;!3@?o4koPFVJy->_u&P zq6O8I>!}Q9uxD_TeOi(0{u{(`(s%)r>*sC|T{x*^Kt^wUL|9+P+sSqfUczW@LL delta 750 zcmW-eOGs2<6vw~EnS1BnnfdOFln=~FvNz(4snN!n(aBM%OsGv^CZrn+i_%alP+COf zlban0qO=BUl`)IY%w$a5REx>cv=aKm6c-&f)NDizc;2lX}vrw|7m@ z;w3qI&)uuNW+;@C0`kyQomo~}sy?lEBp~0pah)Y>Ic!OTI6DkoPzH>T@zXq)h1mk_ zrsb5Yug&XIL6f>d7D7mjnM=!l^Zir>{vKhzNZeIc!RN)!h`YusFv$l#M)#Rr4g1lY z;{h!(;Vpr>Jch0U@JH&8U#;ZZ$%?5)H<{jO2-J(@e(yjL)-bA=%9~KcGO;3(bI2&A zA-Du3wn3xG@Q82XmZ&z}F|Sz}OSi=JPQ5fzA1kzSCKH>h!7smB`6+h3>Xoc|MQ^Xt z*DHUBl^ao`O*$9pU8Q$8fbB==1|05Z8PfloPW5w_@)Fe^mKAY%hZWxLsxSvWxaHCn zrX2te&nBoZGPiDw8jVJd_#y^z$0FODst2`YZA3e%Wm`X4Z?Mm7kX5l1`b?f!WYttQ zzTk<>{>kRY>>!XBVPlv0Auw^|f{-}Q>s=m4oa`WHh<(gK$%Os1J=eD2D7A%cHMVqp zRwV1)dbtk9s5Y)OYum(NpA|Cuw!)Ua7O22S&v^(Z&+>rWL*b)}kB%g`S%W#8jPovu zGki70;7KS%&vV|1-|Kh@cFu7XZ~o$ijB*Um^4(}UAn?y-kmB(R%!kPWI?S?$5PlIb z4gZAUxB=10" } }, + "node_modules/prettier-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prettier-bytes/-/prettier-bytes-1.0.4.tgz", + "integrity": "sha512-dLbWOa4xBn+qeWeIF60qRoB6Pk2jX5P3DIVgOQyMyvBpu931Q+8dXz8X0snJiFkQdohDDLnZQECjzsAj75hgZQ==" + }, "node_modules/pretty-format": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", @@ -2318,9 +2324,9 @@ } }, "node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, diff --git a/package.json b/package.json index 8604330..951089b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", + "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", "try-to-catch": "^3.0.1" }, diff --git a/types.d.ts b/types.d.ts index 32aa21f..eeb4b75 100644 --- a/types.d.ts +++ b/types.d.ts @@ -18,6 +18,7 @@ export type AppServiceRegistrationConfig = { rate_limited: boolean ooye: { namespace_prefix: string + max_file_size: number } } From f16900553a8135e7f6168f7c0be4ea5610512efc Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 13 Jul 2023 17:36:20 +1200 Subject: [PATCH 081/200] large files are now linked instead of uploaded --- d2m/converters/message-to-event.js | 23 ++++++++++++++++++++--- d2m/converters/message-to-event.test.js | 25 +++++++++++++++++++++++++ matrix/api.js | 2 +- package-lock.json | 12 +++++++++--- package.json | 1 + types.d.ts | 1 + 6 files changed, 57 insertions(+), 7 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 300d154..49a387a 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const markdown = require("discord-markdown") +const pb = require("prettier-bytes") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -184,8 +185,24 @@ async function messageToEvent(message, guild, api) { // Then attachments const attachmentEvents = await Promise.all(message.attachments.map(async attachment => { - // TODO: handle large files differently - link them instead of uploading - if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { + const emoji = + attachment.content_type?.startsWith("image/jp") ? "📸" + : attachment.content_type?.startsWith("image/") ? "🖼️" + : attachment.content_type?.startsWith("video/") ? "🎞️" + : attachment.content_type?.startsWith("text/") ? "📝" + : attachment.content_type?.startsWith("audio/") ? "🎶" + : "📄" + // for large files, always link them instead of uploading so I don't use up all the space in the content repo + if (attachment.size > reg.ooye.max_file_size) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.text", + body: `${emoji} Uploaded file: ${attachment.url} (${pb(attachment.size)})`, + format: "org.matrix.custom.html", + formatted_body: `${emoji} Uploaded file: ${attachment.filename} (${pb(attachment.size)})` + } + } else if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { return { $type: "m.room.message", "m.mentions": mentions, @@ -206,7 +223,7 @@ async function messageToEvent(message, guild, api) { $type: "m.room.message", "m.mentions": mentions, msgtype: "m.text", - body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) + body: `Unsupported attachment:\n${JSON.stringify(attachment, null, 2)}\n${attachment.url}` } } })) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 58093ca..17079e5 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -258,4 +258,29 @@ test("message2event: simple written @mention for matrix user", async t => { }]) }) +test("message2event: very large attachment is linked instead of being uploaded", async t => { + const events = await messageToEvent({ + content: "hey", + attachments: [{ + filename: "hey.jpg", + url: "https://discord.com/404/hey.jpg", + content_type: "application/i-made-it-up", + size: 100e6 + }] + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "hey" + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "📄 Uploaded file: https://discord.com/404/hey.jpg (100 MB)", + format: "org.matrix.custom.html", + formatted_body: '📄 Uploaded file: hey.jpg (100 MB)' + }]) +}) + // TODO: read "edits of replies" in the spec diff --git a/matrix/api.js b/matrix/api.js index ed9980b..7f9d74e 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -110,7 +110,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"}`) + console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) /** @type {Ty.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/package-lock.json b/package-lock.json index 7dcde49..13b6799 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", + "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", "try-to-catch": "^3.0.1" }, @@ -2099,6 +2100,11 @@ "node": ">=10" } }, + "node_modules/prettier-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prettier-bytes/-/prettier-bytes-1.0.4.tgz", + "integrity": "sha512-dLbWOa4xBn+qeWeIF60qRoB6Pk2jX5P3DIVgOQyMyvBpu931Q+8dXz8X0snJiFkQdohDDLnZQECjzsAj75hgZQ==" + }, "node_modules/pretty-format": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", @@ -2318,9 +2324,9 @@ } }, "node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, diff --git a/package.json b/package.json index 8604330..951089b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "matrix-js-sdk": "^24.1.0", "mixin-deep": "^2.0.1", "node-fetch": "^2.6.7", + "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", "try-to-catch": "^3.0.1" }, diff --git a/types.d.ts b/types.d.ts index 32aa21f..eeb4b75 100644 --- a/types.d.ts +++ b/types.d.ts @@ -18,6 +18,7 @@ export type AppServiceRegistrationConfig = { rate_limited: boolean ooye: { namespace_prefix: string + max_file_size: number } } From aa09db11694d2332d59559157e6117daa293789b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 28 Jul 2023 17:05:13 +1200 Subject: [PATCH 082/200] progress on edits --- d2m/actions/edit-message.js | 118 ++++++++++++++++++++++++++++++ d2m/actions/send-message.js | 2 +- db/ooye.db | Bin 303104 -> 360448 bytes m2d/actions/send-event.js | 2 +- matrix/api.js | 19 ++++- scripts/save-event-types-to-db.js | 30 ++++++++ 6 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 d2m/actions/edit-message.js create mode 100644 scripts/save-event-types-to-db.js diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js new file mode 100644 index 0000000..662e124 --- /dev/null +++ b/d2m/actions/edit-message.js @@ -0,0 +1,118 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../converters/message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @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").GatewayMessageCreateDispatchData} message + * @param {import("discord-api-types/v10").APIGuild} guild + */ +async function editMessage(message, guild) { + // Figure out what events we will be replacing + + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) + const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ + const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) + + // Figure out what we will be replacing them with + + const newEvents = await messageToEvent.messageToEvent(message, guild, api) + + // Match the new events to the old events + + /* + Rules: + + The events must have the same type. + + The events must have the same subtype. + Events will therefore be divided into three categories: + */ + /** 1. Events that are matched, and should be edited by sending another m.replace event */ + let eventsToReplace = [] + /** 2. Events that are present in the old version only, and should be blanked or redacted */ + let eventsToRedact = [] + /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ + let eventsToSend = [] + + // For each old event... + outer: while (newEvents.length) { + const newe = newEvents[0] + // Find a new event to pair it with... + let handled = false + for (let i = 0; i < oldEventRows.length; i++) { + const olde = oldEventRows[i] + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { + // Found one! + // Set up the pairing + eventsToReplace.push({ + old: olde, + new: newe + }) + // These events have been handled now, so remove them from the source arrays + newEvents.shift() + oldEventRows.splice(i, 1) + // Go all the way back to the start of the next iteration of the outer loop + continue outer + } + } + // If we got this far, we could not pair it to an existing event, so it'll have to be a new one + eventsToSend.push(newe) + newEvents.shift() + } + // Anything remaining in oldEventRows is present in the old version only and should be redacted. + eventsToRedact = oldEventRows + + // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! + // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) + // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. + eventsToReplace = eventsToReplace.filter(ev => { + // Discord does not allow files, images, attachments, or videos to be edited. + if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { + return false + } + // Discord does not allow stickers to be edited. + if (ev.old.event_type === "m.sticker") { + return false + } + // Anything else is fair game. + return true + }) + + // Action time! + + // 1. Replace all the things. + + + // 2. Redact all the things. + + // 3. Send all the things. + + // old code lies here + let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '1' remains in the database? + for (const event of events) { + const eventType = event.$type + /** @type {Pick> & { $type?: string }} */ + const eventWithoutType = {...event} + delete eventWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventIDs.push(eventID) + } + + return eventIDs +} + +module.exports.editMessage = editMessage diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index ff3c1de..258efcf 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -37,7 +37,7 @@ async function sendMessage(message, guild) { delete eventWithoutType.$type const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord + db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting eventIDs.push(eventID) diff --git a/db/ooye.db b/db/ooye.db index a22816bad5426477af017e68fc08af75a4b99f42..33c5ef8abaf5ad514d187a005124f5526b054c64 100644 GIT binary patch delta 28262 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 delta 16202 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 F{{RLcI+Xwb diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index c05f08c..3f49fa4 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -25,7 +25,7 @@ async function sendEvent(event) { let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 0)").run(event.event_id, messageResponse.id, channelID, eventPart) // source 0 = matrix + db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? messageResponses.push(messageResponse) diff --git a/matrix/api.js b/matrix/api.js index 7f9d74e..9111909 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -15,12 +15,18 @@ const makeTxnId = sync.require("./txnid") /** * @param {string} p endpoint to access * @param {string} [mxid] optional: user to act as, for the ?user_id parameter + * @param {{[x: string]: any}} [otherParams] optional: any other query parameters to add * @returns {string} the new endpoint */ -function path(p, mxid) { +function path(p, mxid, otherParams = {}) { if (!mxid) return p const u = new URL(p, "http://localhost") u.searchParams.set("user_id", mxid) + for (const entry of Object.entries(otherParams)) { + if (entry[1] != undefined) { + u.searchParams.set(entry[0], entry[1]) + } + } return u.pathname + "?" + u.searchParams.toString() } @@ -109,10 +115,17 @@ async function sendState(roomID, type, stateKey, content, mxid) { return root.event_id } -async function sendEvent(roomID, type, content, mxid) { +/** + * @param {string} roomID + * @param {string} type + * @param {any} content + * @param {string} [mxid] + * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds + */ +async function sendEvent(roomID, type, content, mxid, timestamp) { console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) /** @type {Ty.R.EventSent} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid), content) + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content) return root.event_id } diff --git a/scripts/save-event-types-to-db.js b/scripts/save-event-types-to-db.js new file mode 100644 index 0000000..83f5d2b --- /dev/null +++ b/scripts/save-event-types-to-db.js @@ -0,0 +1,30 @@ +// @ts-check + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const passthrough = require("../passthrough") +const db = new sqlite("db/ooye.db") + +const sync = new HeatSync({watchFS: false}) + +Object.assign(passthrough, {sync, db}) + +const api = require("../matrix/api") + +/** @type {{event_id: string, room_id: string, event_type: string}[]} */ // @ts-ignore +const rows = db.prepare("SELECT event_id, room_id, event_type FROM event_message INNER JOIN channel_room USING (channel_id)").all() + +const preparedUpdate = db.prepare("UPDATE event_message SET event_type = ?, event_subtype = ? WHERE event_id = ?") + +;(async () => { + for (const row of rows) { + if (row.event_type == null) { + const event = await api.getEvent(row.room_id, row.event_id) + const type = event.type + const subtype = event.content.msgtype || null + preparedUpdate.run(type, subtype, row.event_id) + console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`) + } + } +})() From 96dd488e3935bc3a8a2eabca0546250dc6ac2fe3 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 28 Jul 2023 17:05:13 +1200 Subject: [PATCH 083/200] progress on edits --- d2m/actions/edit-message.js | 118 ++++++++++++++++++++++++++++++ d2m/actions/send-message.js | 2 +- m2d/actions/send-event.js | 2 +- matrix/api.js | 19 ++++- scripts/save-event-types-to-db.js | 30 ++++++++ 5 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 d2m/actions/edit-message.js create mode 100644 scripts/save-event-types-to-db.js diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js new file mode 100644 index 0000000..662e124 --- /dev/null +++ b/d2m/actions/edit-message.js @@ -0,0 +1,118 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../converters/message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @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").GatewayMessageCreateDispatchData} message + * @param {import("discord-api-types/v10").APIGuild} guild + */ +async function editMessage(message, guild) { + // Figure out what events we will be replacing + + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) + const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ + const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) + + // Figure out what we will be replacing them with + + const newEvents = await messageToEvent.messageToEvent(message, guild, api) + + // Match the new events to the old events + + /* + Rules: + + The events must have the same type. + + The events must have the same subtype. + Events will therefore be divided into three categories: + */ + /** 1. Events that are matched, and should be edited by sending another m.replace event */ + let eventsToReplace = [] + /** 2. Events that are present in the old version only, and should be blanked or redacted */ + let eventsToRedact = [] + /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ + let eventsToSend = [] + + // For each old event... + outer: while (newEvents.length) { + const newe = newEvents[0] + // Find a new event to pair it with... + let handled = false + for (let i = 0; i < oldEventRows.length; i++) { + const olde = oldEventRows[i] + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { + // Found one! + // Set up the pairing + eventsToReplace.push({ + old: olde, + new: newe + }) + // These events have been handled now, so remove them from the source arrays + newEvents.shift() + oldEventRows.splice(i, 1) + // Go all the way back to the start of the next iteration of the outer loop + continue outer + } + } + // If we got this far, we could not pair it to an existing event, so it'll have to be a new one + eventsToSend.push(newe) + newEvents.shift() + } + // Anything remaining in oldEventRows is present in the old version only and should be redacted. + eventsToRedact = oldEventRows + + // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! + // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) + // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. + eventsToReplace = eventsToReplace.filter(ev => { + // Discord does not allow files, images, attachments, or videos to be edited. + if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { + return false + } + // Discord does not allow stickers to be edited. + if (ev.old.event_type === "m.sticker") { + return false + } + // Anything else is fair game. + return true + }) + + // Action time! + + // 1. Replace all the things. + + + // 2. Redact all the things. + + // 3. Send all the things. + + // old code lies here + let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '1' remains in the database? + for (const event of events) { + const eventType = event.$type + /** @type {Pick> & { $type?: string }} */ + const eventWithoutType = {...event} + delete eventWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventIDs.push(eventID) + } + + return eventIDs +} + +module.exports.editMessage = editMessage diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index ff3c1de..258efcf 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -37,7 +37,7 @@ async function sendMessage(message, guild) { delete eventWithoutType.$type const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord + db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting eventIDs.push(eventID) diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index c05f08c..3f49fa4 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -25,7 +25,7 @@ async function sendEvent(event) { let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 0)").run(event.event_id, messageResponse.id, channelID, eventPart) // source 0 = matrix + db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? messageResponses.push(messageResponse) diff --git a/matrix/api.js b/matrix/api.js index 7f9d74e..9111909 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -15,12 +15,18 @@ const makeTxnId = sync.require("./txnid") /** * @param {string} p endpoint to access * @param {string} [mxid] optional: user to act as, for the ?user_id parameter + * @param {{[x: string]: any}} [otherParams] optional: any other query parameters to add * @returns {string} the new endpoint */ -function path(p, mxid) { +function path(p, mxid, otherParams = {}) { if (!mxid) return p const u = new URL(p, "http://localhost") u.searchParams.set("user_id", mxid) + for (const entry of Object.entries(otherParams)) { + if (entry[1] != undefined) { + u.searchParams.set(entry[0], entry[1]) + } + } return u.pathname + "?" + u.searchParams.toString() } @@ -109,10 +115,17 @@ async function sendState(roomID, type, stateKey, content, mxid) { return root.event_id } -async function sendEvent(roomID, type, content, mxid) { +/** + * @param {string} roomID + * @param {string} type + * @param {any} content + * @param {string} [mxid] + * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds + */ +async function sendEvent(roomID, type, content, mxid, timestamp) { console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) /** @type {Ty.R.EventSent} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid), content) + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content) return root.event_id } diff --git a/scripts/save-event-types-to-db.js b/scripts/save-event-types-to-db.js new file mode 100644 index 0000000..83f5d2b --- /dev/null +++ b/scripts/save-event-types-to-db.js @@ -0,0 +1,30 @@ +// @ts-check + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const passthrough = require("../passthrough") +const db = new sqlite("db/ooye.db") + +const sync = new HeatSync({watchFS: false}) + +Object.assign(passthrough, {sync, db}) + +const api = require("../matrix/api") + +/** @type {{event_id: string, room_id: string, event_type: string}[]} */ // @ts-ignore +const rows = db.prepare("SELECT event_id, room_id, event_type FROM event_message INNER JOIN channel_room USING (channel_id)").all() + +const preparedUpdate = db.prepare("UPDATE event_message SET event_type = ?, event_subtype = ? WHERE event_id = ?") + +;(async () => { + for (const row of rows) { + if (row.event_type == null) { + const event = await api.getEvent(row.room_id, row.event_id) + const type = event.type + const subtype = event.content.msgtype || null + preparedUpdate.run(type, subtype, row.event_id) + console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`) + } + } +})() From 5ac72df7d0df40906fa980ea470c46ec1b4d0af4 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 11 Aug 2023 01:11:58 +1200 Subject: [PATCH 084/200] add code coverage --- .gitignore | 1 + package-lock.json | 506 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- 3 files changed, 510 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9c3ba55..4c982d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules config.js registration.yaml +coverage diff --git a/package-lock.json b/package-lock.json index 13b6799..e8b6aeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "c8": "^8.0.1", "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" @@ -41,6 +42,12 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@cloudcmd/stub": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", @@ -141,6 +148,15 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/schemas": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", @@ -153,6 +169,31 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@matrix-org/matrix-sdk-crypto-js": { "version": "0.1.0-alpha.8", "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.8.tgz", @@ -281,6 +322,12 @@ "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, "node_modules/@types/node": { "version": "18.16.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", @@ -625,6 +672,32 @@ "node": ">= 0.8" } }, + "node_modules/c8": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.1.tgz", + "integrity": "sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -685,6 +758,41 @@ "node": ">=4" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cloudstorm": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.3.tgz", @@ -731,6 +839,12 @@ "node": ">= 0.8" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -750,6 +864,12 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -983,6 +1103,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1130,6 +1259,22 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1139,6 +1284,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -1203,6 +1361,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -1337,6 +1504,12 @@ "backtracker": "3.3.2" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1674,6 +1847,42 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", @@ -1715,6 +1924,21 @@ "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", "dev": true }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loglevel": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", @@ -1738,6 +1962,21 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/matrix-appservice": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-2.0.0.tgz", @@ -2035,6 +2274,36 @@ "wrappy": "1" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -2055,6 +2324,24 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2266,6 +2553,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -2291,6 +2587,63 @@ "node": ">= 4" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2418,6 +2771,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -2752,6 +3111,62 @@ "node": ">= 6" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -2856,6 +3271,20 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2944,6 +3373,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2955,11 +3422,38 @@ "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -2968,6 +3462,18 @@ "engines": { "node": ">=12" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 951089b..dd6f55d 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,13 @@ "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "c8": "^8.0.1", "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot" + "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot", + "cover": "c8 --skip-full -r html -r text supertape --no-check-assertions-count --format fail test/test.js" } } From f501718691b4f03f18be0d42b098b8a0300ea64d Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 11 Aug 2023 01:11:58 +1200 Subject: [PATCH 085/200] add code coverage --- .gitignore | 1 + package-lock.json | 506 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- 3 files changed, 510 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9c3ba55..4c982d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules config.js registration.yaml +coverage diff --git a/package-lock.json b/package-lock.json index 13b6799..e8b6aeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "c8": "^8.0.1", "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" @@ -41,6 +42,12 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@cloudcmd/stub": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@cloudcmd/stub/-/stub-4.0.1.tgz", @@ -141,6 +148,15 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/schemas": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", @@ -153,6 +169,31 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@matrix-org/matrix-sdk-crypto-js": { "version": "0.1.0-alpha.8", "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.8.tgz", @@ -281,6 +322,12 @@ "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, "node_modules/@types/node": { "version": "18.16.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", @@ -625,6 +672,32 @@ "node": ">= 0.8" } }, + "node_modules/c8": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.1.tgz", + "integrity": "sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -685,6 +758,41 @@ "node": ">=4" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cloudstorm": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.3.tgz", @@ -731,6 +839,12 @@ "node": ">= 0.8" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -750,6 +864,12 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -983,6 +1103,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1130,6 +1259,22 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1139,6 +1284,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -1203,6 +1361,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -1337,6 +1504,12 @@ "backtracker": "3.3.2" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1674,6 +1847,42 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", @@ -1715,6 +1924,21 @@ "integrity": "sha512-QkuwuBMQ9BQHMUEkAtIA4INLrkmnnveqlFB1oFi09gbU0wBdZo6tTnyxNWMR84zHxBuwK7GLAwqN8nrvVxOLTA==", "dev": true }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loglevel": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", @@ -1738,6 +1962,21 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/matrix-appservice": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-2.0.0.tgz", @@ -2035,6 +2274,36 @@ "wrappy": "1" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -2055,6 +2324,24 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2266,6 +2553,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -2291,6 +2587,63 @@ "node": ">= 4" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2418,6 +2771,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -2752,6 +3111,62 @@ "node": ">= 6" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -2856,6 +3271,20 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2944,6 +3373,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2955,11 +3422,38 @@ "integrity": "sha512-23LJhkIw940uTcDFyJZmNyO0z8lEINOTGCr4vR5YCG3urkdXwduRIhivBm9wKaVynLHYvxoHHYbKsDiafCLp6w==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -2968,6 +3462,18 @@ "engines": { "node": ">=12" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 951089b..dd6f55d 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,13 @@ "devDependencies": { "@types/node": "^18.16.0", "@types/node-fetch": "^2.6.3", + "c8": "^8.0.1", "cross-env": "^7.0.3", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot" + "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot", + "cover": "c8 --skip-full -r html -r text supertape --no-check-assertions-count --format fail test/test.js" } } From 85bdb98891c2d6d03cccc49225a56ead77da460a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 15 Aug 2023 17:20:31 +1200 Subject: [PATCH 086/200] script for capturing message update events --- d2m/actions/edit-message.js | 96 +---------------------- d2m/converters/edit-to-changes.js | 96 +++++++++++++++++++++++ d2m/discord-client.js | 6 +- scripts/capture-message-update-events.js | 51 ++++++++++++ scripts/events.db | Bin 0 -> 8192 bytes 5 files changed, 154 insertions(+), 95 deletions(-) create mode 100644 d2m/converters/edit-to-changes.js create mode 100644 scripts/capture-message-update-events.js create mode 100644 scripts/events.db diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 662e124..933267c 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -1,94 +1,5 @@ -// @ts-check - -const assert = require("assert") - -const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough -/** @type {import("../converters/message-to-event")} */ -const messageToEvent = sync.require("../converters/message-to-event") -/** @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").GatewayMessageCreateDispatchData} message - * @param {import("discord-api-types/v10").APIGuild} guild - */ -async function editMessage(message, guild) { - // Figure out what events we will be replacing - - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) - const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) - /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ - const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) - - // Figure out what we will be replacing them with - - const newEvents = await messageToEvent.messageToEvent(message, guild, api) - - // Match the new events to the old events - - /* - Rules: - + The events must have the same type. - + The events must have the same subtype. - Events will therefore be divided into three categories: - */ - /** 1. Events that are matched, and should be edited by sending another m.replace event */ - let eventsToReplace = [] - /** 2. Events that are present in the old version only, and should be blanked or redacted */ - let eventsToRedact = [] - /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ - let eventsToSend = [] - - // For each old event... - outer: while (newEvents.length) { - const newe = newEvents[0] - // Find a new event to pair it with... - let handled = false - for (let i = 0; i < oldEventRows.length; i++) { - const olde = oldEventRows[i] - if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { - // Found one! - // Set up the pairing - eventsToReplace.push({ - old: olde, - new: newe - }) - // These events have been handled now, so remove them from the source arrays - newEvents.shift() - oldEventRows.splice(i, 1) - // Go all the way back to the start of the next iteration of the outer loop - continue outer - } - } - // If we got this far, we could not pair it to an existing event, so it'll have to be a new one - eventsToSend.push(newe) - newEvents.shift() - } - // Anything remaining in oldEventRows is present in the old version only and should be redacted. - eventsToRedact = oldEventRows - - // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! - // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) - // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. - eventsToReplace = eventsToReplace.filter(ev => { - // Discord does not allow files, images, attachments, or videos to be edited. - if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { - return false - } - // Discord does not allow stickers to be edited. - if (ev.old.event_type === "m.sticker") { - return false - } - // Anything else is fair game. - return true - }) - - // Action time! +async function editMessage() { + // Action time! // 1. Replace all the things. @@ -113,6 +24,5 @@ async function editMessage(message, guild) { } return eventIDs -} -module.exports.editMessage = editMessage +{eventsToReplace, eventsToRedact, eventsToSend} diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js new file mode 100644 index 0000000..0dd084f --- /dev/null +++ b/d2m/converters/edit-to-changes.js @@ -0,0 +1,96 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("./message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") +/** @type {import("../actions/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").GatewayMessageCreateDispatchData} message + * IMPORTANT: This may not have all the normal fields! The API documentation doesn't provide possible types, just says it's all optional! + * Since I don't have a spec, I will have to capture some real traffic and add it as test cases... I hope they don't change anything later... + * @param {import("discord-api-types/v10").APIGuild} guild + */ +async function editToChanges(message, guild) { + // Figure out what events we will be replacing + + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) + const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ + const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) + + // Figure out what we will be replacing them with + + const newEvents = await messageToEvent.messageToEvent(message, guild, api) + + // Match the new events to the old events + + /* + Rules: + + The events must have the same type. + + The events must have the same subtype. + Events will therefore be divided into three categories: + */ + /** 1. Events that are matched, and should be edited by sending another m.replace event */ + let eventsToReplace = [] + /** 2. Events that are present in the old version only, and should be blanked or redacted */ + let eventsToRedact = [] + /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ + let eventsToSend = [] + + // For each old event... + outer: while (newEvents.length) { + const newe = newEvents[0] + // Find a new event to pair it with... + let handled = false + for (let i = 0; i < oldEventRows.length; i++) { + const olde = oldEventRows[i] + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { + // Found one! + // Set up the pairing + eventsToReplace.push({ + old: olde, + new: newe + }) + // These events have been handled now, so remove them from the source arrays + newEvents.shift() + oldEventRows.splice(i, 1) + // Go all the way back to the start of the next iteration of the outer loop + continue outer + } + } + // If we got this far, we could not pair it to an existing event, so it'll have to be a new one + eventsToSend.push(newe) + newEvents.shift() + } + // Anything remaining in oldEventRows is present in the old version only and should be redacted. + eventsToRedact = oldEventRows + + // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! + // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) + // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. + eventsToReplace = eventsToReplace.filter(ev => { + // Discord does not allow files, images, attachments, or videos to be edited. + if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { + return false + } + // Discord does not allow stickers to be edited. + if (ev.old.event_type === "m.sticker") { + return false + } + // Anything else is fair game. + return true + }) + + return {eventsToReplace, eventsToRedact, eventsToSend} +} + +module.exports.editMessage = editMessage diff --git a/d2m/discord-client.js b/d2m/discord-client.js index 91682bd..5e90d85 100644 --- a/d2m/discord-client.js +++ b/d2m/discord-client.js @@ -14,7 +14,7 @@ class DiscordClient { * @param {string} discordToken * @param {boolean} listen whether to set up the event listeners for OOYE to operate */ - constructor(discordToken, listen) { + constructor(discordToken, listen = true) { this.discordToken = discordToken this.snow = new SnowTransfer(discordToken) this.cloud = new CloudStorm(discordToken, { @@ -44,7 +44,9 @@ class DiscordClient { this.guilds = new Map() /** @type {Map>} */ this.guildChannelMap = new Map() - this.cloud.on("event", message => discordPackets.onPacket(this, message)) + if (listen) { + this.cloud.on("event", message => discordPackets.onPacket(this, message)) + } this.cloud.on("error", console.error) } } diff --git a/scripts/capture-message-update-events.js b/scripts/capture-message-update-events.js new file mode 100644 index 0000000..2ff9b49 --- /dev/null +++ b/scripts/capture-message-update-events.js @@ -0,0 +1,51 @@ +// @ts-check + +// **** +const interestingFields = ["author", "content", "edited_timestamp", "mentions", "attachments", "embeds", "type", "message_reference", "referenced_message", "sticker_items"] +// ***** + +function fieldToPresenceValue(field) { + if (field === undefined) return 0 + else if (field === null) return 1 + else if (Array.isArray(field) && field.length === 0) return 10 + else if (typeof field === "object" && Object.keys(field).length === 0) return 20 + else if (field === "") return 30 + else return 99 +} + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const config = require("../config") +const passthrough = require("../passthrough") + +const sync = new HeatSync({watchFS: false}) + +Object.assign(passthrough, {config, sync}) + +const DiscordClient = require("../d2m/discord-client", false) + +const discord = new DiscordClient(config.discordToken, false) +passthrough.discord = discord + +;(async () => { + await discord.cloud.connect() + console.log("Discord gateway started") + + const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) + discord.cloud.on("event", f) +})() + +const events = new sqlite("scripts/events.db") +const sql = "INSERT INTO \"update\" (json, " + interestingFields.join(", ") + ") VALUES (" + "?".repeat(interestingFields.length + 1).split("").join(", ") + ")" +console.log(sql) +const prepared = events.prepare(sql) + +/** @param {DiscordClient} discord */ +function onPacket(discord, event, unsubscribe) { + if (event.t === "MESSAGE_UPDATE") { + const data = [JSON.stringify(event.d), ...interestingFields.map(f => fieldToPresenceValue(event.d[f]))] + console.log(data) + prepared.run(...data) + } +} diff --git a/scripts/events.db b/scripts/events.db new file mode 100644 index 0000000000000000000000000000000000000000..c8f5bad88858b0ddc7a3b54ec36577c94791c001 GIT binary patch literal 8192 zcmeH~-EJH;6vuZ16-iJk?ojXCxuJz5^0&vM3elE`R*G6^7m13XiD&HHOfoZG%}1lC z>J@O&TONa_;T?Dp&g^bBOW8y5$w>2Euo@l8K~zy9e7^aOeWJ%OG;PoO8z z6X*%_1bPBJf&T}AA8!sn`1sbX;m_xdNpjatt!WnD8xNly+#emF(ftR92Pl|#1?bN9 zR&ZMTGT0g&d^bW*p2Fwb!^2%@Hf?j_t6=N#lhMIf2haZ0m-@1CWwXB1S*Uq!++>BT z8&gczTNN-q^JTrh*EEeuPdb}x3|x^odre<+F?H*w1&?YoadG9wu5x91joGr>#)}c_ zr>mPRJ#$qI@fK@**}mJ=<{O*84|aZqum0%?^aOeWJ%OG;PoO98-yrbj%Z;61isAg( z+}wN>%&uP%-VGYKyhUS^*ACv7x4|f&n6f<_?h!G~i$-vKW2;!)~zn6<0oYu)=DSd+DkRU%Hi%a8ay+=v8oO9S}m5R7^_Bgch1=2@4Vu zYRW^xg;bOfj5RbeO$jH0hni8$LMg(TC1qS<&9R2@8XWJ&Gb*70(@ri26mb6=+V2%|NIHX`jqzb_Sh<{qSB5RAd z&bk|HRJM5zjjps=Iavl?!qqg0f~R@mQkbe}n=2@%J_EY3X}|zB1Md;MNA-xZNQj7$ zeX7*zW{h()0nV_a)|v5gh13^C3(J`99v|DR?rIJjw{Wk|FTFA^O=AFm=i}?$AcHN1 zSUHC1fuRZ+Kmg?6;MF3FtNm-?I$lC zI`<_qD6QJ8cDK=;7p}g0R34R&VB&jGQbOGlf$xeF-bs@P4Dtd08xK>1zVm$m3EKjT z%qpiD!t}(zS<2%%dyuDw-Q^VY$~2Lkq8F7OoRPM6RcQ)PO7Os+n)D1k@>T5s9+*wr zB+t_LYNKW|Dn&$hIJi`BRwNu0FrDN+F?l?nYOYVvM_Ofo#WYY=Mu7&MgJt4DgiX~# zikNF9wl4L+1~m1yhyqLam@A@rB3gAL4~Z@{c2<=y zGR0czZwslzxlTx+F=qrP%rb)0P`E@pYmBxDBytyGBE(v~nCTQ`K2K^hs50af%rzAP NWX=`uviRRq)gLj?i?aX# literal 0 HcmV?d00001 From cae591e5fd63a7afe66ac93f617a7ff53e5c430c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 15 Aug 2023 17:20:31 +1200 Subject: [PATCH 087/200] script for capturing message update events --- d2m/actions/edit-message.js | 96 +---------------------- d2m/converters/edit-to-changes.js | 96 +++++++++++++++++++++++ d2m/discord-client.js | 6 +- scripts/capture-message-update-events.js | 51 ++++++++++++ scripts/events.db | Bin 0 -> 8192 bytes 5 files changed, 154 insertions(+), 95 deletions(-) create mode 100644 d2m/converters/edit-to-changes.js create mode 100644 scripts/capture-message-update-events.js create mode 100644 scripts/events.db diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 662e124..933267c 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -1,94 +1,5 @@ -// @ts-check - -const assert = require("assert") - -const passthrough = require("../../passthrough") -const { discord, sync, db } = passthrough -/** @type {import("../converters/message-to-event")} */ -const messageToEvent = sync.require("../converters/message-to-event") -/** @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").GatewayMessageCreateDispatchData} message - * @param {import("discord-api-types/v10").APIGuild} guild - */ -async function editMessage(message, guild) { - // Figure out what events we will be replacing - - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) - const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) - /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ - const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) - - // Figure out what we will be replacing them with - - const newEvents = await messageToEvent.messageToEvent(message, guild, api) - - // Match the new events to the old events - - /* - Rules: - + The events must have the same type. - + The events must have the same subtype. - Events will therefore be divided into three categories: - */ - /** 1. Events that are matched, and should be edited by sending another m.replace event */ - let eventsToReplace = [] - /** 2. Events that are present in the old version only, and should be blanked or redacted */ - let eventsToRedact = [] - /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ - let eventsToSend = [] - - // For each old event... - outer: while (newEvents.length) { - const newe = newEvents[0] - // Find a new event to pair it with... - let handled = false - for (let i = 0; i < oldEventRows.length; i++) { - const olde = oldEventRows[i] - if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { - // Found one! - // Set up the pairing - eventsToReplace.push({ - old: olde, - new: newe - }) - // These events have been handled now, so remove them from the source arrays - newEvents.shift() - oldEventRows.splice(i, 1) - // Go all the way back to the start of the next iteration of the outer loop - continue outer - } - } - // If we got this far, we could not pair it to an existing event, so it'll have to be a new one - eventsToSend.push(newe) - newEvents.shift() - } - // Anything remaining in oldEventRows is present in the old version only and should be redacted. - eventsToRedact = oldEventRows - - // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! - // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) - // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. - eventsToReplace = eventsToReplace.filter(ev => { - // Discord does not allow files, images, attachments, or videos to be edited. - if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { - return false - } - // Discord does not allow stickers to be edited. - if (ev.old.event_type === "m.sticker") { - return false - } - // Anything else is fair game. - return true - }) - - // Action time! +async function editMessage() { + // Action time! // 1. Replace all the things. @@ -113,6 +24,5 @@ async function editMessage(message, guild) { } return eventIDs -} -module.exports.editMessage = editMessage +{eventsToReplace, eventsToRedact, eventsToSend} diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js new file mode 100644 index 0000000..0dd084f --- /dev/null +++ b/d2m/converters/edit-to-changes.js @@ -0,0 +1,96 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("./message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") +/** @type {import("../actions/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").GatewayMessageCreateDispatchData} message + * IMPORTANT: This may not have all the normal fields! The API documentation doesn't provide possible types, just says it's all optional! + * Since I don't have a spec, I will have to capture some real traffic and add it as test cases... I hope they don't change anything later... + * @param {import("discord-api-types/v10").APIGuild} guild + */ +async function editToChanges(message, guild) { + // Figure out what events we will be replacing + + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) + const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ + const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) + + // Figure out what we will be replacing them with + + const newEvents = await messageToEvent.messageToEvent(message, guild, api) + + // Match the new events to the old events + + /* + Rules: + + The events must have the same type. + + The events must have the same subtype. + Events will therefore be divided into three categories: + */ + /** 1. Events that are matched, and should be edited by sending another m.replace event */ + let eventsToReplace = [] + /** 2. Events that are present in the old version only, and should be blanked or redacted */ + let eventsToRedact = [] + /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ + let eventsToSend = [] + + // For each old event... + outer: while (newEvents.length) { + const newe = newEvents[0] + // Find a new event to pair it with... + let handled = false + for (let i = 0; i < oldEventRows.length; i++) { + const olde = oldEventRows[i] + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { + // Found one! + // Set up the pairing + eventsToReplace.push({ + old: olde, + new: newe + }) + // These events have been handled now, so remove them from the source arrays + newEvents.shift() + oldEventRows.splice(i, 1) + // Go all the way back to the start of the next iteration of the outer loop + continue outer + } + } + // If we got this far, we could not pair it to an existing event, so it'll have to be a new one + eventsToSend.push(newe) + newEvents.shift() + } + // Anything remaining in oldEventRows is present in the old version only and should be redacted. + eventsToRedact = oldEventRows + + // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! + // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) + // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. + eventsToReplace = eventsToReplace.filter(ev => { + // Discord does not allow files, images, attachments, or videos to be edited. + if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { + return false + } + // Discord does not allow stickers to be edited. + if (ev.old.event_type === "m.sticker") { + return false + } + // Anything else is fair game. + return true + }) + + return {eventsToReplace, eventsToRedact, eventsToSend} +} + +module.exports.editMessage = editMessage diff --git a/d2m/discord-client.js b/d2m/discord-client.js index 91682bd..5e90d85 100644 --- a/d2m/discord-client.js +++ b/d2m/discord-client.js @@ -14,7 +14,7 @@ class DiscordClient { * @param {string} discordToken * @param {boolean} listen whether to set up the event listeners for OOYE to operate */ - constructor(discordToken, listen) { + constructor(discordToken, listen = true) { this.discordToken = discordToken this.snow = new SnowTransfer(discordToken) this.cloud = new CloudStorm(discordToken, { @@ -44,7 +44,9 @@ class DiscordClient { this.guilds = new Map() /** @type {Map>} */ this.guildChannelMap = new Map() - this.cloud.on("event", message => discordPackets.onPacket(this, message)) + if (listen) { + this.cloud.on("event", message => discordPackets.onPacket(this, message)) + } this.cloud.on("error", console.error) } } diff --git a/scripts/capture-message-update-events.js b/scripts/capture-message-update-events.js new file mode 100644 index 0000000..2ff9b49 --- /dev/null +++ b/scripts/capture-message-update-events.js @@ -0,0 +1,51 @@ +// @ts-check + +// **** +const interestingFields = ["author", "content", "edited_timestamp", "mentions", "attachments", "embeds", "type", "message_reference", "referenced_message", "sticker_items"] +// ***** + +function fieldToPresenceValue(field) { + if (field === undefined) return 0 + else if (field === null) return 1 + else if (Array.isArray(field) && field.length === 0) return 10 + else if (typeof field === "object" && Object.keys(field).length === 0) return 20 + else if (field === "") return 30 + else return 99 +} + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const config = require("../config") +const passthrough = require("../passthrough") + +const sync = new HeatSync({watchFS: false}) + +Object.assign(passthrough, {config, sync}) + +const DiscordClient = require("../d2m/discord-client", false) + +const discord = new DiscordClient(config.discordToken, false) +passthrough.discord = discord + +;(async () => { + await discord.cloud.connect() + console.log("Discord gateway started") + + const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) + discord.cloud.on("event", f) +})() + +const events = new sqlite("scripts/events.db") +const sql = "INSERT INTO \"update\" (json, " + interestingFields.join(", ") + ") VALUES (" + "?".repeat(interestingFields.length + 1).split("").join(", ") + ")" +console.log(sql) +const prepared = events.prepare(sql) + +/** @param {DiscordClient} discord */ +function onPacket(discord, event, unsubscribe) { + if (event.t === "MESSAGE_UPDATE") { + const data = [JSON.stringify(event.d), ...interestingFields.map(f => fieldToPresenceValue(event.d[f]))] + console.log(data) + prepared.run(...data) + } +} diff --git a/scripts/events.db b/scripts/events.db new file mode 100644 index 0000000000000000000000000000000000000000..c8f5bad88858b0ddc7a3b54ec36577c94791c001 GIT binary patch literal 8192 zcmeH~-EJH;6vuZ16-iJk?ojXCxuJz5^0&vM3elE`R*G6^7m13XiD&HHOfoZG%}1lC z>J@O&TONa_;T?Dp&g^bBOW8y5$w>2Euo@l8K~zy9e7^aOeWJ%OG;PoO8z z6X*%_1bPBJf&T}AA8!sn`1sbX;m_xdNpjatt!WnD8xNly+#emF(ftR92Pl|#1?bN9 zR&ZMTGT0g&d^bW*p2Fwb!^2%@Hf?j_t6=N#lhMIf2haZ0m-@1CWwXB1S*Uq!++>BT z8&gczTNN-q^JTrh*EEeuPdb}x3|x^odre<+F?H*w1&?YoadG9wu5x91joGr>#)}c_ zr>mPRJ#$qI@fK@**}mJ=<{O*84|aZqum0%?^aOeWJ%OG;PoO98-yrbj%Z;61isAg( z+}wN>%&uP%-VGYKyhUS^*ACv7x4|f&n6f<_?h!G~i$-vKW2;!)~zn6<0oYu)=DSd+DkRU%Hi%a8ay+=v8oO9S}m5R7^_Bgch1=2@4Vu zYRW^xg;bOfj5RbeO$jH0hni8$LMg(TC1qS<&9R2@8XWJ&Gb*70(@ri26mb6=+V2%|NIHX`jqzb_Sh<{qSB5RAd z&bk|HRJM5zjjps=Iavl?!qqg0f~R@mQkbe}n=2@%J_EY3X}|zB1Md;MNA-xZNQj7$ zeX7*zW{h()0nV_a)|v5gh13^C3(J`99v|DR?rIJjw{Wk|FTFA^O=AFm=i}?$AcHN1 zSUHC1fuRZ+Kmg?6;MF3FtNm-?I$lC zI`<_qD6QJ8cDK=;7p}g0R34R&VB&jGQbOGlf$xeF-bs@P4Dtd08xK>1zVm$m3EKjT z%qpiD!t}(zS<2%%dyuDw-Q^VY$~2Lkq8F7OoRPM6RcQ)PO7Os+n)D1k@>T5s9+*wr zB+t_LYNKW|Dn&$hIJi`BRwNu0FrDN+F?l?nYOYVvM_Ofo#WYY=Mu7&MgJt4DgiX~# zikNF9wl4L+1~m1yhyqLam@A@rB3gAL4~Z@{c2<=y zGR0czZwslzxlTx+F=qrP%rb)0P`E@pYmBxDBytyGBE(v~nCTQ`K2K^hs50af%rzAP NWX=`uviRRq)gLj?i?aX# literal 0 HcmV?d00001 From de2ede5f0d313f57d315c7939071feb10a064c5f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 16 Aug 2023 17:03:05 +1200 Subject: [PATCH 088/200] getting edits closer to working --- .vscode/settings.json | 2 + d2m/actions/register-user.js | 1 + d2m/converters/edit-to-changes.js | 47 ++++++- d2m/converters/edit-to-changes.test.js | 66 ++++++++++ d2m/converters/message-to-event.js | 11 ++ db/ooye.db | Bin 360448 -> 360448 bytes package-lock.json | 7 +- package.json | 1 + test/data.js | 166 ++++++++++++++++++------- test/test.js | 1 + types.d.ts | 8 ++ 11 files changed, 259 insertions(+), 51 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 d2m/converters/edit-to-changes.test.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index ef6045a..beb24bd 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -59,6 +59,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 + * @param {string} roomID * @returns mxid */ async function ensureSimJoined(user, roomID) { diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 0dd084f..87e769b 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -9,7 +9,7 @@ const messageToEvent = sync.require("../converters/message-to-event") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("../actions/register-user")} */ -const registerUser = sync.require("./register-user") +const registerUser = sync.require("../actions/register-user") /** @type {import("../actions/create-room")} */ const createRoom = sync.require("../actions/create-room") @@ -22,7 +22,7 @@ const createRoom = sync.require("../actions/create-room") async function editToChanges(message, guild) { // Figure out what events we will be replacing - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) @@ -37,7 +37,7 @@ async function editToChanges(message, guild) { Rules: + The events must have the same type. + The events must have the same subtype. - Events will therefore be divided into three categories: + Events will therefore be divided into four categories: */ /** 1. Events that are matched, and should be edited by sending another m.replace event */ let eventsToReplace = [] @@ -45,12 +45,12 @@ async function editToChanges(message, guild) { let eventsToRedact = [] /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ let eventsToSend = [] + // 4. Events that are matched and have definitely not changed, so they don't need to be edited or replaced at all. This is represented as nothing. // For each old event... outer: while (newEvents.length) { const newe = newEvents[0] // Find a new event to pair it with... - let handled = false for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { @@ -76,7 +76,7 @@ async function editToChanges(message, guild) { // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) - // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. + // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. (This is category 4 mentioned above.) Everything remaining *may* have changed. eventsToReplace = eventsToReplace.filter(ev => { // Discord does not allow files, images, attachments, or videos to be edited. if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { @@ -90,7 +90,42 @@ async function editToChanges(message, guild) { return true }) + // Removing unnecessary properties before returning + eventsToRedact = eventsToRedact.map(e => e.event_id) + eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.new)})) + return {eventsToReplace, eventsToRedact, eventsToSend} } -module.exports.editMessage = editMessage +/** + * @template T + * @param {string} oldID + * @param {T} content + * @returns {import("../../types").Event.ReplacementContent} content + */ +function eventToReplacementEvent(oldID, content) { + const newContent = { + ...content, + "m.mentions": {}, + "m.new_content": { + ...content + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: oldID + } + } + if (typeof newContent.body === "string") { + newContent.body = "* " + newContent.body + } + if (typeof newContent.formatted_body === "string") { + newContent.formatted_body = "* " + newContent.formatted_body + } + delete newContent["m.new_content"]["$type"] + // Client-Server API spec 11.37.3: Any m.relates_to property within m.new_content is ignored. + delete newContent["m.new_content"]["m.relates_to"] + return newContent +} + +module.exports.editToChanges = editToChanges +module.exports.eventToReplacementEvent = eventToReplacementEvent diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js new file mode 100644 index 0000000..b3e6e0c --- /dev/null +++ b/d2m/converters/edit-to-changes.test.js @@ -0,0 +1,66 @@ +// @ts-check + +const {test} = require("supertape") +const {editToChanges} = require("./edit-to-changes") +const data = require("../../test/data") +const Ty = require("../../types") + +test("edit2changes: bot response", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", + new: { + $type: "m.room.message", + msgtype: "m.text", + body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: '* :ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: Copy Discord's behaviour by not re-notifying anyone that an *edit occurred* + }, + // *** Replaced With: *** + "m.new_content": { + msgtype: "m.text", + body: ":ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: ':ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: This should contain the mentions for the final version of the event + "user_ids": ["@cadence:cadence.moe"] + } + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY" + } + } + }]) +}) + +test("edit2changes: edit of reply to skull webp attachment with content", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", + new: { + $type: "m.room.message", + // TODO: read "edits of replies" in the spec!!! + msgtype: "m.text", + body: "* Edit", + "m.mentions": {}, + "m.new_content": { + msgtype: "m.text", + body: "Edit", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" + } + // TODO: read "edits of replies" in the spec!!! + } + }]) +}) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 49a387a..a2c4915 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -15,6 +15,7 @@ const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) function getDiscordParseCallbacks(message, useHTML) { return { + /** @param {{id: string, type: "discordUser"}} node */ user: node => { const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) const username = message.mentions.find(ment => ment.id === node.id)?.username || node.id @@ -24,6 +25,7 @@ function getDiscordParseCallbacks(message, useHTML) { return `@${username}:` } }, + /** @param {{id: string, type: "discordChannel"}} node */ channel: node => { const {room_id, name, nick} = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) if (room_id && useHTML) { @@ -32,6 +34,15 @@ function getDiscordParseCallbacks(message, useHTML) { return `#${nick || name}` } }, + /** @param {{animated: boolean, name: string, id: string, type: "discordEmoji"}} node */ + emoji: node => { + if (useHTML) { + // TODO: upload the emoji and actually use the right mxc!! + return `:${node.name}:` + } else { + return `:${node.name}:` + } + }, role: node => "@&" + node.id, everyone: node => diff --git a/db/ooye.db b/db/ooye.db index 33c5ef8abaf5ad514d187a005124f5526b054c64..a916aee82643b6a8873edfde0eaf2fe46c59b001 100644 GIT binary patch delta 1055 zcmb7?%TE(g6vpS?J5!1+Z3#*Mi8hf0jH%3>>CCjm2w0}jmePhdl}HPeH>LE!qj6Cd zAgBu}G2_OVCM52#r0PI0(SJZ(8WS{bjK;Vi(JkTxwb8I*adJ;ia)0-`-*=M9;$*UT z^=!c&K@i0dvu*6BBt0l}_mjb%{3k*zW==~c?WwW!WMt#-evR(cB| zL+}P|qbM>_pQuMvkA5f5Mir4Z^po5LE4p5Iom^H;s}6{RHG2P?0iIEAq*b!IR~yxu z;ODj8{SpXlsdTO6zXw#J5SVke%_o|0-}t=KguUXJ(AP6MJ`#_+nuDFT&fypna+(4S ztzr+xX3Wq$!|^m@rWrwL;9ZdnvQS!>rE0SW``qcIRO#zun1Xgm^*j7Ec(C4Cmu$PY50 z9AzJVgrw}+0^5%f2bQHgKg3WuEq-XFMB z`k^-zC_SmGBlN3!qb{SX%NEkKPMBQOs)H9xS**^lIDwT?Y9AH*j@gM72`p=|VwRy9 z3yWEPa#54x4-Gb0JwA?`8)n7&=+I2FFA?Bk?vdD7hsTP$hFMQ6K4sqF+T^K4241y^>K5edvea^Ywil zo;j=6K{vT+Cek{DvelQyV delta 380 zcmZo@5Nl`TN0*H-(SbCy{Ipg(>2}|TTc=@*g1sJ?1^C(DdR$I`=zj=$k z?*V1!yIh#2&ppR% zzghS5%{`1Wrn7Hg)E8jh!tBW~gJ}zsGUEouY(}+-3xl@nZe)~UW^NQRn|$!O>h!u* zjB?X;=QH_lp8J4_WxMwd#+}QzvrJ(A!@sS7iG2a96wqYj?K}d^U-%8U=;Qp$GTR)O hSqiqZJYbnFuzmIc=1lwTEC#H9_}c_nmI<&H002?(bxQyM diff --git a/package-lock.json b/package-lock.json index e8b6aeb..6be3b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@types/node-fetch": "^2.6.3", "c8": "^8.0.1", "cross-env": "^7.0.3", + "discord-api-types": "^0.37.53", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" } @@ -1044,9 +1045,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.47", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.47.tgz", - "integrity": "sha512-rNif8IAv6duS2z47BMXq/V9kkrLfkAoiwpFY3sLxxbyKprk065zqf3HLTg4bEoxRSmi+Lhc7yqGDrG8C3j8GFA==" + "version": "0.37.53", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.53.tgz", + "integrity": "sha512-N6uUgv50OyP981Mfxrrt0uxcqiaNr0BDaQIoqfk+3zM2JpZtwU9v7ce1uaFAP53b2xSDvcbrk80Kneui6XJgGg==" }, "node_modules/discord-markdown": { "version": "2.4.1", diff --git a/package.json b/package.json index dd6f55d..2437aba 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@types/node-fetch": "^2.6.3", "c8": "^8.0.1", "cross-env": "^7.0.3", + "discord-api-types": "^0.37.53", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, diff --git a/test/data.js b/test/data.js index a5ca95e..e23d42f 100644 --- a/test/data.js +++ b/test/data.js @@ -88,7 +88,8 @@ module.exports = { rules_channel_id: null, name: "Psychonauts 3", max_stage_video_channel_users: 300, - system_channel_flags: 0|0 + system_channel_flags: 0|0, + safety_alerts_channel_id: null } }, member: { @@ -744,6 +745,128 @@ module.exports = { attachments: [], guild_id: "112760669178241024" }, + sticker: { + id: "1106366167788044450", + type: 0, + content: "can have attachments too", + channel_id: "122155380120748034", + author: { + id: "113340068197859328", + username: "Cookie 🍪", + global_name: null, + display_name: null, + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "7766", + public_flags: 128, + avatar_decoration: null + }, + attachments: [{ + id: "1106366167486038016", + filename: "image.png", + size: 127373, + url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", + width: 333, + height: 287, + content_type: "image/png" + }], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-05-11T23:44:09.690000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + sticker_items: [{ + id: "1106323941183717586", + format_type: 1, + name: "pomu puff" + }] + } + }, + message_update: { + bot_response: { + attachments: [], + author: { + avatar: "d14f47194b6ebe4da2e18a56fc6dacfd", + avatar_decoration: null, + bot: true, + discriminator: "9703", + global_name: null, + id: "771520384671416320", + public_flags: 0, + username: "Bojack Horseman" + }, + channel_id: "160197704226439168", + components: [], + content: "<:ae_botrac4r:551636841284108289> @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit <:bn_re:362741439211503616> to reroll.", + edited_timestamp: "2023-08-16T03:06:07.128980+00:00", + embeds: [], + flags: 0, + guild_id: "112760669178241024", + id: "1141206225632112650", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2020-10-29T23:55:31.277000+00:00", + mute: false, + nick: "Olmec", + pending: false, + premium_since: null, + roles: [ + "112767366235959296", + "118924814567211009", + "392141548932038658", + "1123460940935991296", + "326409028601249793", + "114526764860047367", + "323966487763353610", + "1107404526870335629", + "1040735082610167858" + ] + }, + mention_everyone: false, + mention_roles: [], + mentions: [ + { + avatar: "8757ad3edee9541427edd7f817ae2f5c", + avatar_decoration: null, + bot: true, + discriminator: "8559", + global_name: null, + id: "353703396483661824", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2017-11-30T04:27:20.749000+00:00", + mute: false, + nick: null, + pending: false, + premium_since: null, + roles: [ + "112767366235959296", + "118924814567211009", + "289671295359254529", + "114526764860047367", + "1040735082610167858" + ] + }, + public_flags: 0, + username: "botrac4r" + } + ], + pinned: false, + timestamp: "2023-08-16T03:06:06.777000+00:00", + tts: false, + type: 0 + }, edit_of_reply_to_skull_webp_attachment_with_content: { type: 19, tts: false, @@ -881,47 +1004,6 @@ module.exports = { } ], guild_id: "112760669178241024" - }, - sticker: { - id: "1106366167788044450", - type: 0, - content: "can have attachments too", - channel_id: "122155380120748034", - author: { - id: "113340068197859328", - username: "Cookie 🍪", - global_name: null, - display_name: null, - avatar: "b48302623a12bc7c59a71328f72ccb39", - discriminator: "7766", - public_flags: 128, - avatar_decoration: null - }, - attachments: [{ - id: "1106366167486038016", - filename: "image.png", - size: 127373, - url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", - proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", - width: 333, - height: 287, - content_type: "image/png" - }], - embeds: [], - mentions: [], - mention_roles: [], - pinned: false, - mention_everyone: false, - tts: false, - timestamp: "2023-05-11T23:44:09.690000+00:00", - edited_timestamp: null, - flags: 0, - components: [], - sticker_items: [{ - id: "1106323941183717586", - format_type: 1, - name: "pomu puff" - }] } } } diff --git a/test/test.js b/test/test.js index 5805d09..c6ee064 100644 --- a/test/test.js +++ b/test/test.js @@ -15,6 +15,7 @@ require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") +require("../d2m/converters/edit-to-changes.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") diff --git a/types.d.ts b/types.d.ts index eeb4b75..76d3bd1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -38,6 +38,14 @@ namespace Event { event_id: string } + export type ReplacementContent = T & { + "m.new_content": T + "m.relates_to": { + rel_type: string // "m.replace" + event_id: string + } + } + export type BaseStateEvent = { type: string room_id: string From b1ca71f37c2d8a20e534aac22c925e17d176e566 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 16 Aug 2023 17:03:05 +1200 Subject: [PATCH 089/200] getting edits closer to working --- .vscode/settings.json | 2 + d2m/actions/register-user.js | 1 + d2m/converters/edit-to-changes.js | 47 ++++++- d2m/converters/edit-to-changes.test.js | 66 ++++++++++ d2m/converters/message-to-event.js | 11 ++ package-lock.json | 7 +- package.json | 1 + test/data.js | 166 ++++++++++++++++++------- test/test.js | 1 + types.d.ts | 8 ++ 10 files changed, 259 insertions(+), 51 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 d2m/converters/edit-to-changes.test.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index ef6045a..beb24bd 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -59,6 +59,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 + * @param {string} roomID * @returns mxid */ async function ensureSimJoined(user, roomID) { diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 0dd084f..87e769b 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -9,7 +9,7 @@ const messageToEvent = sync.require("../converters/message-to-event") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("../actions/register-user")} */ -const registerUser = sync.require("./register-user") +const registerUser = sync.require("../actions/register-user") /** @type {import("../actions/create-room")} */ const createRoom = sync.require("../actions/create-room") @@ -22,7 +22,7 @@ const createRoom = sync.require("../actions/create-room") async function editToChanges(message, guild) { // Figure out what events we will be replacing - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) @@ -37,7 +37,7 @@ async function editToChanges(message, guild) { Rules: + The events must have the same type. + The events must have the same subtype. - Events will therefore be divided into three categories: + Events will therefore be divided into four categories: */ /** 1. Events that are matched, and should be edited by sending another m.replace event */ let eventsToReplace = [] @@ -45,12 +45,12 @@ async function editToChanges(message, guild) { let eventsToRedact = [] /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ let eventsToSend = [] + // 4. Events that are matched and have definitely not changed, so they don't need to be edited or replaced at all. This is represented as nothing. // For each old event... outer: while (newEvents.length) { const newe = newEvents[0] // Find a new event to pair it with... - let handled = false for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { @@ -76,7 +76,7 @@ async function editToChanges(message, guild) { // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) - // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. + // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. (This is category 4 mentioned above.) Everything remaining *may* have changed. eventsToReplace = eventsToReplace.filter(ev => { // Discord does not allow files, images, attachments, or videos to be edited. if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { @@ -90,7 +90,42 @@ async function editToChanges(message, guild) { return true }) + // Removing unnecessary properties before returning + eventsToRedact = eventsToRedact.map(e => e.event_id) + eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.new)})) + return {eventsToReplace, eventsToRedact, eventsToSend} } -module.exports.editMessage = editMessage +/** + * @template T + * @param {string} oldID + * @param {T} content + * @returns {import("../../types").Event.ReplacementContent} content + */ +function eventToReplacementEvent(oldID, content) { + const newContent = { + ...content, + "m.mentions": {}, + "m.new_content": { + ...content + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: oldID + } + } + if (typeof newContent.body === "string") { + newContent.body = "* " + newContent.body + } + if (typeof newContent.formatted_body === "string") { + newContent.formatted_body = "* " + newContent.formatted_body + } + delete newContent["m.new_content"]["$type"] + // Client-Server API spec 11.37.3: Any m.relates_to property within m.new_content is ignored. + delete newContent["m.new_content"]["m.relates_to"] + return newContent +} + +module.exports.editToChanges = editToChanges +module.exports.eventToReplacementEvent = eventToReplacementEvent diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js new file mode 100644 index 0000000..b3e6e0c --- /dev/null +++ b/d2m/converters/edit-to-changes.test.js @@ -0,0 +1,66 @@ +// @ts-check + +const {test} = require("supertape") +const {editToChanges} = require("./edit-to-changes") +const data = require("../../test/data") +const Ty = require("../../types") + +test("edit2changes: bot response", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", + new: { + $type: "m.room.message", + msgtype: "m.text", + body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: '* :ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: Copy Discord's behaviour by not re-notifying anyone that an *edit occurred* + }, + // *** Replaced With: *** + "m.new_content": { + msgtype: "m.text", + body: ":ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: ':ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: This should contain the mentions for the final version of the event + "user_ids": ["@cadence:cadence.moe"] + } + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY" + } + } + }]) +}) + +test("edit2changes: edit of reply to skull webp attachment with content", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", + new: { + $type: "m.room.message", + // TODO: read "edits of replies" in the spec!!! + msgtype: "m.text", + body: "* Edit", + "m.mentions": {}, + "m.new_content": { + msgtype: "m.text", + body: "Edit", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" + } + // TODO: read "edits of replies" in the spec!!! + } + }]) +}) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 49a387a..a2c4915 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -15,6 +15,7 @@ const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) function getDiscordParseCallbacks(message, useHTML) { return { + /** @param {{id: string, type: "discordUser"}} node */ user: node => { const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) const username = message.mentions.find(ment => ment.id === node.id)?.username || node.id @@ -24,6 +25,7 @@ function getDiscordParseCallbacks(message, useHTML) { return `@${username}:` } }, + /** @param {{id: string, type: "discordChannel"}} node */ channel: node => { const {room_id, name, nick} = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) if (room_id && useHTML) { @@ -32,6 +34,15 @@ function getDiscordParseCallbacks(message, useHTML) { return `#${nick || name}` } }, + /** @param {{animated: boolean, name: string, id: string, type: "discordEmoji"}} node */ + emoji: node => { + if (useHTML) { + // TODO: upload the emoji and actually use the right mxc!! + return `:${node.name}:` + } else { + return `:${node.name}:` + } + }, role: node => "@&" + node.id, everyone: node => diff --git a/package-lock.json b/package-lock.json index e8b6aeb..6be3b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@types/node-fetch": "^2.6.3", "c8": "^8.0.1", "cross-env": "^7.0.3", + "discord-api-types": "^0.37.53", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" } @@ -1044,9 +1045,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.47", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.47.tgz", - "integrity": "sha512-rNif8IAv6duS2z47BMXq/V9kkrLfkAoiwpFY3sLxxbyKprk065zqf3HLTg4bEoxRSmi+Lhc7yqGDrG8C3j8GFA==" + "version": "0.37.53", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.53.tgz", + "integrity": "sha512-N6uUgv50OyP981Mfxrrt0uxcqiaNr0BDaQIoqfk+3zM2JpZtwU9v7ce1uaFAP53b2xSDvcbrk80Kneui6XJgGg==" }, "node_modules/discord-markdown": { "version": "2.4.1", diff --git a/package.json b/package.json index dd6f55d..2437aba 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@types/node-fetch": "^2.6.3", "c8": "^8.0.1", "cross-env": "^7.0.3", + "discord-api-types": "^0.37.53", "supertape": "^8.3.0", "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, diff --git a/test/data.js b/test/data.js index a5ca95e..e23d42f 100644 --- a/test/data.js +++ b/test/data.js @@ -88,7 +88,8 @@ module.exports = { rules_channel_id: null, name: "Psychonauts 3", max_stage_video_channel_users: 300, - system_channel_flags: 0|0 + system_channel_flags: 0|0, + safety_alerts_channel_id: null } }, member: { @@ -744,6 +745,128 @@ module.exports = { attachments: [], guild_id: "112760669178241024" }, + sticker: { + id: "1106366167788044450", + type: 0, + content: "can have attachments too", + channel_id: "122155380120748034", + author: { + id: "113340068197859328", + username: "Cookie 🍪", + global_name: null, + display_name: null, + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "7766", + public_flags: 128, + avatar_decoration: null + }, + attachments: [{ + id: "1106366167486038016", + filename: "image.png", + size: 127373, + url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", + width: 333, + height: 287, + content_type: "image/png" + }], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-05-11T23:44:09.690000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + sticker_items: [{ + id: "1106323941183717586", + format_type: 1, + name: "pomu puff" + }] + } + }, + message_update: { + bot_response: { + attachments: [], + author: { + avatar: "d14f47194b6ebe4da2e18a56fc6dacfd", + avatar_decoration: null, + bot: true, + discriminator: "9703", + global_name: null, + id: "771520384671416320", + public_flags: 0, + username: "Bojack Horseman" + }, + channel_id: "160197704226439168", + components: [], + content: "<:ae_botrac4r:551636841284108289> @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit <:bn_re:362741439211503616> to reroll.", + edited_timestamp: "2023-08-16T03:06:07.128980+00:00", + embeds: [], + flags: 0, + guild_id: "112760669178241024", + id: "1141206225632112650", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2020-10-29T23:55:31.277000+00:00", + mute: false, + nick: "Olmec", + pending: false, + premium_since: null, + roles: [ + "112767366235959296", + "118924814567211009", + "392141548932038658", + "1123460940935991296", + "326409028601249793", + "114526764860047367", + "323966487763353610", + "1107404526870335629", + "1040735082610167858" + ] + }, + mention_everyone: false, + mention_roles: [], + mentions: [ + { + avatar: "8757ad3edee9541427edd7f817ae2f5c", + avatar_decoration: null, + bot: true, + discriminator: "8559", + global_name: null, + id: "353703396483661824", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2017-11-30T04:27:20.749000+00:00", + mute: false, + nick: null, + pending: false, + premium_since: null, + roles: [ + "112767366235959296", + "118924814567211009", + "289671295359254529", + "114526764860047367", + "1040735082610167858" + ] + }, + public_flags: 0, + username: "botrac4r" + } + ], + pinned: false, + timestamp: "2023-08-16T03:06:06.777000+00:00", + tts: false, + type: 0 + }, edit_of_reply_to_skull_webp_attachment_with_content: { type: 19, tts: false, @@ -881,47 +1004,6 @@ module.exports = { } ], guild_id: "112760669178241024" - }, - sticker: { - id: "1106366167788044450", - type: 0, - content: "can have attachments too", - channel_id: "122155380120748034", - author: { - id: "113340068197859328", - username: "Cookie 🍪", - global_name: null, - display_name: null, - avatar: "b48302623a12bc7c59a71328f72ccb39", - discriminator: "7766", - public_flags: 128, - avatar_decoration: null - }, - attachments: [{ - id: "1106366167486038016", - filename: "image.png", - size: 127373, - url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", - proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", - width: 333, - height: 287, - content_type: "image/png" - }], - embeds: [], - mentions: [], - mention_roles: [], - pinned: false, - mention_everyone: false, - tts: false, - timestamp: "2023-05-11T23:44:09.690000+00:00", - edited_timestamp: null, - flags: 0, - components: [], - sticker_items: [{ - id: "1106323941183717586", - format_type: 1, - name: "pomu puff" - }] } } } diff --git a/test/test.js b/test/test.js index 5805d09..c6ee064 100644 --- a/test/test.js +++ b/test/test.js @@ -15,6 +15,7 @@ require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") +require("../d2m/converters/edit-to-changes.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") diff --git a/types.d.ts b/types.d.ts index eeb4b75..76d3bd1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -38,6 +38,14 @@ namespace Event { event_id: string } + export type ReplacementContent = T & { + "m.new_content": T + "m.relates_to": { + rel_type: string // "m.replace" + event_id: string + } + } + export type BaseStateEvent = { type: string room_id: string From 749f721aac6637df1a01504c2cbfcb15c005755f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 16 Aug 2023 20:44:38 +1200 Subject: [PATCH 090/200] record more update events --- d2m/actions/send-message.js | 2 +- d2m/converters/edit-to-changes.js | 47 +++++----- d2m/converters/edit-to-changes.test.js | 9 +- d2m/converters/message-to-event.js | 20 +++- d2m/converters/message-to-event.test.js | 116 +++++++++++++++--------- scripts/events.db | Bin 8192 -> 98304 bytes 6 files changed, 122 insertions(+), 72 deletions(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 258efcf..cf87d35 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message, guild, api) + const events = await messageToEvent.messageToEvent(message, guild, {}, {api}) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 87e769b..4afa3ce 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -29,7 +29,9 @@ async function editToChanges(message, guild) { // Figure out what we will be replacing them with - const newEvents = await messageToEvent.messageToEvent(message, guild, api) + const newFallbackContent = await messageToEvent.messageToEvent(message, guild, {includeEditFallbackStar: true}, {api}) + const newInnerContent = await messageToEvent.messageToEvent(message, guild, {includeReplyFallback: false}, {api}) + assert.ok(newFallbackContent.length === newInnerContent.length) // Match the new events to the old events @@ -47,21 +49,27 @@ async function editToChanges(message, guild) { let eventsToSend = [] // 4. Events that are matched and have definitely not changed, so they don't need to be edited or replaced at all. This is represented as nothing. + function shift() { + newFallbackContent.shift() + newInnerContent.shift() + } + // For each old event... - outer: while (newEvents.length) { - const newe = newEvents[0] + outer: while (newFallbackContent.length) { + const newe = newFallbackContent[0] // Find a new event to pair it with... for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] - if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype ?? null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to // Found one! // Set up the pairing eventsToReplace.push({ old: olde, - new: newe + newFallbackContent: newFallbackContent[0], + newInnerContent: newInnerContent[0] }) // These events have been handled now, so remove them from the source arrays - newEvents.shift() + shift() oldEventRows.splice(i, 1) // Go all the way back to the start of the next iteration of the outer loop continue outer @@ -69,7 +77,7 @@ async function editToChanges(message, guild) { } // If we got this far, we could not pair it to an existing event, so it'll have to be a new one eventsToSend.push(newe) - newEvents.shift() + shift() } // Anything remaining in oldEventRows is present in the old version only and should be redacted. eventsToRedact = oldEventRows @@ -92,7 +100,7 @@ async function editToChanges(message, guild) { // Removing unnecessary properties before returning eventsToRedact = eventsToRedact.map(e => e.event_id) - eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.new)})) + eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) return {eventsToReplace, eventsToRedact, eventsToSend} } @@ -100,31 +108,26 @@ async function editToChanges(message, guild) { /** * @template T * @param {string} oldID - * @param {T} content + * @param {T} newFallbackContent + * @param {T} newInnerContent * @returns {import("../../types").Event.ReplacementContent} content */ -function eventToReplacementEvent(oldID, content) { - const newContent = { - ...content, +function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) { + const content = { + ...newFallbackContent, "m.mentions": {}, "m.new_content": { - ...content + ...newInnerContent }, "m.relates_to": { rel_type: "m.replace", event_id: oldID } } - if (typeof newContent.body === "string") { - newContent.body = "* " + newContent.body - } - if (typeof newContent.formatted_body === "string") { - newContent.formatted_body = "* " + newContent.formatted_body - } - delete newContent["m.new_content"]["$type"] + delete content["m.new_content"]["$type"] // Client-Server API spec 11.37.3: Any m.relates_to property within m.new_content is ignored. - delete newContent["m.new_content"]["m.relates_to"] - return newContent + delete content["m.new_content"]["m.relates_to"] + return content } module.exports.editToChanges = editToChanges diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index b3e6e0c..f6ecc8d 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -47,9 +47,13 @@ test("edit2changes: edit of reply to skull webp attachment with content", async oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", new: { $type: "m.room.message", - // TODO: read "edits of replies" in the spec!!! msgtype: "m.text", - body: "* Edit", + body: "> Extremity: Image\n\n* Edit", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to Extremity' + + '
    Image
    ' + + '* Edit', "m.mentions": {}, "m.new_content": { msgtype: "m.text", @@ -60,7 +64,6 @@ test("edit2changes: edit of reply to skull webp attachment with content", async rel_type: "m.replace", event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" } - // TODO: read "edits of replies" in the spec!!! } }]) }) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index a2c4915..c128595 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -55,9 +55,12 @@ function getDiscordParseCallbacks(message, useHTML) { /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild - * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API + * @param {{includeReplyFallback?: boolean, includeEditFallbackStar?: boolean}} options default values: + * - includeReplyFallback: true + * - includeEditFallbackStar: false + * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API */ -async function messageToEvent(message, guild, api) { +async function messageToEvent(message, guild, options = {}, di) { const events = [] /** @@ -99,7 +102,7 @@ async function messageToEvent(message, guild, api) { } if (repliedToEventOriginallyFromMatrix) { // Need to figure out who sent that event... - const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) + const event = await di.api.getEvent(repliedToEventRoomId, repliedToEventId) repliedToEventSenderMxid = event.sender // Need to add the sender to m.mentions addMention(repliedToEventSenderMxid) @@ -133,7 +136,7 @@ async function messageToEvent(message, guild, api) { if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { const writtenMentionsText = matches.map(m => m[1].toLowerCase()) const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) - const {joined} = await api.getJoinedMembers(roomID) + const {joined} = await di.api.getJoinedMembers(roomID) for (const [mxid, member] of Object.entries(joined)) { if (!userRegex.some(rx => mxid.match(rx))) { const localpart = mxid.match(/@([^:]*)/) @@ -143,8 +146,15 @@ async function messageToEvent(message, guild, api) { } } + // Star * prefix for fallback edits + if (options.includeEditFallbackStar) { + body = "* " + body + html = "* " + html + } + // Fallback body/formatted_body for replies - if (repliedToEventId) { + // This branch is optional - do NOT change anything apart from the reply fallback, since it may not be run + if (repliedToEventId && options.includeReplyFallback !== false) { let repliedToDisplayName let repliedToUserHtml if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 17079e5..4200afe 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -30,7 +30,7 @@ function mockGetEvent(t, roomID_in, eventID_in, outer) { } test("message2event: simple plaintext", async t => { - const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) + const events = await messageToEvent(data.message.simple_plaintext, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -40,7 +40,7 @@ test("message2event: simple plaintext", async t => { }) test("message2event: simple user mention", async t => { - const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) + const events = await messageToEvent(data.message.simple_user_mention, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -52,7 +52,7 @@ test("message2event: simple user mention", async t => { }) test("message2event: simple room mention", async t => { - const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) + const events = await messageToEvent(data.message.simple_room_mention, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -64,7 +64,7 @@ test("message2event: simple room mention", async t => { }) test("message2event: simple message link", async t => { - const events = await messageToEvent(data.message.simple_message_link, data.guild.general) + const events = await messageToEvent(data.message.simple_message_link, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -76,7 +76,7 @@ test("message2event: simple message link", async t => { }) test("message2event: attachment with no content", async t => { - const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) + const events = await messageToEvent(data.message.attachment_no_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -94,7 +94,7 @@ test("message2event: attachment with no content", async t => { }) test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker, data.guild.general) + const events = await messageToEvent(data.message.sticker, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -127,7 +127,7 @@ test("message2event: stickers", async t => { }) test("message2event: skull webp attachment with content", async t => { - const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general) + const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -150,7 +150,7 @@ test("message2event: skull webp attachment with content", async t => { }) test("message2event: reply to skull webp attachment with content", async t => { - const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general) + const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.relates_to": { @@ -183,15 +183,17 @@ test("message2event: reply to skull webp attachment with content", async t => { }) test("message2event: simple reply to matrix user", async t => { - const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, { - getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { - type: "m.room.message", - content: { - msgtype: "m.text", - body: "so can you reply to my webhook uwu" - }, - sender: "@cadence:cadence.moe" - }) + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {}, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } }) t.deepEqual(events, [{ $type: "m.room.message", @@ -215,34 +217,66 @@ test("message2event: simple reply to matrix user", async t => { }]) }) +test("message2event: simple reply to matrix user, reply fallbacks disabled", async t => { + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {includeReplyFallback: false}, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + }, + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe" + ] + }, + msgtype: "m.text", + body: "Reply" + }]) +}) + test("message2event: simple written @mention for matrix user", async t => { - const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, { - async getJoinedMembers(roomID) { - t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") - return new Promise(resolve => { - setTimeout(() => { - resolve({ - joined: { - "@cadence:cadence.moe": { - display_name: "cadence [they]", - avatar_url: "whatever" - }, - "@huckleton:cadence.moe": { - display_name: "huck", - avatar_url: "whatever" - }, - "@_ooye_botrac4r:cadence.moe": { - display_name: "botrac4r", - avatar_url: "whatever" - }, - "@_ooye_bot:cadence.moe": { - display_name: "Out Of Your Element", - avatar_url: "whatever" + const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, { + api: { + async getJoinedMembers(roomID) { + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@huckleton:cadence.moe": { + display_name: "huck", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + }, + "@_ooye_bot:cadence.moe": { + display_name: "Out Of Your Element", + avatar_url: "whatever" + } } - } + }) }) }) - }) + } } }) t.deepEqual(events, [{ diff --git a/scripts/events.db b/scripts/events.db index c8f5bad88858b0ddc7a3b54ec36577c94791c001..3356cbe2ba67631f1efdf34292de8256ab6ee024 100644 GIT binary patch literal 98304 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 RzZa(6kc%W!yx3`f{{Qk~$=Lt^ delta 59 zcmZo@U~6!gAkE6kz`(#XQNf;(bz{N;ejZ*Bm*X)5?<$VR8w;;W-ZNYYyh}%5On|m From 8f6bb86b927792b70f3cb49f2faf48ef647082e1 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 16 Aug 2023 20:44:38 +1200 Subject: [PATCH 091/200] record more update events --- d2m/actions/send-message.js | 2 +- d2m/converters/edit-to-changes.js | 47 +++++----- d2m/converters/edit-to-changes.test.js | 9 +- d2m/converters/message-to-event.js | 20 +++- d2m/converters/message-to-event.test.js | 116 +++++++++++++++--------- scripts/events.db | Bin 8192 -> 98304 bytes 6 files changed, 122 insertions(+), 72 deletions(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 258efcf..cf87d35 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message, guild, api) + const events = await messageToEvent.messageToEvent(message, guild, {}, {api}) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 87e769b..4afa3ce 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -29,7 +29,9 @@ async function editToChanges(message, guild) { // Figure out what we will be replacing them with - const newEvents = await messageToEvent.messageToEvent(message, guild, api) + const newFallbackContent = await messageToEvent.messageToEvent(message, guild, {includeEditFallbackStar: true}, {api}) + const newInnerContent = await messageToEvent.messageToEvent(message, guild, {includeReplyFallback: false}, {api}) + assert.ok(newFallbackContent.length === newInnerContent.length) // Match the new events to the old events @@ -47,21 +49,27 @@ async function editToChanges(message, guild) { let eventsToSend = [] // 4. Events that are matched and have definitely not changed, so they don't need to be edited or replaced at all. This is represented as nothing. + function shift() { + newFallbackContent.shift() + newInnerContent.shift() + } + // For each old event... - outer: while (newEvents.length) { - const newe = newEvents[0] + outer: while (newFallbackContent.length) { + const newe = newFallbackContent[0] // Find a new event to pair it with... for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] - if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype ?? null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to // Found one! // Set up the pairing eventsToReplace.push({ old: olde, - new: newe + newFallbackContent: newFallbackContent[0], + newInnerContent: newInnerContent[0] }) // These events have been handled now, so remove them from the source arrays - newEvents.shift() + shift() oldEventRows.splice(i, 1) // Go all the way back to the start of the next iteration of the outer loop continue outer @@ -69,7 +77,7 @@ async function editToChanges(message, guild) { } // If we got this far, we could not pair it to an existing event, so it'll have to be a new one eventsToSend.push(newe) - newEvents.shift() + shift() } // Anything remaining in oldEventRows is present in the old version only and should be redacted. eventsToRedact = oldEventRows @@ -92,7 +100,7 @@ async function editToChanges(message, guild) { // Removing unnecessary properties before returning eventsToRedact = eventsToRedact.map(e => e.event_id) - eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.new)})) + eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) return {eventsToReplace, eventsToRedact, eventsToSend} } @@ -100,31 +108,26 @@ async function editToChanges(message, guild) { /** * @template T * @param {string} oldID - * @param {T} content + * @param {T} newFallbackContent + * @param {T} newInnerContent * @returns {import("../../types").Event.ReplacementContent} content */ -function eventToReplacementEvent(oldID, content) { - const newContent = { - ...content, +function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) { + const content = { + ...newFallbackContent, "m.mentions": {}, "m.new_content": { - ...content + ...newInnerContent }, "m.relates_to": { rel_type: "m.replace", event_id: oldID } } - if (typeof newContent.body === "string") { - newContent.body = "* " + newContent.body - } - if (typeof newContent.formatted_body === "string") { - newContent.formatted_body = "* " + newContent.formatted_body - } - delete newContent["m.new_content"]["$type"] + delete content["m.new_content"]["$type"] // Client-Server API spec 11.37.3: Any m.relates_to property within m.new_content is ignored. - delete newContent["m.new_content"]["m.relates_to"] - return newContent + delete content["m.new_content"]["m.relates_to"] + return content } module.exports.editToChanges = editToChanges diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index b3e6e0c..f6ecc8d 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -47,9 +47,13 @@ test("edit2changes: edit of reply to skull webp attachment with content", async oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", new: { $type: "m.room.message", - // TODO: read "edits of replies" in the spec!!! msgtype: "m.text", - body: "* Edit", + body: "> Extremity: Image\n\n* Edit", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to Extremity' + + '
    Image
    ' + + '* Edit', "m.mentions": {}, "m.new_content": { msgtype: "m.text", @@ -60,7 +64,6 @@ test("edit2changes: edit of reply to skull webp attachment with content", async rel_type: "m.replace", event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" } - // TODO: read "edits of replies" in the spec!!! } }]) }) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index a2c4915..c128595 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -55,9 +55,12 @@ function getDiscordParseCallbacks(message, useHTML) { /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild - * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API + * @param {{includeReplyFallback?: boolean, includeEditFallbackStar?: boolean}} options default values: + * - includeReplyFallback: true + * - includeEditFallbackStar: false + * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API */ -async function messageToEvent(message, guild, api) { +async function messageToEvent(message, guild, options = {}, di) { const events = [] /** @@ -99,7 +102,7 @@ async function messageToEvent(message, guild, api) { } if (repliedToEventOriginallyFromMatrix) { // Need to figure out who sent that event... - const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) + const event = await di.api.getEvent(repliedToEventRoomId, repliedToEventId) repliedToEventSenderMxid = event.sender // Need to add the sender to m.mentions addMention(repliedToEventSenderMxid) @@ -133,7 +136,7 @@ async function messageToEvent(message, guild, api) { if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { const writtenMentionsText = matches.map(m => m[1].toLowerCase()) const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) - const {joined} = await api.getJoinedMembers(roomID) + const {joined} = await di.api.getJoinedMembers(roomID) for (const [mxid, member] of Object.entries(joined)) { if (!userRegex.some(rx => mxid.match(rx))) { const localpart = mxid.match(/@([^:]*)/) @@ -143,8 +146,15 @@ async function messageToEvent(message, guild, api) { } } + // Star * prefix for fallback edits + if (options.includeEditFallbackStar) { + body = "* " + body + html = "* " + html + } + // Fallback body/formatted_body for replies - if (repliedToEventId) { + // This branch is optional - do NOT change anything apart from the reply fallback, since it may not be run + if (repliedToEventId && options.includeReplyFallback !== false) { let repliedToDisplayName let repliedToUserHtml if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 17079e5..4200afe 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -30,7 +30,7 @@ function mockGetEvent(t, roomID_in, eventID_in, outer) { } test("message2event: simple plaintext", async t => { - const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) + const events = await messageToEvent(data.message.simple_plaintext, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -40,7 +40,7 @@ test("message2event: simple plaintext", async t => { }) test("message2event: simple user mention", async t => { - const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) + const events = await messageToEvent(data.message.simple_user_mention, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -52,7 +52,7 @@ test("message2event: simple user mention", async t => { }) test("message2event: simple room mention", async t => { - const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) + const events = await messageToEvent(data.message.simple_room_mention, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -64,7 +64,7 @@ test("message2event: simple room mention", async t => { }) test("message2event: simple message link", async t => { - const events = await messageToEvent(data.message.simple_message_link, data.guild.general) + const events = await messageToEvent(data.message.simple_message_link, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -76,7 +76,7 @@ test("message2event: simple message link", async t => { }) test("message2event: attachment with no content", async t => { - const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) + const events = await messageToEvent(data.message.attachment_no_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -94,7 +94,7 @@ test("message2event: attachment with no content", async t => { }) test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker, data.guild.general) + const events = await messageToEvent(data.message.sticker, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -127,7 +127,7 @@ test("message2event: stickers", async t => { }) test("message2event: skull webp attachment with content", async t => { - const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general) + const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -150,7 +150,7 @@ test("message2event: skull webp attachment with content", async t => { }) test("message2event: reply to skull webp attachment with content", async t => { - const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general) + const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.relates_to": { @@ -183,15 +183,17 @@ test("message2event: reply to skull webp attachment with content", async t => { }) test("message2event: simple reply to matrix user", async t => { - const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, { - getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { - type: "m.room.message", - content: { - msgtype: "m.text", - body: "so can you reply to my webhook uwu" - }, - sender: "@cadence:cadence.moe" - }) + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {}, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } }) t.deepEqual(events, [{ $type: "m.room.message", @@ -215,34 +217,66 @@ test("message2event: simple reply to matrix user", async t => { }]) }) +test("message2event: simple reply to matrix user, reply fallbacks disabled", async t => { + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {includeReplyFallback: false}, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + }, + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe" + ] + }, + msgtype: "m.text", + body: "Reply" + }]) +}) + test("message2event: simple written @mention for matrix user", async t => { - const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, { - async getJoinedMembers(roomID) { - t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") - return new Promise(resolve => { - setTimeout(() => { - resolve({ - joined: { - "@cadence:cadence.moe": { - display_name: "cadence [they]", - avatar_url: "whatever" - }, - "@huckleton:cadence.moe": { - display_name: "huck", - avatar_url: "whatever" - }, - "@_ooye_botrac4r:cadence.moe": { - display_name: "botrac4r", - avatar_url: "whatever" - }, - "@_ooye_bot:cadence.moe": { - display_name: "Out Of Your Element", - avatar_url: "whatever" + const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, { + api: { + async getJoinedMembers(roomID) { + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@huckleton:cadence.moe": { + display_name: "huck", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + }, + "@_ooye_bot:cadence.moe": { + display_name: "Out Of Your Element", + avatar_url: "whatever" + } } - } + }) }) }) - }) + } } }) t.deepEqual(events, [{ diff --git a/scripts/events.db b/scripts/events.db index c8f5bad88858b0ddc7a3b54ec36577c94791c001..3356cbe2ba67631f1efdf34292de8256ab6ee024 100644 GIT binary patch literal 98304 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 RzZa(6kc%W!yx3`f{{Qk~$=Lt^ delta 59 zcmZo@U~6!gAkE6kz`(#XQNf;(bz{N;ejZ*Bm*X)5?<$VR8w;;W-ZNYYyh}%5On|m From 75414678c85441cda41af86630769ebe002eac0f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 12:35:34 +1200 Subject: [PATCH 092/200] test for editing a new caption onto an image --- d2m/actions/edit-message.js | 2 +- d2m/converters/edit-to-changes.js | 9 +- d2m/converters/edit-to-changes.test.js | 45 ++++++- db/ooye.db | Bin 360448 -> 360448 bytes test/data.js | 156 +++++++++++++++++++++++++ 5 files changed, 202 insertions(+), 10 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 933267c..6823602 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -9,7 +9,7 @@ async function editMessage() { // 3. Send all the things. // old code lies here - let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '1' remains in the database? + let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '0' remains in the database? for (const event of events) { const eventType = event.$type /** @type {Pick> & { $type?: string }} */ diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 4afa3ce..f055c90 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -6,8 +6,6 @@ const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough /** @type {import("./message-to-event")} */ const messageToEvent = sync.require("../converters/message-to-event") -/** @type {import("../../matrix/api")} */ -const api = sync.require("../../matrix/api") /** @type {import("../actions/register-user")} */ const registerUser = sync.require("../actions/register-user") /** @type {import("../actions/create-room")} */ @@ -18,8 +16,9 @@ const createRoom = sync.require("../actions/create-room") * IMPORTANT: This may not have all the normal fields! The API documentation doesn't provide possible types, just says it's all optional! * Since I don't have a spec, I will have to capture some real traffic and add it as test cases... I hope they don't change anything later... * @param {import("discord-api-types/v10").APIGuild} guild + * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API */ -async function editToChanges(message, guild) { +async function editToChanges(message, guild, api) { // Figure out what events we will be replacing const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) @@ -76,7 +75,7 @@ async function editToChanges(message, guild) { } } // If we got this far, we could not pair it to an existing event, so it'll have to be a new one - eventsToSend.push(newe) + eventsToSend.push(newInnerContent[0]) shift() } // Anything remaining in oldEventRows is present in the old version only and should be redacted. @@ -102,7 +101,7 @@ async function editToChanges(message, guild) { eventsToRedact = eventsToRedact.map(e => e.event_id) eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) - return {eventsToReplace, eventsToRedact, eventsToSend} + return {eventsToReplace, eventsToRedact, eventsToSend, senderMxid} } /** diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index f6ecc8d..8385cd0 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -1,12 +1,30 @@ -// @ts-check - const {test} = require("supertape") const {editToChanges} = require("./edit-to-changes") const data = require("../../test/data") const Ty = require("../../types") test("edit2changes: bot response", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general, { + async getJoinedMembers(roomID) { + t.equal(roomID, "!uCtjHhfGlYbVnPVlkG:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + } + } + }) + }) + }) + } + }) t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ @@ -39,8 +57,27 @@ test("edit2changes: bot response", async t => { }]) }) +test("edit2changes: remove caption from image", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.removed_caption_from_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, ["$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA"]) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, []) +}) + +test("edit2changes: add caption back to that image", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.added_caption_to_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "some text", + "m.mentions": {} + }]) + t.deepEqual(eventsToReplace, []) +}) + test("edit2changes: edit of reply to skull webp attachment with content", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ diff --git a/db/ooye.db b/db/ooye.db index a916aee82643b6a8873edfde0eaf2fe46c59b001..56b1c29cbc6ff0cfe587c1443c6ab74d0ad26249 100644 GIT binary patch delta 31509 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 diff --git a/test/data.js b/test/data.js index e23d42f..bb2570b 100644 --- a/test/data.js +++ b/test/data.js @@ -867,6 +867,162 @@ module.exports = { tts: false, type: 0 }, + removed_caption_from_image: { + attachments: [ + { + content_type: "image/png", + filename: "piper_2.png", + height: 163, + id: "1141501302497615912", + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1141501302497615912/piper_2.png", + size: 43231, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png", + width: 188 + } + ], + author: { + avatar: "47db1be7ab77e1d812a4573177af0692", + avatar_decoration: null, + discriminator: "0", + global_name: "wing", + id: "112890272819507200", + public_flags: 0, + username: ".wing." + }, + channel_id: "112760669178241024", + components: [], + content: "", + edited_timestamp: "2023-08-16T22:38:43.075298+00:00", + embeds: [], + flags: 0, + guild_id: "112760669178241024", + id: "1141501302736695316", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2015-11-08T12:25:38.461000+00:00", + mute: false, + nick: "windfucker", + pending: false, + premium_since: null, + roles: [ + "204427286542417920", + "118924814567211009", + "222168467627835392", + "265239342648131584", + "303273332248412160", + "303319030163439616", + "305775031223320577", + "318243902521868288", + "349185088157777920", + "378402925128712193", + "391076926573510656", + "230462991751970827", + "392141548932038658", + "397533096012152832", + "454567553738473472", + "482658335536185357", + "482860581670486028", + "495384759074160642", + "638988388740890635", + "764071315388629012", + "373336013109461013", + "872274377150980116", + "1034022405275910164", + "790724320824655873", + "1040735082610167858", + "1123730787653660742", + "1070177137367208036" + ] + }, + mention_everyone: false, + mention_roles: [], + mentions: [], + pinned: false, + timestamp: "2023-08-16T22:38:38.641000+00:00", + tts: false, + type: 0 + }, + added_caption_to_image: { + attachments: [ + { + content_type: "image/png", + filename: "piper_2.png", + height: 163, + id: "1141501302497615912", + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1141501302497615912/piper_2.png", + size: 43231, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png", + width: 188 + } + ], + author: { + avatar: "47db1be7ab77e1d812a4573177af0692", + avatar_decoration: null, + discriminator: "0", + global_name: "wing", + id: "112890272819507200", + public_flags: 0, + username: ".wing." + }, + channel_id: "112760669178241024", + components: [], + content: "some text", + edited_timestamp: "2023-08-17T00:13:18.620975+00:00", + embeds: [], + flags: 0, + guild_id: "112760669178241024", + id: "1141501302736695317", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2015-11-08T12:25:38.461000+00:00", + mute: false, + nick: "windfucker", + pending: false, + premium_since: null, + roles: [ + "204427286542417920", + "118924814567211009", + "222168467627835392", + "265239342648131584", + "303273332248412160", + "303319030163439616", + "305775031223320577", + "318243902521868288", + "349185088157777920", + "378402925128712193", + "391076926573510656", + "230462991751970827", + "392141548932038658", + "397533096012152832", + "454567553738473472", + "482658335536185357", + "482860581670486028", + "495384759074160642", + "638988388740890635", + "764071315388629012", + "373336013109461013", + "872274377150980116", + "1034022405275910164", + "790724320824655873", + "1040735082610167858", + "1123730787653660742", + "1070177137367208036" + ] + }, + mention_everyone: false, + mention_roles: [], + mentions: [], + pinned: false, + timestamp: "2023-08-16T22:38:38.641000+00:00", + tts: false, + type: 0 + }, edit_of_reply_to_skull_webp_attachment_with_content: { type: 19, tts: false, From 2973170e877aa3fecd1a5f2cc9a60420115ce4cb Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 12:35:34 +1200 Subject: [PATCH 093/200] test for editing a new caption onto an image --- d2m/actions/edit-message.js | 2 +- d2m/converters/edit-to-changes.js | 9 +- d2m/converters/edit-to-changes.test.js | 45 ++++++- test/data.js | 156 +++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 10 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 933267c..6823602 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -9,7 +9,7 @@ async function editMessage() { // 3. Send all the things. // old code lies here - let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '1' remains in the database? + let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '0' remains in the database? for (const event of events) { const eventType = event.$type /** @type {Pick> & { $type?: string }} */ diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 4afa3ce..f055c90 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -6,8 +6,6 @@ const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough /** @type {import("./message-to-event")} */ const messageToEvent = sync.require("../converters/message-to-event") -/** @type {import("../../matrix/api")} */ -const api = sync.require("../../matrix/api") /** @type {import("../actions/register-user")} */ const registerUser = sync.require("../actions/register-user") /** @type {import("../actions/create-room")} */ @@ -18,8 +16,9 @@ const createRoom = sync.require("../actions/create-room") * IMPORTANT: This may not have all the normal fields! The API documentation doesn't provide possible types, just says it's all optional! * Since I don't have a spec, I will have to capture some real traffic and add it as test cases... I hope they don't change anything later... * @param {import("discord-api-types/v10").APIGuild} guild + * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API */ -async function editToChanges(message, guild) { +async function editToChanges(message, guild, api) { // Figure out what events we will be replacing const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) @@ -76,7 +75,7 @@ async function editToChanges(message, guild) { } } // If we got this far, we could not pair it to an existing event, so it'll have to be a new one - eventsToSend.push(newe) + eventsToSend.push(newInnerContent[0]) shift() } // Anything remaining in oldEventRows is present in the old version only and should be redacted. @@ -102,7 +101,7 @@ async function editToChanges(message, guild) { eventsToRedact = eventsToRedact.map(e => e.event_id) eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) - return {eventsToReplace, eventsToRedact, eventsToSend} + return {eventsToReplace, eventsToRedact, eventsToSend, senderMxid} } /** diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index f6ecc8d..8385cd0 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -1,12 +1,30 @@ -// @ts-check - const {test} = require("supertape") const {editToChanges} = require("./edit-to-changes") const data = require("../../test/data") const Ty = require("../../types") test("edit2changes: bot response", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general, { + async getJoinedMembers(roomID) { + t.equal(roomID, "!uCtjHhfGlYbVnPVlkG:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + } + } + }) + }) + }) + } + }) t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ @@ -39,8 +57,27 @@ test("edit2changes: bot response", async t => { }]) }) +test("edit2changes: remove caption from image", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.removed_caption_from_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, ["$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA"]) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, []) +}) + +test("edit2changes: add caption back to that image", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.added_caption_to_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "some text", + "m.mentions": {} + }]) + t.deepEqual(eventsToReplace, []) +}) + test("edit2changes: edit of reply to skull webp attachment with content", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ diff --git a/test/data.js b/test/data.js index e23d42f..bb2570b 100644 --- a/test/data.js +++ b/test/data.js @@ -867,6 +867,162 @@ module.exports = { tts: false, type: 0 }, + removed_caption_from_image: { + attachments: [ + { + content_type: "image/png", + filename: "piper_2.png", + height: 163, + id: "1141501302497615912", + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1141501302497615912/piper_2.png", + size: 43231, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png", + width: 188 + } + ], + author: { + avatar: "47db1be7ab77e1d812a4573177af0692", + avatar_decoration: null, + discriminator: "0", + global_name: "wing", + id: "112890272819507200", + public_flags: 0, + username: ".wing." + }, + channel_id: "112760669178241024", + components: [], + content: "", + edited_timestamp: "2023-08-16T22:38:43.075298+00:00", + embeds: [], + flags: 0, + guild_id: "112760669178241024", + id: "1141501302736695316", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2015-11-08T12:25:38.461000+00:00", + mute: false, + nick: "windfucker", + pending: false, + premium_since: null, + roles: [ + "204427286542417920", + "118924814567211009", + "222168467627835392", + "265239342648131584", + "303273332248412160", + "303319030163439616", + "305775031223320577", + "318243902521868288", + "349185088157777920", + "378402925128712193", + "391076926573510656", + "230462991751970827", + "392141548932038658", + "397533096012152832", + "454567553738473472", + "482658335536185357", + "482860581670486028", + "495384759074160642", + "638988388740890635", + "764071315388629012", + "373336013109461013", + "872274377150980116", + "1034022405275910164", + "790724320824655873", + "1040735082610167858", + "1123730787653660742", + "1070177137367208036" + ] + }, + mention_everyone: false, + mention_roles: [], + mentions: [], + pinned: false, + timestamp: "2023-08-16T22:38:38.641000+00:00", + tts: false, + type: 0 + }, + added_caption_to_image: { + attachments: [ + { + content_type: "image/png", + filename: "piper_2.png", + height: 163, + id: "1141501302497615912", + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1141501302497615912/piper_2.png", + size: 43231, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png", + width: 188 + } + ], + author: { + avatar: "47db1be7ab77e1d812a4573177af0692", + avatar_decoration: null, + discriminator: "0", + global_name: "wing", + id: "112890272819507200", + public_flags: 0, + username: ".wing." + }, + channel_id: "112760669178241024", + components: [], + content: "some text", + edited_timestamp: "2023-08-17T00:13:18.620975+00:00", + embeds: [], + flags: 0, + guild_id: "112760669178241024", + id: "1141501302736695317", + member: { + avatar: null, + communication_disabled_until: null, + deaf: false, + flags: 0, + joined_at: "2015-11-08T12:25:38.461000+00:00", + mute: false, + nick: "windfucker", + pending: false, + premium_since: null, + roles: [ + "204427286542417920", + "118924814567211009", + "222168467627835392", + "265239342648131584", + "303273332248412160", + "303319030163439616", + "305775031223320577", + "318243902521868288", + "349185088157777920", + "378402925128712193", + "391076926573510656", + "230462991751970827", + "392141548932038658", + "397533096012152832", + "454567553738473472", + "482658335536185357", + "482860581670486028", + "495384759074160642", + "638988388740890635", + "764071315388629012", + "373336013109461013", + "872274377150980116", + "1034022405275910164", + "790724320824655873", + "1040735082610167858", + "1123730787653660742", + "1070177137367208036" + ] + }, + mention_everyone: false, + mention_roles: [], + mentions: [], + pinned: false, + timestamp: "2023-08-16T22:38:38.641000+00:00", + tts: false, + type: 0 + }, edit_of_reply_to_skull_webp_attachment_with_content: { type: 19, tts: false, From 6a452bd935a54eafe9423a341776012210ae5e33 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 16:41:28 +1200 Subject: [PATCH 094/200] write editMessage action, connected to dispatcher --- d2m/actions/edit-message.js | 70 ++++++++++++++++++-------- d2m/actions/register-user.js | 4 +- d2m/converters/edit-to-changes.js | 10 ++-- d2m/converters/edit-to-changes.test.js | 4 +- d2m/event-dispatcher.js | 22 ++++++++ matrix/api.js | 14 +++++- types.d.ts | 4 ++ 7 files changed, 95 insertions(+), 33 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 6823602..9a329b6 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -1,28 +1,54 @@ -async function editMessage() { - // Action time! +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../converters/edit-to-changes")} */ +const editToChanges = sync.require("../converters/edit-to-changes") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") + +/** + * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message + * @param {import("discord-api-types/v10").APIGuild} guild + */ +async function editMessage(message, guild) { + console.log(`*** applying edit for message ${message.id} in channel ${message.channel_id}`) + const {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid} = await editToChanges.editToChanges(message, guild, api) + console.log("making these changes:", {eventsToRedact, eventsToReplace, eventsToSend}) // 1. Replace all the things. + for (const {oldID, newContent} of eventsToReplace) { + const eventType = newContent.$type + /** @type {Pick> & { $type?: string }} */ + const newContentWithoutType = {...newContent} + delete newContentWithoutType.$type - - // 2. Redact all the things. - - // 3. Send all the things. - - // old code lies here - let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '0' remains in the database? - for (const event of events) { - const eventType = event.$type - /** @type {Pick> & { $type?: string }} */ - const eventWithoutType = {...event} - delete eventWithoutType.$type - - const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord - - eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting - eventIDs.push(eventID) + await api.sendEvent(roomID, eventType, newContentWithoutType, senderMxid) + // Ensure the database is up to date. + // The columns are event_id, event_type, event_subtype, message_id, channel_id, part, source. Only event_subtype could potentially be changed by a replacement event. + const subtype = newContentWithoutType.msgtype ?? null + db.prepare("UPDATE event_message SET event_subtype = ? WHERE event_id = ?").run(subtype, oldID) } - return eventIDs + // 2. Redact all the things. + // Not redacting as the last action because the last action is likely to be shown in the room preview in clients, and we don't want it to look like somebody actually deleted a message. + for (const eventID of eventsToRedact) { + await api.redactEvent(roomID, eventID, senderMxid) + // TODO: I should almost certainly remove the redacted event from our database now, shouldn't I? I mean, it's literally not there any more... you can't do anything else with it... + // TODO: If I just redacted part = 0, I should update one of the other events to make it the new part = 0, right? + // TODO: Consider whether this code could be reused between edited messages and deleted messages. + } -{eventsToReplace, eventsToRedact, eventsToSend} + // 3. Send all the things. + for (const content of eventsToSend) { + const eventType = content.$type + /** @type {Pick> & { $type?: string }} */ + const contentWithoutType = {...content} + delete contentWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, contentWithoutType, senderMxid) + db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, 1, 1)").run(eventID, eventType, content.msgtype || null, message.id, message.channel_id) // part 1 = supporting; source 1 = discord + } +} + +module.exports.editMessage = editMessage diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index beb24bd..1455360 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -43,7 +43,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 + * @returns {Promise} mxid */ async function ensureSim(user) { let mxid = null @@ -60,7 +60,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 * @param {string} roomID - * @returns mxid + * @returns {Promise} mxid */ async function ensureSimJoined(user, roomID) { // Ensure room ID is really an ID, not an alias diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index f055c90..4e6892d 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -82,7 +82,7 @@ async function editToChanges(message, guild, api) { eventsToRedact = oldEventRows // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! - // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) + // (Example: a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. (This is category 4 mentioned above.) Everything remaining *may* have changed. eventsToReplace = eventsToReplace.filter(ev => { // Discord does not allow files, images, attachments, or videos to be edited. @@ -99,9 +99,9 @@ async function editToChanges(message, guild, api) { // Removing unnecessary properties before returning eventsToRedact = eventsToRedact.map(e => e.event_id) - eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) + eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, newContent: makeReplacementEventContent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) - return {eventsToReplace, eventsToRedact, eventsToSend, senderMxid} + return {roomID, eventsToReplace, eventsToRedact, eventsToSend, senderMxid} } /** @@ -111,7 +111,7 @@ async function editToChanges(message, guild, api) { * @param {T} newInnerContent * @returns {import("../../types").Event.ReplacementContent} content */ -function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) { +function makeReplacementEventContent(oldID, newFallbackContent, newInnerContent) { const content = { ...newFallbackContent, "m.mentions": {}, @@ -130,4 +130,4 @@ function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) { } module.exports.editToChanges = editToChanges -module.exports.eventToReplacementEvent = eventToReplacementEvent +module.exports.makeReplacementEventContent = makeReplacementEventContent diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index 8385cd0..bb3f3ec 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -29,7 +29,7 @@ test("edit2changes: bot response", async t => { t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", - new: { + newContent: { $type: "m.room.message", msgtype: "m.text", body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", @@ -82,7 +82,7 @@ test("edit2changes: edit of reply to skull webp attachment with content", async t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", - new: { + newContent: { $type: "m.room.message", msgtype: "m.text", body: "> Extremity: Image\n\n* Edit", diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 1686b5f..4bb94ff 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -3,6 +3,9 @@ const {sync, db} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") +/** @type {import("./actions/edit-message")}) */ +const editMessage = sync.require("./actions/edit-message") + /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") @@ -29,6 +32,25 @@ module.exports = { sendMessage.sendMessage(message, guild) }, + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message + */ + onMessageUpdate(client, data) { + // Based on looking at data they've sent me over the gateway, this is the best way to check for meaningful changes. + // If the message content is a string then it includes all interesting fields and is meaningful. + if (typeof data.content === "string") { + /** @type {import("discord-api-types/v10").GatewayMessageCreateDispatchData} */ + const message = data + /** @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = client.channels.get(message.channel_id) + if (!channel.guild_id) return // Nothing we can do in direct messages. + const guild = client.guilds.get(channel.guild_id) + if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + editMessage.editMessage(message, guild) + } + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data diff --git a/matrix/api.js b/matrix/api.js index 9111909..ef3e199 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -14,7 +14,7 @@ const makeTxnId = sync.require("./txnid") /** * @param {string} p endpoint to access - * @param {string} [mxid] optional: user to act as, for the ?user_id parameter + * @param {string?} [mxid] optional: user to act as, for the ?user_id parameter * @param {{[x: string]: any}} [otherParams] optional: any other query parameters to add * @returns {string} the new endpoint */ @@ -119,7 +119,7 @@ async function sendState(roomID, type, stateKey, content, mxid) { * @param {string} roomID * @param {string} type * @param {any} content - * @param {string} [mxid] + * @param {string?} [mxid] * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds */ async function sendEvent(roomID, type, content, mxid, timestamp) { @@ -129,6 +129,15 @@ async function sendEvent(roomID, type, content, mxid, timestamp) { return root.event_id } +/** + * @returns {Promise} room ID + */ +async function redactEvent(roomID, eventID, mxid) { + /** @type {Ty.R.EventRedacted} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid)) + return root.event_id +} + async function profileSetDisplayname(mxid, displayname) { await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), { displayname @@ -152,5 +161,6 @@ module.exports.getAllState = getAllState module.exports.getJoinedMembers = getJoinedMembers module.exports.sendState = sendState module.exports.sendEvent = sendEvent +module.exports.redactEvent = redactEvent module.exports.profileSetDisplayname = profileSetDisplayname module.exports.profileSetAvatarUrl = profileSetAvatarUrl diff --git a/types.d.ts b/types.d.ts index 76d3bd1..b9f7ed6 100644 --- a/types.d.ts +++ b/types.d.ts @@ -112,4 +112,8 @@ namespace R { export type EventSent = { event_id: string } + + export type EventRedacted = { + event_id: string + } } From 040a2d253ffdbdec78afc6dd2008b1e629d9caf2 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 16:41:28 +1200 Subject: [PATCH 095/200] write editMessage action, connected to dispatcher --- d2m/actions/edit-message.js | 70 ++++++++++++++++++-------- d2m/actions/register-user.js | 4 +- d2m/converters/edit-to-changes.js | 10 ++-- d2m/converters/edit-to-changes.test.js | 4 +- d2m/event-dispatcher.js | 22 ++++++++ matrix/api.js | 14 +++++- types.d.ts | 4 ++ 7 files changed, 95 insertions(+), 33 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 6823602..9a329b6 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -1,28 +1,54 @@ -async function editMessage() { - // Action time! +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../converters/edit-to-changes")} */ +const editToChanges = sync.require("../converters/edit-to-changes") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") + +/** + * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message + * @param {import("discord-api-types/v10").APIGuild} guild + */ +async function editMessage(message, guild) { + console.log(`*** applying edit for message ${message.id} in channel ${message.channel_id}`) + const {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid} = await editToChanges.editToChanges(message, guild, api) + console.log("making these changes:", {eventsToRedact, eventsToReplace, eventsToSend}) // 1. Replace all the things. + for (const {oldID, newContent} of eventsToReplace) { + const eventType = newContent.$type + /** @type {Pick> & { $type?: string }} */ + const newContentWithoutType = {...newContent} + delete newContentWithoutType.$type - - // 2. Redact all the things. - - // 3. Send all the things. - - // old code lies here - let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '0' remains in the database? - for (const event of events) { - const eventType = event.$type - /** @type {Pick> & { $type?: string }} */ - const eventWithoutType = {...event} - delete eventWithoutType.$type - - const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord - - eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting - eventIDs.push(eventID) + await api.sendEvent(roomID, eventType, newContentWithoutType, senderMxid) + // Ensure the database is up to date. + // The columns are event_id, event_type, event_subtype, message_id, channel_id, part, source. Only event_subtype could potentially be changed by a replacement event. + const subtype = newContentWithoutType.msgtype ?? null + db.prepare("UPDATE event_message SET event_subtype = ? WHERE event_id = ?").run(subtype, oldID) } - return eventIDs + // 2. Redact all the things. + // Not redacting as the last action because the last action is likely to be shown in the room preview in clients, and we don't want it to look like somebody actually deleted a message. + for (const eventID of eventsToRedact) { + await api.redactEvent(roomID, eventID, senderMxid) + // TODO: I should almost certainly remove the redacted event from our database now, shouldn't I? I mean, it's literally not there any more... you can't do anything else with it... + // TODO: If I just redacted part = 0, I should update one of the other events to make it the new part = 0, right? + // TODO: Consider whether this code could be reused between edited messages and deleted messages. + } -{eventsToReplace, eventsToRedact, eventsToSend} + // 3. Send all the things. + for (const content of eventsToSend) { + const eventType = content.$type + /** @type {Pick> & { $type?: string }} */ + const contentWithoutType = {...content} + delete contentWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, contentWithoutType, senderMxid) + db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, 1, 1)").run(eventID, eventType, content.msgtype || null, message.id, message.channel_id) // part 1 = supporting; source 1 = discord + } +} + +module.exports.editMessage = editMessage diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index beb24bd..1455360 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -43,7 +43,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 + * @returns {Promise} mxid */ async function ensureSim(user) { let mxid = null @@ -60,7 +60,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 * @param {string} roomID - * @returns mxid + * @returns {Promise} mxid */ async function ensureSimJoined(user, roomID) { // Ensure room ID is really an ID, not an alias diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index f055c90..4e6892d 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -82,7 +82,7 @@ async function editToChanges(message, guild, api) { eventsToRedact = oldEventRows // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! - // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) + // (Example: a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. (This is category 4 mentioned above.) Everything remaining *may* have changed. eventsToReplace = eventsToReplace.filter(ev => { // Discord does not allow files, images, attachments, or videos to be edited. @@ -99,9 +99,9 @@ async function editToChanges(message, guild, api) { // Removing unnecessary properties before returning eventsToRedact = eventsToRedact.map(e => e.event_id) - eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) + eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, newContent: makeReplacementEventContent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) - return {eventsToReplace, eventsToRedact, eventsToSend, senderMxid} + return {roomID, eventsToReplace, eventsToRedact, eventsToSend, senderMxid} } /** @@ -111,7 +111,7 @@ async function editToChanges(message, guild, api) { * @param {T} newInnerContent * @returns {import("../../types").Event.ReplacementContent} content */ -function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) { +function makeReplacementEventContent(oldID, newFallbackContent, newInnerContent) { const content = { ...newFallbackContent, "m.mentions": {}, @@ -130,4 +130,4 @@ function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) { } module.exports.editToChanges = editToChanges -module.exports.eventToReplacementEvent = eventToReplacementEvent +module.exports.makeReplacementEventContent = makeReplacementEventContent diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index 8385cd0..bb3f3ec 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -29,7 +29,7 @@ test("edit2changes: bot response", async t => { t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", - new: { + newContent: { $type: "m.room.message", msgtype: "m.text", body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", @@ -82,7 +82,7 @@ test("edit2changes: edit of reply to skull webp attachment with content", async t.deepEqual(eventsToSend, []) t.deepEqual(eventsToReplace, [{ oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", - new: { + newContent: { $type: "m.room.message", msgtype: "m.text", body: "> Extremity: Image\n\n* Edit", diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 1686b5f..4bb94ff 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -3,6 +3,9 @@ const {sync, db} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") +/** @type {import("./actions/edit-message")}) */ +const editMessage = sync.require("./actions/edit-message") + /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") @@ -29,6 +32,25 @@ module.exports = { sendMessage.sendMessage(message, guild) }, + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message + */ + onMessageUpdate(client, data) { + // Based on looking at data they've sent me over the gateway, this is the best way to check for meaningful changes. + // If the message content is a string then it includes all interesting fields and is meaningful. + if (typeof data.content === "string") { + /** @type {import("discord-api-types/v10").GatewayMessageCreateDispatchData} */ + const message = data + /** @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = client.channels.get(message.channel_id) + if (!channel.guild_id) return // Nothing we can do in direct messages. + const guild = client.guilds.get(channel.guild_id) + if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + editMessage.editMessage(message, guild) + } + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data diff --git a/matrix/api.js b/matrix/api.js index 9111909..ef3e199 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -14,7 +14,7 @@ const makeTxnId = sync.require("./txnid") /** * @param {string} p endpoint to access - * @param {string} [mxid] optional: user to act as, for the ?user_id parameter + * @param {string?} [mxid] optional: user to act as, for the ?user_id parameter * @param {{[x: string]: any}} [otherParams] optional: any other query parameters to add * @returns {string} the new endpoint */ @@ -119,7 +119,7 @@ async function sendState(roomID, type, stateKey, content, mxid) { * @param {string} roomID * @param {string} type * @param {any} content - * @param {string} [mxid] + * @param {string?} [mxid] * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds */ async function sendEvent(roomID, type, content, mxid, timestamp) { @@ -129,6 +129,15 @@ async function sendEvent(roomID, type, content, mxid, timestamp) { return root.event_id } +/** + * @returns {Promise} room ID + */ +async function redactEvent(roomID, eventID, mxid) { + /** @type {Ty.R.EventRedacted} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid)) + return root.event_id +} + async function profileSetDisplayname(mxid, displayname) { await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), { displayname @@ -152,5 +161,6 @@ module.exports.getAllState = getAllState module.exports.getJoinedMembers = getJoinedMembers module.exports.sendState = sendState module.exports.sendEvent = sendEvent +module.exports.redactEvent = redactEvent module.exports.profileSetDisplayname = profileSetDisplayname module.exports.profileSetAvatarUrl = profileSetAvatarUrl diff --git a/types.d.ts b/types.d.ts index 76d3bd1..b9f7ed6 100644 --- a/types.d.ts +++ b/types.d.ts @@ -112,4 +112,8 @@ namespace R { export type EventSent = { event_id: string } + + export type EventRedacted = { + event_id: string + } } From cfa1856118e6274020ec258eea59e4da311578f1 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 16:56:51 +1200 Subject: [PATCH 096/200] new database changes --- db/ooye.db | Bin 360448 -> 360448 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/db/ooye.db b/db/ooye.db index 56b1c29cbc6ff0cfe587c1443c6ab74d0ad26249..93ae79171b0cc07a2e60eaba6613a2c03cdfce6f 100644 GIT binary patch delta 381 zcmZo@5Nl`nsMntP71PTT>Vpycf}8?PB0y=0CvSz_y=_i{FXwKWo=^ zu@8(ztkWB_8C9mQe#tDy7`grIS4Q(JM$_r62~37e+>550B`{gBXGVCNl%_FEXY^%t z+`cP;Nt%n9;XKpy^^z?1)5Wuyy4_q^QjM8ZLM&Yq^HQ_jJjzoe{46cX%nkCgi!)07 z{5{Gn!koNZ;(b!QBMd!4Q*(W1k55NzA2EPUh4?+(A zw;1pNas`uM5Jk7m^#M0(0W_BdU;;6hIA8)g2}?9-SW7f@W&oD~O$9->x?lnu3IhQZ z0++8D1V5J-YXXaxqDuuvx9DpE93%r90oRuYkODWe5ipqn0()4OjJ*LLm!6jb9+%0z n0X~ORk^+ZRk^{F?k_50_0}ct_mk00!9kUTIy9u}7`2-<`S~xuk From 036a9f6a046c46c1ea9313469b5dc6636ad53c44 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 18:14:44 +1200 Subject: [PATCH 097/200] update discord-markdown to remove html tags --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6be3b42..c6b6004 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", @@ -351,9 +351,9 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz", - "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==", + "version": "18.2.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.20.tgz", + "integrity": "sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1051,7 +1051,7 @@ }, "node_modules/discord-markdown": { "version": "2.4.1", - "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", + "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.2" diff --git a/package.json b/package.json index 2437aba..6557500 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", From f5ef881cd000aa5840ac0c0320c0a2669667cd50 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 18:14:44 +1200 Subject: [PATCH 098/200] update discord-markdown to remove html tags --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6be3b42..c6b6004 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", @@ -351,9 +351,9 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz", - "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==", + "version": "18.2.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.20.tgz", + "integrity": "sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1051,7 +1051,7 @@ }, "node_modules/discord-markdown": { "version": "2.4.1", - "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", + "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.2" diff --git a/package.json b/package.json index 2437aba..6557500 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", From 67fd5ff01640eda350cfbd13ccc00063fd4775c9 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 18:17:53 +1200 Subject: [PATCH 099/200] support up to 1 space in written mentions --- d2m/converters/message-to-event.js | 4 ++-- d2m/converters/message-to-event.test.js | 5 ++++- scripts/events.db | Bin 98304 -> 192512 bytes 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index c128595..29730d4 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -131,8 +131,8 @@ async function messageToEvent(message, guild, options = {}, di) { escapeHTML: false, }, null, null) - // Mentions scenario 3: scan the message content for written @mentions of matrix users - const matches = [...content.matchAll(/@([a-z0-9._]+)\b/gi)] + // Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention. + const matches = [...content.matchAll(/@ ?([a-z0-9._]+)\b/gi)] if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { const writtenMentionsText = matches.map(m => m[1].toLowerCase()) const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 4200afe..df94196 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -247,10 +247,12 @@ test("message2event: simple reply to matrix user, reply fallbacks disabled", asy }]) }) -test("message2event: simple written @mention for matrix user", async t => { +test("message2event: simple written @mentions for matrix users", async t => { + let called = 0 const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, { api: { async getJoinedMembers(roomID) { + called++ t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") return new Promise(resolve => { setTimeout(() => { @@ -290,6 +292,7 @@ test("message2event: simple written @mention for matrix user", async t => { msgtype: "m.text", body: "@Cadence, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and @huck" }]) + t.equal(called, 1, "should only look up the member list once") }) test("message2event: very large attachment is linked instead of being uploaded", async t => { diff --git a/scripts/events.db b/scripts/events.db index 3356cbe2ba67631f1efdf34292de8256ab6ee024..d3b817d2a036453e0de94479f85f48b5b73d0bb7 100644 GIT binary patch delta 20626 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 t1oT8QM**96-kV>_9Q55EDo-V(A tx;$fU01pdm6a$|(mo!HjYt+U Date: Thu, 17 Aug 2023 18:17:53 +1200 Subject: [PATCH 100/200] support up to 1 space in written mentions --- d2m/converters/message-to-event.js | 4 ++-- d2m/converters/message-to-event.test.js | 5 ++++- scripts/events.db | Bin 98304 -> 192512 bytes 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index c128595..29730d4 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -131,8 +131,8 @@ async function messageToEvent(message, guild, options = {}, di) { escapeHTML: false, }, null, null) - // Mentions scenario 3: scan the message content for written @mentions of matrix users - const matches = [...content.matchAll(/@([a-z0-9._]+)\b/gi)] + // Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention. + const matches = [...content.matchAll(/@ ?([a-z0-9._]+)\b/gi)] if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { const writtenMentionsText = matches.map(m => m[1].toLowerCase()) const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 4200afe..df94196 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -247,10 +247,12 @@ test("message2event: simple reply to matrix user, reply fallbacks disabled", asy }]) }) -test("message2event: simple written @mention for matrix user", async t => { +test("message2event: simple written @mentions for matrix users", async t => { + let called = 0 const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, { api: { async getJoinedMembers(roomID) { + called++ t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") return new Promise(resolve => { setTimeout(() => { @@ -290,6 +292,7 @@ test("message2event: simple written @mention for matrix user", async t => { msgtype: "m.text", body: "@Cadence, tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and @huck" }]) + t.equal(called, 1, "should only look up the member list once") }) test("message2event: very large attachment is linked instead of being uploaded", async t => { diff --git a/scripts/events.db b/scripts/events.db index 3356cbe2ba67631f1efdf34292de8256ab6ee024..d3b817d2a036453e0de94479f85f48b5b73d0bb7 100644 GIT binary patch delta 20626 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 t1oT8QM**96-kV>_9Q55EDo-V(A tx;$fU01pdm6a$|(mo!HjYt+U Date: Thu, 17 Aug 2023 18:27:13 +1200 Subject: [PATCH 101/200] actually call the function lol --- d2m/discord-packets.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 3786393..b8c0af6 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -67,6 +67,8 @@ const utils = { } else if (message.t === "MESSAGE_CREATE") { eventDispatcher.onMessageCreate(client, message.d) + } else if (message.t === "MESSAGE_UPDATE") { + eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_REACTION_ADD") { eventDispatcher.onReactionAdd(client, message.d) From b9de84e58635d4e59e62aba78c1788c9ec2ea310 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 18:27:13 +1200 Subject: [PATCH 102/200] actually call the function lol --- d2m/discord-packets.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 3786393..b8c0af6 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -67,6 +67,8 @@ const utils = { } else if (message.t === "MESSAGE_CREATE") { eventDispatcher.onMessageCreate(client, message.d) + } else if (message.t === "MESSAGE_UPDATE") { + eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_REACTION_ADD") { eventDispatcher.onReactionAdd(client, message.d) From 09b7ba570c69bf3365fee8feeff7d26a00a63782 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 19:03:09 +1200 Subject: [PATCH 103/200] test that edits by webhook work --- d2m/converters/edit-to-changes.js | 9 +- d2m/converters/edit-to-changes.test.js | 205 ++++++++++++++----------- d2m/event-dispatcher.js | 7 + db/ooye.db | Bin 360448 -> 360448 bytes scripts/events.db | Bin 192512 -> 196608 bytes test/data.js | 27 ++++ 6 files changed, 158 insertions(+), 90 deletions(-) diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 4e6892d..3f4b2d2 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -22,7 +22,14 @@ async function editToChanges(message, guild, api) { // Figure out what events we will be replacing const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) - const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + /** @type {string?} */ + let senderMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(message.author.id) ?? null + if (senderMxid) { + const senderIsInRoom = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, senderMxid) + if (!senderIsInRoom) { + senderMxid = null // just send as ooye bot + } + } /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index bb3f3ec..674cb15 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -3,104 +3,131 @@ const {editToChanges} = require("./edit-to-changes") const data = require("../../test/data") const Ty = require("../../types") +test("edit2changes: edit by webhook", async t => { + const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_by_webhook, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "* test 2", + "m.mentions": {}, + "m.new_content": { + // *** Replaced With: *** + msgtype: "m.text", + body: "test 2", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10" + } + } + }]) + t.equal(senderMxid, null) +}) + test("edit2changes: bot response", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general, { - async getJoinedMembers(roomID) { - t.equal(roomID, "!uCtjHhfGlYbVnPVlkG:cadence.moe") - return new Promise(resolve => { - setTimeout(() => { - resolve({ - joined: { - "@cadence:cadence.moe": { - display_name: "cadence [they]", - avatar_url: "whatever" - }, - "@_ooye_botrac4r:cadence.moe": { - display_name: "botrac4r", - avatar_url: "whatever" - } - } - }) - }) - }) - } - }) - t.deepEqual(eventsToRedact, []) - t.deepEqual(eventsToSend, []) - t.deepEqual(eventsToReplace, [{ - oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", - newContent: { - $type: "m.room.message", - msgtype: "m.text", - body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", - format: "org.matrix.custom.html", - formatted_body: '* :ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', - "m.mentions": { - // Client-Server API spec 11.37.7: Copy Discord's behaviour by not re-notifying anyone that an *edit occurred* - }, - // *** Replaced With: *** - "m.new_content": { - msgtype: "m.text", - body: ":ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", - format: "org.matrix.custom.html", - formatted_body: ':ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', - "m.mentions": { - // Client-Server API spec 11.37.7: This should contain the mentions for the final version of the event - "user_ids": ["@cadence:cadence.moe"] - } - }, - "m.relates_to": { - rel_type: "m.replace", - event_id: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY" - } - } - }]) + const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general, { + async getJoinedMembers(roomID) { + t.equal(roomID, "!uCtjHhfGlYbVnPVlkG:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + } + } + }) + }) + }) + } + }) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: '* :ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: Copy Discord's behaviour by not re-notifying anyone that an *edit occurred* + }, + // *** Replaced With: *** + "m.new_content": { + msgtype: "m.text", + body: ":ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: ':ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: This should contain the mentions for the final version of the event + "user_ids": ["@cadence:cadence.moe"] + } + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY" + } + } + }]) + t.equal(senderMxid, "@_ooye_bojack_horseman:cadence.moe") }) test("edit2changes: remove caption from image", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.removed_caption_from_image, data.guild.general, {}) - t.deepEqual(eventsToRedact, ["$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA"]) - t.deepEqual(eventsToSend, []) - t.deepEqual(eventsToReplace, []) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.removed_caption_from_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, ["$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA"]) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, []) }) test("edit2changes: add caption back to that image", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.added_caption_to_image, data.guild.general, {}) - t.deepEqual(eventsToRedact, []) - t.deepEqual(eventsToSend, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "some text", - "m.mentions": {} - }]) - t.deepEqual(eventsToReplace, []) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.added_caption_to_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "some text", + "m.mentions": {} + }]) + t.deepEqual(eventsToReplace, []) }) test("edit2changes: edit of reply to skull webp attachment with content", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(eventsToRedact, []) - t.deepEqual(eventsToSend, []) - t.deepEqual(eventsToReplace, [{ - oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", - newContent: { - $type: "m.room.message", - msgtype: "m.text", - body: "> Extremity: Image\n\n* Edit", - format: "org.matrix.custom.html", - formatted_body: - '
    In reply to Extremity' - + '
    Image
    ' - + '* Edit', - "m.mentions": {}, - "m.new_content": { - msgtype: "m.text", - body: "Edit", - "m.mentions": {} - }, - "m.relates_to": { - rel_type: "m.replace", - event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" - } - } - }]) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "> Extremity: Image\n\n* Edit", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to Extremity' + + '
    Image
    ' + + '* Edit', + "m.mentions": {}, + "m.new_content": { + msgtype: "m.text", + body: "Edit", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" + } + } + }]) }) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 4bb94ff..1527b28 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -37,6 +37,13 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message */ onMessageUpdate(client, data) { + if (data.webhook_id) { + const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + if (row) { + // The update was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. + return + } + } // Based on looking at data they've sent me over the gateway, this is the best way to check for meaningful changes. // If the message content is a string then it includes all interesting fields and is meaningful. if (typeof data.content === "string") { diff --git a/db/ooye.db b/db/ooye.db index 93ae79171b0cc07a2e60eaba6613a2c03cdfce6f..1c52510cc5f1d7902d1e6a05829750a2a0dfba70 100644 GIT binary patch delta 2770 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 delta 1473 zcmZuwdrXyO7=JI%d(L;x`7U61Fu^1@89N}zC17TpqaD6dz@W@EE{XTjP+@p21J`nh zcmxjkG&Kdpt^QzYBp<>oHrLi#;SsZ3TbZ>naAK@!7pKsB!1}9id!K#J@43Fu?|Ex$ z-L=iA(&UM1O30$e&X;$tj@MmX5+KXfIxSWYxVqFK?SXbq+o=1Ed9FY8 z8%B@u7ToLhlrM4x;}Y$*8GhK1PJ^k5i-TQThH>Kz?gbe?8v~oT8H}xP;?13qV$#iMIvGpXjg7Aeymf)BG^ONmR&a=tIpgr_#&>!+J7Tvuybe7_V zncVDNH`G4InN50$k)n_4r-QNM%j2K{Od!0ESMy{x!a7(fbJGy5r+zX)dX4H}QG~bK z8vmVQWF9<+k2qDZXgB0wBBv{G4WlLw&64Ujl&%QISNOUc?%j1_no7-}{edTVdotS2 zW|Ayfv^Ab)#A;WyLz<}($W{C46?KQ2!Y{f;U8ng1S6OQ@jk~a$MpL|dNTyvMpgtjQ zW-|O~CwLiYz>+$Nw=Q!%`I>#cu&>T1`MB;Akq(x5gCe z<=CFXm*FgF5mQ6QB_-U8$zI-!14m?~f0^)NeFiVE*6OsQGM+@p1_)WDJ*5ZKCF&u` z<|bpF?hiAmUYA^MhWY=?r&n@B{lWzGI$4hWzlMiQNCxg*$bDEC4PYaGNZ=VPYo=4C zm$1nWxeke?O}AI6aIc7w`m30{j!pS2!@MGv8%~sk3reT4JdFN}nc=wfT)9gO`{#hy zMs7-=00ZawRNx{!Q8n#egr3alURGktWp4H~Qu=cZ)JukX2puF3Z4)fY=o7`P&8Rk# z^$}Z=-ob`xofy_jbvN(TLbhJ5Uh|76b%Ix^y=s-3#3x+CFz9M?mAGcor927z&MVG? ztXQ-;pLa|+dL31cBxP7>Q;L-+`xW~^yWdtN%l-ESOCmC{d?~jbtz;&isF8%8sbMj= zW2aQp-jU+lCO(9*?=sW6)67rO@Cf`EXpyr#u!|Mor9CVc*Ge;9t(M}>+w27t`y^&o zG9T9MW%-yt?Sgx)uPT|st;S=#T3Ara#CF<&$~Y^xHjlF$9e*i;97e8LBNJ?H zBz*#v2%b)#v=&G3FF%jXB)wFLX6~tXvxBFw5ix4dwM~jL5smXgd?ncW#i)EazNuST zpN04%0Y=2t9Y;N_0`lCBbke7c!4c&n z?J7^T?XjhcF?#L7MyLcvJ26luUc~nbWxh2+JVmYVH8G1?Xordfb&e86jJ|0GQCSFS z-H{82>{H#~*AF1k%FTw4ZNY(UK8z7C)Z+tMnCegziu>ZMGC$0* zUh0~wCh2bH56%|Xuu;vr_!qYQs5C-$gsY8hQtm6~jAUhtIzb2)HbMfv;)M#!Xo5~^ zb-ye;48q%qw_0EjFL6&*`!kzq@*2!$zLjLDghjcv!} z#)h4&+sC1ePG<^y2zBUwT{K|FNicCR3na&SG}1^z;AIJTjE9OlZ)FLPMi J{)VOl{{Uwota$(c diff --git a/scripts/events.db b/scripts/events.db index d3b817d2a036453e0de94479f85f48b5b73d0bb7..436e06e8bba877bf32990534de28e2b7c98531fe 100644 GIT binary patch delta 342 zcmZXNJxjw-7)EnTQ>7+NEGj~!sikY1_a^sK1Sc1_il7dPkhMr7W+{k~7Id^+@eVEy z4!T(!1P7sOCx3uL9sD20!AUsN;XFL)bS_P;>x!8eh5^Ic21-Up56*1U@iJk6vfRC# z<|G`at4%u5Ll|Vzpb9XYK6giY+oJd)gG$25C|#=0Z9a*KtY|W zW{K|rTMJ}HE8tQig$_&E4VPI0j7SLd_z^MpuM0^FGah2y{YDEay~(R-)kJ}cS88P< zIc7MdAVWeKa4vNGhL-ho7cG@J{QGbF&HQoF>8N{uv+_Hpk_U`Xn!zclpG8qrEEc05 DRt;He delta 58 zcmZo@;AwckJwckan1O*of1-jtWAVm>CHx#Z{3<{J2K~uA0udVv6&N?SC@$b_R*`R4 Nk!RelBF}W^0szXq5D)+W diff --git a/test/data.js b/test/data.js index bb2570b..7e3fdae 100644 --- a/test/data.js +++ b/test/data.js @@ -788,6 +788,33 @@ module.exports = { } }, message_update: { + edit_by_webhook: { + application_id: "684280192553844747", + attachments: [], + author: { + avatar: null, + bot: true, + discriminator: "0000", + id: "700285844094845050", + username: "cadence [they]" + }, + channel_id: "497161350934560778", + components: [], + content: "test 2", + edited_timestamp: "2023-08-17T06:29:34.167314+00:00", + embeds: [], + flags: 0, + guild_id: "497159726455455754", + id: "1141619794500649020", + mention_everyone: false, + mention_roles: [], + mentions: [], + pinned: false, + timestamp: "2023-08-17T06:29:29.279000+00:00", + tts: false, + type: 0, + webhook_id: "700285844094845050" + }, bot_response: { attachments: [], author: { From e3737997ec266ed40703a012b6766e8745168abb Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 17 Aug 2023 19:03:09 +1200 Subject: [PATCH 104/200] test that edits by webhook work --- d2m/converters/edit-to-changes.js | 9 +- d2m/converters/edit-to-changes.test.js | 205 ++++++++++++++----------- d2m/event-dispatcher.js | 7 + scripts/events.db | Bin 192512 -> 196608 bytes test/data.js | 27 ++++ 5 files changed, 158 insertions(+), 90 deletions(-) diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 4e6892d..3f4b2d2 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -22,7 +22,14 @@ async function editToChanges(message, guild, api) { // Figure out what events we will be replacing const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) - const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + /** @type {string?} */ + let senderMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(message.author.id) ?? null + if (senderMxid) { + const senderIsInRoom = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, senderMxid) + if (!senderIsInRoom) { + senderMxid = null // just send as ooye bot + } + } /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index bb3f3ec..674cb15 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -3,104 +3,131 @@ const {editToChanges} = require("./edit-to-changes") const data = require("../../test/data") const Ty = require("../../types") +test("edit2changes: edit by webhook", async t => { + const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_by_webhook, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "* test 2", + "m.mentions": {}, + "m.new_content": { + // *** Replaced With: *** + msgtype: "m.text", + body: "test 2", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10" + } + } + }]) + t.equal(senderMxid, null) +}) + test("edit2changes: bot response", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general, { - async getJoinedMembers(roomID) { - t.equal(roomID, "!uCtjHhfGlYbVnPVlkG:cadence.moe") - return new Promise(resolve => { - setTimeout(() => { - resolve({ - joined: { - "@cadence:cadence.moe": { - display_name: "cadence [they]", - avatar_url: "whatever" - }, - "@_ooye_botrac4r:cadence.moe": { - display_name: "botrac4r", - avatar_url: "whatever" - } - } - }) - }) - }) - } - }) - t.deepEqual(eventsToRedact, []) - t.deepEqual(eventsToSend, []) - t.deepEqual(eventsToReplace, [{ - oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", - newContent: { - $type: "m.room.message", - msgtype: "m.text", - body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", - format: "org.matrix.custom.html", - formatted_body: '* :ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', - "m.mentions": { - // Client-Server API spec 11.37.7: Copy Discord's behaviour by not re-notifying anyone that an *edit occurred* - }, - // *** Replaced With: *** - "m.new_content": { - msgtype: "m.text", - body: ":ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", - format: "org.matrix.custom.html", - formatted_body: ':ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', - "m.mentions": { - // Client-Server API spec 11.37.7: This should contain the mentions for the final version of the event - "user_ids": ["@cadence:cadence.moe"] - } - }, - "m.relates_to": { - rel_type: "m.replace", - event_id: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY" - } - } - }]) + const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general, { + async getJoinedMembers(roomID) { + t.equal(roomID, "!uCtjHhfGlYbVnPVlkG:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + } + } + }) + }) + }) + } + }) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "* :ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: '* :ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: Copy Discord's behaviour by not re-notifying anyone that an *edit occurred* + }, + // *** Replaced With: *** + "m.new_content": { + msgtype: "m.text", + body: ":ae_botrac4r: @cadence asked ``­``, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.", + format: "org.matrix.custom.html", + formatted_body: ':ae_botrac4r: @cadence asked ­, I respond: Stop drinking paint. (No)

    Hit :bn_re: to reroll.', + "m.mentions": { + // Client-Server API spec 11.37.7: This should contain the mentions for the final version of the event + "user_ids": ["@cadence:cadence.moe"] + } + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY" + } + } + }]) + t.equal(senderMxid, "@_ooye_bojack_horseman:cadence.moe") }) test("edit2changes: remove caption from image", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.removed_caption_from_image, data.guild.general, {}) - t.deepEqual(eventsToRedact, ["$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA"]) - t.deepEqual(eventsToSend, []) - t.deepEqual(eventsToReplace, []) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.removed_caption_from_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, ["$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA"]) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, []) }) test("edit2changes: add caption back to that image", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.added_caption_to_image, data.guild.general, {}) - t.deepEqual(eventsToRedact, []) - t.deepEqual(eventsToSend, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "some text", - "m.mentions": {} - }]) - t.deepEqual(eventsToReplace, []) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.added_caption_to_image, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "some text", + "m.mentions": {} + }]) + t.deepEqual(eventsToReplace, []) }) test("edit2changes: edit of reply to skull webp attachment with content", async t => { - const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(eventsToRedact, []) - t.deepEqual(eventsToSend, []) - t.deepEqual(eventsToReplace, [{ - oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", - newContent: { - $type: "m.room.message", - msgtype: "m.text", - body: "> Extremity: Image\n\n* Edit", - format: "org.matrix.custom.html", - formatted_body: - '
    In reply to Extremity' - + '
    Image
    ' - + '* Edit', - "m.mentions": {}, - "m.new_content": { - msgtype: "m.text", - body: "Edit", - "m.mentions": {} - }, - "m.relates_to": { - rel_type: "m.replace", - event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" - } - } - }]) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "> Extremity: Image\n\n* Edit", + format: "org.matrix.custom.html", + formatted_body: + '
    In reply to Extremity' + + '
    Image
    ' + + '* Edit', + "m.mentions": {}, + "m.new_content": { + msgtype: "m.text", + body: "Edit", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" + } + } + }]) }) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 4bb94ff..1527b28 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -37,6 +37,13 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message */ onMessageUpdate(client, data) { + if (data.webhook_id) { + const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + if (row) { + // The update was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. + return + } + } // Based on looking at data they've sent me over the gateway, this is the best way to check for meaningful changes. // If the message content is a string then it includes all interesting fields and is meaningful. if (typeof data.content === "string") { diff --git a/scripts/events.db b/scripts/events.db index d3b817d2a036453e0de94479f85f48b5b73d0bb7..436e06e8bba877bf32990534de28e2b7c98531fe 100644 GIT binary patch delta 342 zcmZXNJxjw-7)EnTQ>7+NEGj~!sikY1_a^sK1Sc1_il7dPkhMr7W+{k~7Id^+@eVEy z4!T(!1P7sOCx3uL9sD20!AUsN;XFL)bS_P;>x!8eh5^Ic21-Up56*1U@iJk6vfRC# z<|G`at4%u5Ll|Vzpb9XYK6giY+oJd)gG$25C|#=0Z9a*KtY|W zW{K|rTMJ}HE8tQig$_&E4VPI0j7SLd_z^MpuM0^FGah2y{YDEay~(R-)kJ}cS88P< zIc7MdAVWeKa4vNGhL-ho7cG@J{QGbF&HQoF>8N{uv+_Hpk_U`Xn!zclpG8qrEEc05 DRt;He delta 58 zcmZo@;AwckJwckan1O*of1-jtWAVm>CHx#Z{3<{J2K~uA0udVv6&N?SC@$b_R*`R4 Nk!RelBF}W^0szXq5D)+W diff --git a/test/data.js b/test/data.js index bb2570b..7e3fdae 100644 --- a/test/data.js +++ b/test/data.js @@ -788,6 +788,33 @@ module.exports = { } }, message_update: { + edit_by_webhook: { + application_id: "684280192553844747", + attachments: [], + author: { + avatar: null, + bot: true, + discriminator: "0000", + id: "700285844094845050", + username: "cadence [they]" + }, + channel_id: "497161350934560778", + components: [], + content: "test 2", + edited_timestamp: "2023-08-17T06:29:34.167314+00:00", + embeds: [], + flags: 0, + guild_id: "497159726455455754", + id: "1141619794500649020", + mention_everyone: false, + mention_roles: [], + mentions: [], + pinned: false, + timestamp: "2023-08-17T06:29:29.279000+00:00", + tts: false, + type: 0, + webhook_id: "700285844094845050" + }, bot_response: { attachments: [], author: { From 417f935b9d08c9d0f7d3476c1c2372707eaf05de Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 01:22:14 +1200 Subject: [PATCH 105/200] add error handler and message deleter --- d2m/actions/delete-message.js | 29 ++++++++++++++++++ d2m/discord-packets.js | 22 ++++++++++---- d2m/event-dispatcher.js | 57 +++++++++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 d2m/actions/delete-message.js diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js new file mode 100644 index 0000000..261c8f9 --- /dev/null +++ b/d2m/actions/delete-message.js @@ -0,0 +1,29 @@ +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../converters/edit-to-changes")} */ +const editToChanges = sync.require("../converters/edit-to-changes") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") + +/** + * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data + */ +async function deleteMessage(data) { + /** @type {string?} */ + const roomID = db.prepare("SELECT channel_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) + if (!roomID) return + + /** @type {string[]} */ + const eventsToRedact = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").pluck().all(data.id) + + for (const eventID of eventsToRedact) { + // Unfortuately, we can't specify a sender to do the redaction as, unless we find out that info via the audit logs + await api.redactEvent(roomID, eventID) + db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) + // TODO: Consider whether this code could be reused between edited messages and deleted messages. + } +} + +module.exports.deleteMessage = deleteMessage diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index b8c0af6..6ae1c22 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -16,6 +16,7 @@ const utils = { /** @type {typeof import("./event-dispatcher")} */ const eventDispatcher = sync.require("./event-dispatcher") + // Client internals, keep track of the state we need if (message.t === "READY") { if (client.ready) return client.ready = true @@ -62,16 +63,25 @@ const utils = { } } } + } + // Event dispatcher for OOYE bridge operations + try { + if (message.t === "MESSAGE_CREATE") { + eventDispatcher.onMessageCreate(client, message.d) - } else if (message.t === "MESSAGE_CREATE") { - eventDispatcher.onMessageCreate(client, message.d) + } else if (message.t === "MESSAGE_UPDATE") { + eventDispatcher.onMessageUpdate(client, message.d) - } else if (message.t === "MESSAGE_UPDATE") { - eventDispatcher.onMessageUpdate(client, message.d) + } else if (message.t === "MESSAGE_DELETE") { + eventDispatcher.onMessageDelete(client, message.d) - } else if (message.t === "MESSAGE_REACTION_ADD") { - eventDispatcher.onReactionAdd(client, message.d) + } else if (message.t === "MESSAGE_REACTION_ADD") { + eventDispatcher.onReactionAdd(client, message.d) + } + } catch (e) { + // Let OOYE try to handle errors too + eventDispatcher.onError(client, e, message) } } } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 1527b28..fde228d 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,17 +1,61 @@ const assert = require("assert").strict +const util = require("util") const {sync, db} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") /** @type {import("./actions/edit-message")}) */ const editMessage = sync.require("./actions/edit-message") - +/** @type {import("./actions/delete-message")}) */ +const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("../matrix/api")}) */ +const api = sync.require("../matrix/api") + +let lastReportedEvent = 0 // Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { + /** + * @param {import("./discord-client")} client + * @param {Error} e + * @param {import("cloudstorm").IGatewayMessage} gatewayMessage + */ + onError(client, e, gatewayMessage) { + console.error("hit event-dispatcher's error handler with this exception:") + console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? definitely need to be able to store the formatted event body to load back in later + console.error(`while handling this ${gatewayMessage.t} gateway event:`) + console.dir(gatewayMessage.d) + + if (Date.now() - lastReportedEvent > 5000) { + lastReportedEvent = Date.now() + const channelID = gatewayMessage.d.channel_id + if (channelID) { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) + let stackLines = e.stack.split("\n") + let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) + if (cloudstormLine !== -1) { + stackLines = stackLines.slice(0, cloudstormLine - 2) + } + api.sendEvent(roomID, "m.room.message", { + msgtype: "m.text", + body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.", + format: "org.matrix.custom.html", + formatted_body: "\u26a0 Bridged event from Discord not delivered" + + `
    Gateway event: ${gatewayMessage.t}` + + `
    ${stackLines.join("\n")}
    ` + + `
    Original payload` + + `
    ${util.inspect(gatewayMessage.d, false, 4, false)}
    `, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + } + }) + } + } + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message @@ -38,7 +82,7 @@ module.exports = { */ onMessageUpdate(client, data) { if (data.webhook_id) { - const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(data.webhook_id) if (row) { // The update was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. return @@ -67,5 +111,14 @@ module.exports = { if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) + }, + + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data + */ + onMessageDelete(client, data) { + console.log(data) + deleteMessage.deleteMessage(data) } } From 6de13338a86fb59ae49dccd414b008feb71397d1 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 01:22:14 +1200 Subject: [PATCH 106/200] add error handler and message deleter --- d2m/actions/delete-message.js | 29 ++++++++++++++++++ d2m/discord-packets.js | 22 ++++++++++---- d2m/event-dispatcher.js | 57 +++++++++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 d2m/actions/delete-message.js diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js new file mode 100644 index 0000000..261c8f9 --- /dev/null +++ b/d2m/actions/delete-message.js @@ -0,0 +1,29 @@ +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../converters/edit-to-changes")} */ +const editToChanges = sync.require("../converters/edit-to-changes") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") + +/** + * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data + */ +async function deleteMessage(data) { + /** @type {string?} */ + const roomID = db.prepare("SELECT channel_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) + if (!roomID) return + + /** @type {string[]} */ + const eventsToRedact = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").pluck().all(data.id) + + for (const eventID of eventsToRedact) { + // Unfortuately, we can't specify a sender to do the redaction as, unless we find out that info via the audit logs + await api.redactEvent(roomID, eventID) + db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) + // TODO: Consider whether this code could be reused between edited messages and deleted messages. + } +} + +module.exports.deleteMessage = deleteMessage diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index b8c0af6..6ae1c22 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -16,6 +16,7 @@ const utils = { /** @type {typeof import("./event-dispatcher")} */ const eventDispatcher = sync.require("./event-dispatcher") + // Client internals, keep track of the state we need if (message.t === "READY") { if (client.ready) return client.ready = true @@ -62,16 +63,25 @@ const utils = { } } } + } + // Event dispatcher for OOYE bridge operations + try { + if (message.t === "MESSAGE_CREATE") { + eventDispatcher.onMessageCreate(client, message.d) - } else if (message.t === "MESSAGE_CREATE") { - eventDispatcher.onMessageCreate(client, message.d) + } else if (message.t === "MESSAGE_UPDATE") { + eventDispatcher.onMessageUpdate(client, message.d) - } else if (message.t === "MESSAGE_UPDATE") { - eventDispatcher.onMessageUpdate(client, message.d) + } else if (message.t === "MESSAGE_DELETE") { + eventDispatcher.onMessageDelete(client, message.d) - } else if (message.t === "MESSAGE_REACTION_ADD") { - eventDispatcher.onReactionAdd(client, message.d) + } else if (message.t === "MESSAGE_REACTION_ADD") { + eventDispatcher.onReactionAdd(client, message.d) + } + } catch (e) { + // Let OOYE try to handle errors too + eventDispatcher.onError(client, e, message) } } } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 1527b28..fde228d 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,17 +1,61 @@ const assert = require("assert").strict +const util = require("util") const {sync, db} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") /** @type {import("./actions/edit-message")}) */ const editMessage = sync.require("./actions/edit-message") - +/** @type {import("./actions/delete-message")}) */ +const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("../matrix/api")}) */ +const api = sync.require("../matrix/api") + +let lastReportedEvent = 0 // Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { + /** + * @param {import("./discord-client")} client + * @param {Error} e + * @param {import("cloudstorm").IGatewayMessage} gatewayMessage + */ + onError(client, e, gatewayMessage) { + console.error("hit event-dispatcher's error handler with this exception:") + console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? definitely need to be able to store the formatted event body to load back in later + console.error(`while handling this ${gatewayMessage.t} gateway event:`) + console.dir(gatewayMessage.d) + + if (Date.now() - lastReportedEvent > 5000) { + lastReportedEvent = Date.now() + const channelID = gatewayMessage.d.channel_id + if (channelID) { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) + let stackLines = e.stack.split("\n") + let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) + if (cloudstormLine !== -1) { + stackLines = stackLines.slice(0, cloudstormLine - 2) + } + api.sendEvent(roomID, "m.room.message", { + msgtype: "m.text", + body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.", + format: "org.matrix.custom.html", + formatted_body: "\u26a0 Bridged event from Discord not delivered" + + `
    Gateway event: ${gatewayMessage.t}` + + `
    ${stackLines.join("\n")}
    ` + + `
    Original payload` + + `
    ${util.inspect(gatewayMessage.d, false, 4, false)}
    `, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + } + }) + } + } + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message @@ -38,7 +82,7 @@ module.exports = { */ onMessageUpdate(client, data) { if (data.webhook_id) { - const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(data.webhook_id) if (row) { // The update was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. return @@ -67,5 +111,14 @@ module.exports = { if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) + }, + + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data + */ + onMessageDelete(client, data) { + console.log(data) + deleteMessage.deleteMessage(data) } } From 750a8cd60aeb217e29f3224226ea0f83763d8db5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 01:22:33 +1200 Subject: [PATCH 107/200] update discord-markdown to escape less stuff --- d2m/converters/message-to-event.test.js | 10 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- test/data.js | 31 +++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index df94196..86942a7 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -39,6 +39,16 @@ test("message2event: simple plaintext", async t => { }]) }) +test("message2event: simple plaintext with quotes", async t => { + const events = await messageToEvent(data.message.simple_plaintext_with_quotes, data.guild.general, {}) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: `then he said, "you and her aren't allowed in here!"` + }]) +}) + test("message2event: simple user mention", async t => { const events = await messageToEvent(data.message.simple_user_mention, data.guild.general, {}) t.deepEqual(events, [{ diff --git a/package-lock.json b/package-lock.json index c6b6004..875e329 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", @@ -1051,7 +1051,7 @@ }, "node_modules/discord-markdown": { "version": "2.4.1", - "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", + "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.2" diff --git a/package.json b/package.json index 6557500..bc0a0db 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", diff --git a/test/data.js b/test/data.js index 7e3fdae..a1d3ece 100644 --- a/test/data.js +++ b/test/data.js @@ -170,6 +170,37 @@ module.exports = { flags: 0, components: [] }, + simple_plaintext_with_quotes: { + id: "1126733830494093454", + type: 0, + content: `then he said, "you and her aren't allowed in here!"`, + channel_id: "112760669178241024", + author: { + id: "111604486476181504", + username: "kyuugryphon", + avatar: "e4ce31267ca524d19be80e684d4cafa1", + discriminator: "0", + public_flags: 0, + flags: 0, + banner: null, + accent_color: null, + global_name: "KyuuGryphon", + avatar_decoration: null, + display_name: "KyuuGryphon", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T04:37:58.892000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, simple_user_mention: { id: "1126739682080858234", type: 0, From 2d14e843127c1479e4c1246b74d65222b7508202 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 01:22:33 +1200 Subject: [PATCH 108/200] update discord-markdown to escape less stuff --- d2m/converters/message-to-event.test.js | 10 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- test/data.js | 31 +++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index df94196..86942a7 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -39,6 +39,16 @@ test("message2event: simple plaintext", async t => { }]) }) +test("message2event: simple plaintext with quotes", async t => { + const events = await messageToEvent(data.message.simple_plaintext_with_quotes, data.guild.general, {}) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: `then he said, "you and her aren't allowed in here!"` + }]) +}) + test("message2event: simple user mention", async t => { const events = await messageToEvent(data.message.simple_user_mention, data.guild.general, {}) t.deepEqual(events, [{ diff --git a/package-lock.json b/package-lock.json index c6b6004..875e329 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", @@ -1051,7 +1051,7 @@ }, "node_modules/discord-markdown": { "version": "2.4.1", - "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", + "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.2" diff --git a/package.json b/package.json index 6557500..bc0a0db 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#9799e4f79912d07f89a030e479e82fcc1e75bc81", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", diff --git a/test/data.js b/test/data.js index 7e3fdae..a1d3ece 100644 --- a/test/data.js +++ b/test/data.js @@ -170,6 +170,37 @@ module.exports = { flags: 0, components: [] }, + simple_plaintext_with_quotes: { + id: "1126733830494093454", + type: 0, + content: `then he said, "you and her aren't allowed in here!"`, + channel_id: "112760669178241024", + author: { + id: "111604486476181504", + username: "kyuugryphon", + avatar: "e4ce31267ca524d19be80e684d4cafa1", + discriminator: "0", + public_flags: 0, + flags: 0, + banner: null, + accent_color: null, + global_name: "KyuuGryphon", + avatar_decoration: null, + display_name: "KyuuGryphon", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T04:37:58.892000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, simple_user_mention: { id: "1126739682080858234", type: 0, From 9de940471dbde4fe33a92e203c4815bccce199f7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 01:23:53 +1200 Subject: [PATCH 109/200] remove redactions from database in edit flow --- d2m/actions/edit-message.js | 7 +++++-- db/ooye.db | Bin 360448 -> 360448 bytes scripts/events.db | Bin 196608 -> 208896 bytes 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 9a329b6..1c1b90e 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -14,7 +14,8 @@ const api = sync.require("../../matrix/api") async function editMessage(message, guild) { console.log(`*** applying edit for message ${message.id} in channel ${message.channel_id}`) const {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid} = await editToChanges.editToChanges(message, guild, api) - console.log("making these changes:", {eventsToRedact, eventsToReplace, eventsToSend}) + console.log("making these changes:") + console.dir({eventsToRedact, eventsToReplace, eventsToSend}, {depth: null}) // 1. Replace all the things. for (const {oldID, newContent} of eventsToReplace) { @@ -34,7 +35,9 @@ async function editMessage(message, guild) { // Not redacting as the last action because the last action is likely to be shown in the room preview in clients, and we don't want it to look like somebody actually deleted a message. for (const eventID of eventsToRedact) { await api.redactEvent(roomID, eventID, senderMxid) - // TODO: I should almost certainly remove the redacted event from our database now, shouldn't I? I mean, it's literally not there any more... you can't do anything else with it... + // TODO: Reconsider whether it's the right thing to do to delete it from our database? I mean, it's literally not there any more... you can't do anything else with it... + // and you definitely want to mark it in *some* way to prevent duplicate redactions... + db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) // TODO: If I just redacted part = 0, I should update one of the other events to make it the new part = 0, right? // TODO: Consider whether this code could be reused between edited messages and deleted messages. } diff --git a/db/ooye.db b/db/ooye.db index 1c52510cc5f1d7902d1e6a05829750a2a0dfba70..c691745001bc56419c8960a59933537a66d9f44f 100644 GIT binary patch delta 13898 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 V8WR?=VUKGwBW%3=NBpWI{~vdJsG|S? delta 5713 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 From 6246dc3a7f7ddbea0bb75b2c1912912dbbe238f7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 01:23:53 +1200 Subject: [PATCH 110/200] remove redactions from database in edit flow --- d2m/actions/edit-message.js | 7 +++++-- scripts/events.db | Bin 196608 -> 208896 bytes 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 9a329b6..1c1b90e 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -14,7 +14,8 @@ const api = sync.require("../../matrix/api") async function editMessage(message, guild) { console.log(`*** applying edit for message ${message.id} in channel ${message.channel_id}`) const {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid} = await editToChanges.editToChanges(message, guild, api) - console.log("making these changes:", {eventsToRedact, eventsToReplace, eventsToSend}) + console.log("making these changes:") + console.dir({eventsToRedact, eventsToReplace, eventsToSend}, {depth: null}) // 1. Replace all the things. for (const {oldID, newContent} of eventsToReplace) { @@ -34,7 +35,9 @@ async function editMessage(message, guild) { // Not redacting as the last action because the last action is likely to be shown in the room preview in clients, and we don't want it to look like somebody actually deleted a message. for (const eventID of eventsToRedact) { await api.redactEvent(roomID, eventID, senderMxid) - // TODO: I should almost certainly remove the redacted event from our database now, shouldn't I? I mean, it's literally not there any more... you can't do anything else with it... + // TODO: Reconsider whether it's the right thing to do to delete it from our database? I mean, it's literally not there any more... you can't do anything else with it... + // and you definitely want to mark it in *some* way to prevent duplicate redactions... + db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) // TODO: If I just redacted part = 0, I should update one of the other events to make it the new part = 0, right? // TODO: Consider whether this code could be reused between edited messages and deleted messages. } diff --git a/scripts/events.db b/scripts/events.db index 436e06e8bba877bf32990534de28e2b7c98531fe..045e1471b97b6274aebcb9b2f457c3bc3ab5b898 100644 GIT binary patch delta 2896 zcmeHJTWlQF8TO3h3rW^%L(<#`K3=JDTiLU7zpSW|7zD9!$}Lt25=qa_p4~n1?40$S znVqb&mYou{K}9OjRGPFe2m%j0^uemCyz~Vm1Ollg%2VG`+B{Y&s`}EB(*M|-U{K2g zmHO0|-JNs!FW>)t-|YJO?)6){UpX|iJ}@v~TtP>r^^G6AQ5;o^Cf9(-sQ;6qJcoSgje~{^RhaW66J!#;lm0Vi>Ax*#9Ptu{}-v zHPaNf`i3;R;i&nI%krKtHluIL@-Q!4e&HTwe-TI+70pvLL$^)uUWwVQhlZqW)m@CE z{!p&p{!~6RI9`B(?igGcxbNN3-wt;M7dDn39vuGB^o7F9N3-S5-lFYlhHAK;0Y!PL zo87qc;J0u{1IlB?XXO?S8@TKNDr)%&MtKq?vrkY?sNYPIJmAvdiT*PBeEAROrTy72 z|Ng*!+G;49!4w{sTck$&p|TsjQl@_Q;2K$3n0wL*IW}8o=bo|g)2HT_PCs|*ynlT5 znRCy()`{x*#R$*pJTS}6IBxUue+pTeDK!agG~-eQ*t9xRYDcU?Ya}XFE|fytBBe^{ zct{tZuM+Im2=^np){ZF)VdqqnARe=*izr99%90pi1ihoQ$v8niYqgVr(+&aFg*6iT zlyKNzB8bL_w%UwyS`7%Qvj{b53&jjA5z>Yr=U8HNYcWOWJJ~#fV_PiCEku5!^r#p~p><=M9CIJ)PkmY#idXXAE)&=jc~h7YN*;pCvg<9^hB40I0sto%e6nr z`|K~rfx23Rm#ZYkf*|E84nq?0avh%ySc56;uyM4b-(%hBxbbLs&U|(`Rv(2DU8nK= z#iselyp|qcnydPYb0?ea=f1bHG{^k%H;uc@||GN zV^mE-AOof~z!x>KAWS17I0nMhfnzm-rxc{1M_8*xfo&`l@O*x?wE6-Jq$wH?&e9ri zTGI?uGc8*;HOKU9)i$A!MnZ%9rJM5Mp}2d`y)CitX{O`Yx@MO0SFcIJy?MUwNmE1a z`NGx3>}|syb55yl#d0gErMQ-Led0~&^+6c3qk(%Enri6a=dZmX+Y@&WT9#sZp5uD? z#8r9kT8oA?kPShs4)UOJQ|Mq?a4;Y-e76JK1v(3Hv~Z2Ux&fq2A`Y|#Rtq^ZL^#J? zB-$sK#~3z&>zm>bs)fjcwSEAy1r3Al;8Dn<8etcd3+RHv`#6c4Nr>A(;|7VT4;ldj z2>l>|riVAF(kS2SC3O+EXpUdsP5Di^Z9zWdHUz?u>3krowg$kqER=+@!76G3HjH(1Ih@e?vHfSki@R|fT zSSXg}0YLZjP? z=b2899|D^6dOoXyB}Kg+I*I}?pJ;P3A~Q7TV^JFI10ei2gz6D^Rsd-s|0M^E>&G_vMZ3$+xDn zPub|OG_q%4WY5O(P8c&?EnGAHKjyCmX~jr qmuK8AFVFP!0tXY%DF)tyJf}7m?&D!@EGe3Pk%L)s`?EhxQp^As2O1^- From de610f08f37b89db601983008f91ebdfb1d9d02e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 16:58:46 +1200 Subject: [PATCH 111/200] bug fix where errors weren't being sent --- d2m/actions/delete-message.js | 5 +---- d2m/actions/edit-message.js | 3 --- d2m/actions/register-user.js | 7 +++++++ d2m/actions/send-message.js | 3 +-- d2m/discord-packets.js | 10 +++++----- d2m/event-dispatcher.js | 18 +++++++++--------- matrix/api.js | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js index 261c8f9..c9b43ee 100644 --- a/d2m/actions/delete-message.js +++ b/d2m/actions/delete-message.js @@ -2,8 +2,6 @@ const passthrough = require("../../passthrough") const { sync, db } = passthrough -/** @type {import("../converters/edit-to-changes")} */ -const editToChanges = sync.require("../converters/edit-to-changes") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") @@ -12,7 +10,7 @@ const api = sync.require("../../matrix/api") */ async function deleteMessage(data) { /** @type {string?} */ - const roomID = db.prepare("SELECT channel_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) if (!roomID) return /** @type {string[]} */ @@ -22,7 +20,6 @@ async function deleteMessage(data) { // Unfortuately, we can't specify a sender to do the redaction as, unless we find out that info via the audit logs await api.redactEvent(roomID, eventID) db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) - // TODO: Consider whether this code could be reused between edited messages and deleted messages. } } diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 1c1b90e..8e8c838 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -35,11 +35,8 @@ async function editMessage(message, guild) { // Not redacting as the last action because the last action is likely to be shown in the room preview in clients, and we don't want it to look like somebody actually deleted a message. for (const eventID of eventsToRedact) { await api.redactEvent(roomID, eventID, senderMxid) - // TODO: Reconsider whether it's the right thing to do to delete it from our database? I mean, it's literally not there any more... you can't do anything else with it... - // and you definitely want to mark it in *some* way to prevent duplicate redactions... db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) // TODO: If I just redacted part = 0, I should update one of the other events to make it the new part = 0, right? - // TODO: Consider whether this code could be reused between edited messages and deleted messages. } // 3. Send all the things. diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 1455360..19c3a6d 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -115,8 +115,14 @@ function calculateProfileEventContentHash(content) { } /** + * Sync profile data for a sim user. This function follows the following process: + * 1. Join the sim to the room if needed + * 2. Make an object of what the new room member state content would be, including uploading the profile picture if it hasn't been done before + * 3. Compare against the previously known state content, which is helpfully stored in the database + * 4. If the state content has changes, send it to Matrix and update it in the database for next time * @param {import("discord-api-types/v10").APIUser} user * @param {Omit} member + * @returns {Promise} mxid of the updated sim */ async function syncUser(user, member, guildID, roomID) { const mxid = await ensureSimJoined(user, roomID) @@ -128,6 +134,7 @@ async function syncUser(user, member, guildID, roomID) { await api.sendState(roomID, "m.room.member", mxid, content, mxid) db.prepare("UPDATE sim_member SET profile_event_content_hash = ? WHERE room_id = ? AND mxid = ?").run(profileEventContentHash, roomID, mxid) } + return mxid } async function syncAllUsersInRoom(roomID) { diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index cf87d35..2132905 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -23,8 +23,7 @@ async function sendMessage(message, guild) { let senderMxid = null if (!message.webhook_id) { assert(message.member) - senderMxid = await registerUser.ensureSimJoined(message.author, roomID) - await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + senderMxid = await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } const events = await messageToEvent.messageToEvent(message, guild, {}, {api}) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 6ae1c22..c0ba1a6 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -11,7 +11,7 @@ const utils = { * @param {import("./discord-client")} client * @param {import("cloudstorm").IGatewayMessage} message */ - onPacket(client, message) { + async 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") @@ -68,16 +68,16 @@ const utils = { // Event dispatcher for OOYE bridge operations try { if (message.t === "MESSAGE_CREATE") { - eventDispatcher.onMessageCreate(client, message.d) + await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { - eventDispatcher.onMessageUpdate(client, message.d) + await eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_DELETE") { - eventDispatcher.onMessageDelete(client, message.d) + await eventDispatcher.onMessageDelete(client, message.d) } else if (message.t === "MESSAGE_REACTION_ADD") { - eventDispatcher.onReactionAdd(client, message.d) + await eventDispatcher.onReactionAdd(client, message.d) } } catch (e) { // Let OOYE try to handle errors too diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index fde228d..8e64591 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -27,7 +27,7 @@ module.exports = { console.error("hit event-dispatcher's error handler with this exception:") console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? definitely need to be able to store the formatted event body to load back in later console.error(`while handling this ${gatewayMessage.t} gateway event:`) - console.dir(gatewayMessage.d) + console.dir(gatewayMessage.d, {depth: null}) if (Date.now() - lastReportedEvent > 5000) { lastReportedEvent = Date.now() @@ -60,7 +60,7 @@ module.exports = { * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ - onMessageCreate(client, message) { + async onMessageCreate(client, message) { if (message.webhook_id) { const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) if (row) { @@ -73,14 +73,14 @@ module.exports = { if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) - sendMessage.sendMessage(message, guild) + await sendMessage.sendMessage(message, guild) }, /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message */ - onMessageUpdate(client, data) { + async onMessageUpdate(client, data) { if (data.webhook_id) { const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(data.webhook_id) if (row) { @@ -98,7 +98,7 @@ module.exports = { if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) - editMessage.editMessage(message, guild) + await editMessage.editMessage(message, guild) } }, @@ -106,19 +106,19 @@ module.exports = { * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ - onReactionAdd(client, data) { + async onReactionAdd(client, data) { if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) - addReaction.addReaction(data) + await addReaction.addReaction(data) }, /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data */ - onMessageDelete(client, data) { + async onMessageDelete(client, data) { console.log(data) - deleteMessage.deleteMessage(data) + await deleteMessage.deleteMessage(data) } } diff --git a/matrix/api.js b/matrix/api.js index ef3e199..81d8a16 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -134,7 +134,7 @@ async function sendEvent(roomID, type, content, mxid, timestamp) { */ async function redactEvent(roomID, eventID, mxid) { /** @type {Ty.R.EventRedacted} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid)) + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid), {}) return root.event_id } From e08262388bc40ce697680380bb6d1c4fd7fc3197 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 16:58:46 +1200 Subject: [PATCH 112/200] bug fix where errors weren't being sent --- d2m/actions/delete-message.js | 5 +---- d2m/actions/edit-message.js | 3 --- d2m/actions/register-user.js | 7 +++++++ d2m/actions/send-message.js | 3 +-- d2m/discord-packets.js | 10 +++++----- d2m/event-dispatcher.js | 18 +++++++++--------- matrix/api.js | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js index 261c8f9..c9b43ee 100644 --- a/d2m/actions/delete-message.js +++ b/d2m/actions/delete-message.js @@ -2,8 +2,6 @@ const passthrough = require("../../passthrough") const { sync, db } = passthrough -/** @type {import("../converters/edit-to-changes")} */ -const editToChanges = sync.require("../converters/edit-to-changes") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") @@ -12,7 +10,7 @@ const api = sync.require("../../matrix/api") */ async function deleteMessage(data) { /** @type {string?} */ - const roomID = db.prepare("SELECT channel_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) if (!roomID) return /** @type {string[]} */ @@ -22,7 +20,6 @@ async function deleteMessage(data) { // Unfortuately, we can't specify a sender to do the redaction as, unless we find out that info via the audit logs await api.redactEvent(roomID, eventID) db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) - // TODO: Consider whether this code could be reused between edited messages and deleted messages. } } diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 1c1b90e..8e8c838 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -35,11 +35,8 @@ async function editMessage(message, guild) { // Not redacting as the last action because the last action is likely to be shown in the room preview in clients, and we don't want it to look like somebody actually deleted a message. for (const eventID of eventsToRedact) { await api.redactEvent(roomID, eventID, senderMxid) - // TODO: Reconsider whether it's the right thing to do to delete it from our database? I mean, it's literally not there any more... you can't do anything else with it... - // and you definitely want to mark it in *some* way to prevent duplicate redactions... db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) // TODO: If I just redacted part = 0, I should update one of the other events to make it the new part = 0, right? - // TODO: Consider whether this code could be reused between edited messages and deleted messages. } // 3. Send all the things. diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 1455360..19c3a6d 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -115,8 +115,14 @@ function calculateProfileEventContentHash(content) { } /** + * Sync profile data for a sim user. This function follows the following process: + * 1. Join the sim to the room if needed + * 2. Make an object of what the new room member state content would be, including uploading the profile picture if it hasn't been done before + * 3. Compare against the previously known state content, which is helpfully stored in the database + * 4. If the state content has changes, send it to Matrix and update it in the database for next time * @param {import("discord-api-types/v10").APIUser} user * @param {Omit} member + * @returns {Promise} mxid of the updated sim */ async function syncUser(user, member, guildID, roomID) { const mxid = await ensureSimJoined(user, roomID) @@ -128,6 +134,7 @@ async function syncUser(user, member, guildID, roomID) { await api.sendState(roomID, "m.room.member", mxid, content, mxid) db.prepare("UPDATE sim_member SET profile_event_content_hash = ? WHERE room_id = ? AND mxid = ?").run(profileEventContentHash, roomID, mxid) } + return mxid } async function syncAllUsersInRoom(roomID) { diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index cf87d35..2132905 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -23,8 +23,7 @@ async function sendMessage(message, guild) { let senderMxid = null if (!message.webhook_id) { assert(message.member) - senderMxid = await registerUser.ensureSimJoined(message.author, roomID) - await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + senderMxid = await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } const events = await messageToEvent.messageToEvent(message, guild, {}, {api}) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 6ae1c22..c0ba1a6 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -11,7 +11,7 @@ const utils = { * @param {import("./discord-client")} client * @param {import("cloudstorm").IGatewayMessage} message */ - onPacket(client, message) { + async 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") @@ -68,16 +68,16 @@ const utils = { // Event dispatcher for OOYE bridge operations try { if (message.t === "MESSAGE_CREATE") { - eventDispatcher.onMessageCreate(client, message.d) + await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { - eventDispatcher.onMessageUpdate(client, message.d) + await eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_DELETE") { - eventDispatcher.onMessageDelete(client, message.d) + await eventDispatcher.onMessageDelete(client, message.d) } else if (message.t === "MESSAGE_REACTION_ADD") { - eventDispatcher.onReactionAdd(client, message.d) + await eventDispatcher.onReactionAdd(client, message.d) } } catch (e) { // Let OOYE try to handle errors too diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index fde228d..8e64591 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -27,7 +27,7 @@ module.exports = { console.error("hit event-dispatcher's error handler with this exception:") console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? definitely need to be able to store the formatted event body to load back in later console.error(`while handling this ${gatewayMessage.t} gateway event:`) - console.dir(gatewayMessage.d) + console.dir(gatewayMessage.d, {depth: null}) if (Date.now() - lastReportedEvent > 5000) { lastReportedEvent = Date.now() @@ -60,7 +60,7 @@ module.exports = { * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ - onMessageCreate(client, message) { + async onMessageCreate(client, message) { if (message.webhook_id) { const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) if (row) { @@ -73,14 +73,14 @@ module.exports = { if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) - sendMessage.sendMessage(message, guild) + await sendMessage.sendMessage(message, guild) }, /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message */ - onMessageUpdate(client, data) { + async onMessageUpdate(client, data) { if (data.webhook_id) { const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(data.webhook_id) if (row) { @@ -98,7 +98,7 @@ module.exports = { if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) - editMessage.editMessage(message, guild) + await editMessage.editMessage(message, guild) } }, @@ -106,19 +106,19 @@ module.exports = { * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ - onReactionAdd(client, data) { + async onReactionAdd(client, data) { if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) - addReaction.addReaction(data) + await addReaction.addReaction(data) }, /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data */ - onMessageDelete(client, data) { + async onMessageDelete(client, data) { console.log(data) - deleteMessage.deleteMessage(data) + await deleteMessage.deleteMessage(data) } } diff --git a/matrix/api.js b/matrix/api.js index ef3e199..81d8a16 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -134,7 +134,7 @@ async function sendEvent(roomID, type, content, mxid, timestamp) { */ async function redactEvent(roomID, eventID, mxid) { /** @type {Ty.R.EventRedacted} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid)) + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid), {}) return root.event_id } From 232a9b7caeb3d66e473944d5ee217270495965aa Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 18 Aug 2023 17:00:40 +1200 Subject: [PATCH 113/200] only include necessary data for testing --- db/data-for-test.sql | 94 +++++++++++++++++++++++++++++++++ db/ooye.sql | 120 ------------------------------------------- matrix/file.js | 21 +++++--- scripts/events.db | Bin 208896 -> 249856 bytes test/test.js | 8 ++- 5 files changed, 114 insertions(+), 129 deletions(-) create mode 100644 db/data-for-test.sql delete mode 100644 db/ooye.sql diff --git a/db/data-for-test.sql b/db/data-for-test.sql new file mode 100644 index 0000000..fa04562 --- /dev/null +++ b/db/data-for-test.sql @@ -0,0 +1,94 @@ +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "guild_space" ( + "guild_id" TEXT NOT NULL UNIQUE, + "space_id" TEXT NOT NULL UNIQUE, + PRIMARY KEY("guild_id") +); +CREATE TABLE IF NOT EXISTS "file" ( + "discord_url" TEXT NOT NULL UNIQUE, + "mxc_url" TEXT NOT NULL UNIQUE, + PRIMARY KEY("discord_url") +); +CREATE TABLE IF NOT EXISTS "sim" ( + "discord_id" TEXT NOT NULL UNIQUE, + "sim_name" TEXT NOT NULL UNIQUE, + "localpart" TEXT NOT NULL UNIQUE, + "mxid" TEXT NOT NULL UNIQUE, + PRIMARY KEY("discord_id") +); +CREATE TABLE IF NOT EXISTS "sim_member" ( + "mxid" TEXT NOT NULL, + "room_id" TEXT NOT NULL, + "profile_event_content_hash" BLOB, + PRIMARY KEY("mxid","room_id") +); +CREATE TABLE IF NOT EXISTS "webhook" ( + "channel_id" TEXT NOT NULL UNIQUE, + "webhook_id" TEXT NOT NULL UNIQUE, + "webhook_token" TEXT NOT NULL, + PRIMARY KEY("channel_id") +); +CREATE TABLE IF NOT EXISTS "channel_room" ( + "channel_id" TEXT NOT NULL UNIQUE, + "room_id" TEXT NOT NULL UNIQUE, + "name" TEXT, + "nick" TEXT, + PRIMARY KEY("channel_id") +); +CREATE TABLE IF NOT EXISTS "event_message" ( + "event_id" TEXT NOT NULL, + "event_type" TEXT, + "event_subtype" TEXT, + "message_id" TEXT NOT NULL, + "channel_id" TEXT, + "part" INTEGER NOT NULL, + "source" INTEGER NOT NULL, + PRIMARY KEY("event_id","message_id") +); +COMMIT; + + + +BEGIN TRANSACTION; + +INSERT INTO guild_space (guild_id, space_id) VALUES +('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); + +INSERT INTO channel_room (channel_id, room_id, name, nick) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main'), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots'); + +INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES +('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), +('820865262526005258', 'crunch_god', '_ooye_crunch_god', '@_ooye_crunch_god:cadence.moe'), +('771520384671416320', 'bojack_horseman', '_ooye_bojack_horseman', '@_ooye_bojack_horseman:cadence.moe'), +('112890272819507200', '.wing.', '_ooye_.wing.', '@_ooye_.wing.:cadence.moe'), +('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'); + +INSERT INTO sim_member (mxid, room_id, profile_event_content_hash) VALUES +('@_ooye_bojack_horseman:cadence.moe', '!uCtjHhfGlYbVnPVlkG:cadence.moe', NULL); + +INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES +('$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg', 'm.room.message', 'm.text', '1126786462646550579', '112760669178241024', 0, 1), +('$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4', 'm.room.message', 'm.text', '1128118177155526666', '112760669178241024', 0, 0), +('$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10', 'm.room.message', 'm.text', '1141619794500649020', '497161350934560778', 0, 1), +('$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY', 'm.room.message', 'm.text', '1141206225632112650', '160197704226439168', 0, 1), +('$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA', 'm.room.message', 'm.text', '1141501302736695316', '112760669178241024', 0, 1), +('$51f4yqHinwnSbPEQ9dCgoyy4qiIJSX0QYYVUnvwyTCI', 'm.room.message', 'm.image', '1141501302736695316', '112760669178241024', 1, 1), +('$51f4yqHinwnSbPEQ9dCgoyy4qiIJSX0QYYVUnvwyTCJ', 'm.room.message', 'm.image', '1141501302736695317', '112760669178241024', 0, 1), +('$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M', 'm.room.message', 'm.text', '1128084851279536279', '112760669178241024', 0, 1), +('$YUJFa5j0ZJe7PUvD2DykRt9g51RoadUEYmuJLdSEbJ0', 'm.room.message', 'm.image', '1128084851279536279', '112760669178241024', 1, 1), +('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1); + +INSERT INTO file (discord_url, mxc_url) VALUES +('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), +('https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png', 'mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus'), +('https://cdn.discordapp.com/stickers/1106323941183717586.png', 'mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn'), +('https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp', 'mxc://cadence.moe/sDxWmDErBhYBxtDcJQgBETes'), +('https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png', 'mxc://cadence.moe/KQYdXKRcHWjDYDLPkTOOWOjA'), +('https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg', 'mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa'), +('https://cdn.discordapp.com/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024', 'mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl'), +('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'); + +COMMIT; diff --git a/db/ooye.sql b/db/ooye.sql deleted file mode 100644 index 23c5aee..0000000 --- a/db/ooye.sql +++ /dev/null @@ -1,120 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE IF NOT EXISTS "guild_space" ( - "guild_id" TEXT NOT NULL UNIQUE, - "space_id" TEXT NOT NULL UNIQUE, - PRIMARY KEY("guild_id") -); -INSERT INTO guild_space VALUES('112760669178241024','!jjWAGMeQdNrVZSSfvz:cadence.moe'); -CREATE TABLE IF NOT EXISTS "channel_room" ( - "channel_id" TEXT NOT NULL UNIQUE, - "room_id" TEXT NOT NULL UNIQUE, - PRIMARY KEY("channel_id") -); -INSERT INTO channel_room VALUES('395073525641117699','!HfUDHZheYriEncfKpJ:cadence.moe'); -INSERT INTO channel_room VALUES('808564048294838284','!uMHZzVXeXPrVehmPIm:cadence.moe'); -INSERT INTO channel_room VALUES('694935757160185907','!qAAlGzsrXrLKqAEHas:cadence.moe'); -INSERT INTO channel_room VALUES('739017679796436992','!cGIULAxjVJReFZosbK:cadence.moe'); -INSERT INTO channel_room VALUES('441910023501774858','!JrwSgmaBchooVnCqcF:cadence.moe'); -INSERT INTO channel_room VALUES('841417575120371722','!mwzCPYqPHAHOdeKPMi:cadence.moe'); -INSERT INTO channel_room VALUES('488516474735034389','!dqvFUrlHREgNEWmkua:cadence.moe'); -INSERT INTO channel_room VALUES('920170211330650152','!sTfMRJPdoqTNzMXvbn:cadence.moe'); -INSERT INTO channel_room VALUES('203751294157062145','!repkBsIWwFyOOvudGt:cadence.moe'); -INSERT INTO channel_room VALUES('134477188899536898','!uUzBXdWQblkxNmThLz:cadence.moe'); -INSERT INTO channel_room VALUES('266767590641238027','!IzOgQiDnusFQiwymaL:cadence.moe'); -INSERT INTO channel_room VALUES('834530844743434289','!kczmYjDXhhTVvCRHGE:cadence.moe'); -INSERT INTO channel_room VALUES('265998010092093441','!zGfmedYghbfnFQJXjU:cadence.moe'); -INSERT INTO channel_room VALUES('339294043274215424','!fNDEjktDGIWAPlMWYy:cadence.moe'); -INSERT INTO channel_room VALUES('398661111869865985','!gAnxDqVCDfudmiuwnU:cadence.moe'); -INSERT INTO channel_room VALUES('814300638237163550','!GTkemJlzNRPcbXIjHh:cadence.moe'); -INSERT INTO channel_room VALUES('112760669178241024','!kLRqKKUQXcibIMtOpl:cadence.moe'); -INSERT INTO channel_room VALUES('604901695255740426','!gsMllRhFEUVaVAiJQR:cadence.moe'); -INSERT INTO channel_room VALUES('176333891320283136','!pzHnyQchkiIJzfjYlR:cadence.moe'); -INSERT INTO channel_room VALUES('350076239257796620','!MkOZEeLWrKJLpYjCig:cadence.moe'); -INSERT INTO channel_room VALUES('160197704226439168','!uCtjHhfGlYbVnPVlkG:cadence.moe'); -INSERT INTO channel_room VALUES('204427407539568640','!aaBHsbHThhtFBjJnjs:cadence.moe'); -INSERT INTO channel_room VALUES('288058913985789953','!TBwEJEXTvbvDSYzyJq:cadence.moe'); -INSERT INTO channel_room VALUES('622903842282930198','!RCRvXmNpFSjoFNEmvh:cadence.moe'); -INSERT INTO channel_room VALUES('421368191567265794','!cKgTdFmarjJnGDipUE:cadence.moe'); -INSERT INTO channel_room VALUES('334553412698112002','!EacGxEcLvxmrSPZoOA:cadence.moe'); -INSERT INTO channel_room VALUES('395750520435769347','!btmhtSzFqMYqTzNGmq:cadence.moe'); -INSERT INTO channel_room VALUES('196455508146651136','!QGSDlxmJjIGzqmhngS:cadence.moe'); -INSERT INTO channel_room VALUES('1089120827926134854','!QlsPSkhOsplXOgvKzG:cadence.moe'); -INSERT INTO channel_room VALUES('655216173696286746','!prNIHWeXgudqChdvTJ:cadence.moe'); -INSERT INTO channel_room VALUES('171083630985216001','!kEWSvtyEhPFCviOHnA:cadence.moe'); -INSERT INTO channel_room VALUES('530220226109898752','!bdNsNYyBQyEdFmeoZd:cadence.moe'); -INSERT INTO channel_room VALUES('1075095715396735056','!gPfVCpFUnmoEDVAPph:cadence.moe'); -INSERT INTO channel_room VALUES('392141322863116319','!ErHIqXEnsVdvHbFAUO:cadence.moe'); -INSERT INTO channel_room VALUES('189898393705906177','!UAXXOjEgIYtexsvzZC:cadence.moe'); -INSERT INTO channel_room VALUES('134077753485033472','!qGpHzEREMSFEKDjzdB:cadence.moe'); -INSERT INTO channel_room VALUES('696180458417029170','!thbRkzupoPmsxCiWyv:cadence.moe'); -INSERT INTO channel_room VALUES('191487489943404544','!nOfpEIzWgaLnRHFoAF:cadence.moe'); -INSERT INTO channel_room VALUES('121380024812044288','!QKqjAQJmXwnevnKsGh:cadence.moe'); -INSERT INTO channel_room VALUES('687028734322147344','!fGgIymcYWOqjbSRUdV:cadence.moe'); -INSERT INTO channel_room VALUES('330164254969823233','!vYZSKYOSArwqWyaJZB:cadence.moe'); -INSERT INTO channel_room VALUES('872258827846832139','!INBHUKdIiCMxpizitC:cadence.moe'); -INSERT INTO channel_room VALUES('112767097234305024','!bXgJfZZqdFlmmAeYSj:cadence.moe'); -INSERT INTO channel_room VALUES('249968792346558465','!CRoJhPJAarFkoOVfUg:cadence.moe'); -INSERT INTO channel_room VALUES('130176644093575168','!tgQCmRkBndtQcbhqPX:cadence.moe'); -INSERT INTO channel_room VALUES('312054608535224320','!oDNMTEymekxHTizhEC:cadence.moe'); -INSERT INTO channel_room VALUES('1099031887500034088','!bexkxPoPBmSuUkvACP:cadence.moe'); -INSERT INTO channel_room VALUES('412754166885122048','!ASqMFolSWJsJWAIJqB:cadence.moe'); -INSERT INTO channel_room VALUES('700941324810977333','!gLvyxfpmPetCqtOCka:cadence.moe'); -INSERT INTO channel_room VALUES('361364140448808960','!yyzLRrLSmYGuNGkbup:cadence.moe'); -INSERT INTO channel_room VALUES('132423337019310081','!QcGgaDKBEDvSnFpDUT:cadence.moe'); -INSERT INTO channel_room VALUES('698892233398812742','!ASiHdjKOmjyQCmhWEJ:cadence.moe'); -INSERT INTO channel_room VALUES('591183598166736908','!NMmIckmkngIVeTLmWM:cadence.moe'); -INSERT INTO channel_room VALUES('288882953314893825','!hNBIHkfRMrryuWfPrb:cadence.moe'); -INSERT INTO channel_room VALUES('265617582126661642','!YaJsBSpCOtaccZShBy:cadence.moe'); -INSERT INTO channel_room VALUES('113414562417496064','!UHfjZYvdnkjqwvOylF:cadence.moe'); -INSERT INTO channel_room VALUES('373335332436967424','!cFjDyGrtFmHymyLfRE:cadence.moe'); -INSERT INTO channel_room VALUES('331390333810376704','!kdALuKGeNSkYDgRhIZ:cadence.moe'); -INSERT INTO channel_room VALUES('768264034327724132','!FEhofHtvWOSNCuvUxW:cadence.moe'); -INSERT INTO channel_room VALUES('359903425074561024','!IGixYyKLdvmnAzzOGB:cadence.moe'); -INSERT INTO channel_room VALUES('122155380120748034','!iMLtMMlHpWNyAnadZu:cadence.moe'); -INSERT INTO channel_room VALUES('325891921463738369','!IsJKZmawitkFurcjwY:cadence.moe'); -INSERT INTO channel_room VALUES('805261291908104252','!PAGMqppQIkLaNRwkpN:cadence.moe'); -INSERT INTO channel_room VALUES('360567146243293194','!RbrindSuOlbAHAJRFq:cadence.moe'); -INSERT INTO channel_room VALUES('295789411856154624','!RbLRKOjmaEafDtOAAJ:cadence.moe'); -INSERT INTO channel_room VALUES('279985883560804353','!WuYmXisuwEgrxpSkdW:cadence.moe'); -INSERT INTO channel_room VALUES('778183052521111592','!hsALhxajLcTddkKcUE:cadence.moe'); -INSERT INTO channel_room VALUES('360564656265232395','!EZmeznyjKwAcBHbUfX:cadence.moe'); -INSERT INTO channel_room VALUES('802612899990339645','!NVsoZfMPBtLCzlUQAb:cadence.moe'); -INSERT INTO channel_room VALUES('360564868224647168','!YnNokCprKPHliWjKTy:cadence.moe'); -INSERT INTO channel_room VALUES('920171008047079425','!IgtetQZJffJcCQjMqG:cadence.moe'); -INSERT INTO channel_room VALUES('494913643448631296','!WZdAlxVcydGBNdTuoL:cadence.moe'); -INSERT INTO channel_room VALUES('360565596133523459','!OPkSbsYXFqZkRVCIZM:cadence.moe'); -INSERT INTO channel_room VALUES('360567400254537729','!BoXSlmwqtovnOCybXt:cadence.moe'); -INSERT INTO channel_room VALUES('1036840786093953084','!SMEmWBWGgRXeVyiwHN:cadence.moe'); -INSERT INTO channel_room VALUES('360565147854438412','!vwVOQfBJDkaHdiVLUF:cadence.moe'); -INSERT INTO channel_room VALUES('802420775860568074','!QsljgtSqHOtGwMYuIc:cadence.moe'); -CREATE TABLE IF NOT EXISTS "file" ( - "discord_url" TEXT NOT NULL UNIQUE, - "mxc_url" TEXT NOT NULL UNIQUE, - PRIMARY KEY("discord_url") -); -INSERT INTO file VALUES('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c?size=1024','mxc://cadence.moe/sZtPwbfOIsvfSoWCWPrGnzql'); -INSERT INTO file VALUES('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024','mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'); -CREATE TABLE IF NOT EXISTS "sim_member" ( - "mxid" TEXT NOT NULL, - "room_id" TEXT NOT NULL, - PRIMARY KEY("mxid","room_id") -); -INSERT INTO sim_member VALUES('@_ooye_huck:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); -INSERT INTO sim_member VALUES('@_ooye_bojack_horseman:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); -INSERT INTO sim_member VALUES('@_ooye_botrac4r:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); -INSERT INTO sim_member VALUES('@_ooye_crunch_god:cadence.moe','!VwVlIAjOjejUpDhlbA:cadence.moe'); -CREATE TABLE IF NOT EXISTS "sim" ( - "discord_id" TEXT NOT NULL UNIQUE, - "sim_name" TEXT NOT NULL UNIQUE, - "localpart" TEXT NOT NULL UNIQUE, - "mxid" TEXT NOT NULL UNIQUE, - PRIMARY KEY("discord_id") -); -INSERT INTO sim VALUES('0','bot','_ooye_bot','@_ooye_bot:cadence.moe'); -INSERT INTO sim VALUES('113340068197859328','cookie','_ooye_cookie','@_ooye_cookie:cadence.moe'); -INSERT INTO sim VALUES('820865262526005258','crunch_god','_ooye_crunch_god','@_ooye_crunch_god:cadence.moe'); -INSERT INTO sim VALUES('142843483923677184','huck','_ooye_huck','@_ooye_huck:cadence.moe'); -INSERT INTO sim VALUES('771520384671416320','bojack_horseman','_ooye_bojack_horseman','@_ooye_bojack_horseman:cadence.moe'); -INSERT INTO sim VALUES('353703396483661824','botrac4r','_ooye_botrac4r','@_ooye_botrac4r:cadence.moe'); -COMMIT; diff --git a/matrix/file.js b/matrix/file.js index 64cd492..965ec1c 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -40,15 +40,8 @@ async function uploadDiscordFileToMxc(path) { // Download from Discord const promise = fetch(url, {}).then(/** @param {import("node-fetch").Response} res */ async res => { - const body = res.body - // Upload to Matrix - /** @type {import("../types").R.FileUploaded} */ - const root = await mreq.mreq("POST", "/media/v3/upload", body, { - headers: { - "Content-Type": res.headers.get("content-type") - } - }) + const root = await module.exports._actuallyUploadDiscordFileToMxc(url, res) // Store relationship in database db.prepare("INSERT INTO file (discord_url, mxc_url) VALUES (?, ?)").run(url, root.content_uri) @@ -61,6 +54,17 @@ async function uploadDiscordFileToMxc(path) { return promise } +async function _actuallyUploadDiscordFileToMxc(url, res) { + const body = res.body + /** @type {import("../types").R.FileUploaded} */ + const root = await mreq.mreq("POST", "/media/v3/upload", body, { + headers: { + "Content-Type": res.headers.get("content-type") + } + }) + return root +} + function guildIcon(guild) { return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } @@ -102,3 +106,4 @@ module.exports.emoji = emoji module.exports.stickerFormat = stickerFormat module.exports.sticker = sticker module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc +module.exports._actuallyUploadDiscordFileToMxc = _actuallyUploadDiscordFileToMxc diff --git a/scripts/events.db b/scripts/events.db index 045e1471b97b6274aebcb9b2f457c3bc3ab5b898..86957d8684d132e60a394c3305b37bd63cad3e1c 100644 GIT binary patch delta 11883 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 Date: Fri, 18 Aug 2023 17:00:40 +1200 Subject: [PATCH 114/200] only include necessary data for testing --- db/data-for-test.sql | 94 +++++++++++++++++++++++++++++++++++++++++++ matrix/file.js | 21 ++++++---- scripts/events.db | Bin 208896 -> 249856 bytes test/test.js | 8 +++- 4 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 db/data-for-test.sql diff --git a/db/data-for-test.sql b/db/data-for-test.sql new file mode 100644 index 0000000..fa04562 --- /dev/null +++ b/db/data-for-test.sql @@ -0,0 +1,94 @@ +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "guild_space" ( + "guild_id" TEXT NOT NULL UNIQUE, + "space_id" TEXT NOT NULL UNIQUE, + PRIMARY KEY("guild_id") +); +CREATE TABLE IF NOT EXISTS "file" ( + "discord_url" TEXT NOT NULL UNIQUE, + "mxc_url" TEXT NOT NULL UNIQUE, + PRIMARY KEY("discord_url") +); +CREATE TABLE IF NOT EXISTS "sim" ( + "discord_id" TEXT NOT NULL UNIQUE, + "sim_name" TEXT NOT NULL UNIQUE, + "localpart" TEXT NOT NULL UNIQUE, + "mxid" TEXT NOT NULL UNIQUE, + PRIMARY KEY("discord_id") +); +CREATE TABLE IF NOT EXISTS "sim_member" ( + "mxid" TEXT NOT NULL, + "room_id" TEXT NOT NULL, + "profile_event_content_hash" BLOB, + PRIMARY KEY("mxid","room_id") +); +CREATE TABLE IF NOT EXISTS "webhook" ( + "channel_id" TEXT NOT NULL UNIQUE, + "webhook_id" TEXT NOT NULL UNIQUE, + "webhook_token" TEXT NOT NULL, + PRIMARY KEY("channel_id") +); +CREATE TABLE IF NOT EXISTS "channel_room" ( + "channel_id" TEXT NOT NULL UNIQUE, + "room_id" TEXT NOT NULL UNIQUE, + "name" TEXT, + "nick" TEXT, + PRIMARY KEY("channel_id") +); +CREATE TABLE IF NOT EXISTS "event_message" ( + "event_id" TEXT NOT NULL, + "event_type" TEXT, + "event_subtype" TEXT, + "message_id" TEXT NOT NULL, + "channel_id" TEXT, + "part" INTEGER NOT NULL, + "source" INTEGER NOT NULL, + PRIMARY KEY("event_id","message_id") +); +COMMIT; + + + +BEGIN TRANSACTION; + +INSERT INTO guild_space (guild_id, space_id) VALUES +('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); + +INSERT INTO channel_room (channel_id, room_id, name, nick) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main'), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots'); + +INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES +('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), +('820865262526005258', 'crunch_god', '_ooye_crunch_god', '@_ooye_crunch_god:cadence.moe'), +('771520384671416320', 'bojack_horseman', '_ooye_bojack_horseman', '@_ooye_bojack_horseman:cadence.moe'), +('112890272819507200', '.wing.', '_ooye_.wing.', '@_ooye_.wing.:cadence.moe'), +('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'); + +INSERT INTO sim_member (mxid, room_id, profile_event_content_hash) VALUES +('@_ooye_bojack_horseman:cadence.moe', '!uCtjHhfGlYbVnPVlkG:cadence.moe', NULL); + +INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES +('$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg', 'm.room.message', 'm.text', '1126786462646550579', '112760669178241024', 0, 1), +('$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4', 'm.room.message', 'm.text', '1128118177155526666', '112760669178241024', 0, 0), +('$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10', 'm.room.message', 'm.text', '1141619794500649020', '497161350934560778', 0, 1), +('$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY', 'm.room.message', 'm.text', '1141206225632112650', '160197704226439168', 0, 1), +('$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA', 'm.room.message', 'm.text', '1141501302736695316', '112760669178241024', 0, 1), +('$51f4yqHinwnSbPEQ9dCgoyy4qiIJSX0QYYVUnvwyTCI', 'm.room.message', 'm.image', '1141501302736695316', '112760669178241024', 1, 1), +('$51f4yqHinwnSbPEQ9dCgoyy4qiIJSX0QYYVUnvwyTCJ', 'm.room.message', 'm.image', '1141501302736695317', '112760669178241024', 0, 1), +('$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M', 'm.room.message', 'm.text', '1128084851279536279', '112760669178241024', 0, 1), +('$YUJFa5j0ZJe7PUvD2DykRt9g51RoadUEYmuJLdSEbJ0', 'm.room.message', 'm.image', '1128084851279536279', '112760669178241024', 1, 1), +('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1); + +INSERT INTO file (discord_url, mxc_url) VALUES +('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), +('https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png', 'mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus'), +('https://cdn.discordapp.com/stickers/1106323941183717586.png', 'mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn'), +('https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp', 'mxc://cadence.moe/sDxWmDErBhYBxtDcJQgBETes'), +('https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png', 'mxc://cadence.moe/KQYdXKRcHWjDYDLPkTOOWOjA'), +('https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg', 'mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa'), +('https://cdn.discordapp.com/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024', 'mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl'), +('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'); + +COMMIT; diff --git a/matrix/file.js b/matrix/file.js index 64cd492..965ec1c 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -40,15 +40,8 @@ async function uploadDiscordFileToMxc(path) { // Download from Discord const promise = fetch(url, {}).then(/** @param {import("node-fetch").Response} res */ async res => { - const body = res.body - // Upload to Matrix - /** @type {import("../types").R.FileUploaded} */ - const root = await mreq.mreq("POST", "/media/v3/upload", body, { - headers: { - "Content-Type": res.headers.get("content-type") - } - }) + const root = await module.exports._actuallyUploadDiscordFileToMxc(url, res) // Store relationship in database db.prepare("INSERT INTO file (discord_url, mxc_url) VALUES (?, ?)").run(url, root.content_uri) @@ -61,6 +54,17 @@ async function uploadDiscordFileToMxc(path) { return promise } +async function _actuallyUploadDiscordFileToMxc(url, res) { + const body = res.body + /** @type {import("../types").R.FileUploaded} */ + const root = await mreq.mreq("POST", "/media/v3/upload", body, { + headers: { + "Content-Type": res.headers.get("content-type") + } + }) + return root +} + function guildIcon(guild) { return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } @@ -102,3 +106,4 @@ module.exports.emoji = emoji module.exports.stickerFormat = stickerFormat module.exports.sticker = sticker module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc +module.exports._actuallyUploadDiscordFileToMxc = _actuallyUploadDiscordFileToMxc diff --git a/scripts/events.db b/scripts/events.db index 045e1471b97b6274aebcb9b2f457c3bc3ab5b898..86957d8684d132e60a394c3305b37bd63cad3e1c 100644 GIT binary patch delta 11883 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 Date: Sat, 19 Aug 2023 18:37:34 +1200 Subject: [PATCH 115/200] send a slightly more appropriate display name --- m2d/converters/event-to-message.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 817ffff..74a45ce 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -16,11 +16,19 @@ function eventToMessage(event) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ const messages = [] + let displayName = event.sender + let avatarURL = undefined + const match = event.sender.match(/^@(.*?):/) + if (match) { + displayName = match[1] + // TODO: get the media repo domain and the avatar url from the matrix member event + } + if (event.content.msgtype === "m.text") { messages.push({ content: event.content.body, - username: event.sender.replace(/^@/, ""), - avatar_url: undefined, // TODO: provide the URL to the avatar from the homeserver's content repo + username: displayName, + avatar_url: avatarURL }) } From 213bf0a5159e8dd023ab80966d6d73e6b1fa4113 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:37:34 +1200 Subject: [PATCH 116/200] send a slightly more appropriate display name --- m2d/converters/event-to-message.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 817ffff..74a45ce 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -16,11 +16,19 @@ function eventToMessage(event) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ const messages = [] + let displayName = event.sender + let avatarURL = undefined + const match = event.sender.match(/^@(.*?):/) + if (match) { + displayName = match[1] + // TODO: get the media repo domain and the avatar url from the matrix member event + } + if (event.content.msgtype === "m.text") { messages.push({ content: event.content.body, - username: event.sender.replace(/^@/, ""), - avatar_url: undefined, // TODO: provide the URL to the avatar from the homeserver's content repo + username: displayName, + avatar_url: avatarURL }) } From 425a4d3110f9f78706991a99ba2f82c64047f5ba Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:39:23 +1200 Subject: [PATCH 117/200] support threads --- d2m/actions/create-room.js | 68 +++++++++++++++++++++++----------- d2m/actions/create-space.js | 12 +++--- d2m/discord-packets.js | 6 +++ d2m/event-dispatcher.js | 61 ++++++++++++++++-------------- db/data-for-test.sql | 8 ++-- m2d/actions/channel-webhook.js | 5 ++- m2d/actions/send-event.js | 10 ++++- notes.md | 7 ++++ test/data.js | 58 +++++++++++++++++++++++++++++ 9 files changed, 172 insertions(+), 63 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 0fd0646..6d64d58 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -21,8 +21,8 @@ async function roomToKState(roomID) { } /** - * @params {string} roomID - * @params {any} kstate + * @param {string} roomID + * @param {any} kstate */ function applyKStateDiffToRoom(roomID, kstate) { const events = ks.kstateToState(kstate) @@ -51,7 +51,7 @@ function convertNameAndTopic(channel, guild, customName) { } /** - * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel * @param {DiscordTypes.APIGuild} guild */ async function channelToKState(channel, guild) { @@ -98,21 +98,27 @@ async function channelToKState(channel, guild) { * @returns {Promise} room ID */ async function createRoom(channel, guild, spaceID, kstate) { + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) const roomID = await api.createRoom({ - name: channel.name, - topic: channel.topic || undefined, + name: convertedName, + topic: convertedTopic, preset: "private_chat", visibility: "private", invite: ["@cadence:cadence.moe"], // TODO initial_state: ks.kstateToState(kstate) }) - db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID) + let threadParent = null + if (channel.type === DiscordTypes.ChannelType.PublicThread) { + /** @type {DiscordTypes.APIThreadChannel} */ // @ts-ignore + const thread = channel + threadParent = thread.parent_id + } + + db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) // Put the newly created child into the space - await api.sendState(spaceID, "m.space.child", roomID, { // TODO: should I deduplicate with the equivalent code from syncRoom? - via: ["cadence.moe"] // TODO: use the proper server - }) + _syncSpaceMember(channel, spaceID, roomID) return roomID } @@ -156,14 +162,15 @@ async function _syncRoom(channelID, shouldActuallySync) { assert.ok(channel) const guild = channelToGuild(channel) - /** @type {string?} */ - const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) + /** @type {{room_id: string, thread_parent: string?}} */ + const existing = db.prepare("SELECT room_id, thread_parent from channel_room WHERE channel_id = ?").get(channelID) + if (!existing) { const {spaceID, channelKState} = await channelToKState(channel, guild) return createRoom(channel, guild, spaceID, channelKState) } else { if (!shouldActuallySync) { - return existing // only need to ensure room exists, and it does. return the room ID + return existing.room_id // only need to ensure room exists, and it does. return the room ID } console.log(`[room sync] to matrix: ${channel.name}`) @@ -171,24 +178,41 @@ async function _syncRoom(channelID, shouldActuallySync) { const {spaceID, channelKState} = await channelToKState(channel, guild) // sync channel state to room - const roomKState = await roomToKState(existing) + const roomKState = await roomToKState(existing.room_id) const roomDiff = ks.diffKState(roomKState, channelKState) - const roomApply = applyKStateDiffToRoom(existing, roomDiff) + const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) // sync room as space member - const spaceKState = await roomToKState(spaceID) - const spaceDiff = ks.diffKState(spaceKState, { - [`m.space.child/${existing}`]: { - via: ["cadence.moe"] // TODO: use the proper server - } - }) - const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff) + const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) await Promise.all([roomApply, spaceApply]) - return existing + return existing.room_id } } +/** + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {string} spaceID + * @param {string} roomID + * @returns {Promise} + */ +async function _syncSpaceMember(channel, spaceID, roomID) { + const spaceKState = await roomToKState(spaceID) + let spaceEventContent = {} + if ( + channel.type !== DiscordTypes.ChannelType.PrivateThread // private threads do not belong in the space (don't offer people something they can't join) + || channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) + ) { + spaceEventContent = { + via: ["cadence.moe"] // TODO: use the proper server + } + } + const spaceDiff = ks.diffKState(spaceKState, { + [`m.space.child/${roomID}`]: spaceEventContent + }) + return applyKStateDiffToRoom(spaceID, spaceDiff) +} + function ensureRoom(channelID) { return _syncRoom(channelID, false) } diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index e3b6da7..02c2dcf 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -1,5 +1,6 @@ // @ts-check +const assert = require("assert") const passthrough = require("../../passthrough") const { sync, db } = passthrough /** @type {import("../../matrix/api")} */ @@ -9,13 +10,14 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ async function createSpace(guild) { + assert(guild.name) const roomID = await api.createRoom({ name: guild.name, - preset: "private_chat", + preset: "private_chat", // cannot join space unless invited visibility: "private", power_level_content_override: { - events_default: 100, - invite: 50 + events_default: 100, // space can only be managed by bridge + invite: 0 // any existing member can invite others }, invite: ["@cadence:cadence.moe"], // TODO topic: guild.description || undefined, @@ -27,13 +29,13 @@ async function createSpace(guild) { type: "m.room.guest_access", state_key: "", content: { - guest_access: "can_join" + guest_access: "can_join" // guests can join space if other conditions are met } }, { type: "m.room.history_visibility", content: { - history_visibility: "invited" + history_visibility: "invited" // any events sent after user was invited are visible } } ] diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index c0ba1a6..79138b2 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -35,6 +35,12 @@ const utils = { arr.push(channel.id) client.channels.set(channel.id, channel) } + for (const thread of message.d.threads || []) { + // @ts-ignore + thread.guild_id = message.d.id + arr.push(thread.id) + client.channels.set(thread.id, thread) + } } else if (message.t === "GUILD_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 8e64591..9387199 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -15,6 +15,10 @@ const api = sync.require("../matrix/api") let lastReportedEvent = 0 +function isGuildAllowed(guildID) { + return ["112760669178241024", "497159726455455754", "1100319549670301727"].includes(guildID) +} + // Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { @@ -29,31 +33,34 @@ module.exports = { console.error(`while handling this ${gatewayMessage.t} gateway event:`) console.dir(gatewayMessage.d, {depth: null}) - if (Date.now() - lastReportedEvent > 5000) { - lastReportedEvent = Date.now() - const channelID = gatewayMessage.d.channel_id - if (channelID) { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) - let stackLines = e.stack.split("\n") - let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) - if (cloudstormLine !== -1) { - stackLines = stackLines.slice(0, cloudstormLine - 2) - } - api.sendEvent(roomID, "m.room.message", { - msgtype: "m.text", - body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.", - format: "org.matrix.custom.html", - formatted_body: "\u26a0 Bridged event from Discord not delivered" - + `
    Gateway event: ${gatewayMessage.t}` - + `
    ${stackLines.join("\n")}
    ` - + `
    Original payload` - + `
    ${util.inspect(gatewayMessage.d, false, 4, false)}
    `, - "m.mentions": { - user_ids: ["@cadence:cadence.moe"] - } - }) - } + if (Date.now() - lastReportedEvent < 5000) return + lastReportedEvent = Date.now() + + const channelID = gatewayMessage.d.channel_id + if (!channelID) return + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) + if (!roomID) return + + let stackLines = e.stack.split("\n") + let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) + if (cloudstormLine !== -1) { + stackLines = stackLines.slice(0, cloudstormLine - 2) } + api.sendEvent(roomID, "m.room.message", { + msgtype: "m.text", + body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.", + format: "org.matrix.custom.html", + formatted_body: "\u26a0 Bridged event from Discord not delivered" + + `
    Gateway event: ${gatewayMessage.t}` + + `
    ${e.toString()}` + + `
    Error trace` + + `
    ${stackLines.join("\n")}
    ` + + `
    Original payload` + + `
    ${util.inspect(gatewayMessage.d, false, 4, false)}
    `, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + } + }) }, /** @@ -72,7 +79,7 @@ module.exports = { const channel = client.channels.get(message.channel_id) if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (!isGuildAllowed(guild.id)) return await sendMessage.sendMessage(message, guild) }, @@ -97,7 +104,7 @@ module.exports = { const channel = client.channels.get(message.channel_id) if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (!isGuildAllowed(guild.id)) return await editMessage.editMessage(message, guild) } }, @@ -109,7 +116,6 @@ module.exports = { async onReactionAdd(client, data) { if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. if (data.emoji.id !== null) return // TODO: image emoji reactions - console.log(data) await addReaction.addReaction(data) }, @@ -118,7 +124,6 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data */ async onMessageDelete(client, data) { - console.log(data) await deleteMessage.deleteMessage(data) } } diff --git a/db/data-for-test.sql b/db/data-for-test.sql index fa04562..aa82c91 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -54,10 +54,10 @@ BEGIN TRANSACTION; INSERT INTO guild_space (guild_id, space_id) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); -INSERT INTO channel_room (channel_id, room_id, name, nick) VALUES -('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main'), -('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots'); +INSERT INTO channel_room (channel_id, room_id, name, nick, is_thread) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, 0), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, 0), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', 0); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index b62057b..f5fd9a9 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -50,10 +50,11 @@ async function withWebhook(channelID, callback) { /** * @param {string} channelID * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}} data + * @param {string} [threadID] */ -async function sendMessageWithWebhook(channelID, data) { +async function sendMessageWithWebhook(channelID, data, threadID) { const result = await withWebhook(channelID, async webhook => { - return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, thread_id: threadID, disableEveryone: true}) }) return result } diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 3f49fa4..88ba0fd 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -13,7 +13,13 @@ const eventToMessage = sync.require("../converters/event-to-message") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { // TODO: we just assume the bridge has already been created - const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + const row = db.prepare("SELECT channel_id, thread_parent FROM channel_room WHERE room_id = ?").get(event.room_id) + let channelID = row.channel_id + let threadID = undefined + if (row.thread_parent) { + threadID = channelID + channelID = row.thread_parent // it's the thread's parent... get with the times... + } // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it @@ -24,7 +30,7 @@ async function sendEvent(event) { const messageResponses = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { - const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) + const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? diff --git a/notes.md b/notes.md index ec2b9bb..1dcbcd7 100644 --- a/notes.md +++ b/notes.md @@ -9,6 +9,13 @@ A database will be used to store the discord id to matrix event id mapping. Tabl There needs to be a way to easily manually trigger something later. For example, it should be easy to manually retry sending a message, or check all members for changes, etc. +## Current manual process for setting up a server + +1. Call createSpace.createSpace(discord.guilds.get(GUILD_ID)) +2. Call createRoom.createAllForGuild(GUILD_ID) +3. Edit source code of event-dispatcher.js isGuildAllowed() and add the guild ID to the list +4. If developing, make sure SSH port forward is activated, then wait for events to sync over! + ## Transforming content 1. Upload attachments to mxc if they are small enough. diff --git a/test/data.js b/test/data.js index a1d3ece..b579a24 100644 --- a/test/data.js +++ b/test/data.js @@ -816,6 +816,64 @@ module.exports = { format_type: 1, name: "pomu puff" }] + }, + message_in_thread: { + type: 0, + tts: false, + timestamp: "2023-08-19T01:55:02.063000+00:00", + referenced_message: null, + position: 942, + pinned: false, + nonce: "1142275498206822400", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1142275501721911467", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "don't mind me, posting something for cadence", + components: [], + channel_id: "910283343378120754", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration_data: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" } }, message_update: { From 0fc8e68f15c35d490ec88013f8af75afaf5f33f2 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:39:23 +1200 Subject: [PATCH 118/200] support threads --- d2m/actions/create-room.js | 68 +++++++++++++++++++++++----------- d2m/actions/create-space.js | 12 +++--- d2m/discord-packets.js | 6 +++ d2m/event-dispatcher.js | 61 ++++++++++++++++-------------- db/data-for-test.sql | 8 ++-- m2d/actions/channel-webhook.js | 5 ++- m2d/actions/send-event.js | 10 ++++- notes.md | 7 ++++ test/data.js | 58 +++++++++++++++++++++++++++++ 9 files changed, 172 insertions(+), 63 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 0fd0646..6d64d58 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -21,8 +21,8 @@ async function roomToKState(roomID) { } /** - * @params {string} roomID - * @params {any} kstate + * @param {string} roomID + * @param {any} kstate */ function applyKStateDiffToRoom(roomID, kstate) { const events = ks.kstateToState(kstate) @@ -51,7 +51,7 @@ function convertNameAndTopic(channel, guild, customName) { } /** - * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel * @param {DiscordTypes.APIGuild} guild */ async function channelToKState(channel, guild) { @@ -98,21 +98,27 @@ async function channelToKState(channel, guild) { * @returns {Promise} room ID */ async function createRoom(channel, guild, spaceID, kstate) { + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) const roomID = await api.createRoom({ - name: channel.name, - topic: channel.topic || undefined, + name: convertedName, + topic: convertedTopic, preset: "private_chat", visibility: "private", invite: ["@cadence:cadence.moe"], // TODO initial_state: ks.kstateToState(kstate) }) - db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID) + let threadParent = null + if (channel.type === DiscordTypes.ChannelType.PublicThread) { + /** @type {DiscordTypes.APIThreadChannel} */ // @ts-ignore + const thread = channel + threadParent = thread.parent_id + } + + db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) // Put the newly created child into the space - await api.sendState(spaceID, "m.space.child", roomID, { // TODO: should I deduplicate with the equivalent code from syncRoom? - via: ["cadence.moe"] // TODO: use the proper server - }) + _syncSpaceMember(channel, spaceID, roomID) return roomID } @@ -156,14 +162,15 @@ async function _syncRoom(channelID, shouldActuallySync) { assert.ok(channel) const guild = channelToGuild(channel) - /** @type {string?} */ - const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id) + /** @type {{room_id: string, thread_parent: string?}} */ + const existing = db.prepare("SELECT room_id, thread_parent from channel_room WHERE channel_id = ?").get(channelID) + if (!existing) { const {spaceID, channelKState} = await channelToKState(channel, guild) return createRoom(channel, guild, spaceID, channelKState) } else { if (!shouldActuallySync) { - return existing // only need to ensure room exists, and it does. return the room ID + return existing.room_id // only need to ensure room exists, and it does. return the room ID } console.log(`[room sync] to matrix: ${channel.name}`) @@ -171,24 +178,41 @@ async function _syncRoom(channelID, shouldActuallySync) { const {spaceID, channelKState} = await channelToKState(channel, guild) // sync channel state to room - const roomKState = await roomToKState(existing) + const roomKState = await roomToKState(existing.room_id) const roomDiff = ks.diffKState(roomKState, channelKState) - const roomApply = applyKStateDiffToRoom(existing, roomDiff) + const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) // sync room as space member - const spaceKState = await roomToKState(spaceID) - const spaceDiff = ks.diffKState(spaceKState, { - [`m.space.child/${existing}`]: { - via: ["cadence.moe"] // TODO: use the proper server - } - }) - const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff) + const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) await Promise.all([roomApply, spaceApply]) - return existing + return existing.room_id } } +/** + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {string} spaceID + * @param {string} roomID + * @returns {Promise} + */ +async function _syncSpaceMember(channel, spaceID, roomID) { + const spaceKState = await roomToKState(spaceID) + let spaceEventContent = {} + if ( + channel.type !== DiscordTypes.ChannelType.PrivateThread // private threads do not belong in the space (don't offer people something they can't join) + || channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) + ) { + spaceEventContent = { + via: ["cadence.moe"] // TODO: use the proper server + } + } + const spaceDiff = ks.diffKState(spaceKState, { + [`m.space.child/${roomID}`]: spaceEventContent + }) + return applyKStateDiffToRoom(spaceID, spaceDiff) +} + function ensureRoom(channelID) { return _syncRoom(channelID, false) } diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index e3b6da7..02c2dcf 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -1,5 +1,6 @@ // @ts-check +const assert = require("assert") const passthrough = require("../../passthrough") const { sync, db } = passthrough /** @type {import("../../matrix/api")} */ @@ -9,13 +10,14 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild */ async function createSpace(guild) { + assert(guild.name) const roomID = await api.createRoom({ name: guild.name, - preset: "private_chat", + preset: "private_chat", // cannot join space unless invited visibility: "private", power_level_content_override: { - events_default: 100, - invite: 50 + events_default: 100, // space can only be managed by bridge + invite: 0 // any existing member can invite others }, invite: ["@cadence:cadence.moe"], // TODO topic: guild.description || undefined, @@ -27,13 +29,13 @@ async function createSpace(guild) { type: "m.room.guest_access", state_key: "", content: { - guest_access: "can_join" + guest_access: "can_join" // guests can join space if other conditions are met } }, { type: "m.room.history_visibility", content: { - history_visibility: "invited" + history_visibility: "invited" // any events sent after user was invited are visible } } ] diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index c0ba1a6..79138b2 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -35,6 +35,12 @@ const utils = { arr.push(channel.id) client.channels.set(channel.id, channel) } + for (const thread of message.d.threads || []) { + // @ts-ignore + thread.guild_id = message.d.id + arr.push(thread.id) + client.channels.set(thread.id, thread) + } } else if (message.t === "GUILD_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 8e64591..9387199 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -15,6 +15,10 @@ const api = sync.require("../matrix/api") let lastReportedEvent = 0 +function isGuildAllowed(guildID) { + return ["112760669178241024", "497159726455455754", "1100319549670301727"].includes(guildID) +} + // Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { @@ -29,31 +33,34 @@ module.exports = { console.error(`while handling this ${gatewayMessage.t} gateway event:`) console.dir(gatewayMessage.d, {depth: null}) - if (Date.now() - lastReportedEvent > 5000) { - lastReportedEvent = Date.now() - const channelID = gatewayMessage.d.channel_id - if (channelID) { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) - let stackLines = e.stack.split("\n") - let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) - if (cloudstormLine !== -1) { - stackLines = stackLines.slice(0, cloudstormLine - 2) - } - api.sendEvent(roomID, "m.room.message", { - msgtype: "m.text", - body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.", - format: "org.matrix.custom.html", - formatted_body: "\u26a0 Bridged event from Discord not delivered" - + `
    Gateway event: ${gatewayMessage.t}` - + `
    ${stackLines.join("\n")}
    ` - + `
    Original payload` - + `
    ${util.inspect(gatewayMessage.d, false, 4, false)}
    `, - "m.mentions": { - user_ids: ["@cadence:cadence.moe"] - } - }) - } + if (Date.now() - lastReportedEvent < 5000) return + lastReportedEvent = Date.now() + + const channelID = gatewayMessage.d.channel_id + if (!channelID) return + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) + if (!roomID) return + + let stackLines = e.stack.split("\n") + let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) + if (cloudstormLine !== -1) { + stackLines = stackLines.slice(0, cloudstormLine - 2) } + api.sendEvent(roomID, "m.room.message", { + msgtype: "m.text", + body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.", + format: "org.matrix.custom.html", + formatted_body: "\u26a0 Bridged event from Discord not delivered" + + `
    Gateway event: ${gatewayMessage.t}` + + `
    ${e.toString()}` + + `
    Error trace` + + `
    ${stackLines.join("\n")}
    ` + + `
    Original payload` + + `
    ${util.inspect(gatewayMessage.d, false, 4, false)}
    `, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + } + }) }, /** @@ -72,7 +79,7 @@ module.exports = { const channel = client.channels.get(message.channel_id) if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (!isGuildAllowed(guild.id)) return await sendMessage.sendMessage(message, guild) }, @@ -97,7 +104,7 @@ module.exports = { const channel = client.channels.get(message.channel_id) if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (!isGuildAllowed(guild.id)) return await editMessage.editMessage(message, guild) } }, @@ -109,7 +116,6 @@ module.exports = { async onReactionAdd(client, data) { if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. if (data.emoji.id !== null) return // TODO: image emoji reactions - console.log(data) await addReaction.addReaction(data) }, @@ -118,7 +124,6 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data */ async onMessageDelete(client, data) { - console.log(data) await deleteMessage.deleteMessage(data) } } diff --git a/db/data-for-test.sql b/db/data-for-test.sql index fa04562..aa82c91 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -54,10 +54,10 @@ BEGIN TRANSACTION; INSERT INTO guild_space (guild_id, space_id) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); -INSERT INTO channel_room (channel_id, room_id, name, nick) VALUES -('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main'), -('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots'); +INSERT INTO channel_room (channel_id, room_id, name, nick, is_thread) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, 0), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, 0), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', 0); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index b62057b..f5fd9a9 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -50,10 +50,11 @@ async function withWebhook(channelID, callback) { /** * @param {string} channelID * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}} data + * @param {string} [threadID] */ -async function sendMessageWithWebhook(channelID, data) { +async function sendMessageWithWebhook(channelID, data, threadID) { const result = await withWebhook(channelID, async webhook => { - return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, thread_id: threadID, disableEveryone: true}) }) return result } diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 3f49fa4..88ba0fd 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -13,7 +13,13 @@ const eventToMessage = sync.require("../converters/event-to-message") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { // TODO: we just assume the bridge has already been created - const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + const row = db.prepare("SELECT channel_id, thread_parent FROM channel_room WHERE room_id = ?").get(event.room_id) + let channelID = row.channel_id + let threadID = undefined + if (row.thread_parent) { + threadID = channelID + channelID = row.thread_parent // it's the thread's parent... get with the times... + } // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it @@ -24,7 +30,7 @@ async function sendEvent(event) { const messageResponses = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { - const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) + const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? diff --git a/notes.md b/notes.md index ec2b9bb..1dcbcd7 100644 --- a/notes.md +++ b/notes.md @@ -9,6 +9,13 @@ A database will be used to store the discord id to matrix event id mapping. Tabl There needs to be a way to easily manually trigger something later. For example, it should be easy to manually retry sending a message, or check all members for changes, etc. +## Current manual process for setting up a server + +1. Call createSpace.createSpace(discord.guilds.get(GUILD_ID)) +2. Call createRoom.createAllForGuild(GUILD_ID) +3. Edit source code of event-dispatcher.js isGuildAllowed() and add the guild ID to the list +4. If developing, make sure SSH port forward is activated, then wait for events to sync over! + ## Transforming content 1. Upload attachments to mxc if they are small enough. diff --git a/test/data.js b/test/data.js index a1d3ece..b579a24 100644 --- a/test/data.js +++ b/test/data.js @@ -816,6 +816,64 @@ module.exports = { format_type: 1, name: "pomu puff" }] + }, + message_in_thread: { + type: 0, + tts: false, + timestamp: "2023-08-19T01:55:02.063000+00:00", + referenced_message: null, + position: 942, + pinned: false, + nonce: "1142275498206822400", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1142275501721911467", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "don't mind me, posting something for cadence", + components: [], + channel_id: "910283343378120754", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration_data: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" } }, message_update: { From a38e54dcd4d83821164f70db092e6a153025e900 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:40:01 +1200 Subject: [PATCH 119/200] see how this different reactions format goes... --- d2m/actions/add-reaction.js | 2 +- d2m/actions/edit-message.js | 3 --- m2d/actions/add-reaction.js | 8 +++++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index 49afca9..b46af59 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -22,7 +22,7 @@ async function addReaction(data) { 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", { + const eventID = await api.sendEvent(roomID, "m.reaction", { "m.relates_to": { rel_type: "m.annotation", event_id: parentID, diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 8e8c838..fa152cf 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -12,10 +12,7 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").APIGuild} guild */ async function editMessage(message, guild) { - console.log(`*** applying edit for message ${message.id} in channel ${message.channel_id}`) const {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid} = await editToChanges.editToChanges(message, guild, api) - console.log("making these changes:") - console.dir({eventsToRedact, eventsToReplace, eventsToSend}, {depth: null}) // 1. Replace all the things. for (const {oldID, newContent} of eventsToReplace) { diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js index d259ddb..68828dd 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -18,10 +18,12 @@ async function addReaction(event) { // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it let emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions - emoji = encodeURIComponent(emoji) - emoji = emoji.replace(/%EF%B8%8F/g, "") + let encoded = encodeURIComponent(emoji) + let encodedTrimmed = encoded.replace(/%EF%B8%8F/g, "") - return discord.snow.channel.createReaction(channelID, messageID, emoji) + console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed) + + return discord.snow.channel.createReaction(channelID, messageID, encoded) } module.exports.addReaction = addReaction From 3f187e81077a4c0032248b3046ea73e044d6f8df Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:40:01 +1200 Subject: [PATCH 120/200] see how this different reactions format goes... --- d2m/actions/add-reaction.js | 2 +- d2m/actions/edit-message.js | 3 --- m2d/actions/add-reaction.js | 8 +++++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index 49afca9..b46af59 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -22,7 +22,7 @@ async function addReaction(data) { 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", { + const eventID = await api.sendEvent(roomID, "m.reaction", { "m.relates_to": { rel_type: "m.annotation", event_id: parentID, diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index 8e8c838..fa152cf 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -12,10 +12,7 @@ const api = sync.require("../../matrix/api") * @param {import("discord-api-types/v10").APIGuild} guild */ async function editMessage(message, guild) { - console.log(`*** applying edit for message ${message.id} in channel ${message.channel_id}`) const {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid} = await editToChanges.editToChanges(message, guild, api) - console.log("making these changes:") - console.dir({eventsToRedact, eventsToReplace, eventsToSend}, {depth: null}) // 1. Replace all the things. for (const {oldID, newContent} of eventsToReplace) { diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js index d259ddb..68828dd 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -18,10 +18,12 @@ async function addReaction(event) { // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it let emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions - emoji = encodeURIComponent(emoji) - emoji = emoji.replace(/%EF%B8%8F/g, "") + let encoded = encodeURIComponent(emoji) + let encodedTrimmed = encoded.replace(/%EF%B8%8F/g, "") - return discord.snow.channel.createReaction(channelID, messageID, emoji) + console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed) + + return discord.snow.channel.createReaction(channelID, messageID, encoded) } module.exports.addReaction = addReaction From 92d8f57875b71aff145447e897456fe823c97bc5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:40:37 +1200 Subject: [PATCH 121/200] only use nickname as displayname, not nick+user --- d2m/actions/register-user.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 19c3a6d..a33cecc 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -85,7 +85,8 @@ async function ensureSimJoined(user, roomID) { */ async function memberToStateContent(user, member, guildID) { let displayname = user.username - if (member.nick && member.nick !== displayname) displayname = member.nick + " | " + displayname // prepend nick if present + // if (member.nick && member.nick !== displayname) displayname = member.nick + " | " + displayname // prepend nick if present + if (member.nick) displayname = member.nick const content = { displayname, From 4635200bcb8fa4a9ca58618887d138892b90df0c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:40:37 +1200 Subject: [PATCH 122/200] only use nickname as displayname, not nick+user --- d2m/actions/register-user.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 19c3a6d..a33cecc 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -85,7 +85,8 @@ async function ensureSimJoined(user, roomID) { */ async function memberToStateContent(user, member, guildID) { let displayname = user.username - if (member.nick && member.nick !== displayname) displayname = member.nick + " | " + displayname // prepend nick if present + // if (member.nick && member.nick !== displayname) displayname = member.nick + " | " + displayname // prepend nick if present + if (member.nick) displayname = member.nick const content = { displayname, From 3baf007829b85cf7c453c2f2cc1247a402d2704e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:40:46 +1200 Subject: [PATCH 123/200] guard for errors on matrix event dispatcher --- m2d/event-dispatcher.js | 48 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 82ebd75..44eba85 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -1,9 +1,10 @@ // @ts-check -/** +/* * Grab Matrix events we care about, check them, and bridge them. */ +const util = require("util") const Ty = require("../types") const {sync, as} = require("../passthrough") @@ -13,21 +14,58 @@ const sendEvent = sync.require("./actions/send-event") const addReaction = sync.require("./actions/add-reaction") /** @type {import("./converters/utils")} */ const utils = sync.require("./converters/utils") +/** @type {import("../matrix/api")}) */ +const api = sync.require("../matrix/api") -sync.addTemporaryListener(as, "type:m.room.message", +let lastReportedEvent = 0 + +function guard(type, fn) { + return async function(event, ...args) { + try { + return await fn(event, ...args) + } catch (e) { + console.error("hit event-dispatcher's error handler with this exception:") + console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? + console.error(`while handling this ${type} gateway event:`) + console.dir(event, {depth: null}) + + if (Date.now() - lastReportedEvent < 5000) return + lastReportedEvent = Date.now() + + let stackLines = e.stack.split("\n") + api.sendEvent(event.room_id, "m.room.message", { + msgtype: "m.text", + body: "\u26a0 Matrix event not delivered to Discord. See formatted content for full details.", + format: "org.matrix.custom.html", + formatted_body: "\u26a0 Matrix event not delivered to Discord" + + `
    Event type: ${type}` + + `
    ${e.toString()}` + + `
    Error trace` + + `
    ${stackLines.join("\n")}
    ` + + `
    Original payload` + + `
    ${util.inspect(event, false, 4, false)}
    `, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + } + }) + } + } +} + +sync.addTemporaryListener(as, "type:m.room.message", guard("m.room.message", /** * @param {Ty.Event.Outer} event it is a m.room.message because that's what this listener is filtering for */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return const messageResponses = await sendEvent.sendEvent(event) -}) +})) -sync.addTemporaryListener(as, "type:m.reaction", +sync.addTemporaryListener(as, "type:m.reaction", guard("m.reaction", /** * @param {Ty.Event.Outer} event it is a m.reaction because that's what this listener is filtering for */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return await addReaction.addReaction(event) -}) +})) From 0f20dcab6d0e856553f9c4e7fd9d419becfb4627 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 18:40:46 +1200 Subject: [PATCH 124/200] guard for errors on matrix event dispatcher --- m2d/event-dispatcher.js | 48 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 82ebd75..44eba85 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -1,9 +1,10 @@ // @ts-check -/** +/* * Grab Matrix events we care about, check them, and bridge them. */ +const util = require("util") const Ty = require("../types") const {sync, as} = require("../passthrough") @@ -13,21 +14,58 @@ const sendEvent = sync.require("./actions/send-event") const addReaction = sync.require("./actions/add-reaction") /** @type {import("./converters/utils")} */ const utils = sync.require("./converters/utils") +/** @type {import("../matrix/api")}) */ +const api = sync.require("../matrix/api") -sync.addTemporaryListener(as, "type:m.room.message", +let lastReportedEvent = 0 + +function guard(type, fn) { + return async function(event, ...args) { + try { + return await fn(event, ...args) + } catch (e) { + console.error("hit event-dispatcher's error handler with this exception:") + console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? + console.error(`while handling this ${type} gateway event:`) + console.dir(event, {depth: null}) + + if (Date.now() - lastReportedEvent < 5000) return + lastReportedEvent = Date.now() + + let stackLines = e.stack.split("\n") + api.sendEvent(event.room_id, "m.room.message", { + msgtype: "m.text", + body: "\u26a0 Matrix event not delivered to Discord. See formatted content for full details.", + format: "org.matrix.custom.html", + formatted_body: "\u26a0 Matrix event not delivered to Discord" + + `
    Event type: ${type}` + + `
    ${e.toString()}` + + `
    Error trace` + + `
    ${stackLines.join("\n")}
    ` + + `
    Original payload` + + `
    ${util.inspect(event, false, 4, false)}
    `, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + } + }) + } + } +} + +sync.addTemporaryListener(as, "type:m.room.message", guard("m.room.message", /** * @param {Ty.Event.Outer} event it is a m.room.message because that's what this listener is filtering for */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return const messageResponses = await sendEvent.sendEvent(event) -}) +})) -sync.addTemporaryListener(as, "type:m.reaction", +sync.addTemporaryListener(as, "type:m.reaction", guard("m.reaction", /** * @param {Ty.Event.Outer} event it is a m.reaction because that's what this listener is filtering for */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return await addReaction.addReaction(event) -}) +})) From 8a20f98925ae5c9e068759d9f4ec1a832e7bc674 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 22:54:23 +1200 Subject: [PATCH 125/200] catch up on missed d->m messages when logging in --- d2m/actions/create-room.js | 28 +++++ d2m/actions/register-user.test.js | 2 +- d2m/actions/send-message.js | 9 +- .../message-to-event.embeds.test.js | 40 +++++++ d2m/converters/message-to-event.js | 25 ++++- d2m/converters/message-to-event.test.js | 12 ++- d2m/converters/user-to-mxid.test.js | 6 +- d2m/discord-packets.js | 1 + d2m/event-dispatcher.js | 38 ++++++- db/data-for-test.sql | 9 +- m2d/actions/channel-webhook.js | 3 +- m2d/converters/event-to-message.js | 6 ++ m2d/converters/event-to-message.test.js | 2 +- stdin.js | 1 + test/data.js | 101 +++++++++++++++++- test/test.js | 1 + 16 files changed, 268 insertions(+), 16 deletions(-) create mode 100644 d2m/converters/message-to-event.embeds.test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 6d64d58..0178347 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -190,6 +190,33 @@ async function _syncRoom(channelID, shouldActuallySync) { } } +async function _unbridgeRoom(channelID) { + /** @ts-ignore @type {DiscordTypes.APIGuildChannel} */ + const channel = discord.channels.get(channelID) + assert.ok(channel) + const roomID = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channelID) + assert.ok(roomID) + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(channel.guild_id) + assert.ok(spaceID) + + // remove room from being a space member + await api.sendState(spaceID, "m.space.child", roomID, {}) + + // send a notification in the room + await api.sendEvent(roomID, "m.room.message", { + msgtype: "m.notice", + body: "⚠️ This room was removed from the bridge." + }) + + // leave room + await api.leaveRoom(roomID) + + // delete room from database + const {changes} = db.prepare("DELETE FROM channel_room WHERE room_id = ? AND channel_id = ?").run(roomID, channelID) + assert.equal(changes, 1) +} + + /** * @param {DiscordTypes.APIGuildTextChannel} channel * @param {string} spaceID @@ -237,3 +264,4 @@ module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState module.exports._convertNameAndTopic = convertNameAndTopic +module.exports._unbridgeRoom = _unbridgeRoom diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js index 0afce50..34470ba 100644 --- a/d2m/actions/register-user.test.js +++ b/d2m/actions/register-user.test.js @@ -8,7 +8,7 @@ test("member2state: general", async t => { await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), { avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", - displayname: "The Expert's Submarine | aprilsong", + displayname: "The Expert's Submarine", membership: "join", "moe.cadence.ooye.member": { avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 2132905..a5c8dac 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -22,8 +22,11 @@ async function sendMessage(message, guild) { let senderMxid = null if (!message.webhook_id) { - assert(message.member) - senderMxid = await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + if (message.member) { // available on a gateway message create event + senderMxid = await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + } else { // well, good enough... + senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + } } const events = await messageToEvent.messageToEvent(message, guild, {}, {api}) @@ -35,7 +38,7 @@ async function sendMessage(message, guild) { const eventWithoutType = {...event} delete eventWithoutType.$type - const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid, new Date(message.timestamp).getTime()) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting diff --git a/d2m/converters/message-to-event.embeds.test.js b/d2m/converters/message-to-event.embeds.test.js new file mode 100644 index 0000000..7972f13 --- /dev/null +++ b/d2m/converters/message-to-event.embeds.test.js @@ -0,0 +1,40 @@ +const {test} = require("supertape") +const {messageToEvent} = require("./message-to-event") +const data = require("../../test/data") +const Ty = require("../../types") + +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} + +test("message2event embeds: nothing but a field", async t => { + const events = await messageToEvent(data.message_with_embeds.nothing_but_a_field, data.guild.general, {}) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "Amanda" + }]) +}) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 29730d4..3808beb 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -108,6 +108,13 @@ async function messageToEvent(message, guild, options = {}, di) { addMention(repliedToEventSenderMxid) } + let msgtype = "m.text" + // Handle message type 4, channel name changed + if (message.type === DiscordTypes.MessageType.ChannelNameChange) { + msgtype = "m.emote" + message.content = "changed the channel name to **" + message.content + "**" + } + // Text content appears first if (message.content) { let content = message.content @@ -188,7 +195,7 @@ async function messageToEvent(message, guild, options = {}, di) { const newTextMessageEvent = { $type: "m.room.message", "m.mentions": mentions, - msgtype: "m.text", + msgtype, body: body } @@ -239,6 +246,22 @@ async function messageToEvent(message, guild, options = {}, di) { size: attachment.size } } + } else if (attachment.content_type?.startsWith("video/") && attachment.width && attachment.height) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.video", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.description || attachment.filename, + filename: attachment.filename, + info: { + mimetype: attachment.content_type, + w: attachment.width, + h: attachment.height, + size: attachment.size + } + } } else { return { $type: "m.room.message", diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 86942a7..260ecda 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -330,4 +330,14 @@ test("message2event: very large attachment is linked instead of being uploaded", }]) }) -// TODO: read "edits of replies" in the spec +test("message2event: type 4 channel name change", async t => { + const events = await messageToEvent(data.special_message.thread_name_change, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.emote", + body: "changed the channel name to **worming**", + format: "org.matrix.custom.html", + formatted_body: "changed the channel name to worming" + }]) +}) diff --git a/d2m/converters/user-to-mxid.test.js b/d2m/converters/user-to-mxid.test.js index 8c4c430..1b31260 100644 --- a/d2m/converters/user-to-mxid.test.js +++ b/d2m/converters/user-to-mxid.test.js @@ -16,6 +16,10 @@ test("user2name: works on emojis", t => { t.equal(userToSimName({username: "🍪 Cookie Monster 🍪", discriminator: "0001"}), "cookie_monster") }) +test("user2name: works on single emoji at the end", t => { + t.equal(userToSimName({username: "Amanda 🎵", discriminator: "2192"}), "amanda") +}) + test("user2name: works on crazy name", t => { t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7//") }) @@ -34,4 +38,4 @@ test("user2name: uses ID if name becomes too short", t => { 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 79138b2..970bab4 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -41,6 +41,7 @@ const utils = { arr.push(thread.id) client.channels.set(thread.id, thread) } + eventDispatcher.checkMissedMessages(client, message.d) } else if (message.t === "GUILD_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 9387199..273e89b 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -63,6 +63,42 @@ module.exports = { }) }, + /** + * When logging back in, check if we missed any conversations in any channels. Bridge up to 49 missed messages per channel. + * If more messages were missed, only the latest missed message will be posted. TODO: Consider bridging more, or post a warning when skipping history? + * This can ONLY detect new messages, not any other kind of event. Any missed edits, deletes, reactions, etc will not be bridged. + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayGuildCreateDispatchData} guild + */ + async checkMissedMessages(client, guild) { + if (guild.unavailable) return + const bridgedChannels = db.prepare("SELECT channel_id FROM channel_room").pluck().all() + const prepared = db.prepare("SELECT message_id FROM event_message WHERE channel_id = ? AND message_id = ?").pluck() + for (const channel of guild.channels.concat(guild.threads)) { + if (!bridgedChannels.includes(channel.id)) continue + if (!channel.last_message_id) continue + const latestWasBridged = prepared.get(channel.id, channel.last_message_id) + if (latestWasBridged) continue + + /** More recent messages come first. */ + console.log(`[check missed messages] in ${channel.id} (${guild.name} / ${channel.name}) because its last message ${channel.last_message_id} is not in the database`) + const messages = await client.snow.channel.getChannelMessages(channel.id, {limit: 50}) + let latestBridgedMessageIndex = messages.findIndex(m => { + return prepared.get(channel.id, m.id) + }) + console.log(`[check missed messages] got ${messages.length} messages; last message that IS bridged is at position ${latestBridgedMessageIndex} in the channel`) + if (latestBridgedMessageIndex === -1) latestBridgedMessageIndex = 1 // rather than crawling the ENTIRE channel history, let's just bridge the most recent 1 message to make it up to date. + for (let i = Math.min(messages.length, latestBridgedMessageIndex)-1; i >= 0; i--) { + const simulatedGatewayDispatchData = { + guild_id: guild.id, + mentions: [], + ...messages[i] + } + await module.exports.onMessageCreate(client, simulatedGatewayDispatchData) + } + } + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message @@ -85,7 +121,7 @@ module.exports = { /** * @param {import("./discord-client")} client - * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message + * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} data */ async onMessageUpdate(client, data) { if (data.webhook_id) { diff --git a/db/data-for-test.sql b/db/data-for-test.sql index aa82c91..ee31fe3 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -33,6 +33,7 @@ CREATE TABLE IF NOT EXISTS "channel_room" ( "room_id" TEXT NOT NULL UNIQUE, "name" TEXT, "nick" TEXT, + "thread_parent" TEXT, PRIMARY KEY("channel_id") ); CREATE TABLE IF NOT EXISTS "event_message" ( @@ -54,10 +55,10 @@ BEGIN TRANSACTION; INSERT INTO guild_space (guild_id, space_id) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); -INSERT INTO channel_room (channel_id, room_id, name, nick, is_thread) VALUES -('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, 0), -('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, 0), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', 0); +INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index f5fd9a9..6d39da7 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -41,9 +41,8 @@ async function ensureWebhook(channelID, forceCreate = false) { async function withWebhook(channelID, callback) { const webhook = await ensureWebhook(channelID, false) return callback(webhook).catch(e => { - console.error(e) // TODO: check if the error was webhook-related and if webhook.created === false, then: const webhook = ensureWebhook(channelID, true); return callback(webhook) - throw new Error(e) + throw e }) } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 74a45ce..b2c56a9 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -30,6 +30,12 @@ function eventToMessage(event) { username: displayName, avatar_url: avatarURL }) + } else if (event.content.msgtype === "m.emote") { + messages.push({ + content: `*${displayName} ${event.content.body}*`, + username: displayName, + avatar_url: avatarURL + }) } return messages diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index e687059..f0c4664 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -21,7 +21,7 @@ test("event2message: janky test", t => { } }), [{ - username: "cadence:cadence.moe", + username: "cadence", content: "test", avatar_url: undefined }] diff --git a/stdin.js b/stdin.js index 7e0db89..61a2a08 100644 --- a/stdin.js +++ b/stdin.js @@ -13,6 +13,7 @@ const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") const sendEvent = sync.require("./m2d/actions/send-event") +const eventDispatcher = sync.require("./d2m/event-dispatcher") const guildID = "112760669178241024" const extraContext = {} diff --git a/test/data.js b/test/data.js index b579a24..fc8cbbd 100644 --- a/test/data.js +++ b/test/data.js @@ -864,7 +864,7 @@ module.exports = { components: [], channel_id: "910283343378120754", author: { - username: "kumaccino", + username: "kumaccino", public_flags: 128, id: "113340068197859328", global_name: "kumaccino", @@ -876,6 +876,72 @@ module.exports = { guild_id: "112760669178241024" } }, + message_with_embeds: { + nothing_but_a_field: { + guild_id: "497159726455455754", + mentions: [], + id: "1141934888862351440", + type: 20, + content: "", + channel_id: "497161350934560778", + author: { + id: "1109360903096369153", + username: "Amanda 🎵", + avatar: "d56cd1b26e043ae512edae2214962faa", + discriminator: "2192", + public_flags: 524288, + flags: 524288, + bot: true, + banner: null, + accent_color: null, + global_name: null, + avatar_decoration_data: null, + banner_color: null + }, + attachments: [], + embeds: [ + { + type: "rich", + color: 3092790, + fields: [ + { + name: "Amanda 🎵#2192 <:online:606664341298872324>\nwillow tree, branch 0", + value: "**❯ Uptime:**\n3m 55s\n**❯ Memory:**\n64.45MB", + inline: false + } + ] + } + ], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-08-18T03:21:33.629000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + application_id: "1109360903096369153", + interaction: { + id: "1141934887608254475", + type: 2, + name: "stats", + user: { + id: "320067006521147393", + username: "papiophidian", + avatar: "47a19b0445069b826e136da4df4259bb", + discriminator: "0", + public_flags: 4194880, + flags: 4194880, + banner: null, + accent_color: null, + global_name: "PapiOphidian", + avatar_decoration_data: null, + banner_color: null + } + }, + webhook_id: "1109360903096369153" + } + }, message_update: { edit_by_webhook: { application_id: "684280192553844747", @@ -1277,5 +1343,38 @@ module.exports = { ], guild_id: "112760669178241024" } + }, + special_message: { + thread_name_change: { + id: "1142391602799710298", + type: 4, + content: "worming", + channel_id: "1142271000067706880", + author: { + id: "772659086046658620", + username: "cadence.worm", + avatar: "4b5c4b28051144e4c111f0113a0f1cf1", + discriminator: "0", + public_flags: 0, + flags: 0, + banner: null, + accent_color: null, + global_name: "cadence", + avatar_decoration_data: null, + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-08-19T09:36:22.717000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + position: 12 + } } } diff --git a/test/test.js b/test/test.js index 31ab099..03394f0 100644 --- a/test/test.js +++ b/test/test.js @@ -21,6 +21,7 @@ require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") +require("../d2m/converters/message-to-event.embeds.test") require("../d2m/converters/edit-to-changes.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") From 343675950467e660ae0d5e52dbd6d9dd4d082bfa Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 22:54:23 +1200 Subject: [PATCH 126/200] catch up on missed d->m messages when logging in --- d2m/actions/create-room.js | 28 +++++ d2m/actions/register-user.test.js | 2 +- d2m/actions/send-message.js | 9 +- .../message-to-event.embeds.test.js | 40 +++++++ d2m/converters/message-to-event.js | 25 ++++- d2m/converters/message-to-event.test.js | 12 ++- d2m/converters/user-to-mxid.test.js | 6 +- d2m/discord-packets.js | 1 + d2m/event-dispatcher.js | 38 ++++++- db/data-for-test.sql | 9 +- m2d/actions/channel-webhook.js | 3 +- m2d/converters/event-to-message.js | 6 ++ m2d/converters/event-to-message.test.js | 2 +- stdin.js | 1 + test/data.js | 101 +++++++++++++++++- test/test.js | 1 + 16 files changed, 268 insertions(+), 16 deletions(-) create mode 100644 d2m/converters/message-to-event.embeds.test.js diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 6d64d58..0178347 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -190,6 +190,33 @@ async function _syncRoom(channelID, shouldActuallySync) { } } +async function _unbridgeRoom(channelID) { + /** @ts-ignore @type {DiscordTypes.APIGuildChannel} */ + const channel = discord.channels.get(channelID) + assert.ok(channel) + const roomID = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channelID) + assert.ok(roomID) + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(channel.guild_id) + assert.ok(spaceID) + + // remove room from being a space member + await api.sendState(spaceID, "m.space.child", roomID, {}) + + // send a notification in the room + await api.sendEvent(roomID, "m.room.message", { + msgtype: "m.notice", + body: "⚠️ This room was removed from the bridge." + }) + + // leave room + await api.leaveRoom(roomID) + + // delete room from database + const {changes} = db.prepare("DELETE FROM channel_room WHERE room_id = ? AND channel_id = ?").run(roomID, channelID) + assert.equal(changes, 1) +} + + /** * @param {DiscordTypes.APIGuildTextChannel} channel * @param {string} spaceID @@ -237,3 +264,4 @@ module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState module.exports._convertNameAndTopic = convertNameAndTopic +module.exports._unbridgeRoom = _unbridgeRoom diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js index 0afce50..34470ba 100644 --- a/d2m/actions/register-user.test.js +++ b/d2m/actions/register-user.test.js @@ -8,7 +8,7 @@ test("member2state: general", async t => { await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), { avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", - displayname: "The Expert's Submarine | aprilsong", + displayname: "The Expert's Submarine", membership: "join", "moe.cadence.ooye.member": { avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 2132905..a5c8dac 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -22,8 +22,11 @@ async function sendMessage(message, guild) { let senderMxid = null if (!message.webhook_id) { - assert(message.member) - senderMxid = await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + if (message.member) { // available on a gateway message create event + senderMxid = await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) + } else { // well, good enough... + senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + } } const events = await messageToEvent.messageToEvent(message, guild, {}, {api}) @@ -35,7 +38,7 @@ async function sendMessage(message, guild) { const eventWithoutType = {...event} delete eventWithoutType.$type - const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid, new Date(message.timestamp).getTime()) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting diff --git a/d2m/converters/message-to-event.embeds.test.js b/d2m/converters/message-to-event.embeds.test.js new file mode 100644 index 0000000..7972f13 --- /dev/null +++ b/d2m/converters/message-to-event.embeds.test.js @@ -0,0 +1,40 @@ +const {test} = require("supertape") +const {messageToEvent} = require("./message-to-event") +const data = require("../../test/data") +const Ty = require("../../types") + +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} + +test("message2event embeds: nothing but a field", async t => { + const events = await messageToEvent(data.message_with_embeds.nothing_but_a_field, data.guild.general, {}) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "Amanda" + }]) +}) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 29730d4..3808beb 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -108,6 +108,13 @@ async function messageToEvent(message, guild, options = {}, di) { addMention(repliedToEventSenderMxid) } + let msgtype = "m.text" + // Handle message type 4, channel name changed + if (message.type === DiscordTypes.MessageType.ChannelNameChange) { + msgtype = "m.emote" + message.content = "changed the channel name to **" + message.content + "**" + } + // Text content appears first if (message.content) { let content = message.content @@ -188,7 +195,7 @@ async function messageToEvent(message, guild, options = {}, di) { const newTextMessageEvent = { $type: "m.room.message", "m.mentions": mentions, - msgtype: "m.text", + msgtype, body: body } @@ -239,6 +246,22 @@ async function messageToEvent(message, guild, options = {}, di) { size: attachment.size } } + } else if (attachment.content_type?.startsWith("video/") && attachment.width && attachment.height) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.video", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.description || attachment.filename, + filename: attachment.filename, + info: { + mimetype: attachment.content_type, + w: attachment.width, + h: attachment.height, + size: attachment.size + } + } } else { return { $type: "m.room.message", diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 86942a7..260ecda 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -330,4 +330,14 @@ test("message2event: very large attachment is linked instead of being uploaded", }]) }) -// TODO: read "edits of replies" in the spec +test("message2event: type 4 channel name change", async t => { + const events = await messageToEvent(data.special_message.thread_name_change, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.emote", + body: "changed the channel name to **worming**", + format: "org.matrix.custom.html", + formatted_body: "changed the channel name to worming" + }]) +}) diff --git a/d2m/converters/user-to-mxid.test.js b/d2m/converters/user-to-mxid.test.js index 8c4c430..1b31260 100644 --- a/d2m/converters/user-to-mxid.test.js +++ b/d2m/converters/user-to-mxid.test.js @@ -16,6 +16,10 @@ test("user2name: works on emojis", t => { t.equal(userToSimName({username: "🍪 Cookie Monster 🍪", discriminator: "0001"}), "cookie_monster") }) +test("user2name: works on single emoji at the end", t => { + t.equal(userToSimName({username: "Amanda 🎵", discriminator: "2192"}), "amanda") +}) + test("user2name: works on crazy name", t => { t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7//") }) @@ -34,4 +38,4 @@ test("user2name: uses ID if name becomes too short", t => { 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 79138b2..970bab4 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -41,6 +41,7 @@ const utils = { arr.push(thread.id) client.channels.set(thread.id, thread) } + eventDispatcher.checkMissedMessages(client, message.d) } else if (message.t === "GUILD_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 9387199..273e89b 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -63,6 +63,42 @@ module.exports = { }) }, + /** + * When logging back in, check if we missed any conversations in any channels. Bridge up to 49 missed messages per channel. + * If more messages were missed, only the latest missed message will be posted. TODO: Consider bridging more, or post a warning when skipping history? + * This can ONLY detect new messages, not any other kind of event. Any missed edits, deletes, reactions, etc will not be bridged. + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayGuildCreateDispatchData} guild + */ + async checkMissedMessages(client, guild) { + if (guild.unavailable) return + const bridgedChannels = db.prepare("SELECT channel_id FROM channel_room").pluck().all() + const prepared = db.prepare("SELECT message_id FROM event_message WHERE channel_id = ? AND message_id = ?").pluck() + for (const channel of guild.channels.concat(guild.threads)) { + if (!bridgedChannels.includes(channel.id)) continue + if (!channel.last_message_id) continue + const latestWasBridged = prepared.get(channel.id, channel.last_message_id) + if (latestWasBridged) continue + + /** More recent messages come first. */ + console.log(`[check missed messages] in ${channel.id} (${guild.name} / ${channel.name}) because its last message ${channel.last_message_id} is not in the database`) + const messages = await client.snow.channel.getChannelMessages(channel.id, {limit: 50}) + let latestBridgedMessageIndex = messages.findIndex(m => { + return prepared.get(channel.id, m.id) + }) + console.log(`[check missed messages] got ${messages.length} messages; last message that IS bridged is at position ${latestBridgedMessageIndex} in the channel`) + if (latestBridgedMessageIndex === -1) latestBridgedMessageIndex = 1 // rather than crawling the ENTIRE channel history, let's just bridge the most recent 1 message to make it up to date. + for (let i = Math.min(messages.length, latestBridgedMessageIndex)-1; i >= 0; i--) { + const simulatedGatewayDispatchData = { + guild_id: guild.id, + mentions: [], + ...messages[i] + } + await module.exports.onMessageCreate(client, simulatedGatewayDispatchData) + } + } + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message @@ -85,7 +121,7 @@ module.exports = { /** * @param {import("./discord-client")} client - * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message + * @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} data */ async onMessageUpdate(client, data) { if (data.webhook_id) { diff --git a/db/data-for-test.sql b/db/data-for-test.sql index aa82c91..ee31fe3 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -33,6 +33,7 @@ CREATE TABLE IF NOT EXISTS "channel_room" ( "room_id" TEXT NOT NULL UNIQUE, "name" TEXT, "nick" TEXT, + "thread_parent" TEXT, PRIMARY KEY("channel_id") ); CREATE TABLE IF NOT EXISTS "event_message" ( @@ -54,10 +55,10 @@ BEGIN TRANSACTION; INSERT INTO guild_space (guild_id, space_id) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); -INSERT INTO channel_room (channel_id, room_id, name, nick, is_thread) VALUES -('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, 0), -('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, 0), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', 0); +INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index f5fd9a9..6d39da7 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -41,9 +41,8 @@ async function ensureWebhook(channelID, forceCreate = false) { async function withWebhook(channelID, callback) { const webhook = await ensureWebhook(channelID, false) return callback(webhook).catch(e => { - console.error(e) // TODO: check if the error was webhook-related and if webhook.created === false, then: const webhook = ensureWebhook(channelID, true); return callback(webhook) - throw new Error(e) + throw e }) } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 74a45ce..b2c56a9 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -30,6 +30,12 @@ function eventToMessage(event) { username: displayName, avatar_url: avatarURL }) + } else if (event.content.msgtype === "m.emote") { + messages.push({ + content: `*${displayName} ${event.content.body}*`, + username: displayName, + avatar_url: avatarURL + }) } return messages diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index e687059..f0c4664 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -21,7 +21,7 @@ test("event2message: janky test", t => { } }), [{ - username: "cadence:cadence.moe", + username: "cadence", content: "test", avatar_url: undefined }] diff --git a/stdin.js b/stdin.js index 7e0db89..61a2a08 100644 --- a/stdin.js +++ b/stdin.js @@ -13,6 +13,7 @@ const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") const sendEvent = sync.require("./m2d/actions/send-event") +const eventDispatcher = sync.require("./d2m/event-dispatcher") const guildID = "112760669178241024" const extraContext = {} diff --git a/test/data.js b/test/data.js index b579a24..fc8cbbd 100644 --- a/test/data.js +++ b/test/data.js @@ -864,7 +864,7 @@ module.exports = { components: [], channel_id: "910283343378120754", author: { - username: "kumaccino", + username: "kumaccino", public_flags: 128, id: "113340068197859328", global_name: "kumaccino", @@ -876,6 +876,72 @@ module.exports = { guild_id: "112760669178241024" } }, + message_with_embeds: { + nothing_but_a_field: { + guild_id: "497159726455455754", + mentions: [], + id: "1141934888862351440", + type: 20, + content: "", + channel_id: "497161350934560778", + author: { + id: "1109360903096369153", + username: "Amanda 🎵", + avatar: "d56cd1b26e043ae512edae2214962faa", + discriminator: "2192", + public_flags: 524288, + flags: 524288, + bot: true, + banner: null, + accent_color: null, + global_name: null, + avatar_decoration_data: null, + banner_color: null + }, + attachments: [], + embeds: [ + { + type: "rich", + color: 3092790, + fields: [ + { + name: "Amanda 🎵#2192 <:online:606664341298872324>\nwillow tree, branch 0", + value: "**❯ Uptime:**\n3m 55s\n**❯ Memory:**\n64.45MB", + inline: false + } + ] + } + ], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-08-18T03:21:33.629000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + application_id: "1109360903096369153", + interaction: { + id: "1141934887608254475", + type: 2, + name: "stats", + user: { + id: "320067006521147393", + username: "papiophidian", + avatar: "47a19b0445069b826e136da4df4259bb", + discriminator: "0", + public_flags: 4194880, + flags: 4194880, + banner: null, + accent_color: null, + global_name: "PapiOphidian", + avatar_decoration_data: null, + banner_color: null + } + }, + webhook_id: "1109360903096369153" + } + }, message_update: { edit_by_webhook: { application_id: "684280192553844747", @@ -1277,5 +1343,38 @@ module.exports = { ], guild_id: "112760669178241024" } + }, + special_message: { + thread_name_change: { + id: "1142391602799710298", + type: 4, + content: "worming", + channel_id: "1142271000067706880", + author: { + id: "772659086046658620", + username: "cadence.worm", + avatar: "4b5c4b28051144e4c111f0113a0f1cf1", + discriminator: "0", + public_flags: 0, + flags: 0, + banner: null, + accent_color: null, + global_name: "cadence", + avatar_decoration_data: null, + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-08-19T09:36:22.717000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + position: 12 + } } } diff --git a/test/test.js b/test/test.js index 31ab099..03394f0 100644 --- a/test/test.js +++ b/test/test.js @@ -21,6 +21,7 @@ require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") +require("../d2m/converters/message-to-event.embeds.test") require("../d2m/converters/edit-to-changes.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") From a6f27a114472093ec137bfdecb60a3bc0bed419e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 23:12:36 +1200 Subject: [PATCH 127/200] synchronise channel updates --- d2m/actions/create-room.js | 7 +++++-- d2m/discord-packets.js | 12 +++++++++++- d2m/event-dispatcher.js | 13 +++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 0178347..484470e 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -252,8 +252,11 @@ async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) for (const channelID of channelIDs) { - if (discord.channels.get(channelID)?.type === DiscordTypes.ChannelType.GuildText) { // TODO: guild sync thread channels and such. maybe make a helper function to check if a given channel is syncable? - await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + const allowedTypes = [DiscordTypes.ChannelType.GuildText, DiscordTypes.ChannelType.PublicThread] + // @ts-ignore + if (allowedTypes.includes(discord.channels.get(channelID)?.type)) { + const roomID = await syncRoom(channelID) + console.log(`synced ${channelID} <-> ${roomID}`) } } } diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 970bab4..f172321 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -44,6 +44,10 @@ const utils = { eventDispatcher.checkMissedMessages(client, message.d) + } else if (message.t === "CHANNEL_UPDATE" || message.t === "THREAD_UPDATE") { + client.channels.set(message.d.id, message.d) + + } else if (message.t === "GUILD_DELETE") { client.guilds.delete(message.d.id) const channels = client.guildChannelMap.get(message.d.id) @@ -74,7 +78,13 @@ const utils = { // Event dispatcher for OOYE bridge operations try { - if (message.t === "MESSAGE_CREATE") { + if (message.t === "CHANNEL_UPDATE") { + await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) + + } else if (message.t === "THREAD_UPDATE") { + await eventDispatcher.onChannelOrThreadUpdate(client, message.d, true) + + } else if (message.t === "MESSAGE_CREATE") { await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 273e89b..ad2a2d2 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -10,6 +10,8 @@ const editMessage = sync.require("./actions/edit-message") const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("./actions/create-room")}) */ +const createRoom = sync.require("./actions/create-room") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") @@ -99,6 +101,17 @@ module.exports = { } }, + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayChannelUpdateDispatchData} channelOrThread + * @param {boolean} isThread + */ + async onChannelOrThreadUpdate(client, channelOrThread, isThread) { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(channelOrThread.id) + if (!roomID) return // No target room to update the data on + await createRoom.syncRoom(channelOrThread.id) + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message From 08d3f3d804b54e6bc545f34f3658eb03c5dc785f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 19 Aug 2023 23:12:36 +1200 Subject: [PATCH 128/200] synchronise channel updates --- d2m/actions/create-room.js | 7 +++++-- d2m/discord-packets.js | 12 +++++++++++- d2m/event-dispatcher.js | 13 +++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 0178347..484470e 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -252,8 +252,11 @@ async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) for (const channelID of channelIDs) { - if (discord.channels.get(channelID)?.type === DiscordTypes.ChannelType.GuildText) { // TODO: guild sync thread channels and such. maybe make a helper function to check if a given channel is syncable? - await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r)) + const allowedTypes = [DiscordTypes.ChannelType.GuildText, DiscordTypes.ChannelType.PublicThread] + // @ts-ignore + if (allowedTypes.includes(discord.channels.get(channelID)?.type)) { + const roomID = await syncRoom(channelID) + console.log(`synced ${channelID} <-> ${roomID}`) } } } diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 970bab4..f172321 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -44,6 +44,10 @@ const utils = { eventDispatcher.checkMissedMessages(client, message.d) + } else if (message.t === "CHANNEL_UPDATE" || message.t === "THREAD_UPDATE") { + client.channels.set(message.d.id, message.d) + + } else if (message.t === "GUILD_DELETE") { client.guilds.delete(message.d.id) const channels = client.guildChannelMap.get(message.d.id) @@ -74,7 +78,13 @@ const utils = { // Event dispatcher for OOYE bridge operations try { - if (message.t === "MESSAGE_CREATE") { + if (message.t === "CHANNEL_UPDATE") { + await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) + + } else if (message.t === "THREAD_UPDATE") { + await eventDispatcher.onChannelOrThreadUpdate(client, message.d, true) + + } else if (message.t === "MESSAGE_CREATE") { await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 273e89b..ad2a2d2 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -10,6 +10,8 @@ const editMessage = sync.require("./actions/edit-message") const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("./actions/create-room")}) */ +const createRoom = sync.require("./actions/create-room") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") @@ -99,6 +101,17 @@ module.exports = { } }, + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayChannelUpdateDispatchData} channelOrThread + * @param {boolean} isThread + */ + async onChannelOrThreadUpdate(client, channelOrThread, isThread) { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(channelOrThread.id) + if (!roomID) return // No target room to update the data on + await createRoom.syncRoom(channelOrThread.id) + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message From ccdd2f0c9c438a522aecf98b412a2bb2c5493c99 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 08:07:05 +1200 Subject: [PATCH 129/200] attempt to support THREAD_CREATE but it explodes --- d2m/converters/message-to-event.js | 10 ++++++---- d2m/discord-packets.js | 10 ++++++++++ d2m/event-dispatcher.js | 13 ++++++++++++- notes.md | 10 ++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 3808beb..c34b389 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -27,11 +27,13 @@ function getDiscordParseCallbacks(message, useHTML) { }, /** @param {{id: string, type: "discordChannel"}} node */ channel: node => { - const {room_id, name, nick} = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) - if (room_id && useHTML) { - return `#${nick || name}` + const row = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) + if (!row) { + return `<#${node.id}>` // fallback for when this channel is not bridged + } else if (useHTML) { + return `#${row.nick || row.name}` } else { - return `#${nick || name}` + return `#${row.nick || row.name}` } }, /** @param {{animated: boolean, name: string, id: string, type: "discordEmoji"}} node */ diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index f172321..776d4b1 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -44,6 +44,10 @@ const utils = { eventDispatcher.checkMissedMessages(client, message.d) + } else if (message.t === "THREAD_CREATE") { + client.channels.set(message.d.id, message.d) + + } else if (message.t === "CHANNEL_UPDATE" || message.t === "THREAD_UPDATE") { client.channels.set(message.d.id, message.d) @@ -81,13 +85,19 @@ const utils = { if (message.t === "CHANNEL_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) + } else if (message.t === "THREAD_CREATE") { + console.log(message) + // await eventDispatcher.onThreadCreate(client, message.d) + } else if (message.t === "THREAD_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, true) } else if (message.t === "MESSAGE_CREATE") { + console.log(message) await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { + console.log(message) await eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index ad2a2d2..154cb34 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -101,7 +101,18 @@ module.exports = { } }, - /** + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").APIChannel} thread + */ + async onThreadCreate(client, thread) { + console.log(thread) + const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(thread.parent_id) + if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel + await createRoom.syncRoom(thread.id) + }, + + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayChannelUpdateDispatchData} channelOrThread * @param {boolean} isThread diff --git a/notes.md b/notes.md index 1dcbcd7..dbc5ecd 100644 --- a/notes.md +++ b/notes.md @@ -9,6 +9,16 @@ A database will be used to store the discord id to matrix event id mapping. Tabl There needs to be a way to easily manually trigger something later. For example, it should be easy to manually retry sending a message, or check all members for changes, etc. +## Discord's gateway when a new thread is created from an existing message: + +1. Regular MESSAGE_CREATE of the message that it's going to branch off in the future. Example ID -6423 +2. It MESSAGE_UPDATEd the ID -6423 with this whole data: {id:-6423,flags: 32,channel_id:-2084,guild_id:-1727} (ID is the message ID it's branching off, channel ID is the parent channel containing the message ID it's branching off) +3. It THREAD_CREATEd and gave us a channel object with type 11 (public thread) and parent ID -2084 and ID -6423. +4. It MESSAGE_CREATEd type 21 with blank content and a message reference pointing towards channel -2084 message -6423. (That's the message it branched from in the parent channel.) This MESSAGE_CREATE got ID -4631 (a new ID). Apart from that it's a regular message object. +5. Finally, as the first "real" message in that thread (which a user must send to create that thread!) it sent a regular message object with a new message ID and a channel ID of -6423. + +When viewing this thread, it shows the message branched from at the top, and then the first "real" message right underneath, as separate groups. + ## Current manual process for setting up a server 1. Call createSpace.createSpace(discord.guilds.get(GUILD_ID)) From d666c0aedb3ad97b78ad85ef4a915ce6317548fe Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 08:07:05 +1200 Subject: [PATCH 130/200] attempt to support THREAD_CREATE but it explodes --- d2m/converters/message-to-event.js | 10 ++++++---- d2m/discord-packets.js | 10 ++++++++++ d2m/event-dispatcher.js | 13 ++++++++++++- notes.md | 10 ++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 3808beb..c34b389 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -27,11 +27,13 @@ function getDiscordParseCallbacks(message, useHTML) { }, /** @param {{id: string, type: "discordChannel"}} node */ channel: node => { - const {room_id, name, nick} = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) - if (room_id && useHTML) { - return `#${nick || name}` + const row = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) + if (!row) { + return `<#${node.id}>` // fallback for when this channel is not bridged + } else if (useHTML) { + return `#${row.nick || row.name}` } else { - return `#${nick || name}` + return `#${row.nick || row.name}` } }, /** @param {{animated: boolean, name: string, id: string, type: "discordEmoji"}} node */ diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index f172321..776d4b1 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -44,6 +44,10 @@ const utils = { eventDispatcher.checkMissedMessages(client, message.d) + } else if (message.t === "THREAD_CREATE") { + client.channels.set(message.d.id, message.d) + + } else if (message.t === "CHANNEL_UPDATE" || message.t === "THREAD_UPDATE") { client.channels.set(message.d.id, message.d) @@ -81,13 +85,19 @@ const utils = { if (message.t === "CHANNEL_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) + } else if (message.t === "THREAD_CREATE") { + console.log(message) + // await eventDispatcher.onThreadCreate(client, message.d) + } else if (message.t === "THREAD_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, true) } else if (message.t === "MESSAGE_CREATE") { + console.log(message) await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { + console.log(message) await eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index ad2a2d2..154cb34 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -101,7 +101,18 @@ module.exports = { } }, - /** + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").APIChannel} thread + */ + async onThreadCreate(client, thread) { + console.log(thread) + const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(thread.parent_id) + if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel + await createRoom.syncRoom(thread.id) + }, + + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayChannelUpdateDispatchData} channelOrThread * @param {boolean} isThread diff --git a/notes.md b/notes.md index 1dcbcd7..dbc5ecd 100644 --- a/notes.md +++ b/notes.md @@ -9,6 +9,16 @@ A database will be used to store the discord id to matrix event id mapping. Tabl There needs to be a way to easily manually trigger something later. For example, it should be easy to manually retry sending a message, or check all members for changes, etc. +## Discord's gateway when a new thread is created from an existing message: + +1. Regular MESSAGE_CREATE of the message that it's going to branch off in the future. Example ID -6423 +2. It MESSAGE_UPDATEd the ID -6423 with this whole data: {id:-6423,flags: 32,channel_id:-2084,guild_id:-1727} (ID is the message ID it's branching off, channel ID is the parent channel containing the message ID it's branching off) +3. It THREAD_CREATEd and gave us a channel object with type 11 (public thread) and parent ID -2084 and ID -6423. +4. It MESSAGE_CREATEd type 21 with blank content and a message reference pointing towards channel -2084 message -6423. (That's the message it branched from in the parent channel.) This MESSAGE_CREATE got ID -4631 (a new ID). Apart from that it's a regular message object. +5. Finally, as the first "real" message in that thread (which a user must send to create that thread!) it sent a regular message object with a new message ID and a channel ID of -6423. + +When viewing this thread, it shows the message branched from at the top, and then the first "real" message right underneath, as separate groups. + ## Current manual process for setting up a server 1. Call createSpace.createSpace(discord.guilds.get(GUILD_ID)) From 97ef72252c5c36597d15391125f9f5662a93e6f0 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 17:25:51 +1200 Subject: [PATCH 131/200] explain inflight thread room creation --- d2m/actions/announce-thread.js | 44 ++++++++++++++++++ d2m/actions/create-room.js | 68 ++++++++++++++++------------ d2m/event-dispatcher.js | 7 ++- notes.md | 82 ++++++++++++++++++++++++++++++++-- 4 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 d2m/actions/announce-thread.js diff --git a/d2m/actions/announce-thread.js b/d2m/actions/announce-thread.js new file mode 100644 index 0000000..546d307 --- /dev/null +++ b/d2m/actions/announce-thread.js @@ -0,0 +1,44 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../converters/message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @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 {string} parentRoomID + * @param {string} threadRoomID + * @param {import("discord-api-types/v10").APIThreadChannel} thread + */ +async function announceThread(parentRoomID, threadRoomID, thread) { + /** @type {string?} */ + const creatorMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(thread.owner_id) + /** @type {string?} */ + const branchedFromEventID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").get(thread.id) + + const msgtype = creatorMxid ? "m.emote" : "m.text" + const template = creatorMxid ? "started a thread:" : "Thread started:" + let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}` + let html = `${template} ${thread.name}` + + const mentions = {} + + await api.sendEvent(parentRoomID, "m.room.message", { + msgtype, + body: `${template} , + format: "org.matrix.custom.html", + formatted_body: "", + "m.mentions": mentions + + }, creatorMxid) +} + +module.exports.announceThread = announceThread diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 484470e..2ee0913 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -12,6 +12,9 @@ const api = sync.require("../../matrix/api") /** @type {import("../../matrix/kstate")} */ const ks = sync.require("../../matrix/kstate") +/** @type {Map>} channel ID -> Promise */ +const inflightRoomCreate = new Map() + /** * @param {string} roomID */ @@ -98,23 +101,20 @@ async function channelToKState(channel, guild) { * @returns {Promise} room ID */ async function createRoom(channel, guild, spaceID, kstate) { + let threadParent = null + if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id + const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) const roomID = await api.createRoom({ name: convertedName, topic: convertedTopic, preset: "private_chat", visibility: "private", - invite: ["@cadence:cadence.moe"], // TODO + invite, initial_state: ks.kstateToState(kstate) }) - let threadParent = null - if (channel.type === DiscordTypes.ChannelType.PublicThread) { - /** @type {DiscordTypes.APIThreadChannel} */ // @ts-ignore - const thread = channel - threadParent = thread.parent_id - } - db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) // Put the newly created child into the space @@ -162,32 +162,42 @@ async function _syncRoom(channelID, shouldActuallySync) { assert.ok(channel) const guild = channelToGuild(channel) + if (inflightRoomCreate.has(channelID)) { + await inflightRoomCreate.get(channelID) // just waiting, and then doing a new db query afterwards, is the simplest way of doing it + } + /** @type {{room_id: string, thread_parent: string?}} */ const existing = db.prepare("SELECT room_id, thread_parent from channel_room WHERE channel_id = ?").get(channelID) if (!existing) { - const {spaceID, channelKState} = await channelToKState(channel, guild) - return createRoom(channel, guild, spaceID, channelKState) - } else { - if (!shouldActuallySync) { - return existing.room_id // only need to ensure room exists, and it does. return the room ID - } - - console.log(`[room sync] to matrix: ${channel.name}`) - - const {spaceID, channelKState} = await channelToKState(channel, guild) - - // sync channel state to room - const roomKState = await roomToKState(existing.room_id) - const roomDiff = ks.diffKState(roomKState, channelKState) - const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) - - // sync room as space member - const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) - await Promise.all([roomApply, spaceApply]) - - return existing.room_id + const creation = (async () => { + const {spaceID, channelKState} = await channelToKState(channel, guild) + const roomID = await createRoom(channel, guild, spaceID, channelKState) + inflightRoomCreate.delete(channelID) // OK to release inflight waiters now. they will read the correct `existing` row + return roomID + })() + inflightRoomCreate.set(channelID, creation) + return creation // Naturally, the newly created room is already up to date, so we can always skip syncing here. } + + if (!shouldActuallySync) { + return existing.room_id // only need to ensure room exists, and it does. return the room ID + } + + console.log(`[room sync] to matrix: ${channel.name}`) + + const {spaceID, channelKState} = await channelToKState(channel, guild) + + // sync channel state to room + const roomKState = await roomToKState(existing.room_id) + const roomDiff = ks.diffKState(roomKState, channelKState) + const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) + + // sync room as space member + const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) + await Promise.all([roomApply, spaceApply]) + + return existing.room_id } async function _unbridgeRoom(channelID) { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 154cb34..6e4ffc4 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -102,14 +102,17 @@ module.exports = { }, /** + * 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" * @param {import("./discord-client")} client - * @param {import("discord-api-types/v10").APIChannel} thread + * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async onThreadCreate(client, thread) { console.log(thread) const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(thread.parent_id) if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel - await createRoom.syncRoom(thread.id) + const threadRoomID = await createRoom.syncRoom(thread.id) // Create room (will share the same inflight as the initial message to the thread) + await announceThread.announceThread(parentRoomID, threadRoomID, thread) }, /** diff --git a/notes.md b/notes.md index dbc5ecd..7383d3e 100644 --- a/notes.md +++ b/notes.md @@ -19,10 +19,48 @@ There needs to be a way to easily manually trigger something later. For example, When viewing this thread, it shows the message branched from at the top, and then the first "real" message right underneath, as separate groups. +### Problem 1 + +If THREAD_CREATE creates the matrix room, this will still be in-flight when MESSAGE_CREATE ensures the room exists and creates a room too. There will be two rooms created and the bridge falls over. + +#### Possible solution: Ignore THREAD_CREATE + +Then the room will be implicitly created by the two MESSAGE_CREATEs, which are in series. + +#### Possible solution: Store in-flight room creations ✔️ + +Then the room will definitely only be created once, and we can still handle both events if we want to do special things for THREAD_CREATE. + +#### Possible solution: Don't implicitly create rooms + +But then old and current threads would never have their messages bridged unless I manually intervene. Don't like that. + +### Problem 2 + +MESSAGE_UPDATE with flags=32 is telling that message to become an announcement of the new thread's creation, but this happens before THREAD_CREATE. The matrix room won't actually exist when we see MESSAGE_UPDATE, therefore we cannot make the MESSAGE_UPDATE link to the new thread. + +#### Possible solution: Ignore MESSAGE_UPDATE and bridge THREAD_CREATE as the announcement ✔️ + +When seeing THREAD_CREATE (if we use solution B above) we could react to it by creating the thread announcement message in the parent channel. This is possible because THREAD_CREATE gives a thread object and that includes the parent channel ID to send the announcement message to. + +While the thread announcement message could look more like Discord-side by being an edit of the message it branched off: + +> look at my cat +> +> Thread started: [#cat thread] + +if the thread branched off a matrix user's message then the bridge wouldn't be able to edit it, so this wouldn't work. + +Regardless, it would make the most sense to post a new message like this to the parent room: + +> > Reply to: look at my cat +> +> [me] started a new thread: [#cat thread] + ## Current manual process for setting up a server 1. Call createSpace.createSpace(discord.guilds.get(GUILD_ID)) -2. Call createRoom.createAllForGuild(GUILD_ID) +2. Call createRoom.createAllForGuild(GUILD_ID) // TODO: Only create rooms that the bridge bot has read permissions in! 3. Edit source code of event-dispatcher.js isGuildAllowed() and add the guild ID to the list 4. If developing, make sure SSH port forward is activated, then wait for events to sync over! @@ -101,9 +139,9 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See ## Webhook message sent - Consider using the _ooye_bot account to send all webhook messages to prevent extraneous joins? - - Downside: the profile information from the most recently sent message would stick around in the member list. This is toleable. + - Downside: the profile information from the most recently sent message would stick around in the member list. This is tolerable. - Otherwise, could use an account per webhook ID, but if webhook IDs are often deleted and re-created, this could still end up leaving too many accounts in the room. -- The original bridge uses an account per webhook display name, which does the most sense in terms of canonical accounts, but leaves too many accounts in the room. +- The original bridge uses an account per webhook display name, which makes the most sense in terms of canonical accounts, but leaves too many accounts in the room. ## Message deleted @@ -113,7 +151,9 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See ## Message edited / embeds added 1. Look up equivalents on matrix. -2. Replace content on matrix. +2. Transform content. +3. Build replacement event with fallbacks. +4. Send to matrix. ## Reaction added @@ -148,3 +188,37 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See 3. The emojis may now be sent by Matrix users! TOSPEC: m2d emoji uploads?? + +## Issues if the bridge database is rolled back + +### channel_room table + +- Duplicate rooms will be created on matrix. + +### sim table + +- Sims will already be registered, registration will fail, all events from those sims will fail. + +### sim_member table + +- Sims won't be invited because they are already joined, all events from those sims will fail. + +### guild_space table + +- channelToKState will fail, so channel data differences won't be calculated, so channel/thread creation and sync will fail. + +### event_message table + +- Events referenced by other events will be dropped, for example + - edits will be ignored + - deletes will be ignored + - reactions will be ignored + - replies won't generate a reply + +### file + +- Some files like avatars may be re-uploaded to the matrix content repository, secretly taking more storage space on the server. + +### webhook + +- Some duplicate webhooks may be created. From c22f434c1f64b5ae684dabb9ccecf2fa37f61fdd Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 17:25:51 +1200 Subject: [PATCH 132/200] explain inflight thread room creation --- d2m/actions/announce-thread.js | 44 ++++++++++++++++++ d2m/actions/create-room.js | 68 ++++++++++++++++------------ d2m/event-dispatcher.js | 7 ++- notes.md | 82 ++++++++++++++++++++++++++++++++-- 4 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 d2m/actions/announce-thread.js diff --git a/d2m/actions/announce-thread.js b/d2m/actions/announce-thread.js new file mode 100644 index 0000000..546d307 --- /dev/null +++ b/d2m/actions/announce-thread.js @@ -0,0 +1,44 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../converters/message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @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 {string} parentRoomID + * @param {string} threadRoomID + * @param {import("discord-api-types/v10").APIThreadChannel} thread + */ +async function announceThread(parentRoomID, threadRoomID, thread) { + /** @type {string?} */ + const creatorMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(thread.owner_id) + /** @type {string?} */ + const branchedFromEventID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").get(thread.id) + + const msgtype = creatorMxid ? "m.emote" : "m.text" + const template = creatorMxid ? "started a thread:" : "Thread started:" + let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}` + let html = `${template} ${thread.name}` + + const mentions = {} + + await api.sendEvent(parentRoomID, "m.room.message", { + msgtype, + body: `${template} , + format: "org.matrix.custom.html", + formatted_body: "", + "m.mentions": mentions + + }, creatorMxid) +} + +module.exports.announceThread = announceThread diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 484470e..2ee0913 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -12,6 +12,9 @@ const api = sync.require("../../matrix/api") /** @type {import("../../matrix/kstate")} */ const ks = sync.require("../../matrix/kstate") +/** @type {Map>} channel ID -> Promise */ +const inflightRoomCreate = new Map() + /** * @param {string} roomID */ @@ -98,23 +101,20 @@ async function channelToKState(channel, guild) { * @returns {Promise} room ID */ async function createRoom(channel, guild, spaceID, kstate) { + let threadParent = null + if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id + const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) const roomID = await api.createRoom({ name: convertedName, topic: convertedTopic, preset: "private_chat", visibility: "private", - invite: ["@cadence:cadence.moe"], // TODO + invite, initial_state: ks.kstateToState(kstate) }) - let threadParent = null - if (channel.type === DiscordTypes.ChannelType.PublicThread) { - /** @type {DiscordTypes.APIThreadChannel} */ // @ts-ignore - const thread = channel - threadParent = thread.parent_id - } - db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) // Put the newly created child into the space @@ -162,32 +162,42 @@ async function _syncRoom(channelID, shouldActuallySync) { assert.ok(channel) const guild = channelToGuild(channel) + if (inflightRoomCreate.has(channelID)) { + await inflightRoomCreate.get(channelID) // just waiting, and then doing a new db query afterwards, is the simplest way of doing it + } + /** @type {{room_id: string, thread_parent: string?}} */ const existing = db.prepare("SELECT room_id, thread_parent from channel_room WHERE channel_id = ?").get(channelID) if (!existing) { - const {spaceID, channelKState} = await channelToKState(channel, guild) - return createRoom(channel, guild, spaceID, channelKState) - } else { - if (!shouldActuallySync) { - return existing.room_id // only need to ensure room exists, and it does. return the room ID - } - - console.log(`[room sync] to matrix: ${channel.name}`) - - const {spaceID, channelKState} = await channelToKState(channel, guild) - - // sync channel state to room - const roomKState = await roomToKState(existing.room_id) - const roomDiff = ks.diffKState(roomKState, channelKState) - const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) - - // sync room as space member - const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) - await Promise.all([roomApply, spaceApply]) - - return existing.room_id + const creation = (async () => { + const {spaceID, channelKState} = await channelToKState(channel, guild) + const roomID = await createRoom(channel, guild, spaceID, channelKState) + inflightRoomCreate.delete(channelID) // OK to release inflight waiters now. they will read the correct `existing` row + return roomID + })() + inflightRoomCreate.set(channelID, creation) + return creation // Naturally, the newly created room is already up to date, so we can always skip syncing here. } + + if (!shouldActuallySync) { + return existing.room_id // only need to ensure room exists, and it does. return the room ID + } + + console.log(`[room sync] to matrix: ${channel.name}`) + + const {spaceID, channelKState} = await channelToKState(channel, guild) + + // sync channel state to room + const roomKState = await roomToKState(existing.room_id) + const roomDiff = ks.diffKState(roomKState, channelKState) + const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) + + // sync room as space member + const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) + await Promise.all([roomApply, spaceApply]) + + return existing.room_id } async function _unbridgeRoom(channelID) { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 154cb34..6e4ffc4 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -102,14 +102,17 @@ module.exports = { }, /** + * 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" * @param {import("./discord-client")} client - * @param {import("discord-api-types/v10").APIChannel} thread + * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async onThreadCreate(client, thread) { console.log(thread) const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(thread.parent_id) if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel - await createRoom.syncRoom(thread.id) + const threadRoomID = await createRoom.syncRoom(thread.id) // Create room (will share the same inflight as the initial message to the thread) + await announceThread.announceThread(parentRoomID, threadRoomID, thread) }, /** diff --git a/notes.md b/notes.md index dbc5ecd..7383d3e 100644 --- a/notes.md +++ b/notes.md @@ -19,10 +19,48 @@ There needs to be a way to easily manually trigger something later. For example, When viewing this thread, it shows the message branched from at the top, and then the first "real" message right underneath, as separate groups. +### Problem 1 + +If THREAD_CREATE creates the matrix room, this will still be in-flight when MESSAGE_CREATE ensures the room exists and creates a room too. There will be two rooms created and the bridge falls over. + +#### Possible solution: Ignore THREAD_CREATE + +Then the room will be implicitly created by the two MESSAGE_CREATEs, which are in series. + +#### Possible solution: Store in-flight room creations ✔️ + +Then the room will definitely only be created once, and we can still handle both events if we want to do special things for THREAD_CREATE. + +#### Possible solution: Don't implicitly create rooms + +But then old and current threads would never have their messages bridged unless I manually intervene. Don't like that. + +### Problem 2 + +MESSAGE_UPDATE with flags=32 is telling that message to become an announcement of the new thread's creation, but this happens before THREAD_CREATE. The matrix room won't actually exist when we see MESSAGE_UPDATE, therefore we cannot make the MESSAGE_UPDATE link to the new thread. + +#### Possible solution: Ignore MESSAGE_UPDATE and bridge THREAD_CREATE as the announcement ✔️ + +When seeing THREAD_CREATE (if we use solution B above) we could react to it by creating the thread announcement message in the parent channel. This is possible because THREAD_CREATE gives a thread object and that includes the parent channel ID to send the announcement message to. + +While the thread announcement message could look more like Discord-side by being an edit of the message it branched off: + +> look at my cat +> +> Thread started: [#cat thread] + +if the thread branched off a matrix user's message then the bridge wouldn't be able to edit it, so this wouldn't work. + +Regardless, it would make the most sense to post a new message like this to the parent room: + +> > Reply to: look at my cat +> +> [me] started a new thread: [#cat thread] + ## Current manual process for setting up a server 1. Call createSpace.createSpace(discord.guilds.get(GUILD_ID)) -2. Call createRoom.createAllForGuild(GUILD_ID) +2. Call createRoom.createAllForGuild(GUILD_ID) // TODO: Only create rooms that the bridge bot has read permissions in! 3. Edit source code of event-dispatcher.js isGuildAllowed() and add the guild ID to the list 4. If developing, make sure SSH port forward is activated, then wait for events to sync over! @@ -101,9 +139,9 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See ## Webhook message sent - Consider using the _ooye_bot account to send all webhook messages to prevent extraneous joins? - - Downside: the profile information from the most recently sent message would stick around in the member list. This is toleable. + - Downside: the profile information from the most recently sent message would stick around in the member list. This is tolerable. - Otherwise, could use an account per webhook ID, but if webhook IDs are often deleted and re-created, this could still end up leaving too many accounts in the room. -- The original bridge uses an account per webhook display name, which does the most sense in terms of canonical accounts, but leaves too many accounts in the room. +- The original bridge uses an account per webhook display name, which makes the most sense in terms of canonical accounts, but leaves too many accounts in the room. ## Message deleted @@ -113,7 +151,9 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See ## Message edited / embeds added 1. Look up equivalents on matrix. -2. Replace content on matrix. +2. Transform content. +3. Build replacement event with fallbacks. +4. Send to matrix. ## Reaction added @@ -148,3 +188,37 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See 3. The emojis may now be sent by Matrix users! TOSPEC: m2d emoji uploads?? + +## Issues if the bridge database is rolled back + +### channel_room table + +- Duplicate rooms will be created on matrix. + +### sim table + +- Sims will already be registered, registration will fail, all events from those sims will fail. + +### sim_member table + +- Sims won't be invited because they are already joined, all events from those sims will fail. + +### guild_space table + +- channelToKState will fail, so channel data differences won't be calculated, so channel/thread creation and sync will fail. + +### event_message table + +- Events referenced by other events will be dropped, for example + - edits will be ignored + - deletes will be ignored + - reactions will be ignored + - replies won't generate a reply + +### file + +- Some files like avatars may be re-uploaded to the matrix content repository, secretly taking more storage space on the server. + +### webhook + +- Some duplicate webhooks may be created. From 63ae6607bf205892a7ae0171bc9f7700ce33fc05 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 21:04:41 +1200 Subject: [PATCH 133/200] changing spaces to tabs --- .vscode/settings.json | 3 + d2m/actions/add-reaction.js | 22 ++--- m2d/actions/channel-webhook.js | 8 +- m2d/actions/send-event.js | 10 +-- matrix/api.js | 129 +++++++++++++++++----------- matrix/kstate.test.js | 26 +++--- matrix/read-registration.test.js | 10 +-- scripts/save-channel-names-to-db.js | 48 +++++------ scripts/save-event-types-to-db.js | 18 ++-- 9 files changed, 154 insertions(+), 120 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c63c08..9f1e183 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,5 @@ { + "editor.insertSpaces": false, + "editor.detectIndentation": false, + "editor.tabSize": 3 } diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index b46af59..2eab8f0 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -15,20 +15,20 @@ 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) - 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 // Nothing can be done if the parent message was never bridged. - assert.equal(typeof parentID, "string") + const user = data.member?.user + assert.ok(user && user.username) + 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 // Nothing can be done if the parent message was never bridged. + assert.equal(typeof parentID, "string") const roomID = await createRoom.ensureRoom(data.channel_id) const senderMxid = await registerUser.ensureSimJoined(user, roomID) const eventID = await api.sendEvent(roomID, "m.reaction", { - "m.relates_to": { - rel_type: "m.annotation", - event_id: parentID, - key: data.emoji.name - } - }, senderMxid) + "m.relates_to": { + rel_type: "m.annotation", + event_id: parentID, + key: data.emoji.name + } + }, senderMxid) return eventID } diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index 6d39da7..b0bc072 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -52,10 +52,10 @@ async function withWebhook(channelID, callback) { * @param {string} [threadID] */ async function sendMessageWithWebhook(channelID, data, threadID) { - const result = await withWebhook(channelID, async webhook => { - return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, thread_id: threadID, disableEveryone: true}) - }) - return result + const result = await withWebhook(channelID, async webhook => { + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, thread_id: threadID, disableEveryone: true}) + }) + return result } module.exports.ensureWebhook = ensureWebhook diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 88ba0fd..39eed22 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -12,7 +12,7 @@ const eventToMessage = sync.require("../converters/event-to-message") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { - // TODO: we just assume the bridge has already been created + // TODO: we just assume the bridge has already been created const row = db.prepare("SELECT channel_id, thread_parent FROM channel_room WHERE room_id = ?").get(event.room_id) let channelID = row.channel_id let threadID = undefined @@ -21,16 +21,16 @@ async function sendEvent(event) { channelID = row.thread_parent // it's the thread's parent... get with the times... } - // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it + // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it const messages = eventToMessage.eventToMessage(event) - assert(Array.isArray(messages)) // sanity + assert(Array.isArray(messages)) // sanity - /** @type {DiscordTypes.APIMessage[]} */ + /** @type {DiscordTypes.APIMessage[]} */ const messageResponses = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { - const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) + const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? diff --git a/matrix/api.js b/matrix/api.js index 81d8a16..b382631 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -19,15 +19,15 @@ const makeTxnId = sync.require("./txnid") * @returns {string} the new endpoint */ function path(p, mxid, otherParams = {}) { - if (!mxid) return p - const u = new URL(p, "http://localhost") - u.searchParams.set("user_id", mxid) - for (const entry of Object.entries(otherParams)) { - if (entry[1] != undefined) { - u.searchParams.set(entry[0], entry[1]) - } - } - return u.pathname + "?" + u.searchParams.toString() + if (!mxid) return p + const u = new URL(p, "http://localhost") + u.searchParams.set("user_id", mxid) + for (const entry of Object.entries(otherParams)) { + if (entry[1] != undefined) { + u.searchParams.set(entry[0], entry[1]) + } + } + return u.pathname + "?" + u.searchParams.toString() } /** @@ -35,40 +35,40 @@ function path(p, mxid, otherParams = {}) { * @returns {Promise} */ function register(username) { - console.log(`[api] register: ${username}`) - return mreq.mreq("POST", "/client/v3/register", { - type: "m.login.application_service", - username - }) + console.log(`[api] register: ${username}`) + return mreq.mreq("POST", "/client/v3/register", { + type: "m.login.application_service", + username + }) } /** * @returns {Promise} room ID */ async function createRoom(content) { - console.log(`[api] create room:`, content) - /** @type {Ty.R.RoomCreated} */ - const root = await mreq.mreq("POST", "/client/v3/createRoom", content) - return root.room_id + console.log(`[api] create room:`, content) + /** @type {Ty.R.RoomCreated} */ + const root = await mreq.mreq("POST", "/client/v3/createRoom", content) + return root.room_id } /** * @returns {Promise} room ID */ async function joinRoom(roomIDOrAlias, mxid) { - /** @type {Ty.R.RoomJoined} */ - const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) - return root.room_id + /** @type {Ty.R.RoomJoined} */ + const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) + return root.room_id } async function inviteToRoom(roomID, mxidToInvite, mxid) { - await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/invite`, mxid), { - user_id: mxidToInvite - }) + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/invite`, mxid), { + user_id: mxidToInvite + }) } async function leaveRoom(roomID, mxid) { - await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) } /** @@ -77,9 +77,9 @@ async function leaveRoom(roomID, mxid) { * @template T */ async function getEvent(roomID, eventID) { - /** @type {Ty.Event.Outer} */ - const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/event/${eventID}`) - return root + /** @type {Ty.Event.Outer} */ + const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/event/${eventID}`) + return root } /** @@ -87,7 +87,17 @@ async function getEvent(roomID, eventID) { * @returns {Promise} */ function getAllState(roomID) { - return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) +} + +/** + * @param {string} roomID + * @param {string} type + * @param {string} key + * @returns the *content* of the state event + */ +function getStateEvent(roomID, type, key) { + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state/${type}/${key}`) } /** @@ -96,7 +106,7 @@ function getAllState(roomID) { * @returns {Promise<{joined: {[mxid: string]: Ty.R.RoomMember}}>} */ function getJoinedMembers(roomID) { - return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`) + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`) } /** @@ -107,12 +117,12 @@ function getJoinedMembers(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(typeof stateKey === "string") - /** @type {Ty.R.EventSent} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) - return root.event_id + console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) + assert.ok(type) + assert.ok(typeof stateKey === "string") + /** @type {Ty.R.EventSent} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) + return root.event_id } /** @@ -123,31 +133,51 @@ async function sendState(roomID, type, stateKey, content, mxid) { * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds */ async function sendEvent(roomID, type, content, mxid, timestamp) { - console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) - /** @type {Ty.R.EventSent} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content) - return root.event_id + console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) + /** @type {Ty.R.EventSent} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content) + return root.event_id } /** * @returns {Promise} room ID */ async function redactEvent(roomID, eventID, mxid) { - /** @type {Ty.R.EventRedacted} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid), {}) - return root.event_id + /** @type {Ty.R.EventRedacted} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid), {}) + return root.event_id } async function profileSetDisplayname(mxid, displayname) { - await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), { - displayname - }) + await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), { + displayname + }) } async function profileSetAvatarUrl(mxid, avatar_url) { - await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid), { - avatar_url - }) + await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid), { + avatar_url + }) +} + +/** + * Set a user's power level within a room. + * @param {string} roomID + * @param {string} mxid + * @param {number} power + */ +async function setUserPower(roomID, mxid, power) { + // Yes it's this hard https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 + const powerLevels = await getStateEvent(roomID, "m.room.power_levels", "") + const users = powerLevels.users || {} + if (power != null) { + users[mxid] = power + } else { + delete users[mxid] + } + powerLevels.users = users + await sendState(roomID, "m.room.power_levels", "", powerLevels) + return powerLevels } module.exports.path = path @@ -164,3 +194,4 @@ module.exports.sendEvent = sendEvent module.exports.redactEvent = redactEvent module.exports.profileSetDisplayname = profileSetDisplayname module.exports.profileSetAvatarUrl = profileSetAvatarUrl +module.exports.setUserPower = setUserPower diff --git a/matrix/kstate.test.js b/matrix/kstate.test.js index ed59e9d..1541898 100644 --- a/matrix/kstate.test.js +++ b/matrix/kstate.test.js @@ -2,22 +2,22 @@ const {kstateToState, stateToKState, diffKState, kstateStripConditionals} = requ const {test} = require("supertape") test("kstate strip: strips false conditions", t => { - t.deepEqual(kstateStripConditionals({ - a: {$if: false, value: 2}, - b: {value: 4} - }), { - b: {value: 4} - }) + t.deepEqual(kstateStripConditionals({ + a: {$if: false, value: 2}, + b: {value: 4} + }), { + b: {value: 4} + }) }) test("kstate strip: keeps true conditions while removing $if", t => { - t.deepEqual(kstateStripConditionals({ - a: {$if: true, value: 2}, - b: {value: 4} - }), { - a: {value: 2}, - b: {value: 4} - }) + t.deepEqual(kstateStripConditionals({ + a: {$if: true, value: 2}, + b: {value: 4} + }), { + a: {value: 2}, + b: {value: 4} + }) }) test("kstate2state: general", t => { diff --git a/matrix/read-registration.test.js b/matrix/read-registration.test.js index d402cfb..e5123b9 100644 --- a/matrix/read-registration.test.js +++ b/matrix/read-registration.test.js @@ -2,9 +2,9 @@ const {test} = require("supertape") const reg = require("./read-registration") test("reg: has necessary parameters", t => { - const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] - t.deepEqual( - propertiesToCheck.filter(p => p in reg), - propertiesToCheck - ) + const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] + t.deepEqual( + propertiesToCheck.filter(p => p in reg), + propertiesToCheck + ) }) diff --git a/scripts/save-channel-names-to-db.js b/scripts/save-channel-names-to-db.js index a70b1bb..6f5867a 100644 --- a/scripts/save-channel-names-to-db.js +++ b/scripts/save-channel-names-to-db.js @@ -18,10 +18,10 @@ passthrough.discord = discord ;(async () => { await discord.cloud.connect() - console.log("Discord gateway started") + console.log("Discord gateway started") - const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) - discord.cloud.on("event", f) + const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) + discord.cloud.on("event", f) })() const expectedGuilds = new Set() @@ -30,29 +30,29 @@ const prepared = db.prepare("UPDATE channel_room SET name = ? WHERE channel_id = /** @param {DiscordClient} discord */ function onPacket(discord, event, unsubscribe) { - if (event.t === "READY") { - for (const obj of event.d.guilds) { - expectedGuilds.add(obj.id) - } + if (event.t === "READY") { + for (const obj of event.d.guilds) { + expectedGuilds.add(obj.id) + } - } else if (event.t === "GUILD_CREATE") { - expectedGuilds.delete(event.d.id) + } else if (event.t === "GUILD_CREATE") { + expectedGuilds.delete(event.d.id) - // Store the channel. - for (const channel of event.d.channels || []) { - prepared.run(channel.name, channel.id) - } + // Store the channel. + for (const channel of event.d.channels || []) { + prepared.run(channel.name, channel.id) + } - // Checked them all? - if (expectedGuilds.size === 0) { - discord.cloud.disconnect() - unsubscribe() + // Checked them all? + if (expectedGuilds.size === 0) { + discord.cloud.disconnect() + unsubscribe() - // I don't know why node keeps running. - setTimeout(() => { - console.log("Stopping now.") - process.exit() - }, 1500).unref() - } - } + // I don't know why node keeps running. + setTimeout(() => { + console.log("Stopping now.") + process.exit() + }, 1500).unref() + } + } } diff --git a/scripts/save-event-types-to-db.js b/scripts/save-event-types-to-db.js index 83f5d2b..547e85c 100644 --- a/scripts/save-event-types-to-db.js +++ b/scripts/save-event-types-to-db.js @@ -18,13 +18,13 @@ const rows = db.prepare("SELECT event_id, room_id, event_type FROM event_message const preparedUpdate = db.prepare("UPDATE event_message SET event_type = ?, event_subtype = ? WHERE event_id = ?") ;(async () => { - for (const row of rows) { - if (row.event_type == null) { - const event = await api.getEvent(row.room_id, row.event_id) - const type = event.type - const subtype = event.content.msgtype || null - preparedUpdate.run(type, subtype, row.event_id) - console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`) - } - } + for (const row of rows) { + if (row.event_type == null) { + const event = await api.getEvent(row.room_id, row.event_id) + const type = event.type + const subtype = event.content.msgtype || null + preparedUpdate.run(type, subtype, row.event_id) + console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`) + } + } })() From c8021cadecb5c84b69a206413b4eb100ad9f76f2 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 21:04:41 +1200 Subject: [PATCH 134/200] changing spaces to tabs --- .vscode/settings.json | 3 + d2m/actions/add-reaction.js | 22 ++--- m2d/actions/channel-webhook.js | 8 +- m2d/actions/send-event.js | 10 +-- matrix/api.js | 129 +++++++++++++++++----------- matrix/kstate.test.js | 26 +++--- matrix/read-registration.test.js | 10 +-- scripts/save-channel-names-to-db.js | 48 +++++------ scripts/save-event-types-to-db.js | 18 ++-- 9 files changed, 154 insertions(+), 120 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c63c08..9f1e183 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,5 @@ { + "editor.insertSpaces": false, + "editor.detectIndentation": false, + "editor.tabSize": 3 } diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index b46af59..2eab8f0 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -15,20 +15,20 @@ 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) - 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 // Nothing can be done if the parent message was never bridged. - assert.equal(typeof parentID, "string") + const user = data.member?.user + assert.ok(user && user.username) + 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 // Nothing can be done if the parent message was never bridged. + assert.equal(typeof parentID, "string") const roomID = await createRoom.ensureRoom(data.channel_id) const senderMxid = await registerUser.ensureSimJoined(user, roomID) const eventID = await api.sendEvent(roomID, "m.reaction", { - "m.relates_to": { - rel_type: "m.annotation", - event_id: parentID, - key: data.emoji.name - } - }, senderMxid) + "m.relates_to": { + rel_type: "m.annotation", + event_id: parentID, + key: data.emoji.name + } + }, senderMxid) return eventID } diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index 6d39da7..b0bc072 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -52,10 +52,10 @@ async function withWebhook(channelID, callback) { * @param {string} [threadID] */ async function sendMessageWithWebhook(channelID, data, threadID) { - const result = await withWebhook(channelID, async webhook => { - return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, thread_id: threadID, disableEveryone: true}) - }) - return result + const result = await withWebhook(channelID, async webhook => { + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, thread_id: threadID, disableEveryone: true}) + }) + return result } module.exports.ensureWebhook = ensureWebhook diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 88ba0fd..39eed22 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -12,7 +12,7 @@ const eventToMessage = sync.require("../converters/event-to-message") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { - // TODO: we just assume the bridge has already been created + // TODO: we just assume the bridge has already been created const row = db.prepare("SELECT channel_id, thread_parent FROM channel_room WHERE room_id = ?").get(event.room_id) let channelID = row.channel_id let threadID = undefined @@ -21,16 +21,16 @@ async function sendEvent(event) { channelID = row.thread_parent // it's the thread's parent... get with the times... } - // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it + // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it const messages = eventToMessage.eventToMessage(event) - assert(Array.isArray(messages)) // sanity + assert(Array.isArray(messages)) // sanity - /** @type {DiscordTypes.APIMessage[]} */ + /** @type {DiscordTypes.APIMessage[]} */ const messageResponses = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { - const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) + const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? diff --git a/matrix/api.js b/matrix/api.js index 81d8a16..b382631 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -19,15 +19,15 @@ const makeTxnId = sync.require("./txnid") * @returns {string} the new endpoint */ function path(p, mxid, otherParams = {}) { - if (!mxid) return p - const u = new URL(p, "http://localhost") - u.searchParams.set("user_id", mxid) - for (const entry of Object.entries(otherParams)) { - if (entry[1] != undefined) { - u.searchParams.set(entry[0], entry[1]) - } - } - return u.pathname + "?" + u.searchParams.toString() + if (!mxid) return p + const u = new URL(p, "http://localhost") + u.searchParams.set("user_id", mxid) + for (const entry of Object.entries(otherParams)) { + if (entry[1] != undefined) { + u.searchParams.set(entry[0], entry[1]) + } + } + return u.pathname + "?" + u.searchParams.toString() } /** @@ -35,40 +35,40 @@ function path(p, mxid, otherParams = {}) { * @returns {Promise} */ function register(username) { - console.log(`[api] register: ${username}`) - return mreq.mreq("POST", "/client/v3/register", { - type: "m.login.application_service", - username - }) + console.log(`[api] register: ${username}`) + return mreq.mreq("POST", "/client/v3/register", { + type: "m.login.application_service", + username + }) } /** * @returns {Promise} room ID */ async function createRoom(content) { - console.log(`[api] create room:`, content) - /** @type {Ty.R.RoomCreated} */ - const root = await mreq.mreq("POST", "/client/v3/createRoom", content) - return root.room_id + console.log(`[api] create room:`, content) + /** @type {Ty.R.RoomCreated} */ + const root = await mreq.mreq("POST", "/client/v3/createRoom", content) + return root.room_id } /** * @returns {Promise} room ID */ async function joinRoom(roomIDOrAlias, mxid) { - /** @type {Ty.R.RoomJoined} */ - const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) - return root.room_id + /** @type {Ty.R.RoomJoined} */ + const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) + return root.room_id } async function inviteToRoom(roomID, mxidToInvite, mxid) { - await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/invite`, mxid), { - user_id: mxidToInvite - }) + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/invite`, mxid), { + user_id: mxidToInvite + }) } async function leaveRoom(roomID, mxid) { - await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) + await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) } /** @@ -77,9 +77,9 @@ async function leaveRoom(roomID, mxid) { * @template T */ async function getEvent(roomID, eventID) { - /** @type {Ty.Event.Outer} */ - const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/event/${eventID}`) - return root + /** @type {Ty.Event.Outer} */ + const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/event/${eventID}`) + return root } /** @@ -87,7 +87,17 @@ async function getEvent(roomID, eventID) { * @returns {Promise} */ function getAllState(roomID) { - return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) +} + +/** + * @param {string} roomID + * @param {string} type + * @param {string} key + * @returns the *content* of the state event + */ +function getStateEvent(roomID, type, key) { + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state/${type}/${key}`) } /** @@ -96,7 +106,7 @@ function getAllState(roomID) { * @returns {Promise<{joined: {[mxid: string]: Ty.R.RoomMember}}>} */ function getJoinedMembers(roomID) { - return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`) + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`) } /** @@ -107,12 +117,12 @@ function getJoinedMembers(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(typeof stateKey === "string") - /** @type {Ty.R.EventSent} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) - return root.event_id + console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) + assert.ok(type) + assert.ok(typeof stateKey === "string") + /** @type {Ty.R.EventSent} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) + return root.event_id } /** @@ -123,31 +133,51 @@ async function sendState(roomID, type, stateKey, content, mxid) { * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds */ async function sendEvent(roomID, type, content, mxid, timestamp) { - console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) - /** @type {Ty.R.EventSent} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content) - return root.event_id + console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) + /** @type {Ty.R.EventSent} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content) + return root.event_id } /** * @returns {Promise} room ID */ async function redactEvent(roomID, eventID, mxid) { - /** @type {Ty.R.EventRedacted} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid), {}) - return root.event_id + /** @type {Ty.R.EventRedacted} */ + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid), {}) + return root.event_id } async function profileSetDisplayname(mxid, displayname) { - await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), { - displayname - }) + await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), { + displayname + }) } async function profileSetAvatarUrl(mxid, avatar_url) { - await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid), { - avatar_url - }) + await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid), { + avatar_url + }) +} + +/** + * Set a user's power level within a room. + * @param {string} roomID + * @param {string} mxid + * @param {number} power + */ +async function setUserPower(roomID, mxid, power) { + // Yes it's this hard https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 + const powerLevels = await getStateEvent(roomID, "m.room.power_levels", "") + const users = powerLevels.users || {} + if (power != null) { + users[mxid] = power + } else { + delete users[mxid] + } + powerLevels.users = users + await sendState(roomID, "m.room.power_levels", "", powerLevels) + return powerLevels } module.exports.path = path @@ -164,3 +194,4 @@ module.exports.sendEvent = sendEvent module.exports.redactEvent = redactEvent module.exports.profileSetDisplayname = profileSetDisplayname module.exports.profileSetAvatarUrl = profileSetAvatarUrl +module.exports.setUserPower = setUserPower diff --git a/matrix/kstate.test.js b/matrix/kstate.test.js index ed59e9d..1541898 100644 --- a/matrix/kstate.test.js +++ b/matrix/kstate.test.js @@ -2,22 +2,22 @@ const {kstateToState, stateToKState, diffKState, kstateStripConditionals} = requ const {test} = require("supertape") test("kstate strip: strips false conditions", t => { - t.deepEqual(kstateStripConditionals({ - a: {$if: false, value: 2}, - b: {value: 4} - }), { - b: {value: 4} - }) + t.deepEqual(kstateStripConditionals({ + a: {$if: false, value: 2}, + b: {value: 4} + }), { + b: {value: 4} + }) }) test("kstate strip: keeps true conditions while removing $if", t => { - t.deepEqual(kstateStripConditionals({ - a: {$if: true, value: 2}, - b: {value: 4} - }), { - a: {value: 2}, - b: {value: 4} - }) + t.deepEqual(kstateStripConditionals({ + a: {$if: true, value: 2}, + b: {value: 4} + }), { + a: {value: 2}, + b: {value: 4} + }) }) test("kstate2state: general", t => { diff --git a/matrix/read-registration.test.js b/matrix/read-registration.test.js index d402cfb..e5123b9 100644 --- a/matrix/read-registration.test.js +++ b/matrix/read-registration.test.js @@ -2,9 +2,9 @@ const {test} = require("supertape") const reg = require("./read-registration") test("reg: has necessary parameters", t => { - const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] - t.deepEqual( - propertiesToCheck.filter(p => p in reg), - propertiesToCheck - ) + const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] + t.deepEqual( + propertiesToCheck.filter(p => p in reg), + propertiesToCheck + ) }) diff --git a/scripts/save-channel-names-to-db.js b/scripts/save-channel-names-to-db.js index a70b1bb..6f5867a 100644 --- a/scripts/save-channel-names-to-db.js +++ b/scripts/save-channel-names-to-db.js @@ -18,10 +18,10 @@ passthrough.discord = discord ;(async () => { await discord.cloud.connect() - console.log("Discord gateway started") + console.log("Discord gateway started") - const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) - discord.cloud.on("event", f) + const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) + discord.cloud.on("event", f) })() const expectedGuilds = new Set() @@ -30,29 +30,29 @@ const prepared = db.prepare("UPDATE channel_room SET name = ? WHERE channel_id = /** @param {DiscordClient} discord */ function onPacket(discord, event, unsubscribe) { - if (event.t === "READY") { - for (const obj of event.d.guilds) { - expectedGuilds.add(obj.id) - } + if (event.t === "READY") { + for (const obj of event.d.guilds) { + expectedGuilds.add(obj.id) + } - } else if (event.t === "GUILD_CREATE") { - expectedGuilds.delete(event.d.id) + } else if (event.t === "GUILD_CREATE") { + expectedGuilds.delete(event.d.id) - // Store the channel. - for (const channel of event.d.channels || []) { - prepared.run(channel.name, channel.id) - } + // Store the channel. + for (const channel of event.d.channels || []) { + prepared.run(channel.name, channel.id) + } - // Checked them all? - if (expectedGuilds.size === 0) { - discord.cloud.disconnect() - unsubscribe() + // Checked them all? + if (expectedGuilds.size === 0) { + discord.cloud.disconnect() + unsubscribe() - // I don't know why node keeps running. - setTimeout(() => { - console.log("Stopping now.") - process.exit() - }, 1500).unref() - } - } + // I don't know why node keeps running. + setTimeout(() => { + console.log("Stopping now.") + process.exit() + }, 1500).unref() + } + } } diff --git a/scripts/save-event-types-to-db.js b/scripts/save-event-types-to-db.js index 83f5d2b..547e85c 100644 --- a/scripts/save-event-types-to-db.js +++ b/scripts/save-event-types-to-db.js @@ -18,13 +18,13 @@ const rows = db.prepare("SELECT event_id, room_id, event_type FROM event_message const preparedUpdate = db.prepare("UPDATE event_message SET event_type = ?, event_subtype = ? WHERE event_id = ?") ;(async () => { - for (const row of rows) { - if (row.event_type == null) { - const event = await api.getEvent(row.room_id, row.event_id) - const type = event.type - const subtype = event.content.msgtype || null - preparedUpdate.run(type, subtype, row.event_id) - console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`) - } - } + for (const row of rows) { + if (row.event_type == null) { + const event = await api.getEvent(row.room_id, row.event_id) + const type = event.type + const subtype = event.content.msgtype || null + preparedUpdate.run(type, subtype, row.event_id) + console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`) + } + } })() From b8e0ddc79af10aa460c79c3de2c795742ca292a7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 21:16:10 +1200 Subject: [PATCH 135/200] I WAS USING THE WRONG VARIABLE :GRIEF: --- d2m/actions/send-message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index a5c8dac..2dcc324 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -38,7 +38,7 @@ async function sendMessage(message, guild) { const eventWithoutType = {...event} delete eventWithoutType.$type - const eventID = await api.sendEvent(roomID, eventType, event, senderMxid, new Date(message.timestamp).getTime()) + const eventID = await api.sendEvent(roomID, eventType, eventWithoutType, senderMxid, new Date(message.timestamp).getTime()) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting From 180708b60e78a07ff366aa25d69ba5345ed33401 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 21:16:10 +1200 Subject: [PATCH 136/200] I WAS USING THE WRONG VARIABLE :GRIEF: --- d2m/actions/send-message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index a5c8dac..2dcc324 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -38,7 +38,7 @@ async function sendMessage(message, guild) { const eventWithoutType = {...event} delete eventWithoutType.$type - const eventID = await api.sendEvent(roomID, eventType, event, senderMxid, new Date(message.timestamp).getTime()) + const eventID = await api.sendEvent(roomID, eventType, eventWithoutType, senderMxid, new Date(message.timestamp).getTime()) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting From f7be5f158286cb414211450996f2b8cf9bfaa499 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 23:31:40 +1200 Subject: [PATCH 137/200] finally got the thread's early messages working --- d2m/actions/announce-thread.js | 26 +-- d2m/actions/create-room.js | 11 +- d2m/converters/message-to-event.js | 23 +++ d2m/converters/message-to-event.test.js | 22 +++ d2m/converters/thread-to-announcement.js | 46 ++++++ d2m/converters/thread-to-announcement.test.js | 150 ++++++++++++++++++ d2m/discord-packets.js | 6 +- d2m/event-dispatcher.js | 5 +- db/data-for-test.sql | 6 +- test/data.js | 57 +++++++ test/test.js | 1 + 11 files changed, 321 insertions(+), 32 deletions(-) create mode 100644 d2m/converters/thread-to-announcement.js create mode 100644 d2m/converters/thread-to-announcement.test.js diff --git a/d2m/actions/announce-thread.js b/d2m/actions/announce-thread.js index 546d307..aa6def2 100644 --- a/d2m/actions/announce-thread.js +++ b/d2m/actions/announce-thread.js @@ -4,14 +4,10 @@ const assert = require("assert") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough -/** @type {import("../converters/message-to-event")} */ -const messageToEvent = sync.require("../converters/message-to-event") +/** @type {import("../converters/thread-to-announcement")} */ +const threadToAnnouncement = sync.require("../converters/thread-to-announcement") /** @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 {string} parentRoomID @@ -21,24 +17,10 @@ const createRoom = sync.require("../actions/create-room") async function announceThread(parentRoomID, threadRoomID, thread) { /** @type {string?} */ const creatorMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(thread.owner_id) - /** @type {string?} */ - const branchedFromEventID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").get(thread.id) - const msgtype = creatorMxid ? "m.emote" : "m.text" - const template = creatorMxid ? "started a thread:" : "Thread started:" - let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}` - let html = `${template} ${thread.name}` + const content = await threadToAnnouncement.threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thread, {api}) - const mentions = {} - - await api.sendEvent(parentRoomID, "m.room.message", { - msgtype, - body: `${template} , - format: "org.matrix.custom.html", - formatted_body: "", - "m.mentions": mentions - - }, creatorMxid) + await api.sendEvent(parentRoomID, "m.room.message", content, creatorMxid) } module.exports.announceThread = announceThread diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 2ee0913..4576320 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -70,12 +70,15 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } + let history_visibility = "invited" + if (channel["thread_metadata"]) history_visibility = "world_readable" + const channelKState = { "m.room.name/": {name: convertedName}, "m.room.topic/": {topic: convertedTopic}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, - "m.room.history_visibility/": {history_visibility: "invited"}, + "m.room.history_visibility/": {history_visibility}, [`m.space.parent/${spaceID}`]: { via: ["cadence.moe"], // TODO: put the proper server here canonical: true @@ -234,19 +237,23 @@ async function _unbridgeRoom(channelID) { * @returns {Promise} */ async function _syncSpaceMember(channel, spaceID, roomID) { + console.error(channel) + console.error("syncing space for", roomID) const spaceKState = await roomToKState(spaceID) let spaceEventContent = {} if ( channel.type !== DiscordTypes.ChannelType.PrivateThread // private threads do not belong in the space (don't offer people something they can't join) - || channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) + && !channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) ) { spaceEventContent = { via: ["cadence.moe"] // TODO: use the proper server } } + console.error(spaceEventContent) const spaceDiff = ks.diffKState(spaceKState, { [`m.space.child/${roomID}`]: spaceEventContent }) + console.error(spaceDiff) return applyKStateDiffToRoom(spaceID, spaceDiff) } diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index c34b389..c5da78d 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -65,6 +65,29 @@ function getDiscordParseCallbacks(message, useHTML) { async function messageToEvent(message, guild, options = {}, di) { const events = [] + if (message.type === DiscordTypes.MessageType.ThreadCreated) { + // This is the kind of message that appears when somebody makes a thread which isn't close enough to the message it's based off. + // It lacks the lines and the pill, so it looks kind of like a member join message, and it says: + // [#] NICKNAME started a thread: __THREAD NAME__. __See all threads__ + // We're already bridging the THREAD_CREATED gateway event to make a comparable message, so drop this one. + return [] + } + + if (message.type === DiscordTypes.MessageType.ThreadStarterMessage) { + // This is the message that appears at the top of a thread when the thread was based off an existing message. + // It's just a message reference, no content. + const ref = message.message_reference + assert(ref) + assert(ref.message_id) + const row = db.prepare("SELECT room_id, event_id FROM event_message INNER JOIN channel_room USING (channel_id) WHERE channel_id = ? AND message_id = ?").get(ref.channel_id, ref.message_id) + if (!row) return [] + const event = await di.api.getEvent(row.room_id, row.event_id) + return [{ + ...event.content, + $type: event.type + }] + } + /** @type {{room?: boolean, user_ids?: string[]}} We should consider the following scenarios for mentions: diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 260ecda..062ee0b 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -341,3 +341,25 @@ test("message2event: type 4 channel name change", async t => { formatted_body: "changed the channel name to worming" }]) }) + +test("message2event: thread start message reference", async t => { + const events = await messageToEvent(data.special_message.thread_start_context, data.guild.general, {}, { + api: { + getEvent: mockGetEvent(t, "!PnyBKvUBOhjuCucEfk:cadence.moe", "$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo", { + "type": "m.room.message", + "sender": "@_ooye_cadence:cadence.moe", + "content": { + "m.mentions": {}, + "msgtype": "m.text", + "body": "layer 4" + } + }) + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "layer 4", + "m.mentions": {} + }]) +}) diff --git a/d2m/converters/thread-to-announcement.js b/d2m/converters/thread-to-announcement.js new file mode 100644 index 0000000..405f7e9 --- /dev/null +++ b/d2m/converters/thread-to-announcement.js @@ -0,0 +1,46 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/read-registration")} */ +const reg = sync.require("../../matrix/read-registration.js") + +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) + +/** + * @param {string} parentRoomID + * @param {string} threadRoomID + * @param {string?} creatorMxid + * @param {import("discord-api-types/v10").APIThreadChannel} thread + * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API + */ +async function threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thread, di) { + /** @type {string?} */ + const branchedFromEventID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").pluck().get(thread.id) + /** @type {{"m.mentions"?: any, "m.in_reply_to"?: any}} */ + const context = {} + if (branchedFromEventID) { + // Need to figure out who sent that event... + const event = await di.api.getEvent(parentRoomID, branchedFromEventID) + context["m.relates_to"] = {"m.in_reply_to": {event_id: event.event_id}} + if (event.sender && !userRegex.some(rx => event.sender.match(rx))) context["m.mentions"] = {user_ids: [event.sender]} + } + + const msgtype = creatorMxid ? "m.emote" : "m.text" + const template = creatorMxid ? "started a thread:" : "Thread started:" + let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}` + let html = `${template} ${thread.name}` + + return { + msgtype, + body, + format: "org.matrix.custom.html", + formatted_body: html, + "m.mentions": {}, + ...context + } +} + +module.exports.threadToAnnouncement = threadToAnnouncement diff --git a/d2m/converters/thread-to-announcement.test.js b/d2m/converters/thread-to-announcement.test.js new file mode 100644 index 0000000..06d937f --- /dev/null +++ b/d2m/converters/thread-to-announcement.test.js @@ -0,0 +1,150 @@ +const {test} = require("supertape") +const {threadToAnnouncement} = require("./thread-to-announcement") +const data = require("../../test/data") +const Ty = require("../../types") + +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} + +test("thread2announcement: no known creator, no branched from event", async t => { + const content = await threadToAnnouncement("!parent", "!thread", null, { + name: "test thread", + id: "-1" + }) + t.deepEqual(content, { + msgtype: "m.text", + body: "Thread started: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `Thread started: test thread`, + "m.mentions": {} + }) +}) + +test("thread2announcement: known creator, no branched from event", async t => { + const content = await threadToAnnouncement("!parent", "!thread", "@_ooye_crunch_god:cadence.moe", { + name: "test thread", + id: "-1" + }) + t.deepEqual(content, { + msgtype: "m.emote", + body: "started a thread: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `started a thread: test thread`, + "m.mentions": {} + }) +}) + +test("thread2announcement: no known creator, branched from discord event", async t => { + const content = await threadToAnnouncement("!kLRqKKUQXcibIMtOpl:cadence.moe", "!thread", null, { + name: "test thread", + id: "1126786462646550579" + }, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", { + type: 'm.room.message', + sender: '@_ooye_bot:cadence.moe', + content: { + msgtype: 'm.text', + body: 'testing testing testing' + } + }) + } + }) + t.deepEqual(content, { + msgtype: "m.text", + body: "Thread started: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `Thread started: test thread`, + "m.mentions": {}, + "m.relates_to": { + "m.in_reply_to": { + event_id: "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg" + } + } + }) +}) + +test("thread2announcement: known creator, branched from discord event", async t => { + const content = await threadToAnnouncement("!kLRqKKUQXcibIMtOpl:cadence.moe", "!thread", "@_ooye_crunch_god:cadence.moe", { + name: "test thread", + id: "1126786462646550579" + }, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", { + type: 'm.room.message', + sender: '@_ooye_bot:cadence.moe', + content: { + msgtype: 'm.text', + body: 'testing testing testing' + } + }) + } + }) + t.deepEqual(content, { + msgtype: "m.emote", + body: "started a thread: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `started a thread: test thread`, + "m.mentions": {}, + "m.relates_to": { + "m.in_reply_to": { + event_id: "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg" + } + } + }) +}) + +test("thread2announcement: no known creator, branched from matrix event", async t => { + const content = await threadToAnnouncement("!kLRqKKUQXcibIMtOpl:cadence.moe", "!thread", null, { + name: "test thread", + id: "1128118177155526666" + }, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } + }) + t.deepEqual(content, { + msgtype: "m.text", + body: "Thread started: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `Thread started: test thread`, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + }, + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + } + }) +}) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 776d4b1..a1a4505 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -86,18 +86,16 @@ const utils = { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) } else if (message.t === "THREAD_CREATE") { - console.log(message) - // await eventDispatcher.onThreadCreate(client, message.d) + // @ts-ignore + await eventDispatcher.onThreadCreate(client, message.d) } else if (message.t === "THREAD_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, true) } else if (message.t === "MESSAGE_CREATE") { - console.log(message) await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { - console.log(message) await eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 6e4ffc4..c871ff1 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -10,6 +10,8 @@ const editMessage = sync.require("./actions/edit-message") const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("./actions/announce-thread")}) */ +const announceThread = sync.require("./actions/announce-thread") /** @type {import("./actions/create-room")}) */ const createRoom = sync.require("./actions/create-room") /** @type {import("../matrix/api")}) */ @@ -108,8 +110,7 @@ module.exports = { * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async onThreadCreate(client, thread) { - console.log(thread) - const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(thread.parent_id) + const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(thread.parent_id) if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel const threadRoomID = await createRoom.syncRoom(thread.id) // Create room (will share the same inflight as the initial message to the thread) await announceThread.announceThread(parentRoomID, threadRoomID, thread) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index ee31fe3..4a406c9 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -58,7 +58,8 @@ INSERT INTO guild_space (guild_id, space_id) VALUES INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES ('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL), ('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL); +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL), +('1100319550446252084', '!PnyBKvUBOhjuCucEfk:cadence.moe', 'worm-farm', NULL, NULL); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), @@ -80,7 +81,8 @@ INSERT INTO event_message (event_id, event_type, event_subtype, message_id, chan ('$51f4yqHinwnSbPEQ9dCgoyy4qiIJSX0QYYVUnvwyTCJ', 'm.room.message', 'm.image', '1141501302736695317', '112760669178241024', 0, 1), ('$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M', 'm.room.message', 'm.text', '1128084851279536279', '112760669178241024', 0, 1), ('$YUJFa5j0ZJe7PUvD2DykRt9g51RoadUEYmuJLdSEbJ0', 'm.room.message', 'm.image', '1128084851279536279', '112760669178241024', 1, 1), -('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1); +('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1), +('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1); INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), diff --git a/test/data.js b/test/data.js index fc8cbbd..30d108a 100644 --- a/test/data.js +++ b/test/data.js @@ -1375,6 +1375,63 @@ module.exports = { flags: 0, components: [], position: 12 + }, + updated_to_start_thread_from_here: { + t: "MESSAGE_UPDATE", + s: 19, + op: 0, + d: { + id: "1143121514925928541", + flags: 32, + channel_id: "1100319550446252084", + guild_id: "1100319549670301727" + }, + shard_id: 0 + }, + thread_start_context: { + type: 21, + tts: false, + timestamp: "2023-08-21T09:57:12.558000+00:00", + position: 0, + pinned: false, + message_reference: { + message_id: "1143121514925928541", + guild_id: "1100319549670301727", + channel_id: "1100319550446252084" + }, + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [], + premium_since: null, + pending: false, + nick: "worm", + mute: false, + joined_at: "2023-04-25T07:17:03.696000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1143121620744032327", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "", + components: [], + channel_id: "1143121514925928541", + author: { + username: "cadence.worm", + public_flags: 0, + id: "772659086046658620", + global_name: "cadence", + discriminator: "0", + avatar_decoration_data: null, + avatar: "4b5c4b28051144e4c111f0113a0f1cf1" + }, + attachments: [], + guild_id: "1100319549670301727" } } } diff --git a/test/test.js b/test/test.js index 03394f0..606bd4b 100644 --- a/test/test.js +++ b/test/test.js @@ -23,6 +23,7 @@ require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") require("../d2m/converters/message-to-event.embeds.test") require("../d2m/converters/edit-to-changes.test") +require("../d2m/converters/thread-to-announcement.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") From 6d1635539b4edfd31bf2384c944b123d9e9f5031 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 23:31:40 +1200 Subject: [PATCH 138/200] finally got the thread's early messages working --- d2m/actions/announce-thread.js | 26 +-- d2m/actions/create-room.js | 11 +- d2m/converters/message-to-event.js | 23 +++ d2m/converters/message-to-event.test.js | 22 +++ d2m/converters/thread-to-announcement.js | 46 ++++++ d2m/converters/thread-to-announcement.test.js | 150 ++++++++++++++++++ d2m/discord-packets.js | 6 +- d2m/event-dispatcher.js | 5 +- db/data-for-test.sql | 6 +- test/data.js | 57 +++++++ test/test.js | 1 + 11 files changed, 321 insertions(+), 32 deletions(-) create mode 100644 d2m/converters/thread-to-announcement.js create mode 100644 d2m/converters/thread-to-announcement.test.js diff --git a/d2m/actions/announce-thread.js b/d2m/actions/announce-thread.js index 546d307..aa6def2 100644 --- a/d2m/actions/announce-thread.js +++ b/d2m/actions/announce-thread.js @@ -4,14 +4,10 @@ const assert = require("assert") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough -/** @type {import("../converters/message-to-event")} */ -const messageToEvent = sync.require("../converters/message-to-event") +/** @type {import("../converters/thread-to-announcement")} */ +const threadToAnnouncement = sync.require("../converters/thread-to-announcement") /** @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 {string} parentRoomID @@ -21,24 +17,10 @@ const createRoom = sync.require("../actions/create-room") async function announceThread(parentRoomID, threadRoomID, thread) { /** @type {string?} */ const creatorMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(thread.owner_id) - /** @type {string?} */ - const branchedFromEventID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").get(thread.id) - const msgtype = creatorMxid ? "m.emote" : "m.text" - const template = creatorMxid ? "started a thread:" : "Thread started:" - let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}` - let html = `${template} ${thread.name}` + const content = await threadToAnnouncement.threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thread, {api}) - const mentions = {} - - await api.sendEvent(parentRoomID, "m.room.message", { - msgtype, - body: `${template} , - format: "org.matrix.custom.html", - formatted_body: "", - "m.mentions": mentions - - }, creatorMxid) + await api.sendEvent(parentRoomID, "m.room.message", content, creatorMxid) } module.exports.announceThread = announceThread diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 2ee0913..4576320 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -70,12 +70,15 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } + let history_visibility = "invited" + if (channel["thread_metadata"]) history_visibility = "world_readable" + const channelKState = { "m.room.name/": {name: convertedName}, "m.room.topic/": {topic: convertedTopic}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, - "m.room.history_visibility/": {history_visibility: "invited"}, + "m.room.history_visibility/": {history_visibility}, [`m.space.parent/${spaceID}`]: { via: ["cadence.moe"], // TODO: put the proper server here canonical: true @@ -234,19 +237,23 @@ async function _unbridgeRoom(channelID) { * @returns {Promise} */ async function _syncSpaceMember(channel, spaceID, roomID) { + console.error(channel) + console.error("syncing space for", roomID) const spaceKState = await roomToKState(spaceID) let spaceEventContent = {} if ( channel.type !== DiscordTypes.ChannelType.PrivateThread // private threads do not belong in the space (don't offer people something they can't join) - || channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) + && !channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) ) { spaceEventContent = { via: ["cadence.moe"] // TODO: use the proper server } } + console.error(spaceEventContent) const spaceDiff = ks.diffKState(spaceKState, { [`m.space.child/${roomID}`]: spaceEventContent }) + console.error(spaceDiff) return applyKStateDiffToRoom(spaceID, spaceDiff) } diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index c34b389..c5da78d 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -65,6 +65,29 @@ function getDiscordParseCallbacks(message, useHTML) { async function messageToEvent(message, guild, options = {}, di) { const events = [] + if (message.type === DiscordTypes.MessageType.ThreadCreated) { + // This is the kind of message that appears when somebody makes a thread which isn't close enough to the message it's based off. + // It lacks the lines and the pill, so it looks kind of like a member join message, and it says: + // [#] NICKNAME started a thread: __THREAD NAME__. __See all threads__ + // We're already bridging the THREAD_CREATED gateway event to make a comparable message, so drop this one. + return [] + } + + if (message.type === DiscordTypes.MessageType.ThreadStarterMessage) { + // This is the message that appears at the top of a thread when the thread was based off an existing message. + // It's just a message reference, no content. + const ref = message.message_reference + assert(ref) + assert(ref.message_id) + const row = db.prepare("SELECT room_id, event_id FROM event_message INNER JOIN channel_room USING (channel_id) WHERE channel_id = ? AND message_id = ?").get(ref.channel_id, ref.message_id) + if (!row) return [] + const event = await di.api.getEvent(row.room_id, row.event_id) + return [{ + ...event.content, + $type: event.type + }] + } + /** @type {{room?: boolean, user_ids?: string[]}} We should consider the following scenarios for mentions: diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 260ecda..062ee0b 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -341,3 +341,25 @@ test("message2event: type 4 channel name change", async t => { formatted_body: "changed the channel name to worming" }]) }) + +test("message2event: thread start message reference", async t => { + const events = await messageToEvent(data.special_message.thread_start_context, data.guild.general, {}, { + api: { + getEvent: mockGetEvent(t, "!PnyBKvUBOhjuCucEfk:cadence.moe", "$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo", { + "type": "m.room.message", + "sender": "@_ooye_cadence:cadence.moe", + "content": { + "m.mentions": {}, + "msgtype": "m.text", + "body": "layer 4" + } + }) + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "layer 4", + "m.mentions": {} + }]) +}) diff --git a/d2m/converters/thread-to-announcement.js b/d2m/converters/thread-to-announcement.js new file mode 100644 index 0000000..405f7e9 --- /dev/null +++ b/d2m/converters/thread-to-announcement.js @@ -0,0 +1,46 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../../matrix/read-registration")} */ +const reg = sync.require("../../matrix/read-registration.js") + +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) + +/** + * @param {string} parentRoomID + * @param {string} threadRoomID + * @param {string?} creatorMxid + * @param {import("discord-api-types/v10").APIThreadChannel} thread + * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API + */ +async function threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thread, di) { + /** @type {string?} */ + const branchedFromEventID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").pluck().get(thread.id) + /** @type {{"m.mentions"?: any, "m.in_reply_to"?: any}} */ + const context = {} + if (branchedFromEventID) { + // Need to figure out who sent that event... + const event = await di.api.getEvent(parentRoomID, branchedFromEventID) + context["m.relates_to"] = {"m.in_reply_to": {event_id: event.event_id}} + if (event.sender && !userRegex.some(rx => event.sender.match(rx))) context["m.mentions"] = {user_ids: [event.sender]} + } + + const msgtype = creatorMxid ? "m.emote" : "m.text" + const template = creatorMxid ? "started a thread:" : "Thread started:" + let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}` + let html = `${template} ${thread.name}` + + return { + msgtype, + body, + format: "org.matrix.custom.html", + formatted_body: html, + "m.mentions": {}, + ...context + } +} + +module.exports.threadToAnnouncement = threadToAnnouncement diff --git a/d2m/converters/thread-to-announcement.test.js b/d2m/converters/thread-to-announcement.test.js new file mode 100644 index 0000000..06d937f --- /dev/null +++ b/d2m/converters/thread-to-announcement.test.js @@ -0,0 +1,150 @@ +const {test} = require("supertape") +const {threadToAnnouncement} = require("./thread-to-announcement") +const data = require("../../test/data") +const Ty = require("../../types") + +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} + +test("thread2announcement: no known creator, no branched from event", async t => { + const content = await threadToAnnouncement("!parent", "!thread", null, { + name: "test thread", + id: "-1" + }) + t.deepEqual(content, { + msgtype: "m.text", + body: "Thread started: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `Thread started: test thread`, + "m.mentions": {} + }) +}) + +test("thread2announcement: known creator, no branched from event", async t => { + const content = await threadToAnnouncement("!parent", "!thread", "@_ooye_crunch_god:cadence.moe", { + name: "test thread", + id: "-1" + }) + t.deepEqual(content, { + msgtype: "m.emote", + body: "started a thread: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `started a thread: test thread`, + "m.mentions": {} + }) +}) + +test("thread2announcement: no known creator, branched from discord event", async t => { + const content = await threadToAnnouncement("!kLRqKKUQXcibIMtOpl:cadence.moe", "!thread", null, { + name: "test thread", + id: "1126786462646550579" + }, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", { + type: 'm.room.message', + sender: '@_ooye_bot:cadence.moe', + content: { + msgtype: 'm.text', + body: 'testing testing testing' + } + }) + } + }) + t.deepEqual(content, { + msgtype: "m.text", + body: "Thread started: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `Thread started: test thread`, + "m.mentions": {}, + "m.relates_to": { + "m.in_reply_to": { + event_id: "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg" + } + } + }) +}) + +test("thread2announcement: known creator, branched from discord event", async t => { + const content = await threadToAnnouncement("!kLRqKKUQXcibIMtOpl:cadence.moe", "!thread", "@_ooye_crunch_god:cadence.moe", { + name: "test thread", + id: "1126786462646550579" + }, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", { + type: 'm.room.message', + sender: '@_ooye_bot:cadence.moe', + content: { + msgtype: 'm.text', + body: 'testing testing testing' + } + }) + } + }) + t.deepEqual(content, { + msgtype: "m.emote", + body: "started a thread: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `started a thread: test thread`, + "m.mentions": {}, + "m.relates_to": { + "m.in_reply_to": { + event_id: "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg" + } + } + }) +}) + +test("thread2announcement: no known creator, branched from matrix event", async t => { + const content = await threadToAnnouncement("!kLRqKKUQXcibIMtOpl:cadence.moe", "!thread", null, { + name: "test thread", + id: "1128118177155526666" + }, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } + }) + t.deepEqual(content, { + msgtype: "m.text", + body: "Thread started: test thread https://matrix.to/#/!thread", + format: "org.matrix.custom.html", + formatted_body: `Thread started: test thread`, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + }, + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + } + }) +}) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 776d4b1..a1a4505 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -86,18 +86,16 @@ const utils = { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) } else if (message.t === "THREAD_CREATE") { - console.log(message) - // await eventDispatcher.onThreadCreate(client, message.d) + // @ts-ignore + await eventDispatcher.onThreadCreate(client, message.d) } else if (message.t === "THREAD_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, true) } else if (message.t === "MESSAGE_CREATE") { - console.log(message) await eventDispatcher.onMessageCreate(client, message.d) } else if (message.t === "MESSAGE_UPDATE") { - console.log(message) await eventDispatcher.onMessageUpdate(client, message.d) } else if (message.t === "MESSAGE_DELETE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 6e4ffc4..c871ff1 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -10,6 +10,8 @@ const editMessage = sync.require("./actions/edit-message") const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("./actions/announce-thread")}) */ +const announceThread = sync.require("./actions/announce-thread") /** @type {import("./actions/create-room")}) */ const createRoom = sync.require("./actions/create-room") /** @type {import("../matrix/api")}) */ @@ -108,8 +110,7 @@ module.exports = { * @param {import("discord-api-types/v10").APIThreadChannel} thread */ async onThreadCreate(client, thread) { - console.log(thread) - const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(thread.parent_id) + const parentRoomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(thread.parent_id) if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel const threadRoomID = await createRoom.syncRoom(thread.id) // Create room (will share the same inflight as the initial message to the thread) await announceThread.announceThread(parentRoomID, threadRoomID, thread) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index ee31fe3..4a406c9 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -58,7 +58,8 @@ INSERT INTO guild_space (guild_id, space_id) VALUES INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES ('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL), ('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL); +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL), +('1100319550446252084', '!PnyBKvUBOhjuCucEfk:cadence.moe', 'worm-farm', NULL, NULL); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), @@ -80,7 +81,8 @@ INSERT INTO event_message (event_id, event_type, event_subtype, message_id, chan ('$51f4yqHinwnSbPEQ9dCgoyy4qiIJSX0QYYVUnvwyTCJ', 'm.room.message', 'm.image', '1141501302736695317', '112760669178241024', 0, 1), ('$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M', 'm.room.message', 'm.text', '1128084851279536279', '112760669178241024', 0, 1), ('$YUJFa5j0ZJe7PUvD2DykRt9g51RoadUEYmuJLdSEbJ0', 'm.room.message', 'm.image', '1128084851279536279', '112760669178241024', 1, 1), -('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1); +('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1), +('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1); INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), diff --git a/test/data.js b/test/data.js index fc8cbbd..30d108a 100644 --- a/test/data.js +++ b/test/data.js @@ -1375,6 +1375,63 @@ module.exports = { flags: 0, components: [], position: 12 + }, + updated_to_start_thread_from_here: { + t: "MESSAGE_UPDATE", + s: 19, + op: 0, + d: { + id: "1143121514925928541", + flags: 32, + channel_id: "1100319550446252084", + guild_id: "1100319549670301727" + }, + shard_id: 0 + }, + thread_start_context: { + type: 21, + tts: false, + timestamp: "2023-08-21T09:57:12.558000+00:00", + position: 0, + pinned: false, + message_reference: { + message_id: "1143121514925928541", + guild_id: "1100319549670301727", + channel_id: "1100319550446252084" + }, + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [], + premium_since: null, + pending: false, + nick: "worm", + mute: false, + joined_at: "2023-04-25T07:17:03.696000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1143121620744032327", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "", + components: [], + channel_id: "1143121514925928541", + author: { + username: "cadence.worm", + public_flags: 0, + id: "772659086046658620", + global_name: "cadence", + discriminator: "0", + avatar_decoration_data: null, + avatar: "4b5c4b28051144e4c111f0113a0f1cf1" + }, + attachments: [], + guild_id: "1100319549670301727" } } } diff --git a/test/test.js b/test/test.js index 03394f0..606bd4b 100644 --- a/test/test.js +++ b/test/test.js @@ -23,6 +23,7 @@ require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") require("../d2m/converters/message-to-event.embeds.test") require("../d2m/converters/edit-to-changes.test") +require("../d2m/converters/thread-to-announcement.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") From 17d79df009f16f269963af8925e2949cf6fff40c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 23:34:49 +1200 Subject: [PATCH 139/200] remove console.log --- d2m/actions/create-room.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 4576320..3be2429 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -237,8 +237,6 @@ async function _unbridgeRoom(channelID) { * @returns {Promise} */ async function _syncSpaceMember(channel, spaceID, roomID) { - console.error(channel) - console.error("syncing space for", roomID) const spaceKState = await roomToKState(spaceID) let spaceEventContent = {} if ( @@ -249,11 +247,9 @@ async function _syncSpaceMember(channel, spaceID, roomID) { via: ["cadence.moe"] // TODO: use the proper server } } - console.error(spaceEventContent) const spaceDiff = ks.diffKState(spaceKState, { [`m.space.child/${roomID}`]: spaceEventContent }) - console.error(spaceDiff) return applyKStateDiffToRoom(spaceID, spaceDiff) } From e04617eb04cdfce76d1bd544c0414b29c271d7d1 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 21 Aug 2023 23:34:49 +1200 Subject: [PATCH 140/200] remove console.log --- d2m/actions/create-room.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 4576320..3be2429 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -237,8 +237,6 @@ async function _unbridgeRoom(channelID) { * @returns {Promise} */ async function _syncSpaceMember(channel, spaceID, roomID) { - console.error(channel) - console.error("syncing space for", roomID) const spaceKState = await roomToKState(spaceID) let spaceEventContent = {} if ( @@ -249,11 +247,9 @@ async function _syncSpaceMember(channel, spaceID, roomID) { via: ["cadence.moe"] // TODO: use the proper server } } - console.error(spaceEventContent) const spaceDiff = ks.diffKState(spaceKState, { [`m.space.child/${roomID}`]: spaceEventContent }) - console.error(spaceDiff) return applyKStateDiffToRoom(spaceID, spaceDiff) } From 5810fb3955d7be41addba54149b9e27108f6e973 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 22 Aug 2023 17:11:07 +1200 Subject: [PATCH 141/200] start using kstate for guild/spaces too --- d2m/actions/create-space.js | 76 +++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 02c2dcf..1f00312 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -1,18 +1,29 @@ // @ts-check const assert = require("assert") +const DiscordTypes = require("discord-api-types/v10") + const passthrough = require("../../passthrough") const { sync, db } = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") +/** @type {import("./create-room")} */ +const createRoom = sync.require("./create-room") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild + * @param {any} kstate */ -async function createSpace(guild) { - assert(guild.name) +async function createSpace(guild, kstate) { + const name = kstate["m.room.name/"].name + const topic = kstate["m.room.topic/"]?.topic || undefined + + assert(name) + const roomID = await api.createRoom({ - name: guild.name, + name, preset: "private_chat", // cannot join space unless invited visibility: "private", power_level_content_override: { @@ -20,28 +31,55 @@ async function createSpace(guild) { invite: 0 // any existing member can invite others }, invite: ["@cadence:cadence.moe"], // TODO - topic: guild.description || undefined, + topic, creation_content: { type: "m.space" }, - initial_state: [ - { - type: "m.room.guest_access", - state_key: "", - content: { - guest_access: "can_join" // guests can join space if other conditions are met - } - }, - { - type: "m.room.history_visibility", - content: { - history_visibility: "invited" // any events sent after user was invited are visible - } - } - ] + initial_state: ks.kstateToState(kstate) }) db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID) return roomID } +/** + * @param {DiscordTypes.APIGuild} guild] + */ +async function guildToKState(guild) { + const avatarEventContent = {} + if (guild.icon) { + avatarEventContent.discord_path = file.guildIcon(guild) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API + } + + let history_visibility = "invited" + if (guild["thread_metadata"]) history_visibility = "world_readable" + + const guildKState = { + "m.room.name/": {name: guild.name}, + "m.room.avatar/": avatarEventContent, + "m.room.guest_access/": {guest_access: "can_join"}, // guests can join space if other conditions are met + "m.room.history_visibility": {history_visibility: "invited"} // any events sent after user was invited are visible + } + + return guildKState +} + +async function syncSpace(guildID) { + /** @ts-ignore @type {DiscordTypes.APIGuild} */ + const guild = discord.guilds.get(guildID) + assert.ok(guild) + + /** @type {{room_id: string, thread_parent: string?}} */ + const existing = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").get(guildID) + + const guildKState = await guildToKState(guild) + + if (!existing) { + const spaceID = await createSpace(guild, guildKState) + return spaceID + } + + + + module.exports.createSpace = createSpace From 6cd509d27420f9188bf02c9609b5a4118298ba70 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 22 Aug 2023 17:11:07 +1200 Subject: [PATCH 142/200] start using kstate for guild/spaces too --- d2m/actions/create-space.js | 76 +++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 02c2dcf..1f00312 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -1,18 +1,29 @@ // @ts-check const assert = require("assert") +const DiscordTypes = require("discord-api-types/v10") + const passthrough = require("../../passthrough") const { sync, db } = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") +/** @type {import("./create-room")} */ +const createRoom = sync.require("./create-room") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild + * @param {any} kstate */ -async function createSpace(guild) { - assert(guild.name) +async function createSpace(guild, kstate) { + const name = kstate["m.room.name/"].name + const topic = kstate["m.room.topic/"]?.topic || undefined + + assert(name) + const roomID = await api.createRoom({ - name: guild.name, + name, preset: "private_chat", // cannot join space unless invited visibility: "private", power_level_content_override: { @@ -20,28 +31,55 @@ async function createSpace(guild) { invite: 0 // any existing member can invite others }, invite: ["@cadence:cadence.moe"], // TODO - topic: guild.description || undefined, + topic, creation_content: { type: "m.space" }, - initial_state: [ - { - type: "m.room.guest_access", - state_key: "", - content: { - guest_access: "can_join" // guests can join space if other conditions are met - } - }, - { - type: "m.room.history_visibility", - content: { - history_visibility: "invited" // any events sent after user was invited are visible - } - } - ] + initial_state: ks.kstateToState(kstate) }) db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID) return roomID } +/** + * @param {DiscordTypes.APIGuild} guild] + */ +async function guildToKState(guild) { + const avatarEventContent = {} + if (guild.icon) { + avatarEventContent.discord_path = file.guildIcon(guild) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API + } + + let history_visibility = "invited" + if (guild["thread_metadata"]) history_visibility = "world_readable" + + const guildKState = { + "m.room.name/": {name: guild.name}, + "m.room.avatar/": avatarEventContent, + "m.room.guest_access/": {guest_access: "can_join"}, // guests can join space if other conditions are met + "m.room.history_visibility": {history_visibility: "invited"} // any events sent after user was invited are visible + } + + return guildKState +} + +async function syncSpace(guildID) { + /** @ts-ignore @type {DiscordTypes.APIGuild} */ + const guild = discord.guilds.get(guildID) + assert.ok(guild) + + /** @type {{room_id: string, thread_parent: string?}} */ + const existing = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").get(guildID) + + const guildKState = await guildToKState(guild) + + if (!existing) { + const spaceID = await createSpace(guild, guildKState) + return spaceID + } + + + + module.exports.createSpace = createSpace From 25cd9c851bfd328d6533a43ecd1071e27265814f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:31:31 +1200 Subject: [PATCH 143/200] be able to sync space properties --- d2m/actions/create-space.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 1f00312..34aa25f 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -4,13 +4,15 @@ const assert = require("assert") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") -const { sync, db } = passthrough +const { discord, sync, db } = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") /** @type {import("./create-room")} */ const createRoom = sync.require("./create-room") +/** @type {import("../../matrix/kstate")} */ +const ks = sync.require("../../matrix/kstate") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild @@ -58,7 +60,7 @@ async function guildToKState(guild) { "m.room.name/": {name: guild.name}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, // guests can join space if other conditions are met - "m.room.history_visibility": {history_visibility: "invited"} // any events sent after user was invited are visible + "m.room.history_visibility/": {history_visibility: "invited"} // any events sent after user was invited are visible } return guildKState @@ -69,17 +71,26 @@ async function syncSpace(guildID) { const guild = discord.guilds.get(guildID) assert.ok(guild) - /** @type {{room_id: string, thread_parent: string?}} */ - const existing = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").get(guildID) + /** @type {string?} */ + const spaceID = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").pluck().get(guildID) const guildKState = await guildToKState(guild) - if (!existing) { + if (!spaceID) { const spaceID = await createSpace(guild, guildKState) - return spaceID + return spaceID // Naturally, the newly created space is already up to date, so we can always skip syncing here. } + console.log(`[space sync] to matrix: ${guild.name}`) + // sync channel state to room + const spaceKState = await createRoom.roomToKState(spaceID) + const spaceDiff = ks.diffKState(spaceKState, guildKState) + await createRoom.applyKStateDiffToRoom(spaceID, spaceDiff) + return spaceID +} module.exports.createSpace = createSpace +module.exports.syncSpace = syncSpace +module.exports.guildToKState = guildToKState From 892bf4496d93f85e26c08732479a8fdae99d08ce Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:31:31 +1200 Subject: [PATCH 144/200] be able to sync space properties --- d2m/actions/create-space.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 1f00312..34aa25f 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -4,13 +4,15 @@ const assert = require("assert") const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") -const { sync, db } = passthrough +const { discord, sync, db } = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") /** @type {import("./create-room")} */ const createRoom = sync.require("./create-room") +/** @type {import("../../matrix/kstate")} */ +const ks = sync.require("../../matrix/kstate") /** * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild @@ -58,7 +60,7 @@ async function guildToKState(guild) { "m.room.name/": {name: guild.name}, "m.room.avatar/": avatarEventContent, "m.room.guest_access/": {guest_access: "can_join"}, // guests can join space if other conditions are met - "m.room.history_visibility": {history_visibility: "invited"} // any events sent after user was invited are visible + "m.room.history_visibility/": {history_visibility: "invited"} // any events sent after user was invited are visible } return guildKState @@ -69,17 +71,26 @@ async function syncSpace(guildID) { const guild = discord.guilds.get(guildID) assert.ok(guild) - /** @type {{room_id: string, thread_parent: string?}} */ - const existing = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").get(guildID) + /** @type {string?} */ + const spaceID = db.prepare("SELECT space_id from guild_space WHERE guild_id = ?").pluck().get(guildID) const guildKState = await guildToKState(guild) - if (!existing) { + if (!spaceID) { const spaceID = await createSpace(guild, guildKState) - return spaceID + return spaceID // Naturally, the newly created space is already up to date, so we can always skip syncing here. } + console.log(`[space sync] to matrix: ${guild.name}`) + // sync channel state to room + const spaceKState = await createRoom.roomToKState(spaceID) + const spaceDiff = ks.diffKState(spaceKState, guildKState) + await createRoom.applyKStateDiffToRoom(spaceID, spaceDiff) + return spaceID +} module.exports.createSpace = createSpace +module.exports.syncSpace = syncSpace +module.exports.guildToKState = guildToKState From 20fd58418a58012515def6dbbc74605d241063c4 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:37:25 +1200 Subject: [PATCH 145/200] listen for guild updates and connect them --- d2m/discord-packets.js | 5 ++++- d2m/event-dispatcher.js | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index a1a4505..6ece8ca 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -82,7 +82,10 @@ const utils = { // Event dispatcher for OOYE bridge operations try { - if (message.t === "CHANNEL_UPDATE") { + if (message.t === "GUILD_UPDATE") { + await eventDispatcher.onGuildUpdate(client, message.d) + + } else if (message.t === "CHANNEL_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) } else if (message.t === "THREAD_CREATE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index c871ff1..9bb07c0 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -14,6 +14,8 @@ const addReaction = sync.require("./actions/add-reaction") const announceThread = sync.require("./actions/announce-thread") /** @type {import("./actions/create-room")}) */ const createRoom = sync.require("./actions/create-room") +/** @type {import("./actions/create-space")}) */ +const createSpace = sync.require("./actions/create-space") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") @@ -116,6 +118,16 @@ module.exports = { await announceThread.announceThread(parentRoomID, threadRoomID, thread) }, + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayGuildUpdateDispatchData} guild + */ + async onGuildUpdate(client, guild) { + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) + if (!spaceID) return + await createSpace.syncSpace(guild.id) + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayChannelUpdateDispatchData} channelOrThread From bfe9efe62eaccf43cb238b0e5fd79b9fe1674f59 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:37:25 +1200 Subject: [PATCH 146/200] listen for guild updates and connect them --- d2m/discord-packets.js | 5 ++++- d2m/event-dispatcher.js | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index a1a4505..6ece8ca 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -82,7 +82,10 @@ const utils = { // Event dispatcher for OOYE bridge operations try { - if (message.t === "CHANNEL_UPDATE") { + if (message.t === "GUILD_UPDATE") { + await eventDispatcher.onGuildUpdate(client, message.d) + + } else if (message.t === "CHANNEL_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) } else if (message.t === "THREAD_CREATE") { diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index c871ff1..9bb07c0 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -14,6 +14,8 @@ const addReaction = sync.require("./actions/add-reaction") const announceThread = sync.require("./actions/announce-thread") /** @type {import("./actions/create-room")}) */ const createRoom = sync.require("./actions/create-room") +/** @type {import("./actions/create-space")}) */ +const createSpace = sync.require("./actions/create-space") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") @@ -116,6 +118,16 @@ module.exports = { await announceThread.announceThread(parentRoomID, threadRoomID, thread) }, + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayGuildUpdateDispatchData} guild + */ + async onGuildUpdate(client, guild) { + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) + if (!spaceID) return + await createSpace.syncSpace(guild.id) + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayChannelUpdateDispatchData} channelOrThread From 415f2e9020f96247b4f41d93ac4e6f15faa974b8 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:39:37 +1200 Subject: [PATCH 147/200] remember any room avatars set on matrix-side --- d2m/actions/create-room.js | 22 ++++++++++++++++------ m2d/event-dispatcher.js | 13 ++++++++++++- matrix/api.js | 2 ++ matrix/kstate.js | 1 + types.d.ts | 9 +++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 3be2429..166d52f 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -61,11 +61,16 @@ async function channelToKState(channel, guild) { const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) assert.ok(typeof spaceID === "string") - const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + const row = db.prepare("SELECT nick, custom_avatar FROM channel_room WHERE channel_id = ?").get(channel.id) + assert(row) + const customName = row.nick + const customAvatar = row.custom_avatar const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) const avatarEventContent = {} - if (guild.icon) { + if (customAvatar) { + avatarEventContent.url = customAvatar + } else if (guild.icon) { avatarEventContent.discord_path = file.guildIcon(guild) avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } @@ -183,6 +188,8 @@ async function _syncRoom(channelID, shouldActuallySync) { return creation // Naturally, the newly created room is already up to date, so we can always skip syncing here. } + const roomID = existing.room_id + if (!shouldActuallySync) { return existing.room_id // only need to ensure room exists, and it does. return the room ID } @@ -192,15 +199,16 @@ async function _syncRoom(channelID, shouldActuallySync) { const {spaceID, channelKState} = await channelToKState(channel, guild) // sync channel state to room - const roomKState = await roomToKState(existing.room_id) + const roomKState = await roomToKState(roomID) const roomDiff = ks.diffKState(roomKState, channelKState) - const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) + const roomApply = applyKStateDiffToRoom(roomID, roomDiff) + db.prepare("UPDATE channel_room SET name = ? WHERE room_id = ?").run(channel.name, roomID) // sync room as space member - const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) + const spaceApply = _syncSpaceMember(channel, spaceID, roomID) await Promise.all([roomApply, spaceApply]) - return existing.room_id + return roomID } async function _unbridgeRoom(channelID) { @@ -279,5 +287,7 @@ module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState +module.exports.roomToKState = roomToKState +module.exports.applyKStateDiffToRoom = applyKStateDiffToRoom module.exports._convertNameAndTopic = convertNameAndTopic module.exports._unbridgeRoom = _unbridgeRoom diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 44eba85..c62d805 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -6,7 +6,7 @@ const util = require("util") const Ty = require("../types") -const {sync, as} = require("../passthrough") +const {db, sync, as} = require("../passthrough") /** @type {import("./actions/send-event")} */ const sendEvent = sync.require("./actions/send-event") @@ -69,3 +69,14 @@ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return await addReaction.addReaction(event) })) + +sync.addTemporaryListener(as, "type:m.room.avatar", guard("m.room.avatar", +/** + * @param {Ty.Event.StateOuter} event + */ +async event => { + if (event.state_key !== "") return + if (utils.eventSenderIsFromDiscord(event.sender)) return + const url = event.content.url || null + db.prepare("UPDATE channel_room SET custom_avatar = ? WHERE room_id = ?").run(url, event.room_id) +})) diff --git a/matrix/api.js b/matrix/api.js index b382631..9eff6c7 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -167,6 +167,8 @@ async function profileSetAvatarUrl(mxid, avatar_url) { * @param {number} power */ async function setUserPower(roomID, mxid, power) { + assert(roomID[0] === "!") + assert(mxid[0] === "@") // Yes it's this hard https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 const powerLevels = await getStateEvent(roomID, "m.room.power_levels", "") const users = powerLevels.users || {} diff --git a/matrix/kstate.js b/matrix/kstate.js index 398b1b6..1b2ca14 100644 --- a/matrix/kstate.js +++ b/matrix/kstate.js @@ -42,6 +42,7 @@ function diffKState(actual, target) { const diff = {} // go through each key that it should have for (const key of Object.keys(target)) { + if (!key.includes("/")) throw new Error(`target kstate's key "${key}" does not contain a slash separator; if a blank state_key was intended, add a trailing slash to the kstate key.`) if (key in actual) { // diff try { diff --git a/types.d.ts b/types.d.ts index b9f7ed6..faf4c70 100644 --- a/types.d.ts +++ b/types.d.ts @@ -38,6 +38,10 @@ namespace Event { event_id: string } + export type StateOuter = Outer & { + state_key: string + } + export type ReplacementContent = T & { "m.new_content": T "m.relates_to": { @@ -74,6 +78,11 @@ namespace Event { avatar_url?: string } + export type M_Room_Avatar = { + discord_path?: string + url?: string + } + export type M_Reaction = { "m.relates_to": { rel_type: "m.annotation" From 92cd628a6c845dfd16d0eb4e0e8611020759b49a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:39:37 +1200 Subject: [PATCH 148/200] remember any room avatars set on matrix-side --- d2m/actions/create-room.js | 22 ++++++++++++++++------ m2d/event-dispatcher.js | 13 ++++++++++++- matrix/api.js | 2 ++ matrix/kstate.js | 1 + types.d.ts | 9 +++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 3be2429..166d52f 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -61,11 +61,16 @@ async function channelToKState(channel, guild) { const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) assert.ok(typeof spaceID === "string") - const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + const row = db.prepare("SELECT nick, custom_avatar FROM channel_room WHERE channel_id = ?").get(channel.id) + assert(row) + const customName = row.nick + const customAvatar = row.custom_avatar const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) const avatarEventContent = {} - if (guild.icon) { + if (customAvatar) { + avatarEventContent.url = customAvatar + } else if (guild.icon) { avatarEventContent.discord_path = file.guildIcon(guild) avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } @@ -183,6 +188,8 @@ async function _syncRoom(channelID, shouldActuallySync) { return creation // Naturally, the newly created room is already up to date, so we can always skip syncing here. } + const roomID = existing.room_id + if (!shouldActuallySync) { return existing.room_id // only need to ensure room exists, and it does. return the room ID } @@ -192,15 +199,16 @@ async function _syncRoom(channelID, shouldActuallySync) { const {spaceID, channelKState} = await channelToKState(channel, guild) // sync channel state to room - const roomKState = await roomToKState(existing.room_id) + const roomKState = await roomToKState(roomID) const roomDiff = ks.diffKState(roomKState, channelKState) - const roomApply = applyKStateDiffToRoom(existing.room_id, roomDiff) + const roomApply = applyKStateDiffToRoom(roomID, roomDiff) + db.prepare("UPDATE channel_room SET name = ? WHERE room_id = ?").run(channel.name, roomID) // sync room as space member - const spaceApply = _syncSpaceMember(channel, spaceID, existing.room_id) + const spaceApply = _syncSpaceMember(channel, spaceID, roomID) await Promise.all([roomApply, spaceApply]) - return existing.room_id + return roomID } async function _unbridgeRoom(channelID) { @@ -279,5 +287,7 @@ module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState +module.exports.roomToKState = roomToKState +module.exports.applyKStateDiffToRoom = applyKStateDiffToRoom module.exports._convertNameAndTopic = convertNameAndTopic module.exports._unbridgeRoom = _unbridgeRoom diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 44eba85..c62d805 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -6,7 +6,7 @@ const util = require("util") const Ty = require("../types") -const {sync, as} = require("../passthrough") +const {db, sync, as} = require("../passthrough") /** @type {import("./actions/send-event")} */ const sendEvent = sync.require("./actions/send-event") @@ -69,3 +69,14 @@ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return await addReaction.addReaction(event) })) + +sync.addTemporaryListener(as, "type:m.room.avatar", guard("m.room.avatar", +/** + * @param {Ty.Event.StateOuter} event + */ +async event => { + if (event.state_key !== "") return + if (utils.eventSenderIsFromDiscord(event.sender)) return + const url = event.content.url || null + db.prepare("UPDATE channel_room SET custom_avatar = ? WHERE room_id = ?").run(url, event.room_id) +})) diff --git a/matrix/api.js b/matrix/api.js index b382631..9eff6c7 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -167,6 +167,8 @@ async function profileSetAvatarUrl(mxid, avatar_url) { * @param {number} power */ async function setUserPower(roomID, mxid, power) { + assert(roomID[0] === "!") + assert(mxid[0] === "@") // Yes it's this hard https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 const powerLevels = await getStateEvent(roomID, "m.room.power_levels", "") const users = powerLevels.users || {} diff --git a/matrix/kstate.js b/matrix/kstate.js index 398b1b6..1b2ca14 100644 --- a/matrix/kstate.js +++ b/matrix/kstate.js @@ -42,6 +42,7 @@ function diffKState(actual, target) { const diff = {} // go through each key that it should have for (const key of Object.keys(target)) { + if (!key.includes("/")) throw new Error(`target kstate's key "${key}" does not contain a slash separator; if a blank state_key was intended, add a trailing slash to the kstate key.`) if (key in actual) { // diff try { diff --git a/types.d.ts b/types.d.ts index b9f7ed6..faf4c70 100644 --- a/types.d.ts +++ b/types.d.ts @@ -38,6 +38,10 @@ namespace Event { event_id: string } + export type StateOuter = Outer & { + state_key: string + } + export type ReplacementContent = T & { "m.new_content": T "m.relates_to": { @@ -74,6 +78,11 @@ namespace Event { avatar_url?: string } + export type M_Room_Avatar = { + discord_path?: string + url?: string + } + export type M_Reaction = { "m.relates_to": { rel_type: "m.annotation" From 164c0354bf9ed6fa6228745308108d1b4793b9a6 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:45:19 +1200 Subject: [PATCH 149/200] make hardcoded "cadence.moe" configurable --- d2m/actions/create-room.js | 5 +++-- d2m/actions/register-user.js | 2 +- m2d/converters/utils.js | 1 + seed.js | 4 ++-- types.d.ts | 5 +++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 166d52f..c8f0661 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") +const reg = require("../../matrix/read-registration") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough @@ -85,7 +86,7 @@ async function channelToKState(channel, guild) { "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility}, [`m.space.parent/${spaceID}`]: { - via: ["cadence.moe"], // TODO: put the proper server here + via: [reg.ooye.server_name], canonical: true }, "m.room.join_rules/": { @@ -252,7 +253,7 @@ async function _syncSpaceMember(channel, spaceID, roomID) { && !channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) ) { spaceEventContent = { - via: ["cadence.moe"] // TODO: use the proper server + via: [reg.ooye.server_name] } } const spaceDiff = ks.diffKState(spaceKState, { diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index a33cecc..00a985c 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -21,7 +21,7 @@ async function createSim(user) { // Choose sim name const simName = userToMxid.userToSimName(user) const localpart = reg.ooye.namespace_prefix + simName - const mxid = "@" + localpart + ":cadence.moe" + const mxid = `@${localpart}:${reg.ooye.server_name}` // 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 diff --git a/m2d/converters/utils.js b/m2d/converters/utils.js index 108da1f..7b9c504 100644 --- a/m2d/converters/utils.js +++ b/m2d/converters/utils.js @@ -11,6 +11,7 @@ function eventSenderIsFromDiscord(sender) { // If it's from a user in the bridge's namespace, then it originated from discord // This includes messages sent by the appservice's bot user, because that is what's used for webhooks // TODO: It would be nice if bridge system messages wouldn't trigger this check and could be bridged from matrix to discord, while webhook reflections would remain ignored... + // TODO that only applies to the above todo: But you'd have to watch out for the /icon command, where the bridge bot would set the room avatar, and that shouldn't be reflected into the room a second time. if (userRegex.some(x => sender.match(x))) { return true } diff --git a/seed.js b/seed.js index d84ca8d..e513f63 100644 --- a/seed.js +++ b/seed.js @@ -25,8 +25,8 @@ const reg = require("./matrix/read-registration") const avatarUrl = await file.uploadDiscordFileToMxc("https://cadence.moe/friends/out_of_your_element_rev_2.jpg") // set profile data on homeserver... - await api.profileSetDisplayname(`@${reg.sender_localpart}:cadence.moe`, "Out Of Your Element") - await api.profileSetAvatarUrl(`@${reg.sender_localpart}:cadence.moe`, avatarUrl) + await api.profileSetDisplayname(`@${reg.sender_localpart}:${reg.ooye.server_name}`, "Out Of Your Element") + await api.profileSetAvatarUrl(`@${reg.sender_localpart}:${reg.ooye.server_name}`, avatarUrl) // database ddl... diff --git a/types.d.ts b/types.d.ts index faf4c70..2ba8d1d 100644 --- a/types.d.ts +++ b/types.d.ts @@ -19,6 +19,7 @@ export type AppServiceRegistrationConfig = { ooye: { namespace_prefix: string max_file_size: number + server_name: string } } @@ -27,7 +28,7 @@ export type WebhookCreds = { token: string } -namespace Event { +export namespace Event { export type Outer = { type: string room_id: string @@ -92,7 +93,7 @@ namespace Event { } } -namespace R { +export namespace R { export type RoomCreated = { room_id: string } From 458a620f4a160f1ca9dfccf220097239d8084963 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 12:45:19 +1200 Subject: [PATCH 150/200] make hardcoded "cadence.moe" configurable --- d2m/actions/create-room.js | 5 +++-- d2m/actions/register-user.js | 2 +- m2d/converters/utils.js | 1 + seed.js | 4 ++-- types.d.ts | 5 +++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 166d52f..c8f0661 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") +const reg = require("../../matrix/read-registration") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough @@ -85,7 +86,7 @@ async function channelToKState(channel, guild) { "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility}, [`m.space.parent/${spaceID}`]: { - via: ["cadence.moe"], // TODO: put the proper server here + via: [reg.ooye.server_name], canonical: true }, "m.room.join_rules/": { @@ -252,7 +253,7 @@ async function _syncSpaceMember(channel, spaceID, roomID) { && !channel["thread_metadata"]?.archived // archived threads do not belong in the space (don't offer people conversations that are no longer relevant) ) { spaceEventContent = { - via: ["cadence.moe"] // TODO: use the proper server + via: [reg.ooye.server_name] } } const spaceDiff = ks.diffKState(spaceKState, { diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index a33cecc..00a985c 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -21,7 +21,7 @@ async function createSim(user) { // Choose sim name const simName = userToMxid.userToSimName(user) const localpart = reg.ooye.namespace_prefix + simName - const mxid = "@" + localpart + ":cadence.moe" + const mxid = `@${localpart}:${reg.ooye.server_name}` // 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 diff --git a/m2d/converters/utils.js b/m2d/converters/utils.js index 108da1f..7b9c504 100644 --- a/m2d/converters/utils.js +++ b/m2d/converters/utils.js @@ -11,6 +11,7 @@ function eventSenderIsFromDiscord(sender) { // If it's from a user in the bridge's namespace, then it originated from discord // This includes messages sent by the appservice's bot user, because that is what's used for webhooks // TODO: It would be nice if bridge system messages wouldn't trigger this check and could be bridged from matrix to discord, while webhook reflections would remain ignored... + // TODO that only applies to the above todo: But you'd have to watch out for the /icon command, where the bridge bot would set the room avatar, and that shouldn't be reflected into the room a second time. if (userRegex.some(x => sender.match(x))) { return true } diff --git a/seed.js b/seed.js index d84ca8d..e513f63 100644 --- a/seed.js +++ b/seed.js @@ -25,8 +25,8 @@ const reg = require("./matrix/read-registration") const avatarUrl = await file.uploadDiscordFileToMxc("https://cadence.moe/friends/out_of_your_element_rev_2.jpg") // set profile data on homeserver... - await api.profileSetDisplayname(`@${reg.sender_localpart}:cadence.moe`, "Out Of Your Element") - await api.profileSetAvatarUrl(`@${reg.sender_localpart}:cadence.moe`, avatarUrl) + await api.profileSetDisplayname(`@${reg.sender_localpart}:${reg.ooye.server_name}`, "Out Of Your Element") + await api.profileSetAvatarUrl(`@${reg.sender_localpart}:${reg.ooye.server_name}`, avatarUrl) // database ddl... diff --git a/types.d.ts b/types.d.ts index faf4c70..2ba8d1d 100644 --- a/types.d.ts +++ b/types.d.ts @@ -19,6 +19,7 @@ export type AppServiceRegistrationConfig = { ooye: { namespace_prefix: string max_file_size: number + server_name: string } } @@ -27,7 +28,7 @@ export type WebhookCreds = { token: string } -namespace Event { +export namespace Event { export type Outer = { type: string room_id: string @@ -92,7 +93,7 @@ namespace Event { } } -namespace R { +export namespace R { export type RoomCreated = { room_id: string } From 96e125d8c093c4bc2fd572d6334635ab4ec084ac Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 16:16:36 +1200 Subject: [PATCH 151/200] using kstate with power levels should mix them --- db/data-for-test.sql | 11 +++++---- matrix/api.js | 2 +- matrix/kstate.js | 23 +++++++++++++++--- matrix/kstate.test.js | 54 +++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 8 +++---- package.json | 2 +- stdin.js | 1 + 7 files changed, 87 insertions(+), 14 deletions(-) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index 4a406c9..ec9f9ec 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS "channel_room" ( "name" TEXT, "nick" TEXT, "thread_parent" TEXT, + "custom_avatar" TEXT, PRIMARY KEY("channel_id") ); CREATE TABLE IF NOT EXISTS "event_message" ( @@ -55,11 +56,11 @@ BEGIN TRANSACTION; INSERT INTO guild_space (guild_id, space_id) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); -INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES -('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL), -('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL), -('1100319550446252084', '!PnyBKvUBOhjuCucEfk:cadence.moe', 'worm-farm', NULL, NULL); +INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent, custom_avatar) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, NULL), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL, NULL), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL, NULL), +('1100319550446252084', '!PnyBKvUBOhjuCucEfk:cadence.moe', 'worm-farm', NULL, NULL, NULL); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), diff --git a/matrix/api.js b/matrix/api.js index 9eff6c7..2e0763e 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -169,7 +169,7 @@ async function profileSetAvatarUrl(mxid, avatar_url) { async function setUserPower(roomID, mxid, power) { assert(roomID[0] === "!") assert(mxid[0] === "@") - // Yes it's this hard https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 + // Yes there's no shortcut https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 const powerLevels = await getStateEvent(roomID, "m.room.power_levels", "") const users = powerLevels.users || {} if (power != null) { diff --git a/matrix/kstate.js b/matrix/kstate.js index 1b2ca14..469ec91 100644 --- a/matrix/kstate.js +++ b/matrix/kstate.js @@ -1,6 +1,7 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict +const mixin = require("mixin-deep") /** Mutates the input. */ function kstateStripConditionals(kstate) { @@ -43,18 +44,34 @@ function diffKState(actual, target) { // go through each key that it should have for (const key of Object.keys(target)) { if (!key.includes("/")) throw new Error(`target kstate's key "${key}" does not contain a slash separator; if a blank state_key was intended, add a trailing slash to the kstate key.`) - if (key in actual) { + + if (key === "m.room.power_levels/") { + // Special handling for power levels, we want to deep merge the actual and target into the final state. + console.log(actual[key]) + const temp = mixin({}, actual[key], target[key]) + console.log(actual[key]) + console.log(temp) + try { + assert.deepEqual(actual[key], temp) + } catch (e) { + // they differ. use the newly prepared object as the diff. + diff[key] = temp + } + + } else if (key in actual) { // diff try { assert.deepEqual(actual[key], target[key]) } catch (e) { - // they differ. reassign the target + // they differ. use the target as the diff. diff[key] = target[key] } + } else { // not present, needs to be added diff[key] = target[key] } + // keys that are missing in "actual" will not be deleted on "target" (no action) } return diff diff --git a/matrix/kstate.test.js b/matrix/kstate.test.js index 1541898..11d5131 100644 --- a/matrix/kstate.test.js +++ b/matrix/kstate.test.js @@ -92,3 +92,57 @@ test("diffKState: detects new properties", t => { } ) }) + +test("diffKState: power levels are mixed together", t => { + const original = { + "m.room.power_levels/": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100 + }, + "users_default": 0 + } + } + const result = diffKState(original, { + "m.room.power_levels/": { + "events": { + "m.room.avatar": 0 + } + } + }) + t.deepEqual(result, { + "m.room.power_levels/": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100, + "m.room.avatar": 0 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100 + }, + "users_default": 0 + } + }) + t.notDeepEqual(original, result) +}) diff --git a/package-lock.json b/package-lock.json index 875e329..e808e1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", - "mixin-deep": "^2.0.1", + "mixin-deep": "github:cloudrac3r/mixin-deep#v3.0.0", "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", @@ -2111,9 +2111,9 @@ } }, "node_modules/mixin-deep": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", - "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==", + "version": "3.0.0", + "resolved": "git+ssh://git@github.com/cloudrac3r/mixin-deep.git#2dd70d6b8644263f7ed2c1620506c9eb3f11d32a", + "license": "MIT", "engines": { "node": ">=6" } diff --git a/package.json b/package.json index bc0a0db..67aeade 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", - "mixin-deep": "^2.0.1", + "mixin-deep": "github:cloudrac3r/mixin-deep#v3.0.0", "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", diff --git a/stdin.js b/stdin.js index 61a2a08..ce612f5 100644 --- a/stdin.js +++ b/stdin.js @@ -14,6 +14,7 @@ const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") const sendEvent = sync.require("./m2d/actions/send-event") const eventDispatcher = sync.require("./d2m/event-dispatcher") +const ks = sync.require("./matrix/kstate") const guildID = "112760669178241024" const extraContext = {} From 8d536d5ef266a23253a55db2827503b9e42b3bab Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 16:16:36 +1200 Subject: [PATCH 152/200] using kstate with power levels should mix them --- db/data-for-test.sql | 11 +++++---- matrix/api.js | 2 +- matrix/kstate.js | 23 +++++++++++++++--- matrix/kstate.test.js | 54 +++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 8 +++---- package.json | 2 +- stdin.js | 1 + 7 files changed, 87 insertions(+), 14 deletions(-) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index 4a406c9..ec9f9ec 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS "channel_room" ( "name" TEXT, "nick" TEXT, "thread_parent" TEXT, + "custom_avatar" TEXT, PRIMARY KEY("channel_id") ); CREATE TABLE IF NOT EXISTS "event_message" ( @@ -55,11 +56,11 @@ BEGIN TRANSACTION; INSERT INTO guild_space (guild_id, space_id) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); -INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES -('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL), -('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL), -('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL), -('1100319550446252084', '!PnyBKvUBOhjuCucEfk:cadence.moe', 'worm-farm', NULL, NULL); +INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent, custom_avatar) VALUES +('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, NULL), +('497161350934560778', '!edUxjVdzgUvXDUIQCK:cadence.moe', 'amanda-spam', NULL, NULL, NULL), +('160197704226439168', '!uCtjHhfGlYbVnPVlkG:cadence.moe', 'the-stanley-parable-channel', 'bots', NULL, NULL), +('1100319550446252084', '!PnyBKvUBOhjuCucEfk:cadence.moe', 'worm-farm', NULL, NULL, NULL); INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('0', 'bot', '_ooye_bot', '@_ooye_bot:cadence.moe'), diff --git a/matrix/api.js b/matrix/api.js index 9eff6c7..2e0763e 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -169,7 +169,7 @@ async function profileSetAvatarUrl(mxid, avatar_url) { async function setUserPower(roomID, mxid, power) { assert(roomID[0] === "!") assert(mxid[0] === "@") - // Yes it's this hard https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 + // Yes there's no shortcut https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352 const powerLevels = await getStateEvent(roomID, "m.room.power_levels", "") const users = powerLevels.users || {} if (power != null) { diff --git a/matrix/kstate.js b/matrix/kstate.js index 1b2ca14..469ec91 100644 --- a/matrix/kstate.js +++ b/matrix/kstate.js @@ -1,6 +1,7 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict +const mixin = require("mixin-deep") /** Mutates the input. */ function kstateStripConditionals(kstate) { @@ -43,18 +44,34 @@ function diffKState(actual, target) { // go through each key that it should have for (const key of Object.keys(target)) { if (!key.includes("/")) throw new Error(`target kstate's key "${key}" does not contain a slash separator; if a blank state_key was intended, add a trailing slash to the kstate key.`) - if (key in actual) { + + if (key === "m.room.power_levels/") { + // Special handling for power levels, we want to deep merge the actual and target into the final state. + console.log(actual[key]) + const temp = mixin({}, actual[key], target[key]) + console.log(actual[key]) + console.log(temp) + try { + assert.deepEqual(actual[key], temp) + } catch (e) { + // they differ. use the newly prepared object as the diff. + diff[key] = temp + } + + } else if (key in actual) { // diff try { assert.deepEqual(actual[key], target[key]) } catch (e) { - // they differ. reassign the target + // they differ. use the target as the diff. diff[key] = target[key] } + } else { // not present, needs to be added diff[key] = target[key] } + // keys that are missing in "actual" will not be deleted on "target" (no action) } return diff diff --git a/matrix/kstate.test.js b/matrix/kstate.test.js index 1541898..11d5131 100644 --- a/matrix/kstate.test.js +++ b/matrix/kstate.test.js @@ -92,3 +92,57 @@ test("diffKState: detects new properties", t => { } ) }) + +test("diffKState: power levels are mixed together", t => { + const original = { + "m.room.power_levels/": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100 + }, + "users_default": 0 + } + } + const result = diffKState(original, { + "m.room.power_levels/": { + "events": { + "m.room.avatar": 0 + } + } + }) + t.deepEqual(result, { + "m.room.power_levels/": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100, + "m.room.avatar": 0 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100 + }, + "users_default": 0 + } + }) + t.notDeepEqual(original, result) +}) diff --git a/package-lock.json b/package-lock.json index 875e329..e808e1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", - "mixin-deep": "^2.0.1", + "mixin-deep": "github:cloudrac3r/mixin-deep#v3.0.0", "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", @@ -2111,9 +2111,9 @@ } }, "node_modules/mixin-deep": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-2.0.1.tgz", - "integrity": "sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA==", + "version": "3.0.0", + "resolved": "git+ssh://git@github.com/cloudrac3r/mixin-deep.git#2dd70d6b8644263f7ed2c1620506c9eb3f11d32a", + "license": "MIT", "engines": { "node": ">=6" } diff --git a/package.json b/package.json index bc0a0db..67aeade 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", "matrix-js-sdk": "^24.1.0", - "mixin-deep": "^2.0.1", + "mixin-deep": "github:cloudrac3r/mixin-deep#v3.0.0", "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", diff --git a/stdin.js b/stdin.js index 61a2a08..ce612f5 100644 --- a/stdin.js +++ b/stdin.js @@ -14,6 +14,7 @@ const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") const sendEvent = sync.require("./m2d/actions/send-event") const eventDispatcher = sync.require("./d2m/event-dispatcher") +const ks = sync.require("./matrix/kstate") const guildID = "112760669178241024" const extraContext = {} From 97ab77a0608adfa0e4b3f490b11c7717273873f9 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 17:08:20 +1200 Subject: [PATCH 153/200] workaround synapse bug that would make broken PLs --- d2m/actions/create-room.js | 60 ++++++++++++++++++++++++++++++------- d2m/actions/create-space.js | 31 +++++++++---------- matrix/kstate.js | 4 +-- test/data.js | 7 ++++- 4 files changed, 72 insertions(+), 30 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index c8f0661..29bcdc0 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -95,6 +95,11 @@ async function channelToKState(channel, guild) { type: "m.room_membership", room_id: spaceID }] + }, + "m.room.power_levels/": { + events: { + "m.room.avatar": 0 + } } } @@ -114,24 +119,56 @@ async function createRoom(channel, guild, spaceID, kstate) { if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO - const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) - const roomID = await api.createRoom({ - name: convertedName, - topic: convertedTopic, - preset: "private_chat", - visibility: "private", - invite, - initial_state: ks.kstateToState(kstate) + const roomID = await postApplyPowerLevels(kstate, async kstate => { + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) + const roomID = await api.createRoom({ + name: convertedName, + topic: convertedTopic, + preset: "private_chat", + visibility: "private", + invite, + initial_state: ks.kstateToState(kstate) + }) + + db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) + + return roomID }) - db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) - - // Put the newly created child into the space + // Put the newly created child into the space, no need to await this _syncSpaceMember(channel, spaceID, roomID) return roomID } +/** + * Handling power levels separately. The spec doesn't specify what happens, Dendrite differs, + * 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 + * @param {any} kstate + * @param {(_: any) => Promise} callback must return room ID + * @returns {Promise} room ID + */ +async function postApplyPowerLevels(kstate, callback) { + const powerLevelContent = kstate["m.room.power_levels/"] + const kstateWithoutPowerLevels = {...kstate} + delete kstateWithoutPowerLevels["m.room.power_levels/"] + + /** @type {string} */ + const roomID = await callback(kstateWithoutPowerLevels) + + // Now *really* apply the power level overrides on top of what Synapse *really* set + if (powerLevelContent) { + const newRoomKState = await roomToKState(roomID) + const newRoomPowerLevelsDiff = ks.diffKState(newRoomKState, {"m.room.power_levels/": powerLevelContent}) + await applyKStateDiffToRoom(roomID, newRoomPowerLevelsDiff) + } + + return roomID +} + /** * @param {DiscordTypes.APIGuildChannel} channel */ @@ -290,5 +327,6 @@ module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState module.exports.roomToKState = roomToKState module.exports.applyKStateDiffToRoom = applyKStateDiffToRoom +module.exports.postApplyPowerLevels = postApplyPowerLevels module.exports._convertNameAndTopic = convertNameAndTopic module.exports._unbridgeRoom = _unbridgeRoom diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 34aa25f..46fa71f 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -21,23 +21,24 @@ const ks = sync.require("../../matrix/kstate") async function createSpace(guild, kstate) { const name = kstate["m.room.name/"].name const topic = kstate["m.room.topic/"]?.topic || undefined - assert(name) - const roomID = await api.createRoom({ - name, - preset: "private_chat", // cannot join space unless invited - visibility: "private", - power_level_content_override: { - events_default: 100, // space can only be managed by bridge - invite: 0 // any existing member can invite others - }, - invite: ["@cadence:cadence.moe"], // TODO - topic, - creation_content: { - type: "m.space" - }, - initial_state: ks.kstateToState(kstate) + const roomID = await createRoom.postApplyPowerLevels(kstate, async kstate => { + return api.createRoom({ + name, + preset: "private_chat", // cannot join space unless invited + visibility: "private", + power_level_content_override: { + events_default: 100, // space can only be managed by bridge + invite: 0 // any existing member can invite others + }, + invite: ["@cadence:cadence.moe"], // TODO + topic, + creation_content: { + type: "m.space" + }, + initial_state: ks.kstateToState(kstate) + }) }) db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID) return roomID diff --git a/matrix/kstate.js b/matrix/kstate.js index 469ec91..e840254 100644 --- a/matrix/kstate.js +++ b/matrix/kstate.js @@ -47,10 +47,8 @@ function diffKState(actual, target) { if (key === "m.room.power_levels/") { // Special handling for power levels, we want to deep merge the actual and target into the final state. - console.log(actual[key]) + if (!(key in actual)) throw new Error(`want to apply a power levels diff, but original power level data is missing\nstarted with: ${JSON.stringify(actual)}\nwant to apply: ${JSON.stringify(target)}`) const temp = mixin({}, actual[key], target[key]) - console.log(actual[key]) - console.log(temp) try { assert.deepEqual(actual[key], temp) } catch (e) { diff --git a/test/data.js b/test/data.js index 30d108a..6ed2f42 100644 --- a/test/data.js +++ b/test/data.js @@ -27,7 +27,7 @@ module.exports = { "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { - via: ["cadence.moe"], // TODO: put the proper server here + via: ["cadence.moe"], canonical: true }, "m.room.join_rules/": { @@ -40,6 +40,11 @@ module.exports = { "m.room.avatar/": { discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF" + }, + "m.room.power_levels/": { + events: { + "m.room.avatar": 0 + } } } }, From a8fab062a4527d52d45a7be54da11f9c176bd6db Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 23 Aug 2023 17:08:20 +1200 Subject: [PATCH 154/200] workaround synapse bug that would make broken PLs --- d2m/actions/create-room.js | 60 ++++++++++++++++++++++++++++++------- d2m/actions/create-space.js | 31 +++++++++---------- matrix/kstate.js | 4 +-- test/data.js | 7 ++++- 4 files changed, 72 insertions(+), 30 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index c8f0661..29bcdc0 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -95,6 +95,11 @@ async function channelToKState(channel, guild) { type: "m.room_membership", room_id: spaceID }] + }, + "m.room.power_levels/": { + events: { + "m.room.avatar": 0 + } } } @@ -114,24 +119,56 @@ async function createRoom(channel, guild, spaceID, kstate) { if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO - const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) - const roomID = await api.createRoom({ - name: convertedName, - topic: convertedTopic, - preset: "private_chat", - visibility: "private", - invite, - initial_state: ks.kstateToState(kstate) + const roomID = await postApplyPowerLevels(kstate, async kstate => { + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) + const roomID = await api.createRoom({ + name: convertedName, + topic: convertedTopic, + preset: "private_chat", + visibility: "private", + invite, + initial_state: ks.kstateToState(kstate) + }) + + db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) + + return roomID }) - db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent) - - // Put the newly created child into the space + // Put the newly created child into the space, no need to await this _syncSpaceMember(channel, spaceID, roomID) return roomID } +/** + * Handling power levels separately. The spec doesn't specify what happens, Dendrite differs, + * 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 + * @param {any} kstate + * @param {(_: any) => Promise} callback must return room ID + * @returns {Promise} room ID + */ +async function postApplyPowerLevels(kstate, callback) { + const powerLevelContent = kstate["m.room.power_levels/"] + const kstateWithoutPowerLevels = {...kstate} + delete kstateWithoutPowerLevels["m.room.power_levels/"] + + /** @type {string} */ + const roomID = await callback(kstateWithoutPowerLevels) + + // Now *really* apply the power level overrides on top of what Synapse *really* set + if (powerLevelContent) { + const newRoomKState = await roomToKState(roomID) + const newRoomPowerLevelsDiff = ks.diffKState(newRoomKState, {"m.room.power_levels/": powerLevelContent}) + await applyKStateDiffToRoom(roomID, newRoomPowerLevelsDiff) + } + + return roomID +} + /** * @param {DiscordTypes.APIGuildChannel} channel */ @@ -290,5 +327,6 @@ module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState module.exports.roomToKState = roomToKState module.exports.applyKStateDiffToRoom = applyKStateDiffToRoom +module.exports.postApplyPowerLevels = postApplyPowerLevels module.exports._convertNameAndTopic = convertNameAndTopic module.exports._unbridgeRoom = _unbridgeRoom diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 34aa25f..46fa71f 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -21,23 +21,24 @@ const ks = sync.require("../../matrix/kstate") async function createSpace(guild, kstate) { const name = kstate["m.room.name/"].name const topic = kstate["m.room.topic/"]?.topic || undefined - assert(name) - const roomID = await api.createRoom({ - name, - preset: "private_chat", // cannot join space unless invited - visibility: "private", - power_level_content_override: { - events_default: 100, // space can only be managed by bridge - invite: 0 // any existing member can invite others - }, - invite: ["@cadence:cadence.moe"], // TODO - topic, - creation_content: { - type: "m.space" - }, - initial_state: ks.kstateToState(kstate) + const roomID = await createRoom.postApplyPowerLevels(kstate, async kstate => { + return api.createRoom({ + name, + preset: "private_chat", // cannot join space unless invited + visibility: "private", + power_level_content_override: { + events_default: 100, // space can only be managed by bridge + invite: 0 // any existing member can invite others + }, + invite: ["@cadence:cadence.moe"], // TODO + topic, + creation_content: { + type: "m.space" + }, + initial_state: ks.kstateToState(kstate) + }) }) db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID) return roomID diff --git a/matrix/kstate.js b/matrix/kstate.js index 469ec91..e840254 100644 --- a/matrix/kstate.js +++ b/matrix/kstate.js @@ -47,10 +47,8 @@ function diffKState(actual, target) { if (key === "m.room.power_levels/") { // Special handling for power levels, we want to deep merge the actual and target into the final state. - console.log(actual[key]) + if (!(key in actual)) throw new Error(`want to apply a power levels diff, but original power level data is missing\nstarted with: ${JSON.stringify(actual)}\nwant to apply: ${JSON.stringify(target)}`) const temp = mixin({}, actual[key], target[key]) - console.log(actual[key]) - console.log(temp) try { assert.deepEqual(actual[key], temp) } catch (e) { diff --git a/test/data.js b/test/data.js index 30d108a..6ed2f42 100644 --- a/test/data.js +++ b/test/data.js @@ -27,7 +27,7 @@ module.exports = { "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { - via: ["cadence.moe"], // TODO: put the proper server here + via: ["cadence.moe"], canonical: true }, "m.room.join_rules/": { @@ -40,6 +40,11 @@ module.exports = { "m.room.avatar/": { discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF" + }, + "m.room.power_levels/": { + events: { + "m.room.avatar": 0 + } } } }, From 587267250d771483b01af1e55202d48f9c48aa61 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 00:27:51 +1200 Subject: [PATCH 155/200] syncing all room power levels --- d2m/actions/create-room.js | 5 ++--- matrix/read-registration.js | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 29bcdc0..b641d37 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -63,9 +63,8 @@ async function channelToKState(channel, guild) { assert.ok(typeof spaceID === "string") const row = db.prepare("SELECT nick, custom_avatar FROM channel_room WHERE channel_id = ?").get(channel.id) - assert(row) - const customName = row.nick - const customAvatar = row.custom_avatar + const customName = row?.nick + const customAvatar = row?.custom_avatar const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) const avatarEventContent = {} diff --git a/matrix/read-registration.js b/matrix/read-registration.js index a1d920d..54d77ae 100644 --- a/matrix/read-registration.js +++ b/matrix/read-registration.js @@ -1,8 +1,12 @@ // @ts-check const fs = require("fs") +const assert = require("assert").strict const yaml = require("js-yaml") /** @ts-ignore @type {import("../types").AppServiceRegistrationConfig} */ const reg = yaml.load(fs.readFileSync("registration.yaml", "utf8")) -module.exports = reg \ No newline at end of file +assert(reg.ooye.max_file_size) +assert(reg.ooye.namespace_prefix) +assert(reg.ooye.server_name) +module.exports = reg From f0ff89161a7fc4da2e4a31b63ac3ceae8acb5008 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 00:27:51 +1200 Subject: [PATCH 156/200] syncing all room power levels --- d2m/actions/create-room.js | 5 ++--- matrix/read-registration.js | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 29bcdc0..b641d37 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -63,9 +63,8 @@ async function channelToKState(channel, guild) { assert.ok(typeof spaceID === "string") const row = db.prepare("SELECT nick, custom_avatar FROM channel_room WHERE channel_id = ?").get(channel.id) - assert(row) - const customName = row.nick - const customAvatar = row.custom_avatar + const customName = row?.nick + const customAvatar = row?.custom_avatar const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) const avatarEventContent = {} diff --git a/matrix/read-registration.js b/matrix/read-registration.js index a1d920d..54d77ae 100644 --- a/matrix/read-registration.js +++ b/matrix/read-registration.js @@ -1,8 +1,12 @@ // @ts-check const fs = require("fs") +const assert = require("assert").strict const yaml = require("js-yaml") /** @ts-ignore @type {import("../types").AppServiceRegistrationConfig} */ const reg = yaml.load(fs.readFileSync("registration.yaml", "utf8")) -module.exports = reg \ No newline at end of file +assert(reg.ooye.max_file_size) +assert(reg.ooye.namespace_prefix) +assert(reg.ooye.server_name) +module.exports = reg From 7712bc34a6081a62cd06e2b046f3ba8869c44aa7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 00:35:35 +1200 Subject: [PATCH 157/200] add test case for m->d too-long message --- m2d/converters/event-to-message.test.js | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index f0c4664..a45c23b 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -27,3 +27,31 @@ test("event2message: janky test", t => { }] ) }) + +test("event2message: long messages are split", t => { + t.deepEqual( + eventToMessage({ + content: { + body: ("a".repeat(130) + " ").repeat(19), + msgtype: "m.text" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: (("a".repeat(130) + " ").repeat(15)).slice(0, -1), + avatar_url: undefined + }, { + username: "cadence", + content: (("a".repeat(130) + " ").repeat(4)).slice(0, -1), + avatar_url: undefined + }] + ) +}) From 4c0aa57ba77da5a5de33b00a0669031a2b850980 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 00:35:35 +1200 Subject: [PATCH 158/200] add test case for m->d too-long message --- m2d/converters/event-to-message.test.js | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index f0c4664..a45c23b 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -27,3 +27,31 @@ test("event2message: janky test", t => { }] ) }) + +test("event2message: long messages are split", t => { + t.deepEqual( + eventToMessage({ + content: { + body: ("a".repeat(130) + " ").repeat(19), + msgtype: "m.text" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: (("a".repeat(130) + " ").repeat(15)).slice(0, -1), + avatar_url: undefined + }, { + username: "cadence", + content: (("a".repeat(130) + " ").repeat(4)).slice(0, -1), + avatar_url: undefined + }] + ) +}) From 58d15d205a18ea25f86340c1b333708afd4911a8 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 12:42:12 +1200 Subject: [PATCH 159/200] don't set the name and topic twice --- d2m/actions/create-room.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index b641d37..cb1bc85 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -118,13 +118,21 @@ async function createRoom(channel, guild, spaceID, kstate) { if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO + // Name and topic can be done earlier in room creation rather than in initial_state + // https://spec.matrix.org/latest/client-server-api/#creation + const name = kstate["m.room.name/"].name + delete kstate["m.room.name/"] + assert(name) + const topic = kstate["m.room.topic/"].topic + delete kstate["m.room.topic/"] + assert(topic) + const roomID = await postApplyPowerLevels(kstate, async kstate => { - const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) const roomID = await api.createRoom({ - name: convertedName, - topic: convertedTopic, - preset: "private_chat", - visibility: "private", + name, + topic, + preset: "private_chat", // This is closest to what we want, but properties from kstate override it anyway + visibility: "private", // Not shown in the room directory invite, initial_state: ks.kstateToState(kstate) }) From 40c3ef8e83ff27d2c5446266d8347bbe7e254eb8 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 12:42:12 +1200 Subject: [PATCH 160/200] don't set the name and topic twice --- d2m/actions/create-room.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index b641d37..cb1bc85 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -118,13 +118,21 @@ async function createRoom(channel, guild, spaceID, kstate) { if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO + // Name and topic can be done earlier in room creation rather than in initial_state + // https://spec.matrix.org/latest/client-server-api/#creation + const name = kstate["m.room.name/"].name + delete kstate["m.room.name/"] + assert(name) + const topic = kstate["m.room.topic/"].topic + delete kstate["m.room.topic/"] + assert(topic) + const roomID = await postApplyPowerLevels(kstate, async kstate => { - const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null) const roomID = await api.createRoom({ - name: convertedName, - topic: convertedTopic, - preset: "private_chat", - visibility: "private", + name, + topic, + preset: "private_chat", // This is closest to what we want, but properties from kstate override it anyway + visibility: "private", // Not shown in the room directory invite, initial_state: ks.kstateToState(kstate) }) From d41062218f32451a94a819c2c1e4412067004a36 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 17:09:25 +1200 Subject: [PATCH 161/200] start adding command handlers --- d2m/discord-command-handler.js | 81 ++++++++++++++++++++++++++++++++++ d2m/event-dispatcher.js | 8 +++- matrix/api.js | 1 + 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 d2m/discord-command-handler.js diff --git a/d2m/discord-command-handler.js b/d2m/discord-command-handler.js new file mode 100644 index 0000000..9f96c18 --- /dev/null +++ b/d2m/discord-command-handler.js @@ -0,0 +1,81 @@ +// @ts-check + +const assert = require("assert").strict +const util = require("util") +const DiscordTypes = require("discord-api-types/v10") +const {discord, sync, db} = require("../passthrough") +/** @type {import("../matrix/api")}) */ +const api = sync.require("../matrix/api") + +const prefix = "/" + +/** + * @callback CommandExecute + * @param {DiscordTypes.GatewayMessageCreateDispatchData} message + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuild} guild + * @param {any} [ctx] + */ + +/** + * @typedef Command + * @property {string[]} aliases + * @property {(message: DiscordTypes.GatewayMessageCreateDispatchData, channel: DiscordTypes.APIGuildTextChannel, guild: DiscordTypes.APIGuild) => Promise} execute + */ + +/** @param {CommandExecute} execute */ +function replyctx(execute) { + /** @type {CommandExecute} */ + return function(message, channel, guild, ctx = {}) { + ctx.message_reference = { + message_id: message.id, + channel_id: channel.id, + guild_id: guild.id, + fail_if_not_exists: false + } + return execute(message, channel, guild, ctx) + } +} + +/** @type {Command[]} */ +const commands = [{ + aliases: ["icon", "avatar", "roomicon", "roomavatar", "channelicon", "channelavatar"], + execute: replyctx( + async (message, channel, guild, ctx) => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + if (!roomID) return discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: "This channel isn't bridged to the other side." + }) + const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "") + const avatarURL = avatarEvent?.url + return discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: `Current room avatar: ${avatarURL}` + }) + } + ) +}, { + aliases: ["invite"], + execute: replyctx( + async (message, channel, guild, ctx) => { + discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: "This command isn't implemented yet." + }) + } + ) +}] + +/** @type {CommandExecute} */ +async function execute(message, channel, guild) { + if (!message.content.startsWith(prefix)) return + const words = message.content.split(" ") + const commandName = words[0] + const command = commands.find(c => c.aliases.includes(commandName)) + if (!command) return + + await command.execute(message, channel, guild) +} + +module.exports.execute = execute diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 9bb07c0..91a7cba 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -18,6 +18,8 @@ const createRoom = sync.require("./actions/create-room") const createSpace = sync.require("./actions/create-space") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") +/** @type {import("./discord-command-handler")}) */ +const discordCommandHandler = sync.require("./discord-command-handler") let lastReportedEvent = 0 @@ -156,7 +158,11 @@ module.exports = { if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) if (!isGuildAllowed(guild.id)) return - await sendMessage.sendMessage(message, guild) + + await Promise.all([ + sendMessage.sendMessage(message, guild), + discordCommandHandler.execute(message, channel, guild) + ]) }, /** diff --git a/matrix/api.js b/matrix/api.js index 2e0763e..7253a1a 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -190,6 +190,7 @@ module.exports.inviteToRoom = inviteToRoom module.exports.leaveRoom = leaveRoom module.exports.getEvent = getEvent module.exports.getAllState = getAllState +module.exports.getStateEvent = getStateEvent module.exports.getJoinedMembers = getJoinedMembers module.exports.sendState = sendState module.exports.sendEvent = sendEvent From c56e92ccfbbdb3c0948895cb056e4984a4c6f443 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 17:09:25 +1200 Subject: [PATCH 162/200] start adding command handlers --- d2m/discord-command-handler.js | 81 ++++++++++++++++++++++++++++++++++ d2m/event-dispatcher.js | 8 +++- matrix/api.js | 1 + 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 d2m/discord-command-handler.js diff --git a/d2m/discord-command-handler.js b/d2m/discord-command-handler.js new file mode 100644 index 0000000..9f96c18 --- /dev/null +++ b/d2m/discord-command-handler.js @@ -0,0 +1,81 @@ +// @ts-check + +const assert = require("assert").strict +const util = require("util") +const DiscordTypes = require("discord-api-types/v10") +const {discord, sync, db} = require("../passthrough") +/** @type {import("../matrix/api")}) */ +const api = sync.require("../matrix/api") + +const prefix = "/" + +/** + * @callback CommandExecute + * @param {DiscordTypes.GatewayMessageCreateDispatchData} message + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuild} guild + * @param {any} [ctx] + */ + +/** + * @typedef Command + * @property {string[]} aliases + * @property {(message: DiscordTypes.GatewayMessageCreateDispatchData, channel: DiscordTypes.APIGuildTextChannel, guild: DiscordTypes.APIGuild) => Promise} execute + */ + +/** @param {CommandExecute} execute */ +function replyctx(execute) { + /** @type {CommandExecute} */ + return function(message, channel, guild, ctx = {}) { + ctx.message_reference = { + message_id: message.id, + channel_id: channel.id, + guild_id: guild.id, + fail_if_not_exists: false + } + return execute(message, channel, guild, ctx) + } +} + +/** @type {Command[]} */ +const commands = [{ + aliases: ["icon", "avatar", "roomicon", "roomavatar", "channelicon", "channelavatar"], + execute: replyctx( + async (message, channel, guild, ctx) => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + if (!roomID) return discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: "This channel isn't bridged to the other side." + }) + const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "") + const avatarURL = avatarEvent?.url + return discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: `Current room avatar: ${avatarURL}` + }) + } + ) +}, { + aliases: ["invite"], + execute: replyctx( + async (message, channel, guild, ctx) => { + discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: "This command isn't implemented yet." + }) + } + ) +}] + +/** @type {CommandExecute} */ +async function execute(message, channel, guild) { + if (!message.content.startsWith(prefix)) return + const words = message.content.split(" ") + const commandName = words[0] + const command = commands.find(c => c.aliases.includes(commandName)) + if (!command) return + + await command.execute(message, channel, guild) +} + +module.exports.execute = execute diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 9bb07c0..91a7cba 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -18,6 +18,8 @@ const createRoom = sync.require("./actions/create-room") const createSpace = sync.require("./actions/create-space") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") +/** @type {import("./discord-command-handler")}) */ +const discordCommandHandler = sync.require("./discord-command-handler") let lastReportedEvent = 0 @@ -156,7 +158,11 @@ module.exports = { if (!channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) if (!isGuildAllowed(guild.id)) return - await sendMessage.sendMessage(message, guild) + + await Promise.all([ + sendMessage.sendMessage(message, guild), + discordCommandHandler.execute(message, channel, guild) + ]) }, /** diff --git a/matrix/api.js b/matrix/api.js index 2e0763e..7253a1a 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -190,6 +190,7 @@ module.exports.inviteToRoom = inviteToRoom module.exports.leaveRoom = leaveRoom module.exports.getEvent = getEvent module.exports.getAllState = getAllState +module.exports.getStateEvent = getStateEvent module.exports.getJoinedMembers = getJoinedMembers module.exports.sendState = sendState module.exports.sendEvent = sendEvent From e8c172a75386aa9f44aef8e49842c5b842b07a7c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 17:23:32 +1200 Subject: [PATCH 163/200] minor code coverage --- d2m/converters/user-to-mxid.js | 3 ++- m2d/converters/event-to-message.js | 2 +- matrix/txnid.test.js | 12 ++++++++++++ test/test.js | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 matrix/txnid.test.js diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js index 89e47a4..1fe8ffc 100644 --- a/d2m/converters/user-to-mxid.js +++ b/d2m/converters/user-to-mxid.js @@ -39,6 +39,7 @@ function* generateLocalpartAlternatives(preferences) { let i = 2 while (true) { yield best + (i++) + /* c8 ignore next */ } } @@ -69,7 +70,7 @@ function userToSimName(user) { for (const suggestion of generateLocalpartAlternatives(preferences)) { if (!matches.includes(suggestion)) return suggestion } - + /* c8 ignore next */ throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`) } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index b2c56a9..dde77b7 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -32,7 +32,7 @@ function eventToMessage(event) { }) } else if (event.content.msgtype === "m.emote") { messages.push({ - content: `*${displayName} ${event.content.body}*`, + content: `\* _${displayName} ${event.content.body}_`, username: displayName, avatar_url: avatarURL }) diff --git a/matrix/txnid.test.js b/matrix/txnid.test.js new file mode 100644 index 0000000..4db873c --- /dev/null +++ b/matrix/txnid.test.js @@ -0,0 +1,12 @@ +// @ts-check + +const {test} = require("supertape") +const txnid = require("./txnid") + +test("txnid: generates different values each run", t => { + const one = txnid.makeTxnId() + t.ok(one) + const two = txnid.makeTxnId() + t.ok(two) + t.notEqual(two, one) +}) diff --git a/test/test.js b/test/test.js index 606bd4b..e19f8ff 100644 --- a/test/test.js +++ b/test/test.js @@ -20,6 +20,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") +require("../matrix/txnid.test") require("../d2m/converters/message-to-event.test") require("../d2m/converters/message-to-event.embeds.test") require("../d2m/converters/edit-to-changes.test") From 1e9e9685c539565cca760fcc32e732b4d80dbab6 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 24 Aug 2023 17:23:32 +1200 Subject: [PATCH 164/200] minor code coverage --- d2m/converters/user-to-mxid.js | 3 ++- m2d/converters/event-to-message.js | 2 +- matrix/txnid.test.js | 12 ++++++++++++ test/test.js | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 matrix/txnid.test.js diff --git a/d2m/converters/user-to-mxid.js b/d2m/converters/user-to-mxid.js index 89e47a4..1fe8ffc 100644 --- a/d2m/converters/user-to-mxid.js +++ b/d2m/converters/user-to-mxid.js @@ -39,6 +39,7 @@ function* generateLocalpartAlternatives(preferences) { let i = 2 while (true) { yield best + (i++) + /* c8 ignore next */ } } @@ -69,7 +70,7 @@ function userToSimName(user) { for (const suggestion of generateLocalpartAlternatives(preferences)) { if (!matches.includes(suggestion)) return suggestion } - + /* c8 ignore next */ throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`) } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index b2c56a9..dde77b7 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -32,7 +32,7 @@ function eventToMessage(event) { }) } else if (event.content.msgtype === "m.emote") { messages.push({ - content: `*${displayName} ${event.content.body}*`, + content: `\* _${displayName} ${event.content.body}_`, username: displayName, avatar_url: avatarURL }) diff --git a/matrix/txnid.test.js b/matrix/txnid.test.js new file mode 100644 index 0000000..4db873c --- /dev/null +++ b/matrix/txnid.test.js @@ -0,0 +1,12 @@ +// @ts-check + +const {test} = require("supertape") +const txnid = require("./txnid") + +test("txnid: generates different values each run", t => { + const one = txnid.makeTxnId() + t.ok(one) + const two = txnid.makeTxnId() + t.ok(two) + t.notEqual(two, one) +}) diff --git a/test/test.js b/test/test.js index 606bd4b..e19f8ff 100644 --- a/test/test.js +++ b/test/test.js @@ -20,6 +20,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") +require("../matrix/txnid.test") require("../d2m/converters/message-to-event.test") require("../d2m/converters/message-to-event.embeds.test") require("../d2m/converters/edit-to-changes.test") From dc3a234038adc79bbcb1c6306b441fef98023ac7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 11:44:58 +1200 Subject: [PATCH 165/200] test on a member that has no member props --- d2m/actions/register-user.test.js | 22 ++++++++++++++++- db/data-for-test.sql | 3 ++- matrix/file.js | 12 +++++----- stdin.js | 1 + test/data.js | 40 +++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js index 34470ba..74818ea 100644 --- a/d2m/actions/register-user.test.js +++ b/d2m/actions/register-user.test.js @@ -3,7 +3,27 @@ const {_memberToStateContent} = require("./register-user") const {test} = require("supertape") const testData = require("../../test/data") -test("member2state: general", async t => { +test("member2state: without member nick or avatar", async t => { + t.deepEqual( + await _memberToStateContent(testData.member.kumaccino.user, testData.member.kumaccino, testData.guild.general.id), + { + avatar_url: "mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL", + displayname: "kumaccino", + membership: "join", + "moe.cadence.ooye.member": { + avatar: "/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024" + }, + "uk.half-shot.discord.member": { + bot: false, + displayColor: 10206929, + id: "113340068197859328", + username: "@kumaccino" + } + } + ) +}) + +test("member2state: with member nick and avatar", async t => { t.deepEqual( await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), { diff --git a/db/data-for-test.sql b/db/data-for-test.sql index ec9f9ec..e88b967 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -93,6 +93,7 @@ INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png', 'mxc://cadence.moe/KQYdXKRcHWjDYDLPkTOOWOjA'), ('https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg', 'mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa'), ('https://cdn.discordapp.com/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024', 'mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl'), -('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'); +('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'), +('https://cdn.discordapp.com/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024', 'mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL'); COMMIT; diff --git a/matrix/file.js b/matrix/file.js index 965ec1c..7d74d5d 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -27,15 +27,15 @@ async function uploadDiscordFileToMxc(path) { } // Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution - let existing = inflight.get(url) - if (typeof existing === "string") { - return existing + const existingInflight = inflight.get(url) + if (existingInflight) { + return existingInflight } // Has this file already been uploaded in the past? Grab the existing copy from the database. - existing = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) - if (typeof existing === "string") { - return existing + const existingFromDb = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) + if (typeof existingFromDb === "string") { + return existingFromDb } // Download from Discord diff --git a/stdin.js b/stdin.js index ce612f5..a687c6c 100644 --- a/stdin.js +++ b/stdin.js @@ -12,6 +12,7 @@ const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") +const file = sync.require("./matrix/file") const sendEvent = sync.require("./m2d/actions/send-event") const eventDispatcher = sync.require("./d2m/event-dispatcher") const ks = sync.require("./matrix/kstate") diff --git a/test/data.js b/test/data.js index 6ed2f42..a4d836d 100644 --- a/test/data.js +++ b/test/data.js @@ -98,6 +98,46 @@ module.exports = { } }, member: { + kumaccino: { + avatar: null, + communication_disabled_until: null, + flags: 0, + joined_at: "2015-11-11T09:55:40.321000+00:00", + nick: null, + pending: false, + premium_since: null, + roles: [ + "112767366235959296", "118924814567211009", + "199995902742626304", "204427286542417920", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1040735082610167858", + "695946570482450442", "849737964090556488" + ], + user: { + id: "113340068197859328", + username: "kumaccino", + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "0", + public_flags: 128, + flags: 128, + banner: null, + accent_color: 10206929, + global_name: "kumaccino", + avatar_decoration_data: null, + banner_color: "#9bbed1" + }, + mute: false, + deaf: false + }, sheep: { avatar: "38dd359aa12bcd52dd3164126c587f8c", communication_disabled_until: null, From beeb27bbd87498a18abfc06bcfca962531118433 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 11:44:58 +1200 Subject: [PATCH 166/200] test on a member that has no member props --- d2m/actions/register-user.test.js | 22 ++++++++++++++++- db/data-for-test.sql | 3 ++- matrix/file.js | 12 +++++----- stdin.js | 1 + test/data.js | 40 +++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js index 34470ba..74818ea 100644 --- a/d2m/actions/register-user.test.js +++ b/d2m/actions/register-user.test.js @@ -3,7 +3,27 @@ const {_memberToStateContent} = require("./register-user") const {test} = require("supertape") const testData = require("../../test/data") -test("member2state: general", async t => { +test("member2state: without member nick or avatar", async t => { + t.deepEqual( + await _memberToStateContent(testData.member.kumaccino.user, testData.member.kumaccino, testData.guild.general.id), + { + avatar_url: "mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL", + displayname: "kumaccino", + membership: "join", + "moe.cadence.ooye.member": { + avatar: "/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024" + }, + "uk.half-shot.discord.member": { + bot: false, + displayColor: 10206929, + id: "113340068197859328", + username: "@kumaccino" + } + } + ) +}) + +test("member2state: with member nick and avatar", async t => { t.deepEqual( await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), { diff --git a/db/data-for-test.sql b/db/data-for-test.sql index ec9f9ec..e88b967 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -93,6 +93,7 @@ INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png', 'mxc://cadence.moe/KQYdXKRcHWjDYDLPkTOOWOjA'), ('https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg', 'mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa'), ('https://cdn.discordapp.com/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024', 'mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl'), -('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'); +('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'), +('https://cdn.discordapp.com/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024', 'mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL'); COMMIT; diff --git a/matrix/file.js b/matrix/file.js index 965ec1c..7d74d5d 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -27,15 +27,15 @@ async function uploadDiscordFileToMxc(path) { } // Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution - let existing = inflight.get(url) - if (typeof existing === "string") { - return existing + const existingInflight = inflight.get(url) + if (existingInflight) { + return existingInflight } // Has this file already been uploaded in the past? Grab the existing copy from the database. - existing = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) - if (typeof existing === "string") { - return existing + const existingFromDb = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url) + if (typeof existingFromDb === "string") { + return existingFromDb } // Download from Discord diff --git a/stdin.js b/stdin.js index ce612f5..a687c6c 100644 --- a/stdin.js +++ b/stdin.js @@ -12,6 +12,7 @@ const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") +const file = sync.require("./matrix/file") const sendEvent = sync.require("./m2d/actions/send-event") const eventDispatcher = sync.require("./d2m/event-dispatcher") const ks = sync.require("./matrix/kstate") diff --git a/test/data.js b/test/data.js index 6ed2f42..a4d836d 100644 --- a/test/data.js +++ b/test/data.js @@ -98,6 +98,46 @@ module.exports = { } }, member: { + kumaccino: { + avatar: null, + communication_disabled_until: null, + flags: 0, + joined_at: "2015-11-11T09:55:40.321000+00:00", + nick: null, + pending: false, + premium_since: null, + roles: [ + "112767366235959296", "118924814567211009", + "199995902742626304", "204427286542417920", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1040735082610167858", + "695946570482450442", "849737964090556488" + ], + user: { + id: "113340068197859328", + username: "kumaccino", + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "0", + public_flags: 128, + flags: 128, + banner: null, + accent_color: 10206929, + global_name: "kumaccino", + avatar_decoration_data: null, + banner_color: "#9bbed1" + }, + mute: false, + deaf: false + }, sheep: { avatar: "38dd359aa12bcd52dd3164126c587f8c", communication_disabled_until: null, From d2c3e7eaa30aba08b43ccae425ad02c082d6dca6 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 12:05:16 +1200 Subject: [PATCH 167/200] channel name decoration for threads and voice-text --- d2m/actions/create-room.js | 11 ++++++++--- d2m/actions/create-room.test.js | 29 +++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index cb1bc85..6fbc42e 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -36,12 +36,17 @@ function applyKStateDiffToRoom(roomID, kstate) { } /** - * @param {{id: string, name: string, topic?: string?}} channel + * @param {{id: string, name: string, topic?: string?, type: number}} channel * @param {{id: string}} guild * @param {string?} customName */ function convertNameAndTopic(channel, guild, customName) { - const convertedName = customName || channel.name; + let channelPrefix = + ( channel.type === DiscordTypes.ChannelType.PublicThread ? "[⛓️] " + : channel.type === DiscordTypes.ChannelType.PrivateThread ? "[🔒⛓️] " + : channel.type === DiscordTypes.ChannelType.GuildVoice ? "[🔊] " + : "") + const chosenName = customName || (channelPrefix + channel.name); const maybeTopicWithPipe = channel.topic ? ` | ${channel.topic}` : ''; const maybeTopicWithNewlines = channel.topic ? `${channel.topic}\n\n` : ''; const channelIDPart = `Channel ID: ${channel.id}`; @@ -51,7 +56,7 @@ function convertNameAndTopic(channel, guild, customName) { ? `#${channel.name}${maybeTopicWithPipe}\n\n${channelIDPart}\n${guildIDPart}` : `${maybeTopicWithNewlines}${channelIDPart}\n${guildIDPart}`; - return [convertedName, convertedTopic]; + return [chosenName, convertedTopic]; } /** diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js index ec5c3d3..e40bf6f 100644 --- a/d2m/actions/create-room.test.js +++ b/d2m/actions/create-room.test.js @@ -14,28 +14,49 @@ test("channel2room: general", async t => { test("convertNameAndTopic: custom name and topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, "hauntings"), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"), ["hauntings", "#the-twilight-zone | Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] ) }) test("convertNameAndTopic: custom name, no topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, "hauntings"), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, "hauntings"), ["hauntings", "#the-twilight-zone\n\nChannel ID: 123\nGuild ID: 456"] ) }) test("convertNameAndTopic: original name and topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, null), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, null), ["the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] ) }) test("convertNameAndTopic: original name, no topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, null), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, null), ["the-twilight-zone", "Channel ID: 123\nGuild ID: 456"] ) }) + +test("convertNameAndTopic: public thread icon", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 11}, {id: "456"}, null), + ["[⛓️] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: private thread icon", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 12}, {id: "456"}, null), + ["[🔒⛓️] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: voice channel icon", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 2}, {id: "456"}, null), + ["[🔊] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) From 30bf87b106a94a0dc64ad87b851baf21111e3414 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 12:05:16 +1200 Subject: [PATCH 168/200] channel name decoration for threads and voice-text --- d2m/actions/create-room.js | 11 ++++++++--- d2m/actions/create-room.test.js | 29 +++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index cb1bc85..6fbc42e 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -36,12 +36,17 @@ function applyKStateDiffToRoom(roomID, kstate) { } /** - * @param {{id: string, name: string, topic?: string?}} channel + * @param {{id: string, name: string, topic?: string?, type: number}} channel * @param {{id: string}} guild * @param {string?} customName */ function convertNameAndTopic(channel, guild, customName) { - const convertedName = customName || channel.name; + let channelPrefix = + ( channel.type === DiscordTypes.ChannelType.PublicThread ? "[⛓️] " + : channel.type === DiscordTypes.ChannelType.PrivateThread ? "[🔒⛓️] " + : channel.type === DiscordTypes.ChannelType.GuildVoice ? "[🔊] " + : "") + const chosenName = customName || (channelPrefix + channel.name); const maybeTopicWithPipe = channel.topic ? ` | ${channel.topic}` : ''; const maybeTopicWithNewlines = channel.topic ? `${channel.topic}\n\n` : ''; const channelIDPart = `Channel ID: ${channel.id}`; @@ -51,7 +56,7 @@ function convertNameAndTopic(channel, guild, customName) { ? `#${channel.name}${maybeTopicWithPipe}\n\n${channelIDPart}\n${guildIDPart}` : `${maybeTopicWithNewlines}${channelIDPart}\n${guildIDPart}`; - return [convertedName, convertedTopic]; + return [chosenName, convertedTopic]; } /** diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js index ec5c3d3..e40bf6f 100644 --- a/d2m/actions/create-room.test.js +++ b/d2m/actions/create-room.test.js @@ -14,28 +14,49 @@ test("channel2room: general", async t => { test("convertNameAndTopic: custom name and topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, "hauntings"), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"), ["hauntings", "#the-twilight-zone | Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] ) }) test("convertNameAndTopic: custom name, no topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, "hauntings"), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, "hauntings"), ["hauntings", "#the-twilight-zone\n\nChannel ID: 123\nGuild ID: 456"] ) }) test("convertNameAndTopic: original name and topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, null), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, null), ["the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] ) }) test("convertNameAndTopic: original name, no topic", t => { t.deepEqual( - _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, null), + _convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, null), ["the-twilight-zone", "Channel ID: 123\nGuild ID: 456"] ) }) + +test("convertNameAndTopic: public thread icon", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 11}, {id: "456"}, null), + ["[⛓️] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: private thread icon", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 12}, {id: "456"}, null), + ["[🔒⛓️] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: voice channel icon", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 2}, {id: "456"}, null), + ["[🔊] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) From d3d9195f72cce90a460e086de1a526b00c9d39c2 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 16:01:19 +1200 Subject: [PATCH 169/200] implemented //icon with button confirmation system --- d2m/discord-command-handler.js | 77 ++++++++++++++++++++++++++++++---- d2m/event-dispatcher.js | 1 + 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/d2m/discord-command-handler.js b/d2m/discord-command-handler.js index 9f96c18..1bd52c8 100644 --- a/d2m/discord-command-handler.js +++ b/d2m/discord-command-handler.js @@ -6,15 +6,48 @@ const DiscordTypes = require("discord-api-types/v10") const {discord, sync, db} = require("../passthrough") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") +/** @type {import("../matrix/file")} */ +const file = sync.require("../matrix/file") -const prefix = "/" +const PREFIX = "//" + +let buttons = [] + +/** + * @param {string} channelID where to add the button + * @param {string} messageID where to add the button + * @param {string} emoji emoji to add as a button + * @param {string} userID only listen for responses from this user + * @returns {Promise} + */ +async function addButton(channelID, messageID, emoji, userID) { + await discord.snow.channel.createReaction(channelID, messageID, emoji) + return new Promise(resolve => { + buttons.push({channelID, messageID, userID, resolve, created: Date.now()}) + }) +} + +// Clear out old buttons every so often to free memory +setInterval(() => { + const now = Date.now() + buttons = buttons.filter(b => now - b.created < 2*60*60*1000) +}, 10*60*1000) + +/** @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ +function onReactionAdd(data) { + const button = buttons.find(b => b.channelID === data.channel_id && b.messageID === data.message_id && b.userID === data.user_id) + if (button) { + buttons = buttons.filter(b => b !== button) // remove button data so it can't be clicked again + button.resolve(data) + } +} /** * @callback CommandExecute * @param {DiscordTypes.GatewayMessageCreateDispatchData} message * @param {DiscordTypes.APIGuildTextChannel} channel * @param {DiscordTypes.APIGuild} guild - * @param {any} [ctx] + * @param {Partial} [ctx] */ /** @@ -42,24 +75,51 @@ const commands = [{ aliases: ["icon", "avatar", "roomicon", "roomavatar", "channelicon", "channelavatar"], execute: replyctx( async (message, channel, guild, ctx) => { + // Guard const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) if (!roomID) return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "This channel isn't bridged to the other side." }) + + // Current avatar const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "") - const avatarURL = avatarEvent?.url - return discord.snow.channel.createMessage(channel.id, { + const avatarURLParts = avatarEvent?.url.match(/^mxc:\/\/([^/]+)\/(\w+)$/) + let currentAvatarMessage = + ( avatarURLParts ? `Current room-specific avatar: https://matrix.cadence.moe/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}` + : "No avatar. Now's your time to strike. Use `//icon` again with a link or upload to set the room-specific avatar.") + + // Next potential avatar + const nextAvatarURL = message.attachments.find(a => a.content_type?.startsWith("image/"))?.url || message.content.match(/https?:\/\/[^ ]+\.[^ ]+\.(?:png|jpg|jpeg|webp)\b/)?.[0] + let nextAvatarMessage = + ( nextAvatarURL ? `\nYou want to set it to: ${nextAvatarURL}\nHit ✅ to make it happen.` + : "") + + const sent = await discord.snow.channel.createMessage(channel.id, { ...ctx, - content: `Current room avatar: ${avatarURL}` + content: currentAvatarMessage + nextAvatarMessage }) + + if (nextAvatarURL) { + addButton(channel.id, sent.id, "✅", message.author.id).then(async data => { + const mxcUrl = await file.uploadDiscordFileToMxc(nextAvatarURL) + await api.sendState(roomID, "m.room.avatar", "", { + url: mxcUrl + }) + db.prepare("UPDATE channel_room SET custom_avatar = ? WHERE channel_id = ?").run(mxcUrl, channel.id) + await discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: "Your creation is unleashed. Any complaints will be redirected to Grelbo." + }) + }) + } } ) }, { aliases: ["invite"], execute: replyctx( async (message, channel, guild, ctx) => { - discord.snow.channel.createMessage(channel.id, { + return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "This command isn't implemented yet." }) @@ -69,8 +129,8 @@ const commands = [{ /** @type {CommandExecute} */ async function execute(message, channel, guild) { - if (!message.content.startsWith(prefix)) return - const words = message.content.split(" ") + if (!message.content.startsWith(PREFIX)) return + const words = message.content.slice(PREFIX.length).split(" ") const commandName = words[0] const command = commands.find(c => c.aliases.includes(commandName)) if (!command) return @@ -79,3 +139,4 @@ async function execute(message, channel, guild) { } module.exports.execute = execute +module.exports.onReactionAdd = onReactionAdd diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 91a7cba..bf9bbd2 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -197,6 +197,7 @@ module.exports = { */ async onReactionAdd(client, data) { if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. + discordCommandHandler.onReactionAdd(data) if (data.emoji.id !== null) return // TODO: image emoji reactions await addReaction.addReaction(data) }, From 69a01a0608966492639bbc7da8656ea3d4a39e02 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 16:01:19 +1200 Subject: [PATCH 170/200] implemented //icon with button confirmation system --- d2m/discord-command-handler.js | 77 ++++++++++++++++++++++++++++++---- d2m/event-dispatcher.js | 1 + 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/d2m/discord-command-handler.js b/d2m/discord-command-handler.js index 9f96c18..1bd52c8 100644 --- a/d2m/discord-command-handler.js +++ b/d2m/discord-command-handler.js @@ -6,15 +6,48 @@ const DiscordTypes = require("discord-api-types/v10") const {discord, sync, db} = require("../passthrough") /** @type {import("../matrix/api")}) */ const api = sync.require("../matrix/api") +/** @type {import("../matrix/file")} */ +const file = sync.require("../matrix/file") -const prefix = "/" +const PREFIX = "//" + +let buttons = [] + +/** + * @param {string} channelID where to add the button + * @param {string} messageID where to add the button + * @param {string} emoji emoji to add as a button + * @param {string} userID only listen for responses from this user + * @returns {Promise} + */ +async function addButton(channelID, messageID, emoji, userID) { + await discord.snow.channel.createReaction(channelID, messageID, emoji) + return new Promise(resolve => { + buttons.push({channelID, messageID, userID, resolve, created: Date.now()}) + }) +} + +// Clear out old buttons every so often to free memory +setInterval(() => { + const now = Date.now() + buttons = buttons.filter(b => now - b.created < 2*60*60*1000) +}, 10*60*1000) + +/** @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ +function onReactionAdd(data) { + const button = buttons.find(b => b.channelID === data.channel_id && b.messageID === data.message_id && b.userID === data.user_id) + if (button) { + buttons = buttons.filter(b => b !== button) // remove button data so it can't be clicked again + button.resolve(data) + } +} /** * @callback CommandExecute * @param {DiscordTypes.GatewayMessageCreateDispatchData} message * @param {DiscordTypes.APIGuildTextChannel} channel * @param {DiscordTypes.APIGuild} guild - * @param {any} [ctx] + * @param {Partial} [ctx] */ /** @@ -42,24 +75,51 @@ const commands = [{ aliases: ["icon", "avatar", "roomicon", "roomavatar", "channelicon", "channelavatar"], execute: replyctx( async (message, channel, guild, ctx) => { + // Guard const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) if (!roomID) return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "This channel isn't bridged to the other side." }) + + // Current avatar const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "") - const avatarURL = avatarEvent?.url - return discord.snow.channel.createMessage(channel.id, { + const avatarURLParts = avatarEvent?.url.match(/^mxc:\/\/([^/]+)\/(\w+)$/) + let currentAvatarMessage = + ( avatarURLParts ? `Current room-specific avatar: https://matrix.cadence.moe/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}` + : "No avatar. Now's your time to strike. Use `//icon` again with a link or upload to set the room-specific avatar.") + + // Next potential avatar + const nextAvatarURL = message.attachments.find(a => a.content_type?.startsWith("image/"))?.url || message.content.match(/https?:\/\/[^ ]+\.[^ ]+\.(?:png|jpg|jpeg|webp)\b/)?.[0] + let nextAvatarMessage = + ( nextAvatarURL ? `\nYou want to set it to: ${nextAvatarURL}\nHit ✅ to make it happen.` + : "") + + const sent = await discord.snow.channel.createMessage(channel.id, { ...ctx, - content: `Current room avatar: ${avatarURL}` + content: currentAvatarMessage + nextAvatarMessage }) + + if (nextAvatarURL) { + addButton(channel.id, sent.id, "✅", message.author.id).then(async data => { + const mxcUrl = await file.uploadDiscordFileToMxc(nextAvatarURL) + await api.sendState(roomID, "m.room.avatar", "", { + url: mxcUrl + }) + db.prepare("UPDATE channel_room SET custom_avatar = ? WHERE channel_id = ?").run(mxcUrl, channel.id) + await discord.snow.channel.createMessage(channel.id, { + ...ctx, + content: "Your creation is unleashed. Any complaints will be redirected to Grelbo." + }) + }) + } } ) }, { aliases: ["invite"], execute: replyctx( async (message, channel, guild, ctx) => { - discord.snow.channel.createMessage(channel.id, { + return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "This command isn't implemented yet." }) @@ -69,8 +129,8 @@ const commands = [{ /** @type {CommandExecute} */ async function execute(message, channel, guild) { - if (!message.content.startsWith(prefix)) return - const words = message.content.split(" ") + if (!message.content.startsWith(PREFIX)) return + const words = message.content.slice(PREFIX.length).split(" ") const commandName = words[0] const command = commands.find(c => c.aliases.includes(commandName)) if (!command) return @@ -79,3 +139,4 @@ async function execute(message, channel, guild) { } module.exports.execute = execute +module.exports.onReactionAdd = onReactionAdd diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 91a7cba..bf9bbd2 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -197,6 +197,7 @@ module.exports = { */ async onReactionAdd(client, data) { if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. + discordCommandHandler.onReactionAdd(data) if (data.emoji.id !== null) return // TODO: image emoji reactions await addReaction.addReaction(data) }, From e9fe2502114592ec8b2e7aa15060f219dd900029 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 16:03:43 +1200 Subject: [PATCH 171/200] preserve order: finish bridge before command reply --- d2m/event-dispatcher.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index bf9bbd2..6939c59 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -159,10 +159,8 @@ module.exports = { const guild = client.guilds.get(channel.guild_id) if (!isGuildAllowed(guild.id)) return - await Promise.all([ - sendMessage.sendMessage(message, guild), - discordCommandHandler.execute(message, channel, guild) - ]) + await sendMessage.sendMessage(message, guild), + await discordCommandHandler.execute(message, channel, guild) }, /** From 9f717dc24fb701cbccf7e525785c1514c85366b9 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 16:03:43 +1200 Subject: [PATCH 172/200] preserve order: finish bridge before command reply --- d2m/event-dispatcher.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index bf9bbd2..6939c59 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -159,10 +159,8 @@ module.exports = { const guild = client.guilds.get(channel.guild_id) if (!isGuildAllowed(guild.id)) return - await Promise.all([ - sendMessage.sendMessage(message, guild), - discordCommandHandler.execute(message, channel, guild) - ]) + await sendMessage.sendMessage(message, guild), + await discordCommandHandler.execute(message, channel, guild) }, /** From 1643a46812ed5b96f568e9712ea3bd64750fbf3e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 17:23:51 +1200 Subject: [PATCH 173/200] sync child room avatars when guild is updated --- d2m/actions/create-room.js | 27 ++++++++++++++++----------- d2m/actions/create-space.js | 24 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 6fbc42e..2095130 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -17,6 +17,7 @@ const ks = sync.require("../../matrix/kstate") const inflightRoomCreate = new Map() /** + * Async because it gets all room state from the homeserver. * @param {string} roomID */ async function roomToKState(roomID) { @@ -60,6 +61,7 @@ function convertNameAndTopic(channel, guild, customName) { } /** + * Async because it may upload the guild icon to mxc. * @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel * @param {DiscordTypes.APIGuild} guild */ @@ -200,10 +202,10 @@ function channelToGuild(channel) { 3. Get kstate for channel 4. Create room, return new ID - New combined flow with ensure / sync: + Ensure + sync flow: 1. Get IDs 2. Does room exist? - 2.5: If room does exist AND don't need to sync: return here + 2.5: If room does exist AND wasn't asked to sync: return here 3. Get kstate for channel 4. Create room with kstate if room doesn't exist 5. Get and update room state with kstate if room does exist @@ -246,7 +248,7 @@ async function _syncRoom(channelID, shouldActuallySync) { console.log(`[room sync] to matrix: ${channel.name}`) - const {spaceID, channelKState} = await channelToKState(channel, guild) + const {spaceID, channelKState} = await channelToKState(channel, guild) // calling this in both branches because we don't want to calculate this if not syncing // sync channel state to room const roomKState = await roomToKState(roomID) @@ -261,6 +263,16 @@ async function _syncRoom(channelID, shouldActuallySync) { return roomID } +/** Ensures the room exists. If it doesn't, creates the room with an accurate initial state. */ +function ensureRoom(channelID) { + return _syncRoom(channelID, false) +} + +/** Actually syncs. Gets all room state from the homeserver in order to diff, and uploads the icon to mxc if it has changed. */ +function syncRoom(channelID) { + return _syncRoom(channelID, true) +} + async function _unbridgeRoom(channelID) { /** @ts-ignore @type {DiscordTypes.APIGuildChannel} */ const channel = discord.channels.get(channelID) @@ -289,6 +301,7 @@ async function _unbridgeRoom(channelID) { /** + * Async because it gets all space state from the homeserver, then if necessary sends one state event back. * @param {DiscordTypes.APIGuildTextChannel} channel * @param {string} spaceID * @param {string} roomID @@ -311,14 +324,6 @@ async function _syncSpaceMember(channel, spaceID, roomID) { return applyKStateDiffToRoom(spaceID, spaceDiff) } -function ensureRoom(channelID) { - return _syncRoom(channelID, false) -} - -function syncRoom(channelID) { - return _syncRoom(channelID, true) -} - async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 46fa71f..838bef9 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -1,6 +1,6 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -84,11 +84,31 @@ async function syncSpace(guildID) { console.log(`[space sync] to matrix: ${guild.name}`) - // sync channel state to room + // sync guild state to space const spaceKState = await createRoom.roomToKState(spaceID) const spaceDiff = ks.diffKState(spaceKState, guildKState) await createRoom.applyKStateDiffToRoom(spaceID, spaceDiff) + // guild icon was changed, so room avatars need to be updated as well as the space ones + // doing it this way rather than calling syncRoom for great efficiency gains + const newAvatarState = spaceDiff["m.room.avatar/"] + if (guild.icon && newAvatarState?.url) { + // don't try to update rooms with custom avatars though + const roomsWithCustomAvatars = db.prepare("SELECT room_id FROM channel_room WHERE custom_avatar IS NOT NULL").pluck().all() + + const childRooms = ks.kstateToState(spaceKState).filter(({type, state_key, content}) => { + return type === "m.space.child" && "via" in content && roomsWithCustomAvatars.includes(state_key) + }).map(({state_key}) => state_key) + + for (const roomID of childRooms) { + const avatarEventContent = await api.getStateEvent(roomID, "m.room.avatar", "") + if (avatarEventContent.url !== newAvatarState.url) { + await api.sendState(roomID, "m.room.avatar", "", newAvatarState) + } + } + } + + return spaceID } From 20cd7ab38e8177edbf742484f1e8db0d6ddd268a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 17:23:51 +1200 Subject: [PATCH 174/200] sync child room avatars when guild is updated --- d2m/actions/create-room.js | 27 ++++++++++++++++----------- d2m/actions/create-space.js | 24 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 6fbc42e..2095130 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -17,6 +17,7 @@ const ks = sync.require("../../matrix/kstate") const inflightRoomCreate = new Map() /** + * Async because it gets all room state from the homeserver. * @param {string} roomID */ async function roomToKState(roomID) { @@ -60,6 +61,7 @@ function convertNameAndTopic(channel, guild, customName) { } /** + * Async because it may upload the guild icon to mxc. * @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel * @param {DiscordTypes.APIGuild} guild */ @@ -200,10 +202,10 @@ function channelToGuild(channel) { 3. Get kstate for channel 4. Create room, return new ID - New combined flow with ensure / sync: + Ensure + sync flow: 1. Get IDs 2. Does room exist? - 2.5: If room does exist AND don't need to sync: return here + 2.5: If room does exist AND wasn't asked to sync: return here 3. Get kstate for channel 4. Create room with kstate if room doesn't exist 5. Get and update room state with kstate if room does exist @@ -246,7 +248,7 @@ async function _syncRoom(channelID, shouldActuallySync) { console.log(`[room sync] to matrix: ${channel.name}`) - const {spaceID, channelKState} = await channelToKState(channel, guild) + const {spaceID, channelKState} = await channelToKState(channel, guild) // calling this in both branches because we don't want to calculate this if not syncing // sync channel state to room const roomKState = await roomToKState(roomID) @@ -261,6 +263,16 @@ async function _syncRoom(channelID, shouldActuallySync) { return roomID } +/** Ensures the room exists. If it doesn't, creates the room with an accurate initial state. */ +function ensureRoom(channelID) { + return _syncRoom(channelID, false) +} + +/** Actually syncs. Gets all room state from the homeserver in order to diff, and uploads the icon to mxc if it has changed. */ +function syncRoom(channelID) { + return _syncRoom(channelID, true) +} + async function _unbridgeRoom(channelID) { /** @ts-ignore @type {DiscordTypes.APIGuildChannel} */ const channel = discord.channels.get(channelID) @@ -289,6 +301,7 @@ async function _unbridgeRoom(channelID) { /** + * Async because it gets all space state from the homeserver, then if necessary sends one state event back. * @param {DiscordTypes.APIGuildTextChannel} channel * @param {string} spaceID * @param {string} roomID @@ -311,14 +324,6 @@ async function _syncSpaceMember(channel, spaceID, roomID) { return applyKStateDiffToRoom(spaceID, spaceDiff) } -function ensureRoom(channelID) { - return _syncRoom(channelID, false) -} - -function syncRoom(channelID) { - return _syncRoom(channelID, true) -} - async function createAllForGuild(guildID) { const channelIDs = discord.guildChannelMap.get(guildID) assert.ok(channelIDs) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 46fa71f..838bef9 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -1,6 +1,6 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") @@ -84,11 +84,31 @@ async function syncSpace(guildID) { console.log(`[space sync] to matrix: ${guild.name}`) - // sync channel state to room + // sync guild state to space const spaceKState = await createRoom.roomToKState(spaceID) const spaceDiff = ks.diffKState(spaceKState, guildKState) await createRoom.applyKStateDiffToRoom(spaceID, spaceDiff) + // guild icon was changed, so room avatars need to be updated as well as the space ones + // doing it this way rather than calling syncRoom for great efficiency gains + const newAvatarState = spaceDiff["m.room.avatar/"] + if (guild.icon && newAvatarState?.url) { + // don't try to update rooms with custom avatars though + const roomsWithCustomAvatars = db.prepare("SELECT room_id FROM channel_room WHERE custom_avatar IS NOT NULL").pluck().all() + + const childRooms = ks.kstateToState(spaceKState).filter(({type, state_key, content}) => { + return type === "m.space.child" && "via" in content && roomsWithCustomAvatars.includes(state_key) + }).map(({state_key}) => state_key) + + for (const roomID of childRooms) { + const avatarEventContent = await api.getStateEvent(roomID, "m.room.avatar", "") + if (avatarEventContent.url !== newAvatarState.url) { + await api.sendState(roomID, "m.room.avatar", "", newAvatarState) + } + } + } + + return spaceID } From 712a525fbff1d6e9eb5a60ad3c5d9703a36cabbd Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 17:35:34 +1200 Subject: [PATCH 175/200] bug fix :( --- d2m/actions/create-space.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 838bef9..87e25eb 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -97,7 +97,7 @@ async function syncSpace(guildID) { const roomsWithCustomAvatars = db.prepare("SELECT room_id FROM channel_room WHERE custom_avatar IS NOT NULL").pluck().all() const childRooms = ks.kstateToState(spaceKState).filter(({type, state_key, content}) => { - return type === "m.space.child" && "via" in content && roomsWithCustomAvatars.includes(state_key) + return type === "m.space.child" && "via" in content && !roomsWithCustomAvatars.includes(state_key) }).map(({state_key}) => state_key) for (const roomID of childRooms) { @@ -108,7 +108,6 @@ async function syncSpace(guildID) { } } - return spaceID } From c4bc07986510af811bfb6f40f0fedd39017f82db Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 17:35:34 +1200 Subject: [PATCH 176/200] bug fix :( --- d2m/actions/create-space.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 838bef9..87e25eb 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -97,7 +97,7 @@ async function syncSpace(guildID) { const roomsWithCustomAvatars = db.prepare("SELECT room_id FROM channel_room WHERE custom_avatar IS NOT NULL").pluck().all() const childRooms = ks.kstateToState(spaceKState).filter(({type, state_key, content}) => { - return type === "m.space.child" && "via" in content && roomsWithCustomAvatars.includes(state_key) + return type === "m.space.child" && "via" in content && !roomsWithCustomAvatars.includes(state_key) }).map(({state_key}) => state_key) for (const roomID of childRooms) { @@ -108,7 +108,6 @@ async function syncSpace(guildID) { } } - return spaceID } From 7186b824f5e6d4ad01b7dd01b79a6d3839ddbd70 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 23:27:44 +1200 Subject: [PATCH 177/200] updating tap-dot for prettier output --- d2m/actions/create-room.js | 1 + package-lock.json | 17 +++++++++-------- package.json | 2 +- types.d.ts | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 2095130..e778d22 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -283,6 +283,7 @@ async function _unbridgeRoom(channelID) { assert.ok(spaceID) // remove room from being a space member + await api.sendState(roomID, "m.space.parent", spaceID, {}) await api.sendState(spaceID, "m.space.child", roomID, {}) // send a notification in the room diff --git a/package-lock.json b/package-lock.json index e808e1f..4d32bf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "cross-env": "^7.0.3", "discord-api-types": "^0.37.53", "supertape": "^8.3.0", - "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" + "tap-dot": "github:cloudrac3r/tap-dot#9dd7750ececeae3a96afba91905be812b6b2cc2d" } }, "node_modules/@babel/runtime": { @@ -2518,15 +2518,16 @@ "dev": true }, "node_modules/readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", "dev": true, "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", - "process": "^0.11.10" + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3043,8 +3044,8 @@ }, "node_modules/tap-dot": { "version": "2.0.0", - "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "integrity": "sha512-nhpVoX/s4IJJdm7OymbZ1rdZNlqt3l/yQ9Z9if06jcgRNto6QAZOrLIvdCILYQ6GE0mu+cyVA8s24amdwbvHiQ==", + "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#9dd7750ececeae3a96afba91905be812b6b2cc2d", + "integrity": "sha512-SLg6KF3cSkKII+5hA/we9FjnMCrL5uk0wYap7RXD9KJziy7xqZolvEOamt3CJlm5LSzRXIGblm3nmhY/EBE3AA==", "dev": true, "license": "MIT", "dependencies": { @@ -3058,7 +3059,7 @@ "node_modules/tap-out": { "version": "3.2.1", "resolved": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "integrity": "sha512-hyMMeN6jagEyeEOq7Xyg3GNIAR3iUDDocaoK5QRPjnEGbFZOYJ39Dkn7BsFUXyGVl+s4b3zPkDcTS38+6KTXCQ==", + "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 67aeade..155bf2e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "cross-env": "^7.0.3", "discord-api-types": "^0.37.53", "supertape": "^8.3.0", - "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" + "tap-dot": "github:cloudrac3r/tap-dot#9dd7750ececeae3a96afba91905be812b6b2cc2d" }, "scripts": { "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot", diff --git a/types.d.ts b/types.d.ts index 2ba8d1d..e9b8a7a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -69,8 +69,8 @@ export namespace Event { export type M_Room_Message = { msgtype: "m.text" body: string - formatted_body?: "org.matrix.custom.html" - format?: string + format?: "org.matrix.custom.html" + formatted_body?: string } export type M_Room_Member = { From 27b8c547e3a71b30fb7f7d781635931d2c9b2098 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 25 Aug 2023 23:27:44 +1200 Subject: [PATCH 178/200] updating tap-dot for prettier output --- d2m/actions/create-room.js | 1 + package-lock.json | 17 +++++++++-------- package.json | 2 +- types.d.ts | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 2095130..e778d22 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -283,6 +283,7 @@ async function _unbridgeRoom(channelID) { assert.ok(spaceID) // remove room from being a space member + await api.sendState(roomID, "m.space.parent", spaceID, {}) await api.sendState(spaceID, "m.space.child", roomID, {}) // send a notification in the room diff --git a/package-lock.json b/package-lock.json index e808e1f..4d32bf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "cross-env": "^7.0.3", "discord-api-types": "^0.37.53", "supertape": "^8.3.0", - "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" + "tap-dot": "github:cloudrac3r/tap-dot#9dd7750ececeae3a96afba91905be812b6b2cc2d" } }, "node_modules/@babel/runtime": { @@ -2518,15 +2518,16 @@ "dev": true }, "node_modules/readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", "dev": true, "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", - "process": "^0.11.10" + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3043,8 +3044,8 @@ }, "node_modules/tap-dot": { "version": "2.0.0", - "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#223a4e67a6f7daf015506a12a7af74605f06c7f4", - "integrity": "sha512-nhpVoX/s4IJJdm7OymbZ1rdZNlqt3l/yQ9Z9if06jcgRNto6QAZOrLIvdCILYQ6GE0mu+cyVA8s24amdwbvHiQ==", + "resolved": "git+ssh://git@github.com/cloudrac3r/tap-dot.git#9dd7750ececeae3a96afba91905be812b6b2cc2d", + "integrity": "sha512-SLg6KF3cSkKII+5hA/we9FjnMCrL5uk0wYap7RXD9KJziy7xqZolvEOamt3CJlm5LSzRXIGblm3nmhY/EBE3AA==", "dev": true, "license": "MIT", "dependencies": { @@ -3058,7 +3059,7 @@ "node_modules/tap-out": { "version": "3.2.1", "resolved": "git+ssh://git@github.com/cloudrac3r/tap-out.git#1b4ec6084aedb9f44ccaa0c7185ff9bfd83da771", - "integrity": "sha512-hyMMeN6jagEyeEOq7Xyg3GNIAR3iUDDocaoK5QRPjnEGbFZOYJ39Dkn7BsFUXyGVl+s4b3zPkDcTS38+6KTXCQ==", + "integrity": "sha512-55eUSaX5AeEOqJMRlj9XSqUlLV/yYPOPeC3kOFqjmorq6/jlH5kIeqpgLNW5PlPEAuggzYREYYXqrN8E37ZPfQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 67aeade..155bf2e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "cross-env": "^7.0.3", "discord-api-types": "^0.37.53", "supertape": "^8.3.0", - "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" + "tap-dot": "github:cloudrac3r/tap-dot#9dd7750ececeae3a96afba91905be812b6b2cc2d" }, "scripts": { "test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot", diff --git a/types.d.ts b/types.d.ts index 2ba8d1d..e9b8a7a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -69,8 +69,8 @@ export namespace Event { export type M_Room_Message = { msgtype: "m.text" body: string - formatted_body?: "org.matrix.custom.html" - format?: string + format?: "org.matrix.custom.html" + formatted_body?: string } export type M_Room_Member = { From 4a5e76c4933f0c3758d219c9b42103a2c715b25b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 01:43:17 +1200 Subject: [PATCH 179/200] add turndown for m->d formatting --- m2d/converters/event-to-message.js | 79 ++++++++-- m2d/converters/event-to-message.test.js | 191 ++++++++++++++++++++++++ package-lock.json | 37 ++++- package.json | 4 +- types.d.ts | 2 +- 5 files changed, 297 insertions(+), 16 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index dde77b7..f793f85 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -2,19 +2,41 @@ const Ty = require("../../types") const DiscordTypes = require("discord-api-types/v10") -const markdown = require("discord-markdown") +const chunk = require("chunk-text") +const TurndownService = require("turndown") const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +// https://github.com/mixmark-io/turndown/blob/97e4535ca76bb2e70d9caa2aa4d4686956b06d44/src/utilities.js#L26C28-L33C2 +const BLOCK_ELEMENTS = [ + "ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", + "CENTER", "DD", "DETAILS", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", + "FOOTER", "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", + "HGROUP", "HR", "HTML", "ISINDEX", "LI", "MAIN", "MENU", "NAV", "NOFRAMES", + "NOSCRIPT", "OL", "OUTPUT", "P", "PRE", "SECTION", "SUMMARY", "TABLE", "TBODY", "TD", + "TFOOT", "TH", "THEAD", "TR", "UL" +] + +const turndownService = new TurndownService({ + hr: "----" +}) + +turndownService.addRule("strikethrough", { + filter: ["del", "s", "strike"], + replacement: function (content) { + return "~~" + content + "~~" + } +}) + /** * @param {Ty.Event.Outer} event */ function eventToMessage(event) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ - const messages = [] + let messages = [] let displayName = event.sender let avatarURL = undefined @@ -24,20 +46,51 @@ function eventToMessage(event) { // TODO: get the media repo domain and the avatar url from the matrix member event } - if (event.content.msgtype === "m.text") { - messages.push({ - content: event.content.body, - username: displayName, - avatar_url: avatarURL - }) - } else if (event.content.msgtype === "m.emote") { - messages.push({ - content: `\* _${displayName} ${event.content.body}_`, - username: displayName, - avatar_url: avatarURL + // Convert content depending on what the message is + let content = event.content.body // ultimate fallback + if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) { + let input = event.content.formatted_body + if (event.content.msgtype === "m.emote") { + input = `* ${displayName} ${input}` + } + + // Note: Element's renderers on Web and Android currently collapse whitespace, like the browser does. Turndown also collapses whitespace which is good for me. + // If later I'm using a client that doesn't collapse whitespace and I want turndown to follow suit, uncomment the following line of code, and it Just Works: + // input = input.replace(/ /g, " ") + // There is also a corresponding test to uncomment, named "event2message: whitespace is retained" + + // The matrix spec hasn't decided whether \n counts as a newline or not, but I'm going to count it, because if it's in the data it's there for a reason. + // But I should not count it if it's between block elements. + input = input.replace(/(<\/?([^ >]+)[^>]*>)?\n(<\/?([^ >]+)[^>]*>)?/g, (whole, beforeContext, beforeTag, afterContext, afterTag) => { + if (typeof beforeTag !== "string" && typeof afterTag !== "string") { + return "
    " + } + beforeContext = beforeContext || "" + beforeTag = beforeTag || "" + afterContext = afterContext || "" + afterTag = afterTag || "" + if (!BLOCK_ELEMENTS.includes(beforeTag.toUpperCase()) && !BLOCK_ELEMENTS.includes(afterTag.toUpperCase())) { + return beforeContext + "
    " + afterContext + } else { + return whole + } }) + + // @ts-ignore + content = turndownService.turndown(input) + + // It's optimised for commonmark, we need to replace the space-space-newline with just newline + content = content.replace(/ \n/g, "\n") } + // Split into 2000 character chunks + const chunks = chunk(content, 2000) + messages = messages.concat(chunks.map(content => ({ + content, + username: displayName, + avatar_url: avatarURL + }))) + return messages } diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index a45c23b..ac62bf3 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -4,6 +4,12 @@ const {test} = require("supertape") const {eventToMessage} = require("./event-to-message") const data = require("../../test/data") +function sameFirstContentAndWhitespace(t, a, b) { + const a2 = JSON.stringify(a[0].content) + const b2 = JSON.stringify(b[0].content) + t.equal(a2, b2) +} + test("event2message: janky test", t => { t.deepEqual( eventToMessage({ @@ -28,6 +34,165 @@ test("event2message: janky test", t => { ) }) +test("event2message: basic html is converted to markdown", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "this is a test of formatting" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "this **is** a **_test_** of ~~formatting~~", + avatar_url: undefined + }] + ) +}) + +test("event2message: markdown syntax is escaped", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "this **is** an extreme \\*test\\* of" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "this \\*\\*is\\*\\* an **_extreme_** \\\\\\*test\\\\\\* of", + avatar_url: undefined + }] + ) +}) + +test("event2message: html lines are bridged correctly", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "

    paragraph one
    line two
    line three

    paragraph two\nline two\nline three\n\nparagraph three

    paragraph four\nline two
    line three\nline four

    paragraph five" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "paragraph one\nline _two_\nline three\n\nparagraph two\nline _two_\nline three\n\nparagraph three\n\nparagraph four\nline two\nline three\nline four\n\nparagraph five", + avatar_url: undefined + }] + ) +}) + +/*test("event2message: whitespace is retained", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "line one: test test
    line two: test test
    line three: test test
    line four: test test
    line five" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\n line five", + avatar_url: undefined + }] + ) +})*/ + +test("event2message: whitespace is collapsed", t => { + sameFirstContentAndWhitespace( + t, + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "line one: test test
    line two: test test
    line three: test test
    line four: test test
    line five" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\nline five", + avatar_url: undefined + }] + ) +}) + +test("event2message: lists are bridged correctly", t => { + sameFirstContentAndWhitespace( + t, + eventToMessage({ + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "* line one\n* line two\n* line three\n * nested one\n * nested two\n* line four", + "format": "org.matrix.custom.html", + "formatted_body": "
      \n
    • line one
    • \n
    • line two
    • \n
    • line three\n
        \n
      • nested one
      • \n
      • nested two
      • \n
      \n
    • \n
    • line four
    • \n
    \n" + }, + "origin_server_ts": 1692967314062, + "unsigned": { + "age": 112, + "transaction_id": "m1692967313951.441" + }, + "event_id": "$l-xQPY5vNJo3SNxU9d8aOWNVD1glMslMyrp4M_JEF70", + "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" + }), + [{ + username: "cadence", + content: "* line one\n* line two\n* line three\n * nested one\n * nested two\n* line four", + avatar_url: undefined + }] + ) +}) + test("event2message: long messages are split", t => { t.deepEqual( eventToMessage({ @@ -55,3 +220,29 @@ test("event2message: long messages are split", t => { }] ) }) + +test("event2message: m.emote markdown syntax is escaped", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.emote", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "shows you **her** extreme \\*test\\* of" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "\\* cadence shows you \\*\\*her\\*\\* **_extreme_** \\\\\\*test\\\\\\* of", + avatar_url: undefined + }] + ) +}) diff --git a/package-lock.json b/package-lock.json index 4d32bf5..dfd21ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", + "chunk-text": "^2.0.1", "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", @@ -20,7 +21,8 @@ "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", - "try-to-catch": "^3.0.1" + "try-to-catch": "^3.0.1", + "turndown": "^7.1.2" }, "devDependencies": { "@types/node": "^18.16.0", @@ -732,6 +734,18 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, + "node_modules/chunk-text": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/chunk-text/-/chunk-text-2.0.1.tgz", + "integrity": "sha512-ER6TSpe2DT4wjOVOKJ3FFAYv7wE77HA/Ztz88Peiv3lq/2oVMsItYJJsVVI0xNZM8cdImOOTNqlw+LQz7gYdJg==", + "dependencies": { + "runes": "^0.4.3" + }, + "bin": { + "chunk": "bin/server.js", + "chunk-text": "bin/server.js" + } + }, "node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -1057,6 +1071,11 @@ "simple-markdown": "^0.7.2" } }, + "node_modules/domino": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", + "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2646,6 +2665,14 @@ "node": "*" } }, + "node_modules/runes": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/runes/-/runes-0.4.3.tgz", + "integrity": "sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3216,6 +3243,14 @@ "node": "*" } }, + "node_modules/turndown": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.1.2.tgz", + "integrity": "sha512-ntI9R7fcUKjqBP6QU8rBK2Ehyt8LAzt3UBT9JR9tgo6GtuKvyUzpayWmeMKJw1DPdXzktvtIT8m2mVXz+bL/Qg==", + "dependencies": { + "domino": "^2.1.6" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index 155bf2e..238b9ae 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", + "chunk-text": "^2.0.1", "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", @@ -26,7 +27,8 @@ "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", - "try-to-catch": "^3.0.1" + "try-to-catch": "^3.0.1", + "turndown": "^7.1.2" }, "devDependencies": { "@types/node": "^18.16.0", diff --git a/types.d.ts b/types.d.ts index e9b8a7a..badbbab 100644 --- a/types.d.ts +++ b/types.d.ts @@ -67,7 +67,7 @@ export namespace Event { } export type M_Room_Message = { - msgtype: "m.text" + msgtype: "m.text" | "m.emote" body: string format?: "org.matrix.custom.html" formatted_body?: string From 8c4e16e255fcb75da55696b9597fb20fccc64b89 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 01:43:17 +1200 Subject: [PATCH 180/200] add turndown for m->d formatting --- m2d/converters/event-to-message.js | 79 ++++++++-- m2d/converters/event-to-message.test.js | 191 ++++++++++++++++++++++++ package-lock.json | 37 ++++- package.json | 4 +- types.d.ts | 2 +- 5 files changed, 297 insertions(+), 16 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index dde77b7..f793f85 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -2,19 +2,41 @@ const Ty = require("../../types") const DiscordTypes = require("discord-api-types/v10") -const markdown = require("discord-markdown") +const chunk = require("chunk-text") +const TurndownService = require("turndown") const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +// https://github.com/mixmark-io/turndown/blob/97e4535ca76bb2e70d9caa2aa4d4686956b06d44/src/utilities.js#L26C28-L33C2 +const BLOCK_ELEMENTS = [ + "ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", + "CENTER", "DD", "DETAILS", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", + "FOOTER", "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", + "HGROUP", "HR", "HTML", "ISINDEX", "LI", "MAIN", "MENU", "NAV", "NOFRAMES", + "NOSCRIPT", "OL", "OUTPUT", "P", "PRE", "SECTION", "SUMMARY", "TABLE", "TBODY", "TD", + "TFOOT", "TH", "THEAD", "TR", "UL" +] + +const turndownService = new TurndownService({ + hr: "----" +}) + +turndownService.addRule("strikethrough", { + filter: ["del", "s", "strike"], + replacement: function (content) { + return "~~" + content + "~~" + } +}) + /** * @param {Ty.Event.Outer} event */ function eventToMessage(event) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ - const messages = [] + let messages = [] let displayName = event.sender let avatarURL = undefined @@ -24,20 +46,51 @@ function eventToMessage(event) { // TODO: get the media repo domain and the avatar url from the matrix member event } - if (event.content.msgtype === "m.text") { - messages.push({ - content: event.content.body, - username: displayName, - avatar_url: avatarURL - }) - } else if (event.content.msgtype === "m.emote") { - messages.push({ - content: `\* _${displayName} ${event.content.body}_`, - username: displayName, - avatar_url: avatarURL + // Convert content depending on what the message is + let content = event.content.body // ultimate fallback + if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) { + let input = event.content.formatted_body + if (event.content.msgtype === "m.emote") { + input = `* ${displayName} ${input}` + } + + // Note: Element's renderers on Web and Android currently collapse whitespace, like the browser does. Turndown also collapses whitespace which is good for me. + // If later I'm using a client that doesn't collapse whitespace and I want turndown to follow suit, uncomment the following line of code, and it Just Works: + // input = input.replace(/ /g, " ") + // There is also a corresponding test to uncomment, named "event2message: whitespace is retained" + + // The matrix spec hasn't decided whether \n counts as a newline or not, but I'm going to count it, because if it's in the data it's there for a reason. + // But I should not count it if it's between block elements. + input = input.replace(/(<\/?([^ >]+)[^>]*>)?\n(<\/?([^ >]+)[^>]*>)?/g, (whole, beforeContext, beforeTag, afterContext, afterTag) => { + if (typeof beforeTag !== "string" && typeof afterTag !== "string") { + return "
    " + } + beforeContext = beforeContext || "" + beforeTag = beforeTag || "" + afterContext = afterContext || "" + afterTag = afterTag || "" + if (!BLOCK_ELEMENTS.includes(beforeTag.toUpperCase()) && !BLOCK_ELEMENTS.includes(afterTag.toUpperCase())) { + return beforeContext + "
    " + afterContext + } else { + return whole + } }) + + // @ts-ignore + content = turndownService.turndown(input) + + // It's optimised for commonmark, we need to replace the space-space-newline with just newline + content = content.replace(/ \n/g, "\n") } + // Split into 2000 character chunks + const chunks = chunk(content, 2000) + messages = messages.concat(chunks.map(content => ({ + content, + username: displayName, + avatar_url: avatarURL + }))) + return messages } diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index a45c23b..ac62bf3 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -4,6 +4,12 @@ const {test} = require("supertape") const {eventToMessage} = require("./event-to-message") const data = require("../../test/data") +function sameFirstContentAndWhitespace(t, a, b) { + const a2 = JSON.stringify(a[0].content) + const b2 = JSON.stringify(b[0].content) + t.equal(a2, b2) +} + test("event2message: janky test", t => { t.deepEqual( eventToMessage({ @@ -28,6 +34,165 @@ test("event2message: janky test", t => { ) }) +test("event2message: basic html is converted to markdown", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "this is a test of formatting" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "this **is** a **_test_** of ~~formatting~~", + avatar_url: undefined + }] + ) +}) + +test("event2message: markdown syntax is escaped", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "this **is** an extreme \\*test\\* of" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "this \\*\\*is\\*\\* an **_extreme_** \\\\\\*test\\\\\\* of", + avatar_url: undefined + }] + ) +}) + +test("event2message: html lines are bridged correctly", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "

    paragraph one
    line two
    line three

    paragraph two\nline two\nline three\n\nparagraph three

    paragraph four\nline two
    line three\nline four

    paragraph five" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "paragraph one\nline _two_\nline three\n\nparagraph two\nline _two_\nline three\n\nparagraph three\n\nparagraph four\nline two\nline three\nline four\n\nparagraph five", + avatar_url: undefined + }] + ) +}) + +/*test("event2message: whitespace is retained", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "line one: test test
    line two: test test
    line three: test test
    line four: test test
    line five" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\n line five", + avatar_url: undefined + }] + ) +})*/ + +test("event2message: whitespace is collapsed", t => { + sameFirstContentAndWhitespace( + t, + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "line one: test test
    line two: test test
    line three: test test
    line four: test test
    line five" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\nline five", + avatar_url: undefined + }] + ) +}) + +test("event2message: lists are bridged correctly", t => { + sameFirstContentAndWhitespace( + t, + eventToMessage({ + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "* line one\n* line two\n* line three\n * nested one\n * nested two\n* line four", + "format": "org.matrix.custom.html", + "formatted_body": "
      \n
    • line one
    • \n
    • line two
    • \n
    • line three\n
        \n
      • nested one
      • \n
      • nested two
      • \n
      \n
    • \n
    • line four
    • \n
    \n" + }, + "origin_server_ts": 1692967314062, + "unsigned": { + "age": 112, + "transaction_id": "m1692967313951.441" + }, + "event_id": "$l-xQPY5vNJo3SNxU9d8aOWNVD1glMslMyrp4M_JEF70", + "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" + }), + [{ + username: "cadence", + content: "* line one\n* line two\n* line three\n * nested one\n * nested two\n* line four", + avatar_url: undefined + }] + ) +}) + test("event2message: long messages are split", t => { t.deepEqual( eventToMessage({ @@ -55,3 +220,29 @@ test("event2message: long messages are split", t => { }] ) }) + +test("event2message: m.emote markdown syntax is escaped", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.emote", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "shows you **her** extreme \\*test\\* of" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "\\* cadence shows you \\*\\*her\\*\\* **_extreme_** \\\\\\*test\\\\\\* of", + avatar_url: undefined + }] + ) +}) diff --git a/package-lock.json b/package-lock.json index 4d32bf5..dfd21ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", + "chunk-text": "^2.0.1", "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", @@ -20,7 +21,8 @@ "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", - "try-to-catch": "^3.0.1" + "try-to-catch": "^3.0.1", + "turndown": "^7.1.2" }, "devDependencies": { "@types/node": "^18.16.0", @@ -732,6 +734,18 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, + "node_modules/chunk-text": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/chunk-text/-/chunk-text-2.0.1.tgz", + "integrity": "sha512-ER6TSpe2DT4wjOVOKJ3FFAYv7wE77HA/Ztz88Peiv3lq/2oVMsItYJJsVVI0xNZM8cdImOOTNqlw+LQz7gYdJg==", + "dependencies": { + "runes": "^0.4.3" + }, + "bin": { + "chunk": "bin/server.js", + "chunk-text": "bin/server.js" + } + }, "node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -1057,6 +1071,11 @@ "simple-markdown": "^0.7.2" } }, + "node_modules/domino": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", + "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2646,6 +2665,14 @@ "node": "*" } }, + "node_modules/runes": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/runes/-/runes-0.4.3.tgz", + "integrity": "sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3216,6 +3243,14 @@ "node": "*" } }, + "node_modules/turndown": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.1.2.tgz", + "integrity": "sha512-ntI9R7fcUKjqBP6QU8rBK2Ehyt8LAzt3UBT9JR9tgo6GtuKvyUzpayWmeMKJw1DPdXzktvtIT8m2mVXz+bL/Qg==", + "dependencies": { + "domino": "^2.1.6" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index 155bf2e..238b9ae 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "license": "MIT", "dependencies": { "better-sqlite3": "^8.3.0", + "chunk-text": "^2.0.1", "cloudstorm": "^0.8.0", "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#440130ef343c8183a81c7c09809731484aa3a182", "heatsync": "^2.4.1", @@ -26,7 +27,8 @@ "node-fetch": "^2.6.7", "prettier-bytes": "^1.0.4", "snowtransfer": "^0.8.0", - "try-to-catch": "^3.0.1" + "try-to-catch": "^3.0.1", + "turndown": "^7.1.2" }, "devDependencies": { "@types/node": "^18.16.0", diff --git a/types.d.ts b/types.d.ts index e9b8a7a..badbbab 100644 --- a/types.d.ts +++ b/types.d.ts @@ -67,7 +67,7 @@ export namespace Event { } export type M_Room_Message = { - msgtype: "m.text" + msgtype: "m.text" | "m.emote" body: string format?: "org.matrix.custom.html" formatted_body?: string From deb63a79d34d5ea1d564f24eb00eba2f1f094846 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 01:44:50 +1200 Subject: [PATCH 181/200] 100% edit-to-changes code coverage --- d2m/actions/edit-message.js | 2 +- d2m/converters/edit-to-changes.js | 4 +-- d2m/converters/edit-to-changes.test.js | 26 ++++++++++++++++ db/data-for-test.sql | 5 +++- test/data.js | 41 ++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index fa152cf..d7cb81d 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -24,7 +24,7 @@ async function editMessage(message, guild) { await api.sendEvent(roomID, eventType, newContentWithoutType, senderMxid) // Ensure the database is up to date. // The columns are event_id, event_type, event_subtype, message_id, channel_id, part, source. Only event_subtype could potentially be changed by a replacement event. - const subtype = newContentWithoutType.msgtype ?? null + const subtype = newContentWithoutType.msgtype || null db.prepare("UPDATE event_message SET event_subtype = ? WHERE event_id = ?").run(subtype, oldID) } diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 3f4b2d2..814bb97 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -23,7 +23,7 @@ async function editToChanges(message, guild, api) { const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) /** @type {string?} */ - let senderMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(message.author.id) ?? null + let senderMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(message.author.id) || null if (senderMxid) { const senderIsInRoom = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, senderMxid) if (!senderIsInRoom) { @@ -66,7 +66,7 @@ async function editToChanges(message, guild, api) { // Find a new event to pair it with... for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] - if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype ?? null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to // Found one! // Set up the pairing eventsToReplace.push({ diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index 674cb15..022b396 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -103,6 +103,32 @@ test("edit2changes: add caption back to that image", async t => { t.deepEqual(eventsToReplace, []) }) +test("edit2changes: stickers and attachments are not changed, only the content can be edited", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "* only the content can be edited", + "m.mentions": {}, + // *** Replaced With: *** + "m.new_content": { + msgtype: "m.text", + body: "only the content can be edited", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4" + } + } + }]) +}) + + test("edit2changes: edit of reply to skull webp attachment with content", async t => { const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(eventsToRedact, []) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index e88b967..7148b19 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -83,7 +83,10 @@ INSERT INTO event_message (event_id, event_type, event_subtype, message_id, chan ('$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M', 'm.room.message', 'm.text', '1128084851279536279', '112760669178241024', 0, 1), ('$YUJFa5j0ZJe7PUvD2DykRt9g51RoadUEYmuJLdSEbJ0', 'm.room.message', 'm.image', '1128084851279536279', '112760669178241024', 1, 1), ('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1), -('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1); +('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1), +('$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4', 'm.room.message', 'm.text', '1106366167788044450', '122155380120748034', 0, 1), +('$Ijf1MFCD39ktrNHxrA-i2aKoRWNYdAV2ZXYQeiZIgEU', 'm.room.message', 'm.image', '1106366167788044450', '122155380120748034', 0, 0), +('$f9cjKiacXI9qPF_nUAckzbiKnJEi0LM399kOkhdd8f8', 'm.sticker', NULL, '1106366167788044450', '122155380120748034', 0, 0); INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), diff --git a/test/data.js b/test/data.js index a4d836d..32ee3b0 100644 --- a/test/data.js +++ b/test/data.js @@ -1250,6 +1250,47 @@ module.exports = { tts: false, type: 0 }, + edited_content_with_sticker_and_attachments: { + id: "1106366167788044450", + type: 0, + content: "only the content can be edited", + channel_id: "122155380120748034", + author: { + id: "113340068197859328", + username: "Cookie 🍪", + global_name: null, + display_name: null, + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "7766", + public_flags: 128, + avatar_decoration: null + }, + attachments: [{ + id: "1106366167486038016", + filename: "image.png", + size: 127373, + url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", + width: 333, + height: 287, + content_type: "image/png" + }], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-05-11T23:44:09.690000+00:00", + edited_timestamp: "2023-05-11T23:44:19.690000+00:00", + flags: 0, + components: [], + sticker_items: [{ + id: "1106323941183717586", + format_type: 1, + name: "pomu puff" + }] + }, edit_of_reply_to_skull_webp_attachment_with_content: { type: 19, tts: false, From 7780ee806aa1d0b291826cd204814aeb3ff2568e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 01:44:50 +1200 Subject: [PATCH 182/200] 100% edit-to-changes code coverage --- d2m/actions/edit-message.js | 2 +- d2m/converters/edit-to-changes.js | 4 +-- d2m/converters/edit-to-changes.test.js | 26 ++++++++++++++++ db/data-for-test.sql | 5 +++- test/data.js | 41 ++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js index fa152cf..d7cb81d 100644 --- a/d2m/actions/edit-message.js +++ b/d2m/actions/edit-message.js @@ -24,7 +24,7 @@ async function editMessage(message, guild) { await api.sendEvent(roomID, eventType, newContentWithoutType, senderMxid) // Ensure the database is up to date. // The columns are event_id, event_type, event_subtype, message_id, channel_id, part, source. Only event_subtype could potentially be changed by a replacement event. - const subtype = newContentWithoutType.msgtype ?? null + const subtype = newContentWithoutType.msgtype || null db.prepare("UPDATE event_message SET event_subtype = ? WHERE event_id = ?").run(subtype, oldID) } diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 3f4b2d2..814bb97 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -23,7 +23,7 @@ async function editToChanges(message, guild, api) { const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) /** @type {string?} */ - let senderMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(message.author.id) ?? null + let senderMxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(message.author.id) || null if (senderMxid) { const senderIsInRoom = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, senderMxid) if (!senderIsInRoom) { @@ -66,7 +66,7 @@ async function editToChanges(message, guild, api) { // Find a new event to pair it with... for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] - if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype ?? null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to // Found one! // Set up the pairing eventsToReplace.push({ diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index 674cb15..022b396 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -103,6 +103,32 @@ test("edit2changes: add caption back to that image", async t => { t.deepEqual(eventsToReplace, []) }) +test("edit2changes: stickers and attachments are not changed, only the content can be edited", async t => { + const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments, data.guild.general, {}) + t.deepEqual(eventsToRedact, []) + t.deepEqual(eventsToSend, []) + t.deepEqual(eventsToReplace, [{ + oldID: "$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4", + newContent: { + $type: "m.room.message", + msgtype: "m.text", + body: "* only the content can be edited", + "m.mentions": {}, + // *** Replaced With: *** + "m.new_content": { + msgtype: "m.text", + body: "only the content can be edited", + "m.mentions": {} + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: "$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4" + } + } + }]) +}) + + test("edit2changes: edit of reply to skull webp attachment with content", async t => { const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(eventsToRedact, []) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index e88b967..7148b19 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -83,7 +83,10 @@ INSERT INTO event_message (event_id, event_type, event_subtype, message_id, chan ('$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M', 'm.room.message', 'm.text', '1128084851279536279', '112760669178241024', 0, 1), ('$YUJFa5j0ZJe7PUvD2DykRt9g51RoadUEYmuJLdSEbJ0', 'm.room.message', 'm.image', '1128084851279536279', '112760669178241024', 1, 1), ('$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q', 'm.room.message', 'm.text', '1128084748338741392', '112760669178241024', 0, 1), -('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1); +('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1), +('$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4', 'm.room.message', 'm.text', '1106366167788044450', '122155380120748034', 0, 1), +('$Ijf1MFCD39ktrNHxrA-i2aKoRWNYdAV2ZXYQeiZIgEU', 'm.room.message', 'm.image', '1106366167788044450', '122155380120748034', 0, 0), +('$f9cjKiacXI9qPF_nUAckzbiKnJEi0LM399kOkhdd8f8', 'm.sticker', NULL, '1106366167788044450', '122155380120748034', 0, 0); INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), diff --git a/test/data.js b/test/data.js index a4d836d..32ee3b0 100644 --- a/test/data.js +++ b/test/data.js @@ -1250,6 +1250,47 @@ module.exports = { tts: false, type: 0 }, + edited_content_with_sticker_and_attachments: { + id: "1106366167788044450", + type: 0, + content: "only the content can be edited", + channel_id: "122155380120748034", + author: { + id: "113340068197859328", + username: "Cookie 🍪", + global_name: null, + display_name: null, + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "7766", + public_flags: 128, + avatar_decoration: null + }, + attachments: [{ + id: "1106366167486038016", + filename: "image.png", + size: 127373, + url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", + width: 333, + height: 287, + content_type: "image/png" + }], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-05-11T23:44:09.690000+00:00", + edited_timestamp: "2023-05-11T23:44:19.690000+00:00", + flags: 0, + components: [], + sticker_items: [{ + id: "1106323941183717586", + format_type: 1, + name: "pomu puff" + }] + }, edit_of_reply_to_skull_webp_attachment_with_content: { type: 19, tts: false, From a7ecdcd7db0ad7ff8b2c4ad3f0b85fcdda4e446d Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 02:04:49 +1200 Subject: [PATCH 183/200] configure turndown for code blocks --- m2d/converters/event-to-message.js | 10 ++++- m2d/converters/event-to-message.test.js | 57 +++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f793f85..7893799 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -10,7 +10,6 @@ const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") -// https://github.com/mixmark-io/turndown/blob/97e4535ca76bb2e70d9caa2aa4d4686956b06d44/src/utilities.js#L26C28-L33C2 const BLOCK_ELEMENTS = [ "ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", "CENTER", "DD", "DETAILS", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", @@ -21,7 +20,10 @@ const BLOCK_ELEMENTS = [ ] const turndownService = new TurndownService({ - hr: "----" + hr: "----", + headingStyle: "atx", + preformattedCode: true, + codeBlockStyle: "fenced" }) turndownService.addRule("strikethrough", { @@ -81,6 +83,10 @@ function eventToMessage(event) { // It's optimised for commonmark, we need to replace the space-space-newline with just newline content = content.replace(/ \n/g, "\n") + } else { + // Looks like we're using the plaintext body! + // Markdown needs to be escaped + content = content.replace(/([*_~`#])/g, `\\$1`) } // Split into 2000 character chunks diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index ac62bf3..7aefdb1 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -10,11 +10,11 @@ function sameFirstContentAndWhitespace(t, a, b) { t.equal(a2, b2) } -test("event2message: janky test", t => { +test("event2message: body is used when there is no formatted_body", t => { t.deepEqual( eventToMessage({ content: { - body: "test", + body: "testing plaintext", msgtype: "m.text" }, event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", @@ -28,7 +28,31 @@ test("event2message: janky test", t => { }), [{ username: "cadence", - content: "test", + content: "testing plaintext", + avatar_url: undefined + }] + ) +}) + +test("event2message: any markdown in body is escaped", t => { + t.deepEqual( + eventToMessage({ + content: { + body: "testing **special** ~~things~~ which _should_ *not* `trigger` @any ", + msgtype: "m.text" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "testing \\*\\*special\\*\\* \\~\\~things\\~\\~ which \\_should\\_ \\*not\\* \\`trigger\\` @any ", avatar_url: undefined }] ) @@ -221,6 +245,33 @@ test("event2message: long messages are split", t => { ) }) +test("event2message: code blocks work", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "

    preceding

    \n
    code block\n
    \n

    following code is inline

    \n" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "preceding\n\n```\ncode block\n```\n\nfollowing `code` is inline", + avatar_url: undefined + }] + ) +}) + + test("event2message: m.emote markdown syntax is escaped", t => { t.deepEqual( eventToMessage({ From 88eb8c22904e1cc4d2fae57f1e34d2f58a35f0cf Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 02:04:49 +1200 Subject: [PATCH 184/200] configure turndown for code blocks --- m2d/converters/event-to-message.js | 10 ++++- m2d/converters/event-to-message.test.js | 57 +++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f793f85..7893799 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -10,7 +10,6 @@ const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") -// https://github.com/mixmark-io/turndown/blob/97e4535ca76bb2e70d9caa2aa4d4686956b06d44/src/utilities.js#L26C28-L33C2 const BLOCK_ELEMENTS = [ "ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", "CENTER", "DD", "DETAILS", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", @@ -21,7 +20,10 @@ const BLOCK_ELEMENTS = [ ] const turndownService = new TurndownService({ - hr: "----" + hr: "----", + headingStyle: "atx", + preformattedCode: true, + codeBlockStyle: "fenced" }) turndownService.addRule("strikethrough", { @@ -81,6 +83,10 @@ function eventToMessage(event) { // It's optimised for commonmark, we need to replace the space-space-newline with just newline content = content.replace(/ \n/g, "\n") + } else { + // Looks like we're using the plaintext body! + // Markdown needs to be escaped + content = content.replace(/([*_~`#])/g, `\\$1`) } // Split into 2000 character chunks diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index ac62bf3..7aefdb1 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -10,11 +10,11 @@ function sameFirstContentAndWhitespace(t, a, b) { t.equal(a2, b2) } -test("event2message: janky test", t => { +test("event2message: body is used when there is no formatted_body", t => { t.deepEqual( eventToMessage({ content: { - body: "test", + body: "testing plaintext", msgtype: "m.text" }, event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", @@ -28,7 +28,31 @@ test("event2message: janky test", t => { }), [{ username: "cadence", - content: "test", + content: "testing plaintext", + avatar_url: undefined + }] + ) +}) + +test("event2message: any markdown in body is escaped", t => { + t.deepEqual( + eventToMessage({ + content: { + body: "testing **special** ~~things~~ which _should_ *not* `trigger` @any ", + msgtype: "m.text" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "testing \\*\\*special\\*\\* \\~\\~things\\~\\~ which \\_should\\_ \\*not\\* \\`trigger\\` @any ", avatar_url: undefined }] ) @@ -221,6 +245,33 @@ test("event2message: long messages are split", t => { ) }) +test("event2message: code blocks work", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "

    preceding

    \n
    code block\n
    \n

    following code is inline

    \n" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "preceding\n\n```\ncode block\n```\n\nfollowing `code` is inline", + avatar_url: undefined + }] + ) +}) + + test("event2message: m.emote markdown syntax is escaped", t => { t.deepEqual( eventToMessage({ From 936c9820ec9f13383d1516782696fa75e94ca884 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 18:58:41 +1200 Subject: [PATCH 185/200] store room name changes to nick in db --- m2d/event-dispatcher.js | 11 +++++++++++ types.d.ts | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index c62d805..3425fdb 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -80,3 +80,14 @@ async event => { const url = event.content.url || null db.prepare("UPDATE channel_room SET custom_avatar = ? WHERE room_id = ?").run(url, event.room_id) })) + +sync.addTemporaryListener(as, "type:m.room.name", guard("m.room.name", +/** + * @param {Ty.Event.StateOuter} event + */ +async event => { + if (event.state_key !== "") return + if (utils.eventSenderIsFromDiscord(event.sender)) return + const name = event.content.name || null + db.prepare("UPDATE channel_room SET nick = ? WHERE room_id = ?").run(name, event.room_id) +})) diff --git a/types.d.ts b/types.d.ts index badbbab..2bf0af0 100644 --- a/types.d.ts +++ b/types.d.ts @@ -70,7 +70,12 @@ export namespace Event { msgtype: "m.text" | "m.emote" body: string format?: "org.matrix.custom.html" - formatted_body?: string + formatted_body?: string, + "m.relates_to"?: { + "m.in_reply_to": { + event_id: string + } + } } export type M_Room_Member = { @@ -84,6 +89,10 @@ export namespace Event { url?: string } + export type M_Room_Name = { + name?: string + } + export type M_Reaction = { "m.relates_to": { rel_type: "m.annotation" From df651241a54b4429b51db88664e946901a89f986 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 18:58:41 +1200 Subject: [PATCH 186/200] store room name changes to nick in db --- m2d/event-dispatcher.js | 11 +++++++++++ types.d.ts | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index c62d805..3425fdb 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -80,3 +80,14 @@ async event => { const url = event.content.url || null db.prepare("UPDATE channel_room SET custom_avatar = ? WHERE room_id = ?").run(url, event.room_id) })) + +sync.addTemporaryListener(as, "type:m.room.name", guard("m.room.name", +/** + * @param {Ty.Event.StateOuter} event + */ +async event => { + if (event.state_key !== "") return + if (utils.eventSenderIsFromDiscord(event.sender)) return + const name = event.content.name || null + db.prepare("UPDATE channel_room SET nick = ? WHERE room_id = ?").run(name, event.room_id) +})) diff --git a/types.d.ts b/types.d.ts index badbbab..2bf0af0 100644 --- a/types.d.ts +++ b/types.d.ts @@ -70,7 +70,12 @@ export namespace Event { msgtype: "m.text" | "m.emote" body: string format?: "org.matrix.custom.html" - formatted_body?: string + formatted_body?: string, + "m.relates_to"?: { + "m.in_reply_to": { + event_id: string + } + } } export type M_Room_Member = { @@ -84,6 +89,10 @@ export namespace Event { url?: string } + export type M_Room_Name = { + name?: string + } + export type M_Reaction = { "m.relates_to": { rel_type: "m.annotation" From 300197c157faee54ac826abafd115d52119544f3 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 19:07:19 +1200 Subject: [PATCH 187/200] fix m->d formatting of quotes and code --- m2d/converters/event-to-message.js | 38 ++++++++++++++++++ m2d/converters/event-to-message.test.js | 52 +++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 7893799..f4382a3 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -33,6 +33,40 @@ turndownService.addRule("strikethrough", { } }) +turndownService.addRule("blockquote", { + filter: "blockquote", + replacement: function (content) { + content = content.replace(/^\n+|\n+$/g, "") + content = content.replace(/^/gm, "> ") + return content + } +}) + +turndownService.addRule("fencedCodeBlock", { + filter: function (node, options) { + return ( + options.codeBlockStyle === "fenced" && + node.nodeName === "PRE" && + node.firstChild && + node.firstChild.nodeName === "CODE" + ) + }, + replacement: function (content, node, options) { + const className = node.firstChild.getAttribute("class") || "" + const language = (className.match(/language-(\S+)/) || [null, ""])[1] + const code = node.firstChild + const visibleCode = code.childNodes.map(c => c.nodeName === "BR" ? "\n" : c.textContent).join("").replace(/\n*$/g, "") + + var fence = "```" + + return ( + fence + language + "\n" + + visibleCode + + "\n" + fence + ) + } +}) + /** * @param {Ty.Event.Outer} event */ @@ -61,9 +95,13 @@ function eventToMessage(event) { // input = input.replace(/ /g, " ") // There is also a corresponding test to uncomment, named "event2message: whitespace is retained" + // Element adds a bunch of
    before but doesn't render them. I can't figure out how this works, so let's just delete those. + input = input.replace(/(?:\n|
    \s*)*<\/blockquote>/g, "") + // The matrix spec hasn't decided whether \n counts as a newline or not, but I'm going to count it, because if it's in the data it's there for a reason. // But I should not count it if it's between block elements. input = input.replace(/(<\/?([^ >]+)[^>]*>)?\n(<\/?([^ >]+)[^>]*>)?/g, (whole, beforeContext, beforeTag, afterContext, afterTag) => { + // console.error(beforeContext, beforeTag, afterContext, afterTag) if (typeof beforeTag !== "string" && typeof afterTag !== "string") { return "
    " } diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index 7aefdb1..afa40de 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -271,6 +271,58 @@ test("event2message: code blocks work", t => { ) }) +test("event2message: code block contents are formatted correctly and not escaped", t => { + t.deepEqual( + eventToMessage({ + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "wrong body", + "format": "org.matrix.custom.html", + "formatted_body": "
    input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n_input_ = input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n
    \n

    input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,

    \n" + }, + "origin_server_ts": 1693031482275, + "unsigned": { + "age": 99, + "transaction_id": "m1693031482146.511" + }, + "event_id": "$pGkWQuGVmrPNByrFELxhzI6MCBgJecr5I2J3z88Gc2s", + "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" + }), + [{ + username: "cadence", + content: "```\ninput = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n_input_ = input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n```\n\n`input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,`", + avatar_url: undefined + }] + ) +}) + +test("event2message: quotes have an appropriate amount of whitespace", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "
    Chancellor of Germany Angela Merkel, on March 17, 2017: they did not shake hands



    🤨" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "> Chancellor of Germany Angela Merkel, on March 17, 2017: they did not shake hands\n🤨", + avatar_url: undefined + }] + ) +}) test("event2message: m.emote markdown syntax is escaped", t => { t.deepEqual( From 39fb4465f6861de3dc98d638c14c424616f5bf6a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 19:07:19 +1200 Subject: [PATCH 188/200] fix m->d formatting of quotes and code --- m2d/converters/event-to-message.js | 38 ++++++++++++++++++ m2d/converters/event-to-message.test.js | 52 +++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 7893799..f4382a3 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -33,6 +33,40 @@ turndownService.addRule("strikethrough", { } }) +turndownService.addRule("blockquote", { + filter: "blockquote", + replacement: function (content) { + content = content.replace(/^\n+|\n+$/g, "") + content = content.replace(/^/gm, "> ") + return content + } +}) + +turndownService.addRule("fencedCodeBlock", { + filter: function (node, options) { + return ( + options.codeBlockStyle === "fenced" && + node.nodeName === "PRE" && + node.firstChild && + node.firstChild.nodeName === "CODE" + ) + }, + replacement: function (content, node, options) { + const className = node.firstChild.getAttribute("class") || "" + const language = (className.match(/language-(\S+)/) || [null, ""])[1] + const code = node.firstChild + const visibleCode = code.childNodes.map(c => c.nodeName === "BR" ? "\n" : c.textContent).join("").replace(/\n*$/g, "") + + var fence = "```" + + return ( + fence + language + "\n" + + visibleCode + + "\n" + fence + ) + } +}) + /** * @param {Ty.Event.Outer} event */ @@ -61,9 +95,13 @@ function eventToMessage(event) { // input = input.replace(/ /g, " ") // There is also a corresponding test to uncomment, named "event2message: whitespace is retained" + // Element adds a bunch of
    before but doesn't render them. I can't figure out how this works, so let's just delete those. + input = input.replace(/(?:\n|
    \s*)*<\/blockquote>/g, "") + // The matrix spec hasn't decided whether \n counts as a newline or not, but I'm going to count it, because if it's in the data it's there for a reason. // But I should not count it if it's between block elements. input = input.replace(/(<\/?([^ >]+)[^>]*>)?\n(<\/?([^ >]+)[^>]*>)?/g, (whole, beforeContext, beforeTag, afterContext, afterTag) => { + // console.error(beforeContext, beforeTag, afterContext, afterTag) if (typeof beforeTag !== "string" && typeof afterTag !== "string") { return "
    " } diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index 7aefdb1..afa40de 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -271,6 +271,58 @@ test("event2message: code blocks work", t => { ) }) +test("event2message: code block contents are formatted correctly and not escaped", t => { + t.deepEqual( + eventToMessage({ + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "wrong body", + "format": "org.matrix.custom.html", + "formatted_body": "
    input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n_input_ = input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n
    \n

    input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,

    \n" + }, + "origin_server_ts": 1693031482275, + "unsigned": { + "age": 99, + "transaction_id": "m1693031482146.511" + }, + "event_id": "$pGkWQuGVmrPNByrFELxhzI6MCBgJecr5I2J3z88Gc2s", + "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" + }), + [{ + username: "cadence", + content: "```\ninput = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n_input_ = input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n```\n\n`input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,`", + avatar_url: undefined + }] + ) +}) + +test("event2message: quotes have an appropriate amount of whitespace", t => { + t.deepEqual( + eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: "
    Chancellor of Germany Angela Merkel, on March 17, 2017: they did not shake hands



    🤨" + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }), + [{ + username: "cadence", + content: "> Chancellor of Germany Angela Merkel, on March 17, 2017: they did not shake hands\n🤨", + avatar_url: undefined + }] + ) +}) test("event2message: m.emote markdown syntax is escaped", t => { t.deepEqual( From 2eb9abef401662f43acb37bccdd68730611633d7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 20:30:22 +1200 Subject: [PATCH 189/200] m->d rich replies --- db/data-for-test.sql | 6 +- m2d/actions/send-event.js | 8 +- m2d/converters/event-to-message.js | 37 +++- m2d/converters/event-to-message.test.js | 213 ++++++++++++++++++++---- 4 files changed, 231 insertions(+), 33 deletions(-) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index 7148b19..b8da73c 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -67,7 +67,8 @@ INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('820865262526005258', 'crunch_god', '_ooye_crunch_god', '@_ooye_crunch_god:cadence.moe'), ('771520384671416320', 'bojack_horseman', '_ooye_bojack_horseman', '@_ooye_bojack_horseman:cadence.moe'), ('112890272819507200', '.wing.', '_ooye_.wing.', '@_ooye_.wing.:cadence.moe'), -('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'); +('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'), +('111604486476181504', 'kyuugryphon', '_ooye_kyuugryphon', '@_ooye_kyuugryphon:cadence.moe');; INSERT INTO sim_member (mxid, room_id, profile_event_content_hash) VALUES ('@_ooye_bojack_horseman:cadence.moe', '!uCtjHhfGlYbVnPVlkG:cadence.moe', NULL); @@ -86,7 +87,8 @@ INSERT INTO event_message (event_id, event_type, event_subtype, message_id, chan ('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1), ('$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4', 'm.room.message', 'm.text', '1106366167788044450', '122155380120748034', 0, 1), ('$Ijf1MFCD39ktrNHxrA-i2aKoRWNYdAV2ZXYQeiZIgEU', 'm.room.message', 'm.image', '1106366167788044450', '122155380120748034', 0, 0), -('$f9cjKiacXI9qPF_nUAckzbiKnJEi0LM399kOkhdd8f8', 'm.sticker', NULL, '1106366167788044450', '122155380120748034', 0, 0); +('$f9cjKiacXI9qPF_nUAckzbiKnJEi0LM399kOkhdd8f8', 'm.sticker', NULL, '1106366167788044450', '122155380120748034', 0, 0), +('$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04', 'm.room.message', 'm.text', '1144865310588014633', '687028734322147344', 0, 1); INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 39eed22..016768e 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -9,6 +9,8 @@ const {sync, discord, db} = passthrough const channelWebhook = sync.require("./channel-webhook") /** @type {import("../converters/event-to-message")} */ const eventToMessage = sync.require("../converters/event-to-message") +/** @type {import("../../matrix/api")}) */ +const api = sync.require("../../matrix/api") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { @@ -20,10 +22,14 @@ async function sendEvent(event) { threadID = channelID channelID = row.thread_parent // it's the thread's parent... get with the times... } + // @ts-ignore + const guildID = discord.channels.get(channelID).guild_id + const guild = discord.guilds.get(guildID) + assert(guild) // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it - const messages = eventToMessage.eventToMessage(event) + const messages = await eventToMessage.eventToMessage(event, guild, {api}) assert(Array.isArray(messages)) // sanity /** @type {DiscordTypes.APIMessage[]} */ diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f4382a3..cf34705 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -26,6 +26,8 @@ const turndownService = new TurndownService({ codeBlockStyle: "fenced" }) +turndownService.remove("mx-reply") + turndownService.addRule("strikethrough", { filter: ["del", "s", "strike"], replacement: function (content) { @@ -69,13 +71,16 @@ turndownService.addRule("fencedCodeBlock", { /** * @param {Ty.Event.Outer} event + * @param {import("discord-api-types/v10").APIGuild} guild + * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API */ -function eventToMessage(event) { +async function eventToMessage(event, guild, di) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ let messages = [] let displayName = event.sender let avatarURL = undefined + let replyLine = "" const match = event.sender.match(/^@(.*?):/) if (match) { displayName = match[1] @@ -95,7 +100,33 @@ function eventToMessage(event) { // input = input.replace(/ /g, " ") // There is also a corresponding test to uncomment, named "event2message: whitespace is retained" - // Element adds a bunch of
    before but doesn't render them. I can't figure out how this works, so let's just delete those. + // Handling replies. We'll look up the data of the replied-to event from the Matrix homeserver. + await (async () => { + const repliedToEventId = event.content["m.relates_to"]?.["m.in_reply_to"].event_id + if (!repliedToEventId) return + const repliedToEvent = await di.api.getEvent(event.room_id, repliedToEventId) + if (!repliedToEvent) return + const row = db.prepare("SELECT channel_id, message_id FROM event_message WHERE event_id = ? ORDER BY part").get(repliedToEventId) + if (row) { + replyLine = `<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/${guild.id}/${row.channel_id}/${row.message_id} ` + } else { + replyLine = `<:L1:1144820033948762203><:L2:1144820084079087647>` + } + const sender = repliedToEvent.sender + const senderName = sender.match(/@([^:]*)/)?.[1] || sender + const authorID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(repliedToEvent.sender) + if (authorID) { + replyLine += `<@${authorID}>: ` + } else { + replyLine += `Ⓜ️**${senderName}**: ` + } + const repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body + const contentPreviewChunks = chunk(repliedToContent.replace(/.*<\/mx-reply>/, "").replace(/(?:\n|
    )+/g, " ").replace(/<[^>]+>/g, ""), 24) + const contentPreview = contentPreviewChunks.length > 1 ? contentPreviewChunks[0] + "..." : contentPreviewChunks[0] + replyLine += contentPreview + "\n" + })() + + // Element adds a bunch of
    before but doesn't render them. I can't figure out how this even works in the browser, so let's just delete those. input = input.replace(/(?:\n|
    \s*)*<\/blockquote>/g, "") // The matrix spec hasn't decided whether \n counts as a newline or not, but I'm going to count it, because if it's in the data it's there for a reason. @@ -127,6 +158,8 @@ function eventToMessage(event) { content = content.replace(/([*_~`#])/g, `\\$1`) } + content = replyLine + content + // Split into 2000 character chunks const chunks = chunk(content, 2000) messages = messages.concat(chunks.map(content => ({ diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index afa40de..33c4d71 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -1,18 +1,42 @@ -// @ts-check - const {test} = require("supertape") const {eventToMessage} = require("./event-to-message") const data = require("../../test/data") +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} + function sameFirstContentAndWhitespace(t, a, b) { const a2 = JSON.stringify(a[0].content) const b2 = JSON.stringify(b[0].content) t.equal(a2, b2) } -test("event2message: body is used when there is no formatted_body", t => { +test("event2message: body is used when there is no formatted_body", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { body: "testing plaintext", msgtype: "m.text" @@ -34,9 +58,9 @@ test("event2message: body is used when there is no formatted_body", t => { ) }) -test("event2message: any markdown in body is escaped", t => { +test("event2message: any markdown in body is escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { body: "testing **special** ~~things~~ which _should_ *not* `trigger` @any ", msgtype: "m.text" @@ -58,9 +82,9 @@ test("event2message: any markdown in body is escaped", t => { ) }) -test("event2message: basic html is converted to markdown", t => { +test("event2message: basic html is converted to markdown", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -84,9 +108,9 @@ test("event2message: basic html is converted to markdown", t => { ) }) -test("event2message: markdown syntax is escaped", t => { +test("event2message: markdown syntax is escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -110,9 +134,9 @@ test("event2message: markdown syntax is escaped", t => { ) }) -test("event2message: html lines are bridged correctly", t => { +test("event2message: html lines are bridged correctly", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -136,9 +160,9 @@ test("event2message: html lines are bridged correctly", t => { ) }) -/*test("event2message: whitespace is retained", t => { +/*test("event2message: whitespace is retained", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -162,10 +186,10 @@ test("event2message: html lines are bridged correctly", t => { ) })*/ -test("event2message: whitespace is collapsed", t => { +test("event2message: whitespace is collapsed", async t => { sameFirstContentAndWhitespace( t, - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -189,10 +213,10 @@ test("event2message: whitespace is collapsed", t => { ) }) -test("event2message: lists are bridged correctly", t => { +test("event2message: lists are bridged correctly", async t => { sameFirstContentAndWhitespace( t, - eventToMessage({ + await eventToMessage({ "type": "m.room.message", "sender": "@cadence:cadence.moe", "content": { @@ -217,9 +241,9 @@ test("event2message: lists are bridged correctly", t => { ) }) -test("event2message: long messages are split", t => { +test("event2message: long messages are split", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { body: ("a".repeat(130) + " ").repeat(19), msgtype: "m.text" @@ -245,9 +269,9 @@ test("event2message: long messages are split", t => { ) }) -test("event2message: code blocks work", t => { +test("event2message: code blocks work", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -271,9 +295,9 @@ test("event2message: code blocks work", t => { ) }) -test("event2message: code block contents are formatted correctly and not escaped", t => { +test("event2message: code block contents are formatted correctly and not escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ "type": "m.room.message", "sender": "@cadence:cadence.moe", "content": { @@ -298,9 +322,9 @@ test("event2message: code block contents are formatted correctly and not escaped ) }) -test("event2message: quotes have an appropriate amount of whitespace", t => { +test("event2message: quotes have an appropriate amount of whitespace", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -324,9 +348,9 @@ test("event2message: quotes have an appropriate amount of whitespace", t => { ) }) -test("event2message: m.emote markdown syntax is escaped", t => { +test("event2message: m.emote markdown syntax is escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.emote", body: "wrong body", @@ -349,3 +373,136 @@ test("event2message: m.emote markdown syntax is escaped", t => { }] ) }) + +test("event2message: rich reply to a sim user", 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" + } + } + }, + "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, { + 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" + }) + } + }), + [{ + username: "cadence", + content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 <@111604486476181504>: Slow news day.\nTesting this reply, ignore", + avatar_url: undefined + }] + ) +}) + +test("event2message: rich reply to a matrix user's long message with formatting", async t => { + t.deepEqual( + await eventToMessage({ + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "> <@cadence:cadence.moe> ```\n> i should have a little happy test\n> ```\n> * list **bold** _em_ ~~strike~~\n> # heading 1\n> ## heading 2\n> ### heading 3\n> https://cadence.moe\n> [legit website](https://cadence.moe)\n\nno you can't!!!", + "format": "org.matrix.custom.html", + "formatted_body": "
    In reply to @cadence:cadence.moe
    i should have a little happy test\n
    \n
      \n
    • list bold em ~~strike~~
    • \n
    \n

    heading 1

    \n

    heading 2

    \n

    heading 3

    \n

    https://cadence.moe
    legit website

    \n
    no you can't!!!", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04" + } + } + }, + "origin_server_ts": 1693037401693, + "unsigned": { + "age": 381, + "transaction_id": "m1693037401592.521" + }, + "event_id": "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8", + "room_id": "!fGgIymcYWOqjbSRUdV:cadence.moe" + }, data.guild.general, { + api: { + getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04", { + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "```\ni should have a little happy test\n```\n* list **bold** _em_ ~~strike~~\n# heading 1\n## heading 2\n### heading 3\nhttps://cadence.moe\n[legit website](https://cadence.moe)", + "format": "org.matrix.custom.html", + "formatted_body": "
    i should have a little happy test\n
    \n
      \n
    • list bold em ~~strike~~
    • \n
    \n

    heading 1

    \n

    heading 2

    \n

    heading 3

    \n

    https://cadence.moe
    legit website

    \n" + } + }) + } + }), + [{ + username: "cadence", + content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: i should have a little...\n**no you can't!!!**", + avatar_url: undefined + }] + ) +}) + +test("event2message: with layered rich replies, the preview should only be the real text", async t => { + t.deepEqual( + await eventToMessage({ + type: "m.room.message", + sender: "@cadence:cadence.moe", + content: { + msgtype: "m.text", + body: "> <@cadence:cadence.moe> two\n\nthree", + format: "org.matrix.custom.html", + formatted_body: "
    In reply to @cadence:cadence.moe
    two
    three", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04" + } + } + }, + event_id: "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8", + room_id: "!fGgIymcYWOqjbSRUdV:cadence.moe" + }, data.guild.general, { + api: { + getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04", { + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "> <@cadence:cadence.moe> one\n\ntwo", + "format": "org.matrix.custom.html", + "formatted_body": "
    In reply to @cadence:cadence.moe
    one
    two", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$5UtboIC30EFlAYD_Oh0pSYVW8JqOp6GsDIJZHtT0Wls" + } + } + } + }) + } + }), + [{ + username: "cadence", + content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: two\nthree", + avatar_url: undefined + }] + ) +}) From 0ea2b4efc90b4f215a948654ae343a5f61d8bdf9 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 20:30:22 +1200 Subject: [PATCH 190/200] m->d rich replies --- db/data-for-test.sql | 6 +- m2d/actions/send-event.js | 8 +- m2d/converters/event-to-message.js | 37 +++- m2d/converters/event-to-message.test.js | 213 ++++++++++++++++++++---- 4 files changed, 231 insertions(+), 33 deletions(-) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index 7148b19..b8da73c 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -67,7 +67,8 @@ INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES ('820865262526005258', 'crunch_god', '_ooye_crunch_god', '@_ooye_crunch_god:cadence.moe'), ('771520384671416320', 'bojack_horseman', '_ooye_bojack_horseman', '@_ooye_bojack_horseman:cadence.moe'), ('112890272819507200', '.wing.', '_ooye_.wing.', '@_ooye_.wing.:cadence.moe'), -('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'); +('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'), +('111604486476181504', 'kyuugryphon', '_ooye_kyuugryphon', '@_ooye_kyuugryphon:cadence.moe');; INSERT INTO sim_member (mxid, room_id, profile_event_content_hash) VALUES ('@_ooye_bojack_horseman:cadence.moe', '!uCtjHhfGlYbVnPVlkG:cadence.moe', NULL); @@ -86,7 +87,8 @@ INSERT INTO event_message (event_id, event_type, event_subtype, message_id, chan ('$FchUVylsOfmmbj-VwEs5Z9kY49_dt2zd0vWfylzy5Yo', 'm.room.message', 'm.text', '1143121514925928541', '1100319550446252084', 0, 1), ('$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4', 'm.room.message', 'm.text', '1106366167788044450', '122155380120748034', 0, 1), ('$Ijf1MFCD39ktrNHxrA-i2aKoRWNYdAV2ZXYQeiZIgEU', 'm.room.message', 'm.image', '1106366167788044450', '122155380120748034', 0, 0), -('$f9cjKiacXI9qPF_nUAckzbiKnJEi0LM399kOkhdd8f8', 'm.sticker', NULL, '1106366167788044450', '122155380120748034', 0, 0); +('$f9cjKiacXI9qPF_nUAckzbiKnJEi0LM399kOkhdd8f8', 'm.sticker', NULL, '1106366167788044450', '122155380120748034', 0, 0), +('$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04', 'm.room.message', 'm.text', '1144865310588014633', '687028734322147344', 0, 1); INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'), diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 39eed22..016768e 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -9,6 +9,8 @@ const {sync, discord, db} = passthrough const channelWebhook = sync.require("./channel-webhook") /** @type {import("../converters/event-to-message")} */ const eventToMessage = sync.require("../converters/event-to-message") +/** @type {import("../../matrix/api")}) */ +const api = sync.require("../../matrix/api") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { @@ -20,10 +22,14 @@ async function sendEvent(event) { threadID = channelID channelID = row.thread_parent // it's the thread's parent... get with the times... } + // @ts-ignore + const guildID = discord.channels.get(channelID).guild_id + const guild = discord.guilds.get(guildID) + assert(guild) // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it - const messages = eventToMessage.eventToMessage(event) + const messages = await eventToMessage.eventToMessage(event, guild, {api}) assert(Array.isArray(messages)) // sanity /** @type {DiscordTypes.APIMessage[]} */ diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f4382a3..cf34705 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -26,6 +26,8 @@ const turndownService = new TurndownService({ codeBlockStyle: "fenced" }) +turndownService.remove("mx-reply") + turndownService.addRule("strikethrough", { filter: ["del", "s", "strike"], replacement: function (content) { @@ -69,13 +71,16 @@ turndownService.addRule("fencedCodeBlock", { /** * @param {Ty.Event.Outer} event + * @param {import("discord-api-types/v10").APIGuild} guild + * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API */ -function eventToMessage(event) { +async function eventToMessage(event, guild, di) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ let messages = [] let displayName = event.sender let avatarURL = undefined + let replyLine = "" const match = event.sender.match(/^@(.*?):/) if (match) { displayName = match[1] @@ -95,7 +100,33 @@ function eventToMessage(event) { // input = input.replace(/ /g, " ") // There is also a corresponding test to uncomment, named "event2message: whitespace is retained" - // Element adds a bunch of
    before but doesn't render them. I can't figure out how this works, so let's just delete those. + // Handling replies. We'll look up the data of the replied-to event from the Matrix homeserver. + await (async () => { + const repliedToEventId = event.content["m.relates_to"]?.["m.in_reply_to"].event_id + if (!repliedToEventId) return + const repliedToEvent = await di.api.getEvent(event.room_id, repliedToEventId) + if (!repliedToEvent) return + const row = db.prepare("SELECT channel_id, message_id FROM event_message WHERE event_id = ? ORDER BY part").get(repliedToEventId) + if (row) { + replyLine = `<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/${guild.id}/${row.channel_id}/${row.message_id} ` + } else { + replyLine = `<:L1:1144820033948762203><:L2:1144820084079087647>` + } + const sender = repliedToEvent.sender + const senderName = sender.match(/@([^:]*)/)?.[1] || sender + const authorID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(repliedToEvent.sender) + if (authorID) { + replyLine += `<@${authorID}>: ` + } else { + replyLine += `Ⓜ️**${senderName}**: ` + } + const repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body + const contentPreviewChunks = chunk(repliedToContent.replace(/.*<\/mx-reply>/, "").replace(/(?:\n|
    )+/g, " ").replace(/<[^>]+>/g, ""), 24) + const contentPreview = contentPreviewChunks.length > 1 ? contentPreviewChunks[0] + "..." : contentPreviewChunks[0] + replyLine += contentPreview + "\n" + })() + + // Element adds a bunch of
    before but doesn't render them. I can't figure out how this even works in the browser, so let's just delete those. input = input.replace(/(?:\n|
    \s*)*<\/blockquote>/g, "") // The matrix spec hasn't decided whether \n counts as a newline or not, but I'm going to count it, because if it's in the data it's there for a reason. @@ -127,6 +158,8 @@ function eventToMessage(event) { content = content.replace(/([*_~`#])/g, `\\$1`) } + content = replyLine + content + // Split into 2000 character chunks const chunks = chunk(content, 2000) messages = messages.concat(chunks.map(content => ({ diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index afa40de..33c4d71 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -1,18 +1,42 @@ -// @ts-check - const {test} = require("supertape") const {eventToMessage} = require("./event-to-message") const data = require("../../test/data") +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} + function sameFirstContentAndWhitespace(t, a, b) { const a2 = JSON.stringify(a[0].content) const b2 = JSON.stringify(b[0].content) t.equal(a2, b2) } -test("event2message: body is used when there is no formatted_body", t => { +test("event2message: body is used when there is no formatted_body", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { body: "testing plaintext", msgtype: "m.text" @@ -34,9 +58,9 @@ test("event2message: body is used when there is no formatted_body", t => { ) }) -test("event2message: any markdown in body is escaped", t => { +test("event2message: any markdown in body is escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { body: "testing **special** ~~things~~ which _should_ *not* `trigger` @any ", msgtype: "m.text" @@ -58,9 +82,9 @@ test("event2message: any markdown in body is escaped", t => { ) }) -test("event2message: basic html is converted to markdown", t => { +test("event2message: basic html is converted to markdown", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -84,9 +108,9 @@ test("event2message: basic html is converted to markdown", t => { ) }) -test("event2message: markdown syntax is escaped", t => { +test("event2message: markdown syntax is escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -110,9 +134,9 @@ test("event2message: markdown syntax is escaped", t => { ) }) -test("event2message: html lines are bridged correctly", t => { +test("event2message: html lines are bridged correctly", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -136,9 +160,9 @@ test("event2message: html lines are bridged correctly", t => { ) }) -/*test("event2message: whitespace is retained", t => { +/*test("event2message: whitespace is retained", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -162,10 +186,10 @@ test("event2message: html lines are bridged correctly", t => { ) })*/ -test("event2message: whitespace is collapsed", t => { +test("event2message: whitespace is collapsed", async t => { sameFirstContentAndWhitespace( t, - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -189,10 +213,10 @@ test("event2message: whitespace is collapsed", t => { ) }) -test("event2message: lists are bridged correctly", t => { +test("event2message: lists are bridged correctly", async t => { sameFirstContentAndWhitespace( t, - eventToMessage({ + await eventToMessage({ "type": "m.room.message", "sender": "@cadence:cadence.moe", "content": { @@ -217,9 +241,9 @@ test("event2message: lists are bridged correctly", t => { ) }) -test("event2message: long messages are split", t => { +test("event2message: long messages are split", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { body: ("a".repeat(130) + " ").repeat(19), msgtype: "m.text" @@ -245,9 +269,9 @@ test("event2message: long messages are split", t => { ) }) -test("event2message: code blocks work", t => { +test("event2message: code blocks work", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -271,9 +295,9 @@ test("event2message: code blocks work", t => { ) }) -test("event2message: code block contents are formatted correctly and not escaped", t => { +test("event2message: code block contents are formatted correctly and not escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ "type": "m.room.message", "sender": "@cadence:cadence.moe", "content": { @@ -298,9 +322,9 @@ test("event2message: code block contents are formatted correctly and not escaped ) }) -test("event2message: quotes have an appropriate amount of whitespace", t => { +test("event2message: quotes have an appropriate amount of whitespace", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.text", body: "wrong body", @@ -324,9 +348,9 @@ test("event2message: quotes have an appropriate amount of whitespace", t => { ) }) -test("event2message: m.emote markdown syntax is escaped", t => { +test("event2message: m.emote markdown syntax is escaped", async t => { t.deepEqual( - eventToMessage({ + await eventToMessage({ content: { msgtype: "m.emote", body: "wrong body", @@ -349,3 +373,136 @@ test("event2message: m.emote markdown syntax is escaped", t => { }] ) }) + +test("event2message: rich reply to a sim user", 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" + } + } + }, + "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, { + 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" + }) + } + }), + [{ + username: "cadence", + content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 <@111604486476181504>: Slow news day.\nTesting this reply, ignore", + avatar_url: undefined + }] + ) +}) + +test("event2message: rich reply to a matrix user's long message with formatting", async t => { + t.deepEqual( + await eventToMessage({ + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "> <@cadence:cadence.moe> ```\n> i should have a little happy test\n> ```\n> * list **bold** _em_ ~~strike~~\n> # heading 1\n> ## heading 2\n> ### heading 3\n> https://cadence.moe\n> [legit website](https://cadence.moe)\n\nno you can't!!!", + "format": "org.matrix.custom.html", + "formatted_body": "
    In reply to @cadence:cadence.moe
    i should have a little happy test\n
    \n
      \n
    • list bold em ~~strike~~
    • \n
    \n

    heading 1

    \n

    heading 2

    \n

    heading 3

    \n

    https://cadence.moe
    legit website

    \n
    no you can't!!!", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04" + } + } + }, + "origin_server_ts": 1693037401693, + "unsigned": { + "age": 381, + "transaction_id": "m1693037401592.521" + }, + "event_id": "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8", + "room_id": "!fGgIymcYWOqjbSRUdV:cadence.moe" + }, data.guild.general, { + api: { + getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04", { + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "```\ni should have a little happy test\n```\n* list **bold** _em_ ~~strike~~\n# heading 1\n## heading 2\n### heading 3\nhttps://cadence.moe\n[legit website](https://cadence.moe)", + "format": "org.matrix.custom.html", + "formatted_body": "
    i should have a little happy test\n
    \n
      \n
    • list bold em ~~strike~~
    • \n
    \n

    heading 1

    \n

    heading 2

    \n

    heading 3

    \n

    https://cadence.moe
    legit website

    \n" + } + }) + } + }), + [{ + username: "cadence", + content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: i should have a little...\n**no you can't!!!**", + avatar_url: undefined + }] + ) +}) + +test("event2message: with layered rich replies, the preview should only be the real text", async t => { + t.deepEqual( + await eventToMessage({ + type: "m.room.message", + sender: "@cadence:cadence.moe", + content: { + msgtype: "m.text", + body: "> <@cadence:cadence.moe> two\n\nthree", + format: "org.matrix.custom.html", + formatted_body: "
    In reply to @cadence:cadence.moe
    two
    three", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04" + } + } + }, + event_id: "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8", + room_id: "!fGgIymcYWOqjbSRUdV:cadence.moe" + }, data.guild.general, { + api: { + getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$Fxy8SMoJuTduwReVkHZ1uHif9EuvNx36Hg79cltiA04", { + "type": "m.room.message", + "sender": "@cadence:cadence.moe", + "content": { + "msgtype": "m.text", + "body": "> <@cadence:cadence.moe> one\n\ntwo", + "format": "org.matrix.custom.html", + "formatted_body": "
    In reply to @cadence:cadence.moe
    one
    two", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$5UtboIC30EFlAYD_Oh0pSYVW8JqOp6GsDIJZHtT0Wls" + } + } + } + }) + } + }), + [{ + username: "cadence", + content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: two\nthree", + avatar_url: undefined + }] + ) +}) From fd65a57a4c7cf1524e650ab8cdde496a0165c694 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:22:54 +1200 Subject: [PATCH 191/200] show member details on discord from cache --- db/data-for-test.sql | 12 +++++++ m2d/converters/event-to-message.js | 28 ++++++++++++++--- m2d/converters/event-to-message.test.js | 42 ++++++++++++------------- m2d/converters/utils.js | 11 +++++++ 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index b8da73c..8c564df 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -47,6 +47,13 @@ CREATE TABLE IF NOT EXISTS "event_message" ( "source" INTEGER NOT NULL, PRIMARY KEY("event_id","message_id") ); +CREATE TABLE IF NOT EXISTS "member_cache" ( + "room_id" TEXT NOT NULL, + "mxid" TEXT NOT NULL, + "displayname" TEXT, + "avatar_url" TEXT, + PRIMARY KEY("room_id", "mxid") +); COMMIT; @@ -101,4 +108,9 @@ INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'), ('https://cdn.discordapp.com/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024', 'mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL'); +INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES +('!kLRqKKUQXcibIMtOpl:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL), +('!BpMdOUkWWhFxmTrENV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL), +('!fGgIymcYWOqjbSRUdV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'); + COMMIT; diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index cf34705..8363d8f 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -9,6 +9,8 @@ const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +/** @type {import("../converters/utils")} */ +const utils = sync.require("../converters/utils") const BLOCK_ELEMENTS = [ "ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", @@ -69,6 +71,22 @@ turndownService.addRule("fencedCodeBlock", { } }) +/** + * @param {string} roomID + * @param {string} mxid + * @returns {Promise<{displayname?: string?, avatar_url?: string?}>} + */ +async function getMemberFromCacheOrHomeserver(roomID, mxid, api) { + const row = db.prepare("SELECT displayname, avatar_url FROM member_cache WHERE room_id = ? AND mxid = ?").get(roomID, mxid) + if (row) return row + return api.getStateEvent(roomID, "m.room.member", mxid).then(event => { + db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(roomID, mxid, event?.displayname || null, event?.avatar_url || null) + return event + }).catch(() => { + return {displayname: null, avatar_url: null} + }) +} + /** * @param {Ty.Event.Outer} event * @param {import("discord-api-types/v10").APIGuild} guild @@ -81,11 +99,13 @@ async function eventToMessage(event, guild, di) { let displayName = event.sender let avatarURL = undefined let replyLine = "" + // Extract a basic display name from the sender const match = event.sender.match(/^@(.*?):/) - if (match) { - displayName = match[1] - // TODO: get the media repo domain and the avatar url from the matrix member event - } + if (match) displayName = match[1] + // Try to extract an accurate display name and avatar URL from the member event + const member = await getMemberFromCacheOrHomeserver(event.room_id, event.sender, di?.api) + if (member.displayname) displayName = member.displayname + if (member.avatar_url) avatarURL = utils.getPublicUrlForMxc(member.avatar_url) // Convert content depending on what the message is let content = event.content.body // ultimate fallback diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index 33c4d71..b595be8 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -51,7 +51,7 @@ test("event2message: body is used when there is no formatted_body", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "testing plaintext", avatar_url: undefined }] @@ -75,7 +75,7 @@ test("event2message: any markdown in body is escaped", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "testing \\*\\*special\\*\\* \\~\\~things\\~\\~ which \\_should\\_ \\*not\\* \\`trigger\\` @any ", avatar_url: undefined }] @@ -101,7 +101,7 @@ test("event2message: basic html is converted to markdown", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "this **is** a **_test_** of ~~formatting~~", avatar_url: undefined }] @@ -127,7 +127,7 @@ test("event2message: markdown syntax is escaped", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "this \\*\\*is\\*\\* an **_extreme_** \\\\\\*test\\\\\\* of", avatar_url: undefined }] @@ -153,7 +153,7 @@ test("event2message: html lines are bridged correctly", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "paragraph one\nline _two_\nline three\n\nparagraph two\nline _two_\nline three\n\nparagraph three\n\nparagraph four\nline two\nline three\nline four\n\nparagraph five", avatar_url: undefined }] @@ -179,7 +179,7 @@ test("event2message: html lines are bridged correctly", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\n line five", avatar_url: undefined }] @@ -206,7 +206,7 @@ test("event2message: whitespace is collapsed", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\nline five", avatar_url: undefined }] @@ -234,7 +234,7 @@ test("event2message: lists are bridged correctly", async t => { "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" }), [{ - username: "cadence", + username: "cadence [they]", content: "* line one\n* line two\n* line three\n * nested one\n * nested two\n* line four", avatar_url: undefined }] @@ -258,11 +258,11 @@ test("event2message: long messages are split", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: (("a".repeat(130) + " ").repeat(15)).slice(0, -1), avatar_url: undefined }, { - username: "cadence", + username: "cadence [they]", content: (("a".repeat(130) + " ").repeat(4)).slice(0, -1), avatar_url: undefined }] @@ -288,7 +288,7 @@ test("event2message: code blocks work", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "preceding\n\n```\ncode block\n```\n\nfollowing `code` is inline", avatar_url: undefined }] @@ -315,7 +315,7 @@ test("event2message: code block contents are formatted correctly and not escaped "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" }), [{ - username: "cadence", + username: "cadence [they]", content: "```\ninput = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n_input_ = input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n```\n\n`input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,`", avatar_url: undefined }] @@ -341,7 +341,7 @@ test("event2message: quotes have an appropriate amount of whitespace", async t = } }), [{ - username: "cadence", + username: "cadence [they]", content: "> Chancellor of Germany Angela Merkel, on March 17, 2017: they did not shake hands\n🤨", avatar_url: undefined }] @@ -367,8 +367,8 @@ test("event2message: m.emote markdown syntax is escaped", async t => { } }), [{ - username: "cadence", - content: "\\* cadence shows you \\*\\*her\\*\\* **_extreme_** \\\\\\*test\\\\\\* of", + username: "cadence [they]", + content: "\\* cadence \\[they\\] shows you \\*\\*her\\*\\* **_extreme_** \\\\\\*test\\\\\\* of", avatar_url: undefined }] ) @@ -410,9 +410,9 @@ test("event2message: rich reply to a sim user", async t => { } }), [{ - username: "cadence", + 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: undefined + avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU" }] ) }) @@ -455,9 +455,9 @@ test("event2message: rich reply to a matrix user's long message with formatting" } }), [{ - username: "cadence", + username: "cadence [they]", content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: i should have a little...\n**no you can't!!!**", - avatar_url: undefined + avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU" }] ) }) @@ -500,9 +500,9 @@ test("event2message: with layered rich replies, the preview should only be the r } }), [{ - username: "cadence", + username: "cadence [they]", content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: two\nthree", - avatar_url: undefined + avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU" }] ) }) diff --git a/m2d/converters/utils.js b/m2d/converters/utils.js index 7b9c504..02ec147 100644 --- a/m2d/converters/utils.js +++ b/m2d/converters/utils.js @@ -19,4 +19,15 @@ function eventSenderIsFromDiscord(sender) { return false } +/** + * @param {string} mxc + * @returns {string?} + */ +function getPublicUrlForMxc(mxc) { + const avatarURLParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/) + if (avatarURLParts) return `https://matrix.cadence.moe/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}` + else return null +} + module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord +module.exports.getPublicUrlForMxc = getPublicUrlForMxc From 3ebfa8e3a7aa10fd26394b53b1f65cfc3b9149e3 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:22:54 +1200 Subject: [PATCH 192/200] show member details on discord from cache --- db/data-for-test.sql | 12 +++++++ m2d/converters/event-to-message.js | 28 ++++++++++++++--- m2d/converters/event-to-message.test.js | 42 ++++++++++++------------- m2d/converters/utils.js | 11 +++++++ 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/db/data-for-test.sql b/db/data-for-test.sql index b8da73c..8c564df 100644 --- a/db/data-for-test.sql +++ b/db/data-for-test.sql @@ -47,6 +47,13 @@ CREATE TABLE IF NOT EXISTS "event_message" ( "source" INTEGER NOT NULL, PRIMARY KEY("event_id","message_id") ); +CREATE TABLE IF NOT EXISTS "member_cache" ( + "room_id" TEXT NOT NULL, + "mxid" TEXT NOT NULL, + "displayname" TEXT, + "avatar_url" TEXT, + PRIMARY KEY("room_id", "mxid") +); COMMIT; @@ -101,4 +108,9 @@ INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'), ('https://cdn.discordapp.com/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024', 'mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL'); +INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES +('!kLRqKKUQXcibIMtOpl:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL), +('!BpMdOUkWWhFxmTrENV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL), +('!fGgIymcYWOqjbSRUdV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'); + COMMIT; diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index cf34705..8363d8f 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -9,6 +9,8 @@ const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +/** @type {import("../converters/utils")} */ +const utils = sync.require("../converters/utils") const BLOCK_ELEMENTS = [ "ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", @@ -69,6 +71,22 @@ turndownService.addRule("fencedCodeBlock", { } }) +/** + * @param {string} roomID + * @param {string} mxid + * @returns {Promise<{displayname?: string?, avatar_url?: string?}>} + */ +async function getMemberFromCacheOrHomeserver(roomID, mxid, api) { + const row = db.prepare("SELECT displayname, avatar_url FROM member_cache WHERE room_id = ? AND mxid = ?").get(roomID, mxid) + if (row) return row + return api.getStateEvent(roomID, "m.room.member", mxid).then(event => { + db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(roomID, mxid, event?.displayname || null, event?.avatar_url || null) + return event + }).catch(() => { + return {displayname: null, avatar_url: null} + }) +} + /** * @param {Ty.Event.Outer} event * @param {import("discord-api-types/v10").APIGuild} guild @@ -81,11 +99,13 @@ async function eventToMessage(event, guild, di) { let displayName = event.sender let avatarURL = undefined let replyLine = "" + // Extract a basic display name from the sender const match = event.sender.match(/^@(.*?):/) - if (match) { - displayName = match[1] - // TODO: get the media repo domain and the avatar url from the matrix member event - } + if (match) displayName = match[1] + // Try to extract an accurate display name and avatar URL from the member event + const member = await getMemberFromCacheOrHomeserver(event.room_id, event.sender, di?.api) + if (member.displayname) displayName = member.displayname + if (member.avatar_url) avatarURL = utils.getPublicUrlForMxc(member.avatar_url) // Convert content depending on what the message is let content = event.content.body // ultimate fallback diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index 33c4d71..b595be8 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -51,7 +51,7 @@ test("event2message: body is used when there is no formatted_body", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "testing plaintext", avatar_url: undefined }] @@ -75,7 +75,7 @@ test("event2message: any markdown in body is escaped", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "testing \\*\\*special\\*\\* \\~\\~things\\~\\~ which \\_should\\_ \\*not\\* \\`trigger\\` @any ", avatar_url: undefined }] @@ -101,7 +101,7 @@ test("event2message: basic html is converted to markdown", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "this **is** a **_test_** of ~~formatting~~", avatar_url: undefined }] @@ -127,7 +127,7 @@ test("event2message: markdown syntax is escaped", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "this \\*\\*is\\*\\* an **_extreme_** \\\\\\*test\\\\\\* of", avatar_url: undefined }] @@ -153,7 +153,7 @@ test("event2message: html lines are bridged correctly", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "paragraph one\nline _two_\nline three\n\nparagraph two\nline _two_\nline three\n\nparagraph three\n\nparagraph four\nline two\nline three\nline four\n\nparagraph five", avatar_url: undefined }] @@ -179,7 +179,7 @@ test("event2message: html lines are bridged correctly", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\n line five", avatar_url: undefined }] @@ -206,7 +206,7 @@ test("event2message: whitespace is collapsed", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "line one: test test\nline two: **test** **test**\nline three: **test test**\nline four: test test\nline five", avatar_url: undefined }] @@ -234,7 +234,7 @@ test("event2message: lists are bridged correctly", async t => { "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" }), [{ - username: "cadence", + username: "cadence [they]", content: "* line one\n* line two\n* line three\n * nested one\n * nested two\n* line four", avatar_url: undefined }] @@ -258,11 +258,11 @@ test("event2message: long messages are split", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: (("a".repeat(130) + " ").repeat(15)).slice(0, -1), avatar_url: undefined }, { - username: "cadence", + username: "cadence [they]", content: (("a".repeat(130) + " ").repeat(4)).slice(0, -1), avatar_url: undefined }] @@ -288,7 +288,7 @@ test("event2message: code blocks work", async t => { } }), [{ - username: "cadence", + username: "cadence [they]", content: "preceding\n\n```\ncode block\n```\n\nfollowing `code` is inline", avatar_url: undefined }] @@ -315,7 +315,7 @@ test("event2message: code block contents are formatted correctly and not escaped "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" }), [{ - username: "cadence", + username: "cadence [they]", content: "```\ninput = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n_input_ = input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,\n```\n\n`input = input.replace(/(<\\/?([^ >]+)[^>]*>)?\\n(<\\/?([^ >]+)[^>]*>)?/g,`", avatar_url: undefined }] @@ -341,7 +341,7 @@ test("event2message: quotes have an appropriate amount of whitespace", async t = } }), [{ - username: "cadence", + username: "cadence [they]", content: "> Chancellor of Germany Angela Merkel, on March 17, 2017: they did not shake hands\n🤨", avatar_url: undefined }] @@ -367,8 +367,8 @@ test("event2message: m.emote markdown syntax is escaped", async t => { } }), [{ - username: "cadence", - content: "\\* cadence shows you \\*\\*her\\*\\* **_extreme_** \\\\\\*test\\\\\\* of", + username: "cadence [they]", + content: "\\* cadence \\[they\\] shows you \\*\\*her\\*\\* **_extreme_** \\\\\\*test\\\\\\* of", avatar_url: undefined }] ) @@ -410,9 +410,9 @@ test("event2message: rich reply to a sim user", async t => { } }), [{ - username: "cadence", + 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: undefined + avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU" }] ) }) @@ -455,9 +455,9 @@ test("event2message: rich reply to a matrix user's long message with formatting" } }), [{ - username: "cadence", + username: "cadence [they]", content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: i should have a little...\n**no you can't!!!**", - avatar_url: undefined + avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU" }] ) }) @@ -500,9 +500,9 @@ test("event2message: with layered rich replies, the preview should only be the r } }), [{ - username: "cadence", + username: "cadence [they]", content: "<:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1144865310588014633 Ⓜ️**cadence**: two\nthree", - avatar_url: undefined + avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU" }] ) }) diff --git a/m2d/converters/utils.js b/m2d/converters/utils.js index 7b9c504..02ec147 100644 --- a/m2d/converters/utils.js +++ b/m2d/converters/utils.js @@ -19,4 +19,15 @@ function eventSenderIsFromDiscord(sender) { return false } +/** + * @param {string} mxc + * @returns {string?} + */ +function getPublicUrlForMxc(mxc) { + const avatarURLParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/) + if (avatarURLParts) return `https://matrix.cadence.moe/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}` + else return null +} + module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord +module.exports.getPublicUrlForMxc = getPublicUrlForMxc From 6d9a58a31bad6d1941bee3aed52e2024028b06ca Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:50:54 +1200 Subject: [PATCH 193/200] preemptively cache members as we find them --- m2d/converters/event-to-message.js | 2 +- m2d/event-dispatcher.js | 10 ++++++++++ types.d.ts | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 8363d8f..f5d3c90 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -80,7 +80,7 @@ async function getMemberFromCacheOrHomeserver(roomID, mxid, api) { const row = db.prepare("SELECT displayname, avatar_url FROM member_cache WHERE room_id = ? AND mxid = ?").get(roomID, mxid) if (row) return row return api.getStateEvent(roomID, "m.room.member", mxid).then(event => { - db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(roomID, mxid, event?.displayname || null, event?.avatar_url || null) + db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(roomID, mxid, event?.displayname || null, event?.avatar_url || null) return event }).catch(() => { return {displayname: null, avatar_url: null} diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 3425fdb..9a575fc 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -91,3 +91,13 @@ async event => { const name = event.content.name || null db.prepare("UPDATE channel_room SET nick = ? WHERE room_id = ?").run(name, event.room_id) })) + +sync.addTemporaryListener(as, "type:m.room.member", guard("m.room.member", +/** + * @param {Ty.Event.StateOuter} event + */ +async event => { + if (event.state_key[0] !== "@") return + if (utils.eventSenderIsFromDiscord(event.sender)) return + db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(event.room_id, event.sender, event.content.displayname || null, event.content.avatar_url || null) +})) diff --git a/types.d.ts b/types.d.ts index 2bf0af0..5475904 100644 --- a/types.d.ts +++ b/types.d.ts @@ -80,7 +80,7 @@ export namespace Event { export type M_Room_Member = { membership: string - display_name?: string + displayname?: string avatar_url?: string } From 3f2a8d959c10891921e8dcb5e5e6f6d44f9fb727 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:50:54 +1200 Subject: [PATCH 194/200] preemptively cache members as we find them --- m2d/converters/event-to-message.js | 2 +- m2d/event-dispatcher.js | 10 ++++++++++ types.d.ts | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 8363d8f..f5d3c90 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -80,7 +80,7 @@ async function getMemberFromCacheOrHomeserver(roomID, mxid, api) { const row = db.prepare("SELECT displayname, avatar_url FROM member_cache WHERE room_id = ? AND mxid = ?").get(roomID, mxid) if (row) return row return api.getStateEvent(roomID, "m.room.member", mxid).then(event => { - db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(roomID, mxid, event?.displayname || null, event?.avatar_url || null) + db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(roomID, mxid, event?.displayname || null, event?.avatar_url || null) return event }).catch(() => { return {displayname: null, avatar_url: null} diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 3425fdb..9a575fc 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -91,3 +91,13 @@ async event => { const name = event.content.name || null db.prepare("UPDATE channel_room SET nick = ? WHERE room_id = ?").run(name, event.room_id) })) + +sync.addTemporaryListener(as, "type:m.room.member", guard("m.room.member", +/** + * @param {Ty.Event.StateOuter} event + */ +async event => { + if (event.state_key[0] !== "@") return + if (utils.eventSenderIsFromDiscord(event.sender)) return + db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(event.room_id, event.sender, event.content.displayname || null, event.content.avatar_url || null) +})) diff --git a/types.d.ts b/types.d.ts index 2bf0af0..5475904 100644 --- a/types.d.ts +++ b/types.d.ts @@ -80,7 +80,7 @@ export namespace Event { export type M_Room_Member = { membership: string - display_name?: string + displayname?: string avatar_url?: string } From fa1e01215c4fc0b569ca7007722afe2a388b8a77 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:51:42 +1200 Subject: [PATCH 195/200] trying to make reaction emojis consistent --- m2d/actions/add-reaction.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js index 68828dd..49aa845 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -21,9 +21,20 @@ async function addReaction(event) { let encoded = encodeURIComponent(emoji) let encodedTrimmed = encoded.replace(/%EF%B8%8F/g, "") - console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed) + // https://github.com/discord/discord-api-docs/issues/2723#issuecomment-807022205 ???????????? - return discord.snow.channel.createReaction(channelID, messageID, encoded) + const forceTrimmedList = [ + "%E2%AD%90" // ⭐ + ] + + let discordPreferredEncoding = + ( forceTrimmedList.includes(encodedTrimmed) ? encodedTrimmed + : encodedTrimmed !== encoded && [...emoji].length === 2 ? encoded + : encodedTrimmed) + + console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding) + + return discord.snow.channel.createReaction(channelID, messageID, discordPreferredEncoding) } module.exports.addReaction = addReaction From 7c2fc1536dcc0c44ebb838149ccbb4058fadbfd7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:51:42 +1200 Subject: [PATCH 196/200] trying to make reaction emojis consistent --- m2d/actions/add-reaction.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js index 68828dd..49aa845 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -21,9 +21,20 @@ async function addReaction(event) { let encoded = encodeURIComponent(emoji) let encodedTrimmed = encoded.replace(/%EF%B8%8F/g, "") - console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed) + // https://github.com/discord/discord-api-docs/issues/2723#issuecomment-807022205 ???????????? - return discord.snow.channel.createReaction(channelID, messageID, encoded) + const forceTrimmedList = [ + "%E2%AD%90" // ⭐ + ] + + let discordPreferredEncoding = + ( forceTrimmedList.includes(encodedTrimmed) ? encodedTrimmed + : encodedTrimmed !== encoded && [...emoji].length === 2 ? encoded + : encodedTrimmed) + + console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding) + + return discord.snow.channel.createReaction(channelID, messageID, discordPreferredEncoding) } module.exports.addReaction = addReaction From 07afd07207c4db26c4aee024578fe97a5ad965d9 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:59:22 +1200 Subject: [PATCH 197/200] m->d underline formatting support --- m2d/converters/event-to-message.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f5d3c90..d6daed7 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -37,6 +37,13 @@ turndownService.addRule("strikethrough", { } }) +turndownService.addRule("underline", { + filter: ["u"], + replacement: function (content) { + return "__" + content + "__" + } +}) + turndownService.addRule("blockquote", { filter: "blockquote", replacement: function (content) { From 58f5c3edf7852171d6dd0068c4f602cf7e734239 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 22:59:22 +1200 Subject: [PATCH 198/200] m->d underline formatting support --- m2d/converters/event-to-message.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f5d3c90..d6daed7 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -37,6 +37,13 @@ turndownService.addRule("strikethrough", { } }) +turndownService.addRule("underline", { + filter: ["u"], + replacement: function (content) { + return "__" + content + "__" + } +}) + turndownService.addRule("blockquote", { filter: "blockquote", replacement: function (content) { From 5898c94963466ce5c12e9464c8ccff782e7bd81b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 23:22:23 +1200 Subject: [PATCH 199/200] m->d mentioning bridged users and rooms works --- m2d/converters/event-to-message.js | 42 ++++++++++++- m2d/converters/event-to-message.test.js | 78 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index d6daed7..22ed377 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -21,6 +21,10 @@ const BLOCK_ELEMENTS = [ "TFOOT", "TH", "THEAD", "TR", "UL" ] +function cleanAttribute (attribute) { + return attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : "" +} + const turndownService = new TurndownService({ hr: "----", headingStyle: "atx", @@ -53,6 +57,27 @@ turndownService.addRule("blockquote", { } }) +turndownService.addRule("inlineLink", { + filter: function (node, options) { + return ( + options.linkStyle === "inlined" && + node.nodeName === "A" && + node.getAttribute("href") + ) + }, + + replacement: function (content, node) { + if (node.getAttribute("data-user-id")) return `<@${node.getAttribute("data-user-id")}>` + if (node.getAttribute("data-channel-id")) return `<#${node.getAttribute("data-channel-id")}>` + const href = node.getAttribute("href") + let title = cleanAttribute(node.getAttribute("title")) + if (title) title = ` "` + title + `"` + let brackets = ["", ""] + if (href.startsWith("https://matrix.to")) brackets = ["<", ">"] + return "[" + content + "](" + brackets[0] + href + title + brackets[1] + ")" + } +}) + turndownService.addRule("fencedCodeBlock", { filter: function (node, options) { return ( @@ -153,6 +178,21 @@ async function eventToMessage(event, guild, di) { replyLine += contentPreview + "\n" })() + // Handling mentions of Discord users + input = input.replace(/("https:\/\/matrix.to\/#\/(@[^"]+)")>/g, (whole, attributeValue, mxid) => { + if (!utils.eventSenderIsFromDiscord(mxid)) return whole + const userID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(mxid) + if (!userID) return whole + return `${attributeValue} data-user-id="${userID}">` + }) + + // Handling mentions of Discord rooms + input = input.replace(/("https:\/\/matrix.to\/#\/(![^"]+)")>/g, (whole, attributeValue, roomID) => { + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + if (!channelID) return whole + return `${attributeValue} data-channel-id="${channelID}">` + }) + // Element adds a bunch of
    before but doesn't render them. I can't figure out how this even works in the browser, so let's just delete those. input = input.replace(/(?:\n|
    \s*)*<\/blockquote>/g, "") @@ -174,7 +214,7 @@ async function eventToMessage(event, guild, di) { } }) - // @ts-ignore + // @ts-ignore bad type from turndown content = turndownService.turndown(input) // It's optimised for commonmark, we need to replace the space-space-newline with just newline diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index b595be8..61da83c 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -506,3 +506,81 @@ test("event2message: with layered rich replies, the preview should only be the r }] ) }) + +test("event2message: mentioning 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 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 + } + }), + [{ + username: "cadence [they]", + content: "I'm just <@114147806469554185> testing mentions", + avatar_url: undefined + }] + ) +}) + +test("event2message: mentioning matrix 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 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 + } + }), + [{ + username: "cadence [they]", + content: "I'm just [▲]() testing mentions", + avatar_url: undefined + }] + ) +}) + +test("event2message: mentioning bridged rooms works", async t => { + t.deepEqual( + await eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: `I'm just 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 + } + }), + [{ + username: "cadence [they]", + content: "I'm just [▲]() testing mentions", + avatar_url: undefined + }] + ) +}) From cae8d7c2f2434b44555325244bb1c030e2294b67 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 26 Aug 2023 23:22:23 +1200 Subject: [PATCH 200/200] m->d mentioning bridged users and rooms works --- m2d/converters/event-to-message.js | 42 ++++++++++++- m2d/converters/event-to-message.test.js | 78 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index d6daed7..22ed377 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -21,6 +21,10 @@ const BLOCK_ELEMENTS = [ "TFOOT", "TH", "THEAD", "TR", "UL" ] +function cleanAttribute (attribute) { + return attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : "" +} + const turndownService = new TurndownService({ hr: "----", headingStyle: "atx", @@ -53,6 +57,27 @@ turndownService.addRule("blockquote", { } }) +turndownService.addRule("inlineLink", { + filter: function (node, options) { + return ( + options.linkStyle === "inlined" && + node.nodeName === "A" && + node.getAttribute("href") + ) + }, + + replacement: function (content, node) { + if (node.getAttribute("data-user-id")) return `<@${node.getAttribute("data-user-id")}>` + if (node.getAttribute("data-channel-id")) return `<#${node.getAttribute("data-channel-id")}>` + const href = node.getAttribute("href") + let title = cleanAttribute(node.getAttribute("title")) + if (title) title = ` "` + title + `"` + let brackets = ["", ""] + if (href.startsWith("https://matrix.to")) brackets = ["<", ">"] + return "[" + content + "](" + brackets[0] + href + title + brackets[1] + ")" + } +}) + turndownService.addRule("fencedCodeBlock", { filter: function (node, options) { return ( @@ -153,6 +178,21 @@ async function eventToMessage(event, guild, di) { replyLine += contentPreview + "\n" })() + // Handling mentions of Discord users + input = input.replace(/("https:\/\/matrix.to\/#\/(@[^"]+)")>/g, (whole, attributeValue, mxid) => { + if (!utils.eventSenderIsFromDiscord(mxid)) return whole + const userID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(mxid) + if (!userID) return whole + return `${attributeValue} data-user-id="${userID}">` + }) + + // Handling mentions of Discord rooms + input = input.replace(/("https:\/\/matrix.to\/#\/(![^"]+)")>/g, (whole, attributeValue, roomID) => { + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + if (!channelID) return whole + return `${attributeValue} data-channel-id="${channelID}">` + }) + // Element adds a bunch of
    before but doesn't render them. I can't figure out how this even works in the browser, so let's just delete those. input = input.replace(/(?:\n|
    \s*)*<\/blockquote>/g, "") @@ -174,7 +214,7 @@ async function eventToMessage(event, guild, di) { } }) - // @ts-ignore + // @ts-ignore bad type from turndown content = turndownService.turndown(input) // It's optimised for commonmark, we need to replace the space-space-newline with just newline diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index b595be8..61da83c 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -506,3 +506,81 @@ test("event2message: with layered rich replies, the preview should only be the r }] ) }) + +test("event2message: mentioning 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 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 + } + }), + [{ + username: "cadence [they]", + content: "I'm just <@114147806469554185> testing mentions", + avatar_url: undefined + }] + ) +}) + +test("event2message: mentioning matrix 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 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 + } + }), + [{ + username: "cadence [they]", + content: "I'm just [▲]() testing mentions", + avatar_url: undefined + }] + ) +}) + +test("event2message: mentioning bridged rooms works", async t => { + t.deepEqual( + await eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: `I'm just 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 + } + }), + [{ + username: "cadence [they]", + content: "I'm just [▲]() testing mentions", + avatar_url: undefined + }] + ) +})

    6nlt!XcnroN4Jj%@W5t` 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 I=lr$*2kx_yGynhq delta 8769 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 q)jove*pSr=a(5Hv^MGt0Y$UBY{RTbwa)(u&R { diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 5eb8c04..56a660f 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -12,28 +12,22 @@ const eventToMessage = sync.require("../converters/event-to-message") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { - // TODO: matrix equivalents... - const roomID = await createRoom.ensureRoom(message.channel_id) - // TODO: no need to sync the member to the other side... right? - let senderMxid = null - if (!message.webhook_id) { - assert(message.member) - senderMxid = await registerUser.ensureSimJoined(message.author, roomID) - await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) - } + // TODO: we just assume the bridge has already been created + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + + // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it const messages = eventToMessage.eventToMessage(event) - assert(Array.isArray(messages)) + assert(Array.isArray(messages)) // sanity /** @type {DiscordTypes.APIMessage[]} */ const messageResponses = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) - // TODO: are you sure about that? many to many? and we don't need to store which side it originated from? - db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(event.event_id, messageResponse.id, eventPart) + db.prepare("INSERT INTO event_message (event_id, message_id, part, source) VALUES (?, ?, ?, 0)").run(event.event_id, messageResponse.id, eventPart) // source 0 = matrix - eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? messageResponses.push(messageResponse) } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f48b5da..8b41903 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -1,6 +1,5 @@ // @ts-check -const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") const markdown = require("discord-markdown") diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index a41beef..e687059 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -1,14 +1,12 @@ // @ts-check const {test} = require("supertape") -const assert = require("assert") const {eventToMessage} = require("./event-to-message") const data = require("../../test/data") test("event2message: janky test", t => { t.deepEqual( eventToMessage({ - age: 405299, content: { body: "test", msgtype: "m.text" @@ -20,8 +18,7 @@ test("event2message: janky test", t => { type: "m.room.message", unsigned: { age: 405299 - }, - user_id: "@cadence:cadence.moe" + } }), [{ username: "cadence:cadence.moe", diff --git a/m2d/converters/utils.js b/m2d/converters/utils.js new file mode 100644 index 0000000..108da1f --- /dev/null +++ b/m2d/converters/utils.js @@ -0,0 +1,21 @@ +// @ts-check + +const reg = require("../../matrix/read-registration") +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) +/** + * Determine whether an event is the bridged representation of a discord message. + * Such messages shouldn't be bridged again. + * @param {string} sender + */ +function eventSenderIsFromDiscord(sender) { + // If it's from a user in the bridge's namespace, then it originated from discord + // This includes messages sent by the appservice's bot user, because that is what's used for webhooks + // TODO: It would be nice if bridge system messages wouldn't trigger this check and could be bridged from matrix to discord, while webhook reflections would remain ignored... + if (userRegex.some(x => sender.match(x))) { + return true + } + + return false +} + +module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord diff --git a/m2d/converters/utils.test.js b/m2d/converters/utils.test.js new file mode 100644 index 0000000..ae3159e --- /dev/null +++ b/m2d/converters/utils.test.js @@ -0,0 +1,16 @@ +// @ts-check + +const {test} = require("supertape") +const {eventSenderIsFromDiscord} = require("./utils") + +test("sender type: matrix user", t => { + t.notOk(eventSenderIsFromDiscord("@cadence:cadence.moe")) +}) + +test("sender type: ooye bot", t => { + t.ok(eventSenderIsFromDiscord("@_ooye_bot:cadence.moe")) +}) + +test("sender type: ooye puppet", t => { + t.ok(eventSenderIsFromDiscord("@_ooye_sheep:cadence.moe")) +}) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index b8bfacf..01a3dcc 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -4,34 +4,19 @@ * Grab Matrix events we care about, check them, and bridge them. */ -const assert = require("assert").strict const {sync, as} = require("../passthrough") -const reg = require("../matrix/read-registration") + /** @type {import("./actions/send-event")} */ const sendEvent = sync.require("./actions/send-event") +/** @type {import("./converters/utils")} */ +const utils = sync.require("./converters/utils") -const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) + +sync.addTemporaryListener(as, "type:m.room.message", /** - * Determine whether an event is the bridged representation of a discord message. - * Such messages shouldn't be bridged again. - * @param {import("../types").Event.Outer} event + * @param {import("../types").Event.Outer} event it is a m.room.message because that's what this listener is filtering for */ -function eventOriginatedFromDiscord(event) { - if ( - // If it's from a user in the bridge's namespace... - userRegex.some(x => event.sender.match(x)) - // ...not counting the appservice's own user... - && !event.sender.startsWith(`@${reg.sender_localpart}:`) - ) { - // ...then it originated from discord - return true - } - - return false -} - -sync.addTemporaryListener(as, "type:m.room.message", event => { - console.log(event) - if (eventOriginatedFromDiscord(event)) return - const messageResponses = sendEvent.sendEvent(event) +async event => { + if (utils.eventSenderIsFromDiscord(event.sender)) return + const messageResponses = await sendEvent.sendEvent(event) }) diff --git a/matrix/api.test.js b/matrix/api.test.js index f54c665..6c74e50 100644 --- a/matrix/api.test.js +++ b/matrix/api.test.js @@ -1,5 +1,4 @@ const {test} = require("supertape") -const assert = require("assert") const {path} = require("./api") test("api path: no change for plain path", t => { diff --git a/matrix/read-registration.test.js b/matrix/read-registration.test.js index 9c7f828..c5b3ac8 100644 --- a/matrix/read-registration.test.js +++ b/matrix/read-registration.test.js @@ -1,5 +1,4 @@ const {test} = require("supertape") -const assert = require("assert") const reg = require("./read-registration") test("reg: has necessary parameters", t => { @@ -8,4 +7,4 @@ test("reg: has necessary parameters", t => { propertiesToCheck.filter(p => p in reg), propertiesToCheck ) -}) \ No newline at end of file +}) diff --git a/stdin.js b/stdin.js index 1a5b8d1..cd504f2 100644 --- a/stdin.js +++ b/stdin.js @@ -11,7 +11,7 @@ const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") -const sendMessage = sync.require("./m2d/actions/send-message") +const sendEvent = sync.require("./m2d/actions/send-event") const guildID = "112760669178241024" const extraContext = {} diff --git a/test/test.js b/test/test.js index f2f0912..5805d09 100644 --- a/test/test.js +++ b/test/test.js @@ -19,3 +19,4 @@ require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") require("../m2d/converters/event-to-message.test") +require("../m2d/converters/utils.test") From 39cdba9f906b469d7264a64dcd2976d54810388b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 4 Jul 2023 00:39:42 +1200 Subject: [PATCH 053/200] bridge both ways and prevent reflections --- d2m/actions/add-reaction.js | 1 - d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.test.js | 1 - d2m/event-dispatcher.js | 9 ++++++- m2d/actions/channel-webhook.js | 2 +- m2d/actions/send-event.js | 20 ++++++--------- m2d/converters/event-to-message.js | 1 - m2d/converters/event-to-message.test.js | 5 +--- m2d/converters/utils.js | 21 ++++++++++++++++ m2d/converters/utils.test.js | 16 ++++++++++++ m2d/event-dispatcher.js | 33 +++++++------------------ matrix/api.test.js | 1 - matrix/read-registration.test.js | 3 +-- stdin.js | 2 +- test/test.js | 1 + 15 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 m2d/converters/utils.js create mode 100644 m2d/converters/utils.test.js diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index 82449cd..cd3d296 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -17,7 +17,6 @@ const createRoom = sync.require("../actions/create-room") 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") diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 24a825a..4f111b0 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -37,7 +37,7 @@ async function sendMessage(message, guild) { delete eventWithoutType.$type const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, eventPart) + db.prepare("INSERT INTO event_message (event_id, message_id, part, source) VALUES (?, ?, ?, 1)").run(eventID, message.id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting eventIDs.push(eventID) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index c318389..26cf1f1 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -1,5 +1,4 @@ const {test} = require("supertape") -const assert = require("assert") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 1a1e30a..99c7792 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,5 +1,5 @@ const assert = require("assert").strict -const {sync} = require("../passthrough") +const {sync, db} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") @@ -18,6 +18,13 @@ module.exports = { const channel = client.channels.get(message.channel_id) const guild = client.guilds.get(channel.guild_id) if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) + if (message.webhook_id) { + const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + if (row) { + // The message was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. + return + } + } sendMessage.sendMessage(message, guild) }, diff --git a/m2d/actions/channel-webhook.js b/m2d/actions/channel-webhook.js index 5e56859..b62057b 100644 --- a/m2d/actions/channel-webhook.js +++ b/m2d/actions/channel-webhook.js @@ -49,7 +49,7 @@ async function withWebhook(channelID, callback) { /** * @param {string} channelID - * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}[]} data + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]}} data */ async function sendMessageWithWebhook(channelID, data) { const result = await withWebhook(channelID, async webhook => { diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 5eb8c04..56a660f 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -12,28 +12,22 @@ const eventToMessage = sync.require("../converters/event-to-message") /** @param {import("../../types").Event.Outer} event */ async function sendEvent(event) { - // TODO: matrix equivalents... - const roomID = await createRoom.ensureRoom(message.channel_id) - // TODO: no need to sync the member to the other side... right? - let senderMxid = null - if (!message.webhook_id) { - assert(message.member) - senderMxid = await registerUser.ensureSimJoined(message.author, roomID) - await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) - } + // TODO: we just assume the bridge has already been created + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + + // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it const messages = eventToMessage.eventToMessage(event) - assert(Array.isArray(messages)) + assert(Array.isArray(messages)) // sanity /** @type {DiscordTypes.APIMessage[]} */ const messageResponses = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) - // TODO: are you sure about that? many to many? and we don't need to store which side it originated from? - db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(event.event_id, messageResponse.id, eventPart) + db.prepare("INSERT INTO event_message (event_id, message_id, part, source) VALUES (?, ?, ?, 0)").run(event.event_id, messageResponse.id, eventPart) // source 0 = matrix - eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? messageResponses.push(messageResponse) } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index f48b5da..8b41903 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -1,6 +1,5 @@ // @ts-check -const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") const markdown = require("discord-markdown") diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index a41beef..e687059 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -1,14 +1,12 @@ // @ts-check const {test} = require("supertape") -const assert = require("assert") const {eventToMessage} = require("./event-to-message") const data = require("../../test/data") test("event2message: janky test", t => { t.deepEqual( eventToMessage({ - age: 405299, content: { body: "test", msgtype: "m.text" @@ -20,8 +18,7 @@ test("event2message: janky test", t => { type: "m.room.message", unsigned: { age: 405299 - }, - user_id: "@cadence:cadence.moe" + } }), [{ username: "cadence:cadence.moe", diff --git a/m2d/converters/utils.js b/m2d/converters/utils.js new file mode 100644 index 0000000..108da1f --- /dev/null +++ b/m2d/converters/utils.js @@ -0,0 +1,21 @@ +// @ts-check + +const reg = require("../../matrix/read-registration") +const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) +/** + * Determine whether an event is the bridged representation of a discord message. + * Such messages shouldn't be bridged again. + * @param {string} sender + */ +function eventSenderIsFromDiscord(sender) { + // If it's from a user in the bridge's namespace, then it originated from discord + // This includes messages sent by the appservice's bot user, because that is what's used for webhooks + // TODO: It would be nice if bridge system messages wouldn't trigger this check and could be bridged from matrix to discord, while webhook reflections would remain ignored... + if (userRegex.some(x => sender.match(x))) { + return true + } + + return false +} + +module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord diff --git a/m2d/converters/utils.test.js b/m2d/converters/utils.test.js new file mode 100644 index 0000000..ae3159e --- /dev/null +++ b/m2d/converters/utils.test.js @@ -0,0 +1,16 @@ +// @ts-check + +const {test} = require("supertape") +const {eventSenderIsFromDiscord} = require("./utils") + +test("sender type: matrix user", t => { + t.notOk(eventSenderIsFromDiscord("@cadence:cadence.moe")) +}) + +test("sender type: ooye bot", t => { + t.ok(eventSenderIsFromDiscord("@_ooye_bot:cadence.moe")) +}) + +test("sender type: ooye puppet", t => { + t.ok(eventSenderIsFromDiscord("@_ooye_sheep:cadence.moe")) +}) diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index b8bfacf..01a3dcc 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -4,34 +4,19 @@ * Grab Matrix events we care about, check them, and bridge them. */ -const assert = require("assert").strict const {sync, as} = require("../passthrough") -const reg = require("../matrix/read-registration") + /** @type {import("./actions/send-event")} */ const sendEvent = sync.require("./actions/send-event") +/** @type {import("./converters/utils")} */ +const utils = sync.require("./converters/utils") -const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) + +sync.addTemporaryListener(as, "type:m.room.message", /** - * Determine whether an event is the bridged representation of a discord message. - * Such messages shouldn't be bridged again. - * @param {import("../types").Event.Outer} event + * @param {import("../types").Event.Outer} event it is a m.room.message because that's what this listener is filtering for */ -function eventOriginatedFromDiscord(event) { - if ( - // If it's from a user in the bridge's namespace... - userRegex.some(x => event.sender.match(x)) - // ...not counting the appservice's own user... - && !event.sender.startsWith(`@${reg.sender_localpart}:`) - ) { - // ...then it originated from discord - return true - } - - return false -} - -sync.addTemporaryListener(as, "type:m.room.message", event => { - console.log(event) - if (eventOriginatedFromDiscord(event)) return - const messageResponses = sendEvent.sendEvent(event) +async event => { + if (utils.eventSenderIsFromDiscord(event.sender)) return + const messageResponses = await sendEvent.sendEvent(event) }) diff --git a/matrix/api.test.js b/matrix/api.test.js index f54c665..6c74e50 100644 --- a/matrix/api.test.js +++ b/matrix/api.test.js @@ -1,5 +1,4 @@ const {test} = require("supertape") -const assert = require("assert") const {path} = require("./api") test("api path: no change for plain path", t => { diff --git a/matrix/read-registration.test.js b/matrix/read-registration.test.js index 9c7f828..c5b3ac8 100644 --- a/matrix/read-registration.test.js +++ b/matrix/read-registration.test.js @@ -1,5 +1,4 @@ const {test} = require("supertape") -const assert = require("assert") const reg = require("./read-registration") test("reg: has necessary parameters", t => { @@ -8,4 +7,4 @@ test("reg: has necessary parameters", t => { propertiesToCheck.filter(p => p in reg), propertiesToCheck ) -}) \ No newline at end of file +}) diff --git a/stdin.js b/stdin.js index 1a5b8d1..cd504f2 100644 --- a/stdin.js +++ b/stdin.js @@ -11,7 +11,7 @@ const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") const mreq = sync.require("./matrix/mreq") const api = sync.require("./matrix/api") -const sendMessage = sync.require("./m2d/actions/send-message") +const sendEvent = sync.require("./m2d/actions/send-event") const guildID = "112760669178241024" const extraContext = {} diff --git a/test/test.js b/test/test.js index f2f0912..5805d09 100644 --- a/test/test.js +++ b/test/test.js @@ -19,3 +19,4 @@ require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") require("../d2m/actions/register-user.test") require("../m2d/converters/event-to-message.test") +require("../m2d/converters/utils.test") From 7a5b857d0a1fe02e99c862be55164e47bb19fe4c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 4 Jul 2023 17:19:17 +1200 Subject: [PATCH 054/200] m2d reactions (untested) --- d2m/actions/add-reaction.js | 4 ++-- m2d/actions/add-reaction.js | 25 +++++++++++++++++++++++++ m2d/converters/event-to-message.js | 3 ++- m2d/event-dispatcher.js | 15 +++++++++++++-- matrix/api.js | 13 +++++++------ types.d.ts | 8 ++++++++ types.js | 1 + 7 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 m2d/actions/add-reaction.js create mode 100644 types.js diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index cd3d296..49afca9 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -1,6 +1,6 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough @@ -18,7 +18,7 @@ async function addReaction(data) { const user = data.member?.user assert.ok(user && user.username) 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? + if (!parentID) return // Nothing can be done if the parent message was never bridged. assert.equal(typeof parentID, "string") const roomID = await createRoom.ensureRoom(data.channel_id) const senderMxid = await registerUser.ensureSimJoined(user, roomID) diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js new file mode 100644 index 0000000..342550d --- /dev/null +++ b/m2d/actions/add-reaction.js @@ -0,0 +1,25 @@ +// @ts-check + +const assert = require("assert").strict +const Ty = require("../../types") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough + +/** + * @param {Ty.Event.Outer} event + */ +async function addReaction(event) { + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + if (!channelID) return // We just assume the bridge has already been created + const messageID = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? AND part = 0").pluck().get(event.content["m.relates_to"].event_id) // 0 = primary + if (!messageID) return // Nothing can be done if the parent message was never bridged. + + // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it + + const emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions + + return discord.snow.channel.createReaction(channelID, messageID, emoji) +} + +module.exports.addReaction = addReaction diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 8b41903..817ffff 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -1,5 +1,6 @@ // @ts-check +const Ty = require("../../types") const DiscordTypes = require("discord-api-types/v10") const markdown = require("discord-markdown") @@ -9,7 +10,7 @@ const { sync, db, discord } = passthrough const file = sync.require("../../matrix/file") /** - * @param {import("../../types").Event.Outer} event + * @param {Ty.Event.Outer} event */ function eventToMessage(event) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 01a3dcc..82ebd75 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -4,19 +4,30 @@ * Grab Matrix events we care about, check them, and bridge them. */ +const Ty = require("../types") const {sync, as} = require("../passthrough") /** @type {import("./actions/send-event")} */ const sendEvent = sync.require("./actions/send-event") +/** @type {import("./actions/add-reaction")} */ +const addReaction = sync.require("./actions/add-reaction") /** @type {import("./converters/utils")} */ const utils = sync.require("./converters/utils") - sync.addTemporaryListener(as, "type:m.room.message", /** - * @param {import("../types").Event.Outer} event it is a m.room.message because that's what this listener is filtering for + * @param {Ty.Event.Outer} event it is a m.room.message because that's what this listener is filtering for */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return const messageResponses = await sendEvent.sendEvent(event) }) + +sync.addTemporaryListener(as, "type:m.reaction", +/** + * @param {Ty.Event.Outer} event it is a m.reaction because that's what this listener is filtering for + */ +async event => { + if (utils.eventSenderIsFromDiscord(event.sender)) return + await addReaction.addReaction(event) +}) diff --git a/matrix/api.js b/matrix/api.js index ec85795..cf22933 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -1,5 +1,6 @@ // @ts-check +const Ty = require("../types") const assert = require("assert") const passthrough = require("../passthrough") @@ -25,7 +26,7 @@ function path(p, mxid) { /** * @param {string} username - * @returns {Promise} + * @returns {Promise} */ function register(username) { console.log(`[api] register: ${username}`) @@ -40,7 +41,7 @@ function register(username) { */ async function createRoom(content) { console.log(`[api] create room:`, content) - /** @type {import("../types").R.RoomCreated} */ + /** @type {Ty.R.RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", content) return root.room_id } @@ -49,7 +50,7 @@ async function createRoom(content) { * @returns {Promise} room ID */ async function joinRoom(roomIDOrAlias, mxid) { - /** @type {import("../types").R.RoomJoined} */ + /** @type {Ty.R.RoomJoined} */ const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) return root.room_id } @@ -66,7 +67,7 @@ async function leaveRoom(roomID, mxid) { /** * @param {string} roomID - * @returns {Promise} + * @returns {Promise} */ function getAllState(roomID) { return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) @@ -83,14 +84,14 @@ async function sendState(roomID, type, stateKey, content, mxid) { console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) assert.ok(type) assert.ok(typeof stateKey === "string") - /** @type {import("../types").R.EventSent} */ + /** @type {Ty.R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) return root.event_id } async function sendEvent(roomID, type, content, mxid) { console.log(`[api] event to ${roomID} as ${mxid || "default sim"}`) - /** @type {import("../types").R.EventSent} */ + /** @type {Ty.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/types.d.ts b/types.d.ts index 19ef1f2..01ff6a1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -62,6 +62,14 @@ namespace Event { display_name?: string avatar_url?: string } + + export type M_Reaction = { + "m.relates_to": { + rel_type: "m.annotation" + event_id: string // the event that was reacted to + key: string // the unicode emoji, mxc uri, or reaction text + } + } } namespace R { diff --git a/types.js b/types.js new file mode 100644 index 0000000..4ba52ba --- /dev/null +++ b/types.js @@ -0,0 +1 @@ +module.exports = {} From 61120d92c6b1cadc66e5b1433a9804b55689fc98 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 4 Jul 2023 17:19:17 +1200 Subject: [PATCH 055/200] m2d reactions (untested) --- d2m/actions/add-reaction.js | 4 ++-- m2d/actions/add-reaction.js | 25 +++++++++++++++++++++++++ m2d/converters/event-to-message.js | 3 ++- m2d/event-dispatcher.js | 15 +++++++++++++-- matrix/api.js | 13 +++++++------ types.d.ts | 8 ++++++++ types.js | 1 + 7 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 m2d/actions/add-reaction.js create mode 100644 types.js diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index cd3d296..49afca9 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -1,6 +1,6 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough @@ -18,7 +18,7 @@ async function addReaction(data) { const user = data.member?.user assert.ok(user && user.username) 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? + if (!parentID) return // Nothing can be done if the parent message was never bridged. assert.equal(typeof parentID, "string") const roomID = await createRoom.ensureRoom(data.channel_id) const senderMxid = await registerUser.ensureSimJoined(user, roomID) diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js new file mode 100644 index 0000000..342550d --- /dev/null +++ b/m2d/actions/add-reaction.js @@ -0,0 +1,25 @@ +// @ts-check + +const assert = require("assert").strict +const Ty = require("../../types") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough + +/** + * @param {Ty.Event.Outer} event + */ +async function addReaction(event) { + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + if (!channelID) return // We just assume the bridge has already been created + const messageID = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? AND part = 0").pluck().get(event.content["m.relates_to"].event_id) // 0 = primary + if (!messageID) return // Nothing can be done if the parent message was never bridged. + + // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it + + const emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions + + return discord.snow.channel.createReaction(channelID, messageID, emoji) +} + +module.exports.addReaction = addReaction diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 8b41903..817ffff 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -1,5 +1,6 @@ // @ts-check +const Ty = require("../../types") const DiscordTypes = require("discord-api-types/v10") const markdown = require("discord-markdown") @@ -9,7 +10,7 @@ const { sync, db, discord } = passthrough const file = sync.require("../../matrix/file") /** - * @param {import("../../types").Event.Outer} event + * @param {Ty.Event.Outer} event */ function eventToMessage(event) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 01a3dcc..82ebd75 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -4,19 +4,30 @@ * Grab Matrix events we care about, check them, and bridge them. */ +const Ty = require("../types") const {sync, as} = require("../passthrough") /** @type {import("./actions/send-event")} */ const sendEvent = sync.require("./actions/send-event") +/** @type {import("./actions/add-reaction")} */ +const addReaction = sync.require("./actions/add-reaction") /** @type {import("./converters/utils")} */ const utils = sync.require("./converters/utils") - sync.addTemporaryListener(as, "type:m.room.message", /** - * @param {import("../types").Event.Outer} event it is a m.room.message because that's what this listener is filtering for + * @param {Ty.Event.Outer} event it is a m.room.message because that's what this listener is filtering for */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return const messageResponses = await sendEvent.sendEvent(event) }) + +sync.addTemporaryListener(as, "type:m.reaction", +/** + * @param {Ty.Event.Outer} event it is a m.reaction because that's what this listener is filtering for + */ +async event => { + if (utils.eventSenderIsFromDiscord(event.sender)) return + await addReaction.addReaction(event) +}) diff --git a/matrix/api.js b/matrix/api.js index ec85795..cf22933 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -1,5 +1,6 @@ // @ts-check +const Ty = require("../types") const assert = require("assert") const passthrough = require("../passthrough") @@ -25,7 +26,7 @@ function path(p, mxid) { /** * @param {string} username - * @returns {Promise} + * @returns {Promise} */ function register(username) { console.log(`[api] register: ${username}`) @@ -40,7 +41,7 @@ function register(username) { */ async function createRoom(content) { console.log(`[api] create room:`, content) - /** @type {import("../types").R.RoomCreated} */ + /** @type {Ty.R.RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", content) return root.room_id } @@ -49,7 +50,7 @@ async function createRoom(content) { * @returns {Promise} room ID */ async function joinRoom(roomIDOrAlias, mxid) { - /** @type {import("../types").R.RoomJoined} */ + /** @type {Ty.R.RoomJoined} */ const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) return root.room_id } @@ -66,7 +67,7 @@ async function leaveRoom(roomID, mxid) { /** * @param {string} roomID - * @returns {Promise} + * @returns {Promise} */ function getAllState(roomID) { return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) @@ -83,14 +84,14 @@ async function sendState(roomID, type, stateKey, content, mxid) { console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) assert.ok(type) assert.ok(typeof stateKey === "string") - /** @type {import("../types").R.EventSent} */ + /** @type {Ty.R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) return root.event_id } async function sendEvent(roomID, type, content, mxid) { console.log(`[api] event to ${roomID} as ${mxid || "default sim"}`) - /** @type {import("../types").R.EventSent} */ + /** @type {Ty.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/types.d.ts b/types.d.ts index 19ef1f2..01ff6a1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -62,6 +62,14 @@ namespace Event { display_name?: string avatar_url?: string } + + export type M_Reaction = { + "m.relates_to": { + rel_type: "m.annotation" + event_id: string // the event that was reacted to + key: string // the unicode emoji, mxc uri, or reaction text + } + } } namespace R { diff --git a/types.js b/types.js new file mode 100644 index 0000000..4ba52ba --- /dev/null +++ b/types.js @@ -0,0 +1 @@ +module.exports = {} From e2adff145f1219fa11fa226f00deead6f12f303a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 4 Jul 2023 17:35:29 +1200 Subject: [PATCH 056/200] add tests for convertNameAndTopic --- d2m/actions/create-room.js | 42 +++++++++++++++++++++------------ d2m/actions/create-room.test.js | 32 ++++++++++++++++++++++++- test/data.js | 6 ++--- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 479bb79..98333b2 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -32,28 +32,19 @@ function applyKStateDiffToRoom(roomID, kstate) { } /** - * @param {DiscordTypes.APIGuildTextChannel} channel - * @param {DiscordTypes.APIGuild} guild + * @param {{id: string, name: string, topic?: string?}} channel + * @param {{id: string}} guild + * @param {string?} customName */ -async function channelToKState(channel, guild) { - const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) - assert.ok(typeof spaceID === "string") - const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) - - const avatarEventContent = {} - if (guild.icon) { - avatarEventContent.discord_path = file.guildIcon(guild) - avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API - } - +function convertNameAndTopic(channel, guild, customName) { // TODO: Improve nasty nested ifs let convertedName, convertedTopic if (customName) { convertedName = customName if (channel.topic) { - convertedTopic = `${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + convertedTopic = `#${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` } else { - convertedTopic = `${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + convertedTopic = `#${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` } } else { convertedName = channel.name @@ -64,6 +55,26 @@ async function channelToKState(channel, guild) { } } + return [convertedName, convertedTopic] +} + +/** + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuild} guild + */ +async function channelToKState(channel, guild) { + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) + assert.ok(typeof spaceID === "string") + + const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) + + const avatarEventContent = {} + if (guild.icon) { + avatarEventContent.discord_path = file.guildIcon(guild) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API + } + const channelKState = { "m.room.name/": {name: convertedName}, "m.room.topic/": {topic: convertedTopic}, @@ -209,3 +220,4 @@ module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState +module.exports._convertNameAndTopic = convertNameAndTopic diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js index ab390fc..ec5c3d3 100644 --- a/d2m/actions/create-room.test.js +++ b/d2m/actions/create-room.test.js @@ -1,4 +1,6 @@ -const {channelToKState} = require("./create-room") +// @ts-check + +const {channelToKState, _convertNameAndTopic} = require("./create-room") const {kstateStripConditionals} = require("../../matrix/kstate") const {test} = require("supertape") const testData = require("../../test/data") @@ -9,3 +11,31 @@ test("channel2room: general", async t => { testData.room.general ) }) + +test("convertNameAndTopic: custom name and topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, "hauntings"), + ["hauntings", "#the-twilight-zone | Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: custom name, no topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, "hauntings"), + ["hauntings", "#the-twilight-zone\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: original name and topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, null), + ["the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: original name, no topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, null), + ["the-twilight-zone", "Channel ID: 123\nGuild ID: 456"] + ) +}) diff --git a/test/data.js b/test/data.js index 5efea36..49bbeaf 100644 --- a/test/data.js +++ b/test/data.js @@ -23,7 +23,7 @@ module.exports = { room: { general: { "m.room.name/": {name: "main"}, - "m.room.topic/": {topic: "collective-unconscious | https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:\n\nChannel ID: 112760669178241024\nGuild ID: 112760669178241024"}, + "m.room.topic/": {topic: "#collective-unconscious | https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:\n\nChannel ID: 112760669178241024\nGuild ID: 112760669178241024"}, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { @@ -48,7 +48,6 @@ module.exports = { owner_id: "112760500130975744", premium_tier: 3, stickers: [{ - version: 1683838696974, type: 2, tags: "sunglasses", name: "pomu puff", @@ -56,8 +55,7 @@ module.exports = { guild_id: "112760669178241024", format_type: 1, description: "damn that tiny lil bitch really chuffing. puffing that fat ass dart", - available: true, - asset: "" + available: true }], max_members: 500000, splash: "86a34ed02524b972918bef810087f8e7", From 4cd9da49fd4690bbd873623b265462a393cf7f71 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 4 Jul 2023 17:35:29 +1200 Subject: [PATCH 057/200] add tests for convertNameAndTopic --- d2m/actions/create-room.js | 42 +++++++++++++++++++++------------ d2m/actions/create-room.test.js | 32 ++++++++++++++++++++++++- test/data.js | 6 ++--- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 479bb79..98333b2 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -32,28 +32,19 @@ function applyKStateDiffToRoom(roomID, kstate) { } /** - * @param {DiscordTypes.APIGuildTextChannel} channel - * @param {DiscordTypes.APIGuild} guild + * @param {{id: string, name: string, topic?: string?}} channel + * @param {{id: string}} guild + * @param {string?} customName */ -async function channelToKState(channel, guild) { - const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) - assert.ok(typeof spaceID === "string") - const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) - - const avatarEventContent = {} - if (guild.icon) { - avatarEventContent.discord_path = file.guildIcon(guild) - avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API - } - +function convertNameAndTopic(channel, guild, customName) { // TODO: Improve nasty nested ifs let convertedName, convertedTopic if (customName) { convertedName = customName if (channel.topic) { - convertedTopic = `${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + convertedTopic = `#${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` } else { - convertedTopic = `${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` + convertedTopic = `#${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` } } else { convertedName = channel.name @@ -64,6 +55,26 @@ async function channelToKState(channel, guild) { } } + return [convertedName, convertedTopic] +} + +/** + * @param {DiscordTypes.APIGuildTextChannel} channel + * @param {DiscordTypes.APIGuild} guild + */ +async function channelToKState(channel, guild) { + const spaceID = db.prepare("SELECT space_id FROM guild_space WHERE guild_id = ?").pluck().get(guild.id) + assert.ok(typeof spaceID === "string") + + const customName = db.prepare("SELECT nick FROM channel_room WHERE channel_id = ?").pluck().get(channel.id) + const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, customName) + + const avatarEventContent = {} + if (guild.icon) { + avatarEventContent.discord_path = file.guildIcon(guild) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API + } + const channelKState = { "m.room.name/": {name: convertedName}, "m.room.topic/": {topic: convertedTopic}, @@ -209,3 +220,4 @@ module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom module.exports.createAllForGuild = createAllForGuild module.exports.channelToKState = channelToKState +module.exports._convertNameAndTopic = convertNameAndTopic diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js index ab390fc..ec5c3d3 100644 --- a/d2m/actions/create-room.test.js +++ b/d2m/actions/create-room.test.js @@ -1,4 +1,6 @@ -const {channelToKState} = require("./create-room") +// @ts-check + +const {channelToKState, _convertNameAndTopic} = require("./create-room") const {kstateStripConditionals} = require("../../matrix/kstate") const {test} = require("supertape") const testData = require("../../test/data") @@ -9,3 +11,31 @@ test("channel2room: general", async t => { testData.room.general ) }) + +test("convertNameAndTopic: custom name and topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, "hauntings"), + ["hauntings", "#the-twilight-zone | Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: custom name, no topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, "hauntings"), + ["hauntings", "#the-twilight-zone\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: original name and topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, null), + ["the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"] + ) +}) + +test("convertNameAndTopic: original name, no topic", t => { + t.deepEqual( + _convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, null), + ["the-twilight-zone", "Channel ID: 123\nGuild ID: 456"] + ) +}) diff --git a/test/data.js b/test/data.js index 5efea36..49bbeaf 100644 --- a/test/data.js +++ b/test/data.js @@ -23,7 +23,7 @@ module.exports = { room: { general: { "m.room.name/": {name: "main"}, - "m.room.topic/": {topic: "collective-unconscious | https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:\n\nChannel ID: 112760669178241024\nGuild ID: 112760669178241024"}, + "m.room.topic/": {topic: "#collective-unconscious | https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:\n\nChannel ID: 112760669178241024\nGuild ID: 112760669178241024"}, "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility: "invited"}, "m.space.parent/!jjWAGMeQdNrVZSSfvz:cadence.moe": { @@ -48,7 +48,6 @@ module.exports = { owner_id: "112760500130975744", premium_tier: 3, stickers: [{ - version: 1683838696974, type: 2, tags: "sunglasses", name: "pomu puff", @@ -56,8 +55,7 @@ module.exports = { guild_id: "112760669178241024", format_type: 1, description: "damn that tiny lil bitch really chuffing. puffing that fat ass dart", - available: true, - asset: "" + available: true }], max_members: 500000, splash: "86a34ed02524b972918bef810087f8e7", From 4fd408d62ae0aa49a727da94c850ed26196823cd Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 5 Jul 2023 08:41:15 +1200 Subject: [PATCH 058/200] reactions working --- d2m/event-dispatcher.js | 10 ++++++---- db/ooye.db | Bin 258048 -> 258048 bytes index.js | 4 ++++ m2d/actions/add-reaction.js | 4 +++- notes.md | 2 +- package-lock.json | 20 ++++++++++---------- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 99c7792..1686b5f 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -14,10 +14,6 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { - /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ - const channel = client.channels.get(message.channel_id) - const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) if (message.webhook_id) { const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) if (row) { @@ -25,6 +21,11 @@ module.exports = { return } } + /** @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = client.channels.get(message.channel_id) + if (!channel.guild_id) return // Nothing we can do in direct messages. + const guild = client.guilds.get(channel.guild_id) + if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) sendMessage.sendMessage(message, guild) }, @@ -33,6 +34,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ onReactionAdd(client, data) { + if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) diff --git a/db/ooye.db b/db/ooye.db index df5e6d199d8c0d1918dcbb6d2f18f403e9c8b19d..8400cf00121fc350e6c60be96d1e485b122b8a1b 100644 GIT binary patch delta 8578 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 delta 3268 zcmZWr2~=0b9=&s#Ou4u$nL5V^+|3q8p_>ysM4=IewW zE`5ce4B?!%yA;3aZ2}Av4tAKn!PP3_#rh^6`z@Y)w4S^66 zOjeMiR`t=1%1TSW(=4`=Ev4{l18qRv80YvSpCR4g2}_yF{kg7sMRR0 z3U^{&MD@5N42jxElq^WIq;I5BYNep@npi3O#8&xj+)QVupzz`y=bhwKlwr}@4ivv# zT!`Wz1ofdx6&h8%tyx|)QXD%mo2I0q5l&h)+8PQ$coJ0_(4gwAT`{cgI@qpW6;5v? z;wS6{OS{?yC4o-MSrlgGvTTkP`w668o=UdJ-;?=LAMr35O?2yu^^vvIN|0_UO;$Iv z#jFyO&DYFH@@lh(*iT6^+Kjzw7j>)>V5|{;Qg#`CH6GS~(rfh%x=(sW4$&jDyK;?o zT-&6jX)&7MsrPL0%#-$cMo4Qts<>Xgq*f>^u)3sr>vn(`$e>L}AW}j*IXu*XBW1LM zK};9?0R1Jjozv}GxT2iH>Z8CEWv-&>CRb2S|A)~MD&cVS=UlPURlIu<`pf7Q7pGk2 z>N#9Oj&aFMmp;S#{4Fq;zU%3=5y97@p)E~tY@ly0AyJ(T*k;RtaqpMU>2n-}(T7<+Wod-X(#z1r6g#7s^}{QOtzoa~HR`i`PX#hF!ZmQgo-2cCgSUb1SE8 zmgN`inQ1Y5N|7g&e^*eeJuSbleKqF^ znlutWOlv3L-gaTZD0|`R{?zXx3UKsM`0ms2h;-0aswP%%&6J#j-@{m~q7?h>c@mIc zu8}Pa5%wc3-g6%n;z0QoHNY64x8fN}gD2ZFKy6jGsXlF;blr>>_Tw_$9~MItZj(x- z7o_eeQHhuDi(kP$@eL%1@p6Co9i1in<-KGv8KC<-d-ZL~I;+*%X8Fu@mcQAcH7M(h zeaz^Chb~XSBb?uBu_#md7LJn=#Ee0hwtoX&y7oApLXXIhXb63=S$4W8BA}l!Csnd!4%qIf%k!l zyk_vqsFvHK8&2afG(ZBIrhSDI=nxs!(=ZhZoZ5OE?-#5Z7ZBTNgq3LiYW6ZNnExPs ztRgaBpXY=&v2Pe|4C-GbK!f~AFJnP^cYOY!#3>0cXye!(D1D++GD78&P=q+)w zsKQZLrJaM}R+%-!3N+7|8_ekg`DVT97J)qIvnDrQ4m5jU513lr4q?Zfvm=?4`oXCqz>$kFo z^0(kJJ#!Ue74)3zo4x!nbL}X-sWLaxb+%tQN5guuNUSy?5z|Fyx$~=Oa1ZA9%|JG} zq8H@TH`{UIgRX3;0Izev12YioUF47Kn0{W0hkV=A(^k_gBkp1k`Mp3^w2oBX#^&Z*_gF0Qe+Yfo2wlNSKf}*ID0uiHFpR9zT_$R!R-zaD!o{f8V+OXOl^v+o{8C}-5 z={`L`JF69HiJFi;C2{z$@y@&ycv-z7#IDi0>hS!^P=h7dUCWYQ*6AkPlw;E%Rdu ziROOj<P_AHw4@}TJKr8A$3o|E?fO!nYTj^-_d3nH!&3Tc-d z=55whRw48P^<}aOw=9CQqVkTANFzG%m~{Jr z14q#5IS_zh1P%KEkE0=(EbhyxkRY<3*GxJs6~?${ndYuy#sX)u2KCQ^@qD(L-pYbC z9EDRem(4a`!@+dkEbuZ8y-yc#Zro1XO`5>Fm6r1NUta{`l&=6^_grh1Fa%8%te)mY zvlzZ0`^|}_WYpr`XcL-hESK(x(~KeLYpYeit?v-8N`0(tEN#2UVrjRYuJ_WeXdC6# z_-9eqrpm`fsx-+Xc|kPI^Mxl*dKq#(Bj9o69sH7dN39|QSnU7v6z{I5Q;$F+`-S}T zE5QD(yw1*}5QgY=3UQ8p3_ew9^LOluzP}j?o#NBb4?jpN`-kz;`}MHGSyl?K_X;s5 z3PXiDWXjSVIQ~(ZDQp)+QIM3H=L0rkdEl4pg-oY zJ7wi&7)=|$!xo*j6UU+?nzF*Jq>yh{rZQ`*pgXV4HCma)j7zTpp;vFQ8`ysfq+w@S I1FY@!Ukp;jAOHXW diff --git a/index.js b/index.js index f4c59c6..233d518 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,10 @@ passthrough.as = as sync.require("./m2d/event-dispatcher") +discord.snow.requestHandler.on("requestError", data => { + console.error("request error", data) +}) + ;(async () => { await discord.cloud.connect() console.log("Discord gateway started") diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js index 342550d..d259ddb 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -17,7 +17,9 @@ async function addReaction(event) { // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it - const emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions + let emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions + emoji = encodeURIComponent(emoji) + emoji = emoji.replace(/%EF%B8%8F/g, "") return discord.snow.channel.createReaction(channelID, messageID, emoji) } diff --git a/notes.md b/notes.md index 3491682..ec2b9bb 100644 --- a/notes.md +++ b/notes.md @@ -104,7 +104,7 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See ## Reaction removed -1. Remove reaction on matrix. +1. Remove reaction on matrix. Just redact the event. ## Member data changed diff --git a/package-lock.json b/package-lock.json index fecb682..4908aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -685,11 +685,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.0.tgz", - "integrity": "sha512-CT5/RKvSz1I0wmsf0SmZ2Jg9fPvqY67t9e2Y8n92vU0uEK5WmfPUyPOLZoYPMJwmktmsVCj4N6Pvka9gBIsY4g==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.2.tgz", + "integrity": "sha512-G/P6/+LwXjiS6AmheRG+07DmmsrpHpt21JFMhe+rW8VagFOOKemC2Bcru+Qncl/5jdjZC2gzjKpjfdTjfUm+iw==", "dependencies": { - "snowtransfer": "0.8.0" + "snowtransfer": "^0.8.2" }, "engines": { "node": ">=12.0.0" @@ -1938,9 +1938,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2464,9 +2464,9 @@ } }, "node_modules/snowtransfer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.0.tgz", - "integrity": "sha512-ang6qQsET4VX4u9mdZq6ynJvcm8HQfV6iZOHBh8Y3T0QkJLr6GAjzcv1et7BOXl1HDR/6NhD+j+ZGr8+imTclg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.2.tgz", + "integrity": "sha512-fAmaJSpFZqGwAvbrhT3XOWwhbiuHOgxN8pGeKnDDW0f8zdkPmSQT9aekXhFr1WukB94NIALYGcyIXe902p8S4A==", "dependencies": { "discord-api-types": "^0.37.47", "form-data": "^4.0.0", From bd32fe6c6d60747da35fa4eabf0a9432787efea4 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 5 Jul 2023 08:41:15 +1200 Subject: [PATCH 059/200] reactions working --- d2m/event-dispatcher.js | 10 ++++++---- index.js | 4 ++++ m2d/actions/add-reaction.js | 4 +++- notes.md | 2 +- package-lock.json | 20 ++++++++++---------- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 99c7792..1686b5f 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -14,10 +14,6 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ onMessageCreate(client, message) { - /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ - const channel = client.channels.get(message.channel_id) - const guild = client.guilds.get(channel.guild_id) - if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) if (message.webhook_id) { const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) if (row) { @@ -25,6 +21,11 @@ module.exports = { return } } + /** @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = client.channels.get(message.channel_id) + if (!channel.guild_id) return // Nothing we can do in direct messages. + const guild = client.guilds.get(channel.guild_id) + if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first) sendMessage.sendMessage(message, guild) }, @@ -33,6 +34,7 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */ onReactionAdd(client, data) { + if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) diff --git a/index.js b/index.js index f4c59c6..233d518 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,10 @@ passthrough.as = as sync.require("./m2d/event-dispatcher") +discord.snow.requestHandler.on("requestError", data => { + console.error("request error", data) +}) + ;(async () => { await discord.cloud.connect() console.log("Discord gateway started") diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js index 342550d..d259ddb 100644 --- a/m2d/actions/add-reaction.js +++ b/m2d/actions/add-reaction.js @@ -17,7 +17,9 @@ async function addReaction(event) { // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it - const emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions + let emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions + emoji = encodeURIComponent(emoji) + emoji = emoji.replace(/%EF%B8%8F/g, "") return discord.snow.channel.createReaction(channelID, messageID, emoji) } diff --git a/notes.md b/notes.md index 3491682..ec2b9bb 100644 --- a/notes.md +++ b/notes.md @@ -104,7 +104,7 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See ## Reaction removed -1. Remove reaction on matrix. +1. Remove reaction on matrix. Just redact the event. ## Member data changed diff --git a/package-lock.json b/package-lock.json index fecb682..4908aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -685,11 +685,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.0.tgz", - "integrity": "sha512-CT5/RKvSz1I0wmsf0SmZ2Jg9fPvqY67t9e2Y8n92vU0uEK5WmfPUyPOLZoYPMJwmktmsVCj4N6Pvka9gBIsY4g==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.2.tgz", + "integrity": "sha512-G/P6/+LwXjiS6AmheRG+07DmmsrpHpt21JFMhe+rW8VagFOOKemC2Bcru+Qncl/5jdjZC2gzjKpjfdTjfUm+iw==", "dependencies": { - "snowtransfer": "0.8.0" + "snowtransfer": "^0.8.2" }, "engines": { "node": ">=12.0.0" @@ -1938,9 +1938,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2464,9 +2464,9 @@ } }, "node_modules/snowtransfer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.0.tgz", - "integrity": "sha512-ang6qQsET4VX4u9mdZq6ynJvcm8HQfV6iZOHBh8Y3T0QkJLr6GAjzcv1et7BOXl1HDR/6NhD+j+ZGr8+imTclg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.2.tgz", + "integrity": "sha512-fAmaJSpFZqGwAvbrhT3XOWwhbiuHOgxN8pGeKnDDW0f8zdkPmSQT9aekXhFr1WukB94NIALYGcyIXe902p8S4A==", "dependencies": { "discord-api-types": "^0.37.47", "form-data": "^4.0.0", From 9f3efcd10d8729f79d85f66a806f1137bbc6d79c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 5 Jul 2023 12:04:28 +1200 Subject: [PATCH 060/200] streamline the convertNameAndTopic function --- d2m/actions/create-room.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 98333b2..0fd0646 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -37,25 +37,17 @@ function applyKStateDiffToRoom(roomID, kstate) { * @param {string?} customName */ function convertNameAndTopic(channel, guild, customName) { - // TODO: Improve nasty nested ifs - let convertedName, convertedTopic - if (customName) { - convertedName = customName - if (channel.topic) { - convertedTopic = `#${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` - } else { - convertedTopic = `#${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` - } - } else { - convertedName = channel.name - if (channel.topic) { - convertedTopic = `${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` - } else { - convertedTopic = `Channel ID: ${channel.id}\nGuild ID: ${guild.id}` - } - } + const convertedName = customName || channel.name; + const maybeTopicWithPipe = channel.topic ? ` | ${channel.topic}` : ''; + const maybeTopicWithNewlines = channel.topic ? `${channel.topic}\n\n` : ''; + const channelIDPart = `Channel ID: ${channel.id}`; + const guildIDPart = `Guild ID: ${guild.id}`; - return [convertedName, convertedTopic] + const convertedTopic = customName + ? `#${channel.name}${maybeTopicWithPipe}\n\n${channelIDPart}\n${guildIDPart}` + : `${maybeTopicWithNewlines}${channelIDPart}\n${guildIDPart}`; + + return [convertedName, convertedTopic]; } /** From 9569fda168930a73cd8cbc7c70a238f7b88c0d99 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 5 Jul 2023 12:04:28 +1200 Subject: [PATCH 061/200] streamline the convertNameAndTopic function --- d2m/actions/create-room.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 98333b2..0fd0646 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -37,25 +37,17 @@ function applyKStateDiffToRoom(roomID, kstate) { * @param {string?} customName */ function convertNameAndTopic(channel, guild, customName) { - // TODO: Improve nasty nested ifs - let convertedName, convertedTopic - if (customName) { - convertedName = customName - if (channel.topic) { - convertedTopic = `#${channel.name} | ${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` - } else { - convertedTopic = `#${channel.name}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` - } - } else { - convertedName = channel.name - if (channel.topic) { - convertedTopic = `${channel.topic}\n\nChannel ID: ${channel.id}\nGuild ID: ${guild.id}` - } else { - convertedTopic = `Channel ID: ${channel.id}\nGuild ID: ${guild.id}` - } - } + const convertedName = customName || channel.name; + const maybeTopicWithPipe = channel.topic ? ` | ${channel.topic}` : ''; + const maybeTopicWithNewlines = channel.topic ? `${channel.topic}\n\n` : ''; + const channelIDPart = `Channel ID: ${channel.id}`; + const guildIDPart = `Guild ID: ${guild.id}`; - return [convertedName, convertedTopic] + const convertedTopic = customName + ? `#${channel.name}${maybeTopicWithPipe}\n\n${channelIDPart}\n${guildIDPart}` + : `${maybeTopicWithNewlines}${channelIDPart}\n${guildIDPart}`; + + return [convertedName, convertedTopic]; } /** From 07f24db4131910ec31513da0987149acd52fe83d Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 7 Jul 2023 17:31:39 +1200 Subject: [PATCH 062/200] start getting d2m formatted body conversion --- d2m/converters/message-to-event.js | 64 +++++++++++-------- d2m/converters/message-to-event.test.js | 20 ++++++ db/ooye.db | Bin 258048 -> 258048 bytes stdin.js | 1 + test/data.js | 78 ++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 26 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 382e970..90023ee 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -8,6 +8,34 @@ const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +function getDiscordParseCallbacks(message, useHTML) { + return { + user: node => { + const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) + const username = message.mentions.find(ment => ment.id === node.id)?.username || node.id + if (mxid && useHTML) { + return `@${username}` + } else { + return `@${username}:` + } + }, + channel: node => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) + if (roomID && useHTML) { + return "https://matrix.to/#/" + roomID + } else { + return "#" + node.id + } + }, + role: node => + "@&" + node.id, + everyone: node => + "@room", + here: node => + "@here" + } +} + /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild @@ -17,34 +45,18 @@ async function messageToEvent(message, guild) { // Text content appears first if (message.content) { - const body = message.content - const html = markdown.toHTML(body, { - discordCallback: { - user: node => { - const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) - if (mxid) { - return "https://matrix.to/#/" + mxid - } else { - return "@" + node.id - } - }, - channel: node => { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) - if (roomID) { - return "https://matrix.to/#/" + roomID - } else { - return "#" + node.id - } - }, - role: node => - "@&" + node.id, - everyone: node => - "@room", - here: node => - "@here" - } + const html = markdown.toHTML(message.content, { + discordCallback: getDiscordParseCallbacks(message, true) }, null, null) + + const body = markdown.toHTML(message.content, { + discordCallback: getDiscordParseCallbacks(message, false), //TODO: library bug!! + discordOnly: true, + escapeHTML: false, + }, null, null) + const isPlaintext = body === html + if (isPlaintext) { events.push({ $type: "m.room.message", diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 26cf1f1..1f3a844 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -2,6 +2,26 @@ const {test} = require("supertape") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") +test("message2event: simple plaintext", async t => { + const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "ayy lmao" + }]) +}) + +test("message2event: simple user mention", async t => { + const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + format: "org.matrix.custom.html", + formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + }]) +}) + test("message2event: attachment with no content", async t => { const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) t.deepEqual(events, [{ diff --git a/db/ooye.db b/db/ooye.db index 8400cf00121fc350e6c60be96d1e485b122b8a1b..7df165b729fcd153b82c31b6108e205ba746d49b 100644 GIT binary patch delta 754 zcmZp8z~AtIe}Xil`a~ILR&@rwv{xHb7OZF1W)Ysw9>5g7ap6|xdY9&G)>LB-PL;$= z?<6BvkBrJHSO3WJZ2$O_%F=SfG~<%IAUBiZtdPJ+H%Ff=(@>9~P(wo_GjkIoQ%iFL z3ky?oQ&Tewm*y;pG4U>r-j(K#;b~6g#_=UlrKaw=x<1Aw1ztIR`NpBe#r~<0ArS?| zsqXQ2%fu zOo>8#F)W>Ig)A;i;{3aKyZFR-FHBtMxP9jVrpr7`440<&on^Y>O2~)!J;?Y1Z1?24 zt@hiWonw-l$YH{8kcGXEv#xo>)!E3<7q f#OstL8^+fVQ%FoTphUxqlxi4XZP&|WzSIT)CPn11 delta 209 zcmV;?051Q4;17V{50D!HBas|K1tS11YS6J{psxcb1uv5auurj}*#v_xwYM*|0n+*e z0Zjlimj$o_NtaNt0w4kbO_y-60^I}!Apk~~PVNFi1w{m9GXj^;tph)|*RcXE4goZm z1;7Gf5(J3`aRfvH7Z17)Tn&m277fsmp+UE~zyi|@0s$D8j?Dtpm+u7wMw5TFKDXx1 z0v?eDG62B@2acBvU;`SLC}0Cf1X4{=Qnzqm17!yS0b7^wLIaMMW<>)(1ONa7M3=!s L1BSOQX#>)RAkIa> diff --git a/stdin.js b/stdin.js index cd504f2..7e0db89 100644 --- a/stdin.js +++ b/stdin.js @@ -6,6 +6,7 @@ const util = require("util") const passthrough = require("./passthrough") const { discord, config, sync, db } = passthrough +const data = sync.require("./test/data") const createSpace = sync.require("./d2m/actions/create-space") const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") diff --git a/test/data.js b/test/data.js index 49bbeaf..f4326a6 100644 --- a/test/data.js +++ b/test/data.js @@ -138,6 +138,84 @@ module.exports = { }, message: { // Display order is text content, attachments, then stickers + simple_plaintext: { + id: "1126733830494093453", + type: 0, + content: "ayy lmao", + channel_id: "112760669178241024", + author: { + id: "111604486476181504", + username: "kyuugryphon", + avatar: "e4ce31267ca524d19be80e684d4cafa1", + discriminator: "0", + public_flags: 0, + flags: 0, + banner: null, + accent_color: null, + global_name: "KyuuGryphon", + avatar_decoration: null, + display_name: "KyuuGryphon", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T04:37:58.892000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, + simple_user_mention: { + id: "1126739682080858234", + type: 0, + content: "<@820865262526005258> Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + channel_id: "112760669178241024", + author: { + id: "114147806469554185", + username: "extremity", + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6", + discriminator: "0", + public_flags: 768, + flags: 768, + banner: null, + accent_color: null, + global_name: "Extremity", + avatar_decoration: null, + display_name: "Extremity", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [ + { + id: "820865262526005258", + username: "crunch god", + avatar: "f7a75ca031c1d2326e0f3ca5213eea47", + discriminator: "8889", + public_flags: 0, + flags: 0, + bot: true, + banner: null, + accent_color: null, + global_name: null, + avatar_decoration: null, + display_name: null, + banner_color: null + } + ], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T05:01:14.019000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, attachment_no_content: { id: "1124628646670389348", type: 0, From 1a4f92db973dda8cbf9180d57041bbe7d5b9a105 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 7 Jul 2023 17:31:39 +1200 Subject: [PATCH 063/200] start getting d2m formatted body conversion --- d2m/converters/message-to-event.js | 64 +++++++++++--------- d2m/converters/message-to-event.test.js | 20 +++++++ stdin.js | 1 + test/data.js | 78 +++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 26 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 382e970..90023ee 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -8,6 +8,34 @@ const { sync, db, discord } = passthrough /** @type {import("../../matrix/file")} */ const file = sync.require("../../matrix/file") +function getDiscordParseCallbacks(message, useHTML) { + return { + user: node => { + const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) + const username = message.mentions.find(ment => ment.id === node.id)?.username || node.id + if (mxid && useHTML) { + return `@${username}` + } else { + return `@${username}:` + } + }, + channel: node => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) + if (roomID && useHTML) { + return "https://matrix.to/#/" + roomID + } else { + return "#" + node.id + } + }, + role: node => + "@&" + node.id, + everyone: node => + "@room", + here: node => + "@here" + } +} + /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild @@ -17,34 +45,18 @@ async function messageToEvent(message, guild) { // Text content appears first if (message.content) { - const body = message.content - const html = markdown.toHTML(body, { - discordCallback: { - user: node => { - const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) - if (mxid) { - return "https://matrix.to/#/" + mxid - } else { - return "@" + node.id - } - }, - channel: node => { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) - if (roomID) { - return "https://matrix.to/#/" + roomID - } else { - return "#" + node.id - } - }, - role: node => - "@&" + node.id, - everyone: node => - "@room", - here: node => - "@here" - } + const html = markdown.toHTML(message.content, { + discordCallback: getDiscordParseCallbacks(message, true) }, null, null) + + const body = markdown.toHTML(message.content, { + discordCallback: getDiscordParseCallbacks(message, false), //TODO: library bug!! + discordOnly: true, + escapeHTML: false, + }, null, null) + const isPlaintext = body === html + if (isPlaintext) { events.push({ $type: "m.room.message", diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 26cf1f1..1f3a844 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -2,6 +2,26 @@ const {test} = require("supertape") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") +test("message2event: simple plaintext", async t => { + const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "ayy lmao" + }]) +}) + +test("message2event: simple user mention", async t => { + const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + format: "org.matrix.custom.html", + formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + }]) +}) + test("message2event: attachment with no content", async t => { const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) t.deepEqual(events, [{ diff --git a/stdin.js b/stdin.js index cd504f2..7e0db89 100644 --- a/stdin.js +++ b/stdin.js @@ -6,6 +6,7 @@ const util = require("util") const passthrough = require("./passthrough") const { discord, config, sync, db } = passthrough +const data = sync.require("./test/data") const createSpace = sync.require("./d2m/actions/create-space") const createRoom = sync.require("./d2m/actions/create-room") const registerUser = sync.require("./d2m/actions/register-user") diff --git a/test/data.js b/test/data.js index 49bbeaf..f4326a6 100644 --- a/test/data.js +++ b/test/data.js @@ -138,6 +138,84 @@ module.exports = { }, message: { // Display order is text content, attachments, then stickers + simple_plaintext: { + id: "1126733830494093453", + type: 0, + content: "ayy lmao", + channel_id: "112760669178241024", + author: { + id: "111604486476181504", + username: "kyuugryphon", + avatar: "e4ce31267ca524d19be80e684d4cafa1", + discriminator: "0", + public_flags: 0, + flags: 0, + banner: null, + accent_color: null, + global_name: "KyuuGryphon", + avatar_decoration: null, + display_name: "KyuuGryphon", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T04:37:58.892000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, + simple_user_mention: { + id: "1126739682080858234", + type: 0, + content: "<@820865262526005258> Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + channel_id: "112760669178241024", + author: { + id: "114147806469554185", + username: "extremity", + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6", + discriminator: "0", + public_flags: 768, + flags: 768, + banner: null, + accent_color: null, + global_name: "Extremity", + avatar_decoration: null, + display_name: "Extremity", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [ + { + id: "820865262526005258", + username: "crunch god", + avatar: "f7a75ca031c1d2326e0f3ca5213eea47", + discriminator: "8889", + public_flags: 0, + flags: 0, + bot: true, + banner: null, + accent_color: null, + global_name: null, + avatar_decoration: null, + display_name: null, + banner_color: null + } + ], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T05:01:14.019000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, attachment_no_content: { id: "1124628646670389348", type: 0, From 83c4d21811e565d33950e1a6b4cce8548bd0382f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 7 Jul 2023 19:28:23 +1200 Subject: [PATCH 064/200] update discord-markdown to fix tests --- d2m/actions/register-user.test.js | 34 ++++---- d2m/converters/message-to-event.test.js | 110 ++++++++++++------------ package-lock.json | 18 ++-- package.json | 2 +- 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js index 7e23450..0afce50 100644 --- a/d2m/actions/register-user.test.js +++ b/d2m/actions/register-user.test.js @@ -4,21 +4,21 @@ const {test} = require("supertape") const testData = require("../../test/data") test("member2state: general", async t => { - t.deepEqual( - await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), - { - avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", - displayname: "The Expert's Submarine | aprilsong", - membership: "join", - "moe.cadence.ooye.member": { - avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" - }, - "uk.half-shot.discord.member": { - bot: false, - displayColor: null, - id: "134826546694193153", - username: "@aprilsong" - } - } - ) + t.deepEqual( + await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), + { + avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", + displayname: "The Expert's Submarine | aprilsong", + membership: "join", + "moe.cadence.ooye.member": { + avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" + }, + "uk.half-shot.discord.member": { + bot: false, + displayColor: null, + id: "134826546694193153", + username: "@aprilsong" + } + } + ) }) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 1f3a844..6e673f1 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -3,68 +3,68 @@ const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") test("message2event: simple plaintext", async t => { - const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "ayy lmao" - }]) + const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "ayy lmao" + }]) }) test("message2event: simple user mention", async t => { - const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", - format: "org.matrix.custom.html", - formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' - }]) + const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + format: "org.matrix.custom.html", + formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + }]) }) test("message2event: attachment with no content", async t => { - const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.image", - url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", - body: "image.png", - external_url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", - info: { - mimetype: "image/png", - w: 466, - h: 85, - size: 12919, - }, - }]) + const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", + info: { + mimetype: "image/png", + w: 466, + h: 85, + size: 12919, + }, + }]) }) test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "can have attachments too" - }, { - $type: "m.room.message", - msgtype: "m.image", - url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", - body: "image.png", - external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", - info: { - mimetype: "image/png", - w: 333, - h: 287, - size: 127373, - }, - }, { - $type: "m.sticker", - body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", - info: { - mimetype: "image/png" - // thumbnail_url - // thumbnail_info - }, - url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" - }]) + const events = await messageToEvent(data.message.sticker, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "can have attachments too" + }, { + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + info: { + mimetype: "image/png", + w: 333, + h: 287, + size: 127373, + }, + }, { + $type: "m.sticker", + body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", + info: { + mimetype: "image/png" + // thumbnail_url + // thumbnail_info + }, + url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" + }]) }) diff --git a/package-lock.json b/package-lock.json index 4908aa6..7dcde49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", @@ -685,11 +685,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.2.tgz", - "integrity": "sha512-G/P6/+LwXjiS6AmheRG+07DmmsrpHpt21JFMhe+rW8VagFOOKemC2Bcru+Qncl/5jdjZC2gzjKpjfdTjfUm+iw==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.3.tgz", + "integrity": "sha512-4c2rqFFvzM4P3pcnjnGUlYuyBjx/xnMew6imB0sFwmNLITLCTLYa3qGkrnhI1g/tM0fqg+Gr+EmDHiDZfEr9LQ==", "dependencies": { - "snowtransfer": "^0.8.2" + "snowtransfer": "^0.8.3" }, "engines": { "node": ">=12.0.0" @@ -929,7 +929,7 @@ }, "node_modules/discord-markdown": { "version": "2.4.1", - "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.2" @@ -2464,9 +2464,9 @@ } }, "node_modules/snowtransfer": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.2.tgz", - "integrity": "sha512-fAmaJSpFZqGwAvbrhT3XOWwhbiuHOgxN8pGeKnDDW0f8zdkPmSQT9aekXhFr1WukB94NIALYGcyIXe902p8S4A==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.3.tgz", + "integrity": "sha512-0X6NLFBUKppYT5VH/mVQNGX+ufv0AndunZC84MqGAR/3rfTIGQblgGJlHlDQbeCytlXdMpgRHIGQnBFlE094NQ==", "dependencies": { "discord-api-types": "^0.37.47", "form-data": "^4.0.0", diff --git a/package.json b/package.json index b3e19eb..7fb8cc6 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", From 1d6e833b222fa6ff4bd6c9e549005fa5bf499124 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 7 Jul 2023 19:28:23 +1200 Subject: [PATCH 065/200] update discord-markdown to fix tests --- d2m/actions/register-user.test.js | 34 ++++---- d2m/converters/message-to-event.test.js | 110 ++++++++++++------------ package-lock.json | 18 ++-- package.json | 2 +- 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/d2m/actions/register-user.test.js b/d2m/actions/register-user.test.js index 7e23450..0afce50 100644 --- a/d2m/actions/register-user.test.js +++ b/d2m/actions/register-user.test.js @@ -4,21 +4,21 @@ const {test} = require("supertape") const testData = require("../../test/data") test("member2state: general", async t => { - t.deepEqual( - await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), - { - avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", - displayname: "The Expert's Submarine | aprilsong", - membership: "join", - "moe.cadence.ooye.member": { - avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" - }, - "uk.half-shot.discord.member": { - bot: false, - displayColor: null, - id: "134826546694193153", - username: "@aprilsong" - } - } - ) + t.deepEqual( + await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id), + { + avatar_url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl", + displayname: "The Expert's Submarine | aprilsong", + membership: "join", + "moe.cadence.ooye.member": { + avatar: "/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024" + }, + "uk.half-shot.discord.member": { + bot: false, + displayColor: null, + id: "134826546694193153", + username: "@aprilsong" + } + } + ) }) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 1f3a844..6e673f1 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -3,68 +3,68 @@ const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") test("message2event: simple plaintext", async t => { - const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "ayy lmao" - }]) + const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "ayy lmao" + }]) }) test("message2event: simple user mention", async t => { - const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", - format: "org.matrix.custom.html", - formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' - }]) + const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + format: "org.matrix.custom.html", + formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + }]) }) test("message2event: attachment with no content", async t => { - const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.image", - url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", - body: "image.png", - external_url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", - info: { - mimetype: "image/png", - w: 466, - h: 85, - size: 12919, - }, - }]) + const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", + info: { + mimetype: "image/png", + w: 466, + h: 85, + size: 12919, + }, + }]) }) test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "can have attachments too" - }, { - $type: "m.room.message", - msgtype: "m.image", - url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", - body: "image.png", - external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", - info: { - mimetype: "image/png", - w: 333, - h: 287, - size: 127373, - }, - }, { - $type: "m.sticker", - body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", - info: { - mimetype: "image/png" - // thumbnail_url - // thumbnail_info - }, - url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" - }]) + const events = await messageToEvent(data.message.sticker, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "can have attachments too" + }, { + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + info: { + mimetype: "image/png", + w: 333, + h: 287, + size: 127373, + }, + }, { + $type: "m.sticker", + body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", + info: { + mimetype: "image/png" + // thumbnail_url + // thumbnail_info + }, + url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" + }]) }) diff --git a/package-lock.json b/package-lock.json index 4908aa6..7dcde49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", @@ -685,11 +685,11 @@ } }, "node_modules/cloudstorm": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.2.tgz", - "integrity": "sha512-G/P6/+LwXjiS6AmheRG+07DmmsrpHpt21JFMhe+rW8VagFOOKemC2Bcru+Qncl/5jdjZC2gzjKpjfdTjfUm+iw==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.8.3.tgz", + "integrity": "sha512-4c2rqFFvzM4P3pcnjnGUlYuyBjx/xnMew6imB0sFwmNLITLCTLYa3qGkrnhI1g/tM0fqg+Gr+EmDHiDZfEr9LQ==", "dependencies": { - "snowtransfer": "^0.8.2" + "snowtransfer": "^0.8.3" }, "engines": { "node": ">=12.0.0" @@ -929,7 +929,7 @@ }, "node_modules/discord-markdown": { "version": "2.4.1", - "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "resolved": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.2" @@ -2464,9 +2464,9 @@ } }, "node_modules/snowtransfer": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.2.tgz", - "integrity": "sha512-fAmaJSpFZqGwAvbrhT3XOWwhbiuHOgxN8pGeKnDDW0f8zdkPmSQT9aekXhFr1WukB94NIALYGcyIXe902p8S4A==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.8.3.tgz", + "integrity": "sha512-0X6NLFBUKppYT5VH/mVQNGX+ufv0AndunZC84MqGAR/3rfTIGQblgGJlHlDQbeCytlXdMpgRHIGQnBFlE094NQ==", "dependencies": { "discord-api-types": "^0.37.47", "form-data": "^4.0.0", diff --git a/package.json b/package.json index b3e19eb..7fb8cc6 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "better-sqlite3": "^8.3.0", "cloudstorm": "^0.8.0", - "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#df495b152fdc48fb22284ecda9a988e6df61bf99", "heatsync": "^2.4.1", "js-yaml": "^4.1.0", "matrix-appservice": "^2.0.0", From 30d28f92c72865319759575e56e04b1bd62eef23 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 08:01:11 +1200 Subject: [PATCH 066/200] store the channel_id on event_message --- d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.js | 16 +++- d2m/converters/message-to-event.test.js | 22 +++++ db/ooye.db | Bin 258048 -> 303104 bytes m2d/actions/send-event.js | 2 +- test/data.js | 113 ++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 5 deletions(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 4f111b0..f5fe5ef 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -37,7 +37,7 @@ async function sendMessage(message, guild) { delete eventWithoutType.$type const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, part, source) VALUES (?, ?, ?, 1)").run(eventID, message.id, eventPart) // source 1 = discord + db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting eventIDs.push(eventID) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 90023ee..fc76bf2 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -45,12 +45,22 @@ async function messageToEvent(message, guild) { // Text content appears first if (message.content) { - const html = markdown.toHTML(message.content, { + let content = message.content + content = content.replace(/https:\/\/(?:ptb\.|canary\.|www\.)?discord(?:app)?\.com\/channels\/([0-9]+)\/([0-9]+)\/([0-9]+)/, (whole, guildID, channelID, messageID) => { + const row = db.prepare("SELECT room_id, event_id FROM event_message INNER JOIN channel_room USING (channel_id) WHERE channel_id = ? AND message_id = ? AND part = 0").get(channelID, messageID) + if (row) { + return `https://matrix.to/#/${row.room_id}/${row.event_id}` + } else { + return `${whole} [event not found]` + } + }) + + const html = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) - const body = markdown.toHTML(message.content, { - discordCallback: getDiscordParseCallbacks(message, false), //TODO: library bug!! + const body = markdown.toHTML(content, { + discordCallback: getDiscordParseCallbacks(message, false), discordOnly: true, escapeHTML: false, }, null, null) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 6e673f1..a456a94 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -22,6 +22,28 @@ test("message2event: simple user mention", async t => { }]) }) +test("message2event: simple room mention", async t => { + const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + format: "org.matrix.custom.html", + formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + }]) +}) + +test("message2event: simple message link", async t => { + const events = await messageToEvent(data.message.simple_message_link, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", + format: "org.matrix.custom.html", + formatted_body: 'https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg' + }]) +}) + test("message2event: attachment with no content", async t => { const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) t.deepEqual(events, [{ diff --git a/db/ooye.db b/db/ooye.db index 7df165b729fcd153b82c31b6108e205ba746d49b..b87572013e78f96e7b0fa036a7f964b33b2fad7c 100644 GIT binary patch delta 13060 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 delta 6665 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", + channel_id: "112760669178241024", + author: { + id: "116718249567059974", + username: "rnl", + avatar: "67e70f6424eead669e076b44474164c3", + discriminator: "0", + public_flags: 768, + flags: 768, + banner: null, + accent_color: null, + global_name: "▲", + avatar_decoration: null, + display_name: "▲", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [ + { + id: "113340068197859328", + username: "kumaccino", + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "0", + public_flags: 128, + flags: 128, + banner: null, + accent_color: null, + global_name: "kumaccino", + avatar_decoration: null, + display_name: "kumaccino", + banner_color: null + } + ], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-06T20:05:32.496000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + message_reference: { + channel_id: "112760669178241024", + message_id: "1126577139723026564", + guild_id: "112760669178241024" + }, + referenced_message: { + id: "1126577139723026564", + type: 0, + content: "this message was replied to", + channel_id: "112760669178241024", + author: { + id: "113340068197859328", + username: "kumaccino", + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "0", + public_flags: 128, + flags: 128, + banner: null, + accent_color: null, + global_name: "kumaccino", + avatar_decoration: null, + display_name: "kumaccino", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-06T18:15:20.901000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + } + }, attachment_no_content: { id: "1124628646670389348", type: 0, From 0f4f4041601a70d72aaa8d64187b87144eb5fcf5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 08:01:11 +1200 Subject: [PATCH 067/200] store the channel_id on event_message --- d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.js | 16 +++- d2m/converters/message-to-event.test.js | 22 +++++ m2d/actions/send-event.js | 2 +- test/data.js | 113 ++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 5 deletions(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 4f111b0..f5fe5ef 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -37,7 +37,7 @@ async function sendMessage(message, guild) { delete eventWithoutType.$type const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, part, source) VALUES (?, ?, ?, 1)").run(eventID, message.id, eventPart) // source 1 = discord + db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting eventIDs.push(eventID) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 90023ee..fc76bf2 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -45,12 +45,22 @@ async function messageToEvent(message, guild) { // Text content appears first if (message.content) { - const html = markdown.toHTML(message.content, { + let content = message.content + content = content.replace(/https:\/\/(?:ptb\.|canary\.|www\.)?discord(?:app)?\.com\/channels\/([0-9]+)\/([0-9]+)\/([0-9]+)/, (whole, guildID, channelID, messageID) => { + const row = db.prepare("SELECT room_id, event_id FROM event_message INNER JOIN channel_room USING (channel_id) WHERE channel_id = ? AND message_id = ? AND part = 0").get(channelID, messageID) + if (row) { + return `https://matrix.to/#/${row.room_id}/${row.event_id}` + } else { + return `${whole} [event not found]` + } + }) + + const html = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) - const body = markdown.toHTML(message.content, { - discordCallback: getDiscordParseCallbacks(message, false), //TODO: library bug!! + const body = markdown.toHTML(content, { + discordCallback: getDiscordParseCallbacks(message, false), discordOnly: true, escapeHTML: false, }, null, null) diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 6e673f1..a456a94 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -22,6 +22,28 @@ test("message2event: simple user mention", async t => { }]) }) +test("message2event: simple room mention", async t => { + const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + format: "org.matrix.custom.html", + formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + }]) +}) + +test("message2event: simple message link", async t => { + const events = await messageToEvent(data.message.simple_message_link, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", + format: "org.matrix.custom.html", + formatted_body: 'https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg' + }]) +}) + test("message2event: attachment with no content", async t => { const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) t.deepEqual(events, [{ diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 56a660f..c05f08c 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -25,7 +25,7 @@ async function sendEvent(event) { let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) - db.prepare("INSERT INTO event_message (event_id, message_id, part, source) VALUES (?, ?, ?, 0)").run(event.event_id, messageResponse.id, eventPart) // source 0 = matrix + db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 0)").run(event.event_id, messageResponse.id, channelID, eventPart) // source 0 = matrix eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? messageResponses.push(messageResponse) diff --git a/test/data.js b/test/data.js index f4326a6..52b8770 100644 --- a/test/data.js +++ b/test/data.js @@ -216,6 +216,119 @@ module.exports = { flags: 0, components: [] }, + simple_message_link: { + id: "1126788210308161626", + type: 0, + content: "https://ptb.discord.com/channels/112760669178241024/112760669178241024/1126786462646550579", + channel_id: "112760669178241024", + author: { + id: "113340068197859328", + username: "kumaccino", + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "0", + public_flags: 128, + flags: 128, + banner: null, + accent_color: null, + global_name: "kumaccino", + avatar_decoration: null, + display_name: "kumaccino", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-07T08:14:04.050000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + }, + simple_reply: { + id: "1126604870762369124", + type: 19, + content: "", + channel_id: "112760669178241024", + author: { + id: "116718249567059974", + username: "rnl", + avatar: "67e70f6424eead669e076b44474164c3", + discriminator: "0", + public_flags: 768, + flags: 768, + banner: null, + accent_color: null, + global_name: "▲", + avatar_decoration: null, + display_name: "▲", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [ + { + id: "113340068197859328", + username: "kumaccino", + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "0", + public_flags: 128, + flags: 128, + banner: null, + accent_color: null, + global_name: "kumaccino", + avatar_decoration: null, + display_name: "kumaccino", + banner_color: null + } + ], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-06T20:05:32.496000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + message_reference: { + channel_id: "112760669178241024", + message_id: "1126577139723026564", + guild_id: "112760669178241024" + }, + referenced_message: { + id: "1126577139723026564", + type: 0, + content: "this message was replied to", + channel_id: "112760669178241024", + author: { + id: "113340068197859328", + username: "kumaccino", + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "0", + public_flags: 128, + flags: 128, + banner: null, + accent_color: null, + global_name: "kumaccino", + avatar_decoration: null, + display_name: "kumaccino", + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-07-06T18:15:20.901000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + } + }, attachment_no_content: { id: "1124628646670389348", type: 0, From 15cba11ebdee9acf99c6d1d38eff09f0b62b5c6a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 10:10:47 +1200 Subject: [PATCH 068/200] get room mentions formatting working --- d2m/converters/message-to-event.js | 8 ++-- d2m/converters/message-to-event.test.js | 4 +- d2m/discord-client.js | 3 +- db/ooye.db | Bin 303104 -> 303104 bytes index.js | 2 +- scripts/save-channel-names-to-db.js | 58 ++++++++++++++++++++++++ test/data.js | 57 +++++++++++++++++++++++ 7 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 scripts/save-channel-names-to-db.js diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index fc76bf2..9999df9 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -20,11 +20,11 @@ function getDiscordParseCallbacks(message, useHTML) { } }, channel: node => { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) - if (roomID && useHTML) { - return "https://matrix.to/#/" + roomID + const {room_id, name, nick} = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) + if (room_id && useHTML) { + return `#${nick || name}` } else { - return "#" + node.id + return `#${nick || name}` } }, role: node => diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index a456a94..e3fcb06 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -27,9 +27,9 @@ test("message2event: simple room mention", async t => { t.deepEqual(events, [{ $type: "m.room.message", msgtype: "m.text", - body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + body: "#main", format: "org.matrix.custom.html", - formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + formatted_body: '#main' }]) }) diff --git a/d2m/discord-client.js b/d2m/discord-client.js index 2092718..91682bd 100644 --- a/d2m/discord-client.js +++ b/d2m/discord-client.js @@ -12,8 +12,9 @@ const discordPackets = sync.require("./discord-packets") class DiscordClient { /** * @param {string} discordToken + * @param {boolean} listen whether to set up the event listeners for OOYE to operate */ - constructor(discordToken) { + constructor(discordToken, listen) { this.discordToken = discordToken this.snow = new SnowTransfer(discordToken) this.cloud = new CloudStorm(discordToken, { diff --git a/db/ooye.db b/db/ooye.db index b87572013e78f96e7b0fa036a7f964b33b2fad7c..560fa72042d091d7c598c89ce57ae3556dd5c227 100644 GIT binary patch delta 32601 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 ReFJ3aLFwWllu-1-{{wt_3a$VE delta 8340 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 { + await discord.cloud.connect() + console.log("Discord gateway started") + + const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) + discord.cloud.on("event", f) +})() + +const expectedGuilds = new Set() + +const prepared = db.prepare("UPDATE channel_room SET name = ? WHERE channel_id = ?") + +/** @param {DiscordClient} discord */ +function onPacket(discord, event, unsubscribe) { + if (event.t === "READY") { + for (const obj of event.d.guilds) { + expectedGuilds.add(obj.id) + } + + } else if (event.t === "GUILD_CREATE") { + expectedGuilds.delete(event.d.id) + + // Store the channel. + for (const channel of event.d.channels || []) { + prepared.run(channel.name, channel.id) + } + + // Checked them all? + if (expectedGuilds.size === 0) { + discord.cloud.disconnect() + unsubscribe() + + // I don't know why node keeps running. + setTimeout(() => { + console.log("Stopping now.") + process.exit() + }, 1500).unref() + } + } +} diff --git a/test/data.js b/test/data.js index 52b8770..d2c586a 100644 --- a/test/data.js +++ b/test/data.js @@ -216,6 +216,63 @@ module.exports = { flags: 0, components: [] }, + simple_room_mention: { + type: 0, + tts: false, + timestamp: "2023-07-10T20:04:25.939000+00:00", + referenced_message: null, + pinned: false, + nonce: "1128054139385806848", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1128054143064494233", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "<#112760669178241024>", + components: [], + channel_id: "266767590641238027", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" + }, simple_message_link: { id: "1126788210308161626", type: 0, From 5326b7d6be1eacac987ec83a6f38d1b24b5eb0cc Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 10:10:47 +1200 Subject: [PATCH 069/200] get room mentions formatting working --- d2m/converters/message-to-event.js | 8 ++-- d2m/converters/message-to-event.test.js | 4 +- d2m/discord-client.js | 3 +- index.js | 2 +- scripts/save-channel-names-to-db.js | 58 +++++++++++++++++++++++++ test/data.js | 57 ++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 scripts/save-channel-names-to-db.js diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index fc76bf2..9999df9 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -20,11 +20,11 @@ function getDiscordParseCallbacks(message, useHTML) { } }, channel: node => { - const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) - if (roomID && useHTML) { - return "https://matrix.to/#/" + roomID + const {room_id, name, nick} = db.prepare("SELECT room_id, name, nick FROM channel_room WHERE channel_id = ?").get(node.id) + if (room_id && useHTML) { + return `#${nick || name}` } else { - return "#" + node.id + return `#${nick || name}` } }, role: node => diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index a456a94..e3fcb06 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -27,9 +27,9 @@ test("message2event: simple room mention", async t => { t.deepEqual(events, [{ $type: "m.room.message", msgtype: "m.text", - body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", + body: "#main", format: "org.matrix.custom.html", - formatted_body: '@crunch god Tell me about Phil, renowned martial arts master and creator of the Chin Trick' + formatted_body: '#main' }]) }) diff --git a/d2m/discord-client.js b/d2m/discord-client.js index 2092718..91682bd 100644 --- a/d2m/discord-client.js +++ b/d2m/discord-client.js @@ -12,8 +12,9 @@ const discordPackets = sync.require("./discord-packets") class DiscordClient { /** * @param {string} discordToken + * @param {boolean} listen whether to set up the event listeners for OOYE to operate */ - constructor(discordToken) { + constructor(discordToken, listen) { this.discordToken = discordToken this.snow = new SnowTransfer(discordToken) this.cloud = new CloudStorm(discordToken, { diff --git a/index.js b/index.js index 233d518..447e944 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ Object.assign(passthrough, {config, sync, db}) const DiscordClient = require("./d2m/discord-client") -const discord = new DiscordClient(config.discordToken) +const discord = new DiscordClient(config.discordToken, true) passthrough.discord = discord const as = require("./m2d/appservice") diff --git a/scripts/save-channel-names-to-db.js b/scripts/save-channel-names-to-db.js new file mode 100644 index 0000000..a70b1bb --- /dev/null +++ b/scripts/save-channel-names-to-db.js @@ -0,0 +1,58 @@ +// @ts-check + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const config = require("../config") +const passthrough = require("../passthrough") +const db = new sqlite("db/ooye.db") + +const sync = new HeatSync({watchFS: false}) + +Object.assign(passthrough, {config, sync, db}) + +const DiscordClient = require("../d2m/discord-client") + +const discord = new DiscordClient(config.discordToken, false) +passthrough.discord = discord + +;(async () => { + await discord.cloud.connect() + console.log("Discord gateway started") + + const f = event => onPacket(discord, event, () => discord.cloud.off("event", f)) + discord.cloud.on("event", f) +})() + +const expectedGuilds = new Set() + +const prepared = db.prepare("UPDATE channel_room SET name = ? WHERE channel_id = ?") + +/** @param {DiscordClient} discord */ +function onPacket(discord, event, unsubscribe) { + if (event.t === "READY") { + for (const obj of event.d.guilds) { + expectedGuilds.add(obj.id) + } + + } else if (event.t === "GUILD_CREATE") { + expectedGuilds.delete(event.d.id) + + // Store the channel. + for (const channel of event.d.channels || []) { + prepared.run(channel.name, channel.id) + } + + // Checked them all? + if (expectedGuilds.size === 0) { + discord.cloud.disconnect() + unsubscribe() + + // I don't know why node keeps running. + setTimeout(() => { + console.log("Stopping now.") + process.exit() + }, 1500).unref() + } + } +} diff --git a/test/data.js b/test/data.js index 52b8770..d2c586a 100644 --- a/test/data.js +++ b/test/data.js @@ -216,6 +216,63 @@ module.exports = { flags: 0, components: [] }, + simple_room_mention: { + type: 0, + tts: false, + timestamp: "2023-07-10T20:04:25.939000+00:00", + referenced_message: null, + pinned: false, + nonce: "1128054139385806848", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1128054143064494233", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "<#112760669178241024>", + components: [], + channel_id: "266767590641238027", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" + }, simple_message_link: { id: "1126788210308161626", type: 0, From 00bdab713a9d1b2f7232c593773f8798743e69c5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 11 Jul 2023 16:51:30 +1200 Subject: [PATCH 070/200] support rich replies, support basic m.mentions --- d2m/actions/send-message.js | 2 +- d2m/converters/message-to-event.js | 120 ++++++- d2m/converters/message-to-event.test.js | 126 +++++++ db/ooye.db | Bin 303104 -> 303104 bytes matrix/api.js | 22 ++ package.json | 2 +- test/data.js | 428 ++++++++++++++++++++++++ types.d.ts | 5 + 8 files changed, 689 insertions(+), 16 deletions(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index f5fe5ef..ff3c1de 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message, guild) + const events = await messageToEvent.messageToEvent(message, guild, api) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 9999df9..0e94a70 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const markdown = require("discord-markdown") +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough @@ -39,10 +40,56 @@ function getDiscordParseCallbacks(message, useHTML) { /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild + * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API */ -async function messageToEvent(message, guild) { +async function messageToEvent(message, guild, api) { const events = [] + /** + @type {{room?: boolean, user_ids?: string[]}} + We should consider the following scenarios for mentions: + 1. TODO A discord user rich-replies to a matrix user with a text post + + The matrix user needs to be m.mentioned in the text event + + The matrix user needs to have their name/mxid/link in the text event (notification fallback) + - So prepend their `@name:` to the start of the plaintext body + 2. TODO A discord user rich-replies to a matrix user with an image event only + + The matrix user needs to be m.mentioned in the image event + + The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) + - So append their name to the filename body, I guess!!! + 3. TODO A discord user `@`s a matrix user in the text body of their text box + + The matrix user needs to be m.mentioned in the text event + + No change needed to the text event content: it already has their name + - So make sure we don't do anything in this case. + */ + const mentions = {} + let repliedToEventId = null + let repliedToEventRoomId = null + let repliedToEventSenderMxid = null + let repliedToEventOriginallyFromMatrix = false + + function addMention(mxid) { + if (!mentions.user_ids) mentions.user_ids = [] + mentions.user_ids.push(mxid) + } + + // Mentions scenarios 1 and 2, part A. i.e. translate relevant message.mentions to m.mentions + // (Still need to do scenarios 1 and 2 part B, and scenario 3.) + if (message.type === DiscordTypes.MessageType.Reply && message.message_reference?.message_id) { + const row = db.prepare("SELECT event_id, room_id, source FROM event_message INNER JOIN channel_room USING (channel_id) WHERE message_id = ? AND part = 0").get(message.message_reference.message_id) + if (row) { + repliedToEventId = row.event_id + repliedToEventRoomId = row.room_id + repliedToEventOriginallyFromMatrix = row.source === 0 // source 0 = matrix + } + } + if (repliedToEventOriginallyFromMatrix) { + // Need to figure out who sent that event... + const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) + repliedToEventSenderMxid = event.sender + // Need to add the sender to m.mentions + addMention(repliedToEventSenderMxid) + } + // Text content appears first if (message.content) { let content = message.content @@ -55,33 +102,63 @@ async function messageToEvent(message, guild) { } }) - const html = markdown.toHTML(content, { + let html = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) - const body = markdown.toHTML(content, { + let body = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, false), discordOnly: true, escapeHTML: false, }, null, null) + // Fallback body/formatted_body for replies + if (repliedToEventId) { + let repliedToDisplayName + let repliedToUserHtml + if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { + const match = repliedToEventSenderMxid.match(/^@([^:]*)/) + assert(match) + repliedToDisplayName = match[1] || "a Matrix user" // grab the localpart as the display name, whatever + repliedToUserHtml = `${repliedToDisplayName}` + } else { + repliedToDisplayName = message.referenced_message?.author.global_name || message.referenced_message?.author.username || "a Discord user" + repliedToUserHtml = repliedToDisplayName + } + const repliedToContent = message.referenced_message?.content || "[Replied-to message content wasn't provided by Discord]" + const repliedToHtml = markdown.toHTML(repliedToContent, { + discordCallback: getDiscordParseCallbacks(message, true) + }, null, null) + const repliedToBody = markdown.toHTML(repliedToContent, { + discordCallback: getDiscordParseCallbacks(message, false), + discordOnly: true, + escapeHTML: false, + }, null, null) + html = `