Compare commits

..

16 Commits

79 changed files with 1191 additions and 471 deletions

4
.gitignore vendored
View File

@ -39,4 +39,6 @@ redis/
dist/ dist/
build/ build/
docker-compose.yml docker-compose.yml
ucaptcha-config.yaml

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="TypeScriptCompiler">
<option name="useTypesFromServer" value="true" />
</component>
</project>

View File

@ -28,6 +28,8 @@
<excludeFolder url="file://$MODULE_DIR$/packages/crawler/logs" /> <excludeFolder url="file://$MODULE_DIR$/packages/crawler/logs" />
<excludeFolder url="file://$MODULE_DIR$/data" /> <excludeFolder url="file://$MODULE_DIR$/data" />
<excludeFolder url="file://$MODULE_DIR$/redis" /> <excludeFolder url="file://$MODULE_DIR$/redis" />
<excludeFolder url="file://$MODULE_DIR$/ml" />
<excludeFolder url="file://$MODULE_DIR$/src" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

136
bun.lock
View File

@ -19,6 +19,7 @@
"hono": "^4.7.8", "hono": "^4.7.8",
"hono-rate-limiter": "^0.4.2", "hono-rate-limiter": "^0.4.2",
"ioredis": "^5.6.1", "ioredis": "^5.6.1",
"limiter": "^3.0.0",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"rate-limit-redis": "^4.2.0", "rate-limit-redis": "^4.2.0",
"yup": "^1.6.1", "yup": "^1.6.1",
@ -32,6 +33,7 @@
"packages/core": { "packages/core": {
"name": "core", "name": "core",
"dependencies": { "dependencies": {
"@koshnic/ratelimit": "^1.0.3",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"ioredis": "^5.6.1", "ioredis": "^5.6.1",
"logform": "^2.7.0", "logform": "^2.7.0",
@ -233,6 +235,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@koshnic/ratelimit": ["@koshnic/ratelimit@1.0.3", "", { "dependencies": { "@types/chai": "^4.3.9", "@types/mocha": "^10.0.3", "chai": "^4.3.10", "ioredis": "^5.3.2", "mocha": "^10.2.0" } }, "sha512-cfDcSc+I+M4hNM+/4M+lfn8UuTq4OEFKl78ThOcGNaO7g8tWb1vm2qVpV1p1loYao1mqk00NBNwHQu2E/qFq2g=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
@ -369,6 +373,8 @@
"@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="], "@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="],
"@types/chai": ["@types/chai@4.3.20", "", {}, "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ=="],
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
@ -381,6 +387,8 @@
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/mocha": ["@types/mocha@10.0.10", "", {}, "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
@ -417,6 +425,8 @@
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@ -455,6 +465,8 @@
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="],
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
@ -463,10 +475,14 @@
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="], "brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="],
"browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="],
"browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], "browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="],
"bullmq": ["bullmq@5.52.1", "", { "dependencies": { "cron-parser": "^4.9.0", "ioredis": "^5.4.1", "msgpackr": "^1.11.2", "node-abort-controller": "^3.1.1", "semver": "^7.5.4", "tslib": "^2.0.0", "uuid": "^9.0.0" } }, "sha512-u7CSV9wID3MBEX2DNubEErbAlrADgm8abUBAi6h8rQTnuTkhhgMs2iD7uhqplK8lIgUOkBIW3sDJWaMSInH47A=="], "bullmq": ["bullmq@5.52.1", "", { "dependencies": { "cron-parser": "^4.9.0", "ioredis": "^5.4.1", "msgpackr": "^1.11.2", "node-abort-controller": "^3.1.1", "semver": "^7.5.4", "tslib": "^2.0.0", "uuid": "^9.0.0" } }, "sha512-u7CSV9wID3MBEX2DNubEErbAlrADgm8abUBAi6h8rQTnuTkhhgMs2iD7uhqplK8lIgUOkBIW3sDJWaMSInH47A=="],
@ -499,7 +515,7 @@
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
@ -567,6 +583,8 @@
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="],
"decode-named-character-reference": ["decode-named-character-reference@1.1.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w=="], "decode-named-character-reference": ["decode-named-character-reference@1.1.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w=="],
"dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="], "dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="],
@ -677,8 +695,14 @@
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="],
"flatbuffers": ["flatbuffers@25.2.10", "", {}, "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw=="], "flatbuffers": ["flatbuffers@25.2.10", "", {}, "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw=="],
"flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="], "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
@ -699,6 +723,8 @@
"frontend": ["frontend@workspace:packages/frontend"], "frontend": ["frontend@workspace:packages/frontend"],
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@ -707,13 +733,17 @@
"get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="], "get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
"get-func-name": ["get-func-name@2.0.2", "", {}, "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], "glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"glob-regex": ["glob-regex@0.3.2", "", {}, "sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw=="], "glob-regex": ["glob-regex@0.3.2", "", {}, "sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw=="],
@ -761,6 +791,8 @@
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"hono": ["hono@4.7.8", "", {}, "sha512-PCibtFdxa7/Ldud9yddl1G81GjYaeMYYTq4ywSaNsYbB1Lug4mwtOMJf2WXykL0pntYwmpRJeOI3NmoDgD+Jxw=="], "hono": ["hono@4.7.8", "", {}, "sha512-PCibtFdxa7/Ldud9yddl1G81GjYaeMYYTq4ywSaNsYbB1Lug4mwtOMJf2WXykL0pntYwmpRJeOI3NmoDgD+Jxw=="],
"hono-rate-limiter": ["hono-rate-limiter@0.4.2", "", { "peerDependencies": { "hono": "^4.1.1" } }, "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw=="], "hono-rate-limiter": ["hono-rate-limiter@0.4.2", "", { "peerDependencies": { "hono": "^4.1.1" } }, "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw=="],
@ -777,6 +809,8 @@
"import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="], "import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ioredis": ["ioredis@5.6.1", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA=="], "ioredis": ["ioredis@5.6.1", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA=="],
@ -787,13 +821,21 @@
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
@ -803,6 +845,8 @@
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
"is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
@ -845,16 +889,22 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="], "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
"limiter": ["limiter@3.0.0", "", {}, "sha512-hev7DuXojsTFl2YwyzUJMDnZ/qBDd3yZQLSH3aD4tdL1cqfc3TMnoecEJtWFaQFdErZsKoFMBTxF/FBSkgDbEg=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
"logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="], "logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="],
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
@ -971,7 +1021,7 @@
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@ -981,6 +1031,8 @@
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"mocha": ["mocha@10.8.2", "", { "dependencies": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", "chokidar": "^3.5.3", "debug": "^4.3.5", "diff": "^5.2.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^8.1.0", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^5.1.6", "ms": "^2.1.3", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^6.5.1", "yargs": "^16.2.0", "yargs-parser": "^20.2.9", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@ -1047,6 +1099,8 @@
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"p-queue": ["p-queue@8.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw=="], "p-queue": ["p-queue@8.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw=="],
"p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="],
@ -1065,6 +1119,8 @@
"pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="], "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
@ -1135,6 +1191,8 @@
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"rate-limit-redis": ["rate-limit-redis@4.2.0", "", { "peerDependencies": { "express-rate-limit": ">= 6" } }, "sha512-wV450NQyKC24NmPosJb2131RoczLdfIJdKCReNwtVpm5998U8SgKrAZrIHaN/NfQgqOHaan8Uq++B4sa5REwjA=="], "rate-limit-redis": ["rate-limit-redis@4.2.0", "", { "peerDependencies": { "express-rate-limit": ">= 6" } }, "sha512-wV450NQyKC24NmPosJb2131RoczLdfIJdKCReNwtVpm5998U8SgKrAZrIHaN/NfQgqOHaan8Uq++B4sa5REwjA=="],
@ -1143,7 +1201,7 @@
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"recrawl-sync": ["recrawl-sync@2.2.3", "", { "dependencies": { "@cush/relative": "^1.0.0", "glob-regex": "^0.3.0", "slash": "^3.0.0", "sucrase": "^3.20.3", "tslib": "^1.9.3" } }, "sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ=="], "recrawl-sync": ["recrawl-sync@2.2.3", "", { "dependencies": { "@cush/relative": "^1.0.0", "glob-regex": "^0.3.0", "slash": "^3.0.0", "sucrase": "^3.20.3", "tslib": "^1.9.3" } }, "sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ=="],
@ -1211,6 +1269,8 @@
"serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
"server-destroy": ["server-destroy@1.0.1", "", {}, "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="], "server-destroy": ["server-destroy@1.0.1", "", {}, "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="],
@ -1279,6 +1339,8 @@
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
@ -1315,6 +1377,8 @@
"tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="], "toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="],
@ -1337,6 +1401,8 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-detect": ["type-detect@4.1.0", "", {}, "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw=="],
"type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
@ -1429,6 +1495,8 @@
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="], "winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
"workerpool": ["workerpool@6.5.1", "", {}, "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA=="],
"wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
@ -1447,6 +1515,8 @@
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"yargs-unparser": ["yargs-unparser@2.0.0", "", { "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" } }, "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA=="],
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="], "yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],
"yocto-spinner": ["yocto-spinner@0.2.2", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-21rPcM3e4vCpOXThiFRByX8amU5By1R0wNS8Oex+DP3YgC8xdU0vEJ/K8cbPLiIJVosSSysgcFof6s6MSD5/Vw=="], "yocto-spinner": ["yocto-spinner@0.2.2", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-21rPcM3e4vCpOXThiFRByX8amU5By1R0wNS8Oex+DP3YgC8xdU0vEJ/K8cbPLiIJVosSSysgcFof6s6MSD5/Vw=="],
@ -1473,6 +1543,8 @@
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"@koshnic/ratelimit/chai": ["chai@4.5.0", "", { "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", "deep-eql": "^4.1.3", "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", "type-detect": "^4.1.0" } }, "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
@ -1503,28 +1575,42 @@
"concurrently/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "concurrently/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], "hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
"jake/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "jake/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"jake/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"mocha/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
"mocha/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
"onnxruntime-web/onnxruntime-common": ["onnxruntime-common@1.22.0-dev.20250409-89f8206ba4", "", {}, "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ=="], "onnxruntime-web/onnxruntime-common": ["onnxruntime-common@1.22.0-dev.20250409-89f8206ba4", "", {}, "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ=="],
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], "pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"recrawl-sync/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "recrawl-sync/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
"sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"unified/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"unstorage/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
@ -1533,6 +1619,8 @@
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"yargs-unparser/camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
"@huggingface/transformers/onnxruntime-node/onnxruntime-common": ["onnxruntime-common@1.21.0", "", {}, "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ=="], "@huggingface/transformers/onnxruntime-node/onnxruntime-common": ["onnxruntime-common@1.21.0", "", {}, "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
@ -1541,6 +1629,16 @@
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"@koshnic/ratelimit/chai/assertion-error": ["assertion-error@1.1.0", "", {}, "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="],
"@koshnic/ratelimit/chai/check-error": ["check-error@1.0.3", "", { "dependencies": { "get-func-name": "^2.0.2" } }, "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg=="],
"@koshnic/ratelimit/chai/deep-eql": ["deep-eql@4.1.4", "", { "dependencies": { "type-detect": "^4.0.0" } }, "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg=="],
"@koshnic/ratelimit/chai/loupe": ["loupe@2.3.7", "", { "dependencies": { "get-func-name": "^2.0.1" } }, "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA=="],
"@koshnic/ratelimit/chai/pathval": ["pathval@1.1.1", "", {}, "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="],
"astro/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "astro/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"astro/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], "astro/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
@ -1587,14 +1685,18 @@
"concurrently/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "concurrently/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"jake/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "jake/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"jake/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"mocha/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], "pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], "pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="],
@ -1603,6 +1705,10 @@
"pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], "pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"unstorage/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"widest-line/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], "widest-line/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"widest-line/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "widest-line/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
@ -1615,6 +1721,8 @@
"colorspace/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "colorspace/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"mocha/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
} }
} }

View File

@ -1,16 +0,0 @@
{
"lock": false,
"workspace": ["./packages/crawler", "./packages/core"],
"nodeModulesDir": "auto",
"tasks": {
"crawler": "deno task --filter 'crawler' all",
"backend": "deno task --filter 'backend' start"
},
"fmt": {
"useTabs": true,
"lineWidth": 120,
"indentWidth": 4,
"semiColons": true,
"proseWrap": "always"
}
}

View File

@ -1,46 +0,0 @@
const requiredEnvVars = ["DB_HOST", "DB_NAME", "DB_USER", "DB_PASSWORD", "DB_PORT", "DB_NAME_CRED"];
const unsetVars = requiredEnvVars.filter((key) => process.env[key] === undefined);
if (unsetVars.length > 0) {
throw new Error(`Missing required environment variables: ${unsetVars.join(", ")}`);
}
const databaseHost = process.env["DB_HOST"]!;
const databaseName = process.env["DB_NAME"];
const databaseNameCred = process.env["DB_NAME_CRED"]!;
const databaseUser = process.env["DB_USER"]!;
const databasePassword = process.env["DB_PASSWORD"]!;
const databasePort = process.env["DB_PORT"]!;
export const postgresConfig = {
hostname: databaseHost,
port: parseInt(databasePort),
database: databaseName,
user: databaseUser,
password: databasePassword
};
export const postgresConfigNpm = {
host: databaseHost,
port: parseInt(databasePort),
database: databaseName,
username: databaseUser,
password: databasePassword
};
export const postgresCredConfigNpm = {
host: databaseHost,
port: parseInt(databasePort),
database: databaseNameCred,
username: databaseUser,
password: databasePassword
}
export const postgresConfigCred = {
hostname: databaseHost,
port: parseInt(databasePort),
database: databaseNameCred,
user: databaseUser,
password: databasePassword
};

View File

@ -1,5 +0,0 @@
import postgres from "postgres";
import { postgresConfigNpm, postgresCredConfigNpm } from "./config";
export const sql = postgres(postgresConfigNpm);
export const sqlCred = postgres(postgresCredConfigNpm)

View File

@ -1,4 +1,4 @@
import { sql } from "./db"; import { sql } from "@core/db/dbNew";
import type { VideoSnapshotType } from "@core/db/schema.d.ts"; import type { VideoSnapshotType } from "@core/db/schema.d.ts";
export async function getVideoSnapshots( export async function getVideoSnapshots(

View File

@ -0,0 +1,62 @@
import { Psql } from "@core/db/psql";
import { SlidingWindow } from "@core/mq/slidingWindow.ts";
import { redis } from "@core/db/redis.ts";
import { getIdentifier } from "@/middleware/rateLimiters.ts";
import { Context } from "hono";
type seconds = number;
export interface CaptchaDifficultyConfig {
global: boolean;
duration: seconds;
threshold: number;
difficulty: number;
}
export const getCaptchaDifficultyConfigByRoute = async (sql: Psql, route: string): Promise<CaptchaDifficultyConfig[]> => {
return sql<CaptchaDifficultyConfig[]>`
SELECT duration, threshold, difficulty, global
FROM captcha_difficulty_settings
WHERE CONCAT(method, '-', path) = ${route}
ORDER BY duration
`;
};
export const getCaptchaConfigMaxDuration = async (sql: Psql, route: string): Promise<seconds> => {
const rows = await sql<{max: number}[]>`
SELECT MAX(duration)
FROM captcha_difficulty_settings
WHERE CONCAT(method, '-', path) = ${route}
`;
if (rows.length < 1){
return Number.MAX_SAFE_INTEGER;
}
return rows[0].max;
}
export const getCurrentCaptchaDifficulty = async (sql: Psql, c: Context | string): Promise<number | null> => {
const isRoute = typeof c === "string";
const route = isRoute ? c : `${c.req.method}-${c.req.path}`
const configs = await getCaptchaDifficultyConfigByRoute(sql, route);
if (configs.length < 1) {
return null
}
else if (configs.length == 1) {
return configs[0].difficulty
}
const maxDuration = configs.reduce((max, config) =>
Math.max(max, config.duration), 0);
const slidingWindow = new SlidingWindow(redis, maxDuration);
for (let i = 1; i < configs.length; i++) {
const config = configs[i];
const lastConfig = configs[i - 1];
const identifier = isRoute ? c : getIdentifier(c, config.global);
const count = await slidingWindow.count(`captcha-${identifier}`, config.duration);
if (count >= config.threshold) {
continue;
}
return lastConfig.difficulty
}
return configs[configs.length-1].difficulty;
}

View File

@ -0,0 +1,13 @@
import { ErrorResponse } from "src/schema";
export const getJWTsecret = () => {
const secret = process.env["JWT_SECRET"];
if (!secret) {
const response: ErrorResponse = {
message: "JWT_SECRET is not set",
code: "SERVER_ERROR"
};
return [response, true];
}
return [secret, null];
}

View File

@ -0,0 +1,117 @@
import { Context, Next } from "hono";
import { ErrorResponse } from "src/schema";
import { SlidingWindow } from "@core/mq/slidingWindow.ts";
import { getCaptchaConfigMaxDuration, getCurrentCaptchaDifficulty } from "@/lib/auth/captchaDifficulty.ts";
import { sqlCred } from "@core/db/dbNew.ts";
import { redis } from "@core/db/redis.ts";
import { verify } from 'hono/jwt';
import { JwtTokenInvalid, JwtTokenExpired } from "hono/utils/jwt/types";
import { getJWTsecret } from "@/lib/auth/getJWTsecret.ts";
import { lockManager } from "@core/mq/lockManager.ts";
import { object, string, number, ValidationError } from "yup";
import { getIdentifier } from "@/middleware/rateLimiters.ts";
const tokenSchema = object({
exp: number().integer(),
id: string().length(6),
difficulty: number().integer().moreThan(0)
});
export const captchaMiddleware = async (c: Context, next: Next) => {
const authHeader = c.req.header("Authorization");
if (!authHeader) {
const response: ErrorResponse = {
message: "'Authorization' header is missing.",
code: "UNAUTHORIZED"
};
return c.json<ErrorResponse>(response, 401);
}
const authIsBearer = authHeader.startsWith("Bearer ");
if (!authIsBearer || authHeader.length < 8) {
const response: ErrorResponse = {
message: "'Authorization' header is invalid.",
code: "INVALID_HEADER"
};
return c.json<ErrorResponse>(response, 400);
}
const [r, err] = getJWTsecret();
if (err) {
return c.json<ErrorResponse>(r as ErrorResponse, 500);
}
const jwtSecret = r as string;
const token = authHeader.substring(7);
const path = c.req.path;
const method = c.req.method;
const route = `${method}-${path}`;
const requiredDifficulty = await getCurrentCaptchaDifficulty(sqlCred, c);
try {
const decodedPayload = await verify(token, jwtSecret);
const payload = await tokenSchema.validate(decodedPayload);
const difficulty = payload.difficulty;
const tokenID = payload.id;
const consumed = await lockManager.isLocked(tokenID);
if (consumed) {
const response: ErrorResponse = {
message: "Token has already been used.",
code: "INVALID_CREDENTIALS"
};
return c.json<ErrorResponse>(response, 401);
}
if (difficulty < requiredDifficulty) {
const response: ErrorResponse = {
message: "Token too weak.",
code: "UNAUTHORIZED"
};
return c.json<ErrorResponse>(response, 401);
}
const EXPIRE_FIVE_MINUTES = 300;
await lockManager.acquireLock(tokenID, EXPIRE_FIVE_MINUTES);
}
catch (e) {
if (e instanceof JwtTokenInvalid) {
const response: ErrorResponse = {
message: "Failed to verify the token.",
code: "INVALID_CREDENTIALS"
};
return c.json<ErrorResponse>(response, 400);
}
else if (e instanceof JwtTokenExpired) {
const response: ErrorResponse = {
message: "Token expired.",
code: "INVALID_CREDENTIALS"
};
return c.json<ErrorResponse>(response, 400);
}
else if (e instanceof ValidationError) {
const response: ErrorResponse = {
code: "INVALID_QUERY_PARAMS",
message: "Invalid query parameters",
errors: e.errors
};
return c.json<ErrorResponse>(response, 400);
}
else {
const response: ErrorResponse = {
message: "Unknown error.",
code: "UNKNOWN_ERROR"
};
return c.json<ErrorResponse>(response, 500);
}
}
const duration = await getCaptchaConfigMaxDuration(sqlCred, route);
const window = new SlidingWindow(redis, duration);
const identifierWithIP = getIdentifier(c, true);
const identifier = getIdentifier(c, false);
await window.event(`captcha-${identifier}`);
await window.event(`captcha-${identifierWithIP}`);
await next();
};

View File

@ -3,4 +3,4 @@ import { Context, Next } from "hono";
export const contentType = async (c: Context, next: Next) => { export const contentType = async (c: Context, next: Next) => {
await next(); await next();
c.header("Content-Type", "application/json; charset=utf-8"); c.header("Content-Type", "application/json; charset=utf-8");
}; };

View File

@ -1,19 +1,23 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { Variables } from "hono/types"; import { Variables } from "hono/types";
import { bodyLimitForPing } from "./bodyLimits.ts"; import { bodyLimitForPing } from "./bodyLimits.ts";
import { pingHandler } from "../routes/ping.ts"; import { pingHandler } from "routes/ping";
import { registerRateLimiter } from "./rateLimiters.ts"; import { registerRateLimiter } from "./rateLimiters.ts";
import { preetifyResponse } from "./preetifyResponse.ts"; import { preetifyResponse } from "./preetifyResponse.ts";
import { logger } from "./logger.ts"; import { logger } from "./logger.ts";
import { timing } from "hono/timing"; import { timing } from "hono/timing";
import { contentType } from "./contentType.ts"; import { contentType } from "./contentType.ts";
import { captchaMiddleware } from "./captcha.ts";
import { cors } from 'hono/cors';
export function configureMiddleWares(app: Hono<{Variables: Variables }>) { export function configureMiddleWares(app: Hono<{ Variables: Variables }>) {
app.all("*", cors());
app.use("*", contentType); app.use("*", contentType);
app.use(timing()); app.use(timing());
app.use("*", preetifyResponse); app.use("*", preetifyResponse);
app.use("*", logger({})); app.use("*", logger({}));
app.post("/user", registerRateLimiter); app.post("/user", registerRateLimiter);
app.post("/user", captchaMiddleware);
app.all("/ping", bodyLimitForPing, ...pingHandler); app.all("/ping", bodyLimitForPing, ...pingHandler);
} }

View File

@ -77,7 +77,7 @@ const defaultFormatter = (params) => {
`${methodColor} ${params.method.padEnd(6)}${reset} ${params.path}` `${methodColor} ${params.method.padEnd(6)}${reset} ${params.path}`
); );
}; };
type Ctx = Context type Ctx = Context;
export const logger = (config) => { export const logger = (config) => {
const { formatter = defaultFormatter, output = console, skipPaths = [], skip = null } = config; const { formatter = defaultFormatter, output = console, skipPaths = [], skip = null } = config;

View File

@ -15,4 +15,4 @@ export const preetifyResponse = async (c: Context, next: Next) => {
endTime(c, "seralize"); endTime(c, "seralize");
c.res = new Response(prettyJson, { headers: { "Content-Type": "text/plain; charset=utf-8" } }); c.res = new Response(prettyJson, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
} }
}; };

View File

@ -1,27 +1,44 @@
import { rateLimiter, Store } from "hono-rate-limiter";
import type { BlankEnv } from "hono/types"; import type { BlankEnv } from "hono/types";
import { MINUTE } from "@core/const/time.ts";
import { getConnInfo } from "hono/bun"; import { getConnInfo } from "hono/bun";
import type { Context } from "hono"; import { Context, Next } from "hono";
import { generateRandomId } from "@core/lib/randomID.ts";
import { RateLimiter } from "@koshnic/ratelimit";
import { ErrorResponse } from "@/src/schema";
import { redis } from "@core/db/redis.ts"; import { redis } from "@core/db/redis.ts";
import { RedisStore } from "rate-limit-redis";
export const registerRateLimiter = rateLimiter<BlankEnv, "/user", {}>({ export const getIdentifier = (c: Context, includeIP: boolean = true) => {
windowMs: 60 * MINUTE, let ipAddr = generateRandomId(6);
limit: 10, const info = getConnInfo(c);
standardHeaders: "draft-6", if (info.remote && info.remote.address) {
keyGenerator: (c) => { ipAddr = info.remote.address;
const info = getConnInfo(c as unknown as Context<BlankEnv, "/user", {}>); }
if (!info.remote || !info.remote.address) { const forwardedFor = c.req.header("X-Forwarded-For");
return crypto.randomUUID(); if (forwardedFor) {
} ipAddr = forwardedFor.split(",")[0];
const addr = info.remote.address; }
const path = new URL(c.req.url).pathname; const path = c.req.path;
const method = c.req.method; const method = c.req.method;
return `${method}-${path}@${addr}`; const ipIdentifier = includeIP ? `@${ipAddr}` : "";
}, return `${method}-${path}${ipIdentifier}`;
store: new RedisStore({ };
// @ts-expect-error - Known issue: the `c`all` function is not present in @types/ioredis
sendCommand: (...args: string[]) => redis.call(...args) export const registerRateLimiter = async (c: Context<BlankEnv, "/user", {}>, next: Next) => {
}) as unknown as Store const limiter = new RateLimiter(redis);
}); const identifier = getIdentifier(c, true);
const { allowed, retryAfter } = await limiter.allow(identifier, {
burst: 5,
ratePerPeriod: 5,
period: 120,
cost: 1
});
if (!allowed) {
const response: ErrorResponse = {
message: `Too many requests, please retry after ${Math.round(retryAfter)} seconds.`,
code: "RATE_LIMIT_EXCEEDED"
};
return c.json<ErrorResponse>(response, 429);
}
await next();
};

View File

@ -10,6 +10,7 @@
"hono": "^4.7.8", "hono": "^4.7.8",
"hono-rate-limiter": "^0.4.2", "hono-rate-limiter": "^0.4.2",
"ioredis": "^5.6.1", "ioredis": "^5.6.1",
"limiter": "^3.0.0",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"rate-limit-redis": "^4.2.0", "rate-limit-redis": "^4.2.0",
"yup": "^1.6.1", "yup": "^1.6.1",

View File

@ -7,4 +7,4 @@ export const notFoundRoute = (c: Context) => {
}, },
404 404
); );
}; };

View File

@ -0,0 +1,88 @@
import { Context } from "hono";
import { Bindings, BlankEnv } from "hono/types";
import { ErrorResponse } from "src/schema";
import { createHandlers } from "src/utils.ts";
import { sign } from 'hono/jwt'
import { generateRandomId } from "@core/lib/randomID.ts";
import { getJWTsecret } from "lib/auth/getJWTsecret.ts";
interface CaptchaResponse {
success: boolean;
difficulty?: number;
error?: string;
}
const getChallengeVerificationResult = async (id: string, ans: string) => {
const baseURL = process.env["UCAPTCHA_URL"];
const url = new URL(baseURL);
url.pathname = `/challenge/${id}/validation`;
return await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
y: ans
})
});
};
export const verifyChallengeHandler = createHandlers(
async (c: Context<BlankEnv & { Bindings: Bindings }, "/captcha/:id/result">) => {
const id = c.req.param("id");
const ans = c.req.query("ans");
if (!ans) {
const response: ErrorResponse = {
message: "Missing required query parameter: ans",
code: "INVALID_QUERY_PARAMS"
};
return c.json<ErrorResponse>(response, 400);
}
const res = await getChallengeVerificationResult(id, ans);
const data: CaptchaResponse = await res.json();
if (data.error && res.status === 404) {
const response: ErrorResponse = {
message: data.error,
code: "ENTITY_NOT_FOUND"
};
return c.json<ErrorResponse>(response, 401);
} else if (data.error && res.status === 400) {
const response: ErrorResponse = {
message: data.error,
code: "INVALID_QUERY_PARAMS"
};
return c.json<ErrorResponse>(response, 400);
} else if (data.error) {
const response: ErrorResponse = {
message: data.error,
code: "UNKNOWN_ERROR"
};
return c.json<ErrorResponse>(response, 500);
}
if (!data.success) {
const response: ErrorResponse = {
message: "Incorrect answer",
code: "INVALID_CREDENTIALS"
};
return c.json<ErrorResponse>(response, 401);
}
const [r, err] = getJWTsecret();
if (err) {
return c.json<ErrorResponse>(r as ErrorResponse, 500);
}
const jwtSecret = r as string;
const tokenID = generateRandomId(6);
const NOW = Math.floor(Date.now() / 1000)
const FIVE_MINUTES_LATER = NOW + 60 * 5;
const jwt = await sign({
difficulty: data.difficulty!,
id: tokenID,
exp: FIVE_MINUTES_LATER
}, jwtSecret);
return c.json({
token: jwt
});
}
);

View File

@ -0,0 +1,43 @@
import { createHandlers } from "src/utils.ts";
import { object, string, ValidationError } from "yup";
import { ErrorResponse } from "src/schema";
import { getCurrentCaptchaDifficulty } from "@/lib/auth/captchaDifficulty.ts";
import { sqlCred } from "@core/db/dbNew.ts";
const queryParamsSchema = object({
route: string().matches(/(?:GET|POST|PUT|PATCH|DELETE)-\/.*/g)
});
export const getCaptchaDifficultyHandler = createHandlers(async (c) => {
try {
const queryParams = await queryParamsSchema.validate(c.req.query());
const { route } = queryParams;
const difficulty = await getCurrentCaptchaDifficulty(sqlCred, route);
if (!difficulty) {
const response: ErrorResponse<unknown> = {
code: "ENTITY_NOT_FOUND",
message: "No difficulty configs found for this route."
};
return c.json<ErrorResponse<unknown>>(response, 404);
}
return c.json({
"difficulty": difficulty
});
} catch (e: unknown) {
if (e instanceof ValidationError) {
const response: ErrorResponse = {
code: "INVALID_QUERY_PARAMS",
message: "Invalid query parameters",
errors: e.errors
};
return c.json<ErrorResponse>(response, 400);
} else {
const response: ErrorResponse<unknown> = {
code: "UNKNOWN_ERROR",
message: "Unknown error",
errors: [e]
};
return c.json<ErrorResponse<unknown>>(response, 500);
}
}
});

View File

@ -0,0 +1,2 @@
export * from "./session/POST.ts";
export * from "./[id]/result/GET.ts";

View File

@ -0,0 +1,59 @@
import { createHandlers } from "src/utils.ts";
import { getCurrentCaptchaDifficulty } from "@/lib/auth/captchaDifficulty.ts";
import { sqlCred } from "@core/db/dbNew.ts";
import { object, string, ValidationError } from "yup";
import { ErrorResponse } from "@/src/schema";
import type { ContentfulStatusCode } from "hono/utils/http-status";
const bodySchema = object({
route: string().matches(/(?:GET|POST|PUT|PATCH|DELETE)-\/.*/g)
});
interface CaptchaSessionResponse {
success: boolean;
id: string;
g: string;
n: string;
t: number;
}
const createNewChallenge = async (difficulty: number) => {
const baseURL = process.env["UCAPTCHA_URL"];
const url = new URL(baseURL);
url.pathname = "/challenge";
return await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
difficulty: difficulty,
})
});
}
export const createCaptchaSessionHandler = createHandlers(async (c) => {
try {
const requestBody = await bodySchema.validate(await c.req.json());
const { route } = requestBody;
const difficuly = await getCurrentCaptchaDifficulty(sqlCred, route)
const res = await createNewChallenge(difficuly);
return c.json<CaptchaSessionResponse|unknown>(await res.json(), res.status as ContentfulStatusCode);
} catch (e: unknown) {
if (e instanceof ValidationError) {
const response: ErrorResponse = {
code: "INVALID_QUERY_PARAMS",
message: "Invalid query parameters",
errors: e.errors
};
return c.json<ErrorResponse>(response, 400);
} else {
const response: ErrorResponse<unknown> = {
code: "UNKNOWN_ERROR",
message: "Unknown error",
errors: [e]
};
return c.json<ErrorResponse<unknown>>(response, 500);
}
}
});

View File

@ -1,17 +1,29 @@
import { rootHandler } from "./root/root.ts"; import { getSingerForBirthday, pickSinger, pickSpecialSinger, type Singer } from "lib/const/singers.ts";
import { pingHandler } from "./ping.ts"; import { VERSION } from "src/main.ts";
import { getSnapshotsHanlder } from "./snapshots.ts"; import { createHandlers } from "src/utils.ts";
import { registerHandler } from "./user.ts";
import { videoInfoHandler } from "db/videoInfo.ts";
import { Hono } from "hono";
import { Variables } from "hono/types";
export function configureRoutes(app: Hono<{Variables: Variables }>) { export const rootHandler = createHandlers((c) => {
app.get("/", ...rootHandler); let singer: Singer | Singer[];
app.all("/ping", ...pingHandler); const shouldShowSpecialSinger = Math.random() < 0.016;
if (getSingerForBirthday().length !== 0) {
app.get("/video/:id/snapshots", ...getSnapshotsHanlder); singer = JSON.parse(JSON.stringify(getSingerForBirthday())) as Singer[];
app.post("/user", ...registerHandler); for (const s of singer) {
delete s.birthday;
app.get("/video/:id/info", ...videoInfoHandler); s.message = `${s.name}生日快乐~`;
} }
} else if (shouldShowSpecialSinger) {
singer = pickSpecialSinger();
} else {
singer = pickSinger();
}
return c.json({
project: {
name: "中V档案馆",
motto: "一起唱吧,心中的歌!"
},
status: 200,
version: VERSION,
time: Date.now(),
singer: singer
});
});

View File

@ -1,24 +0,0 @@
import { getClientIP } from "middleware/logger.ts";
import { createHandlers } from "../src/utils.ts";
import { VERSION } from "../src/main.ts";
export const pingHandler = createHandlers(async (c) => {
const requestHeaders = c.req.raw.headers;
return c.json({
"message": "pong",
"request": {
"headers": requestHeaders,
"ip": getClientIP(c),
"mode": c.req.raw.mode,
"method": c.req.method,
"query": new URL(c.req.url).searchParams,
"body": await c.req.text(),
"url": c.req.raw.url
},
"response": {
"time": new Date().getTime(),
"status": 200,
"version": VERSION,
}
});
});

View File

@ -0,0 +1,24 @@
import { getClientIP } from "middleware/logger.ts";
import { createHandlers } from "src/utils.ts";
import { VERSION } from "src/main.ts";
export const pingHandler = createHandlers(async (c) => {
const requestHeaders = c.req.raw.headers;
return c.json({
message: "pong",
request: {
headers: requestHeaders,
ip: getClientIP(c),
mode: c.req.raw.mode,
method: c.req.method,
query: new URL(c.req.url).searchParams,
body: await c.req.text(),
url: c.req.raw.url
},
response: {
time: new Date().getTime(),
status: 200,
version: VERSION
}
});
});

View File

@ -1,29 +0,0 @@
import { getSingerForBirthday, pickSinger, pickSpecialSinger, type Singer } from "./singers.ts";
import { VERSION } from "../../src/main.ts";
import { createHandlers } from "../../src/utils.ts";
export const rootHandler = createHandlers((c) => {
let singer: Singer | Singer[];
const shouldShowSpecialSinger = Math.random() < 0.016;
if (getSingerForBirthday().length !== 0) {
singer = JSON.parse(JSON.stringify(getSingerForBirthday())) as Singer[];
for (const s of singer) {
delete s.birthday;
s.message = `${s.name}生日快乐~`;
}
} else if (shouldShowSpecialSinger) {
singer = pickSpecialSinger();
} else {
singer = pickSinger();
}
return c.json({
project: {
name: "中V档案馆",
motto: "一起唱吧,心中的歌!"
},
status: 200,
version: VERSION,
time: Date.now(),
singer: singer
});
});

View File

@ -0,0 +1 @@
export * from "./register.ts";

View File

@ -3,7 +3,7 @@ import Argon2id from "@rabbit-company/argon2id";
import { object, string, ValidationError } from "yup"; import { object, string, ValidationError } from "yup";
import type { Context } from "hono"; import type { Context } from "hono";
import type { Bindings, BlankEnv, BlankInput } from "hono/types"; import type { Bindings, BlankEnv, BlankInput } from "hono/types";
import { sqlCred } from "db/db.ts"; import { sqlCred } from "@core/db/dbNew.ts";
import { ErrorResponse, StatusResponse } from "src/schema"; import { ErrorResponse, StatusResponse } from "src/schema";
const RegistrationBodySchema = object({ const RegistrationBodySchema = object({
@ -44,7 +44,7 @@ export const registerHandler = createHandlers(async (c: ContextType) => {
const response: StatusResponse = { const response: StatusResponse = {
message: `User '${username}' registered successfully.` message: `User '${username}' registered successfully.`
} };
return c.json<StatusResponse>(response, 201); return c.json<StatusResponse>(response, 201);
} catch (e) { } catch (e) {
@ -53,21 +53,21 @@ export const registerHandler = createHandlers(async (c: ContextType) => {
message: "Invalid registration data.", message: "Invalid registration data.",
errors: e.errors, errors: e.errors,
code: "INVALID_PAYLOAD" code: "INVALID_PAYLOAD"
} };
return c.json<ErrorResponse<string>>(response, 400); return c.json<ErrorResponse<string>>(response, 400);
} else if (e instanceof SyntaxError) { } else if (e instanceof SyntaxError) {
const response: ErrorResponse<string> = { const response: ErrorResponse<string> = {
message: "Invalid JSON payload.", message: "Invalid JSON payload.",
errors: [e.message], errors: [e.message],
code: "INVALID_FORMAT" code: "INVALID_FORMAT"
} };
return c.json<ErrorResponse<string>>(response, 400); return c.json<ErrorResponse<string>>(response, 400);
} else { } else {
const response: ErrorResponse<string> = { const response: ErrorResponse<string> = {
message: "Invalid JSON payload.", message: "Unknown error.",
errors: [(e as Error).message], errors: [(e as Error).message],
code: "UNKNOWN_ERR" code: "UNKNOWN_ERROR"
} };
return c.json<ErrorResponse<string>>(response, 500); return c.json<ErrorResponse<string>>(response, 500);
} }
} }

View File

@ -1,15 +1,15 @@
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { redis } from "@core/db/redis.ts"; import { redis } from "@core/db/redis.ts";
import { sql } from "./db.ts"; import { sql } from "@core/db/dbNew.ts";
import { number, ValidationError } from "yup"; import { number, ValidationError } from "yup";
import { createHandlers } from "../src/utils.ts"; import { createHandlers } from "@/src/utils.ts";
import { getVideoInfo, getVideoInfoByBV } from "@core/net/getVideoInfo.ts"; import { getVideoInfo, getVideoInfoByBV } from "@core/net/getVideoInfo.ts";
import { idSchema } from "../routes/snapshots.ts"; import { idSchema } from "./snapshots.ts";
import { NetSchedulerError } from "@core/net/delegate.ts"; import { NetSchedulerError } from "@core/net/delegate.ts";
import type { Context } from "hono"; import type { Context } from "hono";
import type { BlankEnv, BlankInput } from "hono/types"; import type { BlankEnv, BlankInput } from "hono/types";
import type { VideoInfoData } from "@core/net/bilibili.d.ts"; import type { VideoInfoData } from "@core/net/bilibili.d.ts";
import { startTime, endTime } from 'hono/timing' import { startTime, endTime } from "hono/timing";
const CACHE_EXPIRATION_SECONDS = 60; const CACHE_EXPIRATION_SECONDS = 60;
@ -34,7 +34,7 @@ async function insertVideoSnapshot(data: VideoInfoData) {
} }
export const videoInfoHandler = createHandlers(async (c: ContextType) => { export const videoInfoHandler = createHandlers(async (c: ContextType) => {
startTime(c, 'parse', 'Parse the request'); startTime(c, "parse", "Parse the request");
try { try {
const id = await idSchema.validate(c.req.param("id")); const id = await idSchema.validate(c.req.param("id"));
let videoId: string | number = id as string; let videoId: string | number = id as string;
@ -45,33 +45,33 @@ export const videoInfoHandler = createHandlers(async (c: ContextType) => {
} }
const cacheKey = `cvsa:videoInfo:${videoId}`; const cacheKey = `cvsa:videoInfo:${videoId}`;
endTime(c, 'parse'); endTime(c, "parse");
startTime(c, 'cache', 'Check for cached data'); startTime(c, "cache", "Check for cached data");
const cachedData = await redis.get(cacheKey); const cachedData = await redis.get(cacheKey);
endTime(c, 'cache'); endTime(c, "cache");
if (cachedData) { if (cachedData) {
return c.json(JSON.parse(cachedData)); return c.json(JSON.parse(cachedData));
} }
startTime(c, 'net', 'Fetch data'); startTime(c, "net", "Fetch data");
let result: VideoInfoData | number; let result: VideoInfoData | number;
if (typeof videoId === "number") { if (typeof videoId === "number") {
result = await getVideoInfo(videoId, "getVideoInfo"); result = await getVideoInfo(videoId, "getVideoInfo");
} else { } else {
result = await getVideoInfoByBV(videoId, "getVideoInfo"); result = await getVideoInfoByBV(videoId, "getVideoInfo");
} }
endTime(c, 'net'); endTime(c, "net");
if (typeof result === "number") { if (typeof result === "number") {
return c.json({ message: "Error fetching video info", code: result }, 500); return c.json({ message: "Error fetching video info", code: result }, 500);
} }
startTime(c, 'db', 'Write data to database'); startTime(c, "db", "Write data to database");
await redis.setex(cacheKey, CACHE_EXPIRATION_SECONDS, JSON.stringify(result)); await redis.setex(cacheKey, CACHE_EXPIRATION_SECONDS, JSON.stringify(result));
await insertVideoSnapshot(result); await insertVideoSnapshot(result);
endTime(c, 'db'); endTime(c, "db");
return c.json(result); return c.json(result);
} catch (e) { } catch (e) {
if (e instanceof ValidationError) { if (e instanceof ValidationError) {

View File

@ -1,10 +1,10 @@
import type { Context } from "hono"; import type { Context } from "hono";
import { createHandlers } from "../src/utils.ts"; import { createHandlers } from "src/utils.ts";
import type { BlankEnv, BlankInput } from "hono/types"; import type { BlankEnv, BlankInput } from "hono/types";
import { getVideoSnapshots, getVideoSnapshotsByBV } from "../db/videoSnapshot.ts"; import { getVideoSnapshots, getVideoSnapshotsByBV } from "db/snapshots.ts";
import type { VideoSnapshotType } from "@core/db/schema.d.ts"; import type { VideoSnapshotType } from "@core/db/schema.d.ts";
import { boolean, mixed, number, object, ValidationError } from "yup"; import { boolean, mixed, number, object, ValidationError } from "yup";
import { ErrorResponse } from "../src/schema"; import { ErrorResponse } from "src/schema";
import { startTime, endTime } from "hono/timing"; import { startTime, endTime } from "hono/timing";
const SnapshotQueryParamsSchema = object({ const SnapshotQueryParamsSchema = object({
@ -96,7 +96,7 @@ export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
return c.json<ErrorResponse<string>>(response, 400); return c.json<ErrorResponse<string>>(response, 400);
} else { } else {
const response: ErrorResponse<unknown> = { const response: ErrorResponse<unknown> = {
code: "UNKNOWN_ERR", code: "UNKNOWN_ERROR",
message: "Unhandled error", message: "Unhandled error",
errors: [e] errors: [e]
}; };

View File

@ -0,0 +1,2 @@
export * from "./[id]/info";
export * from "./[id]/snapshots";

View File

@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import type { TimingVariables } from "hono/timing"; import type { TimingVariables } from "hono/timing";
import { startServer } from "./startServer.ts"; import { startServer } from "./startServer.ts";
import { configureRoutes } from "routes"; import { configureRoutes } from "./routing.ts";
import { configureMiddleWares } from "middleware"; import { configureMiddleWares } from "middleware";
import { notFoundRoute } from "routes/404.ts"; import { notFoundRoute } from "routes/404.ts";

View File

@ -0,0 +1,23 @@
import { rootHandler } from "routes";
import { pingHandler } from "routes/ping";
import { registerHandler } from "routes/user";
import { videoInfoHandler, getSnapshotsHanlder } from "routes/video";
import { Hono } from "hono";
import { Variables } from "hono/types";
import { createCaptchaSessionHandler, verifyChallengeHandler } from "routes/captcha";
import { getCaptchaDifficultyHandler } from "../routes/captcha/difficulty/GET.ts";
export function configureRoutes(app: Hono<{ Variables: Variables }>) {
app.get("/", ...rootHandler);
app.all("/ping", ...pingHandler);
app.get("/video/:id/snapshots", ...getSnapshotsHanlder);
app.post("/user", ...registerHandler);
app.get("/video/:id/info", ...videoInfoHandler);
app.post("/captcha/session", ...createCaptchaSessionHandler);
app.get("/captcha/:id/result", ...verifyChallengeHandler);
app.get("/captcha/difficulty", ...getCaptchaDifficultyHandler)
}

View File

@ -1,11 +1,22 @@
type ErrorCode = "INVALID_QUERY_PARAMS" | "UNKNOWN_ERR" | "INVALID_PAYLOAD" | "INVALID_FORMAT" | "BODY_TOO_LARGE"; type ErrorCode =
| "INVALID_QUERY_PARAMS"
| "UNKNOWN_ERROR"
| "INVALID_PAYLOAD"
| "INVALID_FORMAT"
| "INVALID_HEADER"
| "BODY_TOO_LARGE"
| "UNAUTHORIZED"
| "INVALID_CREDENTIALS"
| "ENTITY_NOT_FOUND"
| "SERVER_ERROR"
| "RATE_LIMIT_EXCEEDED";
export interface ErrorResponse<E> { export interface ErrorResponse<E=string> {
code: ErrorCode code: ErrorCode;
message: string; message: string;
errors: E[]; errors?: E[];
} }
export interface StatusResponse { export interface StatusResponse {
message: string; message: string;
} }

View File

@ -32,7 +32,7 @@ function logStartup(hostname: string, port: number, wasAutoIncremented: boolean,
console.log("\nPress Ctrl+C to quit."); console.log("\nPress Ctrl+C to quit.");
} }
export async function startServer(app: Hono<{Variables: Variables }>) { export async function startServer(app: Hono<{ Variables: Variables }>) {
const NODE_ENV = process.env.NODE_ENV || "production"; const NODE_ENV = process.env.NODE_ENV || "production";
const HOST = process.env.HOST ?? (NODE_ENV === "development" ? "0.0.0.0" : "127.0.0.1"); const HOST = process.env.HOST ?? (NODE_ENV === "development" ? "0.0.0.0" : "127.0.0.1");
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined; const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;

View File

@ -10,6 +10,7 @@
"skipLibCheck": true, "skipLibCheck": true,
"paths": { "paths": {
"@core/*": ["../core/*"], "@core/*": ["../core/*"],
"@/*": ["./*"],
"@crawler/*": ["../crawler/*"] "@crawler/*": ["../crawler/*"]
}, },
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,

View File

@ -1,6 +1,8 @@
import postgres from "postgres"; import postgres from "postgres";
import { postgresConfigNpm } from "./pgConfigNew"; import { postgresConfigCred, postgresConfig } from "./pgConfigNew";
export const sql = postgres(postgresConfigNpm); export const sql = postgres(postgresConfig);
export const sqlTest = postgres(postgresConfigNpm); export const sqlCred = postgres(postgresConfigCred);
export const sqlTest = postgres(postgresConfig);

View File

@ -18,14 +18,6 @@ const databasePassword = getEnvVar("DB_PASSWORD")!;
const databasePort = getEnvVar("DB_PORT")!; const databasePort = getEnvVar("DB_PORT")!;
export const postgresConfig = { export const postgresConfig = {
hostname: databaseHost,
port: parseInt(databasePort),
database: databaseName,
user: databaseUser,
password: databasePassword
};
export const postgresConfigNpm = {
host: databaseHost, host: databaseHost,
port: parseInt(databasePort), port: parseInt(databasePort),
database: databaseName, database: databaseName,

View File

@ -1,3 +1,3 @@
import type postgres from "postgres"; import type postgres from "postgres";
export type Psql = postgres.Sql<{}>; export type Psql = postgres.Sql;

View File

@ -1,17 +0,0 @@
{
"name": "@cvsa/core",
"exports": "./main.ts",
"imports": {
"ioredis": "npm:ioredis",
"log/": "./log/",
"db/": "./db/",
"$std/": "https://deno.land/std@0.216.0/",
"mq/": "./mq/",
"chalk": "npm:chalk",
"winston": "npm:winston",
"logform": "npm:logform",
"@core/": "./",
"child_process": "node:child_process",
"util": "node:util"
}
}

View File

@ -0,0 +1,15 @@
export function generateRandomId(length: number): string {
const characters = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
const charactersLength = characters.length;
const randomBytes = new Uint8Array(length);
crypto.getRandomValues(randomBytes);
let result = '';
for (let i = 0; i < length; i++) {
const randomIndex = randomBytes[i] % charactersLength;
result += characters.charAt(randomIndex);
}
return result;
}

View File

@ -1,5 +1,5 @@
import { Redis } from "ioredis"; import { Redis } from "ioredis";
import { redis } from "../../core/db/redis.ts"; import { redis } from "@core/db/redis.ts";
class LockManager { class LockManager {
private redis: Redis; private redis: Redis;

View File

@ -0,0 +1,55 @@
import { RateLimiter as Limiter } from "@koshnic/ratelimit";
import { redis } from "@core/db/redis.ts";
export interface RateLimiterConfig {
duration: number;
max: number;
}
export class RateLimiterError extends Error {
public code: string;
constructor(message: string) {
super(message);
this.name = "RateLimiterError";
this.code = "RATE_LIMIT_EXCEEDED";
}
}
export class MultipleRateLimiter {
private readonly name: string;
private readonly configs: RateLimiterConfig[] = [];
private readonly limiter: Limiter;
/*
* @param name The name of the rate limiter
* @param configs The configuration of the rate limiter, containing:
* - duration: The duration of window in seconds
* - max: The maximum number of tokens allowed in the window
*/
constructor(
name: string,
configs: RateLimiterConfig[]
) {
this.configs = configs;
this.limiter = new Limiter(redis);
this.name = name;
}
/*
* Trigger an event in the rate limiter
*/
async trigger(shouldThrow = true): Promise<void> {
for (let i = 0; i < this.configs.length; i++) {
const { duration, max } = this.configs[i];
const { allowed } = await this.limiter.allow(`cvsa:${this.name}_${i}`, {
burst: max,
ratePerPeriod: max,
period: duration,
cost: 1
});
if (!allowed && shouldThrow) {
throw new RateLimiterError("Rate limit exceeded")
}
}
}
}

View File

@ -1,56 +0,0 @@
import type { SlidingWindow } from "./slidingWindow.ts";
export interface RateLimiterConfig {
window: SlidingWindow;
max: number;
}
export class RateLimiter {
private readonly configs: RateLimiterConfig[];
private readonly configEventNames: string[];
/*
* @param name The name of the rate limiter
* @param configs The configuration of the rate limiter, containing:
* - window: The sliding window to use
* - max: The maximum number of events allowed in the window
*/
constructor(name: string, configs: RateLimiterConfig[]) {
this.configs = configs;
this.configEventNames = configs.map((_, index) => `${name}_config_${index}`);
}
/*
* Check if the event has reached the rate limit
*/
async getAvailability(): Promise<boolean> {
for (let i = 0; i < this.configs.length; i++) {
const config = this.configs[i];
const eventName = this.configEventNames[i];
const count = await config.window.count(eventName);
if (count >= config.max) {
return false;
}
}
return true;
}
/*
* Trigger an event in the rate limiter
*/
async trigger(): Promise<void> {
for (let i = 0; i < this.configs.length; i++) {
const config = this.configs[i];
const eventName = this.configEventNames[i];
await config.window.event(eventName);
}
}
async clear(): Promise<void> {
for (let i = 0; i < this.configs.length; i++) {
const config = this.configs[i];
const eventName = this.configEventNames[i];
await config.window.clear(eventName);
}
}
}

View File

@ -32,14 +32,20 @@ export class SlidingWindow {
/* /*
* Count the number of events in the sliding window * Count the number of events in the sliding window
* @param eventName The name of the event * @param {string} eventName The name of the event
* @param {number} [duration] The duration of the window in seconds
*/ */
async count(eventName: string): Promise<number> { async count(eventName: string, duration?: number): Promise<number> {
const key = `cvsa:sliding_window:${eventName}`; const key = `cvsa:sliding_window:${eventName}`;
const now = Date.now(); const now = Date.now();
// Remove timestamps outside the window // Remove timestamps outside the window
await this.redis.zremrangebyscore(key, 0, now - this.windowSize); await this.redis.zremrangebyscore(key, 0, now - this.windowSize);
if (duration) {
return this.redis.zcount(key, now - duration * 1000, now);
}
// Get the number of timestamps in the window // Get the number of timestamps in the window
return this.redis.zcard(key); return this.redis.zcard(key);
} }
@ -48,4 +54,4 @@ export class SlidingWindow {
const key = `cvsa:sliding_window:${eventName}`; const key = `cvsa:sliding_window:${eventName}`;
return this.redis.del(key); return this.redis.del(key);
} }
} }

View File

@ -1,9 +1,7 @@
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { RateLimiter, type RateLimiterConfig } from "mq/rateLimiter.ts"; import { MultipleRateLimiter, RateLimiterError, type RateLimiterConfig } from "@core/mq/multipleRateLimiter.ts";
import { SlidingWindow } from "mq/slidingWindow.ts";
import { redis } from "db/redis.ts";
import { ReplyError } from "ioredis"; import { ReplyError } from "ioredis";
import { SECOND } from "../const/time.ts"; import { SECOND } from "@core/const/time.ts";
import { spawn, SpawnOptions } from "child_process"; import { spawn, SpawnOptions } from "child_process";
export function spawnPromise( export function spawnPromise(
@ -73,11 +71,11 @@ export class NetSchedulerError extends Error {
} }
type LimiterMap = { type LimiterMap = {
[name: string]: RateLimiter; [name: string]: MultipleRateLimiter;
}; };
type OptionalLimiterMap = { type OptionalLimiterMap = {
[name: string]: RateLimiter | null; [name: string]: MultipleRateLimiter | null;
}; };
type TaskMap = { type TaskMap = {
@ -121,20 +119,23 @@ class NetworkDelegate {
const proxies = this.getTaskProxies(taskName); const proxies = this.getTaskProxies(taskName);
for (const proxyName of proxies) { for (const proxyName of proxies) {
const limiterId = "proxy-" + proxyName + "-" + taskName; const limiterId = "proxy-" + proxyName + "-" + taskName;
this.proxyLimiters[limiterId] = config ? new RateLimiter(limiterId, config) : null; this.proxyLimiters[limiterId] = config ? new MultipleRateLimiter(limiterId, config) : null;
} }
} }
async triggerLimiter(task: string, proxy: string): Promise<void> { async triggerLimiter(task: string, proxy: string, force: boolean = false): Promise<void> {
const limiterId = "proxy-" + proxy + "-" + task; const limiterId = "proxy-" + proxy + "-" + task;
const providerLimiterId = "provider-" + proxy + "-" + this.tasks[task].provider; const providerLimiterId = "provider-" + proxy + "-" + this.tasks[task].provider;
try { try {
await this.proxyLimiters[limiterId]?.trigger(); await this.proxyLimiters[limiterId]?.trigger(!force);
await this.providerLimiters[providerLimiterId]?.trigger(); await this.providerLimiters[providerLimiterId]?.trigger(!force);
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
if (e instanceof ReplyError) { if (e instanceof ReplyError) {
logger.error(error, "redis"); logger.error(error, "redis");
} else if (e instanceof RateLimiterError) {
// Re-throw it to ensure this.request can catch it
throw e;
} }
logger.warn(`Unhandled error: ${error.message}`, "mq", "proxyRequest"); logger.warn(`Unhandled error: ${error.message}`, "mq", "proxyRequest");
} }
@ -149,7 +150,7 @@ class NetworkDelegate {
} }
for (const proxyName of bindProxies) { for (const proxyName of bindProxies) {
const limiterId = "provider-" + proxyName + "-" + providerName; const limiterId = "provider-" + proxyName + "-" + providerName;
this.providerLimiters[limiterId] = new RateLimiter(limiterId, config); this.providerLimiters[limiterId] = new MultipleRateLimiter(limiterId, config);
} }
} }
@ -168,9 +169,15 @@ class NetworkDelegate {
// find a available proxy // find a available proxy
const proxiesNames = this.getTaskProxies(task); const proxiesNames = this.getTaskProxies(task);
for (const proxyName of shuffleArray(proxiesNames)) { for (const proxyName of shuffleArray(proxiesNames)) {
if (await this.getProxyAvailability(proxyName, task)) { try {
return await this.proxyRequest<R>(url, proxyName, task, method); return await this.proxyRequest<R>(url, proxyName, task, method);
} }
catch (e) {
if (e instanceof RateLimiterError) {
continue;
}
throw e;
}
} }
throw new NetSchedulerError("No proxy is available currently.", "NO_PROXY_AVAILABLE"); throw new NetSchedulerError("No proxy is available currently.", "NO_PROXY_AVAILABLE");
} }
@ -202,16 +209,8 @@ class NetworkDelegate {
throw new NetSchedulerError(`Proxy "${proxyName}" not found`, "PROXY_NOT_FOUND"); throw new NetSchedulerError(`Proxy "${proxyName}" not found`, "PROXY_NOT_FOUND");
} }
if (!force) { await this.triggerLimiter(task, proxyName, force);
const isAvailable = await this.getProxyAvailability(proxyName, task);
const limiter = "proxy-" + proxyName + "-" + task;
if (!isAvailable) {
throw new NetSchedulerError(`Proxy "${limiter}" is rate limited`, "PROXY_RATE_LIMITED");
}
}
const result = await this.makeRequest<R>(url, proxy, method); const result = await this.makeRequest<R>(url, proxy, method);
await this.triggerLimiter(task, proxyName);
return result; return result;
} }
@ -226,32 +225,6 @@ class NetworkDelegate {
} }
} }
private async getProxyAvailability(proxyName: string, taskName: string): Promise<boolean> {
try {
const task = this.tasks[taskName];
const provider = task.provider;
const proxyLimiterId = "proxy-" + proxyName + "-" + task;
const providerLimiterId = "provider-" + proxyName + "-" + provider;
if (!this.proxyLimiters[proxyLimiterId]) {
const providerLimiter = this.providerLimiters[providerLimiterId];
return await providerLimiter.getAvailability();
}
const proxyLimiter = this.proxyLimiters[proxyLimiterId];
const providerLimiter = this.providerLimiters[providerLimiterId];
const providerAvailable = await providerLimiter.getAvailability();
const proxyAvailable = await proxyLimiter.getAvailability();
return providerAvailable && proxyAvailable;
} catch (e) {
const error = e as Error;
if (e instanceof ReplyError) {
logger.error(error, "redis");
return false;
}
logger.error(error, "mq", "getProxyAvailability");
return false;
}
}
private async nativeRequest<R>(url: string, method: string): Promise<R> { private async nativeRequest<R>(url: string, method: string): Promise<R> {
try { try {
const controller = new AbortController(); const controller = new AbortController();
@ -316,37 +289,37 @@ class NetworkDelegate {
const networkDelegate = new NetworkDelegate(); const networkDelegate = new NetworkDelegate();
const videoInfoRateLimiterConfig: RateLimiterConfig[] = [ const videoInfoRateLimiterConfig: RateLimiterConfig[] = [
{ {
window: new SlidingWindow(redis, 0.3), duration: 0.3,
max: 1, max: 1,
}, },
{ {
window: new SlidingWindow(redis, 3), duration: 3,
max: 5, max: 5,
}, },
{ {
window: new SlidingWindow(redis, 30), duration: 30,
max: 30, max: 30,
}, },
{ {
window: new SlidingWindow(redis, 2 * 60), duration: 2 * 60,
max: 50, max: 50,
}, },
]; ];
const biliLimiterConfig: RateLimiterConfig[] = [ const biliLimiterConfig: RateLimiterConfig[] = [
{ {
window: new SlidingWindow(redis, 1), duration: 1,
max: 6, max: 6,
}, },
{ {
window: new SlidingWindow(redis, 5), duration: 5,
max: 20, max: 20,
}, },
{ {
window: new SlidingWindow(redis, 30), duration: 30,
max: 100, max: 100,
}, },
{ {
window: new SlidingWindow(redis, 5 * 60), duration: 5 * 60,
max: 200, max: 200,
}, },
]; ];

View File

@ -1,13 +1,17 @@
{ {
"name": "core", "name": "core",
"dependencies": { "scripts": {
"chalk": "^5.4.1", "test": "bun --env-file=.env.test run vitest"
"ioredis": "^5.6.1", },
"logform": "^2.7.0", "dependencies": {
"postgres": "^3.4.5", "@koshnic/ratelimit": "^1.0.3",
"winston": "^3.17.0" "chalk": "^5.4.1",
}, "ioredis": "^5.6.1",
"devDependencies": { "logform": "^2.7.0",
"@types/ioredis": "^5.0.0" "postgres": "^3.4.5",
} "winston": "^3.17.0"
} },
"devDependencies": {
"@types/ioredis": "^5.0.0"
}
}

View File

@ -0,0 +1,18 @@
import { describe, expect, it } from "vitest";
import { generateRandomId } from "@core/lib/randomID.ts";
describe("generateRandomId", () => {
it("should generate an ID of the specified length", () => {
const length = 15;
const id = generateRandomId(length);
expect(id).toHaveLength(length);
});
it("should generate an ID containing only allowed characters", () => {
const allowedChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
const id = generateRandomId(20);
for (const char of id) {
expect(allowedChars).toContain(char);
}
});
});

View File

@ -1,4 +1,4 @@
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
import { AllDataType, BiliUserType } from "@core/db/schema"; import { AllDataType, BiliUserType } from "@core/db/schema";
import { AkariModelVersion } from "ml/const"; import { AkariModelVersion } from "ml/const";

View File

@ -1,6 +1,6 @@
import { LatestSnapshotType } from "@core/db/schema"; import { LatestSnapshotType } from "@core/db/schema";
import { SnapshotNumber } from "mq/task/getVideoStats.ts"; import { SnapshotNumber } from "mq/task/getVideoStats.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
export async function getVideosNearMilestone(sql: Psql) { export async function getVideosNearMilestone(sql: Psql) {
const queryResult = await sql<LatestSnapshotType[]>` const queryResult = await sql<LatestSnapshotType[]>`

View File

@ -4,7 +4,7 @@ import { MINUTE } from "@core/const/time.ts";
import { redis } from "@core/db/redis.ts"; import { redis } from "@core/db/redis.ts";
import { Redis } from "ioredis"; import { Redis } from "ioredis";
import { parseTimestampFromPsql } from "../utils/formatTimestampToPostgre.ts"; import { parseTimestampFromPsql } from "../utils/formatTimestampToPostgre.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
const REDIS_KEY = "cvsa:snapshot_window_counts"; const REDIS_KEY = "cvsa:snapshot_window_counts";

View File

@ -1,4 +1,4 @@
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
import { parseTimestampFromPsql } from "utils/formatTimestampToPostgre.ts"; import { parseTimestampFromPsql } from "utils/formatTimestampToPostgre.ts";
export async function getNotCollectedSongs(sql: Psql) { export async function getNotCollectedSongs(sql: Psql) {

View File

@ -1,7 +1,7 @@
import { Job } from "bullmq"; import { Job } from "bullmq";
import { getAllVideosWithoutActiveSnapshotSchedule, scheduleSnapshot } from "db/snapshotSchedule.ts"; import { getAllVideosWithoutActiveSnapshotSchedule, scheduleSnapshot } from "db/snapshotSchedule.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { lockManager } from "mq/lockManager.ts"; import { lockManager } from "@core/mq/lockManager.ts";
import { getLatestVideoSnapshot } from "db/snapshot.ts"; import { getLatestVideoSnapshot } from "db/snapshot.ts";
import { HOUR, MINUTE } from "@core/const/time.ts"; import { HOUR, MINUTE } from "@core/const/time.ts";
import { sql } from "@core/db/dbNew"; import { sql } from "@core/db/dbNew";

View File

@ -3,7 +3,7 @@ import { getUnlabelledVideos, getVideoInfoFromAllData, insertVideoLabel } from "
import Akari from "ml/akari.ts"; import Akari from "ml/akari.ts";
import { ClassifyVideoQueue } from "mq/index.ts"; import { ClassifyVideoQueue } from "mq/index.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { lockManager } from "mq/lockManager.ts"; import { lockManager } from "@core/mq/lockManager.ts";
import { aidExistsInSongs } from "db/songs.ts"; import { aidExistsInSongs } from "db/songs.ts";
import { insertIntoSongs } from "mq/task/collectSongs.ts"; import { insertIntoSongs } from "mq/task/collectSongs.ts";
import { scheduleSnapshot } from "db/snapshotSchedule.ts"; import { scheduleSnapshot } from "db/snapshotSchedule.ts";

View File

@ -4,7 +4,7 @@ import { truncate } from "utils/truncate.ts";
import { getVideosWithoutActiveSnapshotSchedule, scheduleSnapshot } from "db/snapshotSchedule.ts"; import { getVideosWithoutActiveSnapshotSchedule, scheduleSnapshot } from "db/snapshotSchedule.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { HOUR, MINUTE, WEEK } from "@core/const/time.ts"; import { HOUR, MINUTE, WEEK } from "@core/const/time.ts";
import { lockManager } from "mq/lockManager.ts"; import { lockManager } from "@core/mq/lockManager.ts";
import { getRegularSnapshotInterval } from "mq/task/regularSnapshotInterval.ts"; import { getRegularSnapshotInterval } from "mq/task/regularSnapshotInterval.ts";
import { sql } from "@core/db/dbNew.ts"; import { sql } from "@core/db/dbNew.ts";

View File

@ -2,7 +2,7 @@ import { Job } from "bullmq";
import { scheduleSnapshot, setSnapshotStatus, snapshotScheduleExists } from "db/snapshotSchedule.ts"; import { scheduleSnapshot, setSnapshotStatus, snapshotScheduleExists } from "db/snapshotSchedule.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { HOUR, MINUTE, SECOND } from "@core/const/time.ts"; import { HOUR, MINUTE, SECOND } from "@core/const/time.ts";
import { lockManager } from "mq/lockManager.ts"; import { lockManager } from "@core/mq/lockManager.ts";
import { getBiliVideoStatus, setBiliVideoStatus } from "../../db/bilibili_metadata.ts"; import { getBiliVideoStatus, setBiliVideoStatus } from "../../db/bilibili_metadata.ts";
import { insertVideoSnapshot } from "mq/task/getVideoStats.ts"; import { insertVideoSnapshot } from "mq/task/getVideoStats.ts";
import { getSongsPublihsedAt } from "db/songs.ts"; import { getSongsPublihsedAt } from "db/songs.ts";

View File

@ -2,7 +2,7 @@ import { findClosestSnapshot, getLatestSnapshot, hasAtLeast2Snapshots } from "db
import { truncate } from "utils/truncate.ts"; import { truncate } from "utils/truncate.ts";
import { closetMilestone } from "./exec/snapshotTick.ts"; import { closetMilestone } from "./exec/snapshotTick.ts";
import { HOUR, MINUTE } from "@core/const/time.ts"; import { HOUR, MINUTE } from "@core/const/time.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/global.d.ts";
const log = (value: number, base: number = 10) => Math.log(value) / Math.log(base); const log = (value: number, base: number = 10) => Math.log(value) / Math.log(base);

View File

@ -3,7 +3,7 @@ import { aidExistsInSongs, getNotCollectedSongs } from "db/songs.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { scheduleSnapshot } from "db/snapshotSchedule.ts"; import { scheduleSnapshot } from "db/snapshotSchedule.ts";
import { MINUTE } from "@core/const/time.ts"; import { MINUTE } from "@core/const/time.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
export async function collectSongs() { export async function collectSongs() {
const aids = await getNotCollectedSongs(sql); const aids = await getNotCollectedSongs(sql);

View File

@ -4,7 +4,7 @@ import logger from "@core/log/logger.ts";
import { ClassifyVideoQueue } from "mq/index.ts"; import { ClassifyVideoQueue } from "mq/index.ts";
import { userExistsInBiliUsers, videoExistsInAllData } from "../../db/bilibili_metadata.ts"; import { userExistsInBiliUsers, videoExistsInAllData } from "../../db/bilibili_metadata.ts";
import { HOUR, SECOND } from "@core/const/time.ts"; import { HOUR, SECOND } from "@core/const/time.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
export async function insertVideoInfo(sql: Psql, aid: number) { export async function insertVideoInfo(sql: Psql, aid: number) {
const videoExists = await videoExistsInAllData(sql, aid); const videoExists = await videoExistsInAllData(sql, aid);

View File

@ -1,6 +1,6 @@
import { getVideoInfo } from "@core/net/getVideoInfo.ts"; import { getVideoInfo } from "@core/net/getVideoInfo.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
export interface SnapshotNumber { export interface SnapshotNumber {
time: number; time: number;

View File

@ -4,7 +4,7 @@ import { sleep } from "utils/sleep.ts";
import { SECOND } from "@core/const/time.ts"; import { SECOND } from "@core/const/time.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { LatestVideosQueue } from "mq/index.ts"; import { LatestVideosQueue } from "mq/index.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/psql.d.ts";
export async function queueLatestVideos( export async function queueLatestVideos(
sql: Psql, sql: Psql,

View File

@ -1,6 +1,6 @@
import { findClosestSnapshot, findSnapshotBefore, getLatestSnapshot } from "db/snapshotSchedule.ts"; import { findClosestSnapshot, findSnapshotBefore, getLatestSnapshot } from "db/snapshotSchedule.ts";
import { HOUR } from "@core/const/time.ts"; import { HOUR } from "@core/const/time.ts";
import type { Psql } from "global.d.ts"; import type { Psql } from "@core/db/global.d.ts";
export const getRegularSnapshotInterval = async (sql: Psql, aid: number) => { export const getRegularSnapshotInterval = async (sql: Psql, aid: number) => {
const now = Date.now(); const now = Date.now();

View File

@ -3,7 +3,7 @@ import { redis } from "@core/db/redis.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { classifyVideosWorker, classifyVideoWorker } from "mq/exec/classifyVideo.ts"; import { classifyVideosWorker, classifyVideoWorker } from "mq/exec/classifyVideo.ts";
import { WorkerError } from "mq/schema.ts"; import { WorkerError } from "mq/schema.ts";
import { lockManager } from "mq/lockManager.ts"; import { lockManager } from "@core/mq/lockManager.ts";
import Akari from "ml/akari.ts"; import Akari from "ml/akari.ts";
const shutdown = async (signal: string) => { const shutdown = async (signal: string) => {

View File

@ -14,7 +14,7 @@ import {
} from "mq/exec/executors.ts"; } from "mq/exec/executors.ts";
import { redis } from "@core/db/redis.ts"; import { redis } from "@core/db/redis.ts";
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { lockManager } from "mq/lockManager.ts"; import { lockManager } from "@core/mq/lockManager.ts";
import { WorkerError } from "mq/schema.ts"; import { WorkerError } from "mq/schema.ts";
const releaseLockForJob = async (name: string) => { const releaseLockForJob = async (name: string) => {

View File

@ -0,0 +1,3 @@
<div class="absolute bg-surface-container-high dark:bg-dark-surface-container-high z-50">
</div>

View File

@ -6,7 +6,6 @@
export let show: boolean = false; export let show: boolean = false;
export let onClose: () => void; export let onClose: () => void;
let drawer: HTMLDivElement;
let cover: HTMLDivElement; let cover: HTMLDivElement;
onMount(() => { onMount(() => {
@ -27,19 +26,17 @@
<div class="absolute z-50 "> <div class="absolute z-50 ">
{#if show} {#if show}
<div <div
bind:this={cover} bind:this={cover}
transition:fade="{{ duration: 300 }}" transition:fade="{{ duration: 300 }}"
class="fixed top-0 left-0 w-full h-full z-40 bg-[#00000020]" class="fixed top-0 left-0 w-full h-full z-40 bg-[#00000020]"
aria-hidden="true"> aria-hidden="true">
</div> </div>
<div <div
bind:this={drawer} transition:fly="{{ x: -500, duration: 300 }}" class="fixed top-0 left-0 h-full
transition:fly="{{ x: -500, duration: 300 }}" class="fixed top-0 left-0 h-full
bg-[#fff0ee] dark:bg-[#231918] z-50" bg-[#fff0ee] dark:bg-[#231918] z-50"
style="width: min(22.5rem, 70vw);" style="width: min(22.5rem, 70vw);"
role="dialog" aria-modal="true"> role="dialog" aria-modal="true">
<slot></slot> <slot></slot>
</div> </div>
{/if} {/if}

View File

@ -0,0 +1,8 @@
<script>
let ref;
$: ref && document.body.appendChild(ref);
</script>
<div bind:this={ref}>
<slot></slot>
</div>

View File

@ -0,0 +1,78 @@
<script lang="ts">
import TextField from "@components/TextField.svelte";
import LoadingSpinner from "@components/icon/LoadingSpinner.svelte";
import { computeVdfInWorker } from "@lib/vdf.js";
export let backendURL: string;
let password = '';
let username = '';
let nickname = '';
let loading = false;
async function createCaptchaSession() {
const url = new URL(backendURL);
url.pathname = '/captcha/session';
const res = await fetch(url.toString(), {
method: "POST",
body: JSON.stringify({
"route": "POST-/user"
})
});
if (res.status !== 201) {
throw new Error("Failed to create captcha session");
}
return await res.json();
}
async function getCaptchaResult(id: string, ans: string) {
const url = new URL(backendURL);
url.pathname = `/captcha/${id}/result`;
url.searchParams.set("ans", ans);
const res = await fetch(url.toString());
if (res.status !== 200) {
throw new Error("Failed to verify captcha answer");
}
return await res.json();
}
async function register() {
const { g, n, t, id } = await createCaptchaSession();
const ans = await computeVdfInWorker(BigInt(g), BigInt(n), BigInt(t));
const res = await getCaptchaResult(id, ans.result.toString());
console.log(res)
}
</script>
<form class="w-full flex flex-col gap-6">
<TextField labelText="用户名" bind:inputText={username} maxChar={50}
supportingText="*必填。用户名是唯一的,不区分大小写。"
/>
<TextField labelText="密码" type="password" bind:inputText={password}
supportingText="*必填。密码至少为 4 个字符。" maxChar={120}
/>
<TextField labelText="昵称" bind:inputText={nickname}
supportingText="昵称可以重复。" maxChar={30}
/>
<button class="bg-primary dark:bg-dark-primary text-on-primary dark:text-dark-on-primary duration-150
rounded-full hover:bg-on-primary-container hover:dark:bg-dark-on-primary-container mt-2
flex items-center text-sm leading-5 justify-center h-10 w-full"
onclick={async (e) => {
e.preventDefault();
loading = true;
try {
await register();
}
finally {
loading = false;
}
}}
>
{#if !loading}
<span>注册</span>
{:else}
<LoadingSpinner/>
{/if}
</button>
</form>

View File

@ -0,0 +1,72 @@
<script lang="ts">
let focus = $state(false);
let {
labelText = "",
type = "text",
inputText = $bindable(),
maxChar = undefined,
supportingText = undefined,
...rest
} = $props();
function onValueChange(event: Event & { currentTarget: EventTarget & HTMLInputElement }) {
if (!event.target) return;
const { value } = event.target as HTMLInputElement;
inputText = value;
}
</script>
<div {...rest}>
<div class="relative h-14 px-4">
<div class="absolute flex top-0 left-0 h-full w-full">
<div class={`w-3 rounded-l-sm border-outline dark:border-dark-outline
${(focus) ?
"border-primary dark:border-dark-primary border-l-2 border-y-2" :
"border-l-[1px] border-y-[1px] "}
`}></div>
<div class={`px-1 border-outline dark:border-dark-outline transition-none
${(!focus && !inputText) && "border-y-[1px]"}
${(!focus && inputText) && "border-y-[1px] border-t-0"}
${focus && "border-primary dark:border-dark-primary border-y-2 border-t-0"}
`}>
<span class={`
relative leading-6 text-base text-on-surface-variant dark:text-dark-on-surface-variant duration-150
${(focus || inputText) ? "-top-3 text-xs leading-4" : "top-4"}
${focus && "text-primary dark:text-dark-primary"}
`}>
{labelText}
</span>
</div>
<div class={`flex-grow rounded-r-sm border-outline dark:border-dark-outline
${(focus) ?
"border-primary dark:border-dark-primary border-r-2 border-y-2" :
"border-r-[1px] border-y-[1px] "}
`}></div>
</div>
<input
class="relative focus:outline-none h-full w-full"
onfocus={() => focus = true}
onblur={() => focus = false}
oninput={onValueChange}
type={type}
/>
</div>
{#if supportingText || maxChar}
<div class="w-full relative mt-1 text-on-surface-variant dark:text-dark-on-surface-variant
text-xs leading-4 h-4">
{#if supportingText}
<span class="absolute left-4">
{supportingText}
</span>
{/if}
{#if maxChar}
<span class="absolute right-4">
{inputText.length}/{maxChar}
</span>
{/if}
</div>
{/if}
</div>

View File

@ -9,6 +9,7 @@
import HomeIcon from "@components/icon/HomeIcon.svelte"; import HomeIcon from "@components/icon/HomeIcon.svelte";
import InfoIcon from "@components/icon/InfoIcon.svelte"; import InfoIcon from "@components/icon/InfoIcon.svelte";
import RegisterIcon from "@components/icon/RegisterIcon.svelte"; import RegisterIcon from "@components/icon/RegisterIcon.svelte";
import Portal from "@components/Portal.svelte";
let searchBox: SearchBox | null = null; let searchBox: SearchBox | null = null;
let showSearchBox = false; let showSearchBox = false;
@ -19,22 +20,24 @@
} }
</script> </script>
<NavigationDrawer show={showDrawer} onClose={() => showDrawer = false}> <Portal>
<div class="flex flex-col w-full"> <NavigationDrawer show={showDrawer} onClose={() => showDrawer = false}>
<div class="w-full h-14 flex items-center px-4"> <div class="flex flex-col w-full">
<HomeIcon className="text-2xl pr-4"/> <div class="w-full h-14 flex items-center px-4">
<a href="/">首页</a> <HomeIcon className="text-2xl pr-4"/>
<a href="/">首页</a>
</div>
<div class="w-full h-14 flex items-center px-4">
<InfoIcon className="text-2xl pr-4"/>
<a href="/about">关于</a>
</div>
<div class="w-full h-14 flex items-center px-4">
<RegisterIcon className="text-2xl pr-4"/>
<a href="/register">注册</a>
</div>
</div> </div>
<div class="w-full h-14 flex items-center px-4"> </NavigationDrawer>
<InfoIcon className="text-2xl pr-4"/> </Portal>
<a href="/about">关于</a>
</div>
<div class="w-full h-14 flex items-center px-4">
<RegisterIcon className="text-2xl pr-4"/>
<a href="/register">注册</a>
</div>
</div>
</NavigationDrawer>
<div class="md:hidden relative top-0 left-0 w-full h-16 z-20"> <div class="md:hidden relative top-0 left-0 w-full h-16 z-20">
{#if !showSearchBox} {#if !showSearchBox}

View File

@ -0,0 +1,8 @@
---
const { ...props } = Astro.props;
---
<svg {...props} width="1em" height="1em" viewBox="0 0 10.72 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="path"
d="M10.64 4.34C10.69 4.23 10.72 4.11 10.72 3.97C10.72 3.79 10.67 3.65 10.59 3.55C10.55 3.51 10.5 3.46 10.46 3.44C10.39 3.4 10.31 3.38 10.22 3.38L2.16 3.38L4.57 0.97C4.61 0.9 4.7 0.73 4.7 0.63C4.7 0.6 4.7 0.57 4.7 0.52C4.67 0.4 4.6 0.28 4.5 0.16C4.39 0.06 4.29 0 4.17 -0.02C4.13 -0.04 4.09 -0.05 4.04 -0.05C3.95 -0.05 3.87 -0.02 3.81 0.01C3.76 0.04 3.73 0.06 3.7 0.09L0.22 3.58C0.07 3.72 0 3.85 0 3.97C0 4.09 0.07 4.23 0.22 4.38L3.7 7.87C3.82 7.95 3.93 8 4.04 8C4.18 7.98 4.37 7.91 4.5 7.79C4.6 7.68 4.67 7.56 4.7 7.44C4.7 7.4 4.7 7.36 4.7 7.32C4.7 7.26 4.7 7.21 4.67 7.16C4.66 7.1 4.62 7.04 4.57 7L2.16 4.59L10.22 4.59C10.3 4.59 10.37 4.57 10.43 4.54C10.49 4.51 10.59 4.4 10.64 4.34Z"
fill="currentColor" fill-opacity="1.000000" fill-rule="evenodd" />
</svg>

View File

@ -0,0 +1,15 @@
<script lang="ts">
export let className = '';
</script>
<div class={className}>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<g stroke="currentColor" stroke-width="1">
<circle cx="12" cy="12" r="9.5" fill="none" stroke-linecap="round" stroke-width="3">
<animate attributeName="stroke-dasharray" calcMode="spline" dur="1.5s" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" keyTimes="0;0.475;0.95;1" repeatCount="indefinite" values="0 150;42 150;42 150;42 150" />
<animate attributeName="stroke-dashoffset" calcMode="spline" dur="1.5s" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" keyTimes="0;0.475;0.95;1" repeatCount="indefinite" values="0;-16;-59;-59" />
</circle>
<animateTransform attributeName="transform" dur="2s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12" />
</g>
</svg>
</div>

View File

@ -3,18 +3,7 @@
</script> </script>
<div class={className}> <div class={className}>
<svg width="28.000000" height="28.000000" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="28.000000" height="28.000000" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<desc>
Created with Pixso.
</desc>
<defs>
<clipPath id="clip97_210">
<rect id="菜单按钮" width="28.000000" height="28.000000" fill="white" fill-opacity="0"/>
</clipPath>
</defs>
<g clip-path="url(#clip97_210)">
<path id="path" d="M4.66 21C4.33 21 4.05 20.88 3.83 20.66C3.61 20.44 3.5 20.16 3.5 19.83C3.49 19.5 3.61 19.22 3.83 19C4.06 18.77 4.33 18.66 4.66 18.66L23.33 18.66C23.66 18.66 23.94 18.77 24.16 19C24.38 19.22 24.5 19.5 24.5 19.83C24.49 20.16 24.38 20.44 24.16 20.66C23.94 20.88 23.66 21 23.33 21L4.66 21ZM4.66 15.16C4.33 15.16 4.05 15.05 3.83 14.83C3.61 14.6 3.5 14.32 3.5 14C3.49 13.67 3.61 13.39 3.83 13.16C4.06 12.94 4.33 12.83 4.66 12.83L23.33 12.83C23.66 12.83 23.94 12.94 24.16 13.16C24.38 13.39 24.5 13.67 24.5 14C24.49 14.32 24.38 14.6 24.16 14.83C23.94 15.05 23.66 15.16 23.33 15.16L4.66 15.16ZM4.66 9.33C4.33 9.33 4.05 9.22 3.83 8.99C3.61 8.77 3.5 8.49 3.5 8.16C3.49 7.83 3.61 7.56 3.83 7.33C4.06 7.11 4.33 7 4.66 7L23.33 7C23.66 7 23.94 7.11 24.16 7.33C24.38 7.56 24.5 7.83 24.5 8.16C24.49 8.49 24.38 8.77 24.16 8.99C23.94 9.22 23.66 9.33 23.33 9.33L4.66 9.33Z" fill="currentColor" fill-opacity="1.000000" fill-rule="nonzero"/> <path id="path" d="M4.66 21C4.33 21 4.05 20.88 3.83 20.66C3.61 20.44 3.5 20.16 3.5 19.83C3.49 19.5 3.61 19.22 3.83 19C4.06 18.77 4.33 18.66 4.66 18.66L23.33 18.66C23.66 18.66 23.94 18.77 24.16 19C24.38 19.22 24.5 19.5 24.5 19.83C24.49 20.16 24.38 20.44 24.16 20.66C23.94 20.88 23.66 21 23.33 21L4.66 21ZM4.66 15.16C4.33 15.16 4.05 15.05 3.83 14.83C3.61 14.6 3.5 14.32 3.5 14C3.49 13.67 3.61 13.39 3.83 13.16C4.06 12.94 4.33 12.83 4.66 12.83L23.33 12.83C23.66 12.83 23.94 12.94 24.16 13.16C24.38 13.39 24.5 13.67 24.5 14C24.49 14.32 24.38 14.6 24.16 14.83C23.94 15.05 23.66 15.16 23.33 15.16L4.66 15.16ZM4.66 9.33C4.33 9.33 4.05 9.22 3.83 8.99C3.61 8.77 3.5 8.49 3.5 8.16C3.49 7.83 3.61 7.56 3.83 7.33C4.06 7.11 4.33 7 4.66 7L23.33 7C23.66 7 23.94 7.11 24.16 7.33C24.38 7.56 24.5 7.83 24.5 8.16C24.49 8.49 24.38 8.77 24.16 8.99C23.94 9.22 23.66 9.33 23.33 9.33L4.66 9.33Z" fill="currentColor" fill-opacity="1.000000" fill-rule="nonzero"/>
</g>
</svg> </svg>
</div> </div>

View File

@ -2,7 +2,7 @@
const {...props} = Astro.props; const {...props} = Astro.props;
--- ---
<svg {...props} width="10.72" height="8" viewBox="0 0 10.72 8" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg {...props} width="1em" height="1em" viewBox="0 0 10.72 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.08 3.66Q0 3.82 0 4.03Q0 4.29 0.13 4.45Q0.13 4.46 0.13 4.46Q0.19 4.52 0.26 4.56Q0.36 4.62 0.49 4.62L8.56 4.62L6.15 7.03Q6.1 7.07 6.08 7.12Q6.01 7.22 6.01 7.36Q6.01 7.41 6.02 7.47Q6.06 7.66 6.22 7.83Q6.37 7.98 6.54 8.02Q6.61 8.04 6.67 8.04Q6.81 8.04 6.91 7.98Q6.97 7.95 7.01 7.9L10.5 4.42Q10.72 4.2 10.72 4.03Q10.72 3.84 10.5 3.62L7.01 0.13Q6.84 0 6.67 0Q6.64 0 6.61 0Q6.41 0.02 6.22 0.21Q6.06 0.37 6.02 0.56Q6.01 0.62 6.01 0.68Q6.01 0.76 6.04 0.84Q6.07 0.93 6.15 1L8.56 3.41L0.49 3.41Q0.38 3.41 0.29 3.46Q0.2 3.5 0.13 3.59Q0.1 3.63 0.08 3.66Z" <path d="M0.08 3.66Q0 3.82 0 4.03Q0 4.29 0.13 4.45Q0.13 4.46 0.13 4.46Q0.19 4.52 0.26 4.56Q0.36 4.62 0.49 4.62L8.56 4.62L6.15 7.03Q6.1 7.07 6.08 7.12Q6.01 7.22 6.01 7.36Q6.01 7.41 6.02 7.47Q6.06 7.66 6.22 7.83Q6.37 7.98 6.54 8.02Q6.61 8.04 6.67 8.04Q6.81 8.04 6.91 7.98Q6.97 7.95 7.01 7.9L10.5 4.42Q10.72 4.2 10.72 4.03Q10.72 3.84 10.5 3.62L7.01 0.13Q6.84 0 6.67 0Q6.64 0 6.61 0Q6.41 0.02 6.22 0.21Q6.06 0.37 6.02 0.56Q6.01 0.62 6.01 0.68Q6.01 0.76 6.04 0.84Q6.07 0.93 6.15 1L8.56 3.41L0.49 3.41Q0.38 3.41 0.29 3.46Q0.2 3.5 0.13 3.59Q0.1 3.63 0.08 3.66Z"
fill="currentColor" fill-opacity="1.000000" fill-rule="evenodd"/> fill="currentColor" fill-opacity="1.000000" fill-rule="evenodd"/>
</svg> </svg>

View File

@ -0,0 +1,113 @@
// Define interfaces for input and output
interface VdfProgressCallback {
(progress: number): void;
}
interface VdfResult {
result: bigint;
time: number; // Time taken in milliseconds
}
// The content of the Web Worker script
const workerContent = `addEventListener("message", async (event) => {
const { g, N, difficulty } = event.data;
// Although pow is not used in the iterative VDF, it's good to keep the original worker code structure.
// The iterative computeVDFWithProgress is better for progress reporting.
function pow(base, exponent, mod) {
let result = 1n;
base = base % mod;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exponent = exponent / 2n;
// Using BigInt division (/) which performs integer division
}
return result;
}
// Compute VDF iteratively to report progress
function computeVDFWithProgress(g, N, T, postProgress) {
let result = g;
let latestTime = performance.now();
const totalSteps = T; // T is the difficulty, representing 2^T squaring steps
for (let i = 0n; i < totalSteps; i++) {
result = (result * result) % N;
// Report progress periodically (approx. every 16ms to match typical frame rate)
if (performance.now() - latestTime > 16) {
// Calculate progress as a percentage
const progress = Number((i + 1n) * 10000n / totalSteps) / 100; // Using 10000 for better precision before dividing by 100
postProgress(progress);
latestTime = performance.now();
}
}
// Ensure final progress is reported
postProgress(100);
return result;
}
const startTime = performance.now();
// The worker computes g^(2^difficulty) mod N. The loop runs 'difficulty' times, performing squaring.
const result = computeVDFWithProgress(g, N, difficulty, (progress) => {
// Post progress back to the main thread
postMessage({ type: "progress", progress: progress });
});
const endTime = performance.now();
const timeTaken = endTime - startTime;
// Post the final result and time taken back to the main thread
postMessage({ type: "result", result: result.toString(), time: timeTaken });
});
`;
/**
* Computes the Verifiable Delay Function (VDF) result g^(2^difficulty) mod N
* in a Web Worker and reports progress.
* @param g - The base (bigint).
* @param N - The modulus (bigint).
* @param difficulty - The number of squaring steps (T) (bigint).
* @param onProgress - Optional callback function to receive progress updates (0-100).
* @returns A Promise that resolves with the VDF result and time taken.
*/
export function computeVdfInWorker(g: bigint, N: bigint, difficulty: bigint, onProgress?: VdfProgressCallback): Promise<VdfResult> {
return new Promise((resolve, reject) => {
// Create a Blob containing the worker script
const blob = new Blob([workerContent], { type: "text/javascript" });
// Create a URL for the Blob
const workerUrl = URL.createObjectURL(blob);
// Create a new Web Worker
const worker = new Worker(workerUrl);
// Handle messages from the worker
worker.onmessage = (event) => {
const { type, progress, result, time } = event.data;
if (type === "progress") {
if (onProgress) {
onProgress(progress);
}
} else if (type === "result") {
// Resolve the promise with the result and time
resolve({ result: BigInt(result), time });
// Terminate the worker and revoke the URL
worker.terminate();
URL.revokeObjectURL(workerUrl);
}
};
// Handle potential errors in the worker
worker.onerror = (error) => {
reject(error);
// Terminate the worker and revoke the URL in case of error
worker.terminate();
URL.revokeObjectURL(workerUrl);
};
// Post the data to the worker to start the computation
worker.postMessage({ g, N, difficulty });
});
}

View File

@ -1,13 +1,24 @@
--- ---
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import RightArrow from "@components/icon/RightArrow.astro"; import RightArrow from "@components/icon/RightArrow.astro";
import RegisterForm from "@components/RegisterPage/RegisterForm.svelte";
import LeftArrow from "@components/icon/LeftArrow.astro";
const backendURL = import.meta.env.BACKEND_URL;
--- ---
<Layout title="注册"> <Layout title="注册">
<main class="relative flex-grow pt-36 px-4 md:w-full md:flex md:items-center md:flex-col"> <main class="relative flex-grow pt-8 md:pt-0 px-4 md:w-full md:h-full md:flex md:items-center md:justify-center">
<div class="md:w-[40rem] rounded-md md:p-8 md:bg-surface-container md:dark:bg-dark-container"> <div class="md:w-[40rem] rounded-md md:p-8 md:-translate-y-6
md:bg-surface-container md:dark:bg-dark-surface-container">
<p class="mb-2">
<a href="/">
<LeftArrow class="inline -translate-y-[0.1rem] scale-90" aria-hidden="true"/>
首页
</a>
</p>
<h1 class="text-5xl leading-[4rem] font-extralight">欢迎</h1> <h1 class="text-5xl leading-[4rem] font-extralight">欢迎</h1>
<p class="mt-2.5 md:mt-4"> <p class="mt-2 md:mt-3">
欢迎来到中V档案馆。<br/> 欢迎来到中V档案馆。<br/>
这里是中文虚拟歌手相关信息的收集站与档案馆。 这里是中文虚拟歌手相关信息的收集站与档案馆。
</p> </p>
@ -15,16 +26,14 @@ import RightArrow from "@components/icon/RightArrow.astro";
注册一个账号,<br/> 注册一个账号,<br/>
让我们一起见证中V的历史现在与未来。 让我们一起见证中V的历史现在与未来。
</p> </p>
<p class="mt-4"> <p class="mt-4 mb-7">
已有账户? 已有账户?
<a href="/login"> <a href="/login">
<span>登录</span> <span>登录</span>
<RightArrow class="inline -translate-y-0.5" aria-hidden="true"/> <RightArrow class="text-xs inline -translate-y-0.5" aria-hidden="true"/>
</a> </a>
</p> </p>
<p class="mt-4 leading-8 font-medium">很抱歉,但您现在无法注册。</p> <RegisterForm backendURL={backendURL} client:load />
<p class="text-sm text-on-surface-variant dark:text-dark-on-surface-variant">因为目前还没有写好啦~</p>
<p class="mt-4"><a href="/">返回首页</a></p>
</div> </div>
</main> </main>
</Layout> </Layout>

View File

@ -1,14 +1,6 @@
@import "tailwindcss"; @import "tailwindcss";
.content { .content {
@apply text-gray-800 dark:text-zinc-100;
h1,
h2,
h3,
h4 {
@apply font-medium text-gray-900 dark:text-white my-4;
}
h1 { h1 {
@apply text-3xl; @apply text-3xl;
@ -30,10 +22,6 @@
@apply my-4; @apply my-4;
} }
a {
@apply text-slate-800 dark:text-sky-300 hover:underline;
}
ul { ul {
@apply list-disc list-inside my-4; @apply list-disc list-inside my-4;
} }
@ -46,27 +34,6 @@
@apply my-2; @apply my-2;
} }
blockquote {
@apply border-l-4 border-gray-300 pl-4 italic my-4;
}
code {
@apply bg-gray-100 text-gray-800 rounded px-1 duration-300;
}
pre {
@apply bg-gray-100 p-4 rounded overflow-x-auto my-4 duration-300 h-0;
}
table {
@apply w-full border-collapse my-4;
}
th,
td {
@apply border border-gray-300 p-2;
}
th { th {
@apply bg-gray-200 font-medium; @apply bg-gray-200 font-medium;
} }

View File

@ -7,10 +7,10 @@
"paths": { "paths": {
"@components/*": ["src/components/*"], "@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"], "@layouts/*": ["src/layouts/*"],
"@utils/*": ["src/utils/*"], "@lib/*": ["src/lib/*"],
"@assets/*": ["src/assets/*"], "@assets/*": ["src/assets/*"],
"@styles": ["src/styles/*"], "@styles": ["src/styles/*"],
"@core/*": ["../core/*"] "@core/*": ["../core/*"],
}, },
"verbatimModuleSyntax": true "verbatimModuleSyntax": true
} }