paper-search-cli 0.1.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 (166) hide show
  1. package/.env.example +165 -0
  2. package/LICENSE +21 -0
  3. package/README-sc.md +642 -0
  4. package/README.md +642 -0
  5. package/dist/cli.d.ts +3 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +637 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/config/ConfigService.d.ts +26 -0
  10. package/dist/config/ConfigService.d.ts.map +1 -0
  11. package/dist/config/ConfigService.js +145 -0
  12. package/dist/config/ConfigService.js.map +1 -0
  13. package/dist/config/constants.d.ts +140 -0
  14. package/dist/config/constants.d.ts.map +1 -0
  15. package/dist/config/constants.js +93 -0
  16. package/dist/config/constants.js.map +1 -0
  17. package/dist/core/diagnostics.d.ts +43 -0
  18. package/dist/core/diagnostics.d.ts.map +1 -0
  19. package/dist/core/diagnostics.js +544 -0
  20. package/dist/core/diagnostics.js.map +1 -0
  21. package/dist/core/handleToolCall.d.ts +8 -0
  22. package/dist/core/handleToolCall.d.ts.map +1 -0
  23. package/dist/core/handleToolCall.js +440 -0
  24. package/dist/core/handleToolCall.js.map +1 -0
  25. package/dist/core/schemas.d.ts +454 -0
  26. package/dist/core/schemas.d.ts.map +1 -0
  27. package/dist/core/schemas.js +322 -0
  28. package/dist/core/schemas.js.map +1 -0
  29. package/dist/core/searchers.d.ts +45 -0
  30. package/dist/core/searchers.d.ts.map +1 -0
  31. package/dist/core/searchers.js +73 -0
  32. package/dist/core/searchers.js.map +1 -0
  33. package/dist/core/tools.d.ts +7 -0
  34. package/dist/core/tools.d.ts.map +1 -0
  35. package/dist/core/tools.js +640 -0
  36. package/dist/core/tools.js.map +1 -0
  37. package/dist/models/Paper.d.ts +64 -0
  38. package/dist/models/Paper.d.ts.map +1 -0
  39. package/dist/models/Paper.js +70 -0
  40. package/dist/models/Paper.js.map +1 -0
  41. package/dist/platforms/ArxivSearcher.d.ts +64 -0
  42. package/dist/platforms/ArxivSearcher.d.ts.map +1 -0
  43. package/dist/platforms/ArxivSearcher.js +531 -0
  44. package/dist/platforms/ArxivSearcher.js.map +1 -0
  45. package/dist/platforms/BioRxivSearcher.d.ts +47 -0
  46. package/dist/platforms/BioRxivSearcher.d.ts.map +1 -0
  47. package/dist/platforms/BioRxivSearcher.js +196 -0
  48. package/dist/platforms/BioRxivSearcher.js.map +1 -0
  49. package/dist/platforms/CORESearcher.d.ts +16 -0
  50. package/dist/platforms/CORESearcher.d.ts.map +1 -0
  51. package/dist/platforms/CORESearcher.js +148 -0
  52. package/dist/platforms/CORESearcher.js.map +1 -0
  53. package/dist/platforms/CrossrefSearcher.d.ts +34 -0
  54. package/dist/platforms/CrossrefSearcher.d.ts.map +1 -0
  55. package/dist/platforms/CrossrefSearcher.js +339 -0
  56. package/dist/platforms/CrossrefSearcher.js.map +1 -0
  57. package/dist/platforms/EuropePMCSearcher.d.ts +20 -0
  58. package/dist/platforms/EuropePMCSearcher.d.ts.map +1 -0
  59. package/dist/platforms/EuropePMCSearcher.js +173 -0
  60. package/dist/platforms/EuropePMCSearcher.js.map +1 -0
  61. package/dist/platforms/GoogleScholarSearcher.d.ts +77 -0
  62. package/dist/platforms/GoogleScholarSearcher.d.ts.map +1 -0
  63. package/dist/platforms/GoogleScholarSearcher.js +262 -0
  64. package/dist/platforms/GoogleScholarSearcher.js.map +1 -0
  65. package/dist/platforms/IACRSearcher.d.ts +51 -0
  66. package/dist/platforms/IACRSearcher.d.ts.map +1 -0
  67. package/dist/platforms/IACRSearcher.js +339 -0
  68. package/dist/platforms/IACRSearcher.js.map +1 -0
  69. package/dist/platforms/OpenAIRESearcher.d.ts +22 -0
  70. package/dist/platforms/OpenAIRESearcher.d.ts.map +1 -0
  71. package/dist/platforms/OpenAIRESearcher.js +223 -0
  72. package/dist/platforms/OpenAIRESearcher.js.map +1 -0
  73. package/dist/platforms/OpenAlexSearcher.d.ts +14 -0
  74. package/dist/platforms/OpenAlexSearcher.d.ts.map +1 -0
  75. package/dist/platforms/OpenAlexSearcher.js +114 -0
  76. package/dist/platforms/OpenAlexSearcher.js.map +1 -0
  77. package/dist/platforms/PMCSearcher.d.ts +20 -0
  78. package/dist/platforms/PMCSearcher.d.ts.map +1 -0
  79. package/dist/platforms/PMCSearcher.js +177 -0
  80. package/dist/platforms/PMCSearcher.js.map +1 -0
  81. package/dist/platforms/PaperSource.d.ts +143 -0
  82. package/dist/platforms/PaperSource.d.ts.map +1 -0
  83. package/dist/platforms/PaperSource.js +125 -0
  84. package/dist/platforms/PaperSource.js.map +1 -0
  85. package/dist/platforms/PubMedSearcher.d.ts +104 -0
  86. package/dist/platforms/PubMedSearcher.d.ts.map +1 -0
  87. package/dist/platforms/PubMedSearcher.js +422 -0
  88. package/dist/platforms/PubMedSearcher.js.map +1 -0
  89. package/dist/platforms/SciHubSearcher.d.ts +66 -0
  90. package/dist/platforms/SciHubSearcher.d.ts.map +1 -0
  91. package/dist/platforms/SciHubSearcher.js +398 -0
  92. package/dist/platforms/SciHubSearcher.js.map +1 -0
  93. package/dist/platforms/ScienceDirectSearcher.d.ts +42 -0
  94. package/dist/platforms/ScienceDirectSearcher.d.ts.map +1 -0
  95. package/dist/platforms/ScienceDirectSearcher.js +326 -0
  96. package/dist/platforms/ScienceDirectSearcher.js.map +1 -0
  97. package/dist/platforms/ScopusSearcher.d.ts +43 -0
  98. package/dist/platforms/ScopusSearcher.d.ts.map +1 -0
  99. package/dist/platforms/ScopusSearcher.js +364 -0
  100. package/dist/platforms/ScopusSearcher.js.map +1 -0
  101. package/dist/platforms/SemanticScholarSearcher.d.ts +96 -0
  102. package/dist/platforms/SemanticScholarSearcher.d.ts.map +1 -0
  103. package/dist/platforms/SemanticScholarSearcher.js +419 -0
  104. package/dist/platforms/SemanticScholarSearcher.js.map +1 -0
  105. package/dist/platforms/SpringerSearcher.d.ts +54 -0
  106. package/dist/platforms/SpringerSearcher.d.ts.map +1 -0
  107. package/dist/platforms/SpringerSearcher.js +407 -0
  108. package/dist/platforms/SpringerSearcher.js.map +1 -0
  109. package/dist/platforms/UnpaywallSearcher.d.ts +18 -0
  110. package/dist/platforms/UnpaywallSearcher.d.ts.map +1 -0
  111. package/dist/platforms/UnpaywallSearcher.js +115 -0
  112. package/dist/platforms/UnpaywallSearcher.js.map +1 -0
  113. package/dist/platforms/WebOfScienceSearcher.d.ts +111 -0
  114. package/dist/platforms/WebOfScienceSearcher.d.ts.map +1 -0
  115. package/dist/platforms/WebOfScienceSearcher.js +500 -0
  116. package/dist/platforms/WebOfScienceSearcher.js.map +1 -0
  117. package/dist/platforms/WileySearcher.d.ts +44 -0
  118. package/dist/platforms/WileySearcher.d.ts.map +1 -0
  119. package/dist/platforms/WileySearcher.js +148 -0
  120. package/dist/platforms/WileySearcher.js.map +1 -0
  121. package/dist/services/CitationService.d.ts +66 -0
  122. package/dist/services/CitationService.d.ts.map +1 -0
  123. package/dist/services/CitationService.js +237 -0
  124. package/dist/services/CitationService.js.map +1 -0
  125. package/dist/services/MultiSourceSearchService.d.ts +19 -0
  126. package/dist/services/MultiSourceSearchService.d.ts.map +1 -0
  127. package/dist/services/MultiSourceSearchService.js +96 -0
  128. package/dist/services/MultiSourceSearchService.js.map +1 -0
  129. package/dist/services/OpenAccessFallbackService.d.ts +20 -0
  130. package/dist/services/OpenAccessFallbackService.d.ts.map +1 -0
  131. package/dist/services/OpenAccessFallbackService.js +124 -0
  132. package/dist/services/OpenAccessFallbackService.js.map +1 -0
  133. package/dist/utils/ErrorHandler.d.ts +99 -0
  134. package/dist/utils/ErrorHandler.d.ts.map +1 -0
  135. package/dist/utils/ErrorHandler.js +266 -0
  136. package/dist/utils/ErrorHandler.js.map +1 -0
  137. package/dist/utils/Logger.d.ts +6 -0
  138. package/dist/utils/Logger.d.ts.map +1 -0
  139. package/dist/utils/Logger.js +26 -0
  140. package/dist/utils/Logger.js.map +1 -0
  141. package/dist/utils/PDFExtractor.d.ts +34 -0
  142. package/dist/utils/PDFExtractor.d.ts.map +1 -0
  143. package/dist/utils/PDFExtractor.js +130 -0
  144. package/dist/utils/PDFExtractor.js.map +1 -0
  145. package/dist/utils/PdfDownload.d.ts +7 -0
  146. package/dist/utils/PdfDownload.d.ts.map +1 -0
  147. package/dist/utils/PdfDownload.js +52 -0
  148. package/dist/utils/PdfDownload.js.map +1 -0
  149. package/dist/utils/QuotaManager.d.ts +32 -0
  150. package/dist/utils/QuotaManager.d.ts.map +1 -0
  151. package/dist/utils/QuotaManager.js +95 -0
  152. package/dist/utils/QuotaManager.js.map +1 -0
  153. package/dist/utils/RateLimiter.d.ts +50 -0
  154. package/dist/utils/RateLimiter.d.ts.map +1 -0
  155. package/dist/utils/RateLimiter.js +121 -0
  156. package/dist/utils/RateLimiter.js.map +1 -0
  157. package/dist/utils/RequestCache.d.ts +26 -0
  158. package/dist/utils/RequestCache.d.ts.map +1 -0
  159. package/dist/utils/RequestCache.js +66 -0
  160. package/dist/utils/RequestCache.js.map +1 -0
  161. package/dist/utils/SecurityUtils.d.ts +80 -0
  162. package/dist/utils/SecurityUtils.d.ts.map +1 -0
  163. package/dist/utils/SecurityUtils.js +357 -0
  164. package/dist/utils/SecurityUtils.js.map +1 -0
  165. package/package.json +111 -0
  166. package/skills/paper-search/SKILL.md +192 -0
@@ -0,0 +1,130 @@
1
+ import axios from 'axios';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ // @ts-ignore - pdf-parse doesn't have type definitions
5
+ import pdf from 'pdf-parse';
6
+ import { logDebug, logWarn } from './Logger.js';
7
+ import { TIMEOUTS, USER_AGENT } from '../config/constants.js';
8
+ export class PDFExtractor {
9
+ /**
10
+ * Download PDF from URL and extract text
11
+ */
12
+ async extractFromUrl(url, options = {}) {
13
+ try {
14
+ logDebug(`Downloading PDF from: ${url}`);
15
+ const response = await axios.get(url, {
16
+ responseType: 'arraybuffer',
17
+ timeout: TIMEOUTS.DOWNLOAD,
18
+ headers: {
19
+ 'User-Agent': USER_AGENT,
20
+ 'Accept': 'application/pdf'
21
+ }
22
+ });
23
+ if (response.status !== 200) {
24
+ throw new Error(`Failed to download PDF: HTTP ${response.status}`);
25
+ }
26
+ const buffer = Buffer.from(response.data);
27
+ return await this.extractFromBuffer(buffer, options);
28
+ }
29
+ catch (error) {
30
+ logWarn(`Error downloading PDF from ${url}:`, error.message);
31
+ throw error;
32
+ }
33
+ }
34
+ /**
35
+ * Extract text from PDF file
36
+ */
37
+ async extractFromFile(filePath, options = {}) {
38
+ try {
39
+ if (!fs.existsSync(filePath)) {
40
+ throw new Error(`PDF file not found: ${filePath}`);
41
+ }
42
+ logDebug(`Extracting text from PDF file: ${filePath}`);
43
+ const dataBuffer = fs.readFileSync(filePath);
44
+ return await this.extractFromBuffer(dataBuffer, options);
45
+ }
46
+ catch (error) {
47
+ logWarn(`Error extracting PDF from file ${filePath}:`, error.message);
48
+ throw error;
49
+ }
50
+ }
51
+ /**
52
+ * Extract text from PDF buffer
53
+ */
54
+ async extractFromBuffer(buffer, options = {}) {
55
+ try {
56
+ const pdfOptions = {};
57
+ if (options.maxPages) {
58
+ pdfOptions.max = options.maxPages;
59
+ }
60
+ const data = await pdf(buffer, pdfOptions);
61
+ let text = data.text;
62
+ if (options.cleanText !== false) {
63
+ text = this.cleanText(text);
64
+ }
65
+ logDebug(`Extracted ${data.numpages} pages, ${text.length} characters`);
66
+ return {
67
+ text,
68
+ numPages: data.numpages,
69
+ info: data.info,
70
+ metadata: data.metadata
71
+ };
72
+ }
73
+ catch (error) {
74
+ logWarn('Error parsing PDF:', error.message);
75
+ throw error;
76
+ }
77
+ }
78
+ /**
79
+ * Clean extracted text
80
+ */
81
+ cleanText(text) {
82
+ return text
83
+ // Remove excessive whitespace
84
+ .replace(/\s+/g, ' ')
85
+ // Remove page numbers (common patterns)
86
+ .replace(/\n\s*\d+\s*\n/g, '\n')
87
+ // Remove headers/footers (lines with only numbers or short text)
88
+ .replace(/\n\s*[\w\s]{1,30}\s*\n/g, '\n')
89
+ // Normalize line breaks
90
+ .replace(/\n{3,}/g, '\n\n')
91
+ // Trim
92
+ .trim();
93
+ }
94
+ /**
95
+ * Download PDF and save to file
96
+ */
97
+ async downloadPdf(url, savePath) {
98
+ try {
99
+ logDebug(`Downloading PDF from ${url} to ${savePath}`);
100
+ // Ensure directory exists
101
+ const dir = path.dirname(savePath);
102
+ if (!fs.existsSync(dir)) {
103
+ fs.mkdirSync(dir, { recursive: true });
104
+ }
105
+ const response = await axios.get(url, {
106
+ responseType: 'stream',
107
+ timeout: TIMEOUTS.DOWNLOAD,
108
+ headers: {
109
+ 'User-Agent': USER_AGENT,
110
+ 'Accept': 'application/pdf'
111
+ }
112
+ });
113
+ const writer = fs.createWriteStream(savePath);
114
+ response.data.pipe(writer);
115
+ return new Promise((resolve, reject) => {
116
+ writer.on('finish', () => {
117
+ logDebug(`PDF saved to: ${savePath}`);
118
+ resolve(savePath);
119
+ });
120
+ writer.on('error', reject);
121
+ });
122
+ }
123
+ catch (error) {
124
+ logWarn(`Error downloading PDF:`, error.message);
125
+ throw error;
126
+ }
127
+ }
128
+ }
129
+ export default PDFExtractor;
130
+ //# sourceMappingURL=PDFExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PDFExtractor.js","sourceRoot":"","sources":["../../src/utils/PDFExtractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export interface PdfDownloadOptions {
2
+ headers?: Record<string, string>;
3
+ }
4
+ export declare function safeFilename(value: string, fallback?: string): string;
5
+ export declare function isPdfBuffer(buffer: Buffer, contentType?: string): boolean;
6
+ export declare function downloadPdfFromUrl(pdfUrl: string, savePath: string, filenameHint: string, options?: PdfDownloadOptions): Promise<string>;
7
+ //# sourceMappingURL=PdfDownload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PdfDownload.d.ts","sourceRoot":"","sources":["../../src/utils/PdfDownload.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,MAAM,CAGtE;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,SAAK,GAAG,OAAO,CAErE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,CAAC,CA4CjB"}
@@ -0,0 +1,52 @@
1
+ import axios from 'axios';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { TIMEOUTS, USER_AGENT } from '../config/constants.js';
5
+ export function safeFilename(value, fallback = 'paper') {
6
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^[_\-.]+|[_\-.]+$/g, '');
7
+ return (safe || fallback).slice(0, 120);
8
+ }
9
+ export function isPdfBuffer(buffer, contentType = '') {
10
+ return contentType.toLowerCase().includes('pdf') || buffer.subarray(0, 4).toString() === '%PDF';
11
+ }
12
+ export async function downloadPdfFromUrl(pdfUrl, savePath, filenameHint, options = {}) {
13
+ if (!pdfUrl) {
14
+ throw new Error('Missing PDF URL');
15
+ }
16
+ if (pdfUrl.startsWith('ftp://')) {
17
+ throw new Error(`FTP PDF links are not supported by the Node downloader: ${pdfUrl}`);
18
+ }
19
+ fs.mkdirSync(savePath, { recursive: true });
20
+ const outputPath = path.join(savePath, `${safeFilename(filenameHint)}.pdf`);
21
+ const response = await axios.get(pdfUrl, {
22
+ responseType: 'arraybuffer',
23
+ timeout: TIMEOUTS.DOWNLOAD,
24
+ maxRedirects: 5,
25
+ headers: {
26
+ 'User-Agent': USER_AGENT,
27
+ Accept: 'application/pdf,*/*',
28
+ ...(options.headers || {})
29
+ },
30
+ validateStatus: status => status < 500
31
+ });
32
+ if (response.status >= 400) {
33
+ const errorBuffer = Buffer.from(response.data || []);
34
+ const snippet = errorBuffer.subarray(0, 240).toString('utf8').replace(/\s+/g, ' ').trim();
35
+ const challenge = response.headers['cf-mitigated'] || /cloudflare|challenge|just a moment/i.test(snippet);
36
+ const reason = challenge ? 'provider returned an HTML anti-bot challenge instead of a PDF' : 'provider refused the request';
37
+ throw new Error(`PDF download failed with HTTP ${response.status}: ${reason}`);
38
+ }
39
+ const buffer = Buffer.from(response.data);
40
+ const contentType = String(response.headers['content-type'] || '').toLowerCase();
41
+ if (!isPdfBuffer(buffer, contentType)) {
42
+ const snippet = buffer.subarray(0, 240).toString('utf8').replace(/\s+/g, ' ').trim();
43
+ const challenge = response.headers['cf-mitigated'] || /cloudflare|challenge|preparing to download|proof-of-work/i.test(snippet);
44
+ const reason = challenge
45
+ ? 'the provider returned an HTML challenge page instead of a PDF'
46
+ : `content-type was ${contentType || 'unknown'}`;
47
+ throw new Error(`Resolved URL did not return a PDF (${reason}): ${pdfUrl}`);
48
+ }
49
+ fs.writeFileSync(outputPath, buffer);
50
+ return outputPath;
51
+ }
52
+ //# sourceMappingURL=PdfDownload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PdfDownload.js","sourceRoot":"","sources":["../../src/utils/PdfDownload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export interface QuotaConfig {
2
+ dailyLimit: number;
3
+ envPrefix?: string;
4
+ }
5
+ export interface QuotaStatus {
6
+ platform: string;
7
+ used: number;
8
+ limit: number;
9
+ remaining: number;
10
+ resetAt: string;
11
+ }
12
+ export declare class QuotaExhaustedError extends Error {
13
+ readonly platform: string;
14
+ readonly limit: number;
15
+ readonly resetAt: string;
16
+ constructor(platform: string, limit: number, resetAt: string);
17
+ }
18
+ export declare class QuotaManager {
19
+ private static instance;
20
+ private quotas;
21
+ private constructor();
22
+ static getInstance(): QuotaManager;
23
+ registerPlatform(platform: string, config: QuotaConfig): void;
24
+ checkQuota(platform: string): void;
25
+ incrementUsage(platform: string): void;
26
+ getStatus(platform: string): QuotaStatus | null;
27
+ private resetIfNeeded;
28
+ private getDayKey;
29
+ private getNextResetTime;
30
+ }
31
+ export default QuotaManager;
32
+ //# sourceMappingURL=QuotaManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuotaManager.d.ts","sourceRoot":"","sources":["../../src/utils/QuotaManager.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,QAAQ,EAAE,MAAM;aAChB,KAAK,EAAE,MAAM;aACb,OAAO,EAAE,MAAM;gBAFf,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM;CAKlC;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAe;IACtC,OAAO,CAAC,MAAM,CAA2E;IAEzF,OAAO;IAEP,MAAM,CAAC,WAAW,IAAI,YAAY;IAOlC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IAc7D,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAmBlC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAetC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAiB/C,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,gBAAgB;CAMzB;AAED,eAAe,YAAY,CAAC"}
@@ -0,0 +1,95 @@
1
+ import { logDebug, logWarn } from './Logger.js';
2
+ export class QuotaExhaustedError extends Error {
3
+ platform;
4
+ limit;
5
+ resetAt;
6
+ constructor(platform, limit, resetAt) {
7
+ super(`${platform} daily quota exhausted (${limit}/day). Resets at ${resetAt}`);
8
+ this.platform = platform;
9
+ this.limit = limit;
10
+ this.resetAt = resetAt;
11
+ this.name = 'QuotaExhaustedError';
12
+ }
13
+ }
14
+ export class QuotaManager {
15
+ static instance;
16
+ quotas = new Map();
17
+ constructor() { }
18
+ static getInstance() {
19
+ if (!QuotaManager.instance) {
20
+ QuotaManager.instance = new QuotaManager();
21
+ }
22
+ return QuotaManager.instance;
23
+ }
24
+ registerPlatform(platform, config) {
25
+ const envVar = config.envPrefix ? `${config.envPrefix}_DAILY_LIMIT` : undefined;
26
+ const limitFromEnv = envVar ? Number(process.env[envVar]) : NaN;
27
+ const limit = Number.isFinite(limitFromEnv) && limitFromEnv > 0 ? limitFromEnv : config.dailyLimit;
28
+ this.quotas.set(platform, {
29
+ limit,
30
+ used: 0,
31
+ dayKey: this.getDayKey()
32
+ });
33
+ logDebug(`QuotaManager: Registered ${platform} with daily limit ${limit}`);
34
+ }
35
+ checkQuota(platform) {
36
+ const quota = this.quotas.get(platform);
37
+ if (!quota) {
38
+ logWarn(`QuotaManager: Platform ${platform} not registered`);
39
+ return;
40
+ }
41
+ this.resetIfNeeded(platform, quota);
42
+ if (quota.limit <= 0) {
43
+ return;
44
+ }
45
+ if (quota.used >= quota.limit) {
46
+ const resetAt = this.getNextResetTime();
47
+ throw new QuotaExhaustedError(platform, quota.limit, resetAt);
48
+ }
49
+ }
50
+ incrementUsage(platform) {
51
+ const quota = this.quotas.get(platform);
52
+ if (!quota) {
53
+ logWarn(`QuotaManager: Platform ${platform} not registered`);
54
+ return;
55
+ }
56
+ this.resetIfNeeded(platform, quota);
57
+ if (quota.limit > 0) {
58
+ quota.used++;
59
+ logDebug(`QuotaManager: ${platform} usage: ${quota.used}/${quota.limit}`);
60
+ }
61
+ }
62
+ getStatus(platform) {
63
+ const quota = this.quotas.get(platform);
64
+ if (!quota) {
65
+ return null;
66
+ }
67
+ this.resetIfNeeded(platform, quota);
68
+ return {
69
+ platform,
70
+ used: quota.used,
71
+ limit: quota.limit,
72
+ remaining: Math.max(0, quota.limit - quota.used),
73
+ resetAt: this.getNextResetTime()
74
+ };
75
+ }
76
+ resetIfNeeded(platform, quota) {
77
+ const currentKey = this.getDayKey();
78
+ if (currentKey !== quota.dayKey) {
79
+ quota.dayKey = currentKey;
80
+ quota.used = 0;
81
+ logDebug(`QuotaManager: Reset ${platform} quota for new day ${currentKey}`);
82
+ }
83
+ }
84
+ getDayKey(date = new Date()) {
85
+ return date.toISOString().split('T')[0];
86
+ }
87
+ getNextResetTime() {
88
+ const tomorrow = new Date();
89
+ tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
90
+ tomorrow.setUTCHours(0, 0, 0, 0);
91
+ return tomorrow.toISOString();
92
+ }
93
+ }
94
+ export default QuotaManager;
95
+ //# sourceMappingURL=QuotaManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuotaManager.js","sourceRoot":"","sources":["../../src/utils/QuotaManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 请求速率限制器
3
+ * 用于控制API请求频率,遵守各平台的使用限制
4
+ */
5
+ export interface RateLimiterOptions {
6
+ /** 每秒最大请求数 */
7
+ requestsPerSecond: number;
8
+ /** 突发请求容量 */
9
+ burstCapacity?: number;
10
+ /** 是否启用调试日志 */
11
+ debug?: boolean;
12
+ }
13
+ export declare class RateLimiter {
14
+ private readonly requestsPerSecond;
15
+ private readonly intervalMs;
16
+ private readonly burstCapacity;
17
+ private readonly debug;
18
+ private tokens;
19
+ private lastRefill;
20
+ private intervalHandle;
21
+ private readonly pendingRequests;
22
+ constructor(options: RateLimiterOptions);
23
+ /**
24
+ * 等待直到可以发送请求
25
+ */
26
+ waitForPermission(): Promise<void>;
27
+ /**
28
+ * 补充令牌(令牌桶算法)
29
+ */
30
+ private refillTokens;
31
+ /**
32
+ * 处理等待中的请求
33
+ */
34
+ private processPendingRequests;
35
+ /**
36
+ * 获取当前状态
37
+ */
38
+ getStatus(): {
39
+ availableTokens: number;
40
+ maxTokens: number;
41
+ requestsPerSecond: number;
42
+ pendingRequests: number;
43
+ };
44
+ /**
45
+ * 清理过期的等待请求(超过30秒)
46
+ */
47
+ cleanup(): void;
48
+ dispose(): void;
49
+ }
50
+ //# sourceMappingURL=RateLimiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/utils/RateLimiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,kBAAkB;IACjC,cAAc;IACd,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAEhC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAGxB;gBAEI,OAAO,EAAE,kBAAkB;IAevC;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBxC;;OAEG;IACH,OAAO,CAAC,YAAY;IAepB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAiB9B;;OAEG;IACH,SAAS,IAAI;QACX,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;KACzB;IAUD;;OAEG;IACH,OAAO,IAAI,IAAI;IAsBf,OAAO,IAAI,IAAI;CAGhB"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * 请求速率限制器
3
+ * 用于控制API请求频率,遵守各平台的使用限制
4
+ */
5
+ import { logDebug } from './Logger.js';
6
+ export class RateLimiter {
7
+ requestsPerSecond;
8
+ intervalMs;
9
+ burstCapacity;
10
+ debug;
11
+ tokens;
12
+ lastRefill;
13
+ intervalHandle;
14
+ pendingRequests = [];
15
+ constructor(options) {
16
+ this.requestsPerSecond = options.requestsPerSecond;
17
+ this.intervalMs = 1000 / this.requestsPerSecond;
18
+ this.burstCapacity = options.burstCapacity || this.requestsPerSecond;
19
+ this.debug = options.debug || false;
20
+ this.tokens = this.burstCapacity;
21
+ this.lastRefill = Date.now();
22
+ // 定期处理等待中的请求
23
+ this.intervalHandle = setInterval(() => this.processPendingRequests(), Math.min(this.intervalMs, 100));
24
+ // Don't keep the process alive just because of the limiter interval.
25
+ this.intervalHandle.unref?.();
26
+ }
27
+ /**
28
+ * 等待直到可以发送请求
29
+ */
30
+ async waitForPermission() {
31
+ this.refillTokens();
32
+ if (this.tokens > 0) {
33
+ this.tokens--;
34
+ if (this.debug) {
35
+ logDebug(`RateLimiter: Request allowed, ${this.tokens} tokens remaining`);
36
+ }
37
+ return Promise.resolve();
38
+ }
39
+ // 没有可用令牌,加入等待队列
40
+ return new Promise((resolve) => {
41
+ this.pendingRequests.push({
42
+ resolve,
43
+ timestamp: Date.now()
44
+ });
45
+ if (this.debug) {
46
+ logDebug(`RateLimiter: Request queued, ${this.pendingRequests.length} waiting`);
47
+ }
48
+ });
49
+ }
50
+ /**
51
+ * 补充令牌(令牌桶算法)
52
+ */
53
+ refillTokens() {
54
+ const now = Date.now();
55
+ const timePassed = now - this.lastRefill;
56
+ if (timePassed >= this.intervalMs) {
57
+ const tokensToAdd = Math.floor(timePassed / this.intervalMs);
58
+ this.tokens = Math.min(this.burstCapacity, this.tokens + tokensToAdd);
59
+ this.lastRefill = now;
60
+ if (this.debug && tokensToAdd > 0) {
61
+ logDebug(`RateLimiter: Added ${tokensToAdd} tokens, total: ${this.tokens}`);
62
+ }
63
+ }
64
+ }
65
+ /**
66
+ * 处理等待中的请求
67
+ */
68
+ processPendingRequests() {
69
+ this.refillTokens();
70
+ while (this.tokens > 0 && this.pendingRequests.length > 0) {
71
+ const request = this.pendingRequests.shift();
72
+ if (request) {
73
+ this.tokens--;
74
+ request.resolve();
75
+ if (this.debug) {
76
+ const waitTime = Date.now() - request.timestamp;
77
+ logDebug(`RateLimiter: Released waiting request (waited ${waitTime}ms), ${this.tokens} tokens remaining`);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ /**
83
+ * 获取当前状态
84
+ */
85
+ getStatus() {
86
+ this.refillTokens();
87
+ return {
88
+ availableTokens: this.tokens,
89
+ maxTokens: this.burstCapacity,
90
+ requestsPerSecond: this.requestsPerSecond,
91
+ pendingRequests: this.pendingRequests.length
92
+ };
93
+ }
94
+ /**
95
+ * 清理过期的等待请求(超过30秒)
96
+ */
97
+ cleanup() {
98
+ const now = Date.now();
99
+ const timeoutMs = 30000; // 30秒超时
100
+ let removedCount = 0;
101
+ while (this.pendingRequests.length > 0) {
102
+ const first = this.pendingRequests[0];
103
+ if (now - first.timestamp > timeoutMs) {
104
+ this.pendingRequests.shift();
105
+ // 拒绝过期的请求
106
+ first.resolve(); // 或者可以reject,但这里选择允许继续
107
+ removedCount++;
108
+ }
109
+ else {
110
+ break;
111
+ }
112
+ }
113
+ if (this.debug && removedCount > 0) {
114
+ logDebug(`RateLimiter: Cleaned up ${removedCount} expired requests`);
115
+ }
116
+ }
117
+ dispose() {
118
+ clearInterval(this.intervalHandle);
119
+ }
120
+ }
121
+ //# sourceMappingURL=RateLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimiter.js","sourceRoot":"","sources":["../../src/utils/RateLimiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface CacheOptions {
2
+ maxSize?: number;
3
+ ttlMs?: number;
4
+ }
5
+ export interface CacheStats {
6
+ size: number;
7
+ maxSize: number;
8
+ hits: number;
9
+ misses: number;
10
+ hitRate: number;
11
+ }
12
+ export declare class RequestCache<T = any> {
13
+ private cache;
14
+ private hits;
15
+ private misses;
16
+ constructor(options?: CacheOptions);
17
+ generateKey(platform: string, query: string, options?: Record<string, any>): string;
18
+ get(key: string): T | undefined;
19
+ set(key: string, value: T): void;
20
+ has(key: string): boolean;
21
+ delete(key: string): boolean;
22
+ clear(): void;
23
+ getStats(): CacheStats;
24
+ }
25
+ export default RequestCache;
26
+ //# sourceMappingURL=RequestCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestCache.d.ts","sourceRoot":"","sources":["../../src/utils/RequestCache.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,YAAY,CAAC,CAAC,GAAG,GAAG;IAC/B,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,MAAM,CAAa;gBAEf,OAAO,GAAE,YAAiB;IActC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;IASnF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAY/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAKhC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B,KAAK,IAAI,IAAI;IAOb,QAAQ,IAAI,UAAU;CAUvB;AAED,eAAe,YAAY,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { LRUCache } from 'lru-cache';
2
+ import { createHash } from 'crypto';
3
+ import { logDebug } from './Logger.js';
4
+ export class RequestCache {
5
+ cache;
6
+ hits = 0;
7
+ misses = 0;
8
+ constructor(options = {}) {
9
+ const maxSize = options.maxSize || 100;
10
+ const ttlMs = options.ttlMs || 3600000; // 1 hour default
11
+ this.cache = new LRUCache({
12
+ max: maxSize,
13
+ ttl: ttlMs,
14
+ updateAgeOnGet: true,
15
+ updateAgeOnHas: false
16
+ });
17
+ logDebug(`RequestCache initialized: maxSize=${maxSize}, ttl=${ttlMs}ms`);
18
+ }
19
+ generateKey(platform, query, options) {
20
+ const data = JSON.stringify({
21
+ platform,
22
+ query: query.toLowerCase().trim(),
23
+ options: options || {}
24
+ });
25
+ return createHash('sha256').update(data).digest('hex');
26
+ }
27
+ get(key) {
28
+ const value = this.cache.get(key);
29
+ if (value !== undefined) {
30
+ this.hits++;
31
+ logDebug(`Cache hit: ${key.substring(0, 8)}...`);
32
+ return value;
33
+ }
34
+ this.misses++;
35
+ logDebug(`Cache miss: ${key.substring(0, 8)}...`);
36
+ return undefined;
37
+ }
38
+ set(key, value) {
39
+ this.cache.set(key, value);
40
+ logDebug(`Cache set: ${key.substring(0, 8)}...`);
41
+ }
42
+ has(key) {
43
+ return this.cache.has(key);
44
+ }
45
+ delete(key) {
46
+ return this.cache.delete(key);
47
+ }
48
+ clear() {
49
+ this.cache.clear();
50
+ this.hits = 0;
51
+ this.misses = 0;
52
+ logDebug('Cache cleared');
53
+ }
54
+ getStats() {
55
+ const total = this.hits + this.misses;
56
+ return {
57
+ size: this.cache.size,
58
+ maxSize: this.cache.max,
59
+ hits: this.hits,
60
+ misses: this.misses,
61
+ hitRate: total > 0 ? this.hits / total : 0
62
+ };
63
+ }
64
+ }
65
+ export default RequestCache;
66
+ //# sourceMappingURL=RequestCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestCache.js","sourceRoot":"","sources":["../../src/utils/RequestCache.ts"],"names":[],"mappings":"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"}