Host QR codes locally
This commit is contained in:
parent
4287a329f5
commit
5a86c07eb9
7 changed files with 40 additions and 6 deletions
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
19
src/web/routes/qr.js
Normal 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"}})
|
||||||
|
}))
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue