pixivflow 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{README_EN.md → README.en.md} +8 -6
- package/README.md +8 -6
- package/config/examples/README.md +218 -0
- package/config/examples/multi-tag-or-limit.zh.json +49 -0
- package/config/examples/specific-download.example.json +173 -0
- package/config/examples/standalone.config.example.json +167 -0
- package/config/examples/standalone.config.novel-chinese.json +124 -0
- package/config/examples/standalone.config.ranking.json +135 -0
- package/config/examples/standalone.config.simple.json +52 -0
- package/config/examples/yesterday-popular-novel.zh.json +48 -0
- package/config/examples/yesterday-ranking-illustration.json +43 -0
- package/dist/commands/DiagnoseEgressCommand.d.ts +98 -0
- package/dist/commands/DiagnoseEgressCommand.d.ts.map +1 -0
- package/dist/commands/DiagnoseEgressCommand.js +327 -0
- package/dist/commands/DiagnoseEgressCommand.js.map +1 -0
- package/dist/commands/MaintainCommand.d.ts.map +1 -1
- package/dist/commands/MaintainCommand.js +10 -15
- package/dist/commands/MaintainCommand.js.map +1 -1
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +4 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/delivery/LegacyOutboxMigration.js +1 -1
- package/dist/delivery/LegacyOutboxMigration.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/puppeteer-login-adapter/auth-code.d.ts +1 -1
- package/dist/puppeteer-login-adapter/auth-code.d.ts.map +1 -1
- package/dist/puppeteer-login-adapter/browser-launch.d.ts +21 -0
- package/dist/puppeteer-login-adapter/browser-launch.d.ts.map +1 -0
- package/dist/puppeteer-login-adapter/browser-launch.js +46 -0
- package/dist/puppeteer-login-adapter/browser-launch.js.map +1 -0
- package/dist/puppeteer-login-adapter/environment.js +2 -2
- package/dist/puppeteer-login-adapter/environment.js.map +1 -1
- package/dist/puppeteer-login-adapter/login-headless.d.ts.map +1 -1
- package/dist/puppeteer-login-adapter/login-headless.js +2 -5
- package/dist/puppeteer-login-adapter/login-headless.js.map +1 -1
- package/dist/puppeteer-login-adapter/login-interactive.d.ts.map +1 -1
- package/dist/puppeteer-login-adapter/login-interactive.js +2 -5
- package/dist/puppeteer-login-adapter/login-interactive.js.map +1 -1
- package/dist/storage/Database.d.ts +2 -0
- package/dist/storage/Database.d.ts.map +1 -1
- package/dist/storage/Database.js +7 -6
- package/dist/storage/Database.js.map +1 -1
- package/dist/storage/DatabaseMigration.d.ts +2 -2
- package/dist/storage/DatabaseMigration.d.ts.map +1 -1
- package/dist/storage/DatabaseMigration.js.map +1 -1
- package/dist/storage/drivers/NodeSqliteDriver.d.ts +14 -0
- package/dist/storage/drivers/NodeSqliteDriver.d.ts.map +1 -0
- package/dist/storage/drivers/NodeSqliteDriver.js +123 -0
- package/dist/storage/drivers/NodeSqliteDriver.js.map +1 -0
- package/dist/storage/drivers/SqliteDriver.d.ts +55 -0
- package/dist/storage/drivers/SqliteDriver.d.ts.map +1 -0
- package/dist/storage/drivers/SqliteDriver.js +21 -0
- package/dist/storage/drivers/SqliteDriver.js.map +1 -0
- package/dist/storage/repositories/BaseRepository.d.ts +4 -4
- package/dist/storage/repositories/BaseRepository.d.ts.map +1 -1
- package/dist/storage/repositories/BaseRepository.js.map +1 -1
- package/dist/storage/repositories/DownloadRepository.d.ts +2 -1
- package/dist/storage/repositories/DownloadRepository.d.ts.map +1 -1
- package/dist/storage/repositories/DownloadRepository.js.map +1 -1
- package/dist/storage/repositories/RateLimitStateRepository.d.ts +1 -1
- package/dist/storage/repositories/RateLimitStateRepository.js +1 -1
- package/dist/version.js +1 -1
- package/dist/webui/package.json +1 -1
- package/node_modules/@redtidev/pixiv-client/README.en.md +105 -0
- package/node_modules/@redtidev/pixiv-client/README.md +67 -73
- package/node_modules/@redtidev/pixiv-client/dist/rate-limit/RateLimitGate.d.ts +1 -1
- package/node_modules/@redtidev/pixiv-client/dist/rate-limit/RateLimitGate.js +1 -1
- package/node_modules/@redtidev/pixiv-client/src/rate-limit/RateLimitGate.ts +1 -1
- package/package.json +7 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSqliteDriver.js","sourceRoot":"","sources":["../../../src/storage/drivers/NodeSqliteDriver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SQLite driver contract for the storage layer.
|
|
3
|
+
*
|
|
4
|
+
* Everything under `src/storage/` talks to SQLite through this interface and nothing
|
|
5
|
+
* else. The binding is a deployment concern (it decides whether `npm install -g pixivflow`
|
|
6
|
+
* compiles native code), so it lives behind one seam: swapping `node:sqlite` for another
|
|
7
|
+
* binding stays a change to one file instead of a sweep over every repository.
|
|
8
|
+
*
|
|
9
|
+
* The shapes intentionally mirror the synchronous better-sqlite3 API the repositories were
|
|
10
|
+
* written against, so call sites keep working unchanged:
|
|
11
|
+
* - `prepare()` returns a reusable statement;
|
|
12
|
+
* - a statement accepts an optional leading named-parameter object followed by
|
|
13
|
+
* positional parameters (`stmt.run({ id }, 'extra')`);
|
|
14
|
+
* - `.get()` returns `undefined` when no row matches.
|
|
15
|
+
*
|
|
16
|
+
* Bindings differ in their defaults, so implementations are responsible for normalising
|
|
17
|
+
* them (see `NodeSqliteDriver` for the lock-wait and foreign-key details).
|
|
18
|
+
*/
|
|
19
|
+
export interface SqliteRunResult {
|
|
20
|
+
/** Number of rows changed. Normalised to `number` so `changes === 0` keeps working. */
|
|
21
|
+
changes: number;
|
|
22
|
+
/** Rowid of the last inserted row. Normalised to `number` for the same reason. */
|
|
23
|
+
lastInsertRowid: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SqliteStatement {
|
|
26
|
+
run(...params: unknown[]): SqliteRunResult;
|
|
27
|
+
get(...params: unknown[]): unknown;
|
|
28
|
+
all(...params: unknown[]): unknown[];
|
|
29
|
+
}
|
|
30
|
+
export interface SqliteDriver {
|
|
31
|
+
prepare(sql: string): SqliteStatement;
|
|
32
|
+
/** Execute one or more statements, discarding any result rows. */
|
|
33
|
+
exec(sql: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Apply or read a PRAGMA.
|
|
36
|
+
*
|
|
37
|
+
* Accepts either the pragma body (`journal_mode = WAL`) or a full statement
|
|
38
|
+
* (`PRAGMA integrity_check;`), so both calling styles survive the binding swap.
|
|
39
|
+
*
|
|
40
|
+
* Follows better-sqlite3's shape: a single-row/single-column result collapses to that
|
|
41
|
+
* scalar, a multi-row result (e.g. `table_info`) comes back as an array of row objects,
|
|
42
|
+
* and a statement that yields nothing returns `undefined`.
|
|
43
|
+
*/
|
|
44
|
+
pragma(sql: string): unknown;
|
|
45
|
+
/**
|
|
46
|
+
* Wrap `fn` in a transaction and return a function that runs it.
|
|
47
|
+
*
|
|
48
|
+
* Nested calls join the outer transaction rather than starting a second one — the
|
|
49
|
+
* storage layer nests legitimately (a service-level transaction calling repository
|
|
50
|
+
* methods that are themselves transactional).
|
|
51
|
+
*/
|
|
52
|
+
transaction<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R;
|
|
53
|
+
close(): void;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=SqliteDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SqliteDriver.d.ts","sourceRoot":"","sources":["../../../src/storage/drivers/SqliteDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,eAAe;IAC9B,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC;IAC3C,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACnC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IAEtC,kEAAkE;IAClE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAE7B;;;;;;OAMG;IACH,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IAE9E,KAAK,IAAI,IAAI,CAAC;CACf"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal SQLite driver contract for the storage layer.
|
|
4
|
+
*
|
|
5
|
+
* Everything under `src/storage/` talks to SQLite through this interface and nothing
|
|
6
|
+
* else. The binding is a deployment concern (it decides whether `npm install -g pixivflow`
|
|
7
|
+
* compiles native code), so it lives behind one seam: swapping `node:sqlite` for another
|
|
8
|
+
* binding stays a change to one file instead of a sweep over every repository.
|
|
9
|
+
*
|
|
10
|
+
* The shapes intentionally mirror the synchronous better-sqlite3 API the repositories were
|
|
11
|
+
* written against, so call sites keep working unchanged:
|
|
12
|
+
* - `prepare()` returns a reusable statement;
|
|
13
|
+
* - a statement accepts an optional leading named-parameter object followed by
|
|
14
|
+
* positional parameters (`stmt.run({ id }, 'extra')`);
|
|
15
|
+
* - `.get()` returns `undefined` when no row matches.
|
|
16
|
+
*
|
|
17
|
+
* Bindings differ in their defaults, so implementations are responsible for normalising
|
|
18
|
+
* them (see `NodeSqliteDriver` for the lock-wait and foreign-key details).
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
//# sourceMappingURL=SqliteDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SqliteDriver.js","sourceRoot":"","sources":["../../../src/storage/drivers/SqliteDriver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { SqliteDriver } from '../drivers/SqliteDriver';
|
|
2
2
|
/**
|
|
3
3
|
* Base repository class that provides access to the database instance
|
|
4
4
|
* All repositories should extend this class
|
|
5
5
|
*/
|
|
6
6
|
export declare abstract class BaseRepository {
|
|
7
|
-
protected readonly db:
|
|
8
|
-
constructor(db:
|
|
7
|
+
protected readonly db: SqliteDriver;
|
|
8
|
+
constructor(db: SqliteDriver);
|
|
9
9
|
/**
|
|
10
10
|
* Get the database instance
|
|
11
11
|
*/
|
|
12
|
-
protected getDatabase():
|
|
12
|
+
protected getDatabase(): SqliteDriver;
|
|
13
13
|
}
|
|
14
14
|
//# sourceMappingURL=BaseRepository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseRepository.d.ts","sourceRoot":"","sources":["../../../src/storage/repositories/BaseRepository.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"BaseRepository.d.ts","sourceRoot":"","sources":["../../../src/storage/repositories/BaseRepository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;GAGG;AACH,8BAAsB,cAAc;IACtB,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,YAAY;gBAAhB,EAAE,EAAE,YAAY;IAE/C;;OAEG;IACH,SAAS,CAAC,WAAW,IAAI,YAAY;CAGtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseRepository.js","sourceRoot":"","sources":["../../../src/storage/repositories/BaseRepository.ts"],"names":[],"mappings":";;;AAEA;;;GAGG;AACH,MAAsB,cAAc;IACH;IAA/B,YAA+B,
|
|
1
|
+
{"version":3,"file":"BaseRepository.js","sourceRoot":"","sources":["../../../src/storage/repositories/BaseRepository.ts"],"names":[],"mappings":";;;AAEA;;;GAGG;AACH,MAAsB,cAAc;IACH;IAA/B,YAA+B,EAAgB;QAAhB,OAAE,GAAF,EAAE,CAAc;IAAG,CAAC;IAEnD;;OAEG;IACO,WAAW;QACnB,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;CACF;AATD,wCASC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseRepository } from './BaseRepository';
|
|
2
2
|
import { DownloadRecordInput } from '../Database';
|
|
3
|
+
import type { SqliteDriver } from '../drivers/SqliteDriver';
|
|
3
4
|
/**
|
|
4
5
|
* Repository for managing download records
|
|
5
6
|
* Facade pattern that combines query, write, and stats repositories
|
|
@@ -8,7 +9,7 @@ export declare class DownloadRepository extends BaseRepository {
|
|
|
8
9
|
private readonly queryRepo;
|
|
9
10
|
private readonly writeRepo;
|
|
10
11
|
private readonly statsRepo;
|
|
11
|
-
constructor(db:
|
|
12
|
+
constructor(db: SqliteDriver);
|
|
12
13
|
hasDownloaded(pixivId: string, type: 'illustration' | 'novel'): boolean;
|
|
13
14
|
isDownloaded(pixivId: string, type: 'illustration' | 'novel', filePath: string): boolean;
|
|
14
15
|
getDownloadedIds(pixivIds: string[], type: 'illustration' | 'novel'): Set<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DownloadRepository.d.ts","sourceRoot":"","sources":["../../../src/storage/repositories/DownloadRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"DownloadRepository.d.ts","sourceRoot":"","sources":["../../../src/storage/repositories/DownloadRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAIlD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;gBAExC,EAAE,EAAE,YAAY;IAQrB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,GAAG,OAAO;IAIvE,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIxF,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;IAIjF,kBAAkB,CAAC,OAAO,EAAE;QACjC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;QACzD,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;KAC5B;;;;;;;;;;;;;;;;;IAIM,wBAAwB,CAC7B,SAAS,EAAE,MAAM,EAAE,EACnB,IAAI,EAAE,cAAc,GAAG,OAAO,GAC7B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAIf,kBAAkB,CACvB,KAAK,GAAE,MAAW,EAClB,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO;;;;;;;;;;IAK1B,uBAAuB,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO;;;;;;;;;;IAK1B,oBAAoB,CAAC,IAAI,EAAE,MAAM;;;;;;;;;;;IAKjC,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAIjD,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAIjD,cAAc,CACnB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,cAAc,GAAG,OAAO,EAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,MAAM;IAIF,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,GAAG,MAAM;IAKhF,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO;;;;;IAI9D,gBAAgB;;;;;;IAIhB,WAAW,CAAC,KAAK,GAAE,MAAW;;;;IAI9B,cAAc,CAAC,KAAK,GAAE,MAAW;;;;CAGzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DownloadRepository.js","sourceRoot":"","sources":["../../../src/storage/repositories/DownloadRepository.ts"],"names":[],"mappings":";;;AAAA,qDAAkD;AAElD,uEAAoE;AACpE,uEAAoE;AACpE,uEAAoE;
|
|
1
|
+
{"version":3,"file":"DownloadRepository.js","sourceRoot":"","sources":["../../../src/storage/repositories/DownloadRepository.ts"],"names":[],"mappings":";;;AAAA,qDAAkD;AAElD,uEAAoE;AACpE,uEAAoE;AACpE,uEAAoE;AAGpE;;;GAGG;AACH,MAAa,kBAAmB,SAAQ,+BAAc;IACnC,SAAS,CAA0B;IACnC,SAAS,CAA0B;IACnC,SAAS,CAA0B;IAEpD,YAAY,EAAgB;QAC1B,KAAK,CAAC,EAAE,CAAC,CAAC;QACV,IAAI,CAAC,SAAS,GAAG,IAAI,iDAAuB,CAAC,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,SAAS,GAAG,IAAI,iDAAuB,CAAC,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,SAAS,GAAG,IAAI,iDAAuB,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,gBAAgB;IACT,aAAa,CAAC,OAAe,EAAE,IAA8B;QAClE,OAAO,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAEM,YAAY,CAAC,OAAe,EAAE,IAA8B,EAAE,QAAgB;QACnF,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9D,CAAC;IAEM,gBAAgB,CAAC,QAAkB,EAAE,IAA8B;QACxE,OAAO,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAEM,kBAAkB,CAAC,OAUzB;QACC,OAAO,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;IAEM,wBAAwB,CAC7B,SAAmB,EACnB,IAA8B;QAE9B,OAAO,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAClE,CAAC;IAEM,kBAAkB,CACvB,QAAgB,EAAE,EAClB,IAA+B;QAE/B,OAAO,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;IAEM,uBAAuB,CAC5B,SAAiB,EACjB,OAAgB,EAChB,IAA+B;QAE/B,OAAO,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1E,CAAC;IAEM,oBAAoB,CAAC,IAAY;QACtC,OAAO,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,gBAAgB;IACT,cAAc,CAAC,MAA2B;QAC/C,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAEM,cAAc,CAAC,MAA2B;QAC/C,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAEM,cAAc,CACnB,OAAe,EACf,IAA8B,EAC9B,OAAe,EACf,OAAe;QAEf,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC;IAEM,uBAAuB,CAAC,OAAe,EAAE,IAA8B;QAC5E,OAAO,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC;IAED,gBAAgB;IACT,gBAAgB,CAAC,GAAY,EAAE,IAA+B;QACnE,OAAO,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;IAEM,gBAAgB;QACrB,OAAO,IAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC;IAC3C,CAAC;IAEM,WAAW,CAAC,QAAgB,EAAE;QACnC,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;IAEM,cAAc,CAAC,QAAgB,EAAE;QACtC,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;CACF;AAvGD,gDAuGC"}
|
|
@@ -2,7 +2,7 @@ import type { RateLimitState, RateLimitStateStore } from '@redtidev/pixiv-client
|
|
|
2
2
|
import { BaseRepository } from './BaseRepository';
|
|
3
3
|
/**
|
|
4
4
|
* Host-side SQLite adapter for the kit's RateLimitStateStore port.
|
|
5
|
-
* Sync
|
|
5
|
+
* Sync SQLite calls satisfy the async interface trivially.
|
|
6
6
|
*/
|
|
7
7
|
export declare class SQLiteRateLimitStateStore extends BaseRepository implements RateLimitStateStore {
|
|
8
8
|
load(scope: string): RateLimitState | null;
|
|
@@ -4,7 +4,7 @@ exports.SQLiteRateLimitStateStore = void 0;
|
|
|
4
4
|
const BaseRepository_1 = require("./BaseRepository");
|
|
5
5
|
/**
|
|
6
6
|
* Host-side SQLite adapter for the kit's RateLimitStateStore port.
|
|
7
|
-
* Sync
|
|
7
|
+
* Sync SQLite calls satisfy the async interface trivially.
|
|
8
8
|
*/
|
|
9
9
|
class SQLiteRateLimitStateStore extends BaseRepository_1.BaseRepository {
|
|
10
10
|
load(scope) {
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BUILD = void 0;
|
|
4
4
|
// GENERATED by scripts/write-version.js — do not edit manually.
|
|
5
|
-
exports.BUILD = { version: '2.
|
|
5
|
+
exports.BUILD = { version: '2.18.0', commit: '2ce5c9705daf' };
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/dist/webui/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @redtidev/pixiv-client
|
|
2
|
+
|
|
3
|
+
**English** | [中文](./README.md)
|
|
4
|
+
|
|
5
|
+
Independent, reusable **Pixiv App API client kit** for TypeScript/Node.js.
|
|
6
|
+
|
|
7
|
+
It knows Pixiv. It does **not** know your application: no config schema, no
|
|
8
|
+
database, no browser login, no scheduler. It currently lives as an npm
|
|
9
|
+
workspace package inside the PixivFlow monorepo and is intentionally **not
|
|
10
|
+
yet published** (see "When to split out" below).
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createPixivClient, StaticTokenProvider } from '@redtidev/pixiv-client';
|
|
14
|
+
|
|
15
|
+
const pixiv = createPixivClient({ auth: new StaticTokenProvider(process.env.PIXIV_ACCESS_TOKEN!) });
|
|
16
|
+
|
|
17
|
+
const illust = await pixiv.illustrations.get(123_456);
|
|
18
|
+
const page = await pixiv.illustrations.searchPage({ word: '風景', limit: 30 });
|
|
19
|
+
const bytes = await pixiv.media.fetch(illust.meta_single_page!.original_image_url!);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Auth port, not login implementation** — inject anything implementing
|
|
25
|
+
`AccessTokenProvider` (optionally `RefreshableAccessTokenProvider`). Browser
|
|
26
|
+
OAuth/PKCE, puppeteer and python helpers stay in the host application.
|
|
27
|
+
- **One transport** for every call: base URL, App headers, Bearer auth,
|
|
28
|
+
timeout (`AbortController`), HTTP(S) proxy (undici) and SOCKS proxy
|
|
29
|
+
(axios + socks-proxy-agent), response parsing, typed errors.
|
|
30
|
+
- **Typed errors** — `PixivNotFoundError`, `PixivRateLimitError`,
|
|
31
|
+
`PixivAuthenticationError`, `PixivServerError`, `PixivNetworkError`,
|
|
32
|
+
`PixivTimeoutError`, `PixivCircuitOpenError`, ... classify with `instanceof`,
|
|
33
|
+
never with `message.includes('429')`.
|
|
34
|
+
- **Single 429 gate** — one global rate limiter paces all requests and applies
|
|
35
|
+
one shared cooldown. No nested retries, no request storms.
|
|
36
|
+
- **Slot-reservation pacing** — concurrent callers are serialized to
|
|
37
|
+
t, t+interval, t+2·interval ... (the old coordinator woke them in a burst).
|
|
38
|
+
- **Conservative defaults**: 1000 ms minimum interval, jitter 25 %, first 429
|
|
39
|
+
cooldown 60 s, exponential 60→120→240→480 s capped at 15 min, Retry-After
|
|
40
|
+
always honored as a floor.
|
|
41
|
+
- **Penalty decay** — one success never clears the penalty; it decays after a
|
|
42
|
+
configurable run (default 20) of consecutive successes.
|
|
43
|
+
- **Circuit breaker** — repeated 429s OPEN the gate; requests fast-fail with
|
|
44
|
+
`PixivCircuitOpenError`; after the cooldown a single half-open probe tests
|
|
45
|
+
recovery.
|
|
46
|
+
- **Persistent state port** — inject a `RateLimitStateStore` (e.g. an SQLite
|
|
47
|
+
adapter) so restarts/deploys remember an active cooldown. Default is
|
|
48
|
+
in-memory.
|
|
49
|
+
- **In-flight coalescing** — 10 concurrent `get(123)` calls make 1 HTTP
|
|
50
|
+
request.
|
|
51
|
+
- **Pagination without auto-crawl** — `searchPage`/`rankingPage` plus the
|
|
52
|
+
optional `paginate()` helper; always bounded by `limit`/`maxPages` and
|
|
53
|
+
`AbortSignal`-aware.
|
|
54
|
+
- **Structured events** via `onEvent` (`request_start`, `request_retry`,
|
|
55
|
+
`rate_limited`, `circuit_opened`, `auth_refresh`, ...). No credentials are
|
|
56
|
+
ever logged or emitted.
|
|
57
|
+
|
|
58
|
+
## Retry ownership
|
|
59
|
+
|
|
60
|
+
| Failure | Owner |
|
|
61
|
+
| ------------------------------- | -------------------------------------------- |
|
|
62
|
+
| network reset / timeout / 5xx | transport retries (default 2), linear backoff|
|
|
63
|
+
| 429 while circuit CLOSED | transport waits the shared gate cooldown |
|
|
64
|
+
| 429 after circuit OPEN threshold| fails fast; durable retry belongs to the host (scheduler/outbox) |
|
|
65
|
+
| 401 | one `refreshAccessToken()` + retry, then error|
|
|
66
|
+
| 400 / 403 / 404 | never retried |
|
|
67
|
+
|
|
68
|
+
The kit never rotates proxies or IPs on 429 and never alternates between the
|
|
69
|
+
App and Web API to dodge rate limits. Endpoint-capability fallback (the novel
|
|
70
|
+
text v2→v1/ajax chain) is allowed; anti-rate-limit rotation is not.
|
|
71
|
+
|
|
72
|
+
## API surface
|
|
73
|
+
|
|
74
|
+
- `createPixivClient(options)` / `new PixivClient(options)`
|
|
75
|
+
- `pixiv.illustrations` — `get/detail`, `detailWithTags`, `searchPage`,
|
|
76
|
+
`search`, `rankingPage`, `ranking`, `userWorksPage`, `listByUser`,
|
|
77
|
+
`ugoiraMetadata`
|
|
78
|
+
- `pixiv.novels` — `get/detail`, `detailCompatible`, `detailWithTags`,
|
|
79
|
+
`searchPage`, `search`, `rankingPage`, `ranking`, `userWorksPage`,
|
|
80
|
+
`listByUser`, `listSeries`, `text`
|
|
81
|
+
- `pixiv.tags.autocomplete(word)`
|
|
82
|
+
- `pixiv.users.user(userId)` (Pixiv removed `/v1/user/profile`, so there is no "current user" endpoint)
|
|
83
|
+
- `pixiv.media.fetch(url)` → `ArrayBuffer`
|
|
84
|
+
- `pixiv.getRateLimitStatus()` — `{ circuitState, cooldownRemainingMs, penaltyLevel, last429At, nextAllowedInMs }`
|
|
85
|
+
|
|
86
|
+
The package has zero knowledge of host types (`TargetConfig`, schedulers,
|
|
87
|
+
SQLite, delivery, …). Mapping a host query into kit options is the host's
|
|
88
|
+
job, e.g. `mapTargetToPixivQuery(target): IllustSearchOptions`.
|
|
89
|
+
|
|
90
|
+
## When to split out into its own repo
|
|
91
|
+
|
|
92
|
+
Do **not** split yet. Split only once both hold:
|
|
93
|
+
|
|
94
|
+
1. A second real consumer exists (another bot, an Electron client, a
|
|
95
|
+
standalone downloader …) — PixivFlow alone does not justify cross-repo
|
|
96
|
+
version coordination.
|
|
97
|
+
2. The public API has run stable for a while and the host adapter has stopped
|
|
98
|
+
churning.
|
|
99
|
+
|
|
100
|
+
At that point: history-split `packages/pixiv-client` → its own repo →
|
|
101
|
+
publish `@redtidev/pixiv-client` and depend on the released version.
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# @redtidev/pixiv-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](./README.en.md) | **中文**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
面向 TypeScript/Node.js 的独立、可复用的 **Pixiv App API 客户端 kit**。
|
|
6
|
+
|
|
7
|
+
它懂 Pixiv,但**不懂你的应用**:没有配置 schema、没有数据库、没有浏览器登录、
|
|
8
|
+
没有调度器。它目前作为 npm 工作区包存在于 PixivFlow 单仓库内,且有意
|
|
9
|
+
**尚未发布**(见下文「何时拆分到独立仓库」)。
|
|
9
10
|
|
|
10
11
|
```ts
|
|
11
12
|
import { createPixivClient, StaticTokenProvider } from '@redtidev/pixiv-client';
|
|
@@ -17,87 +18,80 @@ const page = await pixiv.illustrations.searchPage({ word: '風景', limit: 30
|
|
|
17
18
|
const bytes = await pixiv.media.fetch(illust.meta_single_page!.original_image_url!);
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
`PixivAuthenticationError
|
|
30
|
-
`PixivTimeoutError
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
t
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ever logged or emitted.
|
|
55
|
-
|
|
56
|
-
## Retry ownership
|
|
57
|
-
|
|
58
|
-
| Failure | Owner |
|
|
21
|
+
## 特性
|
|
22
|
+
|
|
23
|
+
- **认证端口,而非登录实现** —— 注入任何实现了 `AccessTokenProvider`
|
|
24
|
+
(可选 `RefreshableAccessTokenProvider`)的对象。浏览器 OAuth/PKCE、
|
|
25
|
+
puppeteer 与 python 辅助逻辑留在宿主应用中。
|
|
26
|
+
- **单一传输层**服务所有调用:base URL、App 头、Bearer 认证、超时
|
|
27
|
+
(`AbortController`)、HTTP(S) 代理(undici)与 SOCKS 代理
|
|
28
|
+
(axios + socks-proxy-agent)、响应解析、类型化错误。
|
|
29
|
+
- **类型化错误** —— `PixivNotFoundError`、`PixivRateLimitError`、
|
|
30
|
+
`PixivAuthenticationError`、`PixivServerError`、`PixivNetworkError`、
|
|
31
|
+
`PixivTimeoutError`、`PixivCircuitOpenError` 等,用 `instanceof` 分类,
|
|
32
|
+
绝不用 `message.includes('429')`。
|
|
33
|
+
- **单一 429 闸门** —— 一个全局限速器为所有请求节流,并施加同一份冷却。
|
|
34
|
+
没有嵌套重试,也没有请求风暴。
|
|
35
|
+
- **槽位预约式节流** —— 并发调用方被串行化到 t、t+interval、
|
|
36
|
+
t+2·interval…(旧的协调器会把它们在同一瞬间唤醒)。
|
|
37
|
+
- **保守默认值**:最小间隔 1000 ms,抖动 25 %,首次 429 冷却 60 s,
|
|
38
|
+
指数退避 60→120→240→480 s,上限 15 min,Retry-After 始终作为下限被遵守。
|
|
39
|
+
- **惩罚衰减** —— 一次成功绝不会清除惩罚;需连续成功达到可配置次数
|
|
40
|
+
(默认 20)后才衰减。
|
|
41
|
+
- **熔断器** —— 反复 429 会 OPEN 闸门;请求以 `PixivCircuitOpenError`
|
|
42
|
+
快速失败;冷却结束后以一次半开探测检验恢复情况。
|
|
43
|
+
- **持久化状态端口** —— 注入 `RateLimitStateStore`(例如 SQLite 适配器),
|
|
44
|
+
让重启 / 部署后仍记得生效中的冷却。默认是内存实现。
|
|
45
|
+
- **在途请求合并** —— 10 个并发的 `get(123)` 只会发出 1 次 HTTP 请求。
|
|
46
|
+
- **分页但不自动爬取** —— `searchPage`/`rankingPage` 加上可选的
|
|
47
|
+
`paginate()` 辅助函数;始终受 `limit`/`maxPages` 约束,并支持
|
|
48
|
+
`AbortSignal`。
|
|
49
|
+
- **结构化事件**(通过 `onEvent`):`request_start`、`request_retry`、
|
|
50
|
+
`rate_limited`、`circuit_opened`、`auth_refresh` 等。凭据绝不会被记录或发出。
|
|
51
|
+
|
|
52
|
+
## 重试归属
|
|
53
|
+
|
|
54
|
+
| 失败 | 归属方 |
|
|
59
55
|
| ------------------------------- | -------------------------------------------- |
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
| 401
|
|
64
|
-
| 400 / 403 / 404
|
|
56
|
+
| 网络重置 / 超时 / 5xx | 传输层重试(默认 2 次),线性退避 |
|
|
57
|
+
| 熔断处于 CLOSED 时的 429 | 传输层等待共享闸门冷却 |
|
|
58
|
+
| 超过熔断 OPEN 阈值后的 429 | 快速失败;持久化重试属于宿主(调度器 / outbox) |
|
|
59
|
+
| 401 | 一次 `refreshAccessToken()` + 重试,随后报错 |
|
|
60
|
+
| 400 / 403 / 404 | 永不重试 |
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
该 kit 绝不会在 429 时轮换代理或 IP,也绝不在 App API 与 Web API 之间
|
|
63
|
+
切换以规避速率限制。端点能力回退(小说正文 v2→v1/ajax 链)是允许的;
|
|
64
|
+
反速率限制的轮换不允许。
|
|
69
65
|
|
|
70
|
-
## API
|
|
66
|
+
## API 接口面
|
|
71
67
|
|
|
72
68
|
- `createPixivClient(options)` / `new PixivClient(options)`
|
|
73
|
-
- `pixiv.illustrations`
|
|
74
|
-
`search
|
|
69
|
+
- `pixiv.illustrations` —— `get/detail`、`detailWithTags`、`searchPage`、
|
|
70
|
+
`search`、`rankingPage`、`ranking`、`userWorksPage`、`listByUser`、
|
|
75
71
|
`ugoiraMetadata`
|
|
76
|
-
- `pixiv.novels`
|
|
77
|
-
`searchPage
|
|
78
|
-
`listByUser
|
|
72
|
+
- `pixiv.novels` —— `get/detail`、`detailCompatible`、`detailWithTags`、
|
|
73
|
+
`searchPage`、`search`、`rankingPage`、`ranking`、`userWorksPage`、
|
|
74
|
+
`listByUser`、`listSeries`、`text`
|
|
79
75
|
- `pixiv.tags.autocomplete(word)`
|
|
80
|
-
- `pixiv.users.user(userId)
|
|
76
|
+
- `pixiv.users.user(userId)`(Pixiv 已移除 `/v1/user/profile`,因此没有「当前用户」端点)
|
|
81
77
|
- `pixiv.media.fetch(url)` → `ArrayBuffer`
|
|
82
|
-
- `pixiv.getRateLimitStatus()`
|
|
78
|
+
- `pixiv.getRateLimitStatus()` —— `{ circuitState, cooldownRemainingMs, penaltyLevel, last429At, nextAllowedInMs }`
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
该包对宿主类型(`TargetConfig`、调度器、SQLite、投递等)一无所知。
|
|
81
|
+
把宿主查询映射为 kit 选项是宿主的职责,例如
|
|
82
|
+
`mapTargetToPixivQuery(target): IllustSearchOptions`。
|
|
87
83
|
|
|
88
|
-
##
|
|
84
|
+
## 何时拆分到独立仓库
|
|
89
85
|
|
|
90
|
-
|
|
86
|
+
**不要**现在就拆分。只有以下两条同时成立再拆:
|
|
91
87
|
|
|
92
|
-
1.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
2. The public API has run stable for a while and the host adapter has stopped
|
|
96
|
-
churning.
|
|
88
|
+
1. 出现第二个真实消费方(另一个 bot、一个 Electron 客户端、一个独立
|
|
89
|
+
下载器等)—— 仅靠 PixivFlow 不足以支撑跨仓库的版本协调。
|
|
90
|
+
2. 公开 API 已稳定运行一段时间,且宿主适配器已不再频繁变动。
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
到那时:用 history-split 把 `packages/pixiv-client` 拆到独立仓库 →
|
|
93
|
+
发布 `@redtidev/pixiv-client` 并依赖已发布版本。
|
|
100
94
|
|
|
101
|
-
##
|
|
95
|
+
## 许可证
|
|
102
96
|
|
|
103
97
|
MIT
|
|
@@ -49,7 +49,7 @@ export declare class RateLimitGate {
|
|
|
49
49
|
private reservationQueue;
|
|
50
50
|
/**
|
|
51
51
|
* Synchronously warm the state from a state store whose load() is sync
|
|
52
|
-
* (MemoryRateLimitStateStore,
|
|
52
|
+
* (MemoryRateLimitStateStore, node:sqlite-backed stores, ...). Reservation in
|
|
53
53
|
* {@link acquire} then happens with NO preceding await, which is what makes
|
|
54
54
|
* concurrent callers serialize (A 0s, B 1s, C 2s ...).
|
|
55
55
|
*/
|
|
@@ -84,7 +84,7 @@ class RateLimitGate {
|
|
|
84
84
|
reservationQueue = Promise.resolve();
|
|
85
85
|
/**
|
|
86
86
|
* Synchronously warm the state from a state store whose load() is sync
|
|
87
|
-
* (MemoryRateLimitStateStore,
|
|
87
|
+
* (MemoryRateLimitStateStore, node:sqlite-backed stores, ...). Reservation in
|
|
88
88
|
* {@link acquire} then happens with NO preceding await, which is what makes
|
|
89
89
|
* concurrent callers serialize (A 0s, B 1s, C 2s ...).
|
|
90
90
|
*/
|
|
@@ -104,7 +104,7 @@ export class RateLimitGate {
|
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Synchronously warm the state from a state store whose load() is sync
|
|
107
|
-
* (MemoryRateLimitStateStore,
|
|
107
|
+
* (MemoryRateLimitStateStore, node:sqlite-backed stores, ...). Reservation in
|
|
108
108
|
* {@link acquire} then happens with NO preceding await, which is what makes
|
|
109
109
|
* concurrent callers serialize (A 0s, B 1s, C 2s ...).
|
|
110
110
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixivflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "🎨 智能的 Pixiv 自动化下载工具 - 支持批量下载插画和小说、定时任务、Docker部署 | Intelligent Pixiv Automation Downloader with batch download, scheduler, and Docker support",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"test:scheduled": "jest --runTestsByPath src/__tests__/integration/download-flow.test.ts",
|
|
38
38
|
"test:concurrency": "jest --runTestsByPath src/__tests__/utils/concurrency.test.ts",
|
|
39
39
|
"test:performance": "jest --runInBand src/__tests__/performance",
|
|
40
|
+
"smoke:package": "node scripts/package-smoke.mjs",
|
|
40
41
|
"benchmark": "npm run test:performance",
|
|
41
42
|
"analyze:complexity": "node scripts/analyze-complexity.js",
|
|
42
43
|
"analyze:unused": "npx ts-prune",
|
|
@@ -88,7 +89,6 @@
|
|
|
88
89
|
"tags:cleanup:dry-run": "./scripts/cleanup-github-tags.sh --dry-run"
|
|
89
90
|
},
|
|
90
91
|
"devDependencies": {
|
|
91
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
92
92
|
"@types/cors": "^2.8.19",
|
|
93
93
|
"@types/express": "^4.17.21",
|
|
94
94
|
"@types/jest": "^29.5.0",
|
|
@@ -100,7 +100,6 @@
|
|
|
100
100
|
"kill-port": "^2.0.1",
|
|
101
101
|
"nodemon": "^3.1.14",
|
|
102
102
|
"npm-run-all": "^4.1.5",
|
|
103
|
-
"puppeteer": "^24.43.1",
|
|
104
103
|
"ts-jest": "^29.4.12",
|
|
105
104
|
"typescript": "^5.6.2"
|
|
106
105
|
},
|
|
@@ -118,27 +117,27 @@
|
|
|
118
117
|
"dependencies": {
|
|
119
118
|
"@redtidev/pixiv-client": "0.1.0",
|
|
120
119
|
"axios": "^1.20.0",
|
|
121
|
-
"better-sqlite3": "^12.11.1",
|
|
122
120
|
"cors": "^2.8.6",
|
|
123
121
|
"cron-parser": "^4.9.0",
|
|
124
122
|
"express": "^4.22.2",
|
|
125
123
|
"franc-min": "^6.2.0",
|
|
126
124
|
"node-cron": "^4.6.0",
|
|
127
|
-
"pixiv-token-getter": "^2.
|
|
125
|
+
"pixiv-token-getter": "^2.6.1",
|
|
126
|
+
"puppeteer-core": "^25.9.0",
|
|
128
127
|
"socket.io": "^4.8.3",
|
|
129
128
|
"socks-proxy-agent": "^8.0.5",
|
|
130
129
|
"undici": "^6.28.0"
|
|
131
130
|
},
|
|
132
131
|
"engines": {
|
|
133
|
-
"node": ">=22.
|
|
132
|
+
"node": ">=22.13.0",
|
|
134
133
|
"npm": ">=9.0.0"
|
|
135
134
|
},
|
|
136
135
|
"files": [
|
|
137
136
|
"dist/",
|
|
138
|
-
"config/
|
|
137
|
+
"config/examples/",
|
|
139
138
|
"config/fly-two-bots.example.json",
|
|
140
139
|
"README.md",
|
|
141
|
-
"
|
|
140
|
+
"README.en.md",
|
|
142
141
|
"LICENSE"
|
|
143
142
|
],
|
|
144
143
|
"keywords": [
|