From 5a86c07eb9f2a2a64dfb2870711e88cb0d2b0fa3 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 4 Oct 2024 02:21:57 +1300 Subject: [PATCH] Host QR codes locally --- package-lock.json | 7 +++++++ package.json | 1 + readme.md | 1 + src/matrix/api.js | 2 +- src/web/pug/guild.pug | 15 ++++++++++----- src/web/routes/qr.js | 19 +++++++++++++++++++ src/web/server.js | 1 + 7 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 src/web/routes/qr.js diff --git a/package-lock.json b/package-lock.json index 7fe7cb0..1189af7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "snowtransfer": "^0.10.5", "stream-mime-type": "^1.0.2", "try-to-catch": "^3.0.1", + "uqr": "^0.1.2", "xxhash-wasm": "^1.0.2", "zod": "^3.23.8" }, @@ -3237,6 +3238,12 @@ "pathe": "^1.1.2" } }, + "node_modules/uqr": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz", + "integrity": "sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==", + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 68a4583..fb7fbc9 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "snowtransfer": "^0.10.5", "stream-mime-type": "^1.0.2", "try-to-catch": "^3.0.1", + "uqr": "^0.1.2", "xxhash-wasm": "^1.0.2", "zod": "^3.23.8" }, diff --git a/readme.md b/readme.md index daebb1e..f88b6ea 100644 --- a/readme.md +++ b/readme.md @@ -195,5 +195,6 @@ Total transitive production dependencies: 147 * (0) prettier-bytes: It does what I want and has no dependencies. * (2) snowtransfer: Discord API library with bring-your-own-caching that I trust. * (0) try-to-catch: Not strictly necessary, but it's already pulled in by supertape, so I may as well. +* (0) uqr: QR code SVG generator. Used on the website to scan in an invite link. * (0) xxhash-wasm: Used where cryptographically secure hashing is not required. * (0) zod: Input validation for the web server. It's popular and easy to use. diff --git a/src/matrix/api.js b/src/matrix/api.js index f640efe..7f6be01 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -131,7 +131,7 @@ function getJoinedMembers(roomID) { * @returns {Promise<{chunk: Ty.Event.Outer[]}>} */ function getMembers(roomID, membership) { - return mreq.mreq("GET", `/client/v3/rooms/${roomID}/members`, {membership}) + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/members`, undefined, {membership}) } /** diff --git a/src/web/pug/guild.pug b/src/web/pug/guild.pug index 68b1e56..acc1acb 100644 --- a/src/web/pug/guild.pug +++ b/src/web/pug/guild.pug @@ -45,6 +45,8 @@ mixin matrix(row, radio=false, badge="") else .s-user-card--link.fs-body1 a(href=`https://matrix.to/#/${row.room_id}`)= row.nick || row.name + if row.join_rule === "invite" + +badge-private block body if !guild_id && session.data.managedGuilds @@ -91,9 +93,9 @@ block body div - let size = 105 - let src = new URL(`https://api.qrserver.com/v1/create-qr-code/?qzone=1&format=svg&size=${size}x${size}`) - src.searchParams.set("data", `https://bridge.cadence.moe/invite?nonce=${nonce}`) - img(width=size height=size src=src.toString()) + let p = new URLSearchParams() + p.set("data", `https://bridge.cadence.moe/invite?nonce=${nonce}`) + img(width=size height=size src=`/qr?${p}`) h2.mt48.fs-headline1 Moderation @@ -123,7 +125,10 @@ block body unlinkedChannels.sort((a, b) => getPosition(a) - getPosition(b)) let linkedRoomIDs = linkedChannels.map(c => c.room_id) - let unlinkedRooms = rooms.filter(r => !linkedRoomIDs.includes(r.room_id)) + let unlinkedRooms = rooms.filter(r => !linkedRoomIDs.includes(r.room_id) && !r.room_type) + // https://discord.com/developers/docs/topics/threads#active-archived-threads + // need to filter out linked archived threads from unlinkedRooms, will just do that by comparing against the name + unlinkedRooms = unlinkedRooms.filter(r => !r.name.match(/^\[(🔒)?⛓️\]/)) .s-card.bs-sm.p0 .s-table-container table.s-table.s-table__bx-simple @@ -175,7 +180,7 @@ block body != icons.Icons.IconLockSm .fl-grow1 Invite - p.s-description.m0 In-app direct invite from another user; /invite on Discord; web form + p.s-description.m0 In-app direct invite from another user p.s-description.m0 Shareable invite links, like Discord p.s-description.m0 Publicly listed in directory, like Discord server discovery diff --git a/src/web/routes/qr.js b/src/web/routes/qr.js new file mode 100644 index 0000000..314b66a --- /dev/null +++ b/src/web/routes/qr.js @@ -0,0 +1,19 @@ +// @ts-check + +const {z} = require("zod") +const {defineEventHandler, getValidatedQuery} = require("h3") + +const {as} = require("../../passthrough") + +const uqr = require("uqr") + +const schema = { + qr: z.object({ + data: z.string().max(128) + }) +} + +as.router.get("/qr", defineEventHandler(async event => { + const {data} = await getValidatedQuery(event, schema.qr.parse) + return new Response(uqr.renderSVG(data, {pixelSize: 3}), {headers: {"content-type": "image/svg+xml"}}) +})) diff --git a/src/web/server.js b/src/web/server.js index 387439f..d1b4cb8 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -26,6 +26,7 @@ sync.require("./routes/download-discord") sync.require("./routes/invite") sync.require("./routes/guild-settings") sync.require("./routes/oauth") +sync.require("./routes/qr") // Files