Emergency sync #11

Merged
Guzio merged 13 commits from cadence/out-of-your-element:main into main 2026-03-14 07:10:15 +00:00
6 changed files with 62 additions and 163 deletions
Showing only changes of commit f90cdfdbb5 - Show all commits

View file

@ -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: 134
Total transitive production dependencies: 121
### <font size="+2">🦕</font>
@ -108,6 +108,7 @@ Total transitive production dependencies: 134
* (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.

177
package-lock.json generated
View file

@ -10,13 +10,14 @@
"license": "AGPL-3.0-or-later",
"dependencies": {
"@chriscdn/promise-semaphore": "^3.0.1",
"@cloudrac3r/discord-markdown": "^2.6.10",
"@cloudrac3r/discord-markdown": "^2.7.0",
"@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",
@ -37,7 +38,6 @@
"prettier-bytes": "^1.0.4",
"sharp": "^0.34.5",
"snowtransfer": "^0.17.5",
"stream-mime-type": "^1.0.2",
"try-to-catch": "^4.0.5",
"uqr": "^0.1.2",
"xxhash-wasm": "^1.0.2",
@ -68,6 +68,22 @@
"ts-node": "^10.9.2"
}
},
"../nodejs-stream-type": {
"name": "@cloudrac3r/stream-type",
"version": "1.0.0",
"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",
@ -156,9 +172,9 @@
}
},
"node_modules/@cloudrac3r/discord-markdown": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.10.tgz",
"integrity": "sha512-E+F9UYDUHP2kHDCciX63SBzgsUnHpu2Pp/h98x9Zo+vKuzXjCQ5PcFNdUlH6M18bvHDZPoIsKVmjnON8UYaAPQ==",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.7.0.tgz",
"integrity": "sha512-1iR9tKI2WJe8UNB+4VSh7D8m6RP7ugByuf8RNWyJwyhIrSlqQ8ljY1BKXodSvDg7seZkf7B7V2t5FfK7UpTw/A==",
"license": "MIT",
"dependencies": {
"simple-markdown": "^0.7.3"
@ -246,6 +262,10 @@
"pug-error": "^2.1.0"
}
},
"node_modules/@cloudrac3r/stream-type": {
"resolved": "../nodejs-stream-type",
"link": true
},
"node_modules/@cloudrac3r/tap-dot": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@cloudrac3r/tap-dot/-/tap-dot-2.0.3.tgz",
@ -1027,11 +1047,6 @@
"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",
@ -1616,22 +1631,6 @@
"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",
@ -1654,10 +1653,11 @@
}
},
"node_modules/flatted": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
"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"
},
"node_modules/foreground-child": {
"version": "3.3.1",
@ -2183,18 +2183,6 @@
"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",
@ -2362,34 +2350,6 @@
"dev": true,
"license": "MIT"
},
"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==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -2625,30 +2585,6 @@
"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",
@ -2700,22 +2636,6 @@
"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",
@ -2845,27 +2765,6 @@
"node": "20 || >=22"
}
},
"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": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/timer-node/-/timer-node-5.0.9.tgz",
@ -2878,22 +2777,6 @@
"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",

View file

@ -19,13 +19,14 @@
},
"dependencies": {
"@chriscdn/promise-semaphore": "^3.0.1",
"@cloudrac3r/discord-markdown": "^2.6.10",
"@cloudrac3r/discord-markdown": "^2.7.0",
"@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",
@ -46,7 +47,6 @@
"prettier-bytes": "^1.0.4",
"sharp": "^0.34.5",
"snowtransfer": "^0.17.5",
"stream-mime-type": "^1.0.2",
"try-to-catch": "^4.0.5",
"uqr": "^0.1.2",
"xxhash-wasm": "^1.0.2",

View file

@ -977,6 +977,21 @@ test("message2event: written @mentions do not match in inline code", async t =>
}])
})
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: `<pre><code class="language-java">public @Nullable EntityType&lt;?&gt;</code></pre>`
}])
})
test("message2event: entire message may match elaborate display name", async t => {
let called = 0
const events = await messageToEvent({

View file

@ -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 streamMimeType = require("stream-mime-type")
const {streamType} = require("@cloudrac3r/stream-type")
const WIDTH = 160
const HEIGHT = 160
@ -26,13 +26,13 @@ async function getAndResizeSticker(mxc) {
}
const streamIn = Readable.fromWeb(res.body)
const { stream, mime } = await streamMimeType.getMimeType(streamIn)
const animated = ["image/gif", "image/webp"].includes(mime)
const {streamThrough, type} = await streamType(streamIn)
const animated = ["image/gif", "image/webp"].includes(type)
const transformer = sharp({animated: animated})
.resize(WIDTH, HEIGHT, {fit: "inside", background: {r: 0, g: 0, b: 0, alpha: 0}})
.webp()
stream.pipe(transformer)
streamThrough.pipe(transformer)
return Readable.toWeb(transformer)
}

View file

@ -6,7 +6,7 @@ const {pipeline} = require("stream").promises
const sharp = require("sharp")
const {GIFrame} = require("@cloudrac3r/giframe")
const {PNG} = require("@cloudrac3r/pngjs")
const streamMimeType = require("stream-mime-type")
const {streamType} = require("@cloudrac3r/stream-type")
const SIZE = 48
const RESULT_WIDTH = 400
@ -54,11 +54,11 @@ async function compositeMatrixEmojis(mxcs, mxcDownloader) {
* @returns {Promise<Buffer | undefined>} Uncompressed PNG image
*/
async function convertImageStream(streamIn, stopStream) {
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`)
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`)
try {
if (mime === "image/png" || mime === "image/jpeg" || mime === "image/webp") {
if (type === "image/png" || type === "image/jpeg" || type === "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(
stream,
streamThrough,
transformer
)
})
return result.buffer
} else if (mime === "image/gif") {
} else if (type === "image/gif") {
const giframe = new GIFrame(0)
stream.on("data", chunk => {
streamThrough.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 (mime === "image/apng") {
} else if (type === "image/apng") {
const png = new PNG({maxFrames: 1})
// @ts-ignore
stream.pipe(png)
streamThrough.pipe(png)
/** @type {Buffer} */ // @ts-ignore
const frame = await new Promise(resolve => png.on("parsed", resolve))
stopStream()