diff --git a/deno.json b/deno.json index 77acaf66..2409e978 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,5 @@ { "$schema": "https://deno.land/x/deno@v1.41.0/cli/schemas/config-file.v1.json", - "lock": false, "tasks": { "start": "deno run -A src/server.ts", "dev": "deno run -A --watch src/server.ts", @@ -51,7 +50,7 @@ "linkifyjs": "npm:linkifyjs@^4.1.1", "lru-cache": "npm:lru-cache@^10.2.2", "nostr-relaypool": "npm:nostr-relaypool2@0.6.34", - "nostr-tools": "npm:nostr-tools@^2.5.1", + "nostr-tools": "npm:nostr-tools@2.5.1", "nostr-wasm": "npm:nostr-wasm@^0.1.0", "tldts": "npm:tldts@^6.0.14", "tseep": "npm:tseep@^1.2.1", diff --git a/deno.lock b/deno.lock new file mode 100644 index 00000000..8410bc50 --- /dev/null +++ b/deno.lock @@ -0,0 +1,1193 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@bradenmacdonald/s3-lite-client@^0.7.4": "jsr:@bradenmacdonald/s3-lite-client@0.7.6", + "jsr:@db/sqlite@^0.11.1": "jsr:@db/sqlite@0.11.1", + "jsr:@denosaurs/plug@1": "jsr:@denosaurs/plug@1.0.6", + "jsr:@gleasonator/policy": "jsr:@gleasonator/policy@0.2.0", + "jsr:@nostrify/nostrify@^0.22.1": "jsr:@nostrify/nostrify@0.22.4", + "jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", + "jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0", + "jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0", + "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", + "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", + "jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0", + "jsr:@std/assert@^0.225.1": "jsr:@std/assert@0.225.3", + "jsr:@std/bytes@^0.224.0": "jsr:@std/bytes@0.224.0", + "jsr:@std/crypto@^0.224.0": "jsr:@std/crypto@0.224.0", + "jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.0", + "jsr:@std/encoding@^0.221.0": "jsr:@std/encoding@0.221.0", + "jsr:@std/encoding@^0.224.0": "jsr:@std/encoding@0.224.3", + "jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3", + "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", + "jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0", + "jsr:@std/io@^0.224": "jsr:@std/io@0.224.0", + "jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1", + "jsr:@std/path@0.217": "jsr:@std/path@0.217.0", + "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", + "npm:@isaacs/ttlcache@^1.4.1": "npm:@isaacs/ttlcache@1.4.1", + "npm:@noble/hashes@^1.4.0": "npm:@noble/hashes@1.4.0", + "npm:@scure/base@^1.1.6": "npm:@scure/base@1.1.6", + "npm:@scure/bip32@^1.4.0": "npm:@scure/bip32@1.4.0", + "npm:@scure/bip39@^1.3.0": "npm:@scure/bip39@1.3.0", + "npm:@types/node": "npm:@types/node@18.16.19", + "npm:comlink@^4.4.1": "npm:comlink@4.4.1", + "npm:entities@^4.5.0": "npm:entities@4.5.0", + "npm:fast-stable-stringify@^1.0.0": "npm:fast-stable-stringify@1.0.0", + "npm:formdata-helper@^0.3.0": "npm:formdata-helper@0.3.0", + "npm:iso-639-1@2.1.15": "npm:iso-639-1@2.1.15", + "npm:isomorphic-dompurify@^2.11.0": "npm:isomorphic-dompurify@2.11.0", + "npm:kysely@^0.27.2": "npm:kysely@0.27.3", + "npm:kysely@^0.27.3": "npm:kysely@0.27.3", + "npm:linkify-plugin-hashtag@^4.1.1": "npm:linkify-plugin-hashtag@4.1.3_linkifyjs@4.1.3", + "npm:linkify-string@^4.1.1": "npm:linkify-string@4.1.3_linkifyjs@4.1.3", + "npm:linkifyjs@^4.1.1": "npm:linkifyjs@4.1.3", + "npm:lint-staged": "npm:lint-staged@15.2.2", + "npm:lru-cache@^10.2.0": "npm:lru-cache@10.2.2", + "npm:lru-cache@^10.2.2": "npm:lru-cache@10.2.2", + "npm:nostr-relaypool2@0.6.34": "npm:nostr-relaypool2@0.6.34", + "npm:nostr-tools@2.5.1": "npm:nostr-tools@2.5.1", + "npm:nostr-tools@^2.5.0": "npm:nostr-tools@2.5.1", + "npm:nostr-wasm@^0.1.0": "npm:nostr-wasm@0.1.0", + "npm:tldts@^6.0.14": "npm:tldts@6.1.18", + "npm:type-fest@^4.3.0": "npm:type-fest@4.18.2", + "npm:unfurl.js@^6.4.0": "npm:unfurl.js@6.4.0", + "npm:websocket-ts@^2.1.5": "npm:websocket-ts@2.1.5", + "npm:zod@^3.23.8": "npm:zod@3.23.8" + }, + "jsr": { + "@bradenmacdonald/s3-lite-client@0.7.6": { + "integrity": "2b5976dca95d207dc88e23f9807e3eecbc441b0cf547dcda5784afe6668404d1", + "dependencies": [ + "jsr:@std/io@^0.224" + ] + }, + "@db/sqlite@0.11.1": { + "integrity": "546434e7ed762db07e6ade0f963540dd5e06723b802937bf260ff855b21ef9c5", + "dependencies": [ + "jsr:@denosaurs/plug@1", + "jsr:@std/path@0.217" + ] + }, + "@denosaurs/plug@1.0.6": { + "integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7", + "dependencies": [ + "jsr:@std/encoding@^0.221.0", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@gleasonator/policy@0.2.0": { + "integrity": "3fe58b853ab203b2b67e65b64391dbcf5c07bc1caaf46e97b2f8ed5b14f30fdf", + "dependencies": [ + "jsr:@nostrify/nostrify@^0.22.1" + ] + }, + "@nostrify/nostrify@0.22.4": { + "integrity": "1c8a7847e5773213044b491e85fd7cafae2ad194ce59da4d957d2b27c776b42d", + "dependencies": [ + "jsr:@std/encoding@^0.224.1", + "npm:@noble/hashes@^1.4.0", + "npm:@scure/base@^1.1.6", + "npm:@scure/bip32@^1.4.0", + "npm:@scure/bip39@^1.3.0", + "npm:kysely@^0.27.3", + "npm:lru-cache@^10.2.0", + "npm:nostr-tools@^2.5.0", + "npm:websocket-ts@^2.1.5", + "npm:zod@^3.23.8" + ] + }, + "@soapbox/kysely-deno-sqlite@2.2.0": { + "integrity": "668ec94600bc4b4d7bd618dd7ca65d4ef30ee61c46ffcb379b6f45203c08517a", + "dependencies": [ + "npm:kysely@^0.27.2" + ] + }, + "@soapbox/stickynotes@0.4.0": { + "integrity": "60bfe61ab3d7e04bf708273b1e2d391a59534bdf29e54160e98d7afd328ca1ec" + }, + "@std/assert@0.217.0": { + "integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642" + }, + "@std/assert@0.221.0": { + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" + }, + "@std/assert@0.224.0": { + "integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f" + }, + "@std/assert@0.225.3": { + "integrity": "b3c2847aecf6955b50644cdb9cf072004ea3d1998dd7579fc0acb99dbb23bd4f", + "dependencies": [ + "jsr:@std/internal@^1.0.0" + ] + }, + "@std/bytes@0.224.0": { + "integrity": "a2250e1d0eb7d1c5a426f21267ab9bdeac2447fa87a3d0d1a467d3f7a6058e49" + }, + "@std/crypto@0.224.0": { + "integrity": "154ef3ff08ef535562ef1a718718c5b2c5fc3808f0f9100daad69e829bfcdf2d", + "dependencies": [ + "jsr:@std/assert@^0.224.0", + "jsr:@std/encoding@^0.224.0" + ] + }, + "@std/dotenv@0.224.0": { + "integrity": "d9234cdf551507dcda60abb6c474289843741d8c07ee8ce540c60f5c1b220a1d" + }, + "@std/encoding@0.221.0": { + "integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45" + }, + "@std/encoding@0.224.3": { + "integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf" + }, + "@std/fmt@0.221.0": { + "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + }, + "@std/fs@0.221.0": { + "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@std/internal@1.0.0": { + "integrity": "ac6a6dfebf838582c4b4f61a6907374e27e05bedb6ce276e0f1608fe84e7cd9a" + }, + "@std/io@0.224.0": { + "integrity": "0aff885d21d829c050b8a08b1d71b54aed5841aecf227f8d77e99ec529a11e8e", + "dependencies": [ + "jsr:@std/bytes@^0.224.0" + ] + }, + "@std/media-types@0.224.1": { + "integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1" + }, + "@std/path@0.217.0": { + "integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11", + "dependencies": [ + "jsr:@std/assert@^0.217.0" + ] + }, + "@std/path@0.221.0": { + "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "dependencies": [ + "jsr:@std/assert@^0.221.0" + ] + } + }, + "npm": { + "@isaacs/ttlcache@1.4.1": { + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "dependencies": {} + }, + "@noble/ciphers@0.2.0": { + "integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==", + "dependencies": {} + }, + "@noble/ciphers@0.5.3": { + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "dependencies": {} + }, + "@noble/curves@1.1.0": { + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "@noble/hashes@1.3.1" + } + }, + "@noble/curves@1.2.0": { + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "@noble/hashes@1.3.2" + } + }, + "@noble/curves@1.4.0": { + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "@noble/hashes@1.4.0" + } + }, + "@noble/hashes@1.3.1": { + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "dependencies": {} + }, + "@noble/hashes@1.3.2": { + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dependencies": {} + }, + "@noble/hashes@1.4.0": { + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dependencies": {} + }, + "@scure/base@1.1.1": { + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "dependencies": {} + }, + "@scure/base@1.1.6": { + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "dependencies": {} + }, + "@scure/bip32@1.3.1": { + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": { + "@noble/curves": "@noble/curves@1.1.0", + "@noble/hashes": "@noble/hashes@1.3.2", + "@scure/base": "@scure/base@1.1.6" + } + }, + "@scure/bip32@1.4.0": { + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dependencies": { + "@noble/curves": "@noble/curves@1.4.0", + "@noble/hashes": "@noble/hashes@1.4.0", + "@scure/base": "@scure/base@1.1.6" + } + }, + "@scure/bip39@1.2.1": { + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": { + "@noble/hashes": "@noble/hashes@1.3.2", + "@scure/base": "@scure/base@1.1.6" + } + }, + "@scure/bip39@1.3.0": { + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dependencies": { + "@noble/hashes": "@noble/hashes@1.4.0", + "@scure/base": "@scure/base@1.1.6" + } + }, + "@types/dompurify@3.0.5": { + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dependencies": { + "@types/trusted-types": "@types/trusted-types@2.0.7" + } + }, + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + }, + "@types/trusted-types@2.0.7": { + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dependencies": {} + }, + "agent-base@7.1.1": { + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "debug@4.3.4" + } + }, + "ansi-escapes@6.2.0": { + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dependencies": { + "type-fest": "type-fest@3.13.1" + } + }, + "ansi-regex@6.0.1": { + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dependencies": {} + }, + "ansi-styles@6.2.1": { + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dependencies": {} + }, + "asynckit@0.4.0": { + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dependencies": {} + }, + "braces@3.0.2": { + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "fill-range@7.0.1" + } + }, + "chalk@5.3.0": { + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dependencies": {} + }, + "cli-cursor@4.0.0": { + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "restore-cursor@4.0.0" + } + }, + "cli-truncate@4.0.0": { + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dependencies": { + "slice-ansi": "slice-ansi@5.0.0", + "string-width": "string-width@7.1.0" + } + }, + "colorette@2.0.20": { + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dependencies": {} + }, + "combined-stream@1.0.8": { + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "delayed-stream@1.0.0" + } + }, + "comlink@4.4.1": { + "integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==", + "dependencies": {} + }, + "commander@11.1.0": { + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dependencies": {} + }, + "cross-spawn@7.0.3": { + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "path-key@3.1.1", + "shebang-command": "shebang-command@2.0.0", + "which": "which@2.0.2" + } + }, + "cssstyle@4.0.1": { + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dependencies": { + "rrweb-cssom": "rrweb-cssom@0.6.0" + } + }, + "data-urls@5.0.0": { + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "whatwg-mimetype@4.0.0", + "whatwg-url": "whatwg-url@14.0.0" + } + }, + "debug@3.2.7": { + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "ms@2.1.3" + } + }, + "debug@4.3.4": { + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "ms@2.1.2" + } + }, + "decimal.js@10.4.3": { + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dependencies": {} + }, + "delayed-stream@1.0.0": { + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dependencies": {} + }, + "dom-serializer@2.0.0": { + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "domelementtype@2.3.0", + "domhandler": "domhandler@5.0.3", + "entities": "entities@4.5.0" + } + }, + "domelementtype@2.3.0": { + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dependencies": {} + }, + "domhandler@5.0.3": { + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "domelementtype@2.3.0" + } + }, + "dompurify@3.1.4": { + "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==", + "dependencies": {} + }, + "domutils@3.1.0": { + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "dom-serializer@2.0.0", + "domelementtype": "domelementtype@2.3.0", + "domhandler": "domhandler@5.0.3" + } + }, + "emoji-regex@10.3.0": { + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dependencies": {} + }, + "entities@4.5.0": { + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dependencies": {} + }, + "eventemitter3@5.0.1": { + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dependencies": {} + }, + "execa@8.0.1": { + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "cross-spawn@7.0.3", + "get-stream": "get-stream@8.0.1", + "human-signals": "human-signals@5.0.0", + "is-stream": "is-stream@3.0.0", + "merge-stream": "merge-stream@2.0.0", + "npm-run-path": "npm-run-path@5.3.0", + "onetime": "onetime@6.0.0", + "signal-exit": "signal-exit@4.1.0", + "strip-final-newline": "strip-final-newline@3.0.0" + } + }, + "fast-stable-stringify@1.0.0": { + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "dependencies": {} + }, + "fill-range@7.0.1": { + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "to-regex-range@5.0.1" + } + }, + "form-data@4.0.0": { + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "asynckit@0.4.0", + "combined-stream": "combined-stream@1.0.8", + "mime-types": "mime-types@2.1.35" + } + }, + "formdata-helper@0.3.0": { + "integrity": "sha512-QkRUFbNgWSu9lkc5TKLWri0ilTFowo950w13I5pRhj4cUxzMLuz0MIhGbE/gIRyfsZQoFeMNN0h06OCSOgfhUg==", + "dependencies": {} + }, + "get-east-asian-width@1.2.0": { + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dependencies": {} + }, + "get-stream@8.0.1": { + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dependencies": {} + }, + "he@1.2.0": { + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dependencies": {} + }, + "html-encoding-sniffer@4.0.0": { + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "whatwg-encoding@3.1.1" + } + }, + "htmlparser2@8.0.2": { + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dependencies": { + "domelementtype": "domelementtype@2.3.0", + "domhandler": "domhandler@5.0.3", + "domutils": "domutils@3.1.0", + "entities": "entities@4.5.0" + } + }, + "http-proxy-agent@7.0.2": { + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "agent-base@7.1.1", + "debug": "debug@4.3.4" + } + }, + "https-proxy-agent@7.0.4": { + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "agent-base@7.1.1", + "debug": "debug@4.3.4" + } + }, + "human-signals@5.0.0": { + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dependencies": {} + }, + "iconv-lite@0.4.24": { + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": "safer-buffer@2.1.2" + } + }, + "iconv-lite@0.6.3": { + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": "safer-buffer@2.1.2" + } + }, + "is-fullwidth-code-point@4.0.0": { + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dependencies": {} + }, + "is-fullwidth-code-point@5.0.0": { + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dependencies": { + "get-east-asian-width": "get-east-asian-width@1.2.0" + } + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dependencies": {} + }, + "is-potential-custom-element-name@1.0.1": { + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dependencies": {} + }, + "is-stream@3.0.0": { + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dependencies": {} + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dependencies": {} + }, + "iso-639-1@2.1.15": { + "integrity": "sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==", + "dependencies": {} + }, + "isomorphic-dompurify@2.11.0": { + "integrity": "sha512-PNGGCbbSH7+zF45UKu4Kh+yI8hm1bWA8kIZQow4KMImnjYQtrqJA0ZmwHamYUU7+M5tQ84z7xXMWmZF/v5t5eA==", + "dependencies": { + "@types/dompurify": "@types/dompurify@3.0.5", + "dompurify": "dompurify@3.1.4", + "jsdom": "jsdom@24.0.0" + } + }, + "isomorphic-ws@5.0.0_ws@8.17.0": { + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dependencies": { + "ws": "ws@8.17.0" + } + }, + "jsdom@24.0.0": { + "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", + "dependencies": { + "cssstyle": "cssstyle@4.0.1", + "data-urls": "data-urls@5.0.0", + "decimal.js": "decimal.js@10.4.3", + "form-data": "form-data@4.0.0", + "html-encoding-sniffer": "html-encoding-sniffer@4.0.0", + "http-proxy-agent": "http-proxy-agent@7.0.2", + "https-proxy-agent": "https-proxy-agent@7.0.4", + "is-potential-custom-element-name": "is-potential-custom-element-name@1.0.1", + "nwsapi": "nwsapi@2.2.10", + "parse5": "parse5@7.1.2", + "rrweb-cssom": "rrweb-cssom@0.6.0", + "saxes": "saxes@6.0.0", + "symbol-tree": "symbol-tree@3.2.4", + "tough-cookie": "tough-cookie@4.1.4", + "w3c-xmlserializer": "w3c-xmlserializer@5.0.0", + "webidl-conversions": "webidl-conversions@7.0.0", + "whatwg-encoding": "whatwg-encoding@3.1.1", + "whatwg-mimetype": "whatwg-mimetype@4.0.0", + "whatwg-url": "whatwg-url@14.0.0", + "ws": "ws@8.17.0", + "xml-name-validator": "xml-name-validator@5.0.0" + } + }, + "kysely@0.27.3": { + "integrity": "sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==", + "dependencies": {} + }, + "lilconfig@3.0.0": { + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dependencies": {} + }, + "linkify-plugin-hashtag@4.1.3_linkifyjs@4.1.3": { + "integrity": "sha512-sq627UTrmmDhVnYoUbj/EFfSrhGBvAZYIUdUCjtLeW/AWBV7g9NX9JXEglAuJ7DIyJ84Ged0EHOe+xCXRe2Gmw==", + "dependencies": { + "linkifyjs": "linkifyjs@4.1.3" + } + }, + "linkify-string@4.1.3_linkifyjs@4.1.3": { + "integrity": "sha512-6dAgx4MiTcvEX87OS5aNpAioO7cSELUXp61k7azOvMYOLSmREx0w4yM1Uf0+O3JLC08YdkUyZhAX+YkasRt/mw==", + "dependencies": { + "linkifyjs": "linkifyjs@4.1.3" + } + }, + "linkifyjs@4.1.3": { + "integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==", + "dependencies": {} + }, + "lint-staged@15.2.2": { + "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", + "dependencies": { + "chalk": "chalk@5.3.0", + "commander": "commander@11.1.0", + "debug": "debug@4.3.4", + "execa": "execa@8.0.1", + "lilconfig": "lilconfig@3.0.0", + "listr2": "listr2@8.0.1", + "micromatch": "micromatch@4.0.5", + "pidtree": "pidtree@0.6.0", + "string-argv": "string-argv@0.3.2", + "yaml": "yaml@2.3.4" + } + }, + "listr2@8.0.1": { + "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "dependencies": { + "cli-truncate": "cli-truncate@4.0.0", + "colorette": "colorette@2.0.20", + "eventemitter3": "eventemitter3@5.0.1", + "log-update": "log-update@6.0.0", + "rfdc": "rfdc@1.3.1", + "wrap-ansi": "wrap-ansi@9.0.0" + } + }, + "log-update@6.0.0": { + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "dependencies": { + "ansi-escapes": "ansi-escapes@6.2.0", + "cli-cursor": "cli-cursor@4.0.0", + "slice-ansi": "slice-ansi@7.1.0", + "strip-ansi": "strip-ansi@7.1.0", + "wrap-ansi": "wrap-ansi@9.0.0" + } + }, + "lru-cache@10.2.2": { + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dependencies": {} + }, + "merge-stream@2.0.0": { + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dependencies": {} + }, + "micromatch@4.0.5": { + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "braces@3.0.2", + "picomatch": "picomatch@2.3.1" + } + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dependencies": {} + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "mime-db@1.52.0" + } + }, + "mimic-fn@2.1.0": { + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dependencies": {} + }, + "mimic-fn@4.0.0": { + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dependencies": {} + }, + "ms@2.1.2": { + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dependencies": {} + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dependencies": {} + }, + "node-fetch@2.7.0": { + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "whatwg-url@5.0.0" + } + }, + "nostr-relaypool2@0.6.34": { + "integrity": "sha512-e3FDh9w/wQkY513mvoJps1Hc/Y5wiWXeBM6MD+YKSyAg+px+/8uHSSHAuHhlavw7oOEOvEsIGlMDMc57DG3MOA==", + "dependencies": { + "isomorphic-ws": "isomorphic-ws@5.0.0_ws@8.17.0", + "nostr-tools": "nostr-tools@1.17.0", + "safe-stable-stringify": "safe-stable-stringify@2.4.3" + } + }, + "nostr-tools@1.17.0": { + "integrity": "sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw==", + "dependencies": { + "@noble/ciphers": "@noble/ciphers@0.2.0", + "@noble/curves": "@noble/curves@1.1.0", + "@noble/hashes": "@noble/hashes@1.3.1", + "@scure/base": "@scure/base@1.1.1", + "@scure/bip32": "@scure/bip32@1.3.1", + "@scure/bip39": "@scure/bip39@1.2.1" + } + }, + "nostr-tools@2.5.1": { + "integrity": "sha512-bpkhGGAhdiCN0irfV+xoH3YP5CQeOXyXzUq7SYeM6D56xwTXZCPEmBlUGqFVfQidvRsoVeVxeAiOXW2c2HxoRQ==", + "dependencies": { + "@noble/ciphers": "@noble/ciphers@0.5.3", + "@noble/curves": "@noble/curves@1.2.0", + "@noble/hashes": "@noble/hashes@1.3.1", + "@scure/base": "@scure/base@1.1.1", + "@scure/bip32": "@scure/bip32@1.3.1", + "@scure/bip39": "@scure/bip39@1.2.1", + "nostr-wasm": "nostr-wasm@0.1.0" + } + }, + "nostr-wasm@0.1.0": { + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "dependencies": {} + }, + "npm-run-path@5.3.0": { + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dependencies": { + "path-key": "path-key@4.0.0" + } + }, + "nwsapi@2.2.10": { + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", + "dependencies": {} + }, + "onetime@5.1.2": { + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "mimic-fn@2.1.0" + } + }, + "onetime@6.0.0": { + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "mimic-fn@4.0.0" + } + }, + "parse5@7.1.2": { + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "entities@4.5.0" + } + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dependencies": {} + }, + "path-key@4.0.0": { + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dependencies": {} + }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dependencies": {} + }, + "pidtree@0.6.0": { + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dependencies": {} + }, + "psl@1.9.0": { + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dependencies": {} + }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dependencies": {} + }, + "querystringify@2.2.0": { + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dependencies": {} + }, + "requires-port@1.0.0": { + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dependencies": {} + }, + "restore-cursor@4.0.0": { + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "onetime@5.1.2", + "signal-exit": "signal-exit@3.0.7" + } + }, + "rfdc@1.3.1": { + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dependencies": {} + }, + "rrweb-cssom@0.6.0": { + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dependencies": {} + }, + "safe-stable-stringify@2.4.3": { + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dependencies": {} + }, + "safer-buffer@2.1.2": { + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dependencies": {} + }, + "saxes@6.0.0": { + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "xmlchars@2.2.0" + } + }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "shebang-regex@3.0.0" + } + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dependencies": {} + }, + "signal-exit@3.0.7": { + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dependencies": {} + }, + "signal-exit@4.1.0": { + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dependencies": {} + }, + "slice-ansi@5.0.0": { + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dependencies": { + "ansi-styles": "ansi-styles@6.2.1", + "is-fullwidth-code-point": "is-fullwidth-code-point@4.0.0" + } + }, + "slice-ansi@7.1.0": { + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dependencies": { + "ansi-styles": "ansi-styles@6.2.1", + "is-fullwidth-code-point": "is-fullwidth-code-point@5.0.0" + } + }, + "string-argv@0.3.2": { + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dependencies": {} + }, + "string-width@7.1.0": { + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dependencies": { + "emoji-regex": "emoji-regex@10.3.0", + "get-east-asian-width": "get-east-asian-width@1.2.0", + "strip-ansi": "strip-ansi@7.1.0" + } + }, + "strip-ansi@7.1.0": { + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "ansi-regex@6.0.1" + } + }, + "strip-final-newline@3.0.0": { + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dependencies": {} + }, + "symbol-tree@3.2.4": { + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dependencies": {} + }, + "tldts-core@6.1.18": { + "integrity": "sha512-e4wx32F/7dMBSZyKAx825Yte3U0PQtZZ0bkWxYQiwLteRVnQ5zM40fEbi0IyNtwQssgJAk3GCr7Q+w39hX0VKA==", + "dependencies": {} + }, + "tldts@6.1.18": { + "integrity": "sha512-F+6zjPFnFxZ0h6uGb8neQWwHQm8u3orZVFribsGq4eBgEVrzSkHxzWS2l6aKr19T1vXiOMFjqfff4fQt+WgJFg==", + "dependencies": { + "tldts-core": "tldts-core@6.1.18" + } + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "is-number@7.0.0" + } + }, + "tough-cookie@4.1.4": { + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "psl@1.9.0", + "punycode": "punycode@2.3.1", + "universalify": "universalify@0.2.0", + "url-parse": "url-parse@1.5.10" + } + }, + "tr46@0.0.3": { + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dependencies": {} + }, + "tr46@5.0.0": { + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "punycode@2.3.1" + } + }, + "type-fest@3.13.1": { + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dependencies": {} + }, + "type-fest@4.18.2": { + "integrity": "sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==", + "dependencies": {} + }, + "unfurl.js@6.4.0": { + "integrity": "sha512-DogJFWPkOWMcu2xPdpmbcsL+diOOJInD3/jXOv6saX1upnWmMK8ndAtDWUfJkuInqNI9yzADud4ID9T+9UeWCw==", + "dependencies": { + "debug": "debug@3.2.7", + "he": "he@1.2.0", + "htmlparser2": "htmlparser2@8.0.2", + "iconv-lite": "iconv-lite@0.4.24", + "node-fetch": "node-fetch@2.7.0" + } + }, + "universalify@0.2.0": { + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dependencies": {} + }, + "url-parse@1.5.10": { + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "querystringify@2.2.0", + "requires-port": "requires-port@1.0.0" + } + }, + "w3c-xmlserializer@5.0.0": { + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "xml-name-validator@5.0.0" + } + }, + "webidl-conversions@3.0.1": { + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dependencies": {} + }, + "webidl-conversions@7.0.0": { + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dependencies": {} + }, + "websocket-ts@2.1.5": { + "integrity": "sha512-rCNl9w6Hsir1azFm/pbjBEFzLD/gi7Th5ZgOxMifB6STUfTSovYAzryWw0TRvSZ1+Qu1Z5Plw4z42UfTNA9idA==", + "dependencies": {} + }, + "whatwg-encoding@3.1.1": { + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "iconv-lite@0.6.3" + } + }, + "whatwg-mimetype@4.0.0": { + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dependencies": {} + }, + "whatwg-url@14.0.0": { + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "tr46@5.0.0", + "webidl-conversions": "webidl-conversions@7.0.0" + } + }, + "whatwg-url@5.0.0": { + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "tr46@0.0.3", + "webidl-conversions": "webidl-conversions@3.0.1" + } + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "isexe@2.0.0" + } + }, + "wrap-ansi@9.0.0": { + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dependencies": { + "ansi-styles": "ansi-styles@6.2.1", + "string-width": "string-width@7.1.0", + "strip-ansi": "strip-ansi@7.1.0" + } + }, + "ws@8.17.0": { + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "dependencies": {} + }, + "xml-name-validator@5.0.0": { + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dependencies": {} + }, + "xmlchars@2.2.0": { + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dependencies": {} + }, + "yaml@2.3.4": { + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dependencies": {} + }, + "zod@3.23.8": { + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dependencies": {} + } + } + }, + "remote": { + "https://deno.land/std@0.160.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.160.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", + "https://deno.land/std@0.160.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06", + "https://deno.land/std@0.160.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0", + "https://deno.land/std@0.160.0/async/debounce.ts": "dc8b92d4a4fe7eac32c924f2b8d3e62112530db70cadce27042689d82970b350", + "https://deno.land/std@0.160.0/async/deferred.ts": "d8fb253ffde2a056e4889ef7e90f3928f28be9f9294b6505773d33f136aab4e6", + "https://deno.land/std@0.160.0/async/delay.ts": "0419dfc993752849692d1f9647edf13407c7facc3509b099381be99ffbc9d699", + "https://deno.land/std@0.160.0/async/mod.ts": "dd0a8ed4f3984ffabe2fcca7c9f466b7932d57b1864ffee148a5d5388316db6b", + "https://deno.land/std@0.160.0/async/mux_async_iterator.ts": "3447b28a2a582224a3d4d3596bccbba6e85040da3b97ed64012f7decce98d093", + "https://deno.land/std@0.160.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239", + "https://deno.land/std@0.160.0/async/tee.ts": "9af3a3e7612af75861308b52249e167f5ebc3dcfc8a1a4d45462d96606ee2b70", + "https://deno.land/std@0.160.0/bytes/bytes_list.ts": "aba5e2369e77d426b10af1de0dcc4531acecec27f9b9056f4f7bfbf8ac147ab4", + "https://deno.land/std@0.160.0/bytes/equals.ts": "3c3558c3ae85526f84510aa2b48ab2ad7bdd899e2e0f5b7a8ffc85acb3a6043a", + "https://deno.land/std@0.160.0/bytes/mod.ts": "b2e342fd3669176a27a4e15061e9d588b89c1aaf5008ab71766e23669565d179", + "https://deno.land/std@0.160.0/crypto/_fnv/fnv32.ts": "aa9bddead8c6345087d3abd4ef35fb9655622afc333fc41fff382b36e64280b5", + "https://deno.land/std@0.160.0/crypto/_fnv/fnv64.ts": "625d7e7505b6cb2e9801b5fd6ed0a89256bac12b2bbb3e4664b85a88b0ec5bef", + "https://deno.land/std@0.160.0/crypto/_fnv/index.ts": "a8f6a361b4c6d54e5e89c16098f99b6962a1dd6ad1307dbc97fa1ecac5d7060a", + "https://deno.land/std@0.160.0/crypto/_fnv/util.ts": "4848313bed7f00f55be3cb080aa0583fc007812ba965b03e4009665bde614ce3", + "https://deno.land/std@0.160.0/crypto/_wasm_crypto/lib/deno_std_wasm_crypto.generated.mjs": "258b484c2da27578bec61c01d4b62c21f72268d928d03c968c4eb590cb3bd830", + "https://deno.land/std@0.160.0/crypto/_wasm_crypto/mod.ts": "6c60d332716147ded0eece0861780678d51b560f533b27db2e15c64a4ef83665", + "https://deno.land/std@0.160.0/crypto/keystack.ts": "e481eed28007395e554a435e880fee83a5c73b9259ed8a135a75e4b1e4f381f7", + "https://deno.land/std@0.160.0/crypto/mod.ts": "fadedc013b4a86fda6305f1adc6d1c02225834d53cff5d95cc05f62b25127517", + "https://deno.land/std@0.160.0/crypto/timing_safe_equal.ts": "82a29b737bc8932d75d7a20c404136089d5d23629e94ba14efa98a8cc066c73e", + "https://deno.land/std@0.160.0/datetime/formatter.ts": "7c8e6d16a0950f400aef41b9f1eb9168249869776ec520265dfda785d746589e", + "https://deno.land/std@0.160.0/datetime/mod.ts": "ea927ca96dfb28c7b9a5eed5bdc7ac46bb9db38038c4922631895cea342fea87", + "https://deno.land/std@0.160.0/datetime/tokenizer.ts": "7381e28f6ab51cb504c7e132be31773d73ef2f3e1e50a812736962b9df1e8c47", + "https://deno.land/std@0.160.0/encoding/base64.ts": "c57868ca7fa2fbe919f57f88a623ad34e3d970d675bdc1ff3a9d02bba7409db2", + "https://deno.land/std@0.160.0/encoding/base64url.ts": "a5f82a9fa703bd85a5eb8e7c1296bc6529e601ebd9642cc2b5eaa6b38fa9e05a", + "https://deno.land/std@0.160.0/encoding/hex.ts": "4cc5324417cbb4ac9b828453d35aed45b9cc29506fad658f1f138d981ae33795", + "https://deno.land/std@0.160.0/fmt/colors.ts": "9e36a716611dcd2e4865adea9c4bec916b5c60caad4cdcdc630d4974e6bb8bd4", + "https://deno.land/std@0.160.0/io/buffer.ts": "fae02290f52301c4e0188670e730cd902f9307fb732d79c4aa14ebdc82497289", + "https://deno.land/std@0.160.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.160.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.160.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", + "https://deno.land/std@0.160.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.160.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.160.0/path/mod.ts": "56fec03ad0ebd61b6ab39ddb9b0ddb4c4a5c9f2f4f632e09dd37ec9ebfd722ac", + "https://deno.land/std@0.160.0/path/posix.ts": "6b63de7097e68c8663c84ccedc0fd977656eb134432d818ecd3a4e122638ac24", + "https://deno.land/std@0.160.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.160.0/path/win32.ts": "ee8826dce087d31c5c81cd414714e677eb68febc40308de87a2ce4b40e10fb8d", + "https://deno.land/std@0.160.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c", + "https://deno.land/std@0.160.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832", + "https://deno.land/std@0.160.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8", + "https://deno.land/x/hono@v3.10.1/adapter/deno/serve-static.ts": "ba10cf6aaf39da942b0d49c3b9877ddba69d41d414c6551d890beb1085f58eea", + "https://deno.land/x/hono@v3.10.1/client/client.ts": "ff340f58041203879972dd368b011ed130c66914f789826610869a90603406bf", + "https://deno.land/x/hono@v3.10.1/client/index.ts": "3ff4cf246f3543f827a85a2c84d66a025ac350ee927613629bda47e854bfb7ba", + "https://deno.land/x/hono@v3.10.1/client/utils.ts": "053273c002963b549d38268a1b423ac8ca211a8028bdab1ed0b781a62aa5e661", + "https://deno.land/x/hono@v3.10.1/compose.ts": "e8ab4b345aa367f2dd65f221c9fe829dd885326a613f4215b654f93a4066bb5c", + "https://deno.land/x/hono@v3.10.1/context.ts": "261cc8b8b1e8f04b98beab1cca6692f317b7dc6d2b75b4f84c982e54cf1db730", + "https://deno.land/x/hono@v3.10.1/helper/cookie/index.ts": "55ccd20bbd8d9a8bb2ecd998e90845c1d306c19027f54b3d1b89a5be35968b80", + "https://deno.land/x/hono@v3.10.1/helper/html/index.ts": "aba19e8d29f217c7fffa5719cf606c4e259b540d51296e82bbea3c992e2ecbc6", + "https://deno.land/x/hono@v3.10.1/hono-base.ts": "cc55e0a4c63a7bdf44df3e804ea4737d5399eeb6606b45d102f8e48c3ff1e925", + "https://deno.land/x/hono@v3.10.1/hono.ts": "2cc4c292e541463a4d6f83edbcea58048d203e9564ae62ec430a3d466b49a865", + "https://deno.land/x/hono@v3.10.1/http-exception.ts": "6071df078b5f76d279684d52fe82a590f447a64ffe1b75eb5064d0c8a8d2d676", + "https://deno.land/x/hono@v3.10.1/jsx/index.ts": "019512d3a9b3897b879e87fa5fb179cd34f3d326f8ff8b93379c2bb707ec168a", + "https://deno.land/x/hono@v3.10.1/jsx/streaming.ts": "5d03b4d02eaa396c8f0f33c3f6e8c7ed3afb7598283c2d4a7ddea0ada8c212a7", + "https://deno.land/x/hono@v3.10.1/middleware.ts": "57b2047c4b9d775a052a9c44a3b805802c1d1cb477ab9c4bb6185d27382d1b96", + "https://deno.land/x/hono@v3.10.1/middleware/basic-auth/index.ts": "5505288ccf9364f56f7be2dfac841543b72e20656e54ac646a1a73a0aa853261", + "https://deno.land/x/hono@v3.10.1/middleware/bearer-auth/index.ts": "d11fe14e0a3006f6d35c391e455fe20d8ece9561e48b6a5580e4b87dd491cd90", + "https://deno.land/x/hono@v3.10.1/middleware/cache/index.ts": "9e5d31d33206bb5dba46dde16ed606dd2cb361d75c26b02e02c72bd1fb6fe53e", + "https://deno.land/x/hono@v3.10.1/middleware/compress/index.ts": "85d315c9a942d7758e5c524dc94b736124646a56752e56c6e4284f3989b4692a", + "https://deno.land/x/hono@v3.10.1/middleware/cors/index.ts": "d481eba7e05d3448cd326d3dca8b9c7e16ecf0d27a37fd7d700485834123ae5e", + "https://deno.land/x/hono@v3.10.1/middleware/etag/index.ts": "4ad675e108dc98dccca0e9e35cd903701669a1aea676b8b51266c3b602e4d54c", + "https://deno.land/x/hono@v3.10.1/middleware/jsx-renderer/index.ts": "5352d6dda872d419ebafbd4d6b408f66ad473fc3d395d82327850c1e786d7344", + "https://deno.land/x/hono@v3.10.1/middleware/jwt/index.ts": "c6e02a94a3911299d21392b3b1f8710bda7cacf0d60db59c0e2f0d9fa8ff1a70", + "https://deno.land/x/hono@v3.10.1/middleware/logger/index.ts": "c139f372f482baeffbad68b14bef990e011fe8df578dcee71fb612ffad7fe748", + "https://deno.land/x/hono@v3.10.1/middleware/powered-by/index.ts": "c36b7a3d1322c6a37f3d1510f7ff04a85aa6cacfac2173e5f1913eb16c3cc869", + "https://deno.land/x/hono@v3.10.1/middleware/pretty-json/index.ts": "f6967ceecdb42c95ddd5e2e7bc8545d3e8bda111fa659f3f1336b2e6fe6b0bb0", + "https://deno.land/x/hono@v3.10.1/middleware/secure-headers/index.ts": "d2b8a7978e3d201ead5ac8fd22e3adc9094189aebcba0d9cd51b98773927a5d5", + "https://deno.land/x/hono@v3.10.1/middleware/timing/index.ts": "d6976a07d9d51a7b26dae1311fe51d0744f7d234498bac3fe024ec7088c0ca47", + "https://deno.land/x/hono@v3.10.1/mod.ts": "90114a97be9111b348129ad0143e764a64921f60dd03b8f3da529db98a0d3a82", + "https://deno.land/x/hono@v3.10.1/request.ts": "52330303dd7a3bf4f580fde0463ba608bc4c88a8b7b5edd7c1327064c7cf65ce", + "https://deno.land/x/hono@v3.10.1/router.ts": "39d573f48baee429810cd583c931dd44274273c30804d538c86967d310ea4ab5", + "https://deno.land/x/hono@v3.10.1/router/linear-router/index.ts": "8a2a7144c50b1f4a92d9ee99c2c396716af144c868e10608255f969695efccd0", + "https://deno.land/x/hono@v3.10.1/router/linear-router/router.ts": "bc63e8b5bc1dabc815306d50bebd1bb5877ffa3936ba2ad7550d093c95ee6bd1", + "https://deno.land/x/hono@v3.10.1/router/pattern-router/index.ts": "304a66c50e243872037ed41c7dd79ed0c89d815e17e172e7ad7cdc4bc07d3383", + "https://deno.land/x/hono@v3.10.1/router/pattern-router/router.ts": "a9a5a2a182cce8c3ae82139892cc0502be7dd8f579f31e76d0302b19b338e548", + "https://deno.land/x/hono@v3.10.1/router/reg-exp-router/index.ts": "52755829213941756159b7a963097bafde5cc4fc22b13b1c7c9184dc0512d1db", + "https://deno.land/x/hono@v3.10.1/router/reg-exp-router/node.ts": "5b3fb80411db04c65df066e69fedb2c8c0844753c2633d703336de84d569252c", + "https://deno.land/x/hono@v3.10.1/router/reg-exp-router/router.ts": "fbe8917aa24fe25d0208bfa82ce7f49ba0507f9ae158d4d0c177f6a061b0a561", + "https://deno.land/x/hono@v3.10.1/router/reg-exp-router/trie.ts": "852ce7207e6701e47fa30889a0d2b8bfcd56d8862c97e7bc9831e0a64bd8835f", + "https://deno.land/x/hono@v3.10.1/router/smart-router/index.ts": "74f9b4fe15ea535900f2b9b048581915f12fe94e531dd2b0032f5610e61c3bef", + "https://deno.land/x/hono@v3.10.1/router/smart-router/router.ts": "71979c06b32b093960a6e8efc4c185e558f280bff18846b8b1cdc757ade6ff99", + "https://deno.land/x/hono@v3.10.1/router/trie-router/index.ts": "3eb75e7f71ba81801631b30de6b1f5cefb2c7239c03797e2b2cbab5085911b41", + "https://deno.land/x/hono@v3.10.1/router/trie-router/node.ts": "3af15fa9c9994a8664a2b7a7c11233504b5bb9d4fcf7bb34cf30d7199052c39f", + "https://deno.land/x/hono@v3.10.1/router/trie-router/router.ts": "54ced78d35676302c8fcdda4204f7bdf5a7cc907fbf9967c75674b1e394f830d", + "https://deno.land/x/hono@v3.10.1/utils/body.ts": "7a16a6656331a96bcae57642f8d5e3912bd361cbbcc2c0d2157ecc3f218f7a92", + "https://deno.land/x/hono@v3.10.1/utils/buffer.ts": "9066a973e64498cb262c7e932f47eed525a51677b17f90893862b7279dc0773e", + "https://deno.land/x/hono@v3.10.1/utils/cookie.ts": "19920ba6756944aae1ad8585c3ddeaa9df479733f59d05359db096f7361e5e4b", + "https://deno.land/x/hono@v3.10.1/utils/crypto.ts": "bda0e141bbe46d3a4a20f8fbcb6380d473b617123d9fdfa93e4499410b537acc", + "https://deno.land/x/hono@v3.10.1/utils/encode.ts": "3b7c7d736123b5073542b34321700d4dbf5ff129c138f434bb2144a4d425ee89", + "https://deno.land/x/hono@v3.10.1/utils/filepath.ts": "18461b055a914d6da85077f453051b516281bb17cf64fa74bf5ef604dc9d2861", + "https://deno.land/x/hono@v3.10.1/utils/html.ts": "01c1520a4256f899da1954357cf63ae11c348eda141a505f72d7090cf5481aba", + "https://deno.land/x/hono@v3.10.1/utils/jwt/index.ts": "5e4b82a42eb3603351dfce726cd781ca41cb57437395409d227131aec348d2d5", + "https://deno.land/x/hono@v3.10.1/utils/jwt/jwt.ts": "02ff7bbf1298ffcc7a40266842f8eac44b6c136453e32d4441e24d0cbfba3a95", + "https://deno.land/x/hono@v3.10.1/utils/jwt/types.ts": "58ddf908f76ba18d9c62ddfc2d1e40cc2e306bf987409a6169287efa81ce2546", + "https://deno.land/x/hono@v3.10.1/utils/mime.ts": "0105d2b5e8e91f07acc70f5d06b388313995d62af23c802fcfba251f5a744d95", + "https://deno.land/x/hono@v3.10.1/utils/stream.ts": "1789dcc73c5b0ede28f83d7d34e47ae432c20e680907cb3275a9c9187f293983", + "https://deno.land/x/hono@v3.10.1/utils/url.ts": "5fc3307ef3cb2e6f34ec2a03e3d7f2126c6a9f5f0eab677222df3f0e40bd7567", + "https://deno.land/x/hono@v3.10.1/validator/index.ts": "6c986e8b91dcf857ecc8164a506ae8eea8665792a4ff7215471df669c632ae7c", + "https://deno.land/x/hono@v3.10.1/validator/validator.ts": "afa5e52495e0996fbba61996736fab5c486590d72d376f809e9f9ff4e0c463e9", + "https://deno.land/x/kysely_deno_postgres@v0.4.0/deps.ts": "7970f66a52a9fa0cef607cb7ef0171212af2ccb83e73ecfa7629aabc28a38793", + "https://deno.land/x/kysely_deno_postgres@v0.4.0/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de", + "https://deno.land/x/kysely_deno_postgres@v0.4.0/src/PostgreSQLDriver.ts": "590c2fa248cff38e6e0f623050983039b5fde61e9c7131593d2922fb1f0eb921", + "https://deno.land/x/kysely_deno_postgres@v0.4.0/src/PostgreSQLDriverDatabaseConnection.ts": "83cd176ca830407dbff8495140cba870d1a34b27075c91ef1d5dbf7bbe467c40", + "https://deno.land/x/postgres@v0.17.0/client.ts": "348779c9f6a1c75ef1336db662faf08dce7d2101ff72f0d1e341ba1505c8431d", + "https://deno.land/x/postgres@v0.17.0/client/error.ts": "0817583b666fd546664ed52c1d37beccc5a9eebcc6e3c2ead20ada99b681e5f7", + "https://deno.land/x/postgres@v0.17.0/connection/auth.ts": "1070125e2ac4ca4ade36d69a4222d37001903092826d313217987583edd61ce9", + "https://deno.land/x/postgres@v0.17.0/connection/connection.ts": "428ed3efa055870db505092b5d3545ef743497b7b4b72cf8f0593e7dd4788acd", + "https://deno.land/x/postgres@v0.17.0/connection/connection_params.ts": "52bfe90e8860f584b95b1b08c254dde97c3aa763c4b6bee0c80c5930e35459e0", + "https://deno.land/x/postgres@v0.17.0/connection/message.ts": "f9257948b7f87d58bfbfe3fc6e2e08f0de3ef885655904d56a5f73655cc22c5a", + "https://deno.land/x/postgres@v0.17.0/connection/message_code.ts": "466719008b298770c366c5c63f6cf8285b7f76514dadb4b11e7d9756a8a1ddbf", + "https://deno.land/x/postgres@v0.17.0/connection/packet.ts": "050aeff1fc13c9349e89451a155ffcd0b1343dc313a51f84439e3e45f64b56c8", + "https://deno.land/x/postgres@v0.17.0/connection/scram.ts": "0c7a2551fe7b1a1c62dd856b7714731a7e7534ccca10093336782d1bfc5b2bd2", + "https://deno.land/x/postgres@v0.17.0/deps.ts": "f47ccb41f7f97eaad455d94f407ef97146ae99443dbe782894422c869fbba69e", + "https://deno.land/x/postgres@v0.17.0/mod.ts": "a1e18fd9e6fedc8bc24e5aeec3ae6de45e2274be1411fb66e9081420c5e81d7d", + "https://deno.land/x/postgres@v0.17.0/pool.ts": "892db7b5e1787988babecc994a151ebbd7d017f080905cbe9c3d7b44a73032a9", + "https://deno.land/x/postgres@v0.17.0/query/array_parser.ts": "f8a229d82c3801de8266fa2cc4afe12e94fef8d0c479e73655c86ed3667ef33f", + "https://deno.land/x/postgres@v0.17.0/query/decode.ts": "44a4a6cbcf494ed91a4fecae38a57dce63a7b519166f02c702791d9717371419", + "https://deno.land/x/postgres@v0.17.0/query/decoders.ts": "16cb0e60227d86692931e315421b15768c78526e3aeb84e25fcc4111096de9fd", + "https://deno.land/x/postgres@v0.17.0/query/encode.ts": "5f1418a2932b7c2231556e4a5f5f56efef48728014070cfafe7656963f342933", + "https://deno.land/x/postgres@v0.17.0/query/oid.ts": "8c33e1325f34e4ca9f11a48b8066c8cfcace5f64bc1eb17ad7247af4936999e1", + "https://deno.land/x/postgres@v0.17.0/query/query.ts": "edb473cbcfeff2ee1c631272afb25d079d06b66b5853f42492725b03ffa742b6", + "https://deno.land/x/postgres@v0.17.0/query/transaction.ts": "8e75c3ce0aca97da7fe126e68f8e6c08d640e5c8d2016e62cee5c254bebe7fe8", + "https://deno.land/x/postgres@v0.17.0/query/types.ts": "a6dc8024867fe7ccb0ba4b4fa403ee5d474c7742174128c8e689c3b5e5eaa933", + "https://deno.land/x/postgres@v0.17.0/utils/deferred.ts": "dd94f2a57355355c47812b061a51b55263f72d24e9cb3fdb474c7519f4d61083", + "https://deno.land/x/postgres@v0.17.0/utils/utils.ts": "19c3527ddd5c6c4c49ae36397120274c7f41f9d3cbf479cb36065d23329e9f90", + "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/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://unpkg.com/nostr-relaypool2@0.6.34/lib/nostr-relaypool.worker.js": "a336e5c58b1e6946ae8943eb4fef21b810dc2a5a233438cff92b883673e29c96" + }, + "workspace": { + "dependencies": [ + "jsr:@bradenmacdonald/s3-lite-client@^0.7.4", + "jsr:@db/sqlite@^0.11.1", + "jsr:@nostrify/nostrify@^0.22.4", + "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", + "jsr:@soapbox/stickynotes@^0.4.0", + "jsr:@std/assert@^0.225.1", + "jsr:@std/cli@^0.223.0", + "jsr:@std/crypto@^0.224.0", + "jsr:@std/dotenv@^0.224.0", + "jsr:@std/encoding@^0.224.0", + "jsr:@std/json@^0.223.0", + "jsr:@std/media-types@^0.224.1", + "jsr:@std/streams@^0.223.0", + "npm:@isaacs/ttlcache@^1.4.1", + "npm:@noble/secp256k1@^2.0.0", + "npm:@scure/base@^1.1.6", + "npm:comlink@^4.4.1", + "npm:entities@^4.5.0", + "npm:fast-stable-stringify@^1.0.0", + "npm:formdata-helper@^0.3.0", + "npm:iso-639-1@2.1.15", + "npm:isomorphic-dompurify@^2.11.0", + "npm:kysely@^0.27.3", + "npm:linkify-plugin-hashtag@^4.1.1", + "npm:linkify-string@^4.1.1", + "npm:linkifyjs@^4.1.1", + "npm:lru-cache@^10.2.2", + "npm:nostr-relaypool2@0.6.34", + "npm:nostr-tools@2.5.1", + "npm:nostr-wasm@^0.1.0", + "npm:tldts@^6.0.14", + "npm:tseep@^1.2.1", + "npm:type-fest@^4.3.0", + "npm:unfurl.js@^6.4.0", + "npm:zod@^3.23.8" + ] + } +} diff --git a/scripts/nsec.ts b/scripts/nsec.ts index b0da3255..ea68ba77 100644 --- a/scripts/nsec.ts +++ b/scripts/nsec.ts @@ -1,4 +1,4 @@ -import { generateSecretKey, nip19 } from 'npm:nostr-tools'; +import { generateSecretKey, nip19 } from 'nostr-tools'; const sk = generateSecretKey(); const nsec = nip19.nsecEncode(sk); diff --git a/src/app.ts b/src/app.ts index 82e438d5..9174cb41 100644 --- a/src/app.ts +++ b/src/app.ts @@ -129,7 +129,7 @@ app.get('/relay', relayController); app.use( '*', cspMiddleware(), - cors({ origin: '*', exposeHeaders: ['link'] }), + cors({ origin: '*', exposeHeaders: ['link', 'Ln-Invoice'] }), signerMiddleware, uploaderMiddleware, auth98Middleware(), diff --git a/src/controllers/api/mutes.ts b/src/controllers/api/mutes.ts index 31f54ee1..90b5f545 100644 --- a/src/controllers/api/mutes.ts +++ b/src/controllers/api/mutes.ts @@ -16,7 +16,7 @@ const mutesController: AppController = async (c) => { if (event10000) { const pubkeys = getTagSet(event10000.tags, 'p'); - return renderAccounts(c, [...pubkeys].reverse()); + return renderAccounts(c, [...pubkeys]); } else { return c.json([]); } diff --git a/src/controllers/api/oauth.ts b/src/controllers/api/oauth.ts index c1407f43..242646b4 100644 --- a/src/controllers/api/oauth.ts +++ b/src/controllers/api/oauth.ts @@ -1,12 +1,14 @@ -import { encodeBase64 } from '@std/encoding/base64'; +import { NConnectSigner, NSchema as n, NSecSigner } from '@nostrify/nostrify'; +import { bech32 } from '@scure/base'; import { escape } from 'entities'; -import { nip19 } from 'nostr-tools'; +import { generateSecretKey, getPublicKey } from 'nostr-tools'; import { z } from 'zod'; import { AppController } from '@/app.ts'; +import { DittoDB } from '@/db/DittoDB.ts'; import { nostrNow } from '@/utils.ts'; import { parseBody } from '@/utils/api.ts'; -import { getClientConnectUri } from '@/utils/connect.ts'; +import { Storages } from '@/storages.ts'; const passwordGrantSchema = z.object({ grant_type: z.literal('password'), @@ -22,10 +24,18 @@ const credentialsGrantSchema = z.object({ grant_type: z.literal('client_credentials'), }); +const nostrGrantSchema = z.object({ + grant_type: z.literal('nostr_bunker'), + pubkey: n.id(), + relays: z.string().url().array().optional(), + secret: z.string().optional(), +}); + const createTokenSchema = z.discriminatedUnion('grant_type', [ passwordGrantSchema, codeGrantSchema, credentialsGrantSchema, + nostrGrantSchema, ]); const createTokenController: AppController = async (c) => { @@ -37,6 +47,13 @@ const createTokenController: AppController = async (c) => { } switch (result.data.grant_type) { + case 'nostr_bunker': + return c.json({ + access_token: await getToken(result.data), + token_type: 'Bearer', + scope: 'read write follow push', + created_at: nostrNow(), + }); case 'password': return c.json({ access_token: result.data.password, @@ -61,50 +78,63 @@ const createTokenController: AppController = async (c) => { } }; +async function getToken( + { pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] }, +): Promise<`token1${string}`> { + const kysely = await DittoDB.getInstance(); + const token = generateToken(); + + const serverSeckey = generateSecretKey(); + const serverPubkey = getPublicKey(serverSeckey); + + const signer = new NConnectSigner({ + pubkey, + signer: new NSecSigner(serverSeckey), + relay: await Storages.pubsub(), // TODO: Use the relays from the request. + timeout: 60_000, + }); + + await signer.connect(secret); + + await kysely.insertInto('nip46_tokens').values({ + api_token: token, + user_pubkey: pubkey, + server_seckey: serverSeckey, + server_pubkey: serverPubkey, + relays: JSON.stringify(relays), + connected_at: new Date(), + }).execute(); + + return token; +} + +/** Generate a bech32 token for the API. */ +function generateToken(): `token1${string}` { + const words = bech32.toWords(generateSecretKey()); + return bech32.encode('token', words); +} + /** Display the OAuth form. */ -const oauthController: AppController = async (c) => { +const oauthController: AppController = (c) => { const encodedUri = c.req.query('redirect_uri'); if (!encodedUri) { return c.text('Missing `redirect_uri` query param.', 422); } const redirectUri = maybeDecodeUri(encodedUri); - const connectUri = await getClientConnectUri(c.req.raw.signal); - - const script = ` - window.addEventListener('load', function() { - if ('nostr' in window) { - nostr.getPublicKey().then(function(pubkey) { - document.getElementById('pubkey').value = pubkey; - document.getElementById('oauth_form').submit(); - }); - } - }); - `; - - const hash = encodeBase64(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(script))); - - c.res.headers.set( - 'content-security-policy', - `default-src 'self' 'sha256-${hash}'`, - ); return c.html(` Log in with Ditto -
- - +
-
- Nostr Connect `); @@ -125,16 +155,8 @@ function maybeDecodeUri(uri: string): string { /** Schema for FormData POSTed to the OAuthController. */ const oauthAuthorizeSchema = z.object({ - pubkey: z.string().regex(/^[0-9a-f]{64}$/).optional().catch(undefined), - nip19: z.string().regex(new RegExp(`^${nip19.BECH32_REGEX.source}$`)).optional().catch(undefined), + bunker_uri: z.string().url().refine((v) => v.startsWith('bunker://')), redirect_uri: z.string().url(), -}).superRefine((data, ctx) => { - if (!data.pubkey && !data.nip19) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Missing `pubkey` or `nip19`.', - }); - } }); /** Controller the OAuth form is POSTed to. */ @@ -147,18 +169,19 @@ const oauthAuthorizeController: AppController = async (c) => { } // Parsed FormData values. - const { pubkey, nip19: nip19id, redirect_uri: redirectUri } = result.data; + const { bunker_uri, redirect_uri: redirectUri } = result.data; - if (pubkey) { - const encoded = nip19.npubEncode(pubkey!); - const url = addCodeToRedirectUri(redirectUri, encoded); - return c.redirect(url); - } else if (nip19id) { - const url = addCodeToRedirectUri(redirectUri, nip19id); - return c.redirect(url); - } + const bunker = new URL(bunker_uri); - return c.text('The Nostr ID was not provided or invalid.', 422); + const token = await getToken({ + pubkey: bunker.hostname, + secret: bunker.searchParams.get('secret') || undefined, + relays: bunker.searchParams.getAll('relay'), + }); + + const url = addCodeToRedirectUri(redirectUri, token); + + return c.redirect(url); }; /** Append the given `code` as a query param to the `redirect_uri`. */ diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index c81b5876..ad21381c 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -13,15 +13,15 @@ import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts' import { renderEventAccounts } from '@/views.ts'; import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; import { Storages } from '@/storages.ts'; -import { hydrateEvents } from '@/storages/hydrate.ts'; +import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts'; import { createEvent, paginationSchema, parseBody, updateListEvent } from '@/utils/api.ts'; -import { getLnurl } from '@/utils/lnurl.ts'; +import { getInvoice, getLnurl } from '@/utils/lnurl.ts'; import { lookupPubkey } from '@/utils/lookup.ts'; import { addTag, deleteTag } from '@/utils/tags.ts'; import { asyncReplaceAll } from '@/utils/text.ts'; const createStatusSchema = z.object({ - in_reply_to_id: z.string().regex(/[0-9a-f]{64}/).nullish(), + in_reply_to_id: n.id().nullish(), language: z.string().refine(ISO6391.validate).nullish(), media_ids: z.string().array().nullish(), poll: z.object({ @@ -36,7 +36,7 @@ const createStatusSchema = z.object({ status: z.string().nullish(), to: z.string().array().nullish(), visibility: z.enum(['public', 'unlisted', 'private', 'direct']).nullish(), - quote_id: z.string().nullish(), + quote_id: n.id().nullish(), }).refine( (data) => Boolean(data.status || data.media_ids?.length), { message: 'Status must contain text or media.' }, @@ -155,11 +155,12 @@ const createStatusController: AppController = async (c) => { .map(({ data }) => data.find(([name]) => name === 'url')?.[1]) .filter((url): url is string => Boolean(url)); - const mediaCompat: string = mediaUrls.length ? ['', '', ...mediaUrls].join('\n') : ''; + const quoteCompat = data.quote_id ? `\n\nnostr:${nip19.noteEncode(data.quote_id)}` : ''; + const mediaCompat = mediaUrls.length ? `\n\n${mediaUrls.join('\n')}` : ''; const event = await createEvent({ kind: 1, - content: content + mediaCompat, + content: content + quoteCompat + mediaCompat, tags, }, c); @@ -450,15 +451,16 @@ const zapController: AppController = async (c) => { const author = target?.author; const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); const lnurl = getLnurl(meta); + const amount = params.data.amount; if (target && lnurl) { - await createEvent({ + const nostr = await createEvent({ kind: 9734, content: params.data.comment ?? '', tags: [ ['e', target.id], ['p', target.pubkey], - ['amount', params.data.amount.toString()], + ['amount', amount.toString()], ['relays', Conf.relay], ['lnurl', lnurl], ], @@ -467,7 +469,11 @@ const zapController: AppController = async (c) => { const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() }); status.zapped = true; - return c.json(status); + return c.json(status, { + headers: { + 'Ln-Invoice': await getInvoice({ amount, nostr: purifyEvent(nostr), lnurl }, signal), + }, + }); } else { return c.json({ error: 'Event not found.' }, 404); } diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index a69fbb04..427c350e 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -4,6 +4,7 @@ import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; +import { DittoDB } from '@/db/DittoDB.ts'; import { MuteListPolicy } from '@/policies/MuteListPolicy.ts'; import { getFeedPubkeys } from '@/queries.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; @@ -34,7 +35,7 @@ const streamSchema = z.enum([ type Stream = z.infer; -const streamingController: AppController = (c) => { +const streamingController: AppController = async (c) => { const upgrade = c.req.header('upgrade'); const token = c.req.header('sec-websocket-protocol'); const stream = streamSchema.optional().catch(undefined).parse(c.req.query('stream')); @@ -44,7 +45,7 @@ const streamingController: AppController = (c) => { return c.text('Please use websocket protocol', 400); } - const pubkey = token ? bech32ToPubkey(token) : undefined; + const pubkey = token ? await getTokenPubkey(token) : undefined; if (token && !pubkey) { return c.json({ error: 'Invalid access token' }, 401); } @@ -143,4 +144,20 @@ async function topicToFilter( } } +async function getTokenPubkey(token: string): Promise { + if (token.startsWith('token1')) { + const kysely = await DittoDB.getInstance(); + + const { user_pubkey } = await kysely + .selectFrom('nip46_tokens') + .select(['user_pubkey', 'server_seckey', 'relays']) + .where('api_token', '=', token) + .executeTakeFirstOrThrow(); + + return user_pubkey; + } else { + return bech32ToPubkey(token); + } +} + export { streamingController }; diff --git a/src/controllers/nostr/relay.ts b/src/controllers/nostr/relay.ts index 4d239990..5d08e02c 100644 --- a/src/controllers/nostr/relay.ts +++ b/src/controllers/nostr/relay.ts @@ -4,29 +4,19 @@ import { NostrClientEVENT, NostrClientMsg, NostrClientREQ, - NostrEvent, - NostrFilter, + NostrRelayMsg, NSchema as n, } from '@nostrify/nostrify'; + +import { AppController } from '@/app.ts'; import { relayInfoController } from '@/controllers/nostr/relay-info.ts'; import * as pipeline from '@/pipeline.ts'; import { RelayError } from '@/RelayError.ts'; import { Storages } from '@/storages.ts'; -import type { AppController } from '@/app.ts'; -import { Conf } from '@/config.ts'; - /** Limit of initial events returned for a subscription. */ const FILTER_LIMIT = 100; -/** NIP-01 relay to client message. */ -type RelayMsg = - | ['EVENT', string, NostrEvent] - | ['NOTICE', string] - | ['EOSE', string] - | ['OK', string, boolean, string] - | ['COUNT', string, { count: number; approximate?: boolean }]; - /** Set up the Websocket connection. */ function connectStream(socket: WebSocket) { const controllers = new Map(); @@ -65,18 +55,22 @@ function connectStream(socket: WebSocket) { } /** Handle REQ. Start a subscription. */ - async function handleReq([_, subId, ...rest]: NostrClientREQ): Promise { - const filters = prepareFilters(rest); - + async function handleReq([_, subId, ...filters]: NostrClientREQ): Promise { const controller = new AbortController(); controllers.get(subId)?.abort(); controllers.set(subId, controller); - const db = await Storages.db(); + const store = await Storages.db(); const pubsub = await Storages.pubsub(); - for (const event of await db.query(filters, { limit: FILTER_LIMIT })) { - send(['EVENT', subId, event]); + try { + for (const event of await store.query(filters, { limit: FILTER_LIMIT })) { + send(['EVENT', subId, event]); + } + } catch (e) { + send(['CLOSED', subId, e.message]); + controllers.delete(subId); + return; } send(['EOSE', subId]); @@ -118,30 +112,20 @@ function connectStream(socket: WebSocket) { } /** Handle COUNT. Return the number of events matching the filters. */ - async function handleCount([_, subId, ...rest]: NostrClientCOUNT): Promise { + async function handleCount([_, subId, ...filters]: NostrClientCOUNT): Promise { const store = await Storages.db(); - const { count } = await store.count(prepareFilters(rest)); + const { count } = await store.count(filters); send(['COUNT', subId, { count, approximate: false }]); } /** Send a message back to the client. */ - function send(msg: RelayMsg): void { + function send(msg: NostrRelayMsg): void { if (socket.readyState === WebSocket.OPEN) { socket.send(JSON.stringify(msg)); } } } -/** Enforce the filters with certain criteria. */ -function prepareFilters(filters: NostrClientREQ[2][]): NostrFilter[] { - return filters.map((filter) => { - const narrow = Boolean(filter.ids?.length || filter.authors?.length); - const search = narrow ? filter.search : `domain:${Conf.url.host} ${filter.search ?? ''}`; - // Return only local events unless the query is already narrow. - return { ...filter, search }; - }); -} - const relayController: AppController = (c, next) => { const upgrade = c.req.header('upgrade'); diff --git a/src/db/DittoTables.ts b/src/db/DittoTables.ts index c2d1f861..65bc4261 100644 --- a/src/db/DittoTables.ts +++ b/src/db/DittoTables.ts @@ -2,6 +2,7 @@ export interface DittoTables { nostr_events: EventRow; nostr_tags: TagRow; nostr_fts5: EventFTSRow; + nip46_tokens: NIP46TokenRow; unattached_media: UnattachedMediaRow; author_stats: AuthorStatsRow; event_stats: EventStatsRow; @@ -44,6 +45,15 @@ interface TagRow { value: string; } +interface NIP46TokenRow { + api_token: string; + user_pubkey: string; + server_seckey: Uint8Array; + server_pubkey: string; + relays: string; + connected_at: Date; +} + interface UnattachedMediaRow { id: string; pubkey: string; diff --git a/src/db/migrations/023_add_nip46_tokens.ts b/src/db/migrations/023_add_nip46_tokens.ts new file mode 100644 index 00000000..144bd1ec --- /dev/null +++ b/src/db/migrations/023_add_nip46_tokens.ts @@ -0,0 +1,17 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('nip46_tokens') + .addColumn('api_token', 'text', (col) => col.primaryKey().unique().notNull()) + .addColumn('user_pubkey', 'text', (col) => col.notNull()) + .addColumn('server_seckey', 'bytea', (col) => col.notNull()) + .addColumn('server_pubkey', 'text', (col) => col.notNull()) + .addColumn('relays', 'text', (col) => col.defaultTo('[]')) + .addColumn('connected_at', 'timestamp', (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('nip46_tokens').execute(); +} diff --git a/src/filter.ts b/src/filter.ts index fd698c4e..f9288c8a 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -1,6 +1,5 @@ -import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; +import { NKinds, NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; import stringifyStable from 'fast-stable-stringify'; -import { getFilterLimit } from 'nostr-tools'; import { z } from 'zod'; /** Microfilter to get one specific event by ID. */ @@ -65,6 +64,25 @@ function normalizeFilters(filters: F[]): F[] { }, []); } +/** Calculate the intrinsic limit of a filter. This function may return `Infinity`. */ +function getFilterLimit(filter: NostrFilter): number { + if (filter.ids && !filter.ids.length) return 0; + if (filter.kinds && !filter.kinds.length) return 0; + if (filter.authors && !filter.authors.length) return 0; + + for (const [key, value] of Object.entries(filter)) { + if (key[0] === '#' && Array.isArray(value) && !value.length) return 0; + } + + return Math.min( + Math.max(0, filter.limit ?? Infinity), + filter.ids?.length ?? Infinity, + filter.authors?.length && filter.kinds?.every((kind) => NKinds.replaceable(kind)) + ? filter.authors.length * filter.kinds.length + : Infinity, + ); +} + export { type AuthorMicrofilter, canFilter, diff --git a/src/middleware/auth98Middleware.ts b/src/middleware/auth98Middleware.ts index abecea72..34d69379 100644 --- a/src/middleware/auth98Middleware.ts +++ b/src/middleware/auth98Middleware.ts @@ -3,7 +3,7 @@ import { HTTPException } from 'hono'; import { type AppContext, type AppMiddleware } from '@/app.ts'; import { findUser, User } from '@/db/users.ts'; -import { ConnectSigner } from '@/signers/ConnectSigner.ts'; +import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts'; import { localRequest } from '@/utils/api.ts'; import { buildAuthEventTemplate, @@ -22,7 +22,7 @@ function auth98Middleware(opts: ParseAuthRequestOpts = {}): AppMiddleware { const result = await parseAuthRequest(req, opts); if (result.success) { - c.set('signer', new ConnectSigner(result.data.pubkey)); + c.set('signer', new ReadOnlySigner(result.data.pubkey)); c.set('proof', result.data); } @@ -70,7 +70,8 @@ function withProof( opts?: ParseAuthRequestOpts, ): AppMiddleware { return async (c, next) => { - const pubkey = await c.get('signer')?.getPublicKey(); + const signer = c.get('signer'); + const pubkey = await signer?.getPublicKey(); const proof = c.get('proof') || await obtainProof(c, opts); // Prevent people from accidentally using the wrong account. This has no other security implications. @@ -79,8 +80,12 @@ function withProof( } if (proof) { - c.set('signer', new ConnectSigner(proof.pubkey)); c.set('proof', proof); + + if (!signer) { + c.set('signer', new ReadOnlySigner(proof.pubkey)); + } + await handler(c, proof, next); } else { throw new HTTPException(401, { message: 'No proof' }); diff --git a/src/middleware/signerMiddleware.ts b/src/middleware/signerMiddleware.ts index 1d357082..5ea4235c 100644 --- a/src/middleware/signerMiddleware.ts +++ b/src/middleware/signerMiddleware.ts @@ -1,11 +1,11 @@ import { NSecSigner } from '@nostrify/nostrify'; -import { Stickynotes } from '@soapbox/stickynotes'; import { nip19 } from 'nostr-tools'; import { AppMiddleware } from '@/app.ts'; import { ConnectSigner } from '@/signers/ConnectSigner.ts'; - -const console = new Stickynotes('ditto:signerMiddleware'); +import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts'; +import { HTTPException } from 'hono'; +import { DittoDB } from '@/db/DittoDB.ts'; /** We only accept "Bearer" type. */ const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`); @@ -18,22 +18,38 @@ export const signerMiddleware: AppMiddleware = async (c, next) => { if (match) { const [_, bech32] = match; - try { - const decoded = nip19.decode(bech32!); + if (bech32.startsWith('token1')) { + try { + const kysely = await DittoDB.getInstance(); - switch (decoded.type) { - case 'npub': - c.set('signer', new ConnectSigner(decoded.data)); - break; - case 'nprofile': - c.set('signer', new ConnectSigner(decoded.data.pubkey, decoded.data.relays)); - break; - case 'nsec': - c.set('signer', new NSecSigner(decoded.data)); - break; + const { user_pubkey, server_seckey, relays } = await kysely + .selectFrom('nip46_tokens') + .select(['user_pubkey', 'server_seckey', 'relays']) + .where('api_token', '=', bech32) + .executeTakeFirstOrThrow(); + + c.set('signer', new ConnectSigner(user_pubkey, new NSecSigner(server_seckey), JSON.parse(relays))); + } catch { + throw new HTTPException(401); + } + } else { + try { + const decoded = nip19.decode(bech32!); + + switch (decoded.type) { + case 'npub': + c.set('signer', new ReadOnlySigner(decoded.data)); + break; + case 'nprofile': + c.set('signer', new ReadOnlySigner(decoded.data.pubkey)); + break; + case 'nsec': + c.set('signer', new NSecSigner(decoded.data)); + break; + } + } catch { + throw new HTTPException(401); } - } catch { - console.debug('The user is not logged in'); } } diff --git a/src/pipeline.ts b/src/pipeline.ts index fd5fc990..9aa16fda 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -1,5 +1,4 @@ -import { NKinds, NostrEvent, NPolicy, NSchema as n } from '@nostrify/nostrify'; -import { LNURL } from '@nostrify/nostrify/ln'; +import { NKinds, NostrEvent, NSchema as n } from '@nostrify/nostrify'; import { PipePolicy } from '@nostrify/nostrify/policies'; import Debug from '@soapbox/stickynotes/debug'; import { sql } from 'kysely'; @@ -12,15 +11,12 @@ import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { DVM } from '@/pipeline/DVM.ts'; import { MuteListPolicy } from '@/policies/MuteListPolicy.ts'; import { RelayError } from '@/RelayError.ts'; -import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; import { Storages } from '@/storages.ts'; -import { eventAge, nostrDate, nostrNow, parseNip05, Time } from '@/utils.ts'; -import { fetchWorker } from '@/workers/fetch.ts'; +import { eventAge, nostrDate, parseNip05, Time } from '@/utils.ts'; import { policyWorker } from '@/workers/policy.ts'; import { TrendsWorker } from '@/workers/trends.ts'; import { verifyEventWorker } from '@/workers/verify.ts'; -import { AdminSigner } from '@/signers/AdminSigner.ts'; -import { lnurlCache } from '@/utils/lnurl.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { updateStats } from '@/utils/stats.ts'; import { getTagSet } from '@/utils/tags.ts'; @@ -32,6 +28,13 @@ const debug = Debug('ditto:pipeline'); * It is idempotent, so it can be called multiple times for the same event. */ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { + // Integer max value for Postgres. TODO: switch to a bigint in 2038. + if (event.created_at >= 2_147_483_647) { + throw new RelayError('blocked', 'event too far in the future'); + } + if (event.kind >= 2_147_483_647) { + throw new RelayError('blocked', 'event kind too large'); + } if (!(await verifyEventWorker(event))) return; if (encounterEvent(event)) return; debug(`NostrEvent<${event.kind}> ${event.id}`); @@ -48,7 +51,6 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { const debug = Debug('ditto:policy'); - const policies: NPolicy[] = [ + const policy = new PipePolicy([ new MuteListPolicy(Conf.pubkey, await Storages.admin()), - ]; - - try { - await policyWorker.import(Conf.policy); - policies.push(policyWorker); - debug(`Using custom policy: ${Conf.policy}`); - } catch (e) { - if (e.message.includes('Module not found')) { - debug('Custom policy not found '); - } else { - console.error(`DITTO_POLICY (error importing policy): ${Conf.policy}`, e); - throw new RelayError('blocked', 'policy could not be loaded'); - } - } - - const policy = new PipePolicy(policies.reverse()); + policyWorker, + ]); try { const result = await policy.call(event); @@ -189,53 +177,6 @@ function processMedia({ tags, pubkey, user }: DittoEvent) { } } -/** Emit Nostr Wallet Connect event from zaps so users may pay. */ -async function payZap(event: DittoEvent, signal: AbortSignal) { - if (event.kind !== 9734 || !event.user) return; - - const lnurl = event.tags.find(([name]) => name === 'lnurl')?.[1]; - const amount = Number(event.tags.find(([name]) => name === 'amount')?.[1]); - - if (!lnurl || !amount) return; - - try { - const details = await lnurlCache.fetch(lnurl, { signal }); - - if (details.tag !== 'payRequest' || !details.allowsNostr || !details.nostrPubkey) { - throw new Error('invalid lnurl'); - } - - if (amount > details.maxSendable || amount < details.minSendable) { - throw new Error('amount out of range'); - } - - const { pr } = await LNURL.callback( - details.callback, - { amount, nostr: purifyEvent(event), lnurl }, - { fetch: fetchWorker, signal }, - ); - - const signer = new AdminSigner(); - - const nwcRequestEvent = await signer.signEvent({ - kind: 23194, - content: await signer.nip04.encrypt( - event.pubkey, - JSON.stringify({ method: 'pay_invoice', params: { invoice: pr } }), - ), - created_at: nostrNow(), - tags: [ - ['p', event.pubkey], - ['e', event.id], - ], - }); - - await handleEvent(nwcRequestEvent, signal); - } catch (e) { - debug('lnurl error:', e); - } -} - /** Determine if the event is being received in a timely manner. */ function isFresh(event: NostrEvent): boolean { return eventAge(event) < Time.seconds(10); diff --git a/src/signers/ConnectSigner.ts b/src/signers/ConnectSigner.ts index f482413d..d4cf6032 100644 --- a/src/signers/ConnectSigner.ts +++ b/src/signers/ConnectSigner.ts @@ -1,7 +1,6 @@ // deno-lint-ignore-file require-await import { NConnectSigner, NostrEvent, NostrSigner } from '@nostrify/nostrify'; -import { AdminSigner } from '@/signers/AdminSigner.ts'; import { Storages } from '@/storages.ts'; /** @@ -12,17 +11,17 @@ import { Storages } from '@/storages.ts'; export class ConnectSigner implements NostrSigner { private signer: Promise; - constructor(private pubkey: string, private relays?: string[]) { - this.signer = this.init(); + constructor(private pubkey: string, signer: NostrSigner, private relays?: string[]) { + this.signer = this.init(signer); } - async init(): Promise { + async init(signer: NostrSigner): Promise { return new NConnectSigner({ pubkey: this.pubkey, // TODO: use a remote relay for `nprofile` signing (if present and `Conf.relay` isn't already in the list) relay: await Storages.pubsub(), - signer: new AdminSigner(), - timeout: 60000, + signer, + timeout: 60_000, }); } diff --git a/src/signers/ReadOnlySigner.ts b/src/signers/ReadOnlySigner.ts new file mode 100644 index 00000000..8ba15554 --- /dev/null +++ b/src/signers/ReadOnlySigner.ts @@ -0,0 +1,17 @@ +// deno-lint-ignore-file require-await +import { NostrEvent, NostrSigner } from '@nostrify/nostrify'; +import { HTTPException } from 'hono'; + +export class ReadOnlySigner implements NostrSigner { + constructor(private pubkey: string) {} + + async signEvent(): Promise { + throw new HTTPException(401, { + message: 'Log out and back in', + }); + } + + async getPublicKey(): Promise { + return this.pubkey; + } +} diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 778a9e68..00f1efda 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -96,6 +96,20 @@ class EventsDB implements NStore { async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise { filters = await this.expandFilters(filters); + for (const filter of filters) { + if (filter.since && filter.since >= 2_147_483_647) { + throw new Error('since filter too far into the future'); + } + if (filter.until && filter.until >= 2_147_483_647) { + throw new Error('until filter too far into the future'); + } + for (const kind of filter.kinds ?? []) { + if (kind >= 2_147_483_647) { + throw new Error('kind filter too far into the future'); + } + } + } + if (opts.signal?.aborted) return Promise.resolve([]); if (!filters.length) return Promise.resolve([]); diff --git a/src/utils/api.ts b/src/utils/api.ts index dceede7a..5ab4cc61 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -138,8 +138,8 @@ async function parseBody(req: Request): Promise { /** Schema to parse pagination query params. */ const paginationSchema = z.object({ - since: z.coerce.number().optional().catch(undefined), - until: z.coerce.number().optional().catch(undefined), + since: z.coerce.number().nonnegative().optional().catch(undefined), + until: z.coerce.number().nonnegative().optional().catch(undefined), limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)), }); @@ -179,6 +179,48 @@ function paginated(c: AppContext, events: NostrEvent[], entities: (Entity | unde return c.json(results, 200, headers); } +/** Query params for paginating a list. */ +const listPaginationSchema = z.object({ + offset: z.coerce.number().nonnegative().catch(0), + limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)), +}); + +/** Build HTTP Link header for paginating Nostr lists. */ +function buildListLinkHeader(url: string, params: { offset: number; limit: number }): string | undefined { + const { origin } = Conf.url; + const { pathname, search } = new URL(url); + const { offset, limit } = params; + const next = new URL(pathname + search, origin); + const prev = new URL(pathname + search, origin); + + next.searchParams.set('offset', String(offset + limit)); + prev.searchParams.set('offset', String(Math.max(offset - limit, 0))); + + next.searchParams.set('limit', String(limit)); + prev.searchParams.set('limit', String(limit)); + + return `<${next}>; rel="next", <${prev}>; rel="prev"`; +} + +/** paginate a list of tags. */ +function paginatedList( + c: AppContext, + params: { offset: number; limit: number }, + entities: (Entity | undefined)[], + headers: HeaderRecord = {}, +) { + const link = buildListLinkHeader(c.req.url, params); + const hasMore = entities.length > 0; + + if (link) { + headers.link = hasMore ? link : link.split(', ').find((link) => link.endsWith('; rel="prev"'))!; + } + + // Filter out undefined entities. + const results = entities.filter((entity): entity is Entity => Boolean(entity)); + return c.json(results, 200, headers); +} + /** JSON-LD context. */ type LDContext = (string | Record>)[]; @@ -209,8 +251,10 @@ export { createAdminEvent, createEvent, type EventStub, + listPaginationSchema, localRequest, paginated, + paginatedList, type PaginationParams, paginationSchema, parseBody, diff --git a/src/utils/lnurl.ts b/src/utils/lnurl.ts index af344f22..ca7e1256 100644 --- a/src/utils/lnurl.ts +++ b/src/utils/lnurl.ts @@ -4,6 +4,7 @@ import Debug from '@soapbox/stickynotes/debug'; import { SimpleLRU } from '@/utils/SimpleLRU.ts'; import { Time } from '@/utils/time.ts'; import { fetchWorker } from '@/workers/fetch.ts'; +import { NostrEvent } from '@nostrify/nostrify'; const debug = Debug('ditto:lnurl'); @@ -38,4 +39,32 @@ function getLnurl({ lud06, lud16 }: { lud06?: string; lud16?: string }, limit?: } } -export { getLnurl, lnurlCache }; +interface CallbackParams { + amount: number; + nostr: NostrEvent; + lnurl: string; +} + +async function getInvoice(params: CallbackParams, signal?: AbortSignal): Promise { + const { amount, lnurl } = params; + + const details = await lnurlCache.fetch(lnurl, { signal }); + + if (details.tag !== 'payRequest' || !details.allowsNostr || !details.nostrPubkey) { + throw new Error('invalid lnurl'); + } + + if (amount > details.maxSendable || amount < details.minSendable) { + throw new Error('amount out of range'); + } + + const { pr } = await LNURL.callback( + details.callback, + params, + { fetch: fetchWorker, signal }, + ); + + return pr; +} + +export { getInvoice, getLnurl, lnurlCache }; diff --git a/src/utils/note.ts b/src/utils/note.ts index 0d1e3b20..c10e3e9f 100644 --- a/src/utils/note.ts +++ b/src/utils/note.ts @@ -46,7 +46,7 @@ interface ParsedNoteContent { /** Convert Nostr content to Mastodon API HTML. Also return parsed data. */ function parseNoteContent(content: string): ParsedNoteContent { // Parsing twice is ineffecient, but I don't know how to do only once. - const html = linkifyStr(content, linkifyOpts); + const html = linkifyStr(content, linkifyOpts).replace(/\n+$/, ''); const links = linkify.find(content).filter(isLinkURL); const firstUrl = links.find(isNonMediaLink)?.href; diff --git a/src/views.ts b/src/views.ts index 5b6d8c39..e1172306 100644 --- a/src/views.ts +++ b/src/views.ts @@ -4,7 +4,7 @@ import { AppContext } from '@/app.ts'; import { Storages } from '@/storages.ts'; import { renderAccount } from '@/views/mastodon/accounts.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; -import { paginated, paginationSchema } from '@/utils/api.ts'; +import { listPaginationSchema, paginated, paginatedList, paginationSchema } from '@/utils/api.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { accountFromPubkey } from '@/views/mastodon/accounts.ts'; @@ -42,12 +42,14 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], opts?: return paginated(c, events, accounts); } -async function renderAccounts(c: AppContext, authors: string[], signal = AbortSignal.timeout(1000)) { - const { since, until, limit } = paginationSchema.parse(c.req.query()); +async function renderAccounts(c: AppContext, pubkeys: string[]) { + const { offset, limit } = listPaginationSchema.parse(c.req.query()); + const authors = pubkeys.reverse().slice(offset, offset + limit); const store = await Storages.db(); + const signal = c.req.raw.signal; - const events = await store.query([{ kinds: [0], authors, since, until, limit }], { signal }) + const events = await store.query([{ kinds: [0], authors }], { signal }) .then((events) => hydrateEvents({ events, store, signal })); const accounts = await Promise.all( @@ -61,7 +63,7 @@ async function renderAccounts(c: AppContext, authors: string[], signal = AbortSi }), ); - return paginated(c, events, accounts); + return paginatedList(c, { offset, limit }, accounts); } /** Render statuses by event IDs. */ diff --git a/src/views/mastodon/accounts.ts b/src/views/mastodon/accounts.ts index 918d03b9..74d38ed2 100644 --- a/src/views/mastodon/accounts.ts +++ b/src/views/mastodon/accounts.ts @@ -31,7 +31,13 @@ async function renderAccount( website, } = n.json().pipe(n.metadata()).catch({}).parse(event.content); - const npub = nip19.npubEncode(pubkey); + let npub: string; + try { + npub = nip19.npubEncode(pubkey); + } catch { + return; + } + const parsed05 = await parseAndVerifyNip05(nip05, pubkey); const role = event.user?.tags.find(([name]) => name === 'role')?.[1] ?? 'user'; diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index fe03857f..bf71b019 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -138,6 +138,8 @@ async function renderReblog(event: DittoEvent, opts: RenderStatusOpts) { return { ...status, + in_reply_to_id: null, + in_reply_to_account_id: null, reblog, }; } diff --git a/src/workers/policy.ts b/src/workers/policy.ts index e3926675..ef9aa2cd 100644 --- a/src/workers/policy.ts +++ b/src/workers/policy.ts @@ -1,8 +1,11 @@ +import { Stickynotes } from '@soapbox/stickynotes'; import * as Comlink from 'comlink'; import { Conf } from '@/config.ts'; import type { CustomPolicy } from '@/workers/policy.worker.ts'; +const console = new Stickynotes('ditto:policy'); + export const policyWorker = Comlink.wrap( new Worker( new URL('./policy.worker.ts', import.meta.url), @@ -19,3 +22,14 @@ export const policyWorker = Comlink.wrap( }, ), ); + +try { + await policyWorker.import(Conf.policy); + console.debug(`Using custom policy: ${Conf.policy}`); +} catch (e) { + if (e.message.includes('Module not found')) { + console.debug('Custom policy not found '); + } else { + throw new Error(`DITTO_POLICY (error importing policy): ${Conf.policy}`, e); + } +} diff --git a/src/workers/policy.worker.ts b/src/workers/policy.worker.ts index d1368bce..9f94a008 100644 --- a/src/workers/policy.worker.ts +++ b/src/workers/policy.worker.ts @@ -1,6 +1,6 @@ import 'deno-safe-fetch/load'; import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify'; -import { ReadOnlyPolicy } from '@nostrify/nostrify/policies'; +import { NoOpPolicy, ReadOnlyPolicy } from '@nostrify/nostrify/policies'; import * as Comlink from 'comlink'; export class CustomPolicy implements NPolicy { @@ -12,8 +12,15 @@ export class CustomPolicy implements NPolicy { } async import(path: string): Promise { - const Policy = (await import(path)).default; - this.policy = new Policy(); + try { + const Policy = (await import(path)).default; + this.policy = new Policy(); + } catch (e) { + if (e.message.includes('Module not found')) { + this.policy = new NoOpPolicy(); + } + throw e; + } } }