pixivflow 2.8.0 → 2.10.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.
Files changed (87) hide show
  1. package/README.md +6 -0
  2. package/README_EN.md +4 -0
  3. package/dist/commands/TagsCommand.d.ts +25 -0
  4. package/dist/commands/TagsCommand.d.ts.map +1 -0
  5. package/dist/commands/TagsCommand.js +176 -0
  6. package/dist/commands/TagsCommand.js.map +1 -0
  7. package/dist/commands/TopicCommand.d.ts +19 -0
  8. package/dist/commands/TopicCommand.d.ts.map +1 -0
  9. package/dist/commands/TopicCommand.js +113 -0
  10. package/dist/commands/TopicCommand.js.map +1 -0
  11. package/dist/commands/index.d.ts +3 -1
  12. package/dist/commands/index.d.ts.map +1 -1
  13. package/dist/commands/index.js +7 -1
  14. package/dist/commands/index.js.map +1 -1
  15. package/dist/config/types.d.ts +45 -2
  16. package/dist/config/types.d.ts.map +1 -1
  17. package/dist/config/validation.d.ts.map +1 -1
  18. package/dist/config/validation.js +40 -3
  19. package/dist/config/validation.js.map +1 -1
  20. package/dist/download/DownloadManager.d.ts.map +1 -1
  21. package/dist/download/DownloadManager.js +5 -2
  22. package/dist/download/DownloadManager.js.map +1 -1
  23. package/dist/download/handlers/IllustrationTargetHandler.d.ts +5 -1
  24. package/dist/download/handlers/IllustrationTargetHandler.d.ts.map +1 -1
  25. package/dist/download/handlers/IllustrationTargetHandler.js +26 -1
  26. package/dist/download/handlers/IllustrationTargetHandler.js.map +1 -1
  27. package/dist/download/handlers/NovelTargetHandler.d.ts +4 -1
  28. package/dist/download/handlers/NovelTargetHandler.d.ts.map +1 -1
  29. package/dist/download/handlers/NovelTargetHandler.js +23 -1
  30. package/dist/download/handlers/NovelTargetHandler.js.map +1 -1
  31. package/dist/package.json +1 -1
  32. package/dist/pixiv/PixivClient.d.ts +10 -1
  33. package/dist/pixiv/PixivClient.d.ts.map +1 -1
  34. package/dist/pixiv/PixivClient.js +25 -0
  35. package/dist/pixiv/PixivClient.js.map +1 -1
  36. package/dist/pixiv/client/SearchService.d.ts +2 -1
  37. package/dist/pixiv/client/SearchService.d.ts.map +1 -1
  38. package/dist/pixiv/client/SearchService.js +10 -0
  39. package/dist/pixiv/client/SearchService.js.map +1 -1
  40. package/dist/pixiv/types.d.ts +15 -0
  41. package/dist/pixiv/types.d.ts.map +1 -1
  42. package/dist/storage/Database.d.ts +2 -0
  43. package/dist/storage/Database.d.ts.map +1 -1
  44. package/dist/storage/Database.js +4 -0
  45. package/dist/storage/Database.js.map +1 -1
  46. package/dist/tags/TagDiscoveryService.d.ts +22 -0
  47. package/dist/tags/TagDiscoveryService.d.ts.map +1 -0
  48. package/dist/tags/TagDiscoveryService.js +137 -0
  49. package/dist/tags/TagDiscoveryService.js.map +1 -0
  50. package/dist/tags/TagDiscoveryStore.d.ts +21 -0
  51. package/dist/tags/TagDiscoveryStore.d.ts.map +1 -0
  52. package/dist/tags/TagDiscoveryStore.js +67 -0
  53. package/dist/tags/TagDiscoveryStore.js.map +1 -0
  54. package/dist/tags/TagPlanApplier.d.ts +22 -0
  55. package/dist/tags/TagPlanApplier.d.ts.map +1 -0
  56. package/dist/tags/TagPlanApplier.js +86 -0
  57. package/dist/tags/TagPlanApplier.js.map +1 -0
  58. package/dist/tags/types.d.ts +36 -0
  59. package/dist/tags/types.d.ts.map +1 -0
  60. package/dist/tags/types.js +3 -0
  61. package/dist/tags/types.js.map +1 -0
  62. package/dist/topic/TopicCache.d.ts +23 -0
  63. package/dist/topic/TopicCache.d.ts.map +1 -0
  64. package/dist/topic/TopicCache.js +78 -0
  65. package/dist/topic/TopicCache.js.map +1 -0
  66. package/dist/topic/TopicPipeline.d.ts +49 -0
  67. package/dist/topic/TopicPipeline.d.ts.map +1 -0
  68. package/dist/topic/TopicPipeline.js +224 -0
  69. package/dist/topic/TopicPipeline.js.map +1 -0
  70. package/dist/topic/TopicResolver.d.ts +31 -0
  71. package/dist/topic/TopicResolver.d.ts.map +1 -0
  72. package/dist/topic/TopicResolver.js +162 -0
  73. package/dist/topic/TopicResolver.js.map +1 -0
  74. package/dist/topic/TopicTagScorer.d.ts +13 -0
  75. package/dist/topic/TopicTagScorer.d.ts.map +1 -0
  76. package/dist/topic/TopicTagScorer.js +109 -0
  77. package/dist/topic/TopicTagScorer.js.map +1 -0
  78. package/dist/topic/createTopicPipeline.d.ts +12 -0
  79. package/dist/topic/createTopicPipeline.d.ts.map +1 -0
  80. package/dist/topic/createTopicPipeline.js +31 -0
  81. package/dist/topic/createTopicPipeline.js.map +1 -0
  82. package/dist/topic/types.d.ts +93 -0
  83. package/dist/topic/types.d.ts.map +1 -0
  84. package/dist/topic/types.js +8 -0
  85. package/dist/topic/types.js.map +1 -0
  86. package/dist/webui/package.json +1 -1
  87. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicTagScorer.d.ts","sourceRoot":"","sources":["../../src/topic/TopicTagScorer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAoCrD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED,qBAAa,cAAc;IACzB,2DAA2D;IAC3D,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAI1B,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,EAAE;CA+EzC"}
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TopicTagScorer = void 0;
4
+ /**
5
+ * Lightweight, explainable tag relatedness. No ML — pure co-occurrence stats
6
+ * over the small, bounded samples the resolver fetches.
7
+ *
8
+ * score = coverage * specificity * suggestionWeight
9
+ *
10
+ * - coverage: fraction of the seed-topic sample that carries the tag.
11
+ * - specificity: how much more often the tag appears with the topic than in
12
+ * the background. High-frequency but topic-agnostic tags
13
+ * (R-18, オリジナル, 女の子…) get pushed down automatically.
14
+ * - suggestionWeight: a modest boost when Pixiv autocomplete also returns it.
15
+ *
16
+ * The background document frequency is estimated from a cheap background
17
+ * sample (a recent, unrelated search) when available, falling back to a small
18
+ * curated set of platform-wide tags. We deliberately keep that list tiny — the
19
+ * specificity score does the real denoising.
20
+ */
21
+ /** Platform-level tags that are almost never topic-specific. */
22
+ const GENERIC_TAG_PENALTY = new Set([
23
+ 'r-18', 'r-18g', 'r-15', 'original', 'オリジナル', '創作', '女の子', '男の子',
24
+ 'イラスト', '漫画', 'manga', 'illustration', 'artwork', '落書き', 'らくがき',
25
+ '1000users入り', '5000users入り', '10000users入り', '500users入り', '100users入り',
26
+ 'pixiv', 'commission', 'skeb', '依頼絵', '仕事絵',
27
+ ]);
28
+ class TopicTagScorer {
29
+ /** Normalize for matching/keys: trim, NFKC, lower-case. */
30
+ key(value) {
31
+ return value.trim().normalize('NFKC').toLocaleLowerCase();
32
+ }
33
+ score(input) {
34
+ const seedKey = this.key(input.seed);
35
+ const topicSize = Math.max(input.topicWorks.length, 1);
36
+ const backgroundSize = Math.max(input.backgroundWorks.length, 1);
37
+ const stats = new Map();
38
+ const accumulate = (works, bucket) => {
39
+ for (const work of works) {
40
+ const seen = new Set();
41
+ for (const tag of work.tags ?? []) {
42
+ const name = tag.name?.trim();
43
+ const k = this.key(name ?? '');
44
+ if (!k || seen.has(k))
45
+ continue;
46
+ seen.add(k);
47
+ let stat = stats.get(k);
48
+ if (!stat) {
49
+ stat = { name: name, translatedName: tag.translated_name?.trim() || undefined, topicDocs: 0, backgroundDocs: 0, suggested: false };
50
+ stats.set(k, stat);
51
+ }
52
+ else if (!stat.translatedName && tag.translated_name?.trim()) {
53
+ stat.translatedName = tag.translated_name.trim();
54
+ }
55
+ stat[bucket] += 1;
56
+ }
57
+ }
58
+ };
59
+ accumulate(input.topicWorks, 'topicDocs');
60
+ accumulate(input.backgroundWorks, 'backgroundDocs');
61
+ for (const name of input.suggestedNames) {
62
+ const k = this.key(name);
63
+ const stat = stats.get(k);
64
+ if (stat)
65
+ stat.suggested = true;
66
+ }
67
+ const resolved = [];
68
+ for (const [k, stat] of stats) {
69
+ if (k === seedKey)
70
+ continue; // seed is added separately with score 1
71
+ const coverage = stat.topicDocs / topicSize;
72
+ if (stat.topicDocs <= 0)
73
+ continue;
74
+ // Background frequency of the tag in the unrelated sample. Tags that are
75
+ // common everywhere (R-18, オリジナル…) have high backgroundFreq; tags
76
+ // that only travel with the topic have ~0.
77
+ const backgroundFreq = stat.backgroundDocs / backgroundSize;
78
+ // PMI-style lift: how much more the tag appears with the topic than at
79
+ // large. Positive (and growing for background-absent tags), near/below 0
80
+ // for generic tags. Additive smoothing prevents divide-by-zero blowups.
81
+ const lift = Math.log((coverage + 0.03) / (backgroundFreq + 0.03));
82
+ // Map lift to a 0..1+ specificity via a logistic-ish squish; a lift of ~0
83
+ // (equally common in background) maps to ~0.5 and strongly generic tags
84
+ // fall below it.
85
+ const specificity = Math.max(0, Math.min(1.2, lift / 3 + 0.5));
86
+ // Final score rewards both co-occurrence (recall) and specificity
87
+ // (precision), so rare-but-exclusive tags can still surface while
88
+ // ubiquitous tags are suppressed.
89
+ const recall = Math.sqrt(coverage); // dampen so mid-coverage can win
90
+ const suggestionWeight = stat.suggested ? 1.1 : 1.0;
91
+ const genericPenalty = GENERIC_TAG_PENALTY.has(k) ? 0.4 : 1.0;
92
+ const raw = recall * specificity * suggestionWeight * genericPenalty;
93
+ resolved.push({
94
+ name: stat.name,
95
+ translatedName: stat.translatedName,
96
+ score: Number(raw.toFixed(4)),
97
+ occurrences: stat.topicDocs,
98
+ coverage: Number(coverage.toFixed(4)),
99
+ specificity: Number(specificity.toFixed(4)),
100
+ suggested: stat.suggested,
101
+ seed: false,
102
+ });
103
+ }
104
+ resolved.sort((a, b) => b.score - a.score || b.occurrences - a.occurrences || a.name.localeCompare(b.name));
105
+ return resolved;
106
+ }
107
+ }
108
+ exports.TopicTagScorer = TopicTagScorer;
109
+ //# sourceMappingURL=TopicTagScorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicTagScorer.js","sourceRoot":"","sources":["../../src/topic/TopicTagScorer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { IPixivClient } from '../interfaces/IPixivClient';
2
+ import { TopicPipeline } from './TopicPipeline';
3
+ export type TopicPipelineFactory = () => TopicPipeline;
4
+ /**
5
+ * Builds the topic pipeline with a persistent cache beside the SQLite database
6
+ * (which lives on the data volume in Docker, so it survives container rebuilds).
7
+ * One instance is shared across targets within a download run.
8
+ */
9
+ export declare function createTopicPipeline(client: IPixivClient, databasePath: string | undefined, requestDelayMs?: number): TopicPipeline;
10
+ /** Lazily builds one shared pipeline per download run (cache + resolver). */
11
+ export declare function createTopicPipelineFactory(client: IPixivClient, databasePath: string | undefined, requestDelayMs?: number): TopicPipelineFactory;
12
+ //# sourceMappingURL=createTopicPipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTopicPipeline.d.ts","sourceRoot":"","sources":["../../src/topic/createTopicPipeline.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,MAAM,oBAAoB,GAAG,MAAM,aAAa,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,cAAc,SAAM,GAAG,aAAa,CAO/H;AAED,6EAA6E;AAC7E,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,cAAc,SAAM,GAAG,oBAAoB,CAM7I"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTopicPipeline = createTopicPipeline;
4
+ exports.createTopicPipelineFactory = createTopicPipelineFactory;
5
+ const node_path_1 = require("node:path");
6
+ const TopicCache_1 = require("./TopicCache");
7
+ const TopicPipeline_1 = require("./TopicPipeline");
8
+ const TopicResolver_1 = require("./TopicResolver");
9
+ /**
10
+ * Builds the topic pipeline with a persistent cache beside the SQLite database
11
+ * (which lives on the data volume in Docker, so it survives container rebuilds).
12
+ * One instance is shared across targets within a download run.
13
+ */
14
+ function createTopicPipeline(client, databasePath, requestDelayMs = 500) {
15
+ const resolved = databasePath && (0, node_path_1.isAbsolute)(databasePath)
16
+ ? databasePath
17
+ : (0, node_path_1.resolve)(process.cwd(), databasePath ?? 'data/pixiv-downloader.db');
18
+ const cache = new TopicCache_1.TopicCache((0, node_path_1.resolve)((0, node_path_1.dirname)(resolved), 'topic-cache'));
19
+ const resolver = new TopicResolver_1.TopicResolver(client, cache, requestDelayMs);
20
+ return new TopicPipeline_1.TopicPipeline(client, resolver, requestDelayMs);
21
+ }
22
+ /** Lazily builds one shared pipeline per download run (cache + resolver). */
23
+ function createTopicPipelineFactory(client, databasePath, requestDelayMs = 500) {
24
+ let instance;
25
+ return () => {
26
+ if (!instance)
27
+ instance = createTopicPipeline(client, databasePath, requestDelayMs);
28
+ return instance;
29
+ };
30
+ }
31
+ //# sourceMappingURL=createTopicPipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTopicPipeline.js","sourceRoot":"","sources":["../../src/topic/createTopicPipeline.ts"],"names":[],"mappings":";;AAgBA,kDAOC;AAGD,gEAMC;AA/BD,yCAAwE;AAIxE,6CAA0C;AAC1C,mDAAgD;AAChD,mDAAgD;AAIhD;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,MAAoB,EAAE,YAAgC,EAAE,cAAc,GAAG,GAAG;IAC9G,MAAM,QAAQ,GAAG,YAAY,IAAI,IAAA,sBAAU,EAAC,YAAY,CAAC;QACvD,CAAC,CAAC,YAAY;QACd,CAAC,CAAC,IAAA,mBAAW,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,IAAI,0BAA0B,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,IAAI,uBAAU,CAAC,IAAA,mBAAW,EAAC,IAAA,mBAAO,EAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,IAAI,6BAAa,CAAC,MAAe,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;IAC3E,OAAO,IAAI,6BAAa,CAAC,MAAe,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AACtE,CAAC;AAED,6EAA6E;AAC7E,SAAgB,0BAA0B,CAAC,MAAoB,EAAE,YAAgC,EAAE,cAAc,GAAG,GAAG;IACrH,IAAI,QAAmC,CAAC;IACxC,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,QAAQ;YAAE,QAAQ,GAAG,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;QACpF,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Semantic-topic download: resolve a user topic into a dynamic, Pixiv-derived
3
+ * search space. Pure metadata/tag signals only — no LLM, VLM, embeddings or
4
+ * local models are used anywhere in this module.
5
+ */
6
+ export type TopicContentType = 'illustration' | 'novel';
7
+ /** A single related tag with a 0..1 relatedness score and provenance. */
8
+ export interface ResolvedTag {
9
+ name: string;
10
+ translatedName?: string;
11
+ /** Combined relatedness score (co-occurrence * specificity * suggestion). */
12
+ score: number;
13
+ /** How many sampled works (of the seed search) carried this tag. */
14
+ occurrences: number;
15
+ /** Coverage of the sampled seed works (occurrences / sample size). */
16
+ coverage: number;
17
+ /** Specificity: high when common with the topic but rare in the background. */
18
+ specificity: number;
19
+ /** Present in Pixiv autocomplete for the seed. */
20
+ suggested: boolean;
21
+ /** The seed tag always scores 1.0 and is always included. */
22
+ seed: boolean;
23
+ }
24
+ /**
25
+ * The resolved search space for one topic + content type. Cached on the data
26
+ * volume so daily runs reuse it and a failed refresh can fall back to it.
27
+ */
28
+ export interface TopicSpace {
29
+ version: 1;
30
+ topic: string;
31
+ contentType: TopicContentType;
32
+ createdAt: string;
33
+ expiresAt: string;
34
+ sampleSize: number;
35
+ sampledWorks: number;
36
+ tags: ResolvedTag[];
37
+ }
38
+ export interface TopicDiscoveryOptions {
39
+ maxTags?: number;
40
+ sampleWorks?: number;
41
+ cacheDays?: number;
42
+ minScore?: number;
43
+ refresh?: boolean;
44
+ }
45
+ export interface TopicCollectOptions {
46
+ maxPerTag?: number;
47
+ maxCandidates?: number;
48
+ minMetadataScore?: number;
49
+ }
50
+ /** A trimmed candidate: only fields needed for filtering/ranking. */
51
+ export interface TopicCandidate {
52
+ id: number;
53
+ type: TopicContentType;
54
+ title: string;
55
+ caption: string;
56
+ tags: string[];
57
+ bookmarks: number;
58
+ views: number;
59
+ /** Local popularity score (bookmarks + views/1000). */
60
+ popularity: number;
61
+ /** Metadata topic-relevance score computed by the filter stage. */
62
+ metadataScore: number;
63
+ }
64
+ /** Minimal surface the resolver/collector need from the Pixiv client. */
65
+ export interface TopicClient {
66
+ getTagAutocomplete(seed: string): Promise<Array<{
67
+ name: string;
68
+ translated_name?: string;
69
+ }>>;
70
+ searchIllustrationsForTags(seed: string, limit: number, options?: {
71
+ startDate?: string;
72
+ endDate?: string;
73
+ }): Promise<Array<WorkLike>>;
74
+ searchNovelsForTags(seed: string, limit: number, options?: {
75
+ startDate?: string;
76
+ endDate?: string;
77
+ }): Promise<Array<WorkLike>>;
78
+ }
79
+ export interface WorkLike {
80
+ id: number;
81
+ title?: string;
82
+ caption?: string;
83
+ create_date?: string;
84
+ tags?: Array<{
85
+ name: string;
86
+ translated_name?: string;
87
+ }>;
88
+ total_bookmarks?: number;
89
+ bookmark_count?: number;
90
+ total_view?: number;
91
+ view_count?: number;
92
+ }
93
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/topic/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Semantic-topic download: resolve a user topic into a dynamic, Pixiv-derived
4
+ * search space. Pure metadata/tag signals only — no LLM, VLM, embeddings or
5
+ * local models are used anywhere in this module.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/topic/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "commonjs",
3
3
  "name": "pixivflow-webui-backend",
4
- "version": "2.8.0",
4
+ "version": "2.10.0",
5
5
  "description": "PixivFlow WebUI Backend - CommonJS module"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixivflow",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "🎨 智能的 Pixiv 自动化下载工具 - 支持批量下载插画和小说、定时任务、Docker部署 | Intelligent Pixiv Automation Downloader with batch download, scheduler, and Docker support",
5
5
  "repository": {
6
6
  "type": "git",