summary refs log tree commit diff
path: root/src/web
diff options
context:
space:
mode:
authorCadence Ember <cadence@disroot.org>2026-02-11 02:40:18 +1300
committerCadence Ember <cadence@disroot.org>2026-02-11 02:42:10 +1300
commitd1b0fa48cf86cfed76d28fbb9e00cb3ed36ecf10 (patch)
tree9d080636e664ff9c323232eea9b2d157919c701b /src/web
parentLink instead of upload emoji sprite sheets (diff)
downloadout-of-your-element-d1b0fa48cf86cfed76d28fbb9e00cb3ed36ecf10.tar.xz
Add tests for emoji sheet; style and nits
Diffstat (limited to 'src/web')
-rw-r--r--src/web/routes/download-matrix.js51
-rw-r--r--src/web/routes/download-matrix.test.js51
2 files changed, 78 insertions, 24 deletions
diff --git a/src/web/routes/download-matrix.js b/src/web/routes/download-matrix.js

index 0810f9a..bb6b850 100644 --- a/src/web/routes/download-matrix.js +++ b/src/web/routes/download-matrix.js
@@ -11,10 +11,18 @@ require("xxhash-wasm")().then(h => hasher = h) const {sync, as, select} = require("../../passthrough") +/** @type {import("../../m2d/actions/emoji-sheet")} */ +const emojiSheet = sync.require("../../m2d/actions/emoji-sheet") +/** @type {import("../../m2d/converters/emoji-sheet")} */ +const emojiSheetConverter = sync.require("../../m2d/converters/emoji-sheet") + const schema = { params: z.object({ server_name: z.string(), media_id: z.string() + }), + sheet: z.object({ + e: z.array(z.string()).or(z.string()) }) } @@ -27,8 +35,16 @@ function getAPI(event) { return event.context.api || sync.require("../../matrix/api") } -function verifyMediaHash(serverName, mediaId) { - const serverAndMediaID = `${serverName}/${mediaId}` +/** + * @param {H3Event} event + * @returns {typeof emojiSheet["getAndConvertEmoji"]} + */ +function getMxcDownloader(event) { + /* c8 ignore next */ + return event.context.mxcDownloader || emojiSheet.getAndConvertEmoji +} + +function verifyMediaHash(serverAndMediaID) { const unsignedHash = hasher.h64(serverAndMediaID) const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range @@ -44,7 +60,7 @@ function verifyMediaHash(serverName, mediaId) { as.router.get(`/download/matrix/:server_name/:media_id`, defineEventHandler(async event => { const params = await getValidatedRouterParams(event, schema.params.parse) - verifyMediaHash(params.server_name, params.media_id) + verifyMediaHash(`${params.server_name}/${params.media_id}`) const api = getAPI(event) const res = await api.getMedia(`mxc://${params.server_name}/${params.media_id}`) @@ -57,33 +73,20 @@ as.router.get(`/download/matrix/:server_name/:media_id`, defineEventHandler(asyn return res.body })) -const emojiSchema = z.object({ - 'e': z.array(z.string()).or(z.string()) -}) - -const emojiSheet = sync.require("../../m2d/actions/emoji-sheet") -const emojiSheetConverter = sync.require("../../m2d/converters/emoji-sheet") - -as.router.get(`/emoji/matrix`, defineEventHandler(async event => { - - const query = await getValidatedQuery(event, emojiSchema.parse) +as.router.get(`/download/sheet`, defineEventHandler(async event => { + const query = await getValidatedQuery(event, schema.sheet.parse) + /** remember that these have no mxc:// protocol in the string for space reasons */ let mxcs = query.e - if(!Array.isArray(mxcs)) { + if (!Array.isArray(mxcs)) { mxcs = [mxcs] } - for(let mxc of mxcs) { - const mediaParts = mxc.match(/^mxc:\/\/([^/]+)\/(\w+)$/) - if (!mediaParts) return undefined - verifyMediaHash(mediaParts[1], mediaParts[2]) + for (const serverAndMediaID of mxcs) { + verifyMediaHash(serverAndMediaID) } - const buffer = await emojiSheetConverter.compositeMatrixEmojis(mxcs, emojiSheet.getAndConvertEmoji) - - const contentType = 'image/png' - setResponseStatus(event, 200) - setResponseHeader(event, "Content-Type", contentType) - setResponseHeader(event, "Transfer-Encoding", "chunked") + const buffer = await emojiSheetConverter.compositeMatrixEmojis(mxcs.map(s => `mxc://${s}`), getMxcDownloader(event)) + setResponseHeader(event, "Content-Type", "image/png") return buffer })) diff --git a/src/web/routes/download-matrix.test.js b/src/web/routes/download-matrix.test.js
index 421d2da..49a6349 100644 --- a/src/web/routes/download-matrix.test.js +++ b/src/web/routes/download-matrix.test.js
@@ -1,5 +1,7 @@ // @ts-check +const fs = require("fs") +const {convertImageStream} = require("../../m2d/converters/emoji-sheet") const tryToCatch = require("try-to-catch") const {test} = require("supertape") const {router} = require("../../../test/web") @@ -33,3 +35,52 @@ test("web download matrix: works if a known attachment", async t => { t.equal(event.node.res.statusCode, 200) t.equal(event.node.res.getHeader("content-type"), "image/png") }) + +/** + * MOCK: Gets the emoji from the filesystem and converts to uncompressed PNG data. + * @param {string} mxc a single mxc:// URL + * @returns {Promise<Buffer | undefined>} uncompressed PNG data, or undefined if the downloaded emoji is not valid +*/ +async function mockGetAndConvertEmoji(mxc) { + const id = mxc.match(/\/([^./]*)$/)?.[1] + let s + if (fs.existsSync(`test/res/${id}.png`)) { + s = fs.createReadStream(`test/res/${id}.png`) + } else { + s = fs.createReadStream(`test/res/${id}.gif`) + } + return convertImageStream(s, () => { + s.pause() + s.emit("end") + }) +} + +test("web sheet: single emoji", async t => { + const event = {} + const sheet = await router.test("get", "/download/sheet?e=cadence.moe%2FRLMgJGfgTPjIQtvvWZsYjhjy", { + event, + mxcDownloader: mockGetAndConvertEmoji + }) + t.equal(event.node.res.statusCode, 200) + t.equal(sheet.subarray(0, 90).toString("base64"), "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAPoAAAD6AG1e1JrAAALoklEQVR4nM1ZaVBU2RU+LZSIGnAvFUtcRkSk6abpbkDH") +}) + +test("web sheet: multiple sources", async t => { + const event = {} + const sheet = await router.test("get", "/download/sheet?e=cadence.moe%2FWbYqNlACRuicynBfdnPYtmvc&e=cadence.moe%2FHYcztccFIPgevDvoaWNsEtGJ", { + event, + mxcDownloader: mockGetAndConvertEmoji + }) + t.equal(event.node.res.statusCode, 200) + t.equal(sheet.subarray(0, 90).toString("base64"), "iVBORw0KGgoAAAANSUhEUgAAAGAAAAAwCAYAAADuFn/PAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAT/klEQVR4nOVcC3CVRZbuS2KAIMpDQt5PQkIScm/uvYRX") +}) + +test("web sheet: big sheet", async t => { + const event = {} + const sheet = await router.test("get", "/download/sheet?e=cadence.moe%2FlHfmJpzgoNyNtYHdAmBHxXix&e=cadence.moe%2FMtRdXixoKjKKOyHJGWLsWLNU&e=cadence.moe%2FHXfFuougamkURPPMflTJRxGc&e=cadence.moe%2FikYKbkhGhMERAuPPbsnQzZiX&e=cadence.moe%2FAYPpqXzVJvZdzMQJGjioIQBZ&e=cadence.moe%2FUVuzvpVUhqjiueMxYXJiFEAj&e=cadence.moe%2FlHfmJpzgoNyNtYHdAmBHxXix&e=cadence.moe%2FMtRdXixoKjKKOyHJGWLsWLNU&e=cadence.moe%2FHXfFuougamkURPPMflTJRxGc&e=cadence.moe%2FikYKbkhGhMERAuPPbsnQzZiX&e=cadence.moe%2FAYPpqXzVJvZdzMQJGjioIQBZ&e=cadence.moe%2FUVuzvpVUhqjiueMxYXJiFEAj", { + event, + mxcDownloader: mockGetAndConvertEmoji + }) + t.equal(event.node.res.statusCode, 200) + t.equal(sheet.subarray(0, 90).toString("base64"), "iVBORw0KGgoAAAANSUhEUgAAAYAAAABgCAYAAAAU9KWJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAgAElEQVR4nOx9B3hUVdr/KIpKL2nT0pPpLRNQkdXddV1c") +})