mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Merge remote-tracking branch 'origin/main' into configdb
This commit is contained in:
commit
5e523cbb1a
23 changed files with 443 additions and 377 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
image: denoland/deno:2.0.3
|
image: denoland/deno:2.0.5
|
||||||
|
|
||||||
default:
|
default:
|
||||||
interruptible: true
|
interruptible: true
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
deno 2.0.3
|
deno 2.0.5
|
||||||
|
|
@ -3,6 +3,13 @@
|
||||||
hosts: all
|
hosts: all
|
||||||
become: true
|
become: true
|
||||||
tasks:
|
tasks:
|
||||||
|
- name: Update Deno
|
||||||
|
shell:
|
||||||
|
cmd: curl -fsSL https://deno.land/x/install/install.sh | sh
|
||||||
|
environment:
|
||||||
|
DENO_INSTALL: /usr/local
|
||||||
|
become_user: root
|
||||||
|
|
||||||
- name: Update Soapbox
|
- name: Update Soapbox
|
||||||
shell:
|
shell:
|
||||||
cmd: deno task soapbox
|
cmd: deno task soapbox
|
||||||
|
|
|
||||||
32
deno.json
32
deno.json
|
|
@ -1,26 +1,26 @@
|
||||||
{
|
{
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"start": "deno run -A --env-file src/server.ts",
|
"start": "deno run -A --env-file --deny-read=.env src/server.ts",
|
||||||
"dev": "deno run -A --env-file --watch src/server.ts",
|
"dev": "deno run -A --env-file --deny-read=.env --watch src/server.ts",
|
||||||
"hook": "deno run --allow-read --allow-run --allow-write https://deno.land/x/deno_hooks@0.1.1/mod.ts",
|
"hook": "deno run --allow-read --allow-run --allow-write https://deno.land/x/deno_hooks@0.1.1/mod.ts",
|
||||||
"db:export": "deno run -A --env-file scripts/db-export.ts",
|
"db:export": "deno run -A --env-file --deny-read=.env scripts/db-export.ts",
|
||||||
"db:import": "deno run -A --env-file scripts/db-import.ts",
|
"db:import": "deno run -A --env-file --deny-read=.env scripts/db-import.ts",
|
||||||
"db:migrate": "deno run -A --env-file scripts/db-migrate.ts",
|
"db:migrate": "deno run -A --env-file --deny-read=.env scripts/db-migrate.ts",
|
||||||
"nostr:pull": "deno run -A --env-file scripts/nostr-pull.ts",
|
"nostr:pull": "deno run -A --env-file --deny-read=.env scripts/nostr-pull.ts",
|
||||||
"debug": "deno run -A --env-file --inspect src/server.ts",
|
"debug": "deno run -A --env-file --deny-read=.env --inspect src/server.ts",
|
||||||
"test": "deno test -A --env-file=.env.test --junit-path=./deno-test.xml",
|
"test": "deno test -A --env-file=.env.test --deny-read=.env --junit-path=./deno-test.xml",
|
||||||
"check": "deno check --allow-import src/server.ts",
|
"check": "deno check --allow-import src/server.ts",
|
||||||
"nsec": "deno run scripts/nsec.ts",
|
"nsec": "deno run scripts/nsec.ts",
|
||||||
"admin:event": "deno run -A --env-file scripts/admin-event.ts",
|
"admin:event": "deno run -A --env-file --deny-read=.env scripts/admin-event.ts",
|
||||||
"admin:role": "deno run -A --env-file scripts/admin-role.ts",
|
"admin:role": "deno run -A --env-file --deny-read=.env scripts/admin-role.ts",
|
||||||
"setup": "deno run -A --env-file scripts/setup.ts",
|
"setup": "deno run -A --env-file scripts/setup.ts",
|
||||||
"setup:kind0": "deno run -A --env-file scripts/setup-kind0.ts",
|
"setup:kind0": "deno run -A --env-file --deny-read=.env scripts/setup-kind0.ts",
|
||||||
"stats:recompute": "deno run -A --env-file scripts/stats-recompute.ts",
|
"stats:recompute": "deno run -A --env-file --deny-read=.env scripts/stats-recompute.ts",
|
||||||
"soapbox": "curl -O https://dl.soapbox.pub/main/soapbox.zip && mkdir -p public && mv soapbox.zip public/ && cd public/ && unzip -o soapbox.zip && rm soapbox.zip",
|
"soapbox": "curl -O https://dl.soapbox.pub/main/soapbox.zip && mkdir -p public && mv soapbox.zip public/ && cd public/ && unzip -o soapbox.zip && rm soapbox.zip",
|
||||||
"trends": "deno run -A --env-file scripts/trends.ts",
|
"trends": "deno run -A --env-file --deny-read=.env scripts/trends.ts",
|
||||||
"clean:deps": "deno cache --reload src/app.ts",
|
"clean:deps": "deno cache --reload src/app.ts",
|
||||||
"db:populate-search": "deno run -A --env-file scripts/db-populate-search.ts",
|
"db:populate-search": "deno run -A --env-file --deny-read=.env scripts/db-populate-search.ts",
|
||||||
"vapid": "deno run scripts/vapid.ts"
|
"vapid": "deno run scripts/vapid.ts"
|
||||||
},
|
},
|
||||||
"unstable": [
|
"unstable": [
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
"@scure/base": "npm:@scure/base@^1.1.6",
|
"@scure/base": "npm:@scure/base@^1.1.6",
|
||||||
"@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs",
|
"@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs",
|
||||||
"@soapbox/kysely-pglite": "jsr:@soapbox/kysely-pglite@^1.0.0",
|
"@soapbox/kysely-pglite": "jsr:@soapbox/kysely-pglite@^1.0.0",
|
||||||
|
"@soapbox/safe-fetch": "jsr:@soapbox/safe-fetch@^2.0.0",
|
||||||
"@soapbox/stickynotes": "jsr:@soapbox/stickynotes@^0.4.0",
|
"@soapbox/stickynotes": "jsr:@soapbox/stickynotes@^0.4.0",
|
||||||
"@std/assert": "jsr:@std/assert@^0.225.1",
|
"@std/assert": "jsr:@std/assert@^0.225.1",
|
||||||
"@std/cli": "jsr:@std/cli@^0.223.0",
|
"@std/cli": "jsr:@std/cli@^0.223.0",
|
||||||
|
|
@ -59,10 +60,10 @@
|
||||||
"@std/json": "jsr:@std/json@^0.223.0",
|
"@std/json": "jsr:@std/json@^0.223.0",
|
||||||
"@std/media-types": "jsr:@std/media-types@^0.224.1",
|
"@std/media-types": "jsr:@std/media-types@^0.224.1",
|
||||||
"@std/streams": "jsr:@std/streams@^0.223.0",
|
"@std/streams": "jsr:@std/streams@^0.223.0",
|
||||||
|
"blurhash": "npm:blurhash@2.0.5",
|
||||||
"comlink": "npm:comlink@^4.4.1",
|
"comlink": "npm:comlink@^4.4.1",
|
||||||
"comlink-async-generator": "npm:comlink-async-generator@^0.0.1",
|
"comlink-async-generator": "npm:comlink-async-generator@^0.0.1",
|
||||||
"commander": "npm:commander@12.1.0",
|
"commander": "npm:commander@12.1.0",
|
||||||
"deno-safe-fetch/load": "https://gitlab.com/soapbox-pub/deno-safe-fetch/-/raw/v1.0.0/load.ts",
|
|
||||||
"deno.json": "./deno.json",
|
"deno.json": "./deno.json",
|
||||||
"entities": "npm:entities@^4.5.0",
|
"entities": "npm:entities@^4.5.0",
|
||||||
"fast-stable-stringify": "npm:fast-stable-stringify@^1.0.0",
|
"fast-stable-stringify": "npm:fast-stable-stringify@^1.0.0",
|
||||||
|
|
@ -85,6 +86,7 @@
|
||||||
"postgres": "https://gitlab.com/soapbox-pub/postgres.js/-/raw/e79d7d2039446fbf7a37d4eca0d17e94a94b8b53/deno/mod.js",
|
"postgres": "https://gitlab.com/soapbox-pub/postgres.js/-/raw/e79d7d2039446fbf7a37d4eca0d17e94a94b8b53/deno/mod.js",
|
||||||
"prom-client": "npm:prom-client@^15.1.2",
|
"prom-client": "npm:prom-client@^15.1.2",
|
||||||
"question-deno": "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/mod.ts",
|
"question-deno": "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/mod.ts",
|
||||||
|
"sharp": "npm:sharp@^0.33.5",
|
||||||
"tldts": "npm:tldts@^6.0.14",
|
"tldts": "npm:tldts@^6.0.14",
|
||||||
"tseep": "npm:tseep@^1.2.1",
|
"tseep": "npm:tseep@^1.2.1",
|
||||||
"type-fest": "npm:type-fest@^4.3.0",
|
"type-fest": "npm:type-fest@^4.3.0",
|
||||||
|
|
|
||||||
201
deno.lock
generated
201
deno.lock
generated
|
|
@ -36,6 +36,7 @@
|
||||||
"jsr:@nostrify/nostrify@~0.22.4": "0.22.4",
|
"jsr:@nostrify/nostrify@~0.22.4": "0.22.4",
|
||||||
"jsr:@nostrify/nostrify@~0.22.5": "0.22.5",
|
"jsr:@nostrify/nostrify@~0.22.5": "0.22.5",
|
||||||
"jsr:@nostrify/policies@0.33": "0.33.0",
|
"jsr:@nostrify/policies@0.33": "0.33.0",
|
||||||
|
"jsr:@nostrify/policies@0.33.1": "0.33.1",
|
||||||
"jsr:@nostrify/policies@0.34": "0.34.0",
|
"jsr:@nostrify/policies@0.34": "0.34.0",
|
||||||
"jsr:@nostrify/policies@0.35": "0.35.0",
|
"jsr:@nostrify/policies@0.35": "0.35.0",
|
||||||
"jsr:@nostrify/policies@0.36": "0.36.0",
|
"jsr:@nostrify/policies@0.36": "0.36.0",
|
||||||
|
|
@ -45,6 +46,7 @@
|
||||||
"jsr:@nostrify/types@0.35": "0.35.0",
|
"jsr:@nostrify/types@0.35": "0.35.0",
|
||||||
"jsr:@nostrify/types@~0.30.1": "0.30.1",
|
"jsr:@nostrify/types@~0.30.1": "0.30.1",
|
||||||
"jsr:@soapbox/kysely-pglite@1": "1.0.0",
|
"jsr:@soapbox/kysely-pglite@1": "1.0.0",
|
||||||
|
"jsr:@soapbox/safe-fetch@2": "2.0.0",
|
||||||
"jsr:@soapbox/stickynotes@0.4": "0.4.0",
|
"jsr:@soapbox/stickynotes@0.4": "0.4.0",
|
||||||
"jsr:@std/assert@0.223": "0.223.0",
|
"jsr:@std/assert@0.223": "0.223.0",
|
||||||
"jsr:@std/assert@0.224": "0.224.0",
|
"jsr:@std/assert@0.224": "0.224.0",
|
||||||
|
|
@ -86,6 +88,7 @@
|
||||||
"npm:@scure/bip32@^1.4.0": "1.4.0",
|
"npm:@scure/bip32@^1.4.0": "1.4.0",
|
||||||
"npm:@scure/bip39@^1.3.0": "1.3.0",
|
"npm:@scure/bip39@^1.3.0": "1.3.0",
|
||||||
"npm:@types/node@*": "18.16.19",
|
"npm:@types/node@*": "18.16.19",
|
||||||
|
"npm:blurhash@2.0.5": "2.0.5",
|
||||||
"npm:comlink-async-generator@*": "0.0.1",
|
"npm:comlink-async-generator@*": "0.0.1",
|
||||||
"npm:comlink-async-generator@^0.0.1": "0.0.1",
|
"npm:comlink-async-generator@^0.0.1": "0.0.1",
|
||||||
"npm:comlink@^4.4.1": "4.4.1",
|
"npm:comlink@^4.4.1": "4.4.1",
|
||||||
|
|
@ -116,7 +119,9 @@
|
||||||
"npm:png-to-ico@^2.1.8": "2.1.8",
|
"npm:png-to-ico@^2.1.8": "2.1.8",
|
||||||
"npm:postgres@3.4.4": "3.4.4",
|
"npm:postgres@3.4.4": "3.4.4",
|
||||||
"npm:prom-client@^15.1.2": "15.1.2",
|
"npm:prom-client@^15.1.2": "15.1.2",
|
||||||
|
"npm:sharp@~0.33.5": "0.33.5",
|
||||||
"npm:tldts@^6.0.14": "6.1.18",
|
"npm:tldts@^6.0.14": "6.1.18",
|
||||||
|
"npm:tldts@^6.1.61": "6.1.61",
|
||||||
"npm:tseep@^1.2.1": "1.2.1",
|
"npm:tseep@^1.2.1": "1.2.1",
|
||||||
"npm:type-fest@^4.3.0": "4.18.2",
|
"npm:type-fest@^4.3.0": "4.18.2",
|
||||||
"npm:unfurl.js@^6.4.0": "6.4.0",
|
"npm:unfurl.js@^6.4.0": "6.4.0",
|
||||||
|
|
@ -480,6 +485,12 @@
|
||||||
"npm:kysely@~0.27.4"
|
"npm:kysely@~0.27.4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"@soapbox/safe-fetch@2.0.0": {
|
||||||
|
"integrity": "f451d686501c76a0faa058fe9d2073676282a8a42c3b93c59159eb9191f11b5f",
|
||||||
|
"dependencies": [
|
||||||
|
"npm:tldts@^6.1.61"
|
||||||
|
]
|
||||||
|
},
|
||||||
"@soapbox/stickynotes@0.4.0": {
|
"@soapbox/stickynotes@0.4.0": {
|
||||||
"integrity": "60bfe61ab3d7e04bf708273b1e2d391a59534bdf29e54160e98d7afd328ca1ec"
|
"integrity": "60bfe61ab3d7e04bf708273b1e2d391a59534bdf29e54160e98d7afd328ca1ec"
|
||||||
},
|
},
|
||||||
|
|
@ -655,6 +666,96 @@
|
||||||
"@electric-sql/pglite@0.2.8": {
|
"@electric-sql/pglite@0.2.8": {
|
||||||
"integrity": "sha512-0wSmQu22euBRzR5ghqyIHnBH4MfwlkL5WstOrrA3KOsjEWEglvoL/gH92JajEUA6Ufei/+qbkB2hVloC/K/RxQ=="
|
"integrity": "sha512-0wSmQu22euBRzR5ghqyIHnBH4MfwlkL5WstOrrA3KOsjEWEglvoL/gH92JajEUA6Ufei/+qbkB2hVloC/K/RxQ=="
|
||||||
},
|
},
|
||||||
|
"@emnapi/runtime@1.3.1": {
|
||||||
|
"integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
|
||||||
|
"dependencies": [
|
||||||
|
"tslib"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-darwin-arm64@0.33.5": {
|
||||||
|
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-darwin-arm64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-darwin-x64@0.33.5": {
|
||||||
|
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-darwin-x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-darwin-arm64@1.0.4": {
|
||||||
|
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-darwin-x64@1.0.4": {
|
||||||
|
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-linux-arm64@1.0.4": {
|
||||||
|
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-linux-arm@1.0.5": {
|
||||||
|
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-linux-s390x@1.0.4": {
|
||||||
|
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-linux-x64@1.0.4": {
|
||||||
|
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64@1.0.4": {
|
||||||
|
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="
|
||||||
|
},
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64@1.0.4": {
|
||||||
|
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="
|
||||||
|
},
|
||||||
|
"@img/sharp-linux-arm64@0.33.5": {
|
||||||
|
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-linux-arm64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-linux-arm@0.33.5": {
|
||||||
|
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-linux-arm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-linux-s390x@0.33.5": {
|
||||||
|
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-linux-s390x"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-linux-x64@0.33.5": {
|
||||||
|
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-linux-x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-linuxmusl-arm64@0.33.5": {
|
||||||
|
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-linuxmusl-x64@0.33.5": {
|
||||||
|
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-wasm32@0.33.5": {
|
||||||
|
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@emnapi/runtime"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@img/sharp-win32-ia32@0.33.5": {
|
||||||
|
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="
|
||||||
|
},
|
||||||
|
"@img/sharp-win32-x64@0.33.5": {
|
||||||
|
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="
|
||||||
|
},
|
||||||
"@isaacs/ttlcache@1.4.1": {
|
"@isaacs/ttlcache@1.4.1": {
|
||||||
"integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="
|
"integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="
|
||||||
},
|
},
|
||||||
|
|
@ -769,6 +870,9 @@
|
||||||
"bintrees@1.0.2": {
|
"bintrees@1.0.2": {
|
||||||
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="
|
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="
|
||||||
},
|
},
|
||||||
|
"blurhash@2.0.5": {
|
||||||
|
"integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w=="
|
||||||
|
},
|
||||||
"braces@3.0.2": {
|
"braces@3.0.2": {
|
||||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -791,6 +895,29 @@
|
||||||
"string-width"
|
"string-width"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"color-convert@2.0.1": {
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"color-name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color-name@1.1.4": {
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"color-string@1.9.1": {
|
||||||
|
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||||
|
"dependencies": [
|
||||||
|
"color-name",
|
||||||
|
"simple-swizzle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color@4.2.3": {
|
||||||
|
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||||
|
"dependencies": [
|
||||||
|
"color-convert",
|
||||||
|
"color-string"
|
||||||
|
]
|
||||||
|
},
|
||||||
"colorette@2.0.20": {
|
"colorette@2.0.20": {
|
||||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
|
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
|
||||||
},
|
},
|
||||||
|
|
@ -854,6 +981,9 @@
|
||||||
"delayed-stream@1.0.0": {
|
"delayed-stream@1.0.0": {
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||||
},
|
},
|
||||||
|
"detect-libc@2.0.3": {
|
||||||
|
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
|
||||||
|
},
|
||||||
"dom-serializer@2.0.0": {
|
"dom-serializer@2.0.0": {
|
||||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -987,6 +1117,18 @@
|
||||||
"safer-buffer"
|
"safer-buffer"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"image-size@1.1.1": {
|
||||||
|
"integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"queue"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inherits@2.0.4": {
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
|
"is-arrayish@0.3.2": {
|
||||||
|
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||||
|
},
|
||||||
"is-fullwidth-code-point@4.0.0": {
|
"is-fullwidth-code-point@4.0.0": {
|
||||||
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="
|
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="
|
||||||
},
|
},
|
||||||
|
|
@ -1260,6 +1402,12 @@
|
||||||
"punycode@2.3.1": {
|
"punycode@2.3.1": {
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
|
||||||
},
|
},
|
||||||
|
"queue@6.0.2": {
|
||||||
|
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
|
||||||
|
"dependencies": [
|
||||||
|
"inherits"
|
||||||
|
]
|
||||||
|
},
|
||||||
"restore-cursor@4.0.0": {
|
"restore-cursor@4.0.0": {
|
||||||
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
|
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -1282,6 +1430,36 @@
|
||||||
"xmlchars"
|
"xmlchars"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"semver@7.6.3": {
|
||||||
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||||
|
},
|
||||||
|
"sharp@0.33.5": {
|
||||||
|
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@img/sharp-darwin-arm64",
|
||||||
|
"@img/sharp-darwin-x64",
|
||||||
|
"@img/sharp-libvips-darwin-arm64",
|
||||||
|
"@img/sharp-libvips-darwin-x64",
|
||||||
|
"@img/sharp-libvips-linux-arm",
|
||||||
|
"@img/sharp-libvips-linux-arm64",
|
||||||
|
"@img/sharp-libvips-linux-s390x",
|
||||||
|
"@img/sharp-libvips-linux-x64",
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64",
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64",
|
||||||
|
"@img/sharp-linux-arm",
|
||||||
|
"@img/sharp-linux-arm64",
|
||||||
|
"@img/sharp-linux-s390x",
|
||||||
|
"@img/sharp-linux-x64",
|
||||||
|
"@img/sharp-linuxmusl-arm64",
|
||||||
|
"@img/sharp-linuxmusl-x64",
|
||||||
|
"@img/sharp-wasm32",
|
||||||
|
"@img/sharp-win32-ia32",
|
||||||
|
"@img/sharp-win32-x64",
|
||||||
|
"color",
|
||||||
|
"detect-libc",
|
||||||
|
"semver"
|
||||||
|
]
|
||||||
|
},
|
||||||
"shebang-command@2.0.0": {
|
"shebang-command@2.0.0": {
|
||||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -1297,6 +1475,12 @@
|
||||||
"signal-exit@4.1.0": {
|
"signal-exit@4.1.0": {
|
||||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
|
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
|
||||||
},
|
},
|
||||||
|
"simple-swizzle@0.2.2": {
|
||||||
|
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||||
|
"dependencies": [
|
||||||
|
"is-arrayish"
|
||||||
|
]
|
||||||
|
},
|
||||||
"slice-ansi@5.0.0": {
|
"slice-ansi@5.0.0": {
|
||||||
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
|
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -1346,6 +1530,9 @@
|
||||||
"tldts-core@6.1.49": {
|
"tldts-core@6.1.49": {
|
||||||
"integrity": "sha512-ctRO/wzBasOCxAStJG/60Qe8/QpGmaVPsE8djdk0vioxN4uCOgKoveH71Qc2EOmVMIjVf0BjigI5p9ZDuLOygg=="
|
"integrity": "sha512-ctRO/wzBasOCxAStJG/60Qe8/QpGmaVPsE8djdk0vioxN4uCOgKoveH71Qc2EOmVMIjVf0BjigI5p9ZDuLOygg=="
|
||||||
},
|
},
|
||||||
|
"tldts-core@6.1.61": {
|
||||||
|
"integrity": "sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ=="
|
||||||
|
},
|
||||||
"tldts@6.1.18": {
|
"tldts@6.1.18": {
|
||||||
"integrity": "sha512-F+6zjPFnFxZ0h6uGb8neQWwHQm8u3orZVFribsGq4eBgEVrzSkHxzWS2l6aKr19T1vXiOMFjqfff4fQt+WgJFg==",
|
"integrity": "sha512-F+6zjPFnFxZ0h6uGb8neQWwHQm8u3orZVFribsGq4eBgEVrzSkHxzWS2l6aKr19T1vXiOMFjqfff4fQt+WgJFg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -1358,6 +1545,12 @@
|
||||||
"tldts-core@6.1.49"
|
"tldts-core@6.1.49"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"tldts@6.1.61": {
|
||||||
|
"integrity": "sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==",
|
||||||
|
"dependencies": [
|
||||||
|
"tldts-core@6.1.61"
|
||||||
|
]
|
||||||
|
},
|
||||||
"to-regex-range@5.0.1": {
|
"to-regex-range@5.0.1": {
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -1385,6 +1578,9 @@
|
||||||
"tseep@1.2.1": {
|
"tseep@1.2.1": {
|
||||||
"integrity": "sha512-VFnsNcPGC4qFJ1nxbIPSjTmtRZOhlqLmtwRqtLVos8mbRHki8HO9cy9Z1e89EiWyxFmq6LBviI9TQjijxw/mEw=="
|
"integrity": "sha512-VFnsNcPGC4qFJ1nxbIPSjTmtRZOhlqLmtwRqtLVos8mbRHki8HO9cy9Z1e89EiWyxFmq6LBviI9TQjijxw/mEw=="
|
||||||
},
|
},
|
||||||
|
"tslib@2.6.2": {
|
||||||
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
|
},
|
||||||
"type-fest@3.13.1": {
|
"type-fest@3.13.1": {
|
||||||
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="
|
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="
|
||||||
},
|
},
|
||||||
|
|
@ -2002,8 +2198,6 @@
|
||||||
"https://deno.land/x/sentry@7.112.2/index.mjs": "04382d5c2f4e233ba389611db46f77943b2a7f6efbeaaf31193f6e586f4366ef",
|
"https://deno.land/x/sentry@7.112.2/index.mjs": "04382d5c2f4e233ba389611db46f77943b2a7f6efbeaaf31193f6e586f4366ef",
|
||||||
"https://esm.sh/kysely@0.17.1/dist/esm/index-nodeless.js": "9c23bfd307118e3ccd3a9f0ec1261fc3451fb5301aa34aa6f28e05156818755a",
|
"https://esm.sh/kysely@0.17.1/dist/esm/index-nodeless.js": "9c23bfd307118e3ccd3a9f0ec1261fc3451fb5301aa34aa6f28e05156818755a",
|
||||||
"https://esm.sh/v135/kysely@0.17.1/denonext/dist/esm/index-nodeless.js": "6f73bbf2d73bc7e96cdabf941c4ae8c12f58fd7b441031edec44c029aed9532b",
|
"https://esm.sh/v135/kysely@0.17.1/denonext/dist/esm/index-nodeless.js": "6f73bbf2d73bc7e96cdabf941c4ae8c12f58fd7b441031edec44c029aed9532b",
|
||||||
"https://gitlab.com/soapbox-pub/deno-safe-fetch/-/raw/v1.0.0/load.ts": "3f74ab08cf97d4a3e6994cb79422e9b0069495e017416858121d5ff8ae04ac2a",
|
|
||||||
"https://gitlab.com/soapbox-pub/deno-safe-fetch/-/raw/v1.0.0/mod.ts": "5f505cd265aefbcb687cde6f98c79344d3292ee1dd978e85e5ffa84a617c6682",
|
|
||||||
"https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/deps.ts": "b3dbecae69c30a5f161323b8c8ebd91d9af1eceb98fafab3091c7281a4b64fed",
|
"https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/deps.ts": "b3dbecae69c30a5f161323b8c8ebd91d9af1eceb98fafab3091c7281a4b64fed",
|
||||||
"https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de",
|
"https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de",
|
||||||
"https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/src/PostgreSQLDriver.ts": "ea5a523bceeed420858b744beeb95d48976cb2b0d3f519a68b65a8229036cf6a",
|
"https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/src/PostgreSQLDriver.ts": "ea5a523bceeed420858b744beeb95d48976cb2b0d3f519a68b65a8229036cf6a",
|
||||||
|
|
@ -2093,6 +2287,7 @@
|
||||||
"jsr:@nostrify/nostrify@0.36",
|
"jsr:@nostrify/nostrify@0.36",
|
||||||
"jsr:@nostrify/policies@0.35",
|
"jsr:@nostrify/policies@0.35",
|
||||||
"jsr:@soapbox/kysely-pglite@1",
|
"jsr:@soapbox/kysely-pglite@1",
|
||||||
|
"jsr:@soapbox/safe-fetch@2",
|
||||||
"jsr:@soapbox/stickynotes@0.4",
|
"jsr:@soapbox/stickynotes@0.4",
|
||||||
"jsr:@std/assert@~0.225.1",
|
"jsr:@std/assert@~0.225.1",
|
||||||
"jsr:@std/cli@0.223",
|
"jsr:@std/cli@0.223",
|
||||||
|
|
@ -2106,6 +2301,7 @@
|
||||||
"npm:@isaacs/ttlcache@^1.4.1",
|
"npm:@isaacs/ttlcache@^1.4.1",
|
||||||
"npm:@noble/secp256k1@2",
|
"npm:@noble/secp256k1@2",
|
||||||
"npm:@scure/base@^1.1.6",
|
"npm:@scure/base@^1.1.6",
|
||||||
|
"npm:blurhash@2.0.5",
|
||||||
"npm:comlink-async-generator@^0.0.1",
|
"npm:comlink-async-generator@^0.0.1",
|
||||||
"npm:comlink@^4.4.1",
|
"npm:comlink@^4.4.1",
|
||||||
"npm:commander@12.1.0",
|
"npm:commander@12.1.0",
|
||||||
|
|
@ -2128,6 +2324,7 @@
|
||||||
"npm:path-to-regexp@^7.1.0",
|
"npm:path-to-regexp@^7.1.0",
|
||||||
"npm:png-to-ico@^2.1.8",
|
"npm:png-to-ico@^2.1.8",
|
||||||
"npm:prom-client@^15.1.2",
|
"npm:prom-client@^15.1.2",
|
||||||
|
"npm:sharp@~0.33.5",
|
||||||
"npm:tldts@^6.0.14",
|
"npm:tldts@^6.0.14",
|
||||||
"npm:tseep@^1.2.1",
|
"npm:tseep@^1.2.1",
|
||||||
"npm:type-fest@^4.3.0",
|
"npm:type-fest@^4.3.0",
|
||||||
|
|
|
||||||
13
src/app.ts
13
src/app.ts
|
|
@ -49,6 +49,7 @@ import {
|
||||||
nameRequestController,
|
nameRequestController,
|
||||||
nameRequestsController,
|
nameRequestsController,
|
||||||
statusZapSplitsController,
|
statusZapSplitsController,
|
||||||
|
updateInstanceController,
|
||||||
updateZapSplitsController,
|
updateZapSplitsController,
|
||||||
} from '@/controllers/api/ditto.ts';
|
} from '@/controllers/api/ditto.ts';
|
||||||
import { emptyArrayController, notImplementedController } from '@/controllers/api/fallback.ts';
|
import { emptyArrayController, notImplementedController } from '@/controllers/api/fallback.ts';
|
||||||
|
|
@ -169,6 +170,11 @@ const app = new Hono<AppEnv>({ strict: false });
|
||||||
|
|
||||||
const debug = Debug('ditto:http');
|
const debug = Debug('ditto:http');
|
||||||
|
|
||||||
|
/** User-provided files in the gitignored `public/` directory. */
|
||||||
|
const publicFiles = serveStatic({ root: './public/' });
|
||||||
|
/** Static files provided by the Ditto repo, checked into git. */
|
||||||
|
const staticFiles = serveStatic({ root: './static/' });
|
||||||
|
|
||||||
app.use('*', rateLimitMiddleware(300, Time.minutes(5)));
|
app.use('*', rateLimitMiddleware(300, Time.minutes(5)));
|
||||||
|
|
||||||
app.use('/api/*', metricsMiddleware, paginationMiddleware, logger(debug));
|
app.use('/api/*', metricsMiddleware, paginationMiddleware, logger(debug));
|
||||||
|
|
@ -303,6 +309,8 @@ app.delete('/api/v1/pleroma/admin/statuses/:id', requireRole('admin'), pleromaAd
|
||||||
app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysController);
|
app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysController);
|
||||||
app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController);
|
app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController);
|
||||||
|
|
||||||
|
app.put('/api/v1/admin/ditto/instance', requireRole('admin'), updateInstanceController);
|
||||||
|
|
||||||
app.post('/api/v1/ditto/names', requireSigner, nameRequestController);
|
app.post('/api/v1/ditto/names', requireSigner, nameRequestController);
|
||||||
app.get('/api/v1/ditto/names', requireSigner, nameRequestsController);
|
app.get('/api/v1/ditto/names', requireSigner, nameRequestsController);
|
||||||
|
|
||||||
|
|
@ -362,13 +370,10 @@ app.get('/api/v1/conversations', emptyArrayController);
|
||||||
app.get('/api/v1/lists', emptyArrayController);
|
app.get('/api/v1/lists', emptyArrayController);
|
||||||
|
|
||||||
app.use('/api/*', notImplementedController);
|
app.use('/api/*', notImplementedController);
|
||||||
app.use('/.well-known/*', notImplementedController);
|
app.use('/.well-known/*', publicFiles, notImplementedController);
|
||||||
app.use('/nodeinfo/*', notImplementedController);
|
app.use('/nodeinfo/*', notImplementedController);
|
||||||
app.use('/oauth/*', notImplementedController);
|
app.use('/oauth/*', notImplementedController);
|
||||||
|
|
||||||
const publicFiles = serveStatic({ root: './public/' });
|
|
||||||
const staticFiles = serveStatic({ root: './static/' });
|
|
||||||
|
|
||||||
// Known frontend routes
|
// Known frontend routes
|
||||||
app.get('/:acct{@.*}', frontendController);
|
app.get('/:acct{@.*}', frontendController);
|
||||||
app.get('/:acct{@.*}/*', frontendController);
|
app.get('/:acct{@.*}/*', frontendController);
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,13 @@ class Conf {
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Whether to analyze media metadata with [blurhash](https://www.npmjs.com/package/blurhash) and [sharp](https://www.npmjs.com/package/sharp).
|
||||||
|
* This is prone to security vulnerabilities, which is why it's not enabled by default.
|
||||||
|
*/
|
||||||
|
static get mediaAnalyze(): boolean {
|
||||||
|
return optionalBooleanSchema.parse(Deno.env.get('MEDIA_ANALYZE')) ?? false;
|
||||||
|
}
|
||||||
/** Max upload size for files in number of bytes. Default 100MiB. */
|
/** Max upload size for files in number of bytes. Default 100MiB. */
|
||||||
static get maxUploadSize(): number {
|
static get maxUploadSize(): number {
|
||||||
return Number(Deno.env.get('MAX_UPLOAD_SIZE') || 100 * 1024 * 1024);
|
return Number(Deno.env.get('MAX_UPLOAD_SIZE') || 100 * 1024 * 1024);
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { addTag } from '@/utils/tags.ts';
|
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
|
||||||
import { booleanParamSchema, percentageSchema } from '@/schema.ts';
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { createEvent, paginated, parseBody } from '@/utils/api.ts';
|
import { addTag } from '@/utils/tags.ts';
|
||||||
|
import { getAuthor } from '@/queries.ts';
|
||||||
|
import { createEvent, paginated, parseBody, updateEvent } from '@/utils/api.ts';
|
||||||
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
import { deleteTag } from '@/utils/tags.ts';
|
import { deleteTag } from '@/utils/tags.ts';
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts';
|
import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts';
|
||||||
import { getAuthor } from '@/queries.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
|
import { screenshotsSchema } from '@/schemas/nostr.ts';
|
||||||
|
import { booleanParamSchema, percentageSchema } from '@/schema.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { renderNameRequest } from '@/views/ditto.ts';
|
import { renderNameRequest } from '@/views/ditto.ts';
|
||||||
|
import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
|
||||||
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { updateListAdminEvent } from '@/utils/api.ts';
|
import { updateListAdminEvent } from '@/utils/api.ts';
|
||||||
|
|
@ -287,3 +289,55 @@ export const statusZapSplitsController: AppController = async (c) => {
|
||||||
|
|
||||||
return c.json(zapSplits, 200);
|
return c.json(zapSplits, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateInstanceSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
short_description: z.string(),
|
||||||
|
/** Mastodon doesn't have this field. */
|
||||||
|
screenshots: screenshotsSchema,
|
||||||
|
/** https://docs.joinmastodon.org/entities/Instance/#thumbnail-url */
|
||||||
|
thumbnail: z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
}),
|
||||||
|
}).strict();
|
||||||
|
|
||||||
|
export const updateInstanceController: AppController = async (c) => {
|
||||||
|
const body = await parseBody(c.req.raw);
|
||||||
|
const result = updateInstanceSchema.safeParse(body);
|
||||||
|
const pubkey = Conf.pubkey;
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return c.json(result.error, 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateEvent(
|
||||||
|
{ kinds: [0], authors: [pubkey], limit: 1 },
|
||||||
|
async (_) => {
|
||||||
|
const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal);
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
short_description,
|
||||||
|
screenshots,
|
||||||
|
thumbnail,
|
||||||
|
} = result.data;
|
||||||
|
|
||||||
|
meta.name = title;
|
||||||
|
meta.about = description;
|
||||||
|
meta.tagline = short_description;
|
||||||
|
meta.screenshots = screenshots;
|
||||||
|
meta.picture = thumbnail.url;
|
||||||
|
delete meta.event;
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: 0,
|
||||||
|
content: JSON.stringify(meta),
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
c,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(204);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Context } from '@hono/hono';
|
import { type Context } from '@hono/hono';
|
||||||
|
|
||||||
const emptyArrayController = (c: Context) => c.json([]);
|
const emptyArrayController = (c: Context) => c.json([]);
|
||||||
const notImplementedController = (c: Context) => Promise.resolve(c.json({ error: 'Not implemented' }, 404));
|
const notImplementedController = (c: Context) => Promise.resolve(c.json({ error: 'Not implemented' }, 404));
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ const instanceV2Controller: AppController = async (c) => {
|
||||||
'@2x': meta.picture,
|
'@2x': meta.picture,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
screenshots: meta.screenshots,
|
||||||
|
short_description: meta.tagline,
|
||||||
languages: [
|
languages: [
|
||||||
'en',
|
'en',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ export const manifestController: AppController = async (c) => {
|
||||||
scope: '/',
|
scope: '/',
|
||||||
short_name: meta.name,
|
short_name: meta.name,
|
||||||
start_url: '/',
|
start_url: '/',
|
||||||
|
screenshots: meta.screenshots,
|
||||||
};
|
};
|
||||||
|
|
||||||
return c.json(manifest, {
|
return c.json(manifest, {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { assertEquals } from '@std/assert';
|
import { assertEquals } from '@std/assert';
|
||||||
|
|
||||||
import { percentageSchema } from '@/schema.ts';
|
import { percentageSchema, sizesSchema } from '@/schema.ts';
|
||||||
|
|
||||||
Deno.test('Value is any percentage from 1 to 100', () => {
|
Deno.test('Value is any percentage from 1 to 100', () => {
|
||||||
assertEquals(percentageSchema.safeParse('latvia' as unknown).success, false);
|
assertEquals(percentageSchema.safeParse('latvia' as unknown).success, false);
|
||||||
|
|
@ -20,3 +20,12 @@ Deno.test('Value is any percentage from 1 to 100', () => {
|
||||||
|
|
||||||
assertEquals(percentageSchema.safeParse('1e1').success, true);
|
assertEquals(percentageSchema.safeParse('1e1').success, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test('Size or sizes has correct format', () => {
|
||||||
|
assertEquals(sizesSchema.safeParse('orphan' as unknown).success, false);
|
||||||
|
assertEquals(sizesSchema.safeParse('0000x 20x20' as unknown).success, false);
|
||||||
|
assertEquals(sizesSchema.safeParse('0000x10 20X20 1x22' as unknown).success, false);
|
||||||
|
assertEquals(sizesSchema.safeParse('1000x10 20X20 1x22' as unknown).success, true);
|
||||||
|
assertEquals(sizesSchema.safeParse('3333X6666 1x22 f' as unknown).success, false);
|
||||||
|
assertEquals(sizesSchema.safeParse('11xxxxxxx0 20X20 1x22' as unknown).success, false);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,11 @@ const localeSchema = z.string().transform<Intl.Locale>((val, ctx) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** White-space separated list of sizes, each in the format <number with up to 4 digits>x<number with up to 4 digits> or with "X" in upper case. */
|
||||||
|
const sizesSchema = z.string().refine((value) =>
|
||||||
|
value.split(' ').every((v) => /^[1-9]\d{0,3}[xX][1-9]\d{0,3}$/.test(v))
|
||||||
|
);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
booleanParamSchema,
|
booleanParamSchema,
|
||||||
decode64Schema,
|
decode64Schema,
|
||||||
|
|
@ -75,4 +80,5 @@ export {
|
||||||
localeSchema,
|
localeSchema,
|
||||||
percentageSchema,
|
percentageSchema,
|
||||||
safeUrlSchema,
|
safeUrlSchema,
|
||||||
|
sizesSchema,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,323 +0,0 @@
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
const apId = z.string().url();
|
|
||||||
const recipients = z.array(z.string()).catch([]);
|
|
||||||
const published = () => z.string().datetime().catch(new Date().toISOString());
|
|
||||||
|
|
||||||
/** Validates individual items in an array, dropping any that aren't valid. */
|
|
||||||
function filteredArray<T extends z.ZodTypeAny>(schema: T) {
|
|
||||||
return z.any().array()
|
|
||||||
.transform((arr) => (
|
|
||||||
arr.map((item) => {
|
|
||||||
const parsed = schema.safeParse(item);
|
|
||||||
return parsed.success ? parsed.data : undefined;
|
|
||||||
}).filter((item): item is z.infer<T> => Boolean(item))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageSchema = z.object({
|
|
||||||
type: z.literal('Image').catch('Image'),
|
|
||||||
url: z.string().url(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const attachmentSchema = z.object({
|
|
||||||
type: z.literal('Document').catch('Document'),
|
|
||||||
mediaType: z.string().optional().catch(undefined),
|
|
||||||
url: z.string().url(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mentionSchema = z.object({
|
|
||||||
type: z.literal('Mention'),
|
|
||||||
href: z.string().url(),
|
|
||||||
name: z.string().optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const hashtagSchema = z.object({
|
|
||||||
type: z.literal('Hashtag'),
|
|
||||||
href: z.string().url(),
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const emojiSchema = z.object({
|
|
||||||
type: z.literal('Emoji'),
|
|
||||||
icon: imageSchema,
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const tagSchema = z.discriminatedUnion('type', [
|
|
||||||
mentionSchema,
|
|
||||||
hashtagSchema,
|
|
||||||
emojiSchema,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const propertyValueSchema = z.object({
|
|
||||||
type: z.literal('PropertyValue'),
|
|
||||||
name: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
verified_at: z.string().nullish(),
|
|
||||||
});
|
|
||||||
|
|
||||||
/** https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-fffd.md */
|
|
||||||
const proxySchema = z.object({
|
|
||||||
protocol: z.string().url(),
|
|
||||||
proxied: z.string(),
|
|
||||||
authoritative: z.boolean().optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const personSchema = z.object({
|
|
||||||
type: z.literal('Person'),
|
|
||||||
id: apId,
|
|
||||||
icon: imageSchema.optional().catch(undefined),
|
|
||||||
image: imageSchema.optional().catch(undefined),
|
|
||||||
name: z.string().catch(''),
|
|
||||||
preferredUsername: z.string(),
|
|
||||||
inbox: apId,
|
|
||||||
followers: apId.optional().catch(undefined),
|
|
||||||
following: apId.optional().catch(undefined),
|
|
||||||
outbox: apId.optional().catch(undefined),
|
|
||||||
summary: z.string().catch(''),
|
|
||||||
attachment: filteredArray(propertyValueSchema).catch([]),
|
|
||||||
tag: filteredArray(emojiSchema).catch([]),
|
|
||||||
endpoints: z.object({
|
|
||||||
sharedInbox: apId.optional(),
|
|
||||||
}).optional().catch({}),
|
|
||||||
publicKey: z.object({
|
|
||||||
id: apId,
|
|
||||||
owner: apId,
|
|
||||||
publicKeyPem: z.string(),
|
|
||||||
}).optional().catch(undefined),
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const applicationSchema = personSchema.merge(z.object({ type: z.literal('Application') }));
|
|
||||||
const groupSchema = personSchema.merge(z.object({ type: z.literal('Group') }));
|
|
||||||
const organizationSchema = personSchema.merge(z.object({ type: z.literal('Organization') }));
|
|
||||||
const serviceSchema = personSchema.merge(z.object({ type: z.literal('Service') }));
|
|
||||||
|
|
||||||
const actorSchema = z.discriminatedUnion('type', [
|
|
||||||
personSchema,
|
|
||||||
applicationSchema,
|
|
||||||
groupSchema,
|
|
||||||
organizationSchema,
|
|
||||||
serviceSchema,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const noteSchema = z.object({
|
|
||||||
type: z.literal('Note'),
|
|
||||||
id: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
content: z.string(),
|
|
||||||
attachment: z.array(attachmentSchema).optional().catch(undefined),
|
|
||||||
tag: filteredArray(tagSchema).catch([]),
|
|
||||||
inReplyTo: apId.optional().catch(undefined),
|
|
||||||
attributedTo: apId,
|
|
||||||
published: published(),
|
|
||||||
sensitive: z.boolean().optional().catch(undefined),
|
|
||||||
summary: z.string().nullish().catch(undefined),
|
|
||||||
quoteUrl: apId.optional().catch(undefined),
|
|
||||||
source: z.object({
|
|
||||||
content: z.string(),
|
|
||||||
mediaType: z.literal('text/markdown'),
|
|
||||||
}).optional().catch(undefined),
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const flexibleNoteSchema = noteSchema.extend({
|
|
||||||
quoteURL: apId.optional().catch(undefined),
|
|
||||||
quoteUri: apId.optional().catch(undefined),
|
|
||||||
_misskey_quote: apId.optional().catch(undefined),
|
|
||||||
}).transform((note) => {
|
|
||||||
const { quoteUrl, quoteUri, quoteURL, _misskey_quote, ...rest } = note;
|
|
||||||
return {
|
|
||||||
quoteUrl: quoteUrl || quoteUri || quoteURL || _misskey_quote,
|
|
||||||
...rest,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://github.com/colinhacks/zod/discussions/2100#discussioncomment-5109781
|
|
||||||
const objectSchema = z.union([
|
|
||||||
flexibleNoteSchema,
|
|
||||||
personSchema,
|
|
||||||
applicationSchema,
|
|
||||||
groupSchema,
|
|
||||||
organizationSchema,
|
|
||||||
serviceSchema,
|
|
||||||
]).pipe(
|
|
||||||
z.discriminatedUnion('type', [
|
|
||||||
noteSchema,
|
|
||||||
personSchema,
|
|
||||||
applicationSchema,
|
|
||||||
groupSchema,
|
|
||||||
organizationSchema,
|
|
||||||
serviceSchema,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const createNoteSchema = z.object({
|
|
||||||
type: z.literal('Create'),
|
|
||||||
id: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
actor: apId,
|
|
||||||
object: noteSchema,
|
|
||||||
published: published(),
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const announceNoteSchema = z.object({
|
|
||||||
type: z.literal('Announce'),
|
|
||||||
id: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
actor: apId,
|
|
||||||
object: apId.or(noteSchema),
|
|
||||||
published: published(),
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const followSchema = z.object({
|
|
||||||
type: z.literal('Follow'),
|
|
||||||
id: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
actor: apId,
|
|
||||||
object: apId,
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const acceptSchema = z.object({
|
|
||||||
type: z.literal('Accept'),
|
|
||||||
id: apId,
|
|
||||||
actor: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
object: apId.or(followSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const likeSchema = z.object({
|
|
||||||
type: z.literal('Like'),
|
|
||||||
id: apId,
|
|
||||||
actor: apId,
|
|
||||||
object: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const emojiReactSchema = z.object({
|
|
||||||
type: z.literal('EmojiReact'),
|
|
||||||
id: apId,
|
|
||||||
actor: apId,
|
|
||||||
object: apId,
|
|
||||||
content: z.string().refine((v) => /\p{Extended_Pictographic}/u.test(v)),
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const deleteSchema = z.object({
|
|
||||||
type: z.literal('Delete'),
|
|
||||||
id: apId,
|
|
||||||
actor: apId,
|
|
||||||
object: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateActorSchema = z.object({
|
|
||||||
type: z.literal('Update'),
|
|
||||||
id: apId,
|
|
||||||
actor: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
object: actorSchema,
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A custom Zap activity type we made up, based on:
|
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/57.md
|
|
||||||
*/
|
|
||||||
const zapSchema = z.object({
|
|
||||||
type: z.literal('Zap'),
|
|
||||||
id: apId,
|
|
||||||
actor: apId,
|
|
||||||
object: apId,
|
|
||||||
to: recipients,
|
|
||||||
cc: recipients,
|
|
||||||
proxyOf: z.array(proxySchema).optional().catch(undefined),
|
|
||||||
});
|
|
||||||
|
|
||||||
const activitySchema = z.discriminatedUnion('type', [
|
|
||||||
followSchema,
|
|
||||||
acceptSchema,
|
|
||||||
createNoteSchema,
|
|
||||||
announceNoteSchema,
|
|
||||||
updateActorSchema,
|
|
||||||
likeSchema,
|
|
||||||
emojiReactSchema,
|
|
||||||
deleteSchema,
|
|
||||||
zapSchema,
|
|
||||||
]).refine((activity) => {
|
|
||||||
const ids: string[] = [activity.id];
|
|
||||||
|
|
||||||
if (activity.type === 'Create') {
|
|
||||||
ids.push(
|
|
||||||
activity.object.id,
|
|
||||||
activity.object.attributedTo,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity.type === 'Update') {
|
|
||||||
ids.push(activity.object.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { origin: actorOrigin } = new URL(activity.actor);
|
|
||||||
|
|
||||||
// Object containment
|
|
||||||
return ids.every((id) => {
|
|
||||||
const { origin: idOrigin } = new URL(id);
|
|
||||||
return idOrigin === actorOrigin;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
type Activity = z.infer<typeof activitySchema>;
|
|
||||||
type CreateNote = z.infer<typeof createNoteSchema>;
|
|
||||||
type Announce = z.infer<typeof announceNoteSchema>;
|
|
||||||
type Update = z.infer<typeof updateActorSchema>;
|
|
||||||
type Object = z.infer<typeof objectSchema>;
|
|
||||||
type Follow = z.infer<typeof followSchema>;
|
|
||||||
type Accept = z.infer<typeof acceptSchema>;
|
|
||||||
type Actor = z.infer<typeof actorSchema>;
|
|
||||||
type Note = z.infer<typeof noteSchema>;
|
|
||||||
type Mention = z.infer<typeof mentionSchema>;
|
|
||||||
type Hashtag = z.infer<typeof hashtagSchema>;
|
|
||||||
type Emoji = z.infer<typeof emojiSchema>;
|
|
||||||
type Like = z.infer<typeof likeSchema>;
|
|
||||||
type EmojiReact = z.infer<typeof emojiReactSchema>;
|
|
||||||
type Delete = z.infer<typeof deleteSchema>;
|
|
||||||
type Zap = z.infer<typeof zapSchema>;
|
|
||||||
type Proxy = z.infer<typeof proxySchema>;
|
|
||||||
|
|
||||||
export { acceptSchema, activitySchema, actorSchema, emojiSchema, followSchema, imageSchema, noteSchema, objectSchema };
|
|
||||||
export type {
|
|
||||||
Accept,
|
|
||||||
Activity,
|
|
||||||
Actor,
|
|
||||||
Announce,
|
|
||||||
CreateNote,
|
|
||||||
Delete,
|
|
||||||
Emoji,
|
|
||||||
EmojiReact,
|
|
||||||
Follow,
|
|
||||||
Hashtag,
|
|
||||||
Like,
|
|
||||||
Mention,
|
|
||||||
Note,
|
|
||||||
Object,
|
|
||||||
Proxy,
|
|
||||||
Update,
|
|
||||||
Zap,
|
|
||||||
};
|
|
||||||
13
src/schemas/mastodon.ts
Normal file
13
src/schemas/mastodon.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/** https://docs.joinmastodon.org/entities/Instance/#thumbnail */
|
||||||
|
const thumbnailSchema = z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
blurhash: z.string().optional(),
|
||||||
|
versions: z.object({
|
||||||
|
'@1x': z.string().url().optional(),
|
||||||
|
'@2x': z.string().url().optional(),
|
||||||
|
}).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export { thumbnailSchema };
|
||||||
|
|
@ -2,17 +2,48 @@ import { NSchema as n } from '@nostrify/nostrify';
|
||||||
import { getEventHash, verifyEvent } from 'nostr-tools';
|
import { getEventHash, verifyEvent } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { safeUrlSchema } from '@/schema.ts';
|
import { safeUrlSchema, sizesSchema } from '@/schema.ts';
|
||||||
|
|
||||||
/** Nostr event schema that also verifies the event's signature. */
|
/** Nostr event schema that also verifies the event's signature. */
|
||||||
const signedEventSchema = n.event()
|
const signedEventSchema = n.event()
|
||||||
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
|
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
|
||||||
.refine(verifyEvent, 'Event signature is invalid');
|
.refine(verifyEvent, 'Event signature is invalid');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stored in the kind 0 content.
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/Manifest/screenshots
|
||||||
|
*/
|
||||||
|
const screenshotsSchema = z.array(z.object({
|
||||||
|
form_factor: z.enum(['narrow', 'wide']).optional(),
|
||||||
|
label: z.string().optional(),
|
||||||
|
platform: z.enum([
|
||||||
|
'android',
|
||||||
|
'chromeos',
|
||||||
|
'ipados',
|
||||||
|
'ios',
|
||||||
|
'kaios',
|
||||||
|
'macos',
|
||||||
|
'windows',
|
||||||
|
'xbox',
|
||||||
|
'chrome_web_store',
|
||||||
|
'itunes',
|
||||||
|
'microsoft-inbox',
|
||||||
|
'microsoft-store',
|
||||||
|
'play',
|
||||||
|
]).optional(),
|
||||||
|
/** https://developer.mozilla.org/en-US/docs/Web/Manifest/screenshots#sizes */
|
||||||
|
sizes: sizesSchema.optional(),
|
||||||
|
/** Absolute URL. */
|
||||||
|
src: z.string().url(),
|
||||||
|
/** MIME type of the image. */
|
||||||
|
type: z.string().optional(),
|
||||||
|
}));
|
||||||
|
|
||||||
/** Kind 0 content schema for the Ditto server admin user. */
|
/** Kind 0 content schema for the Ditto server admin user. */
|
||||||
const serverMetaSchema = n.metadata().and(z.object({
|
const serverMetaSchema = n.metadata().and(z.object({
|
||||||
tagline: z.string().optional().catch(undefined),
|
tagline: z.string().optional().catch(undefined),
|
||||||
email: z.string().optional().catch(undefined),
|
email: z.string().optional().catch(undefined),
|
||||||
|
screenshots: screenshotsSchema.optional(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/** NIP-11 Relay Information Document. */
|
/** NIP-11 Relay Information Document. */
|
||||||
|
|
@ -32,4 +63,4 @@ const emojiTagSchema = z.tuple([z.literal('emoji'), z.string(), z.string().url()
|
||||||
/** NIP-30 custom emoji tag. */
|
/** NIP-30 custom emoji tag. */
|
||||||
type EmojiTag = z.infer<typeof emojiTagSchema>;
|
type EmojiTag = z.infer<typeof emojiTagSchema>;
|
||||||
|
|
||||||
export { type EmojiTag, emojiTagSchema, relayInfoDocSchema, serverMetaSchema, signedEventSchema };
|
export { type EmojiTag, emojiTagSchema, relayInfoDocSchema, screenshotsSchema, serverMetaSchema, signedEventSchema };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import 'deno-safe-fetch/load';
|
|
||||||
|
|
||||||
import '@/precheck.ts';
|
import '@/precheck.ts';
|
||||||
import '@/sentry.ts';
|
import '@/sentry.ts';
|
||||||
import '@/nostr-wasm.ts';
|
import '@/nostr-wasm.ts';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { NostrEvent, NostrMetadata, NSchema as n, NStore } from '@nostrify/nostrify';
|
import { NostrEvent, NostrMetadata, NSchema as n, NStore } from '@nostrify/nostrify';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { serverMetaSchema } from '@/schemas/nostr.ts';
|
import { screenshotsSchema, serverMetaSchema } from '@/schemas/nostr.ts';
|
||||||
|
|
||||||
/** Like NostrMetadata, but some fields are required and also contains some extra fields. */
|
/** Like NostrMetadata, but some fields are required and also contains some extra fields. */
|
||||||
export interface InstanceMetadata extends NostrMetadata {
|
export interface InstanceMetadata extends NostrMetadata {
|
||||||
|
|
@ -11,6 +12,7 @@ export interface InstanceMetadata extends NostrMetadata {
|
||||||
picture: string;
|
picture: string;
|
||||||
tagline: string;
|
tagline: string;
|
||||||
event?: NostrEvent;
|
event?: NostrEvent;
|
||||||
|
screenshots: z.infer<typeof screenshotsSchema>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get and parse instance metadata from the kind 0 of the admin user. */
|
/** Get and parse instance metadata from the kind 0 of the admin user. */
|
||||||
|
|
@ -34,5 +36,6 @@ export async function getInstanceMetadata(store: NStore, signal?: AbortSignal):
|
||||||
email: meta.email ?? `postmaster@${Conf.url.host}`,
|
email: meta.email ?? `postmaster@${Conf.url.host}`,
|
||||||
picture: meta.picture ?? Conf.local('/images/thumbnail.png'),
|
picture: meta.picture ?? Conf.local('/images/thumbnail.png'),
|
||||||
event,
|
event,
|
||||||
|
screenshots: meta.screenshots ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
|
import { Stickynotes } from '@soapbox/stickynotes';
|
||||||
|
import { crypto } from '@std/crypto';
|
||||||
|
import { encodeHex } from '@std/encoding/hex';
|
||||||
|
import { encode } from 'blurhash';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
|
||||||
import { AppContext } from '@/app.ts';
|
import { AppContext } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
||||||
|
|
||||||
|
const console = new Stickynotes('ditto:uploader');
|
||||||
|
|
||||||
interface FileMeta {
|
interface FileMeta {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
@ -36,6 +43,53 @@ export async function uploadFile(
|
||||||
tags.push(['alt', description]);
|
tags.push(['alt', description]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const x = tags.find(([key]) => key === 'x')?.[1];
|
||||||
|
const m = tags.find(([key]) => key === 'm')?.[1];
|
||||||
|
const dim = tags.find(([key]) => key === 'dim')?.[1];
|
||||||
|
const size = tags.find(([key]) => key === 'size')?.[1];
|
||||||
|
const blurhash = tags.find(([key]) => key === 'blurhash')?.[1];
|
||||||
|
|
||||||
|
if (!x) {
|
||||||
|
const sha256 = encodeHex(await crypto.subtle.digest('SHA-256', file.stream()));
|
||||||
|
tags.push(['x', sha256]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m) {
|
||||||
|
tags.push(['m', file.type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!size) {
|
||||||
|
tags.push(['size', file.size.toString()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the uploader didn't already, try to get a blurhash and media dimensions.
|
||||||
|
// This requires `MEDIA_ANALYZE=true` to be configured because it comes with security tradeoffs.
|
||||||
|
if (Conf.mediaAnalyze && (!blurhash || !dim)) {
|
||||||
|
try {
|
||||||
|
const bytes = await new Response(file.stream()).bytes();
|
||||||
|
const img = sharp(bytes);
|
||||||
|
|
||||||
|
const { width, height } = await img.metadata();
|
||||||
|
|
||||||
|
if (!dim && (width && height)) {
|
||||||
|
tags.push(['dim', `${width}x${height}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blurhash && (width && height)) {
|
||||||
|
const pixels = await img
|
||||||
|
.raw()
|
||||||
|
.ensureAlpha()
|
||||||
|
.toBuffer({ resolveWithObject: false })
|
||||||
|
.then((buffer) => new Uint8ClampedArray(buffer));
|
||||||
|
|
||||||
|
const blurhash = encode(pixels, width, height, 4, 4);
|
||||||
|
tags.push(['blurhash', blurhash]);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error parsing image metadata: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const upload = {
|
const upload = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
url,
|
url,
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import { fetchWorker } from '@/workers/fetch.ts';
|
||||||
Deno.test({
|
Deno.test({
|
||||||
name: 'fetchWorker',
|
name: 'fetchWorker',
|
||||||
async fn() {
|
async fn() {
|
||||||
const response = await fetchWorker('http://httpbin.org/get');
|
const response = await fetchWorker('https://httpbingo.org/get');
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
assertEquals(json.headers.Host, 'httpbin.org');
|
assertEquals(json.headers.Host, ['httpbingo.org']);
|
||||||
},
|
},
|
||||||
sanitizeResources: false,
|
sanitizeResources: false,
|
||||||
});
|
});
|
||||||
|
|
@ -19,7 +19,7 @@ Deno.test({
|
||||||
const signal = controller.signal;
|
const signal = controller.signal;
|
||||||
|
|
||||||
setTimeout(() => controller.abort(), 100);
|
setTimeout(() => controller.abort(), 100);
|
||||||
assertRejects(() => fetchWorker('http://httpbin.org/delay/10', { signal }));
|
assertRejects(() => fetchWorker('https://httpbingo.org/delay/10', { signal }));
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
signal.addEventListener('abort', () => resolve(), { once: true });
|
signal.addEventListener('abort', () => resolve(), { once: true });
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
/// <reference lib="webworker" />
|
/// <reference lib="webworker" />
|
||||||
|
|
||||||
import Debug from '@soapbox/stickynotes/debug';
|
import { safeFetch } from '@soapbox/safe-fetch';
|
||||||
|
import { Stickynotes } from '@soapbox/stickynotes';
|
||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
|
|
||||||
import '@/workers/handlers/abortsignal.ts';
|
import '@/workers/handlers/abortsignal.ts';
|
||||||
import '@/sentry.ts';
|
import '@/sentry.ts';
|
||||||
|
|
||||||
const debug = Debug('ditto:fetch.worker');
|
const console = new Stickynotes('ditto:fetch.worker');
|
||||||
|
|
||||||
export const FetchWorker = {
|
export const FetchWorker = {
|
||||||
async fetch(
|
async fetch(
|
||||||
|
|
@ -14,8 +15,8 @@ export const FetchWorker = {
|
||||||
init: Omit<RequestInit, 'signal'>,
|
init: Omit<RequestInit, 'signal'>,
|
||||||
signal: AbortSignal | null | undefined,
|
signal: AbortSignal | null | undefined,
|
||||||
): Promise<[BodyInit, ResponseInit]> {
|
): Promise<[BodyInit, ResponseInit]> {
|
||||||
debug(init.method, url);
|
console.debug(init.method, url);
|
||||||
const response = await fetch(url, { ...init, signal });
|
const response = await safeFetch(url, { ...init, signal });
|
||||||
return [
|
return [
|
||||||
await response.arrayBuffer(),
|
await response.arrayBuffer(),
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,15 @@ class PolicyWorker implements NPolicy {
|
||||||
{
|
{
|
||||||
type: 'module',
|
type: 'module',
|
||||||
name: 'PolicyWorker',
|
name: 'PolicyWorker',
|
||||||
// FIXME: Disabled until Deno 2.0 adds support for `import` permission here.
|
deno: {
|
||||||
// https://github.com/denoland/deno/issues/26074
|
permissions: {
|
||||||
// deno: {
|
read: [Conf.denoDir, Conf.policy, Conf.dataDir],
|
||||||
// permissions: {
|
write: [Conf.dataDir],
|
||||||
// read: [Conf.denoDir, Conf.policy, Conf.dataDir],
|
net: 'inherit',
|
||||||
// write: [Conf.dataDir],
|
env: false,
|
||||||
// net: 'inherit',
|
import: true,
|
||||||
// env: false,
|
},
|
||||||
// },
|
},
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import 'deno-safe-fetch/load';
|
import '@soapbox/safe-fetch/load';
|
||||||
import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify';
|
import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify';
|
||||||
import { ReadOnlyPolicy } from '@nostrify/policies';
|
import { ReadOnlyPolicy } from '@nostrify/policies';
|
||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue