Compare commits
27 Commits
a31f702499
...
01f5e57864
Author | SHA1 | Date | |
---|---|---|---|
01f5e57864 | |||
bf00918c00 | |||
44f4ee5b01 | |||
cd8fe502ba | |||
95681dcbf3 | |||
59f09ca5eb | |||
d686b6a369 | |||
d8c74a609a | |||
9d5c7cc47d | |||
7528dcdf81 | |||
811a8261b3 | |||
d0aa27b2ad | |||
280825cf67 | |||
e658135a81 | |||
4632bd5906 | |||
43b52dee0b | |||
8900ac7ec7 | |||
2772849933 | |||
784939074a | |||
94dd662c40 | |||
1ebc0d0c1b | |||
728a74f4d3 | |||
0c8a459d92 | |||
d7b6792d05 | |||
50bcb48bd6 | |||
fe386d2b02 | |||
e72b3008d1 |
96
.dockerignore
Normal file
96
.dockerignore
Normal file
@ -0,0 +1,96 @@
|
||||
built/*
|
||||
tests/cases/rwc/*
|
||||
tests/cases/perf/*
|
||||
!tests/cases/webharness/compilerToString.js
|
||||
test-args.txt
|
||||
~*.docx
|
||||
\#*\#
|
||||
.\#*
|
||||
tests/baselines/local/*
|
||||
tests/baselines/local.old/*
|
||||
tests/services/baselines/local/*
|
||||
tests/baselines/prototyping/local/*
|
||||
tests/baselines/rwc/*
|
||||
tests/baselines/reference/projectOutput/*
|
||||
tests/baselines/local/projectOutput/*
|
||||
tests/baselines/reference/testresults.tap
|
||||
tests/baselines/symlinks/*
|
||||
tests/services/baselines/prototyping/local/*
|
||||
tests/services/browser/typescriptServices.js
|
||||
src/harness/*.js
|
||||
src/compiler/diagnosticInformationMap.generated.ts
|
||||
src/compiler/diagnosticMessages.generated.json
|
||||
src/parser/diagnosticInformationMap.generated.ts
|
||||
src/parser/diagnosticMessages.generated.json
|
||||
rwc-report.html
|
||||
*.swp
|
||||
build.json
|
||||
*.actual
|
||||
tests/webTestServer.js
|
||||
tests/webTestServer.js.map
|
||||
tests/webhost/*.d.ts
|
||||
tests/webhost/webtsc.js
|
||||
tests/cases/**/*.js
|
||||
tests/cases/**/*.js.map
|
||||
*.config
|
||||
scripts/eslint/built/
|
||||
scripts/debug.bat
|
||||
scripts/run.bat
|
||||
scripts/**/*.js
|
||||
scripts/**/*.js.map
|
||||
coverage/
|
||||
internal/
|
||||
**/.DS_Store
|
||||
.settings
|
||||
**/.vs
|
||||
**/.vscode/*
|
||||
!**/.vscode/tasks.json
|
||||
!**/.vscode/settings.template.json
|
||||
!**/.vscode/launch.template.json
|
||||
!**/.vscode/extensions.json
|
||||
!tests/cases/projects/projectOption/**/node_modules
|
||||
!tests/cases/projects/NodeModulesSearch/**/*
|
||||
!tests/baselines/reference/project/nodeModules*/**/*
|
||||
yarn.lock
|
||||
yarn-error.log
|
||||
.parallelperf.*
|
||||
tests/baselines/reference/dt
|
||||
.failed-tests
|
||||
TEST-results.xml
|
||||
package-lock.json
|
||||
.eslintcache
|
||||
*v8.log
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# npm dependencies
|
||||
node_modules/
|
||||
|
||||
# project specific
|
||||
logs/
|
||||
__pycache__
|
||||
ml/filter/runs
|
||||
ml/pred/runs
|
||||
ml/pred/checkpoints
|
||||
ml/pred/observed
|
||||
ml/data/
|
||||
ml/filter/checkpoints
|
||||
scripts
|
||||
model/
|
||||
|
||||
|
||||
.astro
|
||||
|
||||
# Database
|
||||
*.dump
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
data/
|
||||
|
||||
docker-compose.yml
|
77
.gitignore
vendored
77
.gitignore
vendored
@ -1,72 +1,14 @@
|
||||
built/*
|
||||
tests/cases/rwc/*
|
||||
tests/cases/perf/*
|
||||
!tests/cases/webharness/compilerToString.js
|
||||
test-args.txt
|
||||
~*.docx
|
||||
\#*\#
|
||||
.\#*
|
||||
tests/baselines/local/*
|
||||
tests/baselines/local.old/*
|
||||
tests/services/baselines/local/*
|
||||
tests/baselines/prototyping/local/*
|
||||
tests/baselines/rwc/*
|
||||
tests/baselines/reference/projectOutput/*
|
||||
tests/baselines/local/projectOutput/*
|
||||
tests/baselines/reference/testresults.tap
|
||||
tests/baselines/symlinks/*
|
||||
tests/services/baselines/prototyping/local/*
|
||||
tests/services/browser/typescriptServices.js
|
||||
src/harness/*.js
|
||||
src/compiler/diagnosticInformationMap.generated.ts
|
||||
src/compiler/diagnosticMessages.generated.json
|
||||
src/parser/diagnosticInformationMap.generated.ts
|
||||
src/parser/diagnosticMessages.generated.json
|
||||
rwc-report.html
|
||||
*.swp
|
||||
build.json
|
||||
*.actual
|
||||
tests/webTestServer.js
|
||||
tests/webTestServer.js.map
|
||||
tests/webhost/*.d.ts
|
||||
tests/webhost/webtsc.js
|
||||
tests/cases/**/*.js
|
||||
tests/cases/**/*.js.map
|
||||
*.config
|
||||
scripts/eslint/built/
|
||||
scripts/debug.bat
|
||||
scripts/run.bat
|
||||
scripts/**/*.js
|
||||
scripts/**/*.js.map
|
||||
coverage/
|
||||
internal/
|
||||
**/.DS_Store
|
||||
.settings
|
||||
**/.vs
|
||||
**/.vscode/*
|
||||
!**/.vscode/tasks.json
|
||||
!**/.vscode/settings.template.json
|
||||
!**/.vscode/launch.template.json
|
||||
!**/.vscode/extensions.json
|
||||
!tests/cases/projects/projectOption/**/node_modules
|
||||
!tests/cases/projects/NodeModulesSearch/**/*
|
||||
!tests/baselines/reference/project/nodeModules*/**/*
|
||||
yarn.lock
|
||||
yarn-error.log
|
||||
.parallelperf.*
|
||||
tests/baselines/reference/dt
|
||||
.failed-tests
|
||||
TEST-results.xml
|
||||
package-lock.json
|
||||
|
||||
.eslintcache
|
||||
*v8.log
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.env.*
|
||||
|
||||
# npm dependencies
|
||||
node_modules/
|
||||
@ -83,5 +25,18 @@ ml/filter/checkpoints
|
||||
scripts
|
||||
model/
|
||||
|
||||
|
||||
.astro
|
||||
|
||||
# Database
|
||||
*.dump
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
data/
|
||||
redis/
|
||||
|
||||
# Build
|
||||
dist/
|
||||
build/
|
||||
|
||||
docker-compose.yml
|
6
.idea/bun.xml
Normal file
6
.idea/bun.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="BunSettings">
|
||||
<option name="bunPath" value="$USER_HOME$/.bun/bin/bun" />
|
||||
</component>
|
||||
</project>
|
55
.idea/codeStyles/Project.xml
Normal file
55
.idea/codeStyles/Project.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<VueCodeStyleSettings>
|
||||
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||
</VueCodeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Vue">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
6
.idea/compiler.xml
Normal file
6
.idea/compiler.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="TypeScriptCompiler">
|
||||
<option name="useTypesFromServer" value="true" />
|
||||
</component>
|
||||
</project>
|
@ -26,6 +26,8 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/backend/logs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/core/net/logs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/crawler/logs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/data" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/redis" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="DenoSettings">
|
||||
<option name="denoInit" value="{ "enable": true, "lint": true, "unstable": true, "importMap": "import_map.json", "config": "deno.json", "fmt": { "useTabs": true, "lineWidth": 120, "indentWidth": 4, "semiColons": true, "proseWrap": "always" } }" />
|
||||
<option name="useDenoValue" value="DISABLE" />
|
||||
</component>
|
||||
</project>
|
@ -31,5 +31,6 @@
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": false,
|
||||
"printWidth": 120,
|
||||
"endOfLine": "lf"
|
||||
}
|
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@ -1,6 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"denoland.vscode-deno",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// Folder-specific settings
|
||||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||
{
|
||||
"lsp": {
|
||||
"deno": {
|
||||
"settings": {
|
||||
"deno": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": {
|
||||
"TypeScript": {
|
||||
"language_servers": [
|
||||
"deno",
|
||||
"!typescript-language-server",
|
||||
"!vtsls",
|
||||
"!eslint"
|
||||
],
|
||||
"formatter": "language_server"
|
||||
},
|
||||
"TSX": {
|
||||
"language_servers": [
|
||||
"deno",
|
||||
"!typescript-language-server",
|
||||
"!vtsls",
|
||||
"!eslint"
|
||||
],
|
||||
"formatter": "language_server"
|
||||
}
|
||||
}
|
||||
}
|
17
Dockerfile.backend
Normal file
17
Dockerfile.backend
Normal file
@ -0,0 +1,17 @@
|
||||
FROM oven/bun
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./packages/core ./core
|
||||
|
||||
COPY ./packages/backend/package.json ./packages/backend/bun.lock ./backend/
|
||||
|
||||
WORKDIR backend
|
||||
|
||||
RUN bun install
|
||||
|
||||
COPY ./packages/backend/ .
|
||||
|
||||
RUN mkdir -p /app/logs
|
||||
|
||||
CMD ["bun", "start"]
|
19
Dockerfile.crawler
Normal file
19
Dockerfile.crawler
Normal file
@ -0,0 +1,19 @@
|
||||
FROM oven/bun:1.2.8-debian
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN bun i
|
||||
|
||||
RUN mkdir -p /app/logs
|
||||
|
||||
RUN apt update && apt install -y curl
|
||||
|
||||
RUN ln -s /bin/uname /usr/bin/uname
|
||||
|
||||
RUN /bin/bash -c "$(curl -fsSL https://aliyuncli.alicdn.com/install.sh)"
|
||||
|
||||
WORKDIR packages/crawler
|
||||
|
||||
CMD ["bun", "all"]
|
20
Dockerfile.frontend
Normal file
20
Dockerfile.frontend
Normal file
@ -0,0 +1,20 @@
|
||||
FROM oven/bun
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN bun install
|
||||
|
||||
WORKDIR packages/frontend
|
||||
|
||||
RUN bun run build
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
|
||||
EXPOSE 4321
|
||||
|
||||
RUN mkdir -p /app/logs
|
||||
|
||||
CMD ["bun", "/app/packages/frontend/dist/server/entry.mjs"]
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"lock": false,
|
||||
"workspace": ["./packages/crawler", "./packages/frontend", "./packages/backend", "./packages/core"],
|
||||
"workspace": ["./packages/crawler", "./packages/core"],
|
||||
"nodeModulesDir": "auto",
|
||||
"tasks": {
|
||||
"crawler": "deno task --filter 'crawler' all",
|
||||
@ -12,10 +12,5 @@
|
||||
"indentWidth": 4,
|
||||
"semiColons": true,
|
||||
"proseWrap": "always"
|
||||
},
|
||||
"imports": {
|
||||
"@astrojs/node": "npm:@astrojs/node@^9.1.3",
|
||||
"@astrojs/svelte": "npm:@astrojs/svelte@^7.0.8",
|
||||
"date-fns": "npm:date-fns@^4.1.0"
|
||||
}
|
||||
}
|
||||
|
71
docker-compose.example.yml
Normal file
71
docker-compose.example.yml
Normal file
@ -0,0 +1,71 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:17
|
||||
ports:
|
||||
- "5431:5432"
|
||||
environment:
|
||||
POSTGRES_USER: cvsa
|
||||
POSTGRES_PASSWORD: ""
|
||||
POSTGRES_DB: cvsa_main
|
||||
volumes:
|
||||
- ./data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
ports:
|
||||
- "6378:6379"
|
||||
volumes:
|
||||
- ./redis/data:/data
|
||||
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
|
||||
- ./redis/logs:/logs
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.frontend
|
||||
ports:
|
||||
- "4321:4321"
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- PORT=4321
|
||||
- DB_HOST=db
|
||||
- DB_NAME=cvsa_main
|
||||
- DB_NAME_CRED=cvsa_cred
|
||||
- DB_USER=cvsa
|
||||
- DB_PORT=5432
|
||||
- DB_PASSWORD=""
|
||||
- LOG_VERBOSE=/app/logs/verbose.log
|
||||
- LOG_WARN=/app/logs/warn.log
|
||||
- LOG_ERR=/app/logs/error.log
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- /path/to/your/logs:/app/logs
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.backend
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- DB_HOST=db
|
||||
- DB_NAME=cvsa_main
|
||||
- DB_NAME_CRED=cvsa_cred
|
||||
- DB_USER=cvsa
|
||||
- DB_PORT=5432
|
||||
- DB_PASSWORD=""
|
||||
- LOG_VERBOSE=/app/logs/verbose.log
|
||||
- LOG_WARN=/app/logs/warn.log
|
||||
- LOG_ERR=/app/logs/error.log
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- /path/to/your/logs:/app/logs
|
||||
|
||||
volumes:
|
||||
db_data:
|
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "cvsa",
|
||||
"version": "2.13.22",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/core",
|
||||
"packages/backend",
|
||||
"packages/crawler"
|
||||
],
|
||||
"dependencies": {
|
||||
"postgres": "^3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.2",
|
||||
"vitest-tsconfig-paths": "^3.4.1"
|
||||
}
|
||||
}
|
8
packages/backend/.prettierrc
Normal file
8
packages/backend/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": false,
|
||||
"printWidth": 120,
|
||||
"endOfLine": "lf"
|
||||
}
|
208
packages/backend/bun.lock
Normal file
208
packages/backend/bun.lock
Normal file
@ -0,0 +1,208 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@rabbit-company/argon2id": "^2.1.0",
|
||||
"hono": "^4.7.8",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"ioredis": "^5.6.1",
|
||||
"postgres": "^3.4.5",
|
||||
"rate-limit-redis": "^4.2.0",
|
||||
"yup": "^1.6.1",
|
||||
"zod": "^3.24.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.11",
|
||||
"prettier": "^3.5.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],
|
||||
|
||||
"@rabbit-company/argon2id": ["@rabbit-company/argon2id@2.1.0", "", { "peerDependencies": { "typescript": "^5.6.2" } }, "sha512-X/kt89qjmS9+Zh+DYCGcWeTwHa4C8vY8T3EnSma+vWj7spMzAYX4F8vmGUkny9hygpTOeC/yXwAUdJAfJ52H+w=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="],
|
||||
|
||||
"@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="],
|
||||
|
||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-dbkp5Lo8HDrXkLrONm6bk+yiiYQSntvFUzQp0v3pzTAsXk6FtgVMjdQ+lzFNVAmQFUkPQZ3WMZqH5tTo+Dp/IA=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
||||
|
||||
"postgres": ["postgres@3.4.5", "", {}, "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg=="],
|
||||
|
||||
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
|
||||
|
||||
"property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||
|
||||
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
||||
|
||||
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
|
||||
|
||||
"redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
|
||||
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||
|
||||
"tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"yup": ["yup@1.6.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA=="],
|
||||
|
||||
"zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="],
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { type Client, Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { postgresConfig, postgresConfigCred } from "@core/db/pgConfig.ts";
|
||||
import { createMiddleware } from "hono/factory";
|
||||
|
||||
const pool = new Pool(postgresConfig, 4);
|
||||
const poolCred = new Pool(postgresConfigCred, 2);
|
||||
|
||||
export const db = pool;
|
||||
export const dbCred = poolCred;
|
||||
|
||||
export const dbMiddleware = createMiddleware(async (c, next) => {
|
||||
const connection = await pool.connect();
|
||||
c.set("db", connection);
|
||||
await next();
|
||||
connection.release();
|
||||
});
|
||||
|
||||
export const dbCredMiddleware = createMiddleware(async (c, next) => {
|
||||
const connection = await poolCred.connect();
|
||||
c.set("dbCred", connection);
|
||||
await next();
|
||||
connection.release();
|
||||
});
|
||||
|
||||
declare module "hono" {
|
||||
interface ContextVariableMap {
|
||||
db: Client;
|
||||
dbCred: Client;
|
||||
}
|
||||
}
|
46
packages/backend/db/config.ts
Normal file
46
packages/backend/db/config.ts
Normal file
@ -0,0 +1,46 @@
|
||||
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
|
||||
};
|
5
packages/backend/db/db.ts
Normal file
5
packages/backend/db/db.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import postgres from "postgres";
|
||||
import { postgresConfigNpm, postgresCredConfigNpm } from "./config";
|
||||
|
||||
export const sql = postgres(postgresConfigNpm);
|
||||
export const sqlCred = postgres(postgresCredConfigNpm);
|
58
packages/backend/db/snapshots.ts
Normal file
58
packages/backend/db/snapshots.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { sql } from "./db";
|
||||
import type { VideoSnapshotType } from "@core/db/schema.d.ts";
|
||||
|
||||
export async function getVideoSnapshots(
|
||||
aid: number,
|
||||
limit: number,
|
||||
pageOrOffset: number,
|
||||
reverse: boolean,
|
||||
mode: "page" | "offset" = "page"
|
||||
) {
|
||||
const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset;
|
||||
if (reverse) {
|
||||
return sql<VideoSnapshotType[]>`
|
||||
SELECT *
|
||||
FROM video_snapshot
|
||||
WHERE aid = ${aid}
|
||||
ORDER BY created_at
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`;
|
||||
} else {
|
||||
return sql<VideoSnapshotType[]>`
|
||||
SELECT *
|
||||
FROM video_snapshot
|
||||
WHERE aid = ${aid}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVideoSnapshotsByBV(
|
||||
bv: string,
|
||||
limit: number,
|
||||
pageOrOffset: number,
|
||||
reverse: boolean,
|
||||
mode: "page" | "offset" = "page"
|
||||
) {
|
||||
const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset;
|
||||
if (reverse) {
|
||||
return sql<VideoSnapshotType[]>`
|
||||
SELECT vs.*
|
||||
FROM video_snapshot vs
|
||||
JOIN bilibili_metadata bm ON vs.aid = bm.aid
|
||||
WHERE bm.bvid = ${bv}
|
||||
ORDER BY vs.created_at
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`;
|
||||
} else {
|
||||
return sql<VideoSnapshotType[]>`
|
||||
SELECT vs.*
|
||||
FROM video_snapshot vs
|
||||
JOIN bilibili_metadata bm ON vs.aid = bm.aid
|
||||
WHERE bm.bvid = ${bv}
|
||||
ORDER BY vs.created_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "@cvsa/backend",
|
||||
"imports": {
|
||||
"@rabbit-company/argon2id": "jsr:@rabbit-company/argon2id@^2.1.0",
|
||||
"hono": "jsr:@hono/hono@^4.7.5",
|
||||
"zod": "npm:zod",
|
||||
"yup": "npm:yup",
|
||||
"@core/": "../core/",
|
||||
"log/": "../core/log/",
|
||||
"@crawler/net/videoInfo": "../crawler/net/getVideoInfo.ts",
|
||||
"ioredis": "npm:ioredis"
|
||||
},
|
||||
"tasks": {
|
||||
"dev": "deno serve --env-file=.env --allow-env --allow-net --allow-read --allow-write --allow-run --watch main.ts",
|
||||
"start": "deno serve --env-file=.env --allow-env --allow-net --allow-read --allow-write --allow-run --host 127.0.0.1 main.ts"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"jsx": "precompile",
|
||||
"jsxImportSource": "hono/jsx"
|
||||
},
|
||||
"exports": "./main.ts"
|
||||
}
|
103
packages/backend/lib/const/singers.ts
Normal file
103
packages/backend/lib/const/singers.ts
Normal file
@ -0,0 +1,103 @@
|
||||
export const singers = [
|
||||
{
|
||||
name: "洛天依",
|
||||
color: "#66CCFF",
|
||||
birthday: "0712"
|
||||
},
|
||||
{
|
||||
name: "言和",
|
||||
color: "#00FFCC",
|
||||
birthday: "0711"
|
||||
},
|
||||
{
|
||||
name: "乐正绫",
|
||||
color: "#EE0000",
|
||||
birthday: "0412"
|
||||
},
|
||||
{
|
||||
name: "乐正龙牙",
|
||||
color: "#006666",
|
||||
birthday: "1002"
|
||||
},
|
||||
{
|
||||
name: "徵羽摩柯",
|
||||
color: "#0080FF",
|
||||
birthday: "1210"
|
||||
},
|
||||
{
|
||||
name: "墨清弦",
|
||||
color: "#FFFF00",
|
||||
birthday: "0520"
|
||||
},
|
||||
{
|
||||
name: "星尘",
|
||||
color: "#9999FF",
|
||||
birthday: "0812"
|
||||
},
|
||||
{
|
||||
name: "心华",
|
||||
color: "#EE82EE",
|
||||
birthday: "0210"
|
||||
},
|
||||
{
|
||||
name: "海伊",
|
||||
color: "#3399FF",
|
||||
birthday: "0722"
|
||||
},
|
||||
{
|
||||
name: "苍穹",
|
||||
color: "#8BC0B5",
|
||||
birthday: "0520"
|
||||
},
|
||||
{
|
||||
name: "赤羽",
|
||||
color: "#FF4004",
|
||||
birthday: "1126"
|
||||
},
|
||||
{
|
||||
name: "诗岸",
|
||||
color: "#F6BE72",
|
||||
birthday: "0119"
|
||||
},
|
||||
{
|
||||
name: "牧心",
|
||||
color: "#2A2859",
|
||||
birthday: "0807"
|
||||
}
|
||||
];
|
||||
|
||||
export interface Singer {
|
||||
name: string;
|
||||
color?: string;
|
||||
birthday?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const specialSingers = [
|
||||
{
|
||||
name: "雅音宫羽",
|
||||
message: "你是我最真模样,从来不曾遗忘。"
|
||||
},
|
||||
{
|
||||
name: "初音未来",
|
||||
message: "初始之音,响彻未来!"
|
||||
}
|
||||
];
|
||||
|
||||
export const pickSinger = () => {
|
||||
const index = Math.floor(Math.random() * singers.length);
|
||||
return singers[index];
|
||||
};
|
||||
|
||||
export const pickSpecialSinger = () => {
|
||||
const index = Math.floor(Math.random() * specialSingers.length);
|
||||
return specialSingers[index];
|
||||
};
|
||||
|
||||
export const getSingerForBirthday = (): Singer[] => {
|
||||
const today = new Date();
|
||||
const month = String(today.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(today.getDate()).padStart(2, "0");
|
||||
const datestring = `${month}${day}`;
|
||||
return singers.filter((singer) => singer.birthday === datestring);
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
import { dbCredMiddleware, dbMiddleware } from "./database.ts";
|
||||
import { rootHandler } from "./root.ts";
|
||||
import { getSnapshotsHanlder } from "./snapshots.ts";
|
||||
import { registerHandler } from "./register.ts";
|
||||
import { videoInfoHandler } from "./videoInfo.ts";
|
||||
|
||||
export const app = new Hono();
|
||||
|
||||
app.use("/video/*", dbMiddleware);
|
||||
app.use("/user", dbCredMiddleware);
|
||||
|
||||
app.get("/", ...rootHandler);
|
||||
|
||||
app.get("/video/:id/snapshots", ...getSnapshotsHanlder);
|
||||
app.post("/user", ...registerHandler);
|
||||
|
||||
app.get("/video/:id/info", ...videoInfoHandler);
|
||||
|
||||
const fetch = app.fetch;
|
||||
|
||||
export default {
|
||||
fetch,
|
||||
} satisfies Deno.ServeDefaultExport;
|
||||
|
||||
export const VERSION = "0.4.2";
|
14
packages/backend/middleware/bodyLimits.ts
Normal file
14
packages/backend/middleware/bodyLimits.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { bodyLimit } from "hono/body-limit";
|
||||
import { ErrorResponse } from "../src/schema";
|
||||
|
||||
export const bodyLimitForPing = bodyLimit({
|
||||
maxSize: 14000,
|
||||
onError: (c) => {
|
||||
const res: ErrorResponse<string> = {
|
||||
message: "Body too large",
|
||||
errors: ["Body should not be larger than 14kB."],
|
||||
code: "BODY_TOO_LARGE"
|
||||
};
|
||||
return c.json(res, 413);
|
||||
}
|
||||
});
|
6
packages/backend/middleware/contentType.ts
Normal file
6
packages/backend/middleware/contentType.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Context, Next } from "hono";
|
||||
|
||||
export const contentType = async (c: Context, next: Next) => {
|
||||
await next();
|
||||
c.header("Content-Type", "application/json; charset=utf-8");
|
||||
};
|
19
packages/backend/middleware/index.ts
Normal file
19
packages/backend/middleware/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Hono } from "hono";
|
||||
import { Variables } from "hono/types";
|
||||
import { bodyLimitForPing } from "./bodyLimits.ts";
|
||||
import { pingHandler } from "../routes/ping.ts";
|
||||
import { registerRateLimiter } from "./rateLimiters.ts";
|
||||
import { preetifyResponse } from "./preetifyResponse.ts";
|
||||
import { logger } from "./logger.ts";
|
||||
import { timing } from "hono/timing";
|
||||
import { contentType } from "./contentType.ts";
|
||||
|
||||
export function configureMiddleWares(app: Hono<{ Variables: Variables }>) {
|
||||
app.use("*", contentType);
|
||||
app.use(timing());
|
||||
app.use("*", preetifyResponse);
|
||||
app.use("*", logger({}));
|
||||
|
||||
app.post("/user", registerRateLimiter);
|
||||
app.all("/ping", bodyLimitForPing, ...pingHandler);
|
||||
}
|
160
packages/backend/middleware/logger.ts
Normal file
160
packages/backend/middleware/logger.ts
Normal file
@ -0,0 +1,160 @@
|
||||
// Color constants
|
||||
import { Context, Next } from "hono";
|
||||
import { TimingVariables } from "hono/timing";
|
||||
import { getConnInfo } from "hono/bun";
|
||||
|
||||
const green = "\x1b[97;42m";
|
||||
const white = "\x1b[90;47m";
|
||||
const yellow = "\x1b[90;43m";
|
||||
const red = "\x1b[97;41m";
|
||||
const blue = "\x1b[97;44m";
|
||||
const magenta = "\x1b[97;45m";
|
||||
const cyan = "\x1b[97;46m";
|
||||
const reset = "\x1b[0m";
|
||||
|
||||
let consoleColorMode = "auto";
|
||||
|
||||
function formatCurrentTime() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0"); // Month is 0-indexed
|
||||
const day = String(now.getDate()).padStart(2, "0");
|
||||
const hours = String(now.getHours()).padStart(2, "0");
|
||||
const minutes = String(now.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(now.getSeconds()).padStart(2, "0");
|
||||
const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
|
||||
}
|
||||
|
||||
export const DisableConsoleColor = () => {
|
||||
consoleColorMode = "disable";
|
||||
};
|
||||
|
||||
export const ForceConsoleColor = () => {
|
||||
consoleColorMode = "force";
|
||||
};
|
||||
|
||||
const defaultFormatter = (params) => {
|
||||
const latency = params.latency > 60000 ? `${Math.round(params.latency / 1000)}s` : `${params.latency}ms`;
|
||||
|
||||
let statusColor = white;
|
||||
if (params.isOutputColor) {
|
||||
if (params.status >= 100 && params.status < 300) statusColor = green;
|
||||
else if (params.status >= 300 && params.status < 400) statusColor = white;
|
||||
else if (params.status >= 400 && params.status < 500) statusColor = yellow;
|
||||
else statusColor = red;
|
||||
}
|
||||
|
||||
let methodColor = reset;
|
||||
switch (params.method) {
|
||||
case "GET":
|
||||
methodColor = blue;
|
||||
break;
|
||||
case "POST":
|
||||
methodColor = cyan;
|
||||
break;
|
||||
case "PUT":
|
||||
methodColor = yellow;
|
||||
break;
|
||||
case "DELETE":
|
||||
methodColor = red;
|
||||
break;
|
||||
case "PATCH":
|
||||
methodColor = green;
|
||||
break;
|
||||
case "HEAD":
|
||||
methodColor = magenta;
|
||||
break;
|
||||
case "OPTIONS":
|
||||
methodColor = white;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
`${params.timestamp} |${statusColor} ${params.status} ${reset}| ` +
|
||||
`${latency.padStart(7)} | ${params.ip.padStart(16)} |` +
|
||||
`${methodColor} ${params.method.padEnd(6)}${reset} ${params.path}`
|
||||
);
|
||||
};
|
||||
type Ctx = Context;
|
||||
export const logger = (config) => {
|
||||
const { formatter = defaultFormatter, output = console, skipPaths = [], skip = null } = config;
|
||||
|
||||
// Convert skipPaths to Set for faster lookups
|
||||
const skipPathsSet = new Set(skipPaths);
|
||||
|
||||
return async (c: Ctx, next: Next) => {
|
||||
const start = Date.now();
|
||||
const url = new URL(c.req.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Check if we should skip logging
|
||||
if (skipPathsSet.has(path) || (typeof skip === "function" && skip(c))) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
await next();
|
||||
} catch (error) {
|
||||
// Handle errors
|
||||
const errorParams = {
|
||||
timestamp: formatCurrentTime(),
|
||||
latency: Date.now() - start,
|
||||
status: 500,
|
||||
ip: getClientIP(c),
|
||||
method: c.req.method,
|
||||
path,
|
||||
error: error.message,
|
||||
isOutputColor: shouldColorize(c)
|
||||
};
|
||||
|
||||
output.error(
|
||||
formatter({
|
||||
...errorParams,
|
||||
errorMessage: error.message
|
||||
})
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const status = c.res.status;
|
||||
const latency = Date.now() - start;
|
||||
|
||||
const params = {
|
||||
timestamp: formatCurrentTime(),
|
||||
latency,
|
||||
status,
|
||||
ip: getClientIP(c),
|
||||
method: c.req.method,
|
||||
path,
|
||||
bodySize: c.res.headers.get("content-length") || 0,
|
||||
isOutputColor: shouldColorize(c)
|
||||
};
|
||||
|
||||
// Format and output the log
|
||||
const logMessage = formatter(params);
|
||||
|
||||
if (status >= 400 && status < 500) {
|
||||
output.warn?.(logMessage) || output.log(logMessage);
|
||||
} else if (status >= 500) {
|
||||
output.error?.(logMessage) || output.log(logMessage);
|
||||
} else {
|
||||
output.log(logMessage);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function shouldColorize(c) {
|
||||
if (consoleColorMode === "disable") return false;
|
||||
if (consoleColorMode === "force") return true;
|
||||
|
||||
// In development environment with TTY
|
||||
return process.stdout.isTTY;
|
||||
}
|
||||
|
||||
export function getClientIP(c: Ctx) {
|
||||
const info = getConnInfo(c);
|
||||
return info.remote.address;
|
||||
}
|
18
packages/backend/middleware/preetifyResponse.ts
Normal file
18
packages/backend/middleware/preetifyResponse.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { startTime, endTime } from "hono/timing";
|
||||
import { Context, Next } from "hono";
|
||||
|
||||
export const preetifyResponse = async (c: Context, next: Next) => {
|
||||
await next();
|
||||
const contentType = c.res.headers.get("Content-Type") || "";
|
||||
if (!contentType.includes("application/json")) return;
|
||||
const accept = c.req.header("Accept") || "";
|
||||
const secFetchMode = c.req.header("Sec-Fetch-Mode");
|
||||
const isBrowser = accept.includes("text/html") || secFetchMode === "navigate";
|
||||
if (isBrowser) {
|
||||
const json = await c.res.json();
|
||||
startTime(c, "seralize", "Prettify the response");
|
||||
const prettyJson = JSON.stringify(json, null, 2);
|
||||
endTime(c, "seralize");
|
||||
c.res = new Response(prettyJson, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
||||
}
|
||||
};
|
27
packages/backend/middleware/rateLimiters.ts
Normal file
27
packages/backend/middleware/rateLimiters.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { rateLimiter, Store } from "hono-rate-limiter";
|
||||
import type { BlankEnv } from "hono/types";
|
||||
import { MINUTE } from "@core/const/time.ts";
|
||||
import { getConnInfo } from "hono/bun";
|
||||
import type { Context } from "hono";
|
||||
import { redis } from "@core/db/redis.ts";
|
||||
import { RedisStore } from "rate-limit-redis";
|
||||
|
||||
export const registerRateLimiter = rateLimiter<BlankEnv, "/user", {}>({
|
||||
windowMs: 60 * MINUTE,
|
||||
limit: 10,
|
||||
standardHeaders: "draft-6",
|
||||
keyGenerator: (c) => {
|
||||
const info = getConnInfo(c as unknown as Context<BlankEnv, "/user", {}>);
|
||||
if (!info.remote || !info.remote.address) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
const addr = info.remote.address;
|
||||
const path = new URL(c.req.url).pathname;
|
||||
const method = c.req.method;
|
||||
return `${method}-${path}@${addr}`;
|
||||
},
|
||||
store: new RedisStore({
|
||||
// @ts-expect-error - Known issue: the `c`all` function is not present in @types/ioredis
|
||||
sendCommand: (...args: string[]) => redis.call(...args)
|
||||
}) as unknown as Store
|
||||
});
|
22
packages/backend/package.json
Normal file
22
packages/backend/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"dev": "NODE_ENV=development bun run --hot src/main.ts",
|
||||
"start": "NODE_ENV=production bun run src/main.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rabbit-company/argon2id": "^2.1.0",
|
||||
"hono": "^4.7.8",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"ioredis": "^5.6.1",
|
||||
"postgres": "^3.4.5",
|
||||
"rate-limit-redis": "^4.2.0",
|
||||
"yup": "^1.6.1",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.11",
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import { createHandlers } from "./utils.ts";
|
||||
import Argon2id from "@rabbit-company/argon2id";
|
||||
import { object, string, ValidationError } from "yup";
|
||||
import type { Context } from "hono";
|
||||
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
|
||||
import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
|
||||
const RegistrationBodySchema = object({
|
||||
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
|
||||
password: string().required("Password is required"),
|
||||
nickname: string().optional(),
|
||||
});
|
||||
|
||||
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
|
||||
|
||||
export const userExists = async (username: string, client: Client) => {
|
||||
const query = `
|
||||
SELECT * FROM users WHERE username = $1
|
||||
`;
|
||||
const result = await client.queryObject(query, [username]);
|
||||
return result.rows.length > 0;
|
||||
};
|
||||
|
||||
export const registerHandler = createHandlers(async (c: ContextType) => {
|
||||
const client = c.get("dbCred");
|
||||
|
||||
try {
|
||||
const body = await RegistrationBodySchema.validate(await c.req.json());
|
||||
const { username, password, nickname } = body;
|
||||
|
||||
if (await userExists(username, client)) {
|
||||
return c.json({
|
||||
message: `User "${username}" already exists.`,
|
||||
}, 400);
|
||||
}
|
||||
|
||||
const hash = await Argon2id.hashEncoded(password);
|
||||
|
||||
const query = `
|
||||
INSERT INTO users (username, password, nickname) VALUES ($1, $2, $3)
|
||||
`;
|
||||
await client.queryObject(query, [username, hash, nickname || null]);
|
||||
|
||||
return c.json({
|
||||
message: `User "${username}" registered successfully.`,
|
||||
}, 201);
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
return c.json({
|
||||
message: "Invalid registration data.",
|
||||
errors: e.errors,
|
||||
}, 400);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
return c.json({
|
||||
message: "Invalid JSON in request body.",
|
||||
}, 400);
|
||||
} else {
|
||||
console.error("Registration error:", e);
|
||||
return c.json({
|
||||
message: "An unexpected error occurred during registration.",
|
||||
error: (e as Error).message,
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
10
packages/backend/routes/404.ts
Normal file
10
packages/backend/routes/404.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Context } from "hono";
|
||||
|
||||
export const notFoundRoute = (c: Context) => {
|
||||
return c.json(
|
||||
{
|
||||
message: "Not Found"
|
||||
},
|
||||
404
|
||||
);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import { getSingerForBirthday, pickSinger, pickSpecialSinger, type Singer } from "./singers.ts";
|
||||
import { VERSION } from "./main.ts";
|
||||
import { createHandlers } from "./utils.ts";
|
||||
import { getSingerForBirthday, pickSinger, pickSpecialSinger, type Singer } from "lib/const/singers.ts";
|
||||
import { VERSION } from "src/main.ts";
|
||||
import { createHandlers } from "src/utils.ts";
|
||||
|
||||
export const rootHandler = createHandlers((c) => {
|
||||
let singer: Singer | Singer[];
|
||||
@ -17,13 +17,13 @@ export const rootHandler = createHandlers((c) => {
|
||||
singer = pickSinger();
|
||||
}
|
||||
return c.json({
|
||||
"project": {
|
||||
"name": "中V档案馆",
|
||||
"motto": "一起唱吧,心中的歌!",
|
||||
project: {
|
||||
name: "中V档案馆",
|
||||
motto: "一起唱吧,心中的歌!"
|
||||
},
|
||||
"status": 200,
|
||||
"version": VERSION,
|
||||
"time": Date.now(),
|
||||
"singer": singer,
|
||||
status: 200,
|
||||
version: VERSION,
|
||||
time: Date.now(),
|
||||
singer: singer
|
||||
});
|
||||
});
|
24
packages/backend/routes/ping/index.ts
Normal file
24
packages/backend/routes/ping/index.ts
Normal 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
|
||||
}
|
||||
});
|
||||
});
|
1
packages/backend/routes/user/index.ts
Normal file
1
packages/backend/routes/user/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./register.ts";
|
74
packages/backend/routes/user/register.ts
Normal file
74
packages/backend/routes/user/register.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { createHandlers } from "src/utils.ts";
|
||||
import Argon2id from "@rabbit-company/argon2id";
|
||||
import { object, string, ValidationError } from "yup";
|
||||
import type { Context } from "hono";
|
||||
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
|
||||
import { sqlCred } from "db/db.ts";
|
||||
import { ErrorResponse, StatusResponse } from "src/schema";
|
||||
|
||||
const RegistrationBodySchema = object({
|
||||
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
|
||||
password: string().required("Password is required"),
|
||||
nickname: string().optional()
|
||||
});
|
||||
|
||||
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
|
||||
|
||||
export const userExists = async (username: string) => {
|
||||
const result = await sqlCred`
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE username = ${username}
|
||||
`;
|
||||
return result.length > 0;
|
||||
};
|
||||
|
||||
export const registerHandler = createHandlers(async (c: ContextType) => {
|
||||
try {
|
||||
const body = await RegistrationBodySchema.validate(await c.req.json());
|
||||
const { username, password, nickname } = body;
|
||||
|
||||
if (await userExists(username)) {
|
||||
const response: StatusResponse = {
|
||||
message: `User "${username}" already exists.`
|
||||
};
|
||||
return c.json<StatusResponse>(response, 400);
|
||||
}
|
||||
|
||||
const hash = await Argon2id.hashEncoded(password);
|
||||
|
||||
await sqlCred`
|
||||
INSERT INTO users (username, password, nickname)
|
||||
VALUES (${username}, ${hash}, ${nickname ? nickname : null})
|
||||
`;
|
||||
|
||||
const response: StatusResponse = {
|
||||
message: `User '${username}' registered successfully.`
|
||||
};
|
||||
|
||||
return c.json<StatusResponse>(response, 201);
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Invalid registration data.",
|
||||
errors: e.errors,
|
||||
code: "INVALID_PAYLOAD"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 400);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Invalid JSON payload.",
|
||||
errors: [e.message],
|
||||
code: "INVALID_FORMAT"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 400);
|
||||
} else {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Invalid JSON payload.",
|
||||
errors: [(e as Error).message],
|
||||
code: "UNKNOWN_ERR"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 500);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,21 +1,21 @@
|
||||
import logger from "log/logger.ts";
|
||||
import { Redis } from "ioredis";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { redis } from "@core/db/redis.ts";
|
||||
import { sql } from "../../../db/db.ts";
|
||||
import { number, ValidationError } from "yup";
|
||||
import { createHandlers } from "./utils.ts";
|
||||
import { getVideoInfo, getVideoInfoByBV } from "@crawler/net/videoInfo";
|
||||
import { createHandlers } from "../../../src/utils.ts";
|
||||
import { getVideoInfo, getVideoInfoByBV } from "@core/net/getVideoInfo.ts";
|
||||
import { idSchema } from "./snapshots.ts";
|
||||
import { NetSchedulerError } from "@core/net/delegate.ts";
|
||||
import type { Context } from "hono";
|
||||
import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import type { BlankEnv, BlankInput } from "hono/types";
|
||||
import type { VideoInfoData } from "@core/net/bilibili.d.ts";
|
||||
import { startTime, endTime } from "hono/timing";
|
||||
|
||||
const redis = new Redis({ maxRetriesPerRequest: null });
|
||||
const CACHE_EXPIRATION_SECONDS = 60;
|
||||
|
||||
type ContextType = Context<BlankEnv, "/video/:id/info", BlankInput>;
|
||||
|
||||
async function insertVideoSnapshot(client: Client, data: VideoInfoData) {
|
||||
async function insertVideoSnapshot(data: VideoInfoData) {
|
||||
const views = data.stat.view;
|
||||
const danmakus = data.stat.danmaku;
|
||||
const replies = data.stat.reply;
|
||||
@ -25,21 +25,16 @@ async function insertVideoSnapshot(client: Client, data: VideoInfoData) {
|
||||
const favorites = data.stat.favorite;
|
||||
const aid = data.aid;
|
||||
|
||||
const query: string = `
|
||||
await sql`
|
||||
INSERT INTO video_snapshot (aid, views, danmakus, replies, likes, coins, shares, favorites)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
VALUES (${aid}, ${views}, ${danmakus}, ${replies}, ${likes}, ${coins}, ${shares}, ${favorites})
|
||||
`;
|
||||
|
||||
await client.queryObject(
|
||||
query,
|
||||
[aid, views, danmakus, replies, likes, coins, shares, favorites],
|
||||
);
|
||||
|
||||
logger.log(`Inserted into snapshot for video ${aid} by videoInfo API.`, "api", "fn:insertVideoSnapshot");
|
||||
}
|
||||
|
||||
export const videoInfoHandler = createHandlers(async (c: ContextType) => {
|
||||
const client = c.get("db");
|
||||
startTime(c, "parse", "Parse the request");
|
||||
try {
|
||||
const id = await idSchema.validate(c.req.param("id"));
|
||||
let videoId: string | number = id as string;
|
||||
@ -50,27 +45,33 @@ export const videoInfoHandler = createHandlers(async (c: ContextType) => {
|
||||
}
|
||||
|
||||
const cacheKey = `cvsa:videoInfo:${videoId}`;
|
||||
endTime(c, "parse");
|
||||
startTime(c, "cache", "Check for cached data");
|
||||
const cachedData = await redis.get(cacheKey);
|
||||
|
||||
endTime(c, "cache");
|
||||
if (cachedData) {
|
||||
return c.json(JSON.parse(cachedData));
|
||||
}
|
||||
|
||||
startTime(c, "net", "Fetch data");
|
||||
let result: VideoInfoData | number;
|
||||
if (typeof videoId === "number") {
|
||||
result = await getVideoInfo(videoId, "getVideoInfo");
|
||||
} else {
|
||||
result = await getVideoInfoByBV(videoId, "getVideoInfo");
|
||||
}
|
||||
endTime(c, "net");
|
||||
|
||||
if (typeof result === "number") {
|
||||
return c.json({ message: "Error fetching video info", code: result }, 500);
|
||||
}
|
||||
|
||||
startTime(c, "db", "Write data to database");
|
||||
|
||||
await redis.setex(cacheKey, CACHE_EXPIRATION_SECONDS, JSON.stringify(result));
|
||||
|
||||
await insertVideoSnapshot(client, result);
|
||||
await insertVideoSnapshot(result);
|
||||
|
||||
endTime(c, "db");
|
||||
return c.json(result);
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
@ -1,22 +1,24 @@
|
||||
import type { Context } from "hono";
|
||||
import { createHandlers } from "./utils.ts";
|
||||
import { createHandlers } from "src/utils.ts";
|
||||
import type { BlankEnv, BlankInput } from "hono/types";
|
||||
import { getVideoSnapshots, getVideoSnapshotsByBV } from "@core/db/videoSnapshot.ts";
|
||||
import { getVideoSnapshots, getVideoSnapshotsByBV } from "db/snapshots.ts";
|
||||
import type { VideoSnapshotType } from "@core/db/schema.d.ts";
|
||||
import { boolean, mixed, number, object, ValidationError } from "yup";
|
||||
import { ErrorResponse } from "src/schema";
|
||||
import { startTime, endTime } from "hono/timing";
|
||||
|
||||
const SnapshotQueryParamsSchema = object({
|
||||
ps: number().integer().optional().positive(),
|
||||
pn: number().integer().optional().positive(),
|
||||
offset: number().integer().optional().positive(),
|
||||
reverse: boolean().optional(),
|
||||
reverse: boolean().optional()
|
||||
});
|
||||
|
||||
export const idSchema = mixed().test(
|
||||
"is-valid-id",
|
||||
'id must be a string starting with "av" followed by digits, or "BV" followed by 10 alphanumeric characters, or a positive integer',
|
||||
async (value) => {
|
||||
if (value && await number().integer().isValid(value)) {
|
||||
if (value && (await number().integer().isValid(value))) {
|
||||
const v = parseInt(value as string);
|
||||
return Number.isInteger(v) && v > 0;
|
||||
}
|
||||
@ -34,13 +36,12 @@ export const idSchema = mixed().test(
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type ContextType = Context<BlankEnv, "/video/:id/snapshots", BlankInput>;
|
||||
export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
|
||||
const client = c.get("db");
|
||||
|
||||
startTime(c, "parse", "Parse the request");
|
||||
try {
|
||||
const idParam = await idSchema.validate(c.req.param("id"));
|
||||
let videoId: string | number = idParam as string;
|
||||
@ -70,23 +71,36 @@ export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
|
||||
|
||||
let result: VideoSnapshotType[];
|
||||
|
||||
endTime(c, "parse");
|
||||
startTime(c, "db", "Query the database");
|
||||
if (typeof videoId === "number") {
|
||||
result = await getVideoSnapshots(client, videoId, limit, pageOrOffset, reverse, mode);
|
||||
result = await getVideoSnapshots(videoId, limit, pageOrOffset, reverse, mode);
|
||||
} else {
|
||||
result = await getVideoSnapshotsByBV(client, videoId, limit, pageOrOffset, reverse, mode);
|
||||
result = await getVideoSnapshotsByBV(videoId, limit, pageOrOffset, reverse, mode);
|
||||
}
|
||||
endTime(c, "db");
|
||||
|
||||
const rows = result.map((row) => ({
|
||||
...row,
|
||||
aid: Number(row.aid),
|
||||
aid: Number(row.aid)
|
||||
}));
|
||||
|
||||
return c.json(rows);
|
||||
} catch (e) {
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof ValidationError) {
|
||||
return c.json({ message: "Invalid query parameters", errors: e.errors }, 400);
|
||||
const response: ErrorResponse<string> = {
|
||||
code: "INVALID_QUERY_PARAMS",
|
||||
message: "Invalid query parameters",
|
||||
errors: e.errors
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 400);
|
||||
} else {
|
||||
return c.json({ message: "Unhandled error", error: e }, 500);
|
||||
const response: ErrorResponse<unknown> = {
|
||||
code: "UNKNOWN_ERR",
|
||||
message: "Unhandled error",
|
||||
errors: [e]
|
||||
};
|
||||
return c.json<ErrorResponse<unknown>>(response, 500);
|
||||
}
|
||||
}
|
||||
});
|
2
packages/backend/routes/video/index.ts
Normal file
2
packages/backend/routes/video/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./[id]/info";
|
||||
export * from "./[id]/snapshots";
|
@ -1,103 +0,0 @@
|
||||
export const singers = [
|
||||
{
|
||||
"name": "洛天依",
|
||||
"color": "#66CCFF",
|
||||
"birthday": "0712",
|
||||
},
|
||||
{
|
||||
"name": "言和",
|
||||
"color": "#00FFCC",
|
||||
"birthday": "0711",
|
||||
},
|
||||
{
|
||||
"name": "乐正绫",
|
||||
"color": "#EE0000",
|
||||
"birthday": "0412",
|
||||
},
|
||||
{
|
||||
"name": "乐正龙牙",
|
||||
"color": "#006666",
|
||||
"birthday": "1002",
|
||||
},
|
||||
{
|
||||
"name": "徵羽摩柯",
|
||||
"color": "#0080FF",
|
||||
"birthday": "1210",
|
||||
},
|
||||
{
|
||||
"name": "墨清弦",
|
||||
"color": "#FFFF00",
|
||||
"birthday": "0520",
|
||||
},
|
||||
{
|
||||
"name": "星尘",
|
||||
"color": "#9999FF",
|
||||
"birthday": "0812",
|
||||
},
|
||||
{
|
||||
"name": "心华",
|
||||
"color": "#EE82EE",
|
||||
"birthday": "0210",
|
||||
},
|
||||
{
|
||||
"name": "海伊",
|
||||
"color": "#3399FF",
|
||||
"birthday": "0722",
|
||||
},
|
||||
{
|
||||
"name": "苍穹",
|
||||
"color": "#8BC0B5",
|
||||
"birthday": "0520",
|
||||
},
|
||||
{
|
||||
"name": "赤羽",
|
||||
"color": "#FF4004",
|
||||
"birthday": "1126",
|
||||
},
|
||||
{
|
||||
"name": "诗岸",
|
||||
"color": "#F6BE72",
|
||||
"birthday": "0119",
|
||||
},
|
||||
{
|
||||
"name": "牧心",
|
||||
"color": "#2A2859",
|
||||
"birthday": "0807",
|
||||
},
|
||||
];
|
||||
|
||||
export interface Singer {
|
||||
name: string;
|
||||
color?: string;
|
||||
birthday?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const specialSingers = [
|
||||
{
|
||||
"name": "雅音宫羽",
|
||||
"message": "你是我最真模样,从来不曾遗忘。",
|
||||
},
|
||||
{
|
||||
"name": "初音未来",
|
||||
"message": "初始之音,响彻未来!",
|
||||
},
|
||||
];
|
||||
|
||||
export const pickSinger = () => {
|
||||
const index = Math.floor(Math.random() * singers.length);
|
||||
return singers[index];
|
||||
};
|
||||
|
||||
export const pickSpecialSinger = () => {
|
||||
const index = Math.floor(Math.random() * specialSingers.length);
|
||||
return specialSingers[index];
|
||||
};
|
||||
|
||||
export const getSingerForBirthday = (): Singer[] => {
|
||||
const today = new Date();
|
||||
const month = String(today.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(today.getDate()).padStart(2, "0");
|
||||
const datestring = `${month}${day}`;
|
||||
return singers.filter((singer) => singer.birthday === datestring);
|
||||
};
|
18
packages/backend/src/main.ts
Normal file
18
packages/backend/src/main.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Hono } from "hono";
|
||||
import type { TimingVariables } from "hono/timing";
|
||||
import { startServer } from "./startServer.ts";
|
||||
import { configureRoutes } from "./routing.ts";
|
||||
import { configureMiddleWares } from "middleware";
|
||||
import { notFoundRoute } from "routes/404.ts";
|
||||
|
||||
type Variables = TimingVariables;
|
||||
const app = new Hono<{ Variables: Variables }>();
|
||||
|
||||
app.notFound(notFoundRoute);
|
||||
|
||||
configureMiddleWares(app);
|
||||
configureRoutes(app);
|
||||
|
||||
await startServer(app);
|
||||
|
||||
export const VERSION = "0.4.6";
|
16
packages/backend/src/routing.ts
Normal file
16
packages/backend/src/routing.ts
Normal file
@ -0,0 +1,16 @@
|
||||
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";
|
||||
|
||||
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);
|
||||
}
|
11
packages/backend/src/schema.d.ts
vendored
Normal file
11
packages/backend/src/schema.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
type ErrorCode = "INVALID_QUERY_PARAMS" | "UNKNOWN_ERR" | "INVALID_PAYLOAD" | "INVALID_FORMAT" | "BODY_TOO_LARGE";
|
||||
|
||||
export interface ErrorResponse<E> {
|
||||
code: ErrorCode;
|
||||
message: string;
|
||||
errors: E[];
|
||||
}
|
||||
|
||||
export interface StatusResponse {
|
||||
message: string;
|
||||
}
|
91
packages/backend/src/startServer.ts
Normal file
91
packages/backend/src/startServer.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { serve } from "bun";
|
||||
import { Hono } from "hono";
|
||||
import os from "os";
|
||||
import { BlankSchema, Variables } from "hono/types";
|
||||
|
||||
function getLocalIpAddress(): string {
|
||||
const interfaces = os.networkInterfaces();
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
for (const iface of interfaces[name]!) {
|
||||
if (iface.family === "IPv4" && !iface.internal) {
|
||||
return iface.address;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
function logStartup(hostname: string, port: number, wasAutoIncremented: boolean, originalPort: number) {
|
||||
const localUrl = `http://localhost:${port}`;
|
||||
const networkIp = hostname === "0.0.0.0" ? getLocalIpAddress() : "";
|
||||
const networkUrl = networkIp ? `http://${networkIp}:${port}` : "";
|
||||
|
||||
console.log("\n");
|
||||
console.log("🚀 Server is running at:");
|
||||
console.log(`> Local: ${localUrl}`);
|
||||
if (networkIp) {
|
||||
console.log(`> Network: ${networkUrl}`);
|
||||
}
|
||||
if (wasAutoIncremented) {
|
||||
console.log(`\n⚠️ Port ${originalPort} is in use, using port ${port} instead.`);
|
||||
}
|
||||
console.log("\nPress Ctrl+C to quit.");
|
||||
}
|
||||
|
||||
export async function startServer(app: Hono<{ Variables: Variables }>) {
|
||||
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 PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
|
||||
|
||||
const DEFAULT_PORT = 3000;
|
||||
const MAX_ATTEMPTS = 15;
|
||||
|
||||
if (PORT !== undefined) {
|
||||
try {
|
||||
const server = serve({
|
||||
fetch: app.fetch,
|
||||
hostname: HOST,
|
||||
port: PORT
|
||||
});
|
||||
logStartup(HOST, PORT, false, DEFAULT_PORT);
|
||||
return server;
|
||||
} catch (e: any) {
|
||||
console.error(`Failed to start server on port ${PORT}:`, e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let attemptPort = DEFAULT_PORT;
|
||||
let success = false;
|
||||
let error: unknown = null;
|
||||
|
||||
for (let i = 0; i <= MAX_ATTEMPTS; i++) {
|
||||
try {
|
||||
const server = serve({
|
||||
fetch: app.fetch,
|
||||
hostname: HOST,
|
||||
port: attemptPort
|
||||
});
|
||||
|
||||
const wasAutoIncremented = attemptPort !== DEFAULT_PORT;
|
||||
|
||||
logStartup(HOST, attemptPort, wasAutoIncremented, DEFAULT_PORT);
|
||||
return server;
|
||||
} catch (e: any) {
|
||||
if (e.code === "EADDRINUSE") {
|
||||
attemptPort++;
|
||||
} else {
|
||||
error = e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
console.error(`Could not find an available port after ${MAX_ATTEMPTS + 1} attempts.`);
|
||||
if (error) {
|
||||
console.error("Last error:", (error as Error).message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
19
packages/backend/tsconfig.json
Normal file
19
packages/backend/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"include": ["**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@core/*": ["../core/*"],
|
||||
"@crawler/*": ["../crawler/*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
104
packages/core/bun.lock
Normal file
104
packages/core/bun.lock
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"chalk": "^5.4.1",
|
||||
"ioredis": "^5.6.1",
|
||||
"logform": "^2.7.0",
|
||||
"postgres": "^3.4.5",
|
||||
"winston": "^3.17.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
|
||||
|
||||
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
|
||||
|
||||
"@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],
|
||||
|
||||
"@types/ioredis": ["@types/ioredis@5.0.0", "", { "dependencies": { "ioredis": "*" } }, "sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g=="],
|
||||
|
||||
"@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
|
||||
|
||||
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
|
||||
|
||||
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
|
||||
|
||||
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
|
||||
|
||||
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
|
||||
|
||||
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="],
|
||||
|
||||
"postgres": ["postgres@3.4.5", "", {}, "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
|
||||
|
||||
"redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
|
||||
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
|
||||
|
||||
"triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"winston": ["winston@3.17.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
5
packages/core/const/time.ts
Normal file
5
packages/core/const/time.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const SECOND = 1000;
|
||||
export const MINUTE = 60 * SECOND;
|
||||
export const HOUR = 60 * MINUTE;
|
||||
export const DAY = 24 * HOUR;
|
||||
export const WEEK = 7 * DAY;
|
6
packages/core/db/dbNew.ts
Normal file
6
packages/core/db/dbNew.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import postgres from "postgres";
|
||||
import { postgresConfigNpm } from "./pgConfigNew";
|
||||
|
||||
export const sql = postgres(postgresConfigNpm);
|
||||
|
||||
export const sqlTest = postgres(postgresConfigNpm);
|
@ -1,30 +0,0 @@
|
||||
const requiredEnvVars = ["DB_HOST", "DB_NAME", "DB_USER", "DB_PASSWORD", "DB_PORT"];
|
||||
|
||||
const unsetVars = requiredEnvVars.filter((key) => Deno.env.get(key) === undefined);
|
||||
|
||||
if (unsetVars.length > 0) {
|
||||
throw new Error(`Missing required environment variables: ${unsetVars.join(", ")}`);
|
||||
}
|
||||
|
||||
const databaseHost = Deno.env.get("DB_HOST")!;
|
||||
const databaseName = Deno.env.get("DB_NAME")!;
|
||||
const databaseNameCred = Deno.env.get("DB_NAME_CRED")!;
|
||||
const databaseUser = Deno.env.get("DB_USER")!;
|
||||
const databasePassword = Deno.env.get("DB_PASSWORD")!;
|
||||
const databasePort = Deno.env.get("DB_PORT")!;
|
||||
|
||||
export const postgresConfig = {
|
||||
hostname: databaseHost,
|
||||
port: parseInt(databasePort),
|
||||
database: databaseName,
|
||||
user: databaseUser,
|
||||
password: databasePassword,
|
||||
};
|
||||
|
||||
export const postgresConfigCred = {
|
||||
hostname: databaseHost,
|
||||
port: parseInt(databasePort),
|
||||
database: databaseNameCred,
|
||||
user: databaseUser,
|
||||
password: databasePassword,
|
||||
};
|
38
packages/core/db/pgConfigNew.ts
Normal file
38
packages/core/db/pgConfigNew.ts
Normal file
@ -0,0 +1,38 @@
|
||||
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 postgresConfigCred = {
|
||||
hostname: databaseHost,
|
||||
port: parseInt(databasePort),
|
||||
database: databaseNameCred,
|
||||
user: databaseUser,
|
||||
password: databasePassword
|
||||
};
|
@ -1,3 +1,10 @@
|
||||
import { Redis } from "ioredis";
|
||||
|
||||
export const redis = new Redis({ maxRetriesPerRequest: null });
|
||||
const host = process.env.REDIS_HOST || "localhost";
|
||||
const port = parseInt(process.env.REDIS_PORT) || 6379;
|
||||
|
||||
export const redis = new Redis({
|
||||
port: port,
|
||||
host: host,
|
||||
maxRetriesPerRequest: null,
|
||||
});
|
||||
|
8
packages/core/db/schema.d.ts
vendored
8
packages/core/db/schema.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
export interface AllDataType {
|
||||
id: number;
|
||||
aid: bigint;
|
||||
aid: number;
|
||||
bvid: string | null;
|
||||
description: string | null;
|
||||
uid: number | null;
|
||||
@ -28,12 +28,12 @@ export interface VideoSnapshotType {
|
||||
favorites: number;
|
||||
shares: number;
|
||||
danmakus: number;
|
||||
aid: bigint;
|
||||
aid: number;
|
||||
replies: number;
|
||||
}
|
||||
|
||||
export interface LatestSnapshotType {
|
||||
aid: bigint;
|
||||
aid: number;
|
||||
time: number;
|
||||
views: number;
|
||||
danmakus: number;
|
||||
@ -46,7 +46,7 @@ export interface LatestSnapshotType {
|
||||
|
||||
export interface SnapshotScheduleType {
|
||||
id: number;
|
||||
aid: bigint;
|
||||
aid: number;
|
||||
type?: string;
|
||||
created_at: string;
|
||||
started_at?: string;
|
||||
|
@ -7,6 +7,11 @@
|
||||
"db/": "./db/",
|
||||
"$std/": "https://deno.land/std@0.216.0/",
|
||||
"mq/": "./mq/",
|
||||
"chalk": "npm:chalk"
|
||||
"chalk": "npm:chalk",
|
||||
"winston": "npm:winston",
|
||||
"logform": "npm:logform",
|
||||
"@core/": "./",
|
||||
"child_process": "node:child_process",
|
||||
"util": "node:util"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import winston, { format, transports } from "npm:winston";
|
||||
import type { TransformableInfo } from "npm:logform";
|
||||
import winston, { format, transports } from "winston";
|
||||
import type { TransformableInfo } from "logform";
|
||||
import chalk from "chalk";
|
||||
|
||||
const customFormat = format.printf((info: TransformableInfo) => {
|
||||
@ -24,13 +24,13 @@ const createTransport = (level: string, filename: string) => {
|
||||
let maxsize = undefined;
|
||||
let maxFiles = undefined;
|
||||
let tailable = undefined;
|
||||
if (level === "verbose") {
|
||||
maxsize = 10 * MB;
|
||||
maxFiles = 10;
|
||||
if (level === "silly") {
|
||||
maxsize = 500 * MB;
|
||||
maxFiles = undefined;
|
||||
tailable = false;
|
||||
} else if (level === "warn") {
|
||||
maxsize = 10 * MB;
|
||||
maxFiles = 1;
|
||||
maxFiles = 5;
|
||||
tailable = false;
|
||||
}
|
||||
function replacer(key: unknown, value: unknown) {
|
||||
@ -52,9 +52,9 @@ const createTransport = (level: string, filename: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const sillyLogPath = Deno.env.get("LOG_VERBOSE") ?? "logs/verbose.log";
|
||||
const warnLogPath = Deno.env.get("LOG_WARN") ?? "logs/warn.log";
|
||||
const errorLogPath = Deno.env.get("LOG_ERROR") ?? "logs/error.log";
|
||||
const sillyLogPath = process.env["LOG_VERBOSE"] ?? "logs/verbose.log";
|
||||
const warnLogPath = process.env["LOG_WARN"] ?? "logs/warn.log";
|
||||
const errorLogPath = process.env["LOG_ERROR"] ?? "logs/error.log";
|
||||
|
||||
const winstonLogger = winston.createLogger({
|
||||
levels: winston.config.npm.levels,
|
||||
@ -62,7 +62,7 @@ const winstonLogger = winston.createLogger({
|
||||
new transports.Console({
|
||||
level: "debug",
|
||||
format: format.combine(
|
||||
format.timestamp({ format: "HH:mm:ss.SSS" }),
|
||||
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
|
||||
format.colorize(),
|
||||
format.errors({ stack: true }),
|
||||
customFormat,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
|
||||
logger.error(Error("test error"), "test service");
|
||||
logger.debug(`some string`);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { SlidingWindow } from "./slidingWindow.ts";
|
||||
import type { SlidingWindow } from "./slidingWindow.ts";
|
||||
|
||||
export interface RateLimiterConfig {
|
||||
window: SlidingWindow;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Redis } from "ioredis";
|
||||
import type { Redis } from "ioredis";
|
||||
|
||||
export class SlidingWindow {
|
||||
private redis: Redis;
|
||||
|
@ -1,9 +1,43 @@
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { RateLimiter, type RateLimiterConfig } from "mq/rateLimiter.ts";
|
||||
import { SlidingWindow } from "mq/slidingWindow.ts";
|
||||
import { redis } from "db/redis.ts";
|
||||
import Redis from "ioredis";
|
||||
import { SECOND } from "$std/datetime/constants.ts";
|
||||
import { ReplyError } from "ioredis";
|
||||
import { SECOND } from "../const/time.ts";
|
||||
import { spawn, SpawnOptions } from "child_process";
|
||||
|
||||
export function spawnPromise(
|
||||
command: string,
|
||||
args: string[] = [],
|
||||
options?: SpawnOptions,
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, options);
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
child.stdout?.on("data", (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data) => {
|
||||
stderr += data;
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Error code: ${code}\nstderr: ${stderr}`));
|
||||
} else {
|
||||
resolve({ stdout, stderr });
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface Proxy {
|
||||
type: string;
|
||||
@ -99,7 +133,7 @@ class NetworkDelegate {
|
||||
await this.providerLimiters[providerLimiterId]?.trigger();
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (e instanceof Redis.ReplyError) {
|
||||
if (e instanceof ReplyError) {
|
||||
logger.error(error, "redis");
|
||||
}
|
||||
logger.warn(`Unhandled error: ${error.message}`, "mq", "proxyRequest");
|
||||
@ -209,7 +243,7 @@ class NetworkDelegate {
|
||||
return providerAvailable && proxyAvailable;
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (e instanceof Redis.ReplyError) {
|
||||
if (e instanceof ReplyError) {
|
||||
logger.error(error, "redis");
|
||||
return false;
|
||||
}
|
||||
@ -230,7 +264,7 @@ class NetworkDelegate {
|
||||
|
||||
clearTimeout(timeout);
|
||||
|
||||
return await response.json() as R;
|
||||
return (await response.json()) as R;
|
||||
} catch (e) {
|
||||
throw new NetSchedulerError("Fetch error", "FETCH_ERROR", e);
|
||||
}
|
||||
@ -238,9 +272,7 @@ class NetworkDelegate {
|
||||
|
||||
private async alicloudFcRequest<R>(url: string, region: string): Promise<R> {
|
||||
try {
|
||||
const decoder = new TextDecoder();
|
||||
const output = await new Deno.Command("aliyun", {
|
||||
args: [
|
||||
const output = await spawnPromise("aliyun", [
|
||||
"fc",
|
||||
"POST",
|
||||
`/2023-03-30/functions/proxy-${region}/invocations`,
|
||||
@ -258,9 +290,8 @@ class NetworkDelegate {
|
||||
"10",
|
||||
"--profile",
|
||||
`CVSA-${region}`,
|
||||
],
|
||||
}).output();
|
||||
const out = decoder.decode(output.stdout);
|
||||
]);
|
||||
const out = output.stdout;
|
||||
const rawData = JSON.parse(out);
|
||||
if (rawData.statusCode !== 200) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
@ -269,11 +300,15 @@ class NetworkDelegate {
|
||||
"ALICLOUD_PROXY_ERR",
|
||||
);
|
||||
} else {
|
||||
return JSON.parse(JSON.parse(rawData.body)) as R;
|
||||
return JSON.parse(rawData.body) as R;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e as Error, "net", "fn:alicloudFcRequest");
|
||||
throw new NetSchedulerError(`Unhandled error: Cannot proxy ${url} to ali-fc-${region}.`, "ALICLOUD_PROXY_ERR", e);
|
||||
throw new NetSchedulerError(
|
||||
`Unhandled error: Cannot proxy ${url} to ali-fc-${region}.`,
|
||||
"ALICLOUD_PROXY_ERR",
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -358,7 +393,11 @@ for (const region of regions) {
|
||||
}
|
||||
networkDelegate.addTask("getVideoInfo", "bilibili", "all");
|
||||
networkDelegate.addTask("getLatestVideos", "bilibili", "all");
|
||||
networkDelegate.addTask("snapshotMilestoneVideo", "bilibili", regions.map((region) => `alicloud-${region}`));
|
||||
networkDelegate.addTask(
|
||||
"snapshotMilestoneVideo",
|
||||
"bilibili",
|
||||
regions.map((region) => `alicloud-${region}`),
|
||||
);
|
||||
networkDelegate.addTask("snapshotVideo", "bili_test", [
|
||||
"alicloud-qingdao",
|
||||
"alicloud-shanghai",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import networkDelegate from "@core/net/delegate.ts";
|
||||
import { VideoInfoData, VideoInfoResponse } from "@core/net/bilibili.d.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import type { VideoInfoData, VideoInfoResponse } from "@core/net/bilibili.d.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
|
||||
/*
|
||||
* Fetch video metadata from bilibili API
|
13
packages/core/package.json
Normal file
13
packages/core/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "core",
|
||||
"dependencies": {
|
||||
"chalk": "^5.4.1",
|
||||
"ioredis": "^5.6.1",
|
||||
"logform": "^2.7.0",
|
||||
"postgres": "^3.4.5",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0"
|
||||
}
|
||||
}
|
18
packages/core/tsconfig.json
Normal file
18
packages/core/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"include": ["**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@core/*": ["./*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
2
packages/crawler/.gitignore
vendored
Normal file
2
packages/crawler/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.cache
|
||||
build/
|
433
packages/crawler/bun.lock
Normal file
433
packages/crawler/bun.lock
Normal file
@ -0,0 +1,433 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "core",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.9.5",
|
||||
"@bull-board/express": "^6.9.5",
|
||||
"@huggingface/transformers": "^3.5.1",
|
||||
"bullmq": "^5.52.1",
|
||||
"express": "^5.1.0",
|
||||
"ioredis": "^5.6.1",
|
||||
"onnxruntime-node": "1.19.2",
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.1.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@bull-board/api": ["@bull-board/api@6.9.5", "", { "dependencies": { "redis-info": "^3.1.0" }, "peerDependencies": { "@bull-board/ui": "6.9.5" } }, "sha512-9d03Mu9fuQ3sHAxAwUmlTo8C9KwKHd6Ef2IUOZMG93cAHsQ0oaOpYc5d0roZacYFvfYKr7bTANptKO1f3GLp2g=="],
|
||||
|
||||
"@bull-board/express": ["@bull-board/express@6.9.5", "", { "dependencies": { "@bull-board/api": "6.9.5", "@bull-board/ui": "6.9.5", "ejs": "^3.1.10", "express": "^4.21.1 || ^5.0.0" } }, "sha512-N2B/RSEX/EpIvT/uOJXCACevElv2CUEqrvgoLuwM2n1Fb3VWaatj/S004VLUtHjOCPfODosJ1xoxelelQcV3Dw=="],
|
||||
|
||||
"@bull-board/ui": ["@bull-board/ui@6.9.5", "", { "dependencies": { "@bull-board/api": "6.9.5" } }, "sha512-+4YnDvyuY3MOVkXFxkspRbqawLtIExHzRQ4raQWagOc35KD7v2/ccFGyRPDI/N0bemsiNkOPkcZGf/LFcaOZmA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||
|
||||
"@huggingface/jinja": ["@huggingface/jinja@0.4.1", "", {}, "sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw=="],
|
||||
|
||||
"@huggingface/transformers": ["@huggingface/transformers@3.5.1", "", { "dependencies": { "@huggingface/jinja": "^0.4.1", "onnxruntime-node": "1.21.0", "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4", "sharp": "^0.34.1" } }, "sha512-qWsPoJMBPYcrGuzRMVL//3dwcLXED9r+j+Hzw+hZpBfrMPdyq57ehNs7lew0D34Paj0DO0E0kZQjOZcIVN17hQ=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.1.0" }, "os": "darwin", "cpu": "x64" }, "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.1.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew=="],
|
||||
|
||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.1.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.1.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.1.0" }, "os": "linux", "cpu": "arm" }, "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ=="],
|
||||
|
||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.1.0" }, "os": "linux", "cpu": "s390x" }, "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA=="],
|
||||
|
||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ=="],
|
||||
|
||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg=="],
|
||||
|
||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.1", "", { "dependencies": { "@emnapi/runtime": "^1.4.0" }, "cpu": "none" }, "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg=="],
|
||||
|
||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.1", "", { "os": "win32", "cpu": "x64" }, "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw=="],
|
||||
|
||||
"@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],
|
||||
|
||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||
|
||||
"@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-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
|
||||
|
||||
"@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="],
|
||||
|
||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"concurrently": ["concurrently@9.1.2", "", { "dependencies": { "chalk": "^4.1.2", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" }, "bin": { "concurrently": "dist/bin/concurrently.js", "conc": "dist/bin/concurrently.js" } }, "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ=="],
|
||||
|
||||
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="],
|
||||
|
||||
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||
|
||||
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"flatbuffers": ["flatbuffers@25.2.10", "", {}, "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"guid-typescript": ["guid-typescript@1.0.9", "", {}, "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
||||
|
||||
"jake": ["jake@10.9.2", "", { "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", "filelist": "^1.0.4", "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" } }, "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA=="],
|
||||
|
||||
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
|
||||
|
||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="],
|
||||
|
||||
"matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="],
|
||||
|
||||
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"msgpackr": ["msgpackr@1.11.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g=="],
|
||||
|
||||
"msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"node-abort-controller": ["node-abort-controller@3.1.1", "", {}, "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="],
|
||||
|
||||
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"onnxruntime-common": ["onnxruntime-common@1.19.2", "", {}, "sha512-a4R7wYEVFbZBlp0BfhpbFWqe4opCor3KM+5Wm22Az3NGDcQMiU2hfG/0MfnBs+1ZrlSGmlgWeMcXQkDk1UFb8Q=="],
|
||||
|
||||
"onnxruntime-node": ["onnxruntime-node@1.19.2", "", { "dependencies": { "onnxruntime-common": "1.19.2", "tar": "^7.0.1" }, "os": [ "linux", "win32", "darwin", ] }, "sha512-9eHMP/HKbbeUcqte1JYzaaRC8JPn7ojWeCeoyShO86TOR97OCyIyAIOGX3V95ErjslVhJRXY8Em/caIUc0hm1Q=="],
|
||||
|
||||
"onnxruntime-web": ["onnxruntime-web@1.22.0-dev.20250409-89f8206ba4", "", { "dependencies": { "flatbuffers": "^25.1.24", "guid-typescript": "^1.0.9", "long": "^5.2.3", "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4", "platform": "^1.3.6", "protobufjs": "^7.2.4" } }, "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ=="],
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
||||
|
||||
"platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.5.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||
|
||||
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
||||
|
||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||
|
||||
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
||||
|
||||
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
|
||||
|
||||
"redis-info": ["redis-info@3.1.0", "", { "dependencies": { "lodash": "^4.17.11" } }, "sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg=="],
|
||||
|
||||
"redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
||||
|
||||
"semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
|
||||
|
||||
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
|
||||
|
||||
"serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"sharp": ["sharp@0.34.1", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.7.1" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.1", "@img/sharp-darwin-x64": "0.34.1", "@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-linux-arm": "0.34.1", "@img/sharp-linux-arm64": "0.34.1", "@img/sharp-linux-s390x": "0.34.1", "@img/sharp-linux-x64": "0.34.1", "@img/sharp-linuxmusl-arm64": "0.34.1", "@img/sharp-linuxmusl-x64": "0.34.1", "@img/sharp-wasm32": "0.34.1", "@img/sharp-win32-ia32": "0.34.1", "@img/sharp-win32-x64": "0.34.1" } }, "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.2", "", {}, "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
|
||||
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"@huggingface/transformers/onnxruntime-node": ["onnxruntime-node@1.21.0", "", { "dependencies": { "global-agent": "^3.0.0", "onnxruntime-common": "1.21.0", "tar": "^7.0.1" }, "os": [ "linux", "win32", "darwin", ] }, "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw=="],
|
||||
|
||||
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||
|
||||
"onnxruntime-web/onnxruntime-common": ["onnxruntime-common@1.22.0-dev.20250409-89f8206ba4", "", {}, "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ=="],
|
||||
|
||||
"@huggingface/transformers/onnxruntime-node/onnxruntime-common": ["onnxruntime-common@1.21.0", "", {}, "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ=="],
|
||||
|
||||
"filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
}
|
||||
}
|
@ -1,48 +1,45 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
import { AllDataType, BiliUserType } from "@core/db/schema";
|
||||
import Akari from "ml/akari.ts";
|
||||
import { AkariModelVersion } from "ml/const";
|
||||
|
||||
export async function videoExistsInAllData(client: Client, aid: number) {
|
||||
return await client.queryObject<{ exists: boolean }>(
|
||||
`SELECT EXISTS(SELECT 1 FROM bilibili_metadata WHERE aid = $1)`,
|
||||
[aid],
|
||||
)
|
||||
.then((result) => result.rows[0].exists);
|
||||
export async function videoExistsInAllData(sql: Psql, aid: number) {
|
||||
const rows = await sql<{ exists: boolean }[]>`
|
||||
SELECT EXISTS(SELECT 1 FROM bilibili_metadata WHERE aid = ${aid})
|
||||
`;
|
||||
return rows[0].exists;
|
||||
}
|
||||
|
||||
export async function userExistsInBiliUsers(client: Client, uid: number) {
|
||||
return await client.queryObject<{ exists: boolean }>(`SELECT EXISTS(SELECT 1 FROM bilibili_user WHERE uid = $1)`, [
|
||||
uid,
|
||||
]);
|
||||
export async function userExistsInBiliUsers(sql: Psql, uid: number) {
|
||||
const rows = await sql<{ exists: boolean }[]>`
|
||||
SELECT EXISTS(SELECT 1 FROM bilibili_user WHERE uid = ${uid})
|
||||
`;
|
||||
return rows[0].exists;
|
||||
}
|
||||
|
||||
export async function getUnlabelledVideos(client: Client) {
|
||||
const queryResult = await client.queryObject<{ aid: number }>(
|
||||
`SELECT a.aid FROM bilibili_metadata a LEFT JOIN labelling_result l ON a.aid = l.aid WHERE l.aid IS NULL`,
|
||||
);
|
||||
return queryResult.rows.map((row) => row.aid);
|
||||
export async function getUnlabelledVideos(sql: Psql) {
|
||||
const rows = await sql<{ aid: number }[]>`
|
||||
SELECT a.aid FROM bilibili_metadata a LEFT JOIN labelling_result l ON a.aid = l.aid WHERE l.aid IS NULL
|
||||
`;
|
||||
return rows.map((row) => row.aid);
|
||||
}
|
||||
|
||||
export async function insertVideoLabel(client: Client, aid: number, label: number) {
|
||||
return await client.queryObject(
|
||||
`INSERT INTO labelling_result (aid, label, model_version) VALUES ($1, $2, $3) ON CONFLICT (aid, model_version) DO NOTHING`,
|
||||
[aid, label, Akari.getModelVersion()],
|
||||
);
|
||||
export async function insertVideoLabel(sql: Psql, aid: number, label: number) {
|
||||
await sql`
|
||||
INSERT INTO labelling_result (aid, label, model_version) VALUES (${aid}, ${label}, ${AkariModelVersion}) ON CONFLICT (aid, model_version) DO NOTHING
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getVideoInfoFromAllData(client: Client, aid: number) {
|
||||
const queryResult = await client.queryObject<AllDataType>(
|
||||
`SELECT * FROM bilibili_metadata WHERE aid = $1`,
|
||||
[aid],
|
||||
);
|
||||
const row = queryResult.rows[0];
|
||||
export async function getVideoInfoFromAllData(sql: Psql, aid: number) {
|
||||
const rows = await sql<AllDataType[]>`
|
||||
SELECT * FROM bilibili_metadata WHERE aid = ${aid}
|
||||
`;
|
||||
const row = rows[0];
|
||||
let authorInfo = "";
|
||||
if (row.uid && await userExistsInBiliUsers(client, row.uid)) {
|
||||
const q = await client.queryObject<BiliUserType>(
|
||||
`SELECT * FROM bilibili_user WHERE uid = $1`,
|
||||
[row.uid],
|
||||
);
|
||||
const userRow = q.rows[0];
|
||||
if (row.uid && await userExistsInBiliUsers(sql, row.uid)) {
|
||||
const userRows = await sql<BiliUserType[]>`
|
||||
SELECT * FROM bilibili_user WHERE uid = ${row.uid}
|
||||
`;
|
||||
const userRow = userRows[0];
|
||||
if (userRow) {
|
||||
authorInfo = userRow.desc;
|
||||
}
|
||||
@ -55,33 +52,26 @@ export async function getVideoInfoFromAllData(client: Client, aid: number) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function getUnArchivedBiliUsers(client: Client) {
|
||||
const queryResult = await client.queryObject<{ uid: number }>(
|
||||
`
|
||||
export async function getUnArchivedBiliUsers(sql: Psql) {
|
||||
const rows = await sql<{ uid: number }[]>`
|
||||
SELECT ad.uid
|
||||
FROM bilibili_metadata ad
|
||||
LEFT JOIN bilibili_user bu ON ad.uid = bu.uid
|
||||
WHERE bu.uid IS NULL;
|
||||
`,
|
||||
[],
|
||||
);
|
||||
const rows = queryResult.rows;
|
||||
`;
|
||||
return rows.map((row) => row.uid);
|
||||
}
|
||||
|
||||
export async function setBiliVideoStatus(client: Client, aid: number, status: number) {
|
||||
return await client.queryObject(
|
||||
`UPDATE bilibili_metadata SET status = $1 WHERE aid = $2`,
|
||||
[status, aid],
|
||||
);
|
||||
export async function setBiliVideoStatus(sql: Psql, aid: number, status: number) {
|
||||
await sql`
|
||||
UPDATE bilibili_metadata SET status = ${status} WHERE aid = ${aid}
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getBiliVideoStatus(client: Client, aid: number) {
|
||||
const queryResult = await client.queryObject<{ status: number }>(
|
||||
`SELECT status FROM bilibili_metadata WHERE aid = $1`,
|
||||
[aid],
|
||||
);
|
||||
const rows = queryResult.rows;
|
||||
export async function getBiliVideoStatus(sql: Psql, aid: number) {
|
||||
const rows = await sql<{ status: number }[]>`
|
||||
SELECT status FROM bilibili_metadata WHERE aid = ${aid}
|
||||
`;
|
||||
if (rows.length === 0) return 0;
|
||||
return rows[0].status;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { postgresConfig } from "@core/db/pgConfig";
|
||||
|
||||
const pool = new Pool(postgresConfig, 12);
|
||||
|
||||
export const db = pool;
|
@ -1,9 +1,9 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { LatestSnapshotType } from "@core/db/schema";
|
||||
import { SnapshotNumber } from "mq/task/getVideoStats.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
export async function getVideosNearMilestone(client: Client) {
|
||||
const queryResult = await client.queryObject<LatestSnapshotType>(`
|
||||
export async function getVideosNearMilestone(sql: Psql) {
|
||||
const queryResult = await sql<LatestSnapshotType[]>`
|
||||
SELECT ls.*
|
||||
FROM latest_video_snapshot ls
|
||||
RIGHT JOIN songs ON songs.aid = ls.aid
|
||||
@ -18,8 +18,8 @@ export async function getVideosNearMilestone(client: Client) {
|
||||
(views >= 90000 AND views < 100000) OR
|
||||
(views >= 900000 AND views < 1000000) OR
|
||||
(views >= 9900000 AND views < 10000000)
|
||||
`);
|
||||
return queryResult.rows.map((row) => {
|
||||
`;
|
||||
return queryResult.map((row) => {
|
||||
return {
|
||||
...row,
|
||||
aid: Number(row.aid),
|
||||
@ -27,19 +27,16 @@ export async function getVideosNearMilestone(client: Client) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getLatestVideoSnapshot(client: Client, aid: number): Promise<null | SnapshotNumber> {
|
||||
const queryResult = await client.queryObject<LatestSnapshotType>(
|
||||
`
|
||||
export async function getLatestVideoSnapshot(sql: Psql, aid: number): Promise<null | SnapshotNumber> {
|
||||
const queryResult = await sql<LatestSnapshotType[]>`
|
||||
SELECT *
|
||||
FROM latest_video_snapshot
|
||||
WHERE aid = $1
|
||||
`,
|
||||
[aid],
|
||||
);
|
||||
if (queryResult.rows.length === 0) {
|
||||
WHERE aid = ${aid}
|
||||
`;
|
||||
if (queryResult.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return queryResult.rows.map((row) => {
|
||||
return queryResult.map((row) => {
|
||||
return {
|
||||
...row,
|
||||
aid: Number(row.aid),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { SnapshotScheduleType } from "@core/db/schema";
|
||||
import logger from "log/logger.ts";
|
||||
import { MINUTE } from "$std/datetime/constants.ts";
|
||||
import type { SnapshotScheduleType } from "@core/db/schema.d.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { MINUTE } from "@core/const/time.ts";
|
||||
import { redis } from "@core/db/redis.ts";
|
||||
import { Redis } from "ioredis";
|
||||
import {parseTimestampFromPsql} from "../utils/formatTimestampToPostgre.ts";
|
||||
import { parseTimestampFromPsql } from "../utils/formatTimestampToPostgre.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
const REDIS_KEY = "cvsa:snapshot_window_counts";
|
||||
|
||||
@ -14,11 +14,11 @@ function getCurrentWindowIndex(): number {
|
||||
return Math.floor(minutesSinceMidnight / 5);
|
||||
}
|
||||
|
||||
export async function refreshSnapshotWindowCounts(client: Client, redisClient: Redis) {
|
||||
export async function refreshSnapshotWindowCounts(sql: Psql, redisClient: Redis) {
|
||||
const now = new Date();
|
||||
const startTime = now.getTime();
|
||||
|
||||
const result = await client.queryObject<{ window_start: Date; count: number }>`
|
||||
const result = await sql<{ window_start: Date; count: number }[]>`
|
||||
SELECT
|
||||
date_trunc('hour', started_at) +
|
||||
(EXTRACT(minute FROM started_at)::int / 5 * INTERVAL '5 minutes') AS window_start,
|
||||
@ -33,7 +33,7 @@ export async function refreshSnapshotWindowCounts(client: Client, redisClient: R
|
||||
|
||||
const currentWindow = getCurrentWindowIndex();
|
||||
|
||||
for (const row of result.rows) {
|
||||
for (const row of result) {
|
||||
const targetOffset = Math.floor((row.window_start.getTime() - startTime) / (5 * MINUTE));
|
||||
const offset = currentWindow + targetOffset;
|
||||
if (offset >= 0) {
|
||||
@ -42,10 +42,10 @@ export async function refreshSnapshotWindowCounts(client: Client, redisClient: R
|
||||
}
|
||||
}
|
||||
|
||||
export async function initSnapshotWindowCounts(client: Client, redisClient: Redis) {
|
||||
await refreshSnapshotWindowCounts(client, redisClient);
|
||||
export async function initSnapshotWindowCounts(sql: Psql, redisClient: Redis) {
|
||||
await refreshSnapshotWindowCounts(sql, redisClient);
|
||||
setInterval(async () => {
|
||||
await refreshSnapshotWindowCounts(client, redisClient);
|
||||
await refreshSnapshotWindowCounts(sql, redisClient);
|
||||
}, 5 * MINUTE);
|
||||
}
|
||||
|
||||
@ -54,44 +54,56 @@ async function getWindowCount(redisClient: Redis, offset: number): Promise<numbe
|
||||
return count ? parseInt(count, 10) : 0;
|
||||
}
|
||||
|
||||
export async function snapshotScheduleExists(client: Client, id: number) {
|
||||
const res = await client.queryObject<{ id: number }>(
|
||||
`SELECT id FROM snapshot_schedule WHERE id = $1`,
|
||||
[id],
|
||||
);
|
||||
return res.rows.length > 0;
|
||||
export async function snapshotScheduleExists(sql: Psql, id: number) {
|
||||
const rows = await sql<{ id: number }[]>`
|
||||
SELECT id
|
||||
FROM snapshot_schedule
|
||||
WHERE id = ${id}
|
||||
`;
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
export async function videoHasActiveSchedule(client: Client, aid: number) {
|
||||
const res = await client.queryObject<{ status: string }>(
|
||||
`SELECT status FROM snapshot_schedule WHERE aid = $1 AND (status = 'pending' OR status = 'processing')`,
|
||||
[aid],
|
||||
);
|
||||
return res.rows.length > 0;
|
||||
export async function videoHasActiveSchedule(sql: Psql, aid: number) {
|
||||
const rows = await sql<{ status: string }[]>`
|
||||
SELECT status
|
||||
FROM snapshot_schedule
|
||||
WHERE aid = ${aid}
|
||||
AND (status = 'pending'
|
||||
OR status = 'processing'
|
||||
)
|
||||
`
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
export async function videoHasActiveScheduleWithType(client: Client, aid: number, type: string) {
|
||||
const res = await client.queryObject<{ status: string }>(
|
||||
`SELECT status FROM snapshot_schedule WHERE aid = $1 AND (status = 'pending' OR status = 'processing') AND type = $2`,
|
||||
[aid, type],
|
||||
);
|
||||
return res.rows.length > 0;
|
||||
export async function videoHasActiveScheduleWithType(sql: Psql, aid: number, type: string) {
|
||||
const rows = await sql<{ status: string }[]>`
|
||||
SELECT status FROM snapshot_schedule
|
||||
WHERE aid = ${aid}
|
||||
AND (status = 'pending' OR status = 'processing')
|
||||
AND type = ${type}
|
||||
`;
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
export async function videoHasProcessingSchedule(client: Client, aid: number) {
|
||||
const res = await client.queryObject<{ status: string }>(
|
||||
`SELECT status FROM snapshot_schedule WHERE aid = $1 AND status = 'processing'`,
|
||||
[aid],
|
||||
);
|
||||
return res.rows.length > 0;
|
||||
export async function videoHasProcessingSchedule(sql: Psql, aid: number) {
|
||||
const rows = await sql<{ status: string }[]>`
|
||||
SELECT status
|
||||
FROM snapshot_schedule
|
||||
WHERE aid = ${aid}
|
||||
AND status = 'processing'
|
||||
`
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
export async function bulkGetVideosWithoutProcessingSchedules(client: Client, aids: number[]) {
|
||||
const res = await client.queryObject<{ aid: number }>(
|
||||
`SELECT aid FROM snapshot_schedule WHERE aid = ANY($1) AND status != 'processing' GROUP BY aid`,
|
||||
[aids],
|
||||
);
|
||||
return res.rows.map((row) => row.aid);
|
||||
export async function bulkGetVideosWithoutProcessingSchedules(sql: Psql, aids: number[]) {
|
||||
const rows = await sql<{ aid: string }[]>`
|
||||
SELECT aid
|
||||
FROM snapshot_schedule
|
||||
WHERE aid = ANY(${aids})
|
||||
AND status != 'processing'
|
||||
GROUP BY aid
|
||||
`
|
||||
return rows.map((row) => Number(row.aid));
|
||||
}
|
||||
|
||||
interface Snapshot {
|
||||
@ -99,118 +111,106 @@ interface Snapshot {
|
||||
views: number;
|
||||
}
|
||||
|
||||
export async function findClosestSnapshot(
|
||||
client: Client,
|
||||
aid: number,
|
||||
targetTime: Date,
|
||||
): Promise<Snapshot | null> {
|
||||
const query = `
|
||||
export async function findClosestSnapshot(sql: Psql, aid: number, targetTime: Date): Promise<Snapshot | null> {
|
||||
const result = await sql<{ created_at: string; views: number }[]>`
|
||||
SELECT created_at, views
|
||||
FROM video_snapshot
|
||||
WHERE aid = $1
|
||||
ORDER BY ABS(EXTRACT(EPOCH FROM (created_at - $2::timestamptz)))
|
||||
WHERE aid = ${aid}
|
||||
ORDER BY ABS(EXTRACT(EPOCH FROM (created_at - ${targetTime.toISOString()}::timestamptz)))
|
||||
LIMIT 1
|
||||
`;
|
||||
const result = await client.queryObject<{ created_at: string; views: number }>(
|
||||
query,
|
||||
[aid, targetTime.toISOString()],
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
const row = result.rows[0];
|
||||
if (result.length === 0) return null;
|
||||
const row = result[0];
|
||||
return {
|
||||
created_at: new Date(row.created_at).getTime(),
|
||||
views: row.views,
|
||||
views: row.views
|
||||
};
|
||||
}
|
||||
|
||||
export async function findSnapshotBefore(
|
||||
client: Client,
|
||||
aid: number,
|
||||
targetTime: Date,
|
||||
): Promise<Snapshot | null> {
|
||||
const query = `
|
||||
export async function findSnapshotBefore(sql: Psql, aid: number, targetTime: Date): Promise<Snapshot | null> {
|
||||
const result = await sql<{ created_at: string; views: number }[]>`
|
||||
SELECT created_at, views
|
||||
FROM video_snapshot
|
||||
WHERE aid = $1
|
||||
AND created_at <= $2::timestamptz
|
||||
WHERE aid = ${aid}
|
||||
AND created_at <= ${targetTime}::timestamptz
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
const result = await client.queryObject<{ created_at: string; views: number }>(
|
||||
query,
|
||||
[aid, targetTime.toISOString()],
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
const row = result.rows[0];
|
||||
if (result.length === 0) return null;
|
||||
const row = result[0];
|
||||
return {
|
||||
created_at: new Date(row.created_at).getTime(),
|
||||
views: row.views,
|
||||
views: row.views
|
||||
};
|
||||
}
|
||||
|
||||
export async function hasAtLeast2Snapshots(client: Client, aid: number) {
|
||||
const res = await client.queryObject<{ count: number }>(
|
||||
`SELECT COUNT(*) FROM video_snapshot WHERE aid = $1`,
|
||||
[aid],
|
||||
);
|
||||
return res.rows[0].count >= 2;
|
||||
export async function hasAtLeast2Snapshots(sql: Psql, aid: number) {
|
||||
const res = await sql<{ count: number }[]>`
|
||||
SELECT COUNT(*)
|
||||
FROM video_snapshot
|
||||
WHERE aid = ${aid}
|
||||
`;
|
||||
return res[0].count >= 2;
|
||||
}
|
||||
|
||||
export async function getLatestSnapshot(client: Client, aid: number): Promise<Snapshot | null> {
|
||||
const res = await client.queryObject<{ created_at: string; views: number }>(
|
||||
`SELECT created_at, views FROM video_snapshot WHERE aid = $1 ORDER BY created_at DESC LIMIT 1`,
|
||||
[aid],
|
||||
);
|
||||
if (res.rows.length === 0) return null;
|
||||
const row = res.rows[0];
|
||||
export async function getLatestSnapshot(sql: Psql, aid: number): Promise<Snapshot | null> {
|
||||
const res = await sql<{ created_at: string; views: number }[]>`
|
||||
SELECT created_at, views
|
||||
FROM video_snapshot
|
||||
WHERE aid = ${aid}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
if (res.length === 0) return null;
|
||||
const row = res[0];
|
||||
return {
|
||||
created_at: new Date(row.created_at).getTime(),
|
||||
views: row.views,
|
||||
views: row.views
|
||||
};
|
||||
}
|
||||
|
||||
export async function getLatestActiveScheduleWithType(client: Client, aid: number, type: string) {
|
||||
const query: string = `
|
||||
export async function getLatestActiveScheduleWithType(sql: Psql, aid: number, type: string) {
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM snapshot_schedule
|
||||
WHERE aid = $1
|
||||
AND type = $2
|
||||
WHERE aid = ${aid}
|
||||
AND type = ${type}
|
||||
AND (status = 'pending' OR status = 'processing')
|
||||
ORDER BY started_at DESC
|
||||
LIMIT 1
|
||||
`
|
||||
const res = await client.queryObject<SnapshotScheduleType>(query, [aid, type]);
|
||||
return res.rows[0];
|
||||
`;
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new snapshot schedule record.
|
||||
* @param client The database client.
|
||||
* @param aid The aid of the video.
|
||||
* @param type Type of the snapshot.
|
||||
* @param targetTime Scheduled time for snapshot. (Timestamp in milliseconds)
|
||||
* @param force Ignore all restrictions and force the creation of the schedule.
|
||||
*/
|
||||
export async function scheduleSnapshot(
|
||||
client: Client,
|
||||
sql: Psql,
|
||||
aid: number,
|
||||
type: string,
|
||||
targetTime: number,
|
||||
force: boolean = false,
|
||||
force: boolean = false
|
||||
) {
|
||||
let adjustedTime = new Date(targetTime);
|
||||
const hashActiveSchedule = await videoHasActiveScheduleWithType(client, aid, type);
|
||||
const hashActiveSchedule = await videoHasActiveScheduleWithType(sql, aid, type);
|
||||
if (type == "milestone" && hashActiveSchedule) {
|
||||
const latestActiveSchedule = await getLatestActiveScheduleWithType(client, aid, type);
|
||||
const latestActiveSchedule = await getLatestActiveScheduleWithType(sql, aid, type);
|
||||
const latestScheduleStartedAt = new Date(parseTimestampFromPsql(latestActiveSchedule.started_at!));
|
||||
if (latestScheduleStartedAt > adjustedTime) {
|
||||
await client.queryObject(`
|
||||
await sql`
|
||||
UPDATE snapshot_schedule
|
||||
SET started_at = $1
|
||||
WHERE id = $2
|
||||
`, [adjustedTime, latestActiveSchedule.id]);
|
||||
SET started_at = ${adjustedTime}
|
||||
WHERE id = ${latestActiveSchedule.id}
|
||||
`;
|
||||
logger.log(
|
||||
`Updated snapshot schedule for ${aid} at ${adjustedTime.toISOString()}`,
|
||||
"mq",
|
||||
"fn:scheduleSnapshot",
|
||||
"fn:scheduleSnapshot"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -220,28 +220,33 @@ export async function scheduleSnapshot(
|
||||
adjustedTime = await adjustSnapshotTime(new Date(targetTime), 2000, redis);
|
||||
}
|
||||
logger.log(`Scheduled snapshot for ${aid} at ${adjustedTime.toISOString()}`, "mq", "fn:scheduleSnapshot");
|
||||
return client.queryObject(
|
||||
`INSERT INTO snapshot_schedule (aid, type, started_at) VALUES ($1, $2, $3)`,
|
||||
[aid, type, adjustedTime.toISOString()],
|
||||
);
|
||||
return sql`
|
||||
INSERT INTO snapshot_schedule
|
||||
(aid, type, started_at)
|
||||
VALUES (
|
||||
${aid},
|
||||
${type},
|
||||
${adjustedTime.toISOString()}
|
||||
)
|
||||
`;
|
||||
}
|
||||
|
||||
export async function bulkScheduleSnapshot(
|
||||
client: Client,
|
||||
sql: Psql,
|
||||
aids: number[],
|
||||
type: string,
|
||||
targetTime: number,
|
||||
force: boolean = false,
|
||||
force: boolean = false
|
||||
) {
|
||||
for (const aid of aids) {
|
||||
await scheduleSnapshot(client, aid, type, targetTime, force);
|
||||
await scheduleSnapshot(sql, aid, type, targetTime, force);
|
||||
}
|
||||
}
|
||||
|
||||
export async function adjustSnapshotTime(
|
||||
expectedStartTime: Date,
|
||||
allowedCounts: number = 1000,
|
||||
redisClient: Redis,
|
||||
redisClient: Redis
|
||||
): Promise<Date> {
|
||||
const currentWindow = getCurrentWindowIndex();
|
||||
const targetOffset = Math.floor((expectedStartTime.getTime() - Date.now()) / (5 * MINUTE)) - 6;
|
||||
@ -286,8 +291,8 @@ export async function adjustSnapshotTime(
|
||||
return expectedStartTime;
|
||||
}
|
||||
|
||||
export async function getSnapshotsInNextSecond(client: Client) {
|
||||
const query = `
|
||||
export async function getSnapshotsInNextSecond(sql: Psql) {
|
||||
const rows = await sql<SnapshotScheduleType[]>`
|
||||
SELECT *
|
||||
FROM snapshot_schedule
|
||||
WHERE started_at <= NOW() + INTERVAL '1 seconds' AND status = 'pending' AND type != 'normal'
|
||||
@ -298,13 +303,12 @@ export async function getSnapshotsInNextSecond(client: Client) {
|
||||
END,
|
||||
started_at
|
||||
LIMIT 10;
|
||||
`;
|
||||
const res = await client.queryObject<SnapshotScheduleType>(query, []);
|
||||
return res.rows;
|
||||
`
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function getBulkSnapshotsInNextSecond(client: Client) {
|
||||
const query = `
|
||||
export async function getBulkSnapshotsInNextSecond(sql: Psql) {
|
||||
const rows = await sql<SnapshotScheduleType[]>`
|
||||
SELECT *
|
||||
FROM snapshot_schedule
|
||||
WHERE (started_at <= NOW() + INTERVAL '15 seconds')
|
||||
@ -316,43 +320,38 @@ export async function getBulkSnapshotsInNextSecond(client: Client) {
|
||||
END,
|
||||
started_at
|
||||
LIMIT 1000;
|
||||
`
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function setSnapshotStatus(sql: Psql, id: number, status: string) {
|
||||
return await sql`
|
||||
UPDATE snapshot_schedule SET status = ${status} WHERE id = ${id}
|
||||
`;
|
||||
const res = await client.queryObject<SnapshotScheduleType>(query, []);
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
export async function setSnapshotStatus(client: Client, id: number, status: string) {
|
||||
return await client.queryObject(
|
||||
`UPDATE snapshot_schedule SET status = $2 WHERE id = $1`,
|
||||
[id, status],
|
||||
);
|
||||
export async function bulkSetSnapshotStatus(sql: Psql, ids: number[], status: string) {
|
||||
return await sql`
|
||||
UPDATE snapshot_schedule SET status = ${status} WHERE id = ANY(${ids})
|
||||
`;
|
||||
}
|
||||
|
||||
export async function bulkSetSnapshotStatus(client: Client, ids: number[], status: string) {
|
||||
return await client.queryObject(
|
||||
`UPDATE snapshot_schedule SET status = $2 WHERE id = ANY($1)`,
|
||||
[ids, status],
|
||||
);
|
||||
}
|
||||
|
||||
export async function getVideosWithoutActiveSnapshotSchedule(client: Client) {
|
||||
const query: string = `
|
||||
export async function getVideosWithoutActiveSnapshotSchedule(sql: Psql) {
|
||||
const rows = await sql<{ aid: string }[]>`
|
||||
SELECT s.aid
|
||||
FROM songs s
|
||||
LEFT JOIN snapshot_schedule ss ON s.aid = ss.aid AND (ss.status = 'pending' OR ss.status = 'processing')
|
||||
WHERE ss.aid IS NULL
|
||||
`;
|
||||
const res = await client.queryObject<{ aid: number }>(query, []);
|
||||
return res.rows.map((r) => Number(r.aid));
|
||||
return rows.map((r) => Number(r.aid));
|
||||
}
|
||||
|
||||
export async function getAllVideosWithoutActiveSnapshotSchedule(client: Client) {
|
||||
const query: string = `
|
||||
export async function getAllVideosWithoutActiveSnapshotSchedule(psql: Psql) {
|
||||
const rows = await psql<{ aid: number }[]>`
|
||||
SELECT s.aid
|
||||
FROM bilibili_metadata s
|
||||
LEFT JOIN snapshot_schedule ss ON s.aid = ss.aid AND (ss.status = 'pending' OR ss.status = 'processing')
|
||||
WHERE ss.aid IS NULL
|
||||
`;
|
||||
const res = await client.queryObject<{ aid: number }>(query, []);
|
||||
return res.rows.map((r) => Number(r.aid));
|
||||
`
|
||||
return rows.map((r) => Number(r.aid));
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
import { parseTimestampFromPsql } from "utils/formatTimestampToPostgre.ts";
|
||||
|
||||
export async function getNotCollectedSongs(client: Client) {
|
||||
const queryResult = await client.queryObject<{ aid: number }>(`
|
||||
export async function getNotCollectedSongs(sql: Psql) {
|
||||
const rows = await sql<{ aid: number }[]>`
|
||||
SELECT lr.aid
|
||||
FROM labelling_result lr
|
||||
WHERE lr.label != 0
|
||||
@ -11,35 +11,29 @@ export async function getNotCollectedSongs(client: Client) {
|
||||
FROM songs s
|
||||
WHERE s.aid = lr.aid
|
||||
);
|
||||
`);
|
||||
return queryResult.rows.map((row) => row.aid);
|
||||
`;
|
||||
return rows.map((row) => row.aid);
|
||||
}
|
||||
|
||||
export async function aidExistsInSongs(client: Client, aid: number) {
|
||||
const queryResult = await client.queryObject<{ exists: boolean }>(
|
||||
`
|
||||
export async function aidExistsInSongs(sql: Psql, aid: number) {
|
||||
const rows = await sql<{ exists: boolean }[]>`
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM songs
|
||||
WHERE aid = $1
|
||||
WHERE aid = ${aid}
|
||||
);
|
||||
`,
|
||||
[aid],
|
||||
);
|
||||
return queryResult.rows[0].exists;
|
||||
`;
|
||||
return rows[0].exists;
|
||||
}
|
||||
|
||||
export async function getSongsPublihsedAt(client: Client, aid: number) {
|
||||
const queryResult = await client.queryObject<{ published_at: string }>(
|
||||
`
|
||||
export async function getSongsPublihsedAt(sql: Psql, aid: number) {
|
||||
const rows = await sql<{ published_at: string }[]>`
|
||||
SELECT published_at
|
||||
FROM songs
|
||||
WHERE aid = $1;
|
||||
`,
|
||||
[aid],
|
||||
);
|
||||
if (queryResult.rows.length === 0) {
|
||||
WHERE aid = ${aid};
|
||||
`;
|
||||
if (rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return parseTimestampFromPsql(queryResult.rows[0].published_at);
|
||||
return parseTimestampFromPsql(rows[0].published_at);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { db } from "db/init.ts";
|
||||
|
||||
/**
|
||||
* Executes a function with a database connection.
|
||||
* @param operation The function that accepts the `client` as the parameter.
|
||||
* @param errorHandling Optional function to handle errors.
|
||||
* If no error handling function is provided, the error will be re-thrown.
|
||||
* @param cleanup Optional function to execute after the operation.
|
||||
* @returns The result of the operation or undefined if an error occurred.
|
||||
*/
|
||||
export async function withDbConnection<T>(
|
||||
operation: (client: Client) => Promise<T>,
|
||||
errorHandling?: (error: unknown, client: Client) => void,
|
||||
cleanup?: () => void,
|
||||
): Promise<T | undefined> {
|
||||
const client = await db.connect();
|
||||
try {
|
||||
return await operation(client);
|
||||
} catch (error) {
|
||||
if (errorHandling) {
|
||||
errorHandling(error, client);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
if (cleanup) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "@cvsa/crawler",
|
||||
"tasks": {
|
||||
"crawl-raw-bili": "deno --allow-env --allow-ffi --allow-read --allow-net --allow-write --allow-run src/db/raw/insertAidsToDB.ts",
|
||||
"crawl-bili-aids": "deno --allow-env --allow-ffi --allow-read --allow-net --allow-write --allow-run src/db/raw/fetchAids.ts",
|
||||
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
|
||||
"manifest": "deno task cli manifest $(pwd)",
|
||||
"start": "deno run -A --watch=static/,routes/ dev.ts",
|
||||
"build": "deno run -A dev.ts build",
|
||||
"preview": "deno run -A main.ts",
|
||||
"worker:main": "deno run --env-file=.env --allow-env --allow-read --allow-ffi --allow-net --allow-write --allow-run ./src/worker.ts",
|
||||
"worker:filter": "deno run --env-file=.env --allow-env --allow-read --allow-ffi --allow-net --allow-write ./src/filterWorker.ts",
|
||||
"adder": "deno run --env-file=.env --allow-env --allow-read --allow-ffi --allow-net ./src/jobAdder.ts",
|
||||
"bullui": "deno run --allow-read --allow-env --allow-ffi --allow-net ./src/bullui.ts",
|
||||
"all": "concurrently --restart-tries -1 'deno task worker:main' 'deno task adder' 'deno task bullui' 'deno task worker:filter'",
|
||||
"test": "deno test ./test/ --allow-env --allow-ffi --allow-read --allow-net --allow-write --allow-run"
|
||||
},
|
||||
"lint": {
|
||||
"rules": {
|
||||
"tags": ["recommended"]
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"@std/assert": "jsr:@std/assert@1",
|
||||
"$std/": "https://deno.land/std@0.216.0/",
|
||||
"@std/datetime": "jsr:@std/datetime@^0.225.4",
|
||||
"@huggingface/transformers": "npm:@huggingface/transformers@3.0.0",
|
||||
"bullmq": "npm:bullmq",
|
||||
"mq/": "./mq/",
|
||||
"db/": "./db/",
|
||||
"@core/": "../core/",
|
||||
"log/": "../core/log/",
|
||||
"net/": "./net/",
|
||||
"ml/": "./ml/",
|
||||
"utils/": "./utils/",
|
||||
"ioredis": "npm:ioredis",
|
||||
"@bull-board/api": "npm:@bull-board/api",
|
||||
"@bull-board/express": "npm:@bull-board/express",
|
||||
"express": "npm:express",
|
||||
"src/": "./src/",
|
||||
"onnxruntime": "npm:onnxruntime-node@1.19.2",
|
||||
"chalk": "npm:chalk",
|
||||
"@core/db/schema": "../core/db/schema.d.ts",
|
||||
"@core/db/pgConfig": "../core/db/pgConfig.ts"
|
||||
},
|
||||
"exports": "./main.ts"
|
||||
}
|
3
packages/crawler/global.d.ts
vendored
Normal file
3
packages/crawler/global.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import type postgres from "postgres";
|
||||
|
||||
export type Psql = postgres.Sql<{}>;
|
@ -1,16 +1,17 @@
|
||||
import { AIManager } from "ml/manager.ts";
|
||||
import * as ort from "onnxruntime";
|
||||
import logger from "log/logger.ts";
|
||||
import * as ort from "onnxruntime-node";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { WorkerError } from "mq/schema.ts";
|
||||
import { AutoTokenizer, PreTrainedTokenizer } from "@huggingface/transformers";
|
||||
import { AkariModelVersion } from "./const";
|
||||
|
||||
const tokenizerModel = "alikia2x/jina-embedding-v3-m2v-1024";
|
||||
const onnxClassifierPath = "../../model/akari/3.17.onnx";
|
||||
const onnxClassifierPath = `../../model/akari/${AkariModelVersion}.onnx`;
|
||||
const onnxEmbeddingPath = "../../model/embedding/model.onnx";
|
||||
|
||||
class AkariProto extends AIManager {
|
||||
private tokenizer: PreTrainedTokenizer | null = null;
|
||||
private readonly modelVersion = "3.17";
|
||||
private readonly modelVersion = AkariModelVersion;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -104,4 +105,5 @@ class AkariProto extends AIManager {
|
||||
}
|
||||
|
||||
const Akari = new AkariProto();
|
||||
await Akari.init();
|
||||
export default Akari;
|
||||
|
1
packages/crawler/ml/const.ts
Normal file
1
packages/crawler/ml/const.ts
Normal file
@ -0,0 +1 @@
|
||||
export const AkariModelVersion = "3.17";
|
@ -1,5 +1,5 @@
|
||||
import * as ort from "onnxruntime";
|
||||
import logger from "log/logger.ts";
|
||||
import * as ort from "onnxruntime-node";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { WorkerError } from "mq/schema.ts";
|
||||
|
||||
export class AIManager {
|
||||
|
@ -1,40 +1,40 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { Job } from "bullmq";
|
||||
import { getAllVideosWithoutActiveSnapshotSchedule, scheduleSnapshot } from "db/snapshotSchedule.ts";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { lockManager } from "mq/lockManager.ts";
|
||||
import { getLatestVideoSnapshot } from "db/snapshot.ts";
|
||||
import { HOUR, MINUTE } from "$std/datetime/constants.ts";
|
||||
import { HOUR, MINUTE } from "@core/const/time.ts";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export const archiveSnapshotsWorker = async (_job: Job) =>
|
||||
await withDbConnection<void>(async (client: Client) => {
|
||||
export const archiveSnapshotsWorker = async (_job: Job) => {
|
||||
try {
|
||||
const startedAt = Date.now();
|
||||
if (await lockManager.isLocked("dispatchArchiveSnapshots")) {
|
||||
logger.log("dispatchArchiveSnapshots is already running", "mq");
|
||||
return;
|
||||
}
|
||||
await lockManager.acquireLock("dispatchArchiveSnapshots", 30 * 60);
|
||||
const aids = await getAllVideosWithoutActiveSnapshotSchedule(client);
|
||||
const aids = await getAllVideosWithoutActiveSnapshotSchedule(sql);
|
||||
for (const rawAid of aids) {
|
||||
const aid = Number(rawAid);
|
||||
const latestSnapshot = await getLatestVideoSnapshot(client, aid);
|
||||
const latestSnapshot = await getLatestVideoSnapshot(sql, aid);
|
||||
const now = Date.now();
|
||||
const lastSnapshotedAt = latestSnapshot?.time ?? now;
|
||||
const interval = 168;
|
||||
logger.log(
|
||||
`Scheduled archive snapshot for aid ${aid} in ${interval} hours.`,
|
||||
"mq",
|
||||
"fn:archiveSnapshotsWorker",
|
||||
"fn:archiveSnapshotsWorker"
|
||||
);
|
||||
const targetTime = lastSnapshotedAt + interval * HOUR;
|
||||
await scheduleSnapshot(client, aid, "archive", targetTime);
|
||||
await scheduleSnapshot(sql, aid, "archive", targetTime);
|
||||
if (now - startedAt > 250 * MINUTE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, (e) => {
|
||||
} catch (e) {
|
||||
logger.error(e as Error, "mq", "fn:archiveSnapshotsWorker");
|
||||
}, async () => {
|
||||
} finally {
|
||||
await lockManager.releaseLock("dispatchArchiveSnapshots");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1,23 +1,22 @@
|
||||
import { Job } from "bullmq";
|
||||
import { db } from "db/init.ts";
|
||||
import { getUnlabelledVideos, getVideoInfoFromAllData, insertVideoLabel } from "../../db/bilibili_metadata.ts";
|
||||
import Akari from "ml/akari.ts";
|
||||
import { ClassifyVideoQueue } from "mq/index.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { lockManager } from "mq/lockManager.ts";
|
||||
import { aidExistsInSongs } from "db/songs.ts";
|
||||
import { insertIntoSongs } from "mq/task/collectSongs.ts";
|
||||
import { scheduleSnapshot } from "db/snapshotSchedule.ts";
|
||||
import { MINUTE } from "@std/datetime";
|
||||
import { MINUTE } from "@core/const/time.ts";
|
||||
import { sql } from "@core/db/dbNew.ts";
|
||||
|
||||
export const classifyVideoWorker = async (job: Job) => {
|
||||
const client = await db.connect();
|
||||
const aid = job.data.aid;
|
||||
if (!aid) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
const videoInfo = await getVideoInfoFromAllData(client, aid);
|
||||
const videoInfo = await getVideoInfoFromAllData(sql, aid);
|
||||
const title = videoInfo.title?.trim() || "untitled";
|
||||
const description = videoInfo.description?.trim() || "N/A";
|
||||
const tags = videoInfo.tags?.trim() || "empty";
|
||||
@ -25,16 +24,14 @@ export const classifyVideoWorker = async (job: Job) => {
|
||||
if (label == -1) {
|
||||
logger.warn(`Failed to classify video ${aid}`, "ml");
|
||||
}
|
||||
await insertVideoLabel(client, aid, label);
|
||||
await insertVideoLabel(sql, aid, label);
|
||||
|
||||
const exists = await aidExistsInSongs(client, aid);
|
||||
const exists = await aidExistsInSongs(sql, aid);
|
||||
if (!exists && label !== 0) {
|
||||
await scheduleSnapshot(client, aid, "new", Date.now() + 10 * MINUTE, true);
|
||||
await insertIntoSongs(client, aid);
|
||||
await scheduleSnapshot(sql, aid, "new", Date.now() + 10 * MINUTE, true);
|
||||
await insertIntoSongs(sql, aid);
|
||||
}
|
||||
|
||||
client.release();
|
||||
|
||||
await job.updateData({
|
||||
...job.data,
|
||||
label: label,
|
||||
@ -51,10 +48,8 @@ export const classifyVideosWorker = async () => {
|
||||
|
||||
await lockManager.acquireLock("classifyVideos");
|
||||
|
||||
const client = await db.connect();
|
||||
const videos = await getUnlabelledVideos(client);
|
||||
const videos = await getUnlabelledVideos(sql);
|
||||
logger.log(`Found ${videos.length} unlabelled videos`);
|
||||
client.release();
|
||||
|
||||
let i = 0;
|
||||
for (const aid of videos) {
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { Job } from "bullmq";
|
||||
import { collectSongs } from "mq/task/collectSongs.ts";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
|
||||
export const collectSongsWorker = (_job: Job): Promise<void> =>
|
||||
withDbConnection(async (client: Client) => {
|
||||
await collectSongs(client);
|
||||
});
|
||||
export const collectSongsWorker = async (_job: Job): Promise<void> =>{
|
||||
await collectSongs();
|
||||
return;
|
||||
}
|
@ -1,19 +1,18 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { Job } from "bullmq";
|
||||
import { getVideosNearMilestone } from "db/snapshot.ts";
|
||||
import { getAdjustedShortTermETA } from "mq/scheduling.ts";
|
||||
import { truncate } from "utils/truncate.ts";
|
||||
import { scheduleSnapshot } from "db/snapshotSchedule.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import { HOUR, MINUTE, SECOND } from "@std/datetime";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { HOUR, MINUTE, SECOND } from "@core/const/time.ts";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export const dispatchMilestoneSnapshotsWorker = (_job: Job): Promise<void> =>
|
||||
withDbConnection(async (client: Client) => {
|
||||
const videos = await getVideosNearMilestone(client);
|
||||
export const dispatchMilestoneSnapshotsWorker = async (_job: Job) => {
|
||||
try {
|
||||
const videos = await getVideosNearMilestone(sql);
|
||||
for (const video of videos) {
|
||||
const aid = Number(video.aid);
|
||||
const eta = await getAdjustedShortTermETA(client, aid);
|
||||
const eta = await getAdjustedShortTermETA(sql, aid);
|
||||
if (eta > 144) continue;
|
||||
const now = Date.now();
|
||||
const scheduledNextSnapshotDelay = eta * HOUR;
|
||||
@ -21,9 +20,10 @@ export const dispatchMilestoneSnapshotsWorker = (_job: Job): Promise<void> =>
|
||||
const minInterval = 1 * SECOND;
|
||||
const delay = truncate(scheduledNextSnapshotDelay, minInterval, maxInterval);
|
||||
const targetTime = now + delay;
|
||||
await scheduleSnapshot(client, aid, "milestone", targetTime);
|
||||
await scheduleSnapshot(sql, aid, "milestone", targetTime);
|
||||
logger.log(`Scheduled milestone snapshot for aid ${aid} in ${(delay / MINUTE).toFixed(2)} mins.`, "mq");
|
||||
}
|
||||
}, (e) => {
|
||||
} catch (e) {
|
||||
logger.error(e as Error, "mq", "fn:dispatchMilestoneSnapshotsWorker");
|
||||
});
|
||||
};
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { Job } from "bullmq";
|
||||
import { getLatestVideoSnapshot } from "db/snapshot.ts";
|
||||
import { truncate } from "utils/truncate.ts";
|
||||
import { getVideosWithoutActiveSnapshotSchedule, scheduleSnapshot } from "db/snapshotSchedule.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import { HOUR, MINUTE, WEEK } from "@std/datetime";
|
||||
import { lockManager } from "../lockManager.ts";
|
||||
import { getRegularSnapshotInterval } from "../task/regularSnapshotInterval.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { HOUR, MINUTE, WEEK } from "@core/const/time.ts";
|
||||
import { lockManager } from "mq/lockManager.ts";
|
||||
import { getRegularSnapshotInterval } from "mq/task/regularSnapshotInterval.ts";
|
||||
import { sql } from "@core/db/dbNew.ts";
|
||||
|
||||
export const dispatchRegularSnapshotsWorker = async (_job: Job): Promise<void> =>
|
||||
await withDbConnection(async (client: Client) => {
|
||||
export const dispatchRegularSnapshotsWorker = async (_job: Job): Promise<void> => {
|
||||
try {
|
||||
const startedAt = Date.now();
|
||||
if (await lockManager.isLocked("dispatchRegularSnapshots")) {
|
||||
logger.log("dispatchRegularSnapshots is already running", "mq");
|
||||
@ -18,22 +17,23 @@ export const dispatchRegularSnapshotsWorker = async (_job: Job): Promise<void> =
|
||||
}
|
||||
await lockManager.acquireLock("dispatchRegularSnapshots", 30 * 60);
|
||||
|
||||
const aids = await getVideosWithoutActiveSnapshotSchedule(client);
|
||||
const aids = await getVideosWithoutActiveSnapshotSchedule(sql);
|
||||
for (const rawAid of aids) {
|
||||
const aid = Number(rawAid);
|
||||
const latestSnapshot = await getLatestVideoSnapshot(client, aid);
|
||||
const latestSnapshot = await getLatestVideoSnapshot(sql, aid);
|
||||
const now = Date.now();
|
||||
const lastSnapshotedAt = latestSnapshot?.time ?? now;
|
||||
const interval = await getRegularSnapshotInterval(client, aid);
|
||||
const interval = await getRegularSnapshotInterval(sql, aid);
|
||||
logger.log(`Scheduled regular snapshot for aid ${aid} in ${interval} hours.`, "mq");
|
||||
const targetTime = truncate(lastSnapshotedAt + interval * HOUR, now + 1, now + 100000 * WEEK);
|
||||
await scheduleSnapshot(client, aid, "normal", targetTime);
|
||||
await scheduleSnapshot(sql, aid, "normal", targetTime);
|
||||
if (now - startedAt > 25 * MINUTE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, (e) => {
|
||||
} catch (e) {
|
||||
logger.error(e as Error, "mq", "fn:regularSnapshotsWorker");
|
||||
}, async () => {
|
||||
} finally {
|
||||
await lockManager.releaseLock("dispatchRegularSnapshots");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { sql } from "@core/db/dbNew";
|
||||
import { Job } from "bullmq";
|
||||
import { queueLatestVideos } from "mq/task/queueLatestVideo.ts";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
|
||||
export const getLatestVideosWorker = (_job: Job): Promise<void> =>
|
||||
withDbConnection(async (client: Client) => {
|
||||
await queueLatestVideos(client);
|
||||
});
|
||||
export const getLatestVideosWorker = async (_job: Job): Promise<void> =>{
|
||||
await queueLatestVideos(sql);
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { Job } from "bullmq";
|
||||
import { insertVideoInfo } from "mq/task/getVideoDetails.ts";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export const getVideoInfoWorker = async (job: Job): Promise<void> =>
|
||||
await withDbConnection<void>(async (client: Client) => {
|
||||
export const getVideoInfoWorker = async (job: Job): Promise<void> => {
|
||||
const aid = job.data.aid;
|
||||
if (!aid) {
|
||||
logger.warn("aid does not exists", "mq", "job:getVideoInfo");
|
||||
return;
|
||||
}
|
||||
await insertVideoInfo(client, aid);
|
||||
});
|
||||
await insertVideoInfo(sql, aid);
|
||||
}
|
||||
|
@ -1,20 +1,25 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import { Job } from "bullmq";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { scheduleSnapshot, setSnapshotStatus } from "db/snapshotSchedule.ts";
|
||||
import { SECOND } from "@std/datetime";
|
||||
import { SECOND } from "@core/const/time.ts";
|
||||
import { getTimeoutSchedulesCount } from "mq/task/getTimeoutSchedulesCount.ts";
|
||||
import { removeAllTimeoutSchedules } from "mq/task/removeAllTimeoutSchedules.ts";
|
||||
|
||||
export const scheduleCleanupWorker = async (_job: Job): Promise<void> =>
|
||||
await withDbConnection<void>(async (client: Client) => {
|
||||
if (await getTimeoutSchedulesCount(client) > 2000) {
|
||||
await removeAllTimeoutSchedules(client);
|
||||
interface SnapshotSchedule {
|
||||
id: bigint;
|
||||
aid: bigint;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export const scheduleCleanupWorker = async (_job: Job): Promise<void> => {
|
||||
try {
|
||||
if ((await getTimeoutSchedulesCount()) > 2000) {
|
||||
await removeAllTimeoutSchedules();
|
||||
return;
|
||||
}
|
||||
|
||||
const query: string = `
|
||||
const rows = await sql<SnapshotSchedule[]>`
|
||||
SELECT id, aid, type
|
||||
FROM snapshot_schedule
|
||||
WHERE status IN ('pending', 'processing')
|
||||
@ -26,20 +31,21 @@ export const scheduleCleanupWorker = async (_job: Job): Promise<void> =>
|
||||
AND started_at < NOW() - INTERVAL '2 minutes'
|
||||
AND type = 'milestone'
|
||||
`;
|
||||
const { rows } = await client.queryObject<{ id: bigint; aid: bigint; type: string }>(query);
|
||||
|
||||
if (rows.length === 0) return;
|
||||
for (const row of rows) {
|
||||
const id = Number(row.id);
|
||||
const aid = Number(row.aid);
|
||||
const type = row.type;
|
||||
await setSnapshotStatus(client, id, "timeout");
|
||||
await scheduleSnapshot(client, aid, type, Date.now() + 10 * SECOND);
|
||||
await setSnapshotStatus(sql, id, "timeout");
|
||||
await scheduleSnapshot(sql, aid, type, Date.now() + 10 * SECOND);
|
||||
logger.log(
|
||||
`Schedule ${id} has not received any response in a while, rescheduled.`,
|
||||
"mq",
|
||||
"fn:scheduleCleanupWorker",
|
||||
"fn:scheduleCleanupWorker"
|
||||
);
|
||||
}
|
||||
}, (e) => {
|
||||
} catch (e) {
|
||||
logger.error(e as Error, "mq", "fn:scheduleCleanupWorker");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Job } from "bullmq";
|
||||
import { db } from "db/init.ts";
|
||||
import {
|
||||
bulkGetVideosWithoutProcessingSchedules,
|
||||
bulkSetSnapshotStatus,
|
||||
@ -8,8 +7,9 @@ import {
|
||||
setSnapshotStatus,
|
||||
videoHasProcessingSchedule,
|
||||
} from "db/snapshotSchedule.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { SnapshotQueue } from "mq/index.ts";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
const priorityMap: { [key: string]: number } = {
|
||||
"milestone": 1,
|
||||
@ -17,17 +17,16 @@ const priorityMap: { [key: string]: number } = {
|
||||
};
|
||||
|
||||
export const bulkSnapshotTickWorker = async (_job: Job) => {
|
||||
const client = await db.connect();
|
||||
try {
|
||||
const schedules = await getBulkSnapshotsInNextSecond(client);
|
||||
const schedules = await getBulkSnapshotsInNextSecond(sql);
|
||||
const count = schedules.length;
|
||||
const groups = Math.ceil(count / 30);
|
||||
for (let i = 0; i < groups; i++) {
|
||||
const group = schedules.slice(i * 30, (i + 1) * 30);
|
||||
const aids = group.map((schedule) => Number(schedule.aid));
|
||||
const filteredAids = await bulkGetVideosWithoutProcessingSchedules(client, aids);
|
||||
const filteredAids = await bulkGetVideosWithoutProcessingSchedules(sql, aids);
|
||||
if (filteredAids.length === 0) continue;
|
||||
await bulkSetSnapshotStatus(client, filteredAids, "processing");
|
||||
await bulkSetSnapshotStatus(sql, filteredAids, "processing");
|
||||
const schedulesData = group.map((schedule) => {
|
||||
return {
|
||||
aid: Number(schedule.aid),
|
||||
@ -46,17 +45,14 @@ export const bulkSnapshotTickWorker = async (_job: Job) => {
|
||||
return `OK`;
|
||||
} catch (e) {
|
||||
logger.error(e as Error);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
export const snapshotTickWorker = async (_job: Job) => {
|
||||
const client = await db.connect();
|
||||
try {
|
||||
const schedules = await getSnapshotsInNextSecond(client);
|
||||
const schedules = await getSnapshotsInNextSecond(sql);
|
||||
for (const schedule of schedules) {
|
||||
if (await videoHasProcessingSchedule(client, Number(schedule.aid))) {
|
||||
if (await videoHasProcessingSchedule(sql, Number(schedule.aid))) {
|
||||
continue;
|
||||
}
|
||||
let priority = 3;
|
||||
@ -64,7 +60,7 @@ export const snapshotTickWorker = async (_job: Job) => {
|
||||
priority = priorityMap[schedule.type];
|
||||
}
|
||||
const aid = Number(schedule.aid);
|
||||
await setSnapshotStatus(client, schedule.id, "processing");
|
||||
await setSnapshotStatus(sql, schedule.id, "processing");
|
||||
await SnapshotQueue.add("snapshotVideo", {
|
||||
aid: Number(aid),
|
||||
id: Number(schedule.id),
|
||||
@ -74,8 +70,6 @@ export const snapshotTickWorker = async (_job: Job) => {
|
||||
return `OK`;
|
||||
} catch (e) {
|
||||
logger.error(e as Error);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { withDbConnection } from "db/withConnection.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { Job } from "bullmq";
|
||||
import { scheduleSnapshot, setSnapshotStatus, snapshotScheduleExists } from "db/snapshotSchedule.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import { HOUR, MINUTE, SECOND } from "@std/datetime";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { HOUR, MINUTE, SECOND } from "@core/const/time.ts";
|
||||
import { lockManager } from "mq/lockManager.ts";
|
||||
import { getBiliVideoStatus, setBiliVideoStatus } from "../../db/bilibili_metadata.ts";
|
||||
import { insertVideoSnapshot } from "mq/task/getVideoStats.ts";
|
||||
import { getSongsPublihsedAt } from "db/songs.ts";
|
||||
import { getAdjustedShortTermETA } from "mq/scheduling.ts";
|
||||
import { NetSchedulerError } from "@core/net/delegate.ts";
|
||||
import { sql } from "@core/db/dbNew.ts";
|
||||
|
||||
const snapshotTypeToTaskMap: { [key: string]: string } = {
|
||||
"milestone": "snapshotMilestoneVideo",
|
||||
@ -23,12 +22,12 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
|
||||
const type = job.data.type;
|
||||
const task = snapshotTypeToTaskMap[type] ?? "snapshotVideo";
|
||||
const retryInterval = type === "milestone" ? 5 * SECOND : 2 * MINUTE;
|
||||
await withDbConnection(async (client: Client) => {
|
||||
const exists = await snapshotScheduleExists(client, id);
|
||||
try {
|
||||
const exists = await snapshotScheduleExists(sql, id);
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
const status = await getBiliVideoStatus(client, aid);
|
||||
const status = await getBiliVideoStatus(sql, aid);
|
||||
if (status !== 0) {
|
||||
logger.warn(
|
||||
`Video ${aid} has status ${status} in the database. Abort snapshoting.`,
|
||||
@ -37,11 +36,11 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
await setSnapshotStatus(client, id, "processing");
|
||||
const stat = await insertVideoSnapshot(client, aid, task);
|
||||
await setSnapshotStatus(sql, id, "processing");
|
||||
const stat = await insertVideoSnapshot(sql, aid, task);
|
||||
if (typeof stat === "number") {
|
||||
await setBiliVideoStatus(client, aid, stat);
|
||||
await setSnapshotStatus(client, id, "bili_error");
|
||||
await setBiliVideoStatus(sql, aid, stat);
|
||||
await setSnapshotStatus(sql, id, "bili_error");
|
||||
logger.warn(
|
||||
`Bilibili return status ${status} when snapshoting for ${aid}.`,
|
||||
"mq",
|
||||
@ -49,9 +48,9 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
await setSnapshotStatus(client, id, "completed");
|
||||
await setSnapshotStatus(sql, id, "completed");
|
||||
if (type === "new") {
|
||||
const publihsedAt = await getSongsPublihsedAt(client, aid);
|
||||
const publihsedAt = await getSongsPublihsedAt(sql, aid);
|
||||
const timeSincePublished = stat.time - publihsedAt!;
|
||||
const viewsPerHour = stat.views / timeSincePublished * HOUR;
|
||||
if (timeSincePublished > 48 * HOUR) {
|
||||
@ -70,10 +69,10 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
|
||||
if (viewsPerHour > 1000) {
|
||||
intervalMins = 15;
|
||||
}
|
||||
await scheduleSnapshot(client, aid, type, Date.now() + intervalMins * MINUTE, true);
|
||||
await scheduleSnapshot(sql, aid, type, Date.now() + intervalMins * MINUTE, true);
|
||||
}
|
||||
if (type !== "milestone") return;
|
||||
const eta = await getAdjustedShortTermETA(client, aid);
|
||||
const eta = await getAdjustedShortTermETA(sql, aid);
|
||||
if (eta > 144) {
|
||||
const etaHoursString = eta.toFixed(2) + " hrs";
|
||||
logger.warn(
|
||||
@ -84,18 +83,19 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
|
||||
}
|
||||
const now = Date.now();
|
||||
const targetTime = now + eta * HOUR;
|
||||
await scheduleSnapshot(client, aid, type, targetTime);
|
||||
await setSnapshotStatus(client, id, "completed");
|
||||
await scheduleSnapshot(sql, aid, type, targetTime);
|
||||
await setSnapshotStatus(sql, id, "completed");
|
||||
return;
|
||||
}, async (e, client) => {
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof NetSchedulerError && e.code === "NO_PROXY_AVAILABLE") {
|
||||
logger.warn(
|
||||
`No available proxy for aid ${job.data.aid}.`,
|
||||
"mq",
|
||||
"fn:takeSnapshotForVideoWorker",
|
||||
);
|
||||
await setSnapshotStatus(client, id, "no_proxy");
|
||||
await scheduleSnapshot(client, aid, type, Date.now() + retryInterval);
|
||||
await setSnapshotStatus(sql, id, "no_proxy");
|
||||
await scheduleSnapshot(sql, aid, type, Date.now() + retryInterval);
|
||||
return;
|
||||
}
|
||||
else if (e instanceof NetSchedulerError && e.code === "ALICLOUD_PROXY_ERR") {
|
||||
@ -104,13 +104,14 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
|
||||
"mq",
|
||||
"fn:takeSnapshotForVideoWorker",
|
||||
);
|
||||
await setSnapshotStatus(client, id, "failed");
|
||||
await scheduleSnapshot(client, aid, type, Date.now() + retryInterval);
|
||||
await setSnapshotStatus(sql, id, "failed");
|
||||
await scheduleSnapshot(sql, aid, type, Date.now() + retryInterval);
|
||||
}
|
||||
logger.error(e as Error, "mq", "fn:takeSnapshotForVideoWorker");
|
||||
await setSnapshotStatus(client, id, "failed");
|
||||
}, async () => {
|
||||
await setSnapshotStatus(sql, id, "failed");
|
||||
}
|
||||
finally {
|
||||
await lockManager.releaseLock("dispatchRegularSnapshots");
|
||||
});
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Job } from "npm:bullmq@5.45.2";
|
||||
import { db } from "db/init.ts";
|
||||
import { Job } from "bullmq";
|
||||
import {
|
||||
bulkScheduleSnapshot,
|
||||
bulkSetSnapshotStatus,
|
||||
@ -7,22 +6,22 @@ import {
|
||||
snapshotScheduleExists,
|
||||
} from "db/snapshotSchedule.ts";
|
||||
import { bulkGetVideoStats } from "net/bulkGetVideoStats.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { NetSchedulerError } from "@core/net/delegate.ts";
|
||||
import { HOUR, MINUTE, SECOND } from "@std/datetime";
|
||||
import { HOUR, MINUTE, SECOND } from "@core/const/time.ts";
|
||||
import { getRegularSnapshotInterval } from "../task/regularSnapshotInterval.ts";
|
||||
import { SnapshotScheduleType } from "@core/db/schema";
|
||||
import { sql } from "@core/db/dbNew.ts";
|
||||
|
||||
export const takeBulkSnapshotForVideosWorker = async (job: Job) => {
|
||||
const schedules: SnapshotScheduleType[] = job.data.schedules;
|
||||
const ids = schedules.map((schedule) => Number(schedule.id));
|
||||
const aidsToFetch: number[] = [];
|
||||
const client = await db.connect();
|
||||
try {
|
||||
for (const schedule of schedules) {
|
||||
const aid = Number(schedule.aid);
|
||||
const id = Number(schedule.id);
|
||||
const exists = await snapshotScheduleExists(client, id);
|
||||
const exists = await snapshotScheduleExists(sql, id);
|
||||
if (!exists) {
|
||||
continue;
|
||||
}
|
||||
@ -30,8 +29,8 @@ export const takeBulkSnapshotForVideosWorker = async (job: Job) => {
|
||||
}
|
||||
const data = await bulkGetVideoStats(aidsToFetch);
|
||||
if (typeof data === "number") {
|
||||
await bulkSetSnapshotStatus(client, ids, "failed");
|
||||
await bulkScheduleSnapshot(client, aidsToFetch, "normal", Date.now() + 15 * SECOND);
|
||||
await bulkSetSnapshotStatus(sql, ids, "failed");
|
||||
await bulkScheduleSnapshot(sql, aidsToFetch, "normal", Date.now() + 15 * SECOND);
|
||||
return `GET_BILI_STATUS_${data}`;
|
||||
}
|
||||
for (const video of data) {
|
||||
@ -44,26 +43,31 @@ export const takeBulkSnapshotForVideosWorker = async (job: Job) => {
|
||||
const coins = stat.coin;
|
||||
const shares = stat.share;
|
||||
const favorites = stat.collect;
|
||||
const query: string = `
|
||||
await sql`
|
||||
INSERT INTO video_snapshot (aid, views, danmakus, replies, likes, coins, shares, favorites)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
`;
|
||||
await client.queryObject(
|
||||
query,
|
||||
[aid, views, danmakus, replies, likes, coins, shares, favorites],
|
||||
);
|
||||
VALUES (
|
||||
${aid},
|
||||
${views},
|
||||
${danmakus},
|
||||
${replies},
|
||||
${likes},
|
||||
${coins},
|
||||
${shares},
|
||||
${favorites}
|
||||
)
|
||||
`
|
||||
|
||||
logger.log(`Taken snapshot for video ${aid} in bulk.`, "net", "fn:takeBulkSnapshotForVideosWorker");
|
||||
}
|
||||
await bulkSetSnapshotStatus(client, ids, "completed");
|
||||
await bulkSetSnapshotStatus(sql, ids, "completed");
|
||||
|
||||
for (const schedule of schedules) {
|
||||
const aid = Number(schedule.aid);
|
||||
const type = schedule.type;
|
||||
if (type == "archive") continue;
|
||||
const interval = await getRegularSnapshotInterval(client, aid);
|
||||
const interval = await getRegularSnapshotInterval(sql, aid);
|
||||
logger.log(`Scheduled regular snapshot for aid ${aid} in ${interval} hours.`, "mq");
|
||||
await scheduleSnapshot(client, aid, "normal", Date.now() + interval * HOUR);
|
||||
await scheduleSnapshot(sql, aid, "normal", Date.now() + interval * HOUR);
|
||||
}
|
||||
return `DONE`;
|
||||
} catch (e) {
|
||||
@ -73,13 +77,11 @@ export const takeBulkSnapshotForVideosWorker = async (job: Job) => {
|
||||
"mq",
|
||||
"fn:takeBulkSnapshotForVideosWorker",
|
||||
);
|
||||
await bulkSetSnapshotStatus(client, ids, "no_proxy");
|
||||
await bulkScheduleSnapshot(client, aidsToFetch, "normal", Date.now() + 20 * MINUTE * Math.random());
|
||||
await bulkSetSnapshotStatus(sql, ids, "no_proxy");
|
||||
await bulkScheduleSnapshot(sql, aidsToFetch, "normal", Date.now() + 20 * MINUTE * Math.random());
|
||||
return;
|
||||
}
|
||||
logger.error(e as Error, "mq", "fn:takeBulkSnapshotForVideosWorker");
|
||||
await bulkSetSnapshotStatus(client, ids, "failed");
|
||||
} finally {
|
||||
client.release();
|
||||
await bulkSetSnapshotStatus(sql, ids, "failed");
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { Queue } from "bullmq";
|
||||
import { Queue, ConnectionOptions } from "bullmq";
|
||||
import { redis } from "@core/db/redis.ts";
|
||||
|
||||
export const LatestVideosQueue = new Queue("latestVideos");
|
||||
export const LatestVideosQueue = new Queue("latestVideos", {
|
||||
connection: redis as ConnectionOptions
|
||||
});
|
||||
|
||||
export const ClassifyVideoQueue = new Queue("classifyVideo");
|
||||
export const ClassifyVideoQueue = new Queue("classifyVideo", {
|
||||
connection: redis as ConnectionOptions
|
||||
});
|
||||
|
||||
export const SnapshotQueue = new Queue("snapshot");
|
||||
export const SnapshotQueue = new Queue("snapshot", {
|
||||
connection: redis as ConnectionOptions
|
||||
});
|
||||
|
@ -1,72 +1,75 @@
|
||||
import { HOUR, MINUTE, SECOND } from "$std/datetime/constants.ts";
|
||||
import { HOUR, MINUTE, SECOND } from "@core/const/time.ts";
|
||||
import { ClassifyVideoQueue, LatestVideosQueue, SnapshotQueue } from "mq/index.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { initSnapshotWindowCounts } from "db/snapshotSchedule.ts";
|
||||
import { db } from "db/init.ts";
|
||||
import { redis } from "@core/db/redis.ts";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export async function initMQ() {
|
||||
const client = await db.connect();
|
||||
try {
|
||||
await initSnapshotWindowCounts(client, redis);
|
||||
await initSnapshotWindowCounts(sql, redis);
|
||||
|
||||
await LatestVideosQueue.upsertJobScheduler("getLatestVideos", {
|
||||
every: 1 * MINUTE,
|
||||
immediately: true,
|
||||
immediately: true
|
||||
});
|
||||
|
||||
await ClassifyVideoQueue.upsertJobScheduler("classifyVideos", {
|
||||
every: 5 * MINUTE,
|
||||
immediately: true,
|
||||
immediately: true
|
||||
});
|
||||
|
||||
await LatestVideosQueue.upsertJobScheduler("collectSongs", {
|
||||
every: 3 * MINUTE,
|
||||
immediately: true,
|
||||
immediately: true
|
||||
});
|
||||
|
||||
await SnapshotQueue.upsertJobScheduler("snapshotTick", {
|
||||
await SnapshotQueue.upsertJobScheduler(
|
||||
"snapshotTick",
|
||||
{
|
||||
every: 1 * SECOND,
|
||||
immediately: true,
|
||||
}, {
|
||||
immediately: true
|
||||
},
|
||||
{
|
||||
opts: {
|
||||
removeOnComplete: 300,
|
||||
removeOnFail: 600,
|
||||
},
|
||||
});
|
||||
removeOnFail: 600
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await SnapshotQueue.upsertJobScheduler("bulkSnapshotTick", {
|
||||
await SnapshotQueue.upsertJobScheduler(
|
||||
"bulkSnapshotTick",
|
||||
{
|
||||
every: 15 * SECOND,
|
||||
immediately: true,
|
||||
}, {
|
||||
immediately: true
|
||||
},
|
||||
{
|
||||
opts: {
|
||||
removeOnComplete: 60,
|
||||
removeOnFail: 600,
|
||||
},
|
||||
});
|
||||
removeOnFail: 600
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await SnapshotQueue.upsertJobScheduler("dispatchMilestoneSnapshots", {
|
||||
every: 5 * MINUTE,
|
||||
immediately: true,
|
||||
immediately: true
|
||||
});
|
||||
|
||||
await SnapshotQueue.upsertJobScheduler("dispatchRegularSnapshots", {
|
||||
every: 30 * MINUTE,
|
||||
immediately: true,
|
||||
immediately: true
|
||||
});
|
||||
|
||||
await SnapshotQueue.upsertJobScheduler("dispatchArchiveSnapshots", {
|
||||
every: 6 * HOUR,
|
||||
immediately: true,
|
||||
immediately: true
|
||||
});
|
||||
|
||||
await SnapshotQueue.upsertJobScheduler("scheduleCleanup", {
|
||||
every: 2 * MINUTE,
|
||||
immediately: true,
|
||||
immediately: true
|
||||
});
|
||||
|
||||
logger.log("Message queue initialized.");
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { findClosestSnapshot, getLatestSnapshot, hasAtLeast2Snapshots } from "db/snapshotSchedule.ts";
|
||||
import { truncate } from "utils/truncate.ts";
|
||||
import { closetMilestone } from "./exec/snapshotTick.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { HOUR, MINUTE } from "@std/datetime";
|
||||
import { HOUR, MINUTE } from "@core/const/time.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
const log = (value: number, base: number = 10) => Math.log(value) / Math.log(base);
|
||||
|
||||
@ -26,11 +26,11 @@ const getFactor = (x: number) => {
|
||||
* @param aid - aid of the video
|
||||
* @returns ETA in hours
|
||||
*/
|
||||
export const getAdjustedShortTermETA = async (client: Client, aid: number) => {
|
||||
const latestSnapshot = await getLatestSnapshot(client, aid);
|
||||
export const getAdjustedShortTermETA = async (sql: Psql, aid: number) => {
|
||||
const latestSnapshot = await getLatestSnapshot(sql, aid);
|
||||
// Immediately dispatch a snapshot if there is no snapshot yet
|
||||
if (!latestSnapshot) return 0;
|
||||
const snapshotsEnough = await hasAtLeast2Snapshots(client, aid);
|
||||
const snapshotsEnough = await hasAtLeast2Snapshots(sql, aid);
|
||||
if (!snapshotsEnough) return 0;
|
||||
|
||||
const currentTimestamp = new Date().getTime();
|
||||
@ -40,7 +40,7 @@ export const getAdjustedShortTermETA = async (client: Client, aid: number) => {
|
||||
|
||||
for (const timeInterval of timeIntervals) {
|
||||
const date = new Date(currentTimestamp - timeInterval);
|
||||
const snapshot = await findClosestSnapshot(client, aid, date);
|
||||
const snapshot = await findClosestSnapshot(sql, aid, date);
|
||||
if (!snapshot) continue;
|
||||
const hoursDiff = (latestSnapshot.created_at - snapshot.created_at) / HOUR;
|
||||
const viewsDiff = latestSnapshot.views - snapshot.views;
|
||||
|
@ -1,31 +1,29 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
import { aidExistsInSongs, getNotCollectedSongs } from "db/songs.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { scheduleSnapshot } from "db/snapshotSchedule.ts";
|
||||
import { MINUTE } from "@std/datetime";
|
||||
import { MINUTE } from "@core/const/time.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
export async function collectSongs(client: Client) {
|
||||
const aids = await getNotCollectedSongs(client);
|
||||
export async function collectSongs() {
|
||||
const aids = await getNotCollectedSongs(sql);
|
||||
for (const aid of aids) {
|
||||
const exists = await aidExistsInSongs(client, aid);
|
||||
const exists = await aidExistsInSongs(sql, aid);
|
||||
if (exists) continue;
|
||||
await insertIntoSongs(client, aid);
|
||||
await scheduleSnapshot(client, aid, "new", Date.now() + 10 * MINUTE, true);
|
||||
await insertIntoSongs(sql, aid);
|
||||
await scheduleSnapshot(sql, aid, "new", Date.now() + 10 * MINUTE, true);
|
||||
logger.log(`Video ${aid} was added into the songs table.`, "mq", "fn:collectSongs");
|
||||
}
|
||||
}
|
||||
|
||||
export async function insertIntoSongs(client: Client, aid: number) {
|
||||
await client.queryObject(
|
||||
`
|
||||
export async function insertIntoSongs(sql: Psql, aid: number) {
|
||||
await sql`
|
||||
INSERT INTO songs (aid, published_at, duration)
|
||||
VALUES (
|
||||
$1,
|
||||
(SELECT published_at FROM bilibili_metadata WHERE aid = $1),
|
||||
(SELECT duration FROM bilibili_metadata WHERE aid = $1)
|
||||
(SELECT published_at FROM bilibili_metadata WHERE aid = ${aid}),
|
||||
(SELECT duration FROM bilibili_metadata WHERE aid = ${aid})
|
||||
)
|
||||
ON CONFLICT DO NOTHING
|
||||
`,
|
||||
[aid],
|
||||
);
|
||||
`
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export async function getTimeoutSchedulesCount(client: Client) {
|
||||
const query: string = `
|
||||
export async function getTimeoutSchedulesCount() {
|
||||
const rows = await sql<{ count: number }[]>`
|
||||
SELECT COUNT(id)
|
||||
FROM snapshot_schedule
|
||||
WHERE status IN ('pending', 'processing')
|
||||
AND started_at < NOW() - INTERVAL '30 minutes'
|
||||
`;
|
||||
|
||||
const { rows } = await client.queryObject<{ count: number }>(query);
|
||||
return rows[0].count;
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { getVideoDetails } from "net/getVideoDetails.ts";
|
||||
import { formatTimestampToPsql } from "utils/formatTimestampToPostgre.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { ClassifyVideoQueue } from "mq/index.ts";
|
||||
import { userExistsInBiliUsers, videoExistsInAllData } from "../../db/bilibili_metadata.ts";
|
||||
import { HOUR, SECOND } from "@std/datetime";
|
||||
import { HOUR, SECOND } from "@core/const/time.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
export async function insertVideoInfo(client: Client, aid: number) {
|
||||
const videoExists = await videoExistsInAllData(client, aid);
|
||||
export async function insertVideoInfo(sql: Psql, aid: number) {
|
||||
const videoExists = await videoExistsInAllData(sql, aid);
|
||||
if (videoExists) {
|
||||
return;
|
||||
}
|
||||
@ -25,34 +25,37 @@ export async function insertVideoInfo(client: Client, aid: number) {
|
||||
const published_at = formatTimestampToPsql(data.View.pubdate * SECOND + 8 * HOUR);
|
||||
const duration = data.View.duration;
|
||||
const cover = data.View.pic;
|
||||
await client.queryObject(
|
||||
`INSERT INTO bilibili_metadata (aid, bvid, description, uid, tags, title, published_at, duration, cover_url)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[aid, bvid, desc, uid, tags, title, published_at, duration, cover],
|
||||
);
|
||||
const userExists = await userExistsInBiliUsers(client, aid);
|
||||
await sql`
|
||||
INSERT INTO bilibili_metadata (aid, bvid, description, uid, tags, title, published_at, duration, cover_url)
|
||||
VALUES (${aid}, ${bvid}, ${desc}, ${uid}, ${tags}, ${title}, ${published_at}, ${duration}, ${cover})
|
||||
`;
|
||||
const userExists = await userExistsInBiliUsers(sql, aid);
|
||||
if (!userExists) {
|
||||
await client.queryObject(
|
||||
`INSERT INTO bilibili_user (uid, username, "desc", fans) VALUES ($1, $2, $3, $4)`,
|
||||
[uid, data.View.owner.name, data.Card.card.sign, data.Card.follower],
|
||||
);
|
||||
await sql`
|
||||
INSERT INTO bilibili_user (uid, username, "desc", fans)
|
||||
VALUES (${uid}, ${data.View.owner.name}, ${data.Card.card.sign}, ${data.Card.follower})
|
||||
`;
|
||||
} else {
|
||||
await client.queryObject(
|
||||
`UPDATE bilibili_user SET fans = $1 WHERE uid = $2`,
|
||||
[data.Card.follower, uid],
|
||||
);
|
||||
await sql`
|
||||
UPDATE bilibili_user SET fans = ${data.Card.follower} WHERE uid = ${uid}
|
||||
`;
|
||||
}
|
||||
|
||||
const stat = data.View.stat;
|
||||
|
||||
const query: string = `
|
||||
await sql`
|
||||
INSERT INTO video_snapshot (aid, views, danmakus, replies, likes, coins, shares, favorites)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
`;
|
||||
await client.queryObject(
|
||||
query,
|
||||
[aid, stat.view, stat.danmaku, stat.reply, stat.like, stat.coin, stat.share, stat.favorite],
|
||||
);
|
||||
VALUES (
|
||||
${aid},
|
||||
${stat.view},
|
||||
${stat.danmaku},
|
||||
${stat.reply},
|
||||
${stat.like},
|
||||
${stat.coin},
|
||||
${stat.share},
|
||||
${stat.favorite}
|
||||
)
|
||||
`
|
||||
|
||||
logger.log(`Inserted video metadata for aid: ${aid}`, "mq");
|
||||
await ClassifyVideoQueue.add("classifyVideo", { aid });
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { getVideoInfo } from "net/getVideoInfo.ts";
|
||||
import logger from "log/logger.ts";
|
||||
import { getVideoInfo } from "@core/net/getVideoInfo.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
export interface SnapshotNumber {
|
||||
time: number;
|
||||
@ -25,7 +25,7 @@ export interface SnapshotNumber {
|
||||
* - The alicloud-fc threw an error: with error code `ALICLOUD_FC_ERROR`
|
||||
*/
|
||||
export async function insertVideoSnapshot(
|
||||
client: Client,
|
||||
sql: Psql,
|
||||
aid: number,
|
||||
task: string,
|
||||
): Promise<number | SnapshotNumber> {
|
||||
@ -42,14 +42,10 @@ export async function insertVideoSnapshot(
|
||||
const shares = data.stat.share;
|
||||
const favorites = data.stat.favorite;
|
||||
|
||||
const query: string = `
|
||||
await sql`
|
||||
INSERT INTO video_snapshot (aid, views, danmakus, replies, likes, coins, shares, favorites)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
`;
|
||||
await client.queryObject(
|
||||
query,
|
||||
[aid, views, danmakus, replies, likes, coins, shares, favorites],
|
||||
);
|
||||
VALUES (${aid}, ${views}, ${danmakus}, ${replies}, ${likes}, ${coins}, ${shares}, ${favorites})
|
||||
`
|
||||
|
||||
logger.log(`Taken snapshot for video ${aid}.`, "net", "fn:insertVideoSnapshot");
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { getLatestVideoAids } from "net/getLatestVideoAids.ts";
|
||||
import { videoExistsInAllData } from "../../db/bilibili_metadata.ts";
|
||||
import { videoExistsInAllData } from "db/bilibili_metadata.ts";
|
||||
import { sleep } from "utils/sleep.ts";
|
||||
import { SECOND } from "@std/datetime";
|
||||
import logger from "log/logger.ts";
|
||||
import { SECOND } from "@core/const/time.ts";
|
||||
import logger from "@core/log/logger.ts";
|
||||
import { LatestVideosQueue } from "mq/index.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
export async function queueLatestVideos(
|
||||
client: Client,
|
||||
sql: Psql,
|
||||
): Promise<number | null> {
|
||||
let page = 1;
|
||||
let i = 0;
|
||||
@ -22,7 +22,7 @@ export async function queueLatestVideos(
|
||||
let allExists = true;
|
||||
let delay = 0;
|
||||
for (const aid of aids) {
|
||||
const videoExists = await videoExistsInAllData(client, aid);
|
||||
const videoExists = await videoExistsInAllData(sql, aid);
|
||||
if (videoExists) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { findClosestSnapshot, findSnapshotBefore, getLatestSnapshot } from "db/snapshotSchedule.ts";
|
||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import { HOUR } from "@std/datetime";
|
||||
import { HOUR } from "@core/const/time.ts";
|
||||
import type { Psql } from "global.d.ts";
|
||||
|
||||
export const getRegularSnapshotInterval = async (client: Client, aid: number) => {
|
||||
export const getRegularSnapshotInterval = async (sql: Psql, aid: number) => {
|
||||
const now = Date.now();
|
||||
const date = new Date(now - 24 * HOUR);
|
||||
let oldSnapshot = await findSnapshotBefore(client, aid, date);
|
||||
if (!oldSnapshot) oldSnapshot = await findClosestSnapshot(client, aid, date);
|
||||
const latestSnapshot = await getLatestSnapshot(client, aid);
|
||||
let oldSnapshot = await findSnapshotBefore(sql, aid, date);
|
||||
if (!oldSnapshot) oldSnapshot = await findClosestSnapshot(sql, aid, date);
|
||||
const latestSnapshot = await getLatestSnapshot(sql, aid);
|
||||
if (!oldSnapshot || !latestSnapshot) return 0;
|
||||
if (oldSnapshot.created_at === latestSnapshot.created_at) return 0;
|
||||
const hoursDiff = (latestSnapshot.created_at - oldSnapshot.created_at) / HOUR;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user