diff --git a/docs/developer-orientation.md b/docs/developer-orientation.md index e17f263..dbb19f3 100644 --- a/docs/developer-orientation.md +++ b/docs/developer-orientation.md @@ -89,7 +89,7 @@ Whether you read those or not, I'm more than happy to help you 1-on-1 with codin # Dependency justification -Total transitive production dependencies: 121 +Total transitive production dependencies: 134 ### 🦕 @@ -108,7 +108,6 @@ Total transitive production dependencies: 121 * (0) @cloudrac3r/in-your-element: This is my Matrix Appservice API library. It depends on h3 and zod, which are already pulled in by OOYE. * (0) @cloudrac3r/mixin-deep: This is my fork. (It fixes a bug in regular mixin-deep.) * (0) @cloudrac3r/pngjs: Lottie stickers are converted to bitmaps with the vendored Rlottie WASM build, then the bitmaps are converted to PNG with pngjs. -* (0) @cloudrac3r/stream-type: Determine type of Matrix files that don't specify it in info. Switched from stream-mime-type to this. * (0) @cloudrac3r/turndown: This HTML-to-Markdown converter looked the most suitable. I forked it to change the escaping logic to match the way Discord works. * (3) @stackoverflow/stacks: Stack Overflow design language and icons. * (0) ansi-colors: Helps with interactive prompting for the initial setup, and it's already pulled in by enquirer. diff --git a/package-lock.json b/package-lock.json index 7b27322..9847400 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,21 +10,20 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@chriscdn/promise-semaphore": "^3.0.1", - "@cloudrac3r/discord-markdown": "^2.7.0", + "@cloudrac3r/discord-markdown": "^2.6.10", "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.1.1", "@cloudrac3r/mixin-deep": "^3.0.1", "@cloudrac3r/pngjs": "^7.0.3", "@cloudrac3r/pug": "^4.0.4", - "@cloudrac3r/stream-type": "^1.0.0", "@cloudrac3r/turndown": "^7.1.4", "@stackoverflow/stacks": "^2.5.4", "@stackoverflow/stacks-icons": "^6.0.2", "ansi-colors": "^4.1.3", "better-sqlite3": "^12.2.0", "chunk-text": "^2.0.1", - "cloudstorm": "^0.17.0", + "cloudstorm": "^0.15.2", "discord-api-types": "^0.38.38", "domino": "^2.1.6", "enquirer": "^2.4.1", @@ -37,8 +36,9 @@ "mime-types": "^2.1.35", "prettier-bytes": "^1.0.4", "sharp": "^0.34.5", - "snowtransfer": "^0.17.5", - "try-to-catch": "^4.0.5", + "snowtransfer": "^0.17.1", + "stream-mime-type": "^1.0.2", + "try-to-catch": "^3.0.1", "uqr": "^0.1.2", "xxhash-wasm": "^1.0.2", "zod": "^4.0.17" @@ -46,7 +46,7 @@ "devDependencies": { "@cloudrac3r/tap-dot": "^2.0.3", "@types/node": "^22.17.1", - "c8": "^11.0.0", + "c8": "^10.1.2", "cross-env": "^7.0.3", "supertape": "^12.0.12" }, @@ -68,23 +68,6 @@ "ts-node": "^10.9.2" } }, - "../nodejs-stream-type": { - "name": "@cloudrac3r/stream-type", - "version": "1.0.0", - "extraneous": true, - "license": "AGPL-3.0-only", - "devDependencies": { - "@cloudrac3r/tap-dot": "^2.0.3", - "@types/node": "^22.19.15", - "c8": "^11.0.0", - "cross-env": "^10.1.0", - "supertape": "^12.10.4", - "try-to-catch": "^4.0.5" - }, - "engines": { - "node": ">=22.6.0" - } - }, "../tap-dot": { "name": "@cloudrac3r/tap-dot", "version": "2.0.0", @@ -154,9 +137,9 @@ } }, "node_modules/@chriscdn/promise-semaphore": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@chriscdn/promise-semaphore/-/promise-semaphore-3.1.3.tgz", - "integrity": "sha512-EAmwIbH1L2CNsJWloXBG4Kv89H7IUsjYFQnGnmus3OX70LcD5Uu5A7sohPx3O0Ks9UQWEgcr5n2IfxBSuYvOeg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@chriscdn/promise-semaphore/-/promise-semaphore-3.1.2.tgz", + "integrity": "sha512-rELbH6FSr9wr5J249Ax8dpzQdTaqEgcW+lilDKZxB13Hz0Bz3Iyx4q/7qZxPMnra9FUW4ZOkVf+bx5tbi6Goog==", "license": "MIT" }, "node_modules/@cloudcmd/stub": { @@ -173,9 +156,9 @@ } }, "node_modules/@cloudrac3r/discord-markdown": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.7.0.tgz", - "integrity": "sha512-1iR9tKI2WJe8UNB+4VSh7D8m6RP7ugByuf8RNWyJwyhIrSlqQ8ljY1BKXodSvDg7seZkf7B7V2t5FfK7UpTw/A==", + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.10.tgz", + "integrity": "sha512-E+F9UYDUHP2kHDCciX63SBzgsUnHpu2Pp/h98x9Zo+vKuzXjCQ5PcFNdUlH6M18bvHDZPoIsKVmjnON8UYaAPQ==", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.3" @@ -195,9 +178,9 @@ } }, "node_modules/@cloudrac3r/in-your-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@cloudrac3r/in-your-element/-/in-your-element-1.1.2.tgz", - "integrity": "sha512-adFZel24sGHpTI1vgJdBN5twcdu6QmPFlO8qAJt49KO6N8mwDcbUC2GPqH5pGerXNv1Lpq0eXsNLm+ytKrOTaQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cloudrac3r/in-your-element/-/in-your-element-1.1.1.tgz", + "integrity": "sha512-AKp9vnSDA9wzJl4O3C/LA8jgI5m1r0M3MRBQGHcVVL22SrrZMdcy+kWjlZWK343KVLOkuTAISA2D+Jb/zyZS6A==", "license": "AGPL-3.0-or-later", "dependencies": { "h3": "^1.12.0", @@ -263,15 +246,6 @@ "pug-error": "^2.1.0" } }, - "node_modules/@cloudrac3r/stream-type": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@cloudrac3r/stream-type/-/stream-type-1.0.0.tgz", - "integrity": "sha512-orfdUaeDT00fkELxAab+pJNZWwis+KijJEWw+cUWOD2VqqQWriL04W5DOPN0dlsJvn4VoyBe6cYGrzsJ5YPcOw==", - "license": "AGPL-3.0-only", - "engines": { - "node": ">=22.6.0" - } - }, "node_modules/@cloudrac3r/tap-dot": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@cloudrac3r/tap-dot/-/tap-dot-2.0.3.tgz", @@ -791,12 +765,21 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "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, - "license": "MIT", "engines": { "node": ">=8" } @@ -1053,6 +1036,11 @@ "node": ">=22" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "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", @@ -1060,9 +1048,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", "dev": true, "license": "MIT", "dependencies": { @@ -1097,15 +1085,6 @@ "node": ">=6" } }, - "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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1153,14 +1132,10 @@ "license": "MIT" }, "node_modules/balanced-match": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", - "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -1236,25 +1211,34 @@ "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/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/c8": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", - "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, - "license": "ISC", "dependencies": { "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", @@ -1263,7 +1247,7 @@ "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", - "test-exclude": "^8.0.0", + "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" @@ -1272,7 +1256,7 @@ "c8": "bin/c8.js" }, "engines": { - "node": "20 || >=22" + "node": ">=18" }, "peerDependencies": { "monocart-coverage-reports": "^2" @@ -1364,14 +1348,35 @@ "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.17.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.17.0.tgz", - "integrity": "sha512-zsd9y5ljNnbxdvDid9TgWePDqo7il4so5spzx6NDwZ67qWQjR96UUhLxJ+BAOdBBSPF9UXFM61dAzC2g918q+A==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.15.2.tgz", + "integrity": "sha512-5y7E0uI39R3d7c+AWksqAQAlZlpx+qNjxjQfNIem2hh68s6QRmOFHTKu34I7pBE6JonpZf8AmoMYArY/4lLVmg==", "license": "MIT", "dependencies": { - "discord-api-types": "^0.38.40", - "snowtransfer": "^0.17.5" + "discord-api-types": "^0.38.37", + "snowtransfer": "^0.17.0" }, "engines": { "node": ">=22.0.0" @@ -1512,9 +1517,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.38.41", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.41.tgz", - "integrity": "sha512-yMECyR8j9c2fVTvCQ+Qc24pweYFIZk/XoxDOmt1UvPeSw5tK6gXBd/2hhP+FEAe9Y6ny8pRMaf618XDK4U53OQ==", + "version": "0.38.38", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.38.tgz", + "integrity": "sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q==", "license": "MIT", "workspaces": [ "scripts/actions/documentation" @@ -1556,6 +1561,25 @@ "node": ">=8.6" } }, + "node_modules/enquirer/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/enquirer/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/entities": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", @@ -1593,6 +1617,22 @@ "node": ">= 4.9.1" } }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/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", @@ -1615,11 +1655,10 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", - "dev": true, - "license": "ISC" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true }, "node_modules/foreground-child": { "version": "3.3.1", @@ -1692,18 +1731,66 @@ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-12.0.0.tgz", + "integrity": "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1886,6 +1973,22 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest-diff": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", @@ -1920,13 +2023,6 @@ "integrity": "sha512-p2BdO7o4BI+pMun3J+dhaOfYan5JsZrw9wjshRjkWY9+p+u+kKSMhNWYnot2yHDR9CSahZ9iT3dcqJ+V72qHMw==", "dev": true }, - "node_modules/just-snake-case": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/just-snake-case/-/just-snake-case-3.2.0.tgz", - "integrity": "sha512-iugHP9bSE0jOq3BzN0W0rdu/OOkFirPe8FtUw6v9y37UlbUDgJ1x4xiGNfUhI6mV9dc/paaifyiyn+F+mrm8gw==", - "dev": true, - "license": "MIT" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1943,9 +2039,9 @@ } }, "node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -1998,16 +2094,16 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2022,11 +2118,10 @@ } }, "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -2104,6 +2199,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2129,9 +2230,9 @@ "dev": true }, "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2139,12 +2240,24 @@ "minipass": "^7.1.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -2312,11 +2425,25 @@ "dev": true, "license": "MIT" }, - "node_modules/readable-stream": { + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-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==", - "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -2521,12 +2648,12 @@ } }, "node_modules/snowtransfer": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.17.5.tgz", - "integrity": "sha512-nVI1UJNFoX1ndGFZxB3zb3X5SWtD9hIAcw7wCgVKWvCf42Wg2B4UFIrZWI83HxaSBY0CGbPZmZzZb3RSt/v2wQ==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.17.1.tgz", + "integrity": "sha512-WSXj055EJhzzfD7B3oHVyRTxkqFCaxcVhwKY6B3NkBSHRyM6wHxZLq6VbFYhopUg+lMtd7S1ZO8JM+Ut+js2iA==", "license": "MIT", "dependencies": { - "discord-api-types": "^0.38.40" + "discord-api-types": "^0.38.37" }, "engines": { "node": ">=22.0.0" @@ -2561,6 +2688,30 @@ "get-source": "^2.0.12" } }, + "node_modules/stream-head": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-head/-/stream-head-2.0.2.tgz", + "integrity": "sha512-aRkUMcmgbDl2Yjd5LqsB1LKB58Ot3JZ4ffuFMkFuvkPQT5X5XFMr4YK2dctApc+d3o52CXU1KUFisYaF/4zjAQ==", + "dependencies": { + "through2": "4.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stream-mime-type": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-mime-type/-/stream-mime-type-1.0.2.tgz", + "integrity": "sha512-80GzRn7JICPDEPBhSyqJjbztqX66+3DpkuUUcgDHtRBQlZRTkbCz0BsISggUl7AnyinJk9zyHVnd2lftlZXDdg==", + "dependencies": { + "file-type": "^16.0.1", + "mime-types": "^2.1.27", + "stream-head": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2583,11 +2734,20 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width/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/string-width/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==", - "license": "MIT", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2603,10 +2763,26 @@ "node": ">=0.10.0" } }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/supertape": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/supertape/-/supertape-12.7.0.tgz", - "integrity": "sha512-5PXh6HsfEJKkC0SMhPNkH35o8Okj8xlVvoju9R0aCohzsK+GEufeYZ1IPhRBRQ2DBLXdMZHVF6N/4pAefxNuAA==", + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/supertape/-/supertape-12.0.12.tgz", + "integrity": "sha512-ugmCQsB7s22fCTJKiMb6+Fd8kP7Hsvlo6/aly0qLGgOepu1PVBydhrBPMWaoY3wf+VqLtMkkvwGxUTCFde5z/g==", "dev": true, "license": "MIT", "dependencies": { @@ -2624,10 +2800,9 @@ "cli-progress": "^3.8.2", "flatted": "^3.3.1", "fullstore": "^4.0.0", - "glob": "^13.0.0", + "glob": "^11.0.1", "jest-diff": "^30.0.3", "json-with-bigint": "^3.4.4", - "just-snake-case": "^3.2.0", "once": "^1.4.0", "resolve": "^1.17.0", "stacktracey": "^2.1.7", @@ -2643,6 +2818,16 @@ "node": ">=22" } }, + "node_modules/supertape/node_modules/try-to-catch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-4.0.3.tgz", + "integrity": "sha512-mUz1zpe6nkRQW0XZ/Ojfe/Eg7e5h3s+r+h7ONfP3Oo27/Jm8mkNDAnLzZ/A3sEMApROolzuJGBiQhGmmVDAFLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, "node_modules/supertape/node_modules/yargs-parser": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", @@ -2704,19 +2889,52 @@ "node": ">=6" } }, - "node_modules/test-exclude": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", - "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", - "dev": true, - "license": "ISC", + "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": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^13.0.6", - "minimatch": "^10.2.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": "20 || >=22" + "node": ">= 6" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2/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/timer-node": { @@ -2731,6 +2949,22 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/try-catch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-4.0.7.tgz", @@ -2742,12 +2976,11 @@ } }, "node_modules/try-to-catch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-4.0.5.tgz", - "integrity": "sha512-VKBslDQsy4pGj2TMNgDdskWb7AWSi/9dPEmcNv3sdL0+aRMQTPJo6aEqlcuN0vbOwFfsE1oAXmx3bFdf6vrJFg==", - "license": "MIT", + "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==", "engines": { - "node": ">=22" + "node": ">=6" } }, "node_modules/tslib": { @@ -2866,6 +3099,27 @@ "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", @@ -2931,9 +3185,9 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 0e666aa..afbb90a 100644 --- a/package.json +++ b/package.json @@ -19,21 +19,20 @@ }, "dependencies": { "@chriscdn/promise-semaphore": "^3.0.1", - "@cloudrac3r/discord-markdown": "^2.7.0", + "@cloudrac3r/discord-markdown": "^2.6.10", "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.1.1", "@cloudrac3r/mixin-deep": "^3.0.1", "@cloudrac3r/pngjs": "^7.0.3", "@cloudrac3r/pug": "^4.0.4", - "@cloudrac3r/stream-type": "^1.0.0", "@cloudrac3r/turndown": "^7.1.4", "@stackoverflow/stacks": "^2.5.4", "@stackoverflow/stacks-icons": "^6.0.2", "ansi-colors": "^4.1.3", "better-sqlite3": "^12.2.0", "chunk-text": "^2.0.1", - "cloudstorm": "^0.17.0", + "cloudstorm": "^0.15.2", "discord-api-types": "^0.38.38", "domino": "^2.1.6", "enquirer": "^2.4.1", @@ -46,8 +45,9 @@ "mime-types": "^2.1.35", "prettier-bytes": "^1.0.4", "sharp": "^0.34.5", - "snowtransfer": "^0.17.5", - "try-to-catch": "^4.0.5", + "snowtransfer": "^0.17.1", + "stream-mime-type": "^1.0.2", + "try-to-catch": "^3.0.1", "uqr": "^0.1.2", "xxhash-wasm": "^1.0.2", "zod": "^4.0.17" @@ -58,7 +58,7 @@ "devDependencies": { "@cloudrac3r/tap-dot": "^2.0.3", "@types/node": "^22.17.1", - "c8": "^11.0.0", + "c8": "^10.1.2", "cross-env": "^7.0.3", "supertape": "^12.0.12" }, diff --git a/scripts/backfill.js b/scripts/backfill.js index 941e803..27600f0 100644 --- a/scripts/backfill.js +++ b/scripts/backfill.js @@ -10,6 +10,7 @@ if (!channelID) { process.exit(1) } +const assert = require("assert/strict") const sqlite = require("better-sqlite3") const backfill = new sqlite("scripts/backfill.db") backfill.prepare("CREATE TABLE IF NOT EXISTS backfill (channel_id TEXT NOT NULL, message_id INTEGER NOT NULL, PRIMARY KEY (channel_id, message_id))").run() @@ -37,8 +38,12 @@ passthrough.select = orm.select /** @type {import("../src/d2m/event-dispatcher")}*/ const eventDispatcher = sync.require("../src/d2m/event-dispatcher") -/** @type {import("../src/d2m/actions/create-room")} */ -const createRoom = sync.require("../src/d2m/actions/create-room") + +const roomID = passthrough.select("channel_room", "room_id", {channel_id: channelID}).pluck().get() +if (!roomID) { + console.error("Please choose a channel that's already bridged.") + process.exit(1) +} ;(async () => { await discord.cloud.connect() @@ -55,29 +60,23 @@ async function event(event) { if (!channel) return const guild_id = event.d.id - try { - await createRoom.syncRoom(channelID) - let last = backfill.prepare("SELECT cast(max(message_id) as TEXT) FROM backfill WHERE channel_id = ?").pluck().get(channelID) || "0" - console.log(`OK, processing messages for #${channel.name}, continuing from ${last}`) + let last = backfill.prepare("SELECT cast(max(message_id) as TEXT) FROM backfill WHERE channel_id = ?").pluck().get(channelID) || "0" + console.log(`OK, processing messages for #${channel.name}, continuing from ${last}`) - while (last) { - const messages = await discord.snow.channel.getChannelMessages(channelID, {limit: 50, after: String(last)}) - messages.reverse() // More recent messages come first -> More recent messages come last - for (const message of messages) { - const simulatedGatewayDispatchData = { - guild_id, - backfill: true, - ...message - } - await eventDispatcher.MESSAGE_CREATE(discord, simulatedGatewayDispatchData) - preparedInsert.run(channelID, message.id) + while (last) { + const messages = await discord.snow.channel.getChannelMessages(channelID, {limit: 50, after: String(last)}) + messages.reverse() // More recent messages come first -> More recent messages come last + for (const message of messages) { + const simulatedGatewayDispatchData = { + guild_id, + backfill: true, + ...message } - last = messages.at(-1)?.id + await eventDispatcher.MESSAGE_CREATE(discord, simulatedGatewayDispatchData) + preparedInsert.run(channelID, message.id) } - - process.exit() - } catch (e) { - console.error(e) - process.exit(1) // won't exit automatically on thrown error due to living discord connection, so manual exit is necessary + last = messages.at(-1)?.id } + + process.exit() } diff --git a/src/d2m/converters/edit-to-changes.test.js b/src/d2m/converters/edit-to-changes.test.js index 842c24e..cb1fb5a 100644 --- a/src/d2m/converters/edit-to-changes.test.js +++ b/src/d2m/converters/edit-to-changes.test.js @@ -78,7 +78,7 @@ test("edit2changes: bot response", async t => { 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.", + body: "* :ae_botrac4r: [@cadence](https://matrix.to/#/@cadence:cadence.moe) 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": { @@ -87,7 +87,7 @@ test("edit2changes: bot response", async t => { // *** 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.", + body: ":ae_botrac4r: [@cadence](https://matrix.to/#/@cadence:cadence.moe) 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": { diff --git a/src/d2m/converters/find-mentions.js b/src/d2m/converters/find-mentions.js index 8107459..8726830 100644 --- a/src/d2m/converters/find-mentions.js +++ b/src/d2m/converters/find-mentions.js @@ -146,18 +146,10 @@ function findMention(pjr, maximumWrittenSection, baseOffset, prefix, content) { // Highlight the relevant part of the message const start = baseOffset + best.scored.matchedInputTokens[0].index const end = baseOffset + prefix.length + best.scored.matchedInputTokens.slice(-1)[0].end - const newNodes = [{ - type: "text", content: content.slice(0, start) - }, { - type: "link", target: `https://matrix.to/#/${best.mxid}`, content: [ - {type: "text", content: content.slice(start, end)} - ] - }, { - type: "text", content: content.slice(end) - }] + const newContent = content.slice(0, start) + "[" + content.slice(start, end) + "](https://matrix.to/#/" + best.mxid + ")" + content.slice(end) return { mxid: best.mxid, - newNodes + newContent } } } diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index adc56e6..7f77b81 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -261,29 +261,6 @@ function getFormattedInteraction(interaction, isThinkingInteraction) { } } -/** - * @param {any} newEvents merge into events - * @param {any} events will be modified - * @param {boolean} forceSameMsgtype whether m.text may only be combined with m.text, etc - */ -function mergeTextEvents(newEvents, events, forceSameMsgtype) { - let prev = events.at(-1) - for (const ne of newEvents) { - const isAllText = prev?.body && prev?.formatted_body && ["m.text", "m.notice"].includes(ne.msgtype) && ["m.text", "m.notice"].includes(prev?.msgtype) - const typesPermitted = !forceSameMsgtype || ne?.msgtype === prev?.msgtype - if (isAllText && typesPermitted) { - const rep = new mxUtils.MatrixStringBuilder() - rep.body = prev.body - rep.formattedBody = prev.formatted_body - rep.addLine(ne.body, ne.formatted_body) - prev.body = rep.body - prev.formatted_body = rep.formattedBody - } else { - events.push(ne) - } - } -} - /** * @param {DiscordTypes.APIMessage} message * @param {DiscordTypes.APIGuild} guild @@ -542,60 +519,29 @@ async function messageToEvent(message, guild, options = {}, di) { return emojiToKey.emojiToKey({id, name, animated}, message.id) // Register the custom emoji if needed })) - async function transformParsedVia(parsed, scanTextForMentions) { - for (let n = 0; n < parsed.length; n++) { - const node = parsed[n] + async function transformParsedVia(parsed) { + for (const node of parsed) { if (node.type === "discordChannel" || node.type === "discordChannelLink") { node.row = select("channel_room", ["room_id", "name", "nick"], {channel_id: node.id}).get() if (node.row?.room_id) { node.via = await getViaServersMemo(node.row.room_id) } } - else if (node.type === "text" && typeof node.content === "string") { - // Merge adjacent text nodes into this one - while (parsed[n+1]?.type === "text" && typeof parsed[n+1].content === "string") { - node.content += parsed[n+1].content - parsed.splice(n+1, 1) - } - // Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention. - if (scanTextForMentions) { - let content = node.content - const matches = [...content.matchAll(/(@ ?)([a-z0-9_.#$][^@\n]+)/gi)] - for (let i = matches.length; i--;) { - const m = matches[i] - const prefix = m[1] - const maximumWrittenSection = m[2].toLowerCase() - if (m.index > 0 && !content[m.index-1].match(/ |\(|\n/)) continue // must have space before it - if (maximumWrittenSection.match(/^everyone\b/) || maximumWrittenSection.match(/^here\b/)) continue // ignore @everyone/@here - - var roomID = roomID ?? select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get() - assert(roomID) - var pjr = pjr ?? findMentions.processJoined(Object.entries((await di.api.getJoinedMembers(roomID)).joined).map(([mxid, ev]) => ({mxid, displayname: ev.display_name}))) - - const found = findMentions.findMention(pjr, maximumWrittenSection, m.index, prefix, content) - if (found) { - addMention(found.mxid) - parsed.splice(n, 1, ...found.newNodes) - content = found.newNodes[0].content - } - } - } - } for (const maybeChildNodesArray of [node, node.content, node.items]) { if (Array.isArray(maybeChildNodesArray)) { - await transformParsedVia(maybeChildNodesArray, scanTextForMentions && ["blockQuote", "list", "paragraph", "em", "strong", "u", "del", "text"].includes(node.type)) + await transformParsedVia(maybeChildNodesArray) } } } return parsed } - let html = await markdown.toHtmlWithPostParser(content, parsed => transformParsedVia(parsed, customOptions.isTheMessageContent && options.scanTextForMentions !== false), { + let html = await markdown.toHtmlWithPostParser(content, transformParsedVia, { discordCallback: getDiscordParseCallbacks(message, guild, true, spoilers), ...customOptions }, customParser, customHtmlOutput) - let body = await markdown.toHtmlWithPostParser(content, parsed => transformParsedVia(parsed, false), { // not scanning plaintext body for mentions as we don't parse whether they're in code + let body = await markdown.toHtmlWithPostParser(content, transformParsedVia, { discordCallback: getDiscordParseCallbacks(message, guild, false), discordOnly: true, escapeHTML: false, @@ -636,8 +582,7 @@ async function messageToEvent(message, guild, options = {}, di) { // check that condition 1 or 2 is met if (repliedToEventInDifferentRoom || repliedToUnknownEvent) { let referenced = message.referenced_message - /* c8 ignore next 4 - backend couldn't be bothered to dereference the message, have to do it ourselves */ - if (!referenced) { + if (!referenced) { // backend couldn't be bothered to dereference the message, have to do it ourselves assert(message.message_reference?.message_id) referenced = await discord.snow.channel.getChannelMessage(message.message_reference.channel_id, message.message_reference.message_id) } @@ -789,30 +734,42 @@ async function messageToEvent(message, guild, options = {}, di) { // Then text content if (message.content && !isOnlyKlipyGIF && !isThinkingInteraction) { + // Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention. + let content = message.content + if (options.scanTextForMentions !== false) { + const matches = [...content.matchAll(/(@ ?)([a-z0-9_.#$][^@\n]+)/gi)] + for (let i = matches.length; i--;) { + const m = matches[i] + const prefix = m[1] + const maximumWrittenSection = m[2].toLowerCase() + if (m.index > 0 && !content[m.index-1].match(/ |\(|\n/)) continue // must have space before it + if (maximumWrittenSection.match(/^everyone\b/) || maximumWrittenSection.match(/^here\b/)) continue // ignore @everyone/@here + + var roomID = roomID ?? select("channel_room", "room_id", {channel_id: message.channel_id}).pluck().get() + assert(roomID) + var pjr = pjr ?? findMentions.processJoined(Object.entries((await di.api.getJoinedMembers(roomID)).joined).map(([mxid, ev]) => ({mxid, displayname: ev.display_name}))) + + const found = findMentions.findMention(pjr, maximumWrittenSection, m.index, prefix, content) + if (found) { + addMention(found.mxid) + content = found.newContent + } + } + } + // Scan the content for emojihax and replace them with real emojis - let content = message.content.replaceAll(/\[([a-zA-Z0-9_-]{2,32})(?:~[0-9]+)?\]\(https:\/\/cdn\.discordapp\.com\/emojis\/([0-9]+)\.[^ \n)`]+\)/g, (_, name, id) => { + content = content.replaceAll(/\[([a-zA-Z0-9_-]{2,32})(?:~[0-9]+)?\]\(https:\/\/cdn\.discordapp\.com\/emojis\/([0-9]+)\.[^ \n)`]+\)/g, (_, name, id) => { return `<:${name}:${id}>` }) - const {body, html} = await transformContent(content, {isTheMessageContent: true}) + const {body, html} = await transformContent(content) await addTextEvent(body, html, msgtype) } // Then scheduled events if (message.content && di?.snow) { for (const match of [...message.content.matchAll(/discord\.gg\/([A-Za-z0-9]+)\?event=([0-9]{18,})/g)]) { // snowflake has minimum 18 because the events feature is at least that old - let invite - try { - invite = await di.snow.invite.getInvite(match[1], {guild_scheduled_event_id: match[2]}) - } catch (e) { - // Skip expired/invalid invites and events - if (e.message === `{"message": "Unknown Invite", "code": 10006}`) { - break - } else { - throw e - } - } - + const invite = await di.snow.invite.getInvite(match[1], {guild_scheduled_event_id: match[2]}) const event = invite.guild_scheduled_event if (!event) continue // the event ID provided was not valid @@ -858,7 +815,15 @@ async function messageToEvent(message, guild, options = {}, di) { // Try to merge attachment events with the previous event // This means that if the attachments ended up as a text link, and especially if there were many of them, the events will be joined together. - mergeTextEvents(attachmentEvents, events, false) + let prev = events.at(-1) + for (const atch of attachmentEvents) { + if (atch.msgtype === "m.text" && prev?.body && prev?.formatted_body && ["m.text", "m.notice"].includes(prev?.msgtype)) { + prev.body = prev.body + "\n" + atch.body + prev.formatted_body = prev.formatted_body + "
" + atch.formatted_body + } else { + events.push(atch) + } + } } // Then components @@ -940,8 +905,11 @@ async function messageToEvent(message, guild, options = {}, di) { else if (component.type === DiscordTypes.ComponentType.Button) { // May only be a section accessory or in an action row (up to 5) if (component.style === DiscordTypes.ButtonStyle.Link) { - assert(component.label) // required for Discord to validate link buttons - stack.msb.add(`[${component.label} ${component.url}] `, tag`${component.label} `) + if (component.label) { + stack.msb.add(`[${component.label} ${component.url}] `, tag`${component.label} `) + } else { + stack.msb.add(component.url) + } } } @@ -996,7 +964,6 @@ async function messageToEvent(message, guild, options = {}, di) { // Start building up a replica ("rep") of the embed in Discord-markdown format, which we will convert into both plaintext and formatted body at once const rep = new mxUtils.MatrixStringBuilder() - let isAdditionalImage = false if (isKlipyGIF) { assert(embed.video?.url) @@ -1063,11 +1030,7 @@ async function messageToEvent(message, guild, options = {}, di) { let chosenImage = embed.image?.url // the thumbnail seems to be used for "article" type but displayed big at the bottom by discord if (embed.type === "article" && embed.thumbnail?.url && !chosenImage) chosenImage = embed.thumbnail.url - - if (chosenImage) { - isAdditionalImage = !rep.body && !!events.length - rep.addParagraph(`📸 ${dUtils.getPublicUrlForCdn(chosenImage)}`) - } + if (chosenImage) rep.addParagraph(`📸 ${dUtils.getPublicUrlForCdn(chosenImage)}`) if (embed.video?.url) rep.addParagraph(`🎞️ ${dUtils.getPublicUrlForCdn(embed.video.url)}`) @@ -1076,11 +1039,6 @@ async function messageToEvent(message, guild, options = {}, di) { body = body.split("\n").map(l => "| " + l).join("\n") html = `
${html}
` - if (isAdditionalImage) { - mergeTextEvents([{...rep.get(), body, html, msgtype: "m.notice"}], events, true) - continue - } - // Send as m.notice to apply the usual automated/subtle appearance, showing this wasn't actually typed by the person await addTextEvent(body, html, "m.notice") } diff --git a/src/d2m/converters/message-to-event.test.embeds.js b/src/d2m/converters/message-to-event.test.embeds.js index 73244d3..259aa66 100644 --- a/src/d2m/converters/message-to-event.test.embeds.js +++ b/src/d2m/converters/message-to-event.test.embeds.js @@ -204,44 +204,6 @@ test("message2event embeds: author url without name", async t => { }]) }) -test("message2event embeds: 4 images", async t => { - const events = await messageToEvent(data.message_with_embeds.four_images, data.guild.general) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "[🔀 Forwarded message]\n» https://fixupx.com/i/status/2032003668787020046", - format: "org.matrix.custom.html", - formatted_body: "🔀 Forwarded message
https://fixupx.com/i/status/2032003668787020046
", - "m.mentions": {} - }, { - $type: "m.room.message", - msgtype: "m.notice", - body: "» | ## ⏺️ AUTOMATON WEST (@AUTOMATON_ENG) https://x.com/AUTOMATON_ENG/status/2032003668787020046" - + "\n» | " - + "\n» | 4chan owner Hiroyuki, Evangelion director Hideaki Anno and GACKT to participate in “humanity’s last non\\-AI made social network”" - + "\n» | ︀︀" - + "\n» | ︀︀[automaton-media.com/en/news/4chan-owner-hiroyuki-evangelion-director-hideaki-anno-and-gackt-to-participate-in-humanitys-last-non-ai-made-social-network/](https://automaton-media.com/en/news/4chan-owner-hiroyuki-evangelion-director-hideaki-anno-and-gackt-to-participate-in-humanitys-last-non-ai-made-social-network/)" - + "\n» | " - + "\n» | **[💬](https://x.com/intent/tweet?in_reply_to=2032003668787020046) 36 [🔁](https://x.com/intent/retweet?tweet_id=2032003668787020046) 212 [❤](https://x.com/intent/like?tweet_id=2032003668787020046) 3\\.0K 👁 131\\.7K **" - + "\n» | " - + "\n» | 📸 https://pbs.twimg.com/media/HDMUyf6bQAM3yts.jpg?name=orig" - + "\n» | — FixupX" - + "\n» | 📸 https://pbs.twimg.com/media/HDMUgxybQAE4FtJ.jpg?name=orig" - + "\n» | 📸 https://pbs.twimg.com/media/HDMUrPobgAAeb90.jpg?name=orig" - + "\n» | 📸 https://pbs.twimg.com/media/HDMUuy5bgAAInj5.jpg?name=orig", - format: "org.matrix.custom.html", - formatted_body: "

⏺️ AUTOMATON WEST (@AUTOMATON_ENG)

" - + "

4chan owner Hiroyuki, Evangelion director Hideaki Anno and GACKT to participate in “humanity’s last non-AI made social network”" - + "
︀︀
︀︀automaton-media.com/en/news/4chan-owner-hiroyuki-evangelion-director-hideaki-anno-and-gackt-to-participate-in-humanitys-last-non-ai-made-social-network/" - + "

💬 36 🔁 212  3.0K 👁 131.7K 

" - + "

📸 https://pbs.twimg.com/media/HDMUyf6bQAM3yts.jpg?name=orig

— FixupX
" - + "

📸 https://pbs.twimg.com/media/HDMUgxybQAE4FtJ.jpg?name=orig

" - + "

📸 https://pbs.twimg.com/media/HDMUrPobgAAeb90.jpg?name=orig

" - + "

📸 https://pbs.twimg.com/media/HDMUuy5bgAAInj5.jpg?name=orig

", - "m.mentions": {} - }]) -}) - test("message2event embeds: vx image", async t => { const events = await messageToEvent(data.message_with_embeds.vx_image, data.guild.general) t.deepEqual(events, [{ diff --git a/src/d2m/converters/message-to-event.test.js b/src/d2m/converters/message-to-event.test.js index c4b812d..1a73aea 100644 --- a/src/d2m/converters/message-to-event.test.js +++ b/src/d2m/converters/message-to-event.test.js @@ -789,7 +789,7 @@ test("message2event: simple written @mention for matrix user", async t => { ] }, msgtype: "m.text", - body: "@ash do you need anything from the store btw as I'm heading there after gym", + body: "[@ash](https://matrix.to/#/@she_who_brings_destruction:cadence.moe) do you need anything from the store btw as I'm heading there after gym", format: "org.matrix.custom.html", formatted_body: `@ash do you need anything from the store btw as I'm heading there after gym` }]) @@ -838,7 +838,7 @@ test("message2event: many written @mentions for matrix users", 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", + body: "[@Cadence](https://matrix.to/#/@cadence:cadence.moe), tell me about @Phil, the creator of the Chin Trick, who has become ever more powerful under the mentorship of @botrac4r and [@huck](https://matrix.to/#/@huckleton:cadence.moe)", format: "org.matrix.custom.html", formatted_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` }]) @@ -890,7 +890,7 @@ test("message2event: written @mentions may match part of the name", async t => { ] }, msgtype: "m.text", - body: "I wonder if @cadence saw this?", + body: "I wonder if [@cadence](https://matrix.to/#/@secret:cadence.moe) saw this?", format: "org.matrix.custom.html", formatted_body: `I wonder if @cadence saw this?` }]) @@ -941,7 +941,7 @@ test("message2event: written @mentions may match part of the mxid", async t => { ] }, msgtype: "m.text", - body: "I wonder if @huck saw this?", + body: "I wonder if [@huck](https://matrix.to/#/@huckleton:cadence.moe) saw this?", format: "org.matrix.custom.html", formatted_body: `I wonder if @huck saw this?` }]) @@ -962,36 +962,6 @@ test("message2event: written @mentions do not match in URLs", async t => { }]) }) -test("message2event: written @mentions do not match in inline code", async t => { - const events = await messageToEvent({ - ...data.message.advanced_written_at_mention_for_matrix, - content: "`public @Nullable EntityType`" - }, data.guild.general, {}, {}) - t.deepEqual(events, [{ - $type: "m.room.message", - "m.mentions": {}, - msgtype: "m.text", - body: "`public @Nullable EntityType`", - format: "org.matrix.custom.html", - formatted_body: `public @Nullable EntityType<?>` - }]) -}) - -test("message2event: written @mentions do not match in code block", async t => { - const events = await messageToEvent({ - ...data.message.advanced_written_at_mention_for_matrix, - content: "```java\npublic @Nullable EntityType\n```" - }, data.guild.general, {}, {}) - t.deepEqual(events, [{ - $type: "m.room.message", - "m.mentions": {}, - msgtype: "m.text", - body: "```java\npublic @Nullable EntityType\n```", - format: "org.matrix.custom.html", - formatted_body: `
public @Nullable EntityType<?>
` - }]) -}) - test("message2event: entire message may match elaborate display name", async t => { let called = 0 const events = await messageToEvent({ @@ -1037,7 +1007,7 @@ test("message2event: entire message may match elaborate display name", async t = ] }, msgtype: "m.text", - body: "@Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆", + body: "[@Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆](https://matrix.to/#/@wa:cadence.moe)", format: "org.matrix.custom.html", formatted_body: `@Cadence, Maid of Creation, Eye of Clarity, Empress of Hope ☆` }]) @@ -1114,7 +1084,7 @@ test("message2event: multiple attachments are combined into the same event where formatted_body: "hey" + `
📄 Uploaded file: hey.jpg (100 MB)` + `
📸 Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg (38 KB)
` - + `📄 Uploaded file: hey.jpg (100 MB)` + + `
📄 Uploaded file: hey.jpg (100 MB)` }, { $type: "m.room.message", "m.mentions": {}, @@ -1568,28 +1538,6 @@ test("message2event: vc invite event renders embed with room link", async t => { ]) }) -test("message2event: expired/invalid invites are sent as-is", async t => { - const events = await messageToEvent({content: "https://discord.gg/placeholder?event=1381190945646710824"}, {}, {}, { - snow: { - invite: { - async getInvite() { - throw new Error(`{"message": "Unknown Invite", "code": 10006}`) - } - } - } - }) - t.deepEqual(events, [ - { - $type: "m.room.message", - body: "https://discord.gg/placeholder?event=1381190945646710824", - format: "org.matrix.custom.html", - formatted_body: "https://discord.gg/placeholder?event=1381190945646710824", - "m.mentions": {}, - msgtype: "m.text", - } - ]) -}) - test("message2event: channel links are converted even inside lists (parser post-processer descends into list items)", async t => { let called = 0 const events = await messageToEvent({ diff --git a/src/d2m/converters/user-to-mxid.test.js b/src/d2m/converters/user-to-mxid.test.js index b020e15..f8cf16a 100644 --- a/src/d2m/converters/user-to-mxid.test.js +++ b/src/d2m/converters/user-to-mxid.test.js @@ -1,5 +1,5 @@ const {test} = require("supertape") -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const assert = require("assert") const data = require("../../../test/data") const {userToSimName, webhookAuthorToSimName} = require("./user-to-mxid") diff --git a/src/d2m/discord-client.js b/src/d2m/discord-client.js index c2a0549..7b0fcf8 100644 --- a/src/d2m/discord-client.js +++ b/src/d2m/discord-client.js @@ -52,11 +52,7 @@ class DiscordClient { /** @type {Map>} */ this.guildChannelMap = new Map() if (listen !== "no") { - this.cloud.on("event", message => { - process.nextTick(() => { - discordPackets.onPacket(this, message, listen) - }) - }) + this.cloud.on("event", message => discordPackets.onPacket(this, message, listen)) } const addEventLogger = (eventName, logName) => { diff --git a/src/db/migrations/0035-role-default.sql b/src/db/migrations/0035-role-default.sql deleted file mode 100644 index 6c44e7e..0000000 --- a/src/db/migrations/0035-role-default.sql +++ /dev/null @@ -1,9 +0,0 @@ -BEGIN TRANSACTION; - -CREATE TABLE "role_default" ( - "guild_id" TEXT NOT NULL, - "role_id" TEXT NOT NULL, - PRIMARY KEY ("guild_id", "role_id") -); - -COMMIT; diff --git a/src/db/orm-defs.d.ts b/src/db/orm-defs.d.ts index f6628f2..79f02ad 100644 --- a/src/db/orm-defs.d.ts +++ b/src/db/orm-defs.d.ts @@ -104,11 +104,6 @@ export type Models = { historical_room_index: number } - role_default: { - guild_id: string - role_id: string - } - room_upgrade_pending: { new_room_id: string old_room_id: string diff --git a/src/m2d/actions/sticker.js b/src/m2d/actions/sticker.js index 8eeb5d2..341d8b0 100644 --- a/src/m2d/actions/sticker.js +++ b/src/m2d/actions/sticker.js @@ -9,7 +9,7 @@ const sharp = require("sharp") const api = sync.require("../../matrix/api") /** @type {import("../../matrix/mreq")} */ const mreq = sync.require("../../matrix/mreq") -const {streamType} = require("@cloudrac3r/stream-type") +const streamMimeType = require("stream-mime-type") const WIDTH = 160 const HEIGHT = 160 @@ -26,13 +26,13 @@ async function getAndResizeSticker(mxc) { } const streamIn = Readable.fromWeb(res.body) - const {streamThrough, type} = await streamType(streamIn) - const animated = ["image/gif", "image/webp"].includes(type) + const { stream, mime } = await streamMimeType.getMimeType(streamIn) + const animated = ["image/gif", "image/webp"].includes(mime) const transformer = sharp({animated: animated}) .resize(WIDTH, HEIGHT, {fit: "inside", background: {r: 0, g: 0, b: 0, alpha: 0}}) .webp() - streamThrough.pipe(transformer) + stream.pipe(transformer) return Readable.toWeb(transformer) } diff --git a/src/m2d/converters/emoji-sheet.js b/src/m2d/converters/emoji-sheet.js index dd66a17..16d5065 100644 --- a/src/m2d/converters/emoji-sheet.js +++ b/src/m2d/converters/emoji-sheet.js @@ -6,7 +6,7 @@ const {pipeline} = require("stream").promises const sharp = require("sharp") const {GIFrame} = require("@cloudrac3r/giframe") const {PNG} = require("@cloudrac3r/pngjs") -const {streamType} = require("@cloudrac3r/stream-type") +const streamMimeType = require("stream-mime-type") const SIZE = 48 const RESULT_WIDTH = 400 @@ -54,11 +54,11 @@ async function compositeMatrixEmojis(mxcs, mxcDownloader) { * @returns {Promise} Uncompressed PNG image */ async function convertImageStream(streamIn, stopStream) { - const {streamThrough, type} = await streamType(streamIn) - assert(["image/png", "image/jpeg", "image/webp", "image/gif", "image/apng"].includes(type), `Mime type ${type} is impossible for emojis`) + const {stream, mime} = await streamMimeType.getMimeType(streamIn) + assert(["image/png", "image/jpeg", "image/webp", "image/gif", "image/apng"].includes(mime), `Mime type ${mime} is impossible for emojis`) try { - if (type === "image/png" || type === "image/jpeg" || type === "image/webp") { + if (mime === "image/png" || mime === "image/jpeg" || mime === "image/webp") { /** @type {{info: sharp.OutputInfo, buffer: Buffer}} */ const result = await new Promise((resolve, reject) => { const transformer = sharp() @@ -70,15 +70,15 @@ async function convertImageStream(streamIn, stopStream) { resolve({info, buffer}) }) pipeline( - streamThrough, + stream, transformer ) }) return result.buffer - } else if (type === "image/gif") { + } else if (mime === "image/gif") { const giframe = new GIFrame(0) - streamThrough.on("data", chunk => { + stream.on("data", chunk => { giframe.feed(chunk) }) const frame = await giframe.getFrame() @@ -91,10 +91,10 @@ async function convertImageStream(streamIn, stopStream) { .toBuffer({resolveWithObject: true}) return buffer.data - } else if (type === "image/apng") { + } else if (mime === "image/apng") { const png = new PNG({maxFrames: 1}) // @ts-ignore - streamThrough.pipe(png) + stream.pipe(png) /** @type {Buffer} */ // @ts-ignore const frame = await new Promise(resolve => png.on("parsed", resolve)) stopStream() diff --git a/src/m2d/converters/event-to-message.test.js b/src/m2d/converters/event-to-message.test.js index aa426cd..629f2b8 100644 --- a/src/m2d/converters/event-to-message.test.js +++ b/src/m2d/converters/event-to-message.test.js @@ -4747,17 +4747,17 @@ test("event2message: stickers work", async t => { messagesToEdit: [], messagesToSend: [{ username: "cadence [they]", - content: "[get_real2](https://bridge.example.org/download/sticker/cadence.moe/NyMXQFAAdniImbHzsygScbmN/_.webp)", + content: "", avatar_url: "https://bridge.example.org/download/matrix/cadence.moe/azCAhThKTojXSZJRoWwZmhvU", - allowed_mentions: { - parse: ["users", "roles"] - } + attachments: [{id: "0", filename: "get_real2.gif"}], + pendingFiles: [{name: "get_real2.gif", mxc: "mxc://cadence.moe/NyMXQFAAdniImbHzsygScbmN"}] }] } ) }) test("event2message: stickers fetch mimetype from server when mimetype not provided", async t => { + let called = 0 t.deepEqual( await eventToMessage({ type: "m.sticker", @@ -4768,6 +4768,20 @@ test("event2message: stickers fetch mimetype from server when mimetype not provi }, event_id: "$mL-eEVWCwOvFtoOiivDP7gepvf-fTYH6_ioK82bWDI0", room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" + }, {}, {}, { + api: { + async getMedia(mxc, options) { + called++ + t.equal(mxc, "mxc://cadence.moe/ybOWQCaXysnyUGuUCaQlTGJf") + t.equal(options.method, "HEAD") + return { + status: 200, + headers: new Map([ + ["content-type", "image/gif"] + ]) + } + } + } }), { ensureJoined: [], @@ -4775,14 +4789,48 @@ test("event2message: stickers fetch mimetype from server when mimetype not provi messagesToEdit: [], messagesToSend: [{ username: "cadence [they]", - content: "[YESYESYES](https://bridge.example.org/download/sticker/cadence.moe/ybOWQCaXysnyUGuUCaQlTGJf/_.webp)", + content: "", avatar_url: undefined, - allowed_mentions: { - parse: ["users", "roles"] - } + attachments: [{id: "0", filename: "YESYESYES.gif"}], + pendingFiles: [{name: "YESYESYES.gif", mxc: "mxc://cadence.moe/ybOWQCaXysnyUGuUCaQlTGJf"}] }] } ) + t.equal(called, 1, "sticker headers should be fetched") +}) + +test("event2message: stickers with unknown mimetype are not allowed", async t => { + let called = 0 + try { + await eventToMessage({ + type: "m.sticker", + sender: "@cadence:cadence.moe", + content: { + body: "something", + url: "mxc://cadence.moe/ybOWQCaXysnyUGuUCaQlTGJe" + }, + event_id: "$mL-eEVWCwOvFtoOiivDP7gepvf-fTYH6_ioK82bWDI0", + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" + }, {}, {}, { + api: { + async getMedia(mxc, options) { + called++ + t.equal(mxc, "mxc://cadence.moe/ybOWQCaXysnyUGuUCaQlTGJe") + t.equal(options.method, "HEAD") + return { + status: 404, + headers: new Map([ + ["content-type", "application/json"] + ]) + } + } + } + }) + /* c8 ignore next */ + t.fail("should throw an error") + } catch (e) { + t.match(e.toString(), "mimetype") + } }) test("event2message: static emojis work", async t => { diff --git a/src/matrix/file.js b/src/matrix/file.js index c469aea..7bc1fec 100644 --- a/src/matrix/file.js +++ b/src/matrix/file.js @@ -85,7 +85,6 @@ async function _actuallyUploadDiscordFileToMxc(url) { writeRegistration(reg) return root } - e.uploadURL = url throw e } } diff --git a/src/matrix/matrix-command-handler.js b/src/matrix/matrix-command-handler.js index da78436..6758f78 100644 --- a/src/matrix/matrix-command-handler.js +++ b/src/matrix/matrix-command-handler.js @@ -1,7 +1,6 @@ // @ts-check const assert = require("assert").strict -const DiscordTypes = require("discord-api-types/v10") const Ty = require("../types") const {pipeline} = require("stream").promises const sharp = require("sharp") @@ -362,46 +361,6 @@ const commands = [{ } } ) -}, { - aliases: ["invite"], - execute: replyctx( - async (event, realBody, words, ctx) => { - // Guard - /** @type {string} */ // @ts-ignore - const channelID = select("channel_room", "channel_id", {room_id: event.room_id}).pluck().get() - const guildID = discord.channels.get(channelID)?.["guild_id"] - if (!guildID) { - return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "This room isn't bridged to the other side." - }) - } - - const guild = discord.guilds.get(guildID) - assert(guild) - const permissions = dUtils.getPermissions(guild.id, [], guild.roles) - if (!dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.CreateInstantInvite)) { - return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "This command creates an invite link to the Discord side. But you aren't allowed to do this, because if you were a Discord user, you wouldn't have the Create Invite permission." - }) - } - - const invite = await discord.snow.channel.createChannelInvite(channelID) - const validHours = Math.ceil(invite.max_age / (60 * 60)) - const validUses = - ( invite.max_uses === 0 ? "unlimited uses" - : invite.max_uses === 1 ? "single-use" - : `${invite.max_uses} uses`) - return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: `https://discord.gg/${invite.code}\nValid for next ${validHours} hours, ${validUses}.` - }) - } - ) }] diff --git a/src/matrix/read-registration.test.js b/src/matrix/read-registration.test.js index a8dcc25..5fb3b55 100644 --- a/src/matrix/read-registration.test.js +++ b/src/matrix/read-registration.test.js @@ -1,6 +1,6 @@ // @ts-check -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {test} = require("supertape") const {reg, checkRegistration, getTemplateRegistration} = require("./read-registration") diff --git a/src/matrix/room-upgrade.js b/src/matrix/room-upgrade.js index e7de906..5a2606e 100644 --- a/src/matrix/room-upgrade.js +++ b/src/matrix/room-upgrade.js @@ -54,17 +54,17 @@ async function onBotMembership(event, api, createRoom) { assert.equal(event.type, "m.room.member") assert.equal(event.state_key, utils.bot) + // Check if an upgrade is pending for this room + const newRoomID = event.room_id + const oldRoomID = select("room_upgrade_pending", "old_room_id", {new_room_id: newRoomID}).pluck().get() + if (!oldRoomID) return false + const channelRow = from("channel_room").join("guild_space", "guild_id").where({room_id: oldRoomID}).select("space_id", "guild_id", "channel_id").get() + assert(channelRow) // this could only fail if the channel was unbridged or something between upgrade and joining + + // Check if is join/invite + if (event.content.membership !== "invite" && event.content.membership !== "join") return false + return await roomUpgradeSema.request(async () => { - // Check if an upgrade is pending for this room - const newRoomID = event.room_id - const oldRoomID = select("room_upgrade_pending", "old_room_id", {new_room_id: newRoomID}).pluck().get() - if (!oldRoomID) return false - const channelRow = from("channel_room").join("guild_space", "guild_id").where({room_id: oldRoomID}).select("space_id", "guild_id", "channel_id").get() - assert(channelRow) // this could only fail if the channel was unbridged or something between upgrade and joining - - // Check if is join/invite - if (event.content.membership !== "invite" && event.content.membership !== "join") return false - // If invited, join if (event.content.membership === "invite") { await api.joinRoom(newRoomID) diff --git a/src/matrix/utils.js b/src/matrix/utils.js index 2351a95..6383c4d 100644 --- a/src/matrix/utils.js +++ b/src/matrix/utils.js @@ -225,6 +225,19 @@ async function getViaServersQuery(roomID, api) { return qs } +function generatePermittedMediaHash(mxc) { + assert(hasher, "xxhash is not ready yet") + const mediaParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/) + if (!mediaParts) return undefined + + const serverAndMediaID = `${mediaParts[1]}/${mediaParts[2]}` + const unsignedHash = hasher.h64(serverAndMediaID) + const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range + db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash) + + return serverAndMediaID +} + /** * Since the introduction of authenticated media, this can no longer just be the /_matrix/media/r0/download URL * because Discord and Discord users cannot use those URLs. Media now has to be proxied through the bridge. diff --git a/src/web/pug-sync.js b/src/web/pug-sync.js index e61c53b..f87550d 100644 --- a/src/web/pug-sync.js +++ b/src/web/pug-sync.js @@ -77,7 +77,6 @@ function renderPath(event, path, locals) { compile() fs.watch(path, {persistent: false}, compile) fs.watch(join(__dirname, "pug", "includes"), {persistent: false}, compile) - fs.watch(join(__dirname, "pug", "fragments"), {persistent: false}, compile) } const cb = pugCache.get(path) diff --git a/src/web/pug/fragments/default-roles-list.pug b/src/web/pug/fragments/default-roles-list.pug deleted file mode 100644 index 3b36549..0000000 --- a/src/web/pug/fragments/default-roles-list.pug +++ /dev/null @@ -1,5 +0,0 @@ -//- locals: guild, guild_id - -include ../includes/default-roles-list.pug -+default-roles-list(guild, guild_id) -+add-roles-menu(guild, guild_id) diff --git a/src/web/pug/guild.pug b/src/web/pug/guild.pug index 74b476a..a9e770b 100644 --- a/src/web/pug/guild.pug +++ b/src/web/pug/guild.pug @@ -1,5 +1,4 @@ extends includes/template.pug -include includes/default-roles-list.pug mixin badge-readonly .s-badge.s-badge__xs.s-badge__icon.s-badge__muted @@ -77,7 +76,7 @@ block body if space_id h2.mt48.fs-headline1 Server settings - h3.mt32.fs-category How Matrix users join + h3.mt32.fs-category Privacy level span#privacy-level-loading .s-card form(hx-post=rel("/api/privacy-level") hx-trigger="change" hx-indicator="#privacy-level-loading" hx-disabled-elt="input") @@ -106,24 +105,6 @@ block body p.s-description.m0 Shareable invite links, like Discord p.s-description.m0 Publicly listed in directory, like Discord server discovery - h3.mt32.fs-category Default roles - .s-card - form(method="post" action=rel("/api/default-roles") hx-post=rel("/api/default-roles") hx-indicator="#add-role-loading" hx-target="#default-roles-list" hx-select="#default-roles-list" hx-swap="outerHTML")#default-roles - input(type="hidden" name="guild_id" value=guild_id) - .d-flex.fw-wrap.g4 - .s-tag.s-tag__md.fs-body1.s-tag__required @everyone - - +default-roles-list(guild, guild_id) - - button(type="button" popovertarget="role-add").s-btn__dropdown.s-tag.s-tag__md.fs-body1.p0 - .s-tag--dismiss.m1 - != icons.Icons.IconPlusSm - - #role-add.s-popover(popover style="display: revert").ws2.px0.py4.bs-lg.overflow-visible - .s-popover--arrow.s-popover--arrow__tc - +add-roles-menu(guild, guild_id) - p.fc-medium.mb0.mt8 Matrix users will start with these roles. If your main channels are gated by a role, use this to let Matrix users skip the gate. - h3.mt32.fs-category Features .s-card.d-grid.px0.g16 form.d-flex.ai-center.g16 diff --git a/src/web/pug/includes/default-roles-list.pug b/src/web/pug/includes/default-roles-list.pug deleted file mode 100644 index 8c0a4e0..0000000 --- a/src/web/pug/includes/default-roles-list.pug +++ /dev/null @@ -1,19 +0,0 @@ -mixin default-roles-list(guild, guild_id) - #default-roles-list(style="display: contents") - each roleID in select("role_default", "role_id", {guild_id}).pluck().all() - - let r = guild.roles.find(r => r.id === roleID) - if r - .s-tag.s-tag__md.fs-body1= r.name - span(id=`role-loading-${roleID}`) - button(name="remove_role" value=roleID hx-post="api/default-roles" hx-trigger="click consume" hx-indicator=`#role-loading-${roleID}`).s-tag--dismiss - != icons.Icons.IconClearSm - -mixin add-roles-menu(guild, guild_id) - ul.s-menu(role="menu" hx-swap-oob="true").overflow-y-auto.overflow-x-hidden#add-roles-menu - li.s-menu--title.d-flex(role="separator") Select role - span#add-role-loading - each r in guild.roles.sort((a, b) => b.position - a.position) - if r.id !== guild_id && !r.managed - - let selected = !!select("role_default", "role_id", {guild_id, role_id: r.id}).get() - li(role="menuitem") - button(name="toggle_role" value=r.id class={"is-selected": selected}).s-block-link.s-block-link__left= r.name diff --git a/src/web/pug/includes/template.pug b/src/web/pug/includes/template.pug index 278a16a..9fe80aa 100644 --- a/src/web/pug/includes/template.pug +++ b/src/web/pug/includes/template.pug @@ -91,19 +91,6 @@ html(lang="en") .s-btn__dropdown:has(+ :popover-open) { background-color: var(--theme-topbar-item-background-hover, var(--black-200)) !important; } - .s-btn__dropdown.s-tag:has(+ :popover-open) .s-tag--dismiss { - background-color: var(--black-500) !important; - color: var(--black-150) !important; - } - .s-tag .is-loading { - margin-right: -4px; - } - .s-tag .is-loading + .s-tag--dismiss { - display: none !important; - } - a.s-block-link, .s-block-link { - --_bl-bs-color: var(--green-400); - } @media (prefers-color-scheme: dark) { body.theme-system .s-popover { --_po-bg: var(--black-100); @@ -154,15 +141,11 @@ html(lang="en") //- Guild list popover script. document.querySelectorAll("[popovertarget]").forEach(e => { - const target = document.getElementById(e.getAttribute("popovertarget")) - e.addEventListener("click", calculate) - target.addEventListener("toggle", calculate) - function calculate() { - const buttonRect = e.getBoundingClientRect() - const targetRect = target.getBoundingClientRect() - const t = `:popover-open { position: absolute; top: ${Math.floor(buttonRect.bottom + window.scrollY)}px; left: ${Math.floor(Math.max(targetRect.width / 2, buttonRect.left + buttonRect.width / 2))}px; width: ${Math.floor(buttonRect.width)}px; transform: translateX(-50%); box-sizing: content-box; margin: 0 }` + e.addEventListener("click", () => { + const rect = e.getBoundingClientRect() + const t = `:popover-open { position: absolute; top: ${Math.floor(rect.bottom)}px; left: ${Math.floor(rect.left + rect.width / 2)}px; width: ${Math.floor(rect.width)}px; transform: translateX(-50%); box-sizing: content-box; margin: 0 }` document.styleSheets[0].insertRule(t, document.styleSheets[0].cssRules.length) - } + }) }) //- Prevent default script. diff --git a/src/web/routes/download-discord.test.js b/src/web/routes/download-discord.test.js index 0d4b884..e4f4ab4 100644 --- a/src/web/routes/download-discord.test.js +++ b/src/web/routes/download-discord.test.js @@ -1,7 +1,7 @@ // @ts-check const assert = require("assert").strict -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {test} = require("supertape") const {router} = require("../../../test/web") const {_cache} = require("./download-discord") diff --git a/src/web/routes/download-matrix.test.js b/src/web/routes/download-matrix.test.js index 610a62d..ccbcfdd 100644 --- a/src/web/routes/download-matrix.test.js +++ b/src/web/routes/download-matrix.test.js @@ -2,7 +2,7 @@ const fs = require("fs") const {convertImageStream} = require("../../m2d/converters/emoji-sheet") -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {test} = require("supertape") const {router} = require("../../../test/web") const streamWeb = require("stream/web") diff --git a/src/web/routes/guild-settings.js b/src/web/routes/guild-settings.js index ae52825..63dd3ec 100644 --- a/src/web/routes/guild-settings.js +++ b/src/web/routes/guild-settings.js @@ -4,12 +4,10 @@ const assert = require("assert/strict") const {z} = require("zod") const {defineEventHandler, createError, readValidatedBody, getRequestHeader, setResponseHeader, sendRedirect, H3Event} = require("h3") -const {as, db, sync, select, discord} = require("../../passthrough") +const {as, db, sync, select} = require("../../passthrough") /** @type {import("../auth")} */ const auth = sync.require("../auth") -/** @type {import("../pug-sync")} */ -const pugSync = sync.require("../pug-sync") /** @type {import("../../d2m/actions/set-presence")} */ const setPresence = sync.require("../../d2m/actions/set-presence") @@ -22,14 +20,6 @@ function getCreateSpace(event) { return event.context.createSpace || sync.require("../../d2m/actions/create-space") } -const schema = { - defaultRoles: z.object({ - guild_id: z.string(), - toggle_role: z.string().optional(), - remove_role: z.string().optional() - }) -} - /** * @typedef Options * @prop {(value: string?) => number} transform @@ -104,36 +94,3 @@ as.router.post("/api/privacy-level", defineToggle("privacy_level", { await createSpace.syncSpaceFully(guildID) // this is inefficient but OK to call infrequently on user request } })) - -as.router.post("/api/default-roles", defineEventHandler(async event => { - const parsedBody = await readValidatedBody(event, schema.defaultRoles.parse) - - const managed = await auth.getManagedGuilds(event) - const guildID = parsedBody.guild_id - if (!managed.has(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"}) - - const roleID = parsedBody.toggle_role || parsedBody.remove_role - assert(roleID) - assert.notEqual(guildID, roleID) // the @everyone role is always default - - const guild = discord.guilds.get(guildID) - assert(guild) - - let shouldRemove = !!parsedBody.remove_role - if (!shouldRemove) { - shouldRemove = !!select("role_default", "role_id", {guild_id: guildID, role_id: roleID}).get() - } - - if (shouldRemove) { - db.prepare("DELETE FROM role_default WHERE guild_id = ? AND role_id = ?").run(guildID, roleID) - } else { - assert(guild.roles.find(r => r.id === roleID)) - db.prepare("INSERT OR IGNORE INTO role_default (guild_id, role_id) VALUES (?, ?)").run(guildID, roleID) - } - - if (getRequestHeader(event, "HX-Request")) { - return pugSync.render(event, "fragments/default-roles-list.pug", {guild, guild_id: guildID}) - } else { - return sendRedirect(event, `/guild?guild_id=${guildID}`, 302) - } -})) diff --git a/src/web/routes/guild-settings.test.js b/src/web/routes/guild-settings.test.js index 00acb89..fccc266 100644 --- a/src/web/routes/guild-settings.test.js +++ b/src/web/routes/guild-settings.test.js @@ -1,6 +1,6 @@ // @ts-check -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {router, test} = require("../../../test/web") const {select} = require("../../passthrough") const {MatrixServerError} = require("../../matrix/mreq") diff --git a/src/web/routes/guild.test.js b/src/web/routes/guild.test.js index 06b604b..aa17548 100644 --- a/src/web/routes/guild.test.js +++ b/src/web/routes/guild.test.js @@ -1,7 +1,7 @@ // @ts-check const DiscordTypes = require("discord-api-types/v10") -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {router, test} = require("../../../test/web") const {MatrixServerError} = require("../../matrix/mreq") const {_getPosition} = require("./guild") diff --git a/src/web/routes/link.test.js b/src/web/routes/link.test.js index 70299d5..e8473f8 100644 --- a/src/web/routes/link.test.js +++ b/src/web/routes/link.test.js @@ -1,6 +1,6 @@ // @ts-check -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {router, test} = require("../../../test/web") const {MatrixServerError} = require("../../matrix/mreq") const {select, db} = require("../../passthrough") diff --git a/src/web/routes/log-in-with-matrix.test.js b/src/web/routes/log-in-with-matrix.test.js index 2f9afcc..830556e 100644 --- a/src/web/routes/log-in-with-matrix.test.js +++ b/src/web/routes/log-in-with-matrix.test.js @@ -1,6 +1,6 @@ // @ts-check -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {router, test} = require("../../../test/web") const {MatrixServerError} = require("../../matrix/mreq") diff --git a/src/web/routes/oauth.test.js b/src/web/routes/oauth.test.js index 1a06e39..2f3a791 100644 --- a/src/web/routes/oauth.test.js +++ b/src/web/routes/oauth.test.js @@ -1,7 +1,7 @@ // @ts-check const DiscordTypes = require("discord-api-types/v10") -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const assert = require("assert/strict") const {router, test} = require("../../../test/web") diff --git a/src/web/routes/password.test.js b/src/web/routes/password.test.js index fca4e70..aa60bd3 100644 --- a/src/web/routes/password.test.js +++ b/src/web/routes/password.test.js @@ -1,6 +1,6 @@ // @ts-check -const {tryToCatch} = require("try-to-catch") +const tryToCatch = require("try-to-catch") const {test} = require("supertape") const {router} = require("../../../test/web") diff --git a/test/data.js b/test/data.js index 46e8b0f..6a53cb0 100644 --- a/test/data.js +++ b/test/data.js @@ -5067,141 +5067,6 @@ module.exports = { pinned: false, mention_everyone: false, tts: false - }, - four_images: { - type: 0, - content: "", - mentions: [], - mention_roles: [], - attachments: [], - embeds: [], - timestamp: "2026-03-12T18:00:50.737000+00:00", - edited_timestamp: null, - flags: 16384, - components: [], - id: "1481713598278533241", - channel_id: "687028734322147344", - author: { - id: "112760500130975744", - username: "minimus", - avatar: "a_a354b9eaff512485b49c82b13691b941", - discriminator: "0", - public_flags: 512, - flags: 512, - banner: null, - accent_color: null, - global_name: "minimus", - avatar_decoration_data: null, - collectibles: null, - display_name_styles: { font_id: 11, effect_id: 5, colors: [ 6106655 ] }, - banner_color: null, - clan: null, - primary_guild: null - }, - pinned: false, - mention_everyone: false, - tts: false, - message_reference: { - type: 1, - channel_id: "637339857118822430", - message_id: "1481696763483258891", - guild_id: "408573045540651009" - }, - message_snapshots: [ - { - message: { - type: 0, - content: "https://fixupx.com/i/status/2032003668787020046", - mentions: [], - mention_roles: [], - attachments: [], - embeds: [ - { - type: "rich", - url: "https://fixupx.com/i/status/2032003668787020046", - description: "4chan owner Hiroyuki, Evangelion director Hideaki Anno and GACKT to participate in “humanity’s last non\\-AI made social network”\n" + - "︀︀\n" + - "︀︀[automaton-media.com/en/news/4chan-owner-hiroyuki-evangelion-director-hideaki-anno-and-gackt-to-participate-in-humanitys-last-non-ai-made-social-network/](https://automaton-media.com/en/news/4chan-owner-hiroyuki-evangelion-director-hideaki-anno-and-gackt-to-participate-in-humanitys-last-non-ai-made-social-network/)\n" + - "\n" + - "**[💬](https://x.com/intent/tweet?in_reply_to=2032003668787020046) 36 [🔁](https://x.com/intent/retweet?tweet_id=2032003668787020046) 212 [❤](https://x.com/intent/like?tweet_id=2032003668787020046) 3\\.0K 👁 131\\.7K **", - color: 6513919, - timestamp: "2026-03-12T08:00:02+00:00", - author: { - name: "AUTOMATON WEST (@AUTOMATON_ENG)", - url: "https://x.com/AUTOMATON_ENG/status/2032003668787020046", - icon_url: "https://pbs.twimg.com/profile_images/1353559126693961729/pz-WVnDc_200x200.jpg", - proxy_icon_url: "https://images-ext-1.discordapp.net/external/1OzGhjvZTRstTxM38_7pqHXlmdbMddqh1F8R0-WrKqw/https/pbs.twimg.com/profile_images/1353559126693961729/pz-WVnDc_200x200.jpg" - }, - image: { - url: "https://pbs.twimg.com/media/HDMUyf6bQAM3yts.jpg?name=orig", - proxy_url: "https://images-ext-1.discordapp.net/external/NkNgp2SyY1OCH9IdS8hqsUqbnbrp3A9oLNwYusVVCVQ/%3Fname%3Dorig/https/pbs.twimg.com/media/HDMUyf6bQAM3yts.jpg", - width: 872, - height: 886, - content_type: "image/jpeg", - placeholder: "6vcFFwL6R3lye2V3l1mIl5l3WPN5FZ8H", - placeholder_version: 1, - flags: 0 - }, - footer: { - text: "FixupX", - icon_url: "https://assets.fxembed.com/logos/fixupx64.png", - proxy_icon_url: "https://images-ext-1.discordapp.net/external/LwQ70Uiqfu0OCN4ZbA4f482TGCgQa-xGsnUFYfhIgYA/https/assets.fxembed.com/logos/fixupx64.png" - }, - content_scan_version: 4 - }, - { - type: "rich", - url: "https://fixupx.com/i/status/2032003668787020046", - image: { - url: "https://pbs.twimg.com/media/HDMUgxybQAE4FtJ.jpg?name=orig", - proxy_url: "https://images-ext-1.discordapp.net/external/Rquh1ec-tG9hMqdHqIVSphO7zf5B5Fg_7yTWhCjlsek/%3Fname%3Dorig/https/pbs.twimg.com/media/HDMUgxybQAE4FtJ.jpg", - width: 1114, - height: 991, - content_type: "image/jpeg", - placeholder: "JQgKDoL3epZ8ZIdnlmmHZ4d4CIGmUEc=", - placeholder_version: 1, - flags: 0 - }, - content_scan_version: 4 - }, - { - type: "rich", - url: "https://fixupx.com/i/status/2032003668787020046", - image: { - url: "https://pbs.twimg.com/media/HDMUrPobgAAeb90.jpg?name=orig", - proxy_url: "https://images-ext-1.discordapp.net/external/XrkhHNH3CvlZYvjkdykVnf-_xdz6HWX8uwesoAwwSfY/%3Fname%3Dorig/https/pbs.twimg.com/media/HDMUrPobgAAeb90.jpg", - width: 944, - height: 954, - content_type: "image/jpeg", - placeholder: "m/cJDwCbV0mfaoZzlihqeXdqCVN9A6oD", - placeholder_version: 1, - flags: 0 - }, - content_scan_version: 4 - }, - { - type: "rich", - url: "https://fixupx.com/i/status/2032003668787020046", - image: { - url: "https://pbs.twimg.com/media/HDMUuy5bgAAInj5.jpg?name=orig", - proxy_url: "https://images-ext-1.discordapp.net/external/lO-5hBMU9bGH13Ax9xum2T2Mg0ATdv0b6BEx_VeVi80/%3Fname%3Dorig/https/pbs.twimg.com/media/HDMUuy5bgAAInj5.jpg", - width: 1200, - height: 630, - content_type: "image/jpeg", - placeholder: "tfcJDIK3mIl1eIiPdY23dX9b9w==", - placeholder_version: 1, - flags: 0 - }, - content_scan_version: 4 - } - ], - timestamp: "2026-03-12T16:53:57.009000+00:00", - edited_timestamp: null, - flags: 0, - components: [] - } - } - ] } }, message_with_components: { diff --git a/test/test.js b/test/test.js index da6bcba..e05b687 100644 --- a/test/test.js +++ b/test/test.js @@ -6,29 +6,31 @@ const sqlite = require("better-sqlite3") const {Writable} = require("stream") const migrate = require("../src/db/migrate") const HeatSync = require("heatsync") -const {test} = require("supertape") +const {test, extend} = require("supertape") const data = require("./data") const {green} = require("ansi-colors") -const mixin = require("@cloudrac3r/mixin-deep") const passthrough = require("../src/passthrough") const db = new sqlite(":memory:") -const registration = require("../src/matrix/read-registration") -registration.reg = mixin(registration.getTemplateRegistration("cadence.moe"), { - id: "baby", - url: "http://localhost:6693", - as_token: "don't actually take authenticated actions on the server", - hs_token: "don't actually take authenticated actions on the server", - ooye: { - server_origin: "https://matrix.cadence.moe", - bridge_origin: "https://bridge.example.org", - discord_token: "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby", - discord_client_secret: "baby", - web_password: "password123", - time_zone: "Pacific/Auckland", - } -}) +const {reg} = require("../src/matrix/read-registration") +reg.ooye.discord_token = "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby" +reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded +reg.ooye.server_name = "cadence.moe" +reg.ooye.namespace_prefix = "_ooye_" +reg.sender_localpart = "_ooye_bot" +reg.id = "baby" +reg.as_token = "don't actually take authenticated actions on the server" +reg.hs_token = "don't actually take authenticated actions on the server" +reg.namespaces = { + users: [{regex: "@_ooye_.*:cadence.moe", exclusive: true}], + aliases: [{regex: "#_ooye_.*:cadence.moe", exclusive: true}] +} +reg.ooye.bridge_origin = "https://bridge.example.org" +reg.ooye.time_zone = "Pacific/Auckland" +reg.ooye.max_file_size = 5000000 +reg.ooye.web_password = "password123" +reg.ooye.include_user_id_in_mxid = false const sync = new HeatSync({watchFS: false})