Host QR codes locally

This commit is contained in:
Cadence Ember 2024-10-04 02:21:57 +13:00
parent 4287a329f5
commit 5a86c07eb9
7 changed files with 40 additions and 6 deletions

7
package-lock.json generated
View file

@ -38,6 +38,7 @@
"snowtransfer": "^0.10.5", "snowtransfer": "^0.10.5",
"stream-mime-type": "^1.0.2", "stream-mime-type": "^1.0.2",
"try-to-catch": "^3.0.1", "try-to-catch": "^3.0.1",
"uqr": "^0.1.2",
"xxhash-wasm": "^1.0.2", "xxhash-wasm": "^1.0.2",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
@ -3237,6 +3238,12 @@
"pathe": "^1.1.2" "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": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -47,6 +47,7 @@
"snowtransfer": "^0.10.5", "snowtransfer": "^0.10.5",
"stream-mime-type": "^1.0.2", "stream-mime-type": "^1.0.2",
"try-to-catch": "^3.0.1", "try-to-catch": "^3.0.1",
"uqr": "^0.1.2",
"xxhash-wasm": "^1.0.2", "xxhash-wasm": "^1.0.2",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },

View file

@ -195,5 +195,6 @@ Total transitive production dependencies: 147
* (0) prettier-bytes: It does what I want and has no dependencies. * (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. * (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) 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) 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. * (0) zod: Input validation for the web server. It's popular and easy to use.

View file

@ -131,7 +131,7 @@ function getJoinedMembers(roomID) {
* @returns {Promise<{chunk: Ty.Event.Outer<Ty.Event.M_Room_Member>[]}>} * @returns {Promise<{chunk: Ty.Event.Outer<Ty.Event.M_Room_Member>[]}>}
*/ */
function getMembers(roomID, membership) { 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})
} }
/** /**

View file

@ -45,6 +45,8 @@ mixin matrix(row, radio=false, badge="")
else else
.s-user-card--link.fs-body1 .s-user-card--link.fs-body1
a(href=`https://matrix.to/#/${row.room_id}`)= row.nick || row.name a(href=`https://matrix.to/#/${row.room_id}`)= row.nick || row.name
if row.join_rule === "invite"
+badge-private
block body block body
if !guild_id && session.data.managedGuilds if !guild_id && session.data.managedGuilds
@ -91,9 +93,9 @@ block body
div div
- -
let size = 105 let size = 105
let src = new URL(`https://api.qrserver.com/v1/create-qr-code/?qzone=1&format=svg&size=${size}x${size}`) let p = new URLSearchParams()
src.searchParams.set("data", `https://bridge.cadence.moe/invite?nonce=${nonce}`) p.set("data", `https://bridge.cadence.moe/invite?nonce=${nonce}`)
img(width=size height=size src=src.toString()) img(width=size height=size src=`/qr?${p}`)
h2.mt48.fs-headline1 Moderation h2.mt48.fs-headline1 Moderation
@ -123,7 +125,10 @@ block body
unlinkedChannels.sort((a, b) => getPosition(a) - getPosition(b)) unlinkedChannels.sort((a, b) => getPosition(a) - getPosition(b))
let linkedRoomIDs = linkedChannels.map(c => c.room_id) 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-card.bs-sm.p0
.s-table-container .s-table-container
table.s-table.s-table__bx-simple table.s-table.s-table__bx-simple
@ -175,7 +180,7 @@ block body
!= icons.Icons.IconLockSm != icons.Icons.IconLockSm
.fl-grow1 Invite .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 Shareable invite links, like Discord
p.s-description.m0 Publicly listed in directory, like Discord server discovery p.s-description.m0 Publicly listed in directory, like Discord server discovery

19
src/web/routes/qr.js Normal file
View file

@ -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"}})
}))

View file

@ -26,6 +26,7 @@ sync.require("./routes/download-discord")
sync.require("./routes/invite") sync.require("./routes/invite")
sync.require("./routes/guild-settings") sync.require("./routes/guild-settings")
sync.require("./routes/oauth") sync.require("./routes/oauth")
sync.require("./routes/qr")
// Files // Files