v2er-insight 1.0.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 (205) hide show
  1. package/README.md +215 -0
  2. package/dist/cli/commands/ai.d.ts +13 -0
  3. package/dist/cli/commands/ai.js +153 -0
  4. package/dist/cli/commands/analyze.d.ts +13 -0
  5. package/dist/cli/commands/analyze.js +80 -0
  6. package/dist/cli/commands/config.d.ts +43 -0
  7. package/dist/cli/commands/config.js +267 -0
  8. package/dist/cli/commands/fetch.d.ts +13 -0
  9. package/dist/cli/commands/fetch.js +150 -0
  10. package/dist/cli/commands/index.d.ts +10 -0
  11. package/dist/cli/commands/index.js +22 -0
  12. package/dist/cli/commands/run.d.ts +23 -0
  13. package/dist/cli/commands/run.js +52 -0
  14. package/dist/cli/commands/show.d.ts +13 -0
  15. package/dist/cli/commands/show.js +154 -0
  16. package/dist/cli/index.d.ts +6 -0
  17. package/dist/cli/index.js +107 -0
  18. package/dist/cli/types.d.ts +58 -0
  19. package/dist/cli/types.js +6 -0
  20. package/dist/cli/utils/error.d.ts +6 -0
  21. package/dist/cli/utils/error.js +18 -0
  22. package/dist/cli/utils.d.ts +20 -0
  23. package/dist/cli/utils.js +48 -0
  24. package/dist/cli/workflow/orchestrator.d.ts +15 -0
  25. package/dist/cli/workflow/orchestrator.js +144 -0
  26. package/dist/cli/workflow/recovery.d.ts +10 -0
  27. package/dist/cli/workflow/recovery.js +134 -0
  28. package/dist/cli/workflow/state.d.ts +19 -0
  29. package/dist/cli/workflow/state.js +45 -0
  30. package/dist/cli/workflow/types.d.ts +60 -0
  31. package/dist/cli/workflow/types.js +3 -0
  32. package/dist/config/defaults.d.ts +48 -0
  33. package/dist/config/defaults.js +42 -0
  34. package/dist/config/index.d.ts +16 -0
  35. package/dist/config/index.js +21 -0
  36. package/dist/config/path.d.ts +11 -0
  37. package/dist/config/path.js +28 -0
  38. package/dist/config/proxy.d.ts +16 -0
  39. package/dist/config/proxy.js +39 -0
  40. package/dist/config/storage.d.ts +23 -0
  41. package/dist/config/storage.js +85 -0
  42. package/dist/config/types/ai.d.ts +31 -0
  43. package/dist/config/types/ai.js +13 -0
  44. package/dist/config/types/analyzer.d.ts +15 -0
  45. package/dist/config/types/analyzer.js +6 -0
  46. package/dist/config/types/data.d.ts +20 -0
  47. package/dist/config/types/data.js +6 -0
  48. package/dist/config/types/fetch.d.ts +9 -0
  49. package/dist/config/types/fetch.js +6 -0
  50. package/dist/config/types/index.d.ts +32 -0
  51. package/dist/config/types/index.js +11 -0
  52. package/dist/config/types/log.d.ts +11 -0
  53. package/dist/config/types/log.js +6 -0
  54. package/dist/core/ai/index.d.ts +11 -0
  55. package/dist/core/ai/index.js +18 -0
  56. package/dist/core/ai/parser/index.d.ts +12 -0
  57. package/dist/core/ai/parser/index.js +44 -0
  58. package/dist/core/ai/parser/validator.d.ts +18 -0
  59. package/dist/core/ai/parser/validator.js +179 -0
  60. package/dist/core/ai/prompt/index.d.ts +20 -0
  61. package/dist/core/ai/prompt/index.js +75 -0
  62. package/dist/core/ai/prompt/system-prompt.md +210 -0
  63. package/dist/core/ai/providers/gemini.d.ts +25 -0
  64. package/dist/core/ai/providers/gemini.js +74 -0
  65. package/dist/core/ai/providers/index.d.ts +6 -0
  66. package/dist/core/ai/providers/index.js +9 -0
  67. package/dist/core/ai/types/index.d.ts +7 -0
  68. package/dist/core/ai/types/index.js +6 -0
  69. package/dist/core/ai/types/options.d.ts +14 -0
  70. package/dist/core/ai/types/options.js +6 -0
  71. package/dist/core/ai/types/provider.d.ts +19 -0
  72. package/dist/core/ai/types/provider.js +6 -0
  73. package/dist/core/ai/types/result.d.ts +64 -0
  74. package/dist/core/ai/types/result.js +6 -0
  75. package/dist/core/ai/utils/api-key.d.ts +15 -0
  76. package/dist/core/ai/utils/api-key.js +36 -0
  77. package/dist/core/ai/utils/index.d.ts +6 -0
  78. package/dist/core/ai/utils/index.js +11 -0
  79. package/dist/core/ai/utils/retry.d.ts +15 -0
  80. package/dist/core/ai/utils/retry.js +37 -0
  81. package/dist/core/analyzer/builder.d.ts +23 -0
  82. package/dist/core/analyzer/builder.js +113 -0
  83. package/dist/core/analyzer/content/chunker.d.ts +18 -0
  84. package/dist/core/analyzer/content/chunker.js +74 -0
  85. package/dist/core/analyzer/content/index.d.ts +7 -0
  86. package/dist/core/analyzer/content/index.js +13 -0
  87. package/dist/core/analyzer/content/transformer.d.ts +19 -0
  88. package/dist/core/analyzer/content/transformer.js +33 -0
  89. package/dist/core/analyzer/index.d.ts +17 -0
  90. package/dist/core/analyzer/index.js +21 -0
  91. package/dist/core/analyzer/periods/detector.d.ts +17 -0
  92. package/dist/core/analyzer/periods/detector.js +36 -0
  93. package/dist/core/analyzer/periods/index.d.ts +6 -0
  94. package/dist/core/analyzer/periods/index.js +11 -0
  95. package/dist/core/analyzer/periods/splitter.d.ts +11 -0
  96. package/dist/core/analyzer/periods/splitter.js +35 -0
  97. package/dist/core/analyzer/stats/index.d.ts +7 -0
  98. package/dist/core/analyzer/stats/index.js +13 -0
  99. package/dist/core/analyzer/stats/reply-stats.d.ts +15 -0
  100. package/dist/core/analyzer/stats/reply-stats.js +45 -0
  101. package/dist/core/analyzer/stats/topic-stats.d.ts +16 -0
  102. package/dist/core/analyzer/stats/topic-stats.js +51 -0
  103. package/dist/core/analyzer/stats/user-overview.d.ts +9 -0
  104. package/dist/core/analyzer/stats/user-overview.js +52 -0
  105. package/dist/core/analyzer/types/index.d.ts +7 -0
  106. package/dist/core/analyzer/types/index.js +6 -0
  107. package/dist/core/analyzer/types/input.d.ts +13 -0
  108. package/dist/core/analyzer/types/input.js +6 -0
  109. package/dist/core/analyzer/types/internal.d.ts +28 -0
  110. package/dist/core/analyzer/types/internal.js +6 -0
  111. package/dist/core/analyzer/types/output.d.ts +68 -0
  112. package/dist/core/analyzer/types/output.js +6 -0
  113. package/dist/core/analyzer/utils/date-parser.d.ts +41 -0
  114. package/dist/core/analyzer/utils/date-parser.js +118 -0
  115. package/dist/core/analyzer/utils/index.d.ts +6 -0
  116. package/dist/core/analyzer/utils/index.js +18 -0
  117. package/dist/core/analyzer/utils/stats.d.ts +12 -0
  118. package/dist/core/analyzer/utils/stats.js +64 -0
  119. package/dist/core/v2ex/index.d.ts +10 -0
  120. package/dist/core/v2ex/index.js +27 -0
  121. package/dist/core/v2ex/parsers/index.d.ts +8 -0
  122. package/dist/core/v2ex/parsers/index.js +15 -0
  123. package/dist/core/v2ex/parsers/replies-page.d.ts +11 -0
  124. package/dist/core/v2ex/parsers/replies-page.js +114 -0
  125. package/dist/core/v2ex/parsers/selectors/index.d.ts +10 -0
  126. package/dist/core/v2ex/parsers/selectors/index.js +18 -0
  127. package/dist/core/v2ex/parsers/selectors/pagination.d.ts +11 -0
  128. package/dist/core/v2ex/parsers/selectors/pagination.js +14 -0
  129. package/dist/core/v2ex/parsers/selectors/replies-page.d.ts +21 -0
  130. package/dist/core/v2ex/parsers/selectors/replies-page.js +24 -0
  131. package/dist/core/v2ex/parsers/selectors/topic-detail.d.ts +19 -0
  132. package/dist/core/v2ex/parsers/selectors/topic-detail.js +22 -0
  133. package/dist/core/v2ex/parsers/selectors/topics-list-page.d.ts +11 -0
  134. package/dist/core/v2ex/parsers/selectors/topics-list-page.js +14 -0
  135. package/dist/core/v2ex/parsers/selectors/user-profile.d.ts +11 -0
  136. package/dist/core/v2ex/parsers/selectors/user-profile.js +14 -0
  137. package/dist/core/v2ex/parsers/topic-detail.d.ts +11 -0
  138. package/dist/core/v2ex/parsers/topic-detail.js +94 -0
  139. package/dist/core/v2ex/parsers/topics-list-page.d.ts +11 -0
  140. package/dist/core/v2ex/parsers/topics-list-page.js +90 -0
  141. package/dist/core/v2ex/parsers/user-profile.d.ts +11 -0
  142. package/dist/core/v2ex/parsers/user-profile.js +70 -0
  143. package/dist/core/v2ex/parsers/utils/index.d.ts +6 -0
  144. package/dist/core/v2ex/parsers/utils/index.js +9 -0
  145. package/dist/core/v2ex/parsers/utils/pagination.d.ts +19 -0
  146. package/dist/core/v2ex/parsers/utils/pagination.js +29 -0
  147. package/dist/core/v2ex/types/entities.d.ts +45 -0
  148. package/dist/core/v2ex/types/entities.js +7 -0
  149. package/dist/core/v2ex/types/index.d.ts +6 -0
  150. package/dist/core/v2ex/types/index.js +6 -0
  151. package/dist/core/v2ex/types/parse-result.d.ts +64 -0
  152. package/dist/core/v2ex/types/parse-result.js +7 -0
  153. package/dist/core/v2ex/urls/constants.d.ts +5 -0
  154. package/dist/core/v2ex/urls/constants.js +8 -0
  155. package/dist/core/v2ex/urls/index.d.ts +7 -0
  156. package/dist/core/v2ex/urls/index.js +16 -0
  157. package/dist/core/v2ex/urls/topic-urls.d.ts +19 -0
  158. package/dist/core/v2ex/urls/topic-urls.js +48 -0
  159. package/dist/core/v2ex/urls/user-urls.d.ts +24 -0
  160. package/dist/core/v2ex/urls/user-urls.js +36 -0
  161. package/dist/core/v2ex/use-cases/index.d.ts +8 -0
  162. package/dist/core/v2ex/use-cases/index.js +14 -0
  163. package/dist/core/v2ex/use-cases/types.d.ts +31 -0
  164. package/dist/core/v2ex/use-cases/types.js +7 -0
  165. package/dist/core/v2ex/use-cases/user/index.d.ts +10 -0
  166. package/dist/core/v2ex/use-cases/user/index.js +16 -0
  167. package/dist/core/v2ex/use-cases/user/profile.d.ts +14 -0
  168. package/dist/core/v2ex/use-cases/user/profile.js +51 -0
  169. package/dist/core/v2ex/use-cases/user/replies.d.ts +14 -0
  170. package/dist/core/v2ex/use-cases/user/replies.js +20 -0
  171. package/dist/core/v2ex/use-cases/user/topic-urls.d.ts +21 -0
  172. package/dist/core/v2ex/use-cases/user/topic-urls.js +29 -0
  173. package/dist/core/v2ex/use-cases/user/topics-detail.d.ts +30 -0
  174. package/dist/core/v2ex/use-cases/user/topics-detail.js +62 -0
  175. package/dist/core/v2ex/use-cases/utils/index.d.ts +6 -0
  176. package/dist/core/v2ex/use-cases/utils/index.js +9 -0
  177. package/dist/core/v2ex/use-cases/utils/page-orchestrator.d.ts +24 -0
  178. package/dist/core/v2ex/use-cases/utils/page-orchestrator.js +93 -0
  179. package/dist/infra/fetcher/agent.d.ts +10 -0
  180. package/dist/infra/fetcher/agent.js +17 -0
  181. package/dist/infra/fetcher/fetcher.d.ts +10 -0
  182. package/dist/infra/fetcher/fetcher.js +81 -0
  183. package/dist/infra/fetcher/index.d.ts +3 -0
  184. package/dist/infra/fetcher/index.js +19 -0
  185. package/dist/infra/fetcher/types.d.ts +29 -0
  186. package/dist/infra/fetcher/types.js +6 -0
  187. package/dist/infra/logger/colors.d.ts +15 -0
  188. package/dist/infra/logger/colors.js +18 -0
  189. package/dist/infra/logger/index.d.ts +16 -0
  190. package/dist/infra/logger/index.js +19 -0
  191. package/dist/infra/logger/logger.d.ts +34 -0
  192. package/dist/infra/logger/logger.js +101 -0
  193. package/dist/infra/storage/cleaner.d.ts +24 -0
  194. package/dist/infra/storage/cleaner.js +73 -0
  195. package/dist/infra/storage/index.d.ts +7 -0
  196. package/dist/infra/storage/index.js +15 -0
  197. package/dist/infra/storage/paths.d.ts +26 -0
  198. package/dist/infra/storage/paths.js +53 -0
  199. package/dist/infra/storage/reader.d.ts +15 -0
  200. package/dist/infra/storage/reader.js +34 -0
  201. package/dist/infra/storage/types.d.ts +21 -0
  202. package/dist/infra/storage/types.js +18 -0
  203. package/dist/infra/storage/writer.d.ts +16 -0
  204. package/dist/infra/storage/writer.js +31 -0
  205. package/package.json +89 -0
@@ -0,0 +1,5 @@
1
+ /**
2
+ * V2EX 基础 URL 配置
3
+ */
4
+ export declare const V2EX_BASE = "https://www.v2ex.com";
5
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * V2EX 基础 URL 配置
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.V2EX_BASE = void 0;
7
+ exports.V2EX_BASE = 'https://www.v2ex.com';
8
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * V2EX URL 生成器导出
3
+ */
4
+ export { V2EX_BASE } from './constants';
5
+ export { getUserProfileUrl, getUserRepliesUrl, getUserTopicsUrl } from './user-urls';
6
+ export { getTopicUrl, extractTopicIdFromPath } from './topic-urls';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * V2EX URL 生成器导出
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractTopicIdFromPath = exports.getTopicUrl = exports.getUserTopicsUrl = exports.getUserRepliesUrl = exports.getUserProfileUrl = exports.V2EX_BASE = void 0;
7
+ var constants_1 = require("./constants");
8
+ Object.defineProperty(exports, "V2EX_BASE", { enumerable: true, get: function () { return constants_1.V2EX_BASE; } });
9
+ var user_urls_1 = require("./user-urls");
10
+ Object.defineProperty(exports, "getUserProfileUrl", { enumerable: true, get: function () { return user_urls_1.getUserProfileUrl; } });
11
+ Object.defineProperty(exports, "getUserRepliesUrl", { enumerable: true, get: function () { return user_urls_1.getUserRepliesUrl; } });
12
+ Object.defineProperty(exports, "getUserTopicsUrl", { enumerable: true, get: function () { return user_urls_1.getUserTopicsUrl; } });
13
+ var topic_urls_1 = require("./topic-urls");
14
+ Object.defineProperty(exports, "getTopicUrl", { enumerable: true, get: function () { return topic_urls_1.getTopicUrl; } });
15
+ Object.defineProperty(exports, "extractTopicIdFromPath", { enumerable: true, get: function () { return topic_urls_1.extractTopicIdFromPath; } });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 帖子相关 URL 生成器
3
+ */
4
+ /**
5
+ * 生成单个帖子页面 URL
6
+ * 支持帖子 ID 或相对路径作为输入
7
+ *
8
+ * @param topicIdOrPath - 帖子 ID(如 123456)或相对路径(如 /t/123456)
9
+ * @returns 完整 URL,如 https://www.v2ex.com/t/123456
10
+ * @throws 当输入的路径格式无效时(包含 /t/ 但无法提取有效 ID)
11
+ */
12
+ export declare function getTopicUrl(topicIdOrPath: string | number): string;
13
+ /**
14
+ * 从帖子相对路径提取帖子 ID
15
+ * @param path - 相对路径,如 /t/1180072 或 /t/1180072#reply1
16
+ * @returns 帖子 ID,如 1180072
17
+ */
18
+ export declare function extractTopicIdFromPath(path: string): string | null;
19
+ //# sourceMappingURL=topic-urls.d.ts.map
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ /**
3
+ * 帖子相关 URL 生成器
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getTopicUrl = getTopicUrl;
7
+ exports.extractTopicIdFromPath = extractTopicIdFromPath;
8
+ const constants_1 = require("./constants");
9
+ /**
10
+ * 生成单个帖子页面 URL
11
+ * 支持帖子 ID 或相对路径作为输入
12
+ *
13
+ * @param topicIdOrPath - 帖子 ID(如 123456)或相对路径(如 /t/123456)
14
+ * @returns 完整 URL,如 https://www.v2ex.com/t/123456
15
+ * @throws 当输入的路径格式无效时(包含 /t/ 但无法提取有效 ID)
16
+ */
17
+ function getTopicUrl(topicIdOrPath) {
18
+ // 数字直接使用
19
+ if (typeof topicIdOrPath === 'number') {
20
+ return `${constants_1.V2EX_BASE}/t/${topicIdOrPath}`;
21
+ }
22
+ // 修剪空白并验证
23
+ const trimmed = topicIdOrPath.trim();
24
+ if (!trimmed) {
25
+ throw new Error('Invalid topic ID: empty string provided');
26
+ }
27
+ // 路径格式(包含 /t/),提取 ID
28
+ if (trimmed.includes('/t/')) {
29
+ const topicId = extractTopicIdFromPath(trimmed);
30
+ if (topicId) {
31
+ return `${constants_1.V2EX_BASE}/t/${topicId}`;
32
+ }
33
+ // 包含 /t/ 但提取失败,抛出错误使问题更早暴露
34
+ throw new Error(`Invalid topic path: cannot extract topic ID from "${trimmed}"`);
35
+ }
36
+ // 纯 ID 字符串
37
+ return `${constants_1.V2EX_BASE}/t/${trimmed}`;
38
+ }
39
+ /**
40
+ * 从帖子相对路径提取帖子 ID
41
+ * @param path - 相对路径,如 /t/1180072 或 /t/1180072#reply1
42
+ * @returns 帖子 ID,如 1180072
43
+ */
44
+ function extractTopicIdFromPath(path) {
45
+ const match = path.match(/\/t\/(\d+)/);
46
+ return match?.[1] ?? null;
47
+ }
48
+ //# sourceMappingURL=topic-urls.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 用户相关 URL 生成器
3
+ */
4
+ /**
5
+ * 生成用户主页 URL
6
+ * @param username - 用户名
7
+ * @returns 完整 URL,如 https://www.v2ex.com/member/<username>
8
+ */
9
+ export declare function getUserProfileUrl(username: string): string;
10
+ /**
11
+ * 生成用户回复列表页 URL
12
+ * @param username - 用户名
13
+ * @param page - 页码,默认为 1
14
+ * @returns 完整 URL,如 https://www.v2ex.com/member/<username>/replies?p=1
15
+ */
16
+ export declare function getUserRepliesUrl(username: string, page?: number): string;
17
+ /**
18
+ * 生成用户发帖列表页 URL
19
+ * @param username - 用户名
20
+ * @param page - 页码,默认为 1
21
+ * @returns 完整 URL,如 https://www.v2ex.com/member/<username>/topics?p=1
22
+ */
23
+ export declare function getUserTopicsUrl(username: string, page?: number): string;
24
+ //# sourceMappingURL=user-urls.d.ts.map
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * 用户相关 URL 生成器
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getUserProfileUrl = getUserProfileUrl;
7
+ exports.getUserRepliesUrl = getUserRepliesUrl;
8
+ exports.getUserTopicsUrl = getUserTopicsUrl;
9
+ const constants_1 = require("./constants");
10
+ /**
11
+ * 生成用户主页 URL
12
+ * @param username - 用户名
13
+ * @returns 完整 URL,如 https://www.v2ex.com/member/<username>
14
+ */
15
+ function getUserProfileUrl(username) {
16
+ return `${constants_1.V2EX_BASE}/member/${username}`;
17
+ }
18
+ /**
19
+ * 生成用户回复列表页 URL
20
+ * @param username - 用户名
21
+ * @param page - 页码,默认为 1
22
+ * @returns 完整 URL,如 https://www.v2ex.com/member/<username>/replies?p=1
23
+ */
24
+ function getUserRepliesUrl(username, page = 1) {
25
+ return `${constants_1.V2EX_BASE}/member/${username}/replies?p=${page}`;
26
+ }
27
+ /**
28
+ * 生成用户发帖列表页 URL
29
+ * @param username - 用户名
30
+ * @param page - 页码,默认为 1
31
+ * @returns 完整 URL,如 https://www.v2ex.com/member/<username>/topics?p=1
32
+ */
33
+ function getUserTopicsUrl(username, page = 1) {
34
+ return `${constants_1.V2EX_BASE}/member/${username}/topics?p=${page}`;
35
+ }
36
+ //# sourceMappingURL=user-urls.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * V2EX Services 导出
3
+ * 提供高级数据获取 API
4
+ */
5
+ export type { ServiceOptions, PagedResult } from './types';
6
+ export { getUserProfile, getAllUserReplies, getAllUserTopicUrls, getAllUserTopicsDetail, } from './user';
7
+ export type { UserTopicUrlsResult, UserTopicsDetailResult } from './user';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * V2EX Services 导出
4
+ * 提供高级数据获取 API
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getAllUserTopicsDetail = exports.getAllUserTopicUrls = exports.getAllUserReplies = exports.getUserProfile = void 0;
8
+ // 用户服务导出
9
+ var user_1 = require("./user");
10
+ Object.defineProperty(exports, "getUserProfile", { enumerable: true, get: function () { return user_1.getUserProfile; } });
11
+ Object.defineProperty(exports, "getAllUserReplies", { enumerable: true, get: function () { return user_1.getAllUserReplies; } });
12
+ Object.defineProperty(exports, "getAllUserTopicUrls", { enumerable: true, get: function () { return user_1.getAllUserTopicUrls; } });
13
+ Object.defineProperty(exports, "getAllUserTopicsDetail", { enumerable: true, get: function () { return user_1.getAllUserTopicsDetail; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Service Layer 类型定义
3
+ * 定义服务层的配置选项和返回结果类型
4
+ */
5
+ import type { FetchEvents } from '../../../infra/fetcher';
6
+ /**
7
+ * Service 调用配置选项
8
+ */
9
+ export interface ServiceOptions {
10
+ /** 请求超时时间(毫秒),默认使用 Fetcher 的 30000ms */
11
+ timeout?: number;
12
+ /** 自定义请求头 */
13
+ headers?: Record<string, string>;
14
+ /** 抓取事件回调,用于进度展示 */
15
+ events?: FetchEvents;
16
+ }
17
+ /**
18
+ * 分页数据获取结果
19
+ * 用于返回多页合并后的数据
20
+ */
21
+ export interface PagedResult<T> {
22
+ /** 合并后的完整数据列表 */
23
+ data: T[];
24
+ /** 总页数(来自第一页解析) */
25
+ totalPages: number;
26
+ /** 成功抓取的页数 */
27
+ fetchedPages: number;
28
+ /** 失败的页数 */
29
+ failedPages: number;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Service Layer 类型定义
4
+ * 定义服务层的配置选项和返回结果类型
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 用户相关服务导出
3
+ */
4
+ export type { UserTopicUrlsResult } from './topic-urls';
5
+ export type { UserTopicsDetailResult } from './topics-detail';
6
+ export { getUserProfile } from './profile';
7
+ export { getAllUserReplies } from './replies';
8
+ export { getAllUserTopicUrls } from './topic-urls';
9
+ export { getAllUserTopicsDetail } from './topics-detail';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * 用户相关服务导出
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAllUserTopicsDetail = exports.getAllUserTopicUrls = exports.getAllUserReplies = exports.getUserProfile = void 0;
7
+ // 服务函数导出
8
+ var profile_1 = require("./profile");
9
+ Object.defineProperty(exports, "getUserProfile", { enumerable: true, get: function () { return profile_1.getUserProfile; } });
10
+ var replies_1 = require("./replies");
11
+ Object.defineProperty(exports, "getAllUserReplies", { enumerable: true, get: function () { return replies_1.getAllUserReplies; } });
12
+ var topic_urls_1 = require("./topic-urls");
13
+ Object.defineProperty(exports, "getAllUserTopicUrls", { enumerable: true, get: function () { return topic_urls_1.getAllUserTopicUrls; } });
14
+ var topics_detail_1 = require("./topics-detail");
15
+ Object.defineProperty(exports, "getAllUserTopicsDetail", { enumerable: true, get: function () { return topics_detail_1.getAllUserTopicsDetail; } });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 用户资料获取服务
3
+ */
4
+ import type { UserProfileParseResult } from '../../types';
5
+ import type { ServiceOptions } from '../types';
6
+ /**
7
+ * 获取用户资料
8
+ *
9
+ * @param username - 用户名
10
+ * @param options - 服务配置选项
11
+ * @returns 用户资料解析结果,失败时返回 null
12
+ */
13
+ export declare function getUserProfile(username: string, options?: ServiceOptions): Promise<UserProfileParseResult | null>;
14
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * 用户资料获取服务
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getUserProfile = getUserProfile;
7
+ const fetcher_1 = require("../../../../infra/fetcher");
8
+ const urls_1 = require("../../urls");
9
+ const parsers_1 = require("../../parsers");
10
+ /**
11
+ * 获取用户资料
12
+ *
13
+ * @param username - 用户名
14
+ * @param options - 服务配置选项
15
+ * @returns 用户资料解析结果,失败时返回 null
16
+ */
17
+ async function getUserProfile(username, options) {
18
+ // 空用户名验证
19
+ if (!username.trim()) {
20
+ throw new Error('Invalid username: empty string provided');
21
+ }
22
+ const fetcher = new fetcher_1.Fetcher(new fetcher_1.SequentialStrategy());
23
+ const fetchOptions = {
24
+ timeout: options?.timeout,
25
+ headers: options?.headers,
26
+ };
27
+ const url = (0, urls_1.getUserProfileUrl)(username);
28
+ options?.events?.onStart?.(url, 0, 1);
29
+ const generator = fetcher.fetch([url], fetchOptions);
30
+ const result = await generator.next();
31
+ if (!result.value?.success || !result.value.content) {
32
+ options?.events?.onError?.(result.value, 0, 1);
33
+ return null;
34
+ }
35
+ options?.events?.onSuccess?.(result.value, 0, 1);
36
+ try {
37
+ return (0, parsers_1.parseUserProfile)(result.value.content);
38
+ }
39
+ catch (error) {
40
+ // 解析失败时也触发错误事件,保持与获取失败的一致性
41
+ options?.events?.onError?.({
42
+ url,
43
+ success: false,
44
+ content: null,
45
+ error: error instanceof Error ? error : new Error(String(error)),
46
+ statusCode: 0,
47
+ }, 0, 1);
48
+ return null;
49
+ }
50
+ }
51
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 用户回复数据获取服务
3
+ */
4
+ import type { V2exReply } from '../../types';
5
+ import type { PagedResult, ServiceOptions } from '../types';
6
+ /**
7
+ * 获取用户所有回复
8
+ *
9
+ * @param username - 用户名
10
+ * @param options - 服务配置选项
11
+ * @returns 包含所有回复的分页结果
12
+ */
13
+ export declare function getAllUserReplies(username: string, options?: ServiceOptions): Promise<PagedResult<V2exReply>>;
14
+ //# sourceMappingURL=replies.d.ts.map
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * 用户回复数据获取服务
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAllUserReplies = getAllUserReplies;
7
+ const urls_1 = require("../../urls");
8
+ const parsers_1 = require("../../parsers");
9
+ const utils_1 = require("../utils");
10
+ /**
11
+ * 获取用户所有回复
12
+ *
13
+ * @param username - 用户名
14
+ * @param options - 服务配置选项
15
+ * @returns 包含所有回复的分页结果
16
+ */
17
+ async function getAllUserReplies(username, options) {
18
+ return (0, utils_1.fetchPagedData)((page) => (0, urls_1.getUserRepliesUrl)(username, page), parsers_1.parseRepliesPage, (result) => result.replies, options);
19
+ }
20
+ //# sourceMappingURL=replies.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 用户发帖 URL 列表获取服务
3
+ */
4
+ import type { PagedResult, ServiceOptions } from '../types';
5
+ /**
6
+ * 用户发帖 URL 获取结果
7
+ * 扩展 PagedResult,增加隐藏状态标识
8
+ */
9
+ export interface UserTopicUrlsResult extends PagedResult<string> {
10
+ /** 用户是否隐藏了主题列表 */
11
+ isHidden: boolean;
12
+ }
13
+ /**
14
+ * 获取用户所有发帖完整 URL
15
+ *
16
+ * @param username - 用户名
17
+ * @param options - 服务配置选项
18
+ * @returns 包含所有发帖完整 URL 的结果
19
+ */
20
+ export declare function getAllUserTopicUrls(username: string, options?: ServiceOptions): Promise<UserTopicUrlsResult>;
21
+ //# sourceMappingURL=topic-urls.d.ts.map
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * 用户发帖 URL 列表获取服务
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAllUserTopicUrls = getAllUserTopicUrls;
7
+ const urls_1 = require("../../urls");
8
+ const parsers_1 = require("../../parsers");
9
+ const utils_1 = require("../utils");
10
+ /**
11
+ * 获取用户所有发帖完整 URL
12
+ *
13
+ * @param username - 用户名
14
+ * @param options - 服务配置选项
15
+ * @returns 包含所有发帖完整 URL 的结果
16
+ */
17
+ async function getAllUserTopicUrls(username, options) {
18
+ const result = await (0, utils_1.fetchPagedData)((page) => (0, urls_1.getUserTopicsUrl)(username, page), parsers_1.parseTopicsListPage, (parsed) => parsed.topicUrls, options);
19
+ // 将相对路径转换为完整 URL
20
+ const fullUrls = result.data.map((path) => (0, urls_1.getTopicUrl)(path));
21
+ // 检测隐藏状态:空数据且单页可能是隐藏
22
+ const isHidden = result.data.length === 0 && result.totalPages === 1;
23
+ return {
24
+ ...result,
25
+ data: fullUrls,
26
+ isHidden,
27
+ };
28
+ }
29
+ //# sourceMappingURL=topic-urls.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 用户发帖详情获取服务
3
+ * 获取用户所有发帖的完整内容
4
+ */
5
+ import type { TopicDetailParseResult } from '../../types';
6
+ import type { ServiceOptions } from '../types';
7
+ /**
8
+ * 用户发帖详情获取结果
9
+ */
10
+ export interface UserTopicsDetailResult {
11
+ /** 所有帖子详情列表 */
12
+ topics: TopicDetailParseResult[];
13
+ /** 帖子总数(URL 数量) */
14
+ totalTopics: number;
15
+ /** 成功获取详情的帖子数 */
16
+ fetchedTopics: number;
17
+ /** 获取失败的帖子数 */
18
+ failedTopics: number;
19
+ /** 用户是否隐藏了主题列表 */
20
+ isHidden: boolean;
21
+ }
22
+ /**
23
+ * 获取用户所有发帖的完整详情
24
+ *
25
+ * @param username - 用户名
26
+ * @param options - 服务配置选项
27
+ * @returns 包含所有帖子详情的结果
28
+ */
29
+ export declare function getAllUserTopicsDetail(username: string, options?: ServiceOptions): Promise<UserTopicsDetailResult>;
30
+ //# sourceMappingURL=topics-detail.d.ts.map
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * 用户发帖详情获取服务
4
+ * 获取用户所有发帖的完整内容
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getAllUserTopicsDetail = getAllUserTopicsDetail;
8
+ const fetcher_1 = require("../../../../infra/fetcher");
9
+ const parsers_1 = require("../../parsers");
10
+ const topic_urls_1 = require("./topic-urls");
11
+ /**
12
+ * 获取用户所有发帖的完整详情
13
+ *
14
+ * @param username - 用户名
15
+ * @param options - 服务配置选项
16
+ * @returns 包含所有帖子详情的结果
17
+ */
18
+ async function getAllUserTopicsDetail(username, options) {
19
+ // 获取用户所有帖子完整 URL
20
+ const urlsResult = await (0, topic_urls_1.getAllUserTopicUrls)(username, options);
21
+ if (urlsResult.isHidden || urlsResult.data.length === 0) {
22
+ return {
23
+ topics: [],
24
+ totalTopics: 0,
25
+ fetchedTopics: 0,
26
+ failedTopics: 0,
27
+ isHidden: urlsResult.isHidden,
28
+ };
29
+ }
30
+ const fetcher = new fetcher_1.Fetcher(new fetcher_1.SequentialStrategy());
31
+ const fetchOptions = {
32
+ timeout: options?.timeout,
33
+ headers: options?.headers,
34
+ };
35
+ const topics = [];
36
+ let fetchedTopics = 0;
37
+ let failedTopics = 0;
38
+ // 批量抓取并解析帖子详情
39
+ for await (const result of fetcher.fetch(urlsResult.data, fetchOptions, options?.events)) {
40
+ if (result.success && result.content) {
41
+ try {
42
+ const detail = (0, parsers_1.parseTopicDetail)(result.content);
43
+ topics.push(detail);
44
+ fetchedTopics++;
45
+ }
46
+ catch {
47
+ failedTopics++;
48
+ }
49
+ }
50
+ else {
51
+ failedTopics++;
52
+ }
53
+ }
54
+ return {
55
+ topics,
56
+ totalTopics: urlsResult.data.length,
57
+ fetchedTopics,
58
+ failedTopics,
59
+ isHidden: false,
60
+ };
61
+ }
62
+ //# sourceMappingURL=topics-detail.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Service 工具函数导出
3
+ */
4
+ export { fetchPagedData } from './page-orchestrator';
5
+ export type { PaginatedParseResult } from './page-orchestrator';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * Service 工具函数导出
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fetchPagedData = void 0;
7
+ var page_orchestrator_1 = require("./page-orchestrator");
8
+ Object.defineProperty(exports, "fetchPagedData", { enumerable: true, get: function () { return page_orchestrator_1.fetchPagedData; } });
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 分页数据编排器
3
+ * 提供通用的多页数据获取逻辑
4
+ */
5
+ import type { PagedResult, ServiceOptions } from '../types';
6
+ /**
7
+ * 分页解析结果的基础接口
8
+ * 任何需要分页的解析结果都必须包含这两个字段
9
+ */
10
+ export interface PaginatedParseResult {
11
+ currentPage: number;
12
+ totalPages: number;
13
+ }
14
+ /**
15
+ * 获取分页数据的通用函数
16
+ *
17
+ * @param urlGenerator - 根据页码生成 URL 的函数
18
+ * @param parser - 解析 HTML 并返回包含分页信息的结果
19
+ * @param extractor - 从解析结果中提取数据列表的函数
20
+ * @param options - 服务配置选项
21
+ * @returns 合并后的分页结果
22
+ */
23
+ export declare function fetchPagedData<TParseResult extends PaginatedParseResult, TData>(urlGenerator: (page: number) => string, parser: (html: string) => TParseResult, extractor: (result: TParseResult) => TData[], options?: ServiceOptions): Promise<PagedResult<TData>>;
24
+ //# sourceMappingURL=page-orchestrator.d.ts.map
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ /**
3
+ * 分页数据编排器
4
+ * 提供通用的多页数据获取逻辑
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.fetchPagedData = fetchPagedData;
8
+ const fetcher_1 = require("../../../../infra/fetcher");
9
+ /**
10
+ * 获取分页数据的通用函数
11
+ *
12
+ * @param urlGenerator - 根据页码生成 URL 的函数
13
+ * @param parser - 解析 HTML 并返回包含分页信息的结果
14
+ * @param extractor - 从解析结果中提取数据列表的函数
15
+ * @param options - 服务配置选项
16
+ * @returns 合并后的分页结果
17
+ */
18
+ async function fetchPagedData(urlGenerator, parser, extractor, options) {
19
+ const fetcher = new fetcher_1.Fetcher(new fetcher_1.SequentialStrategy());
20
+ const fetchOptions = {
21
+ timeout: options?.timeout,
22
+ headers: options?.headers,
23
+ };
24
+ const allData = [];
25
+ let totalPages = 1;
26
+ let fetchedPages = 0;
27
+ let failedPages = 0;
28
+ // 抓取第一页,获取分页信息
29
+ // total 参数使用 -1 表示尚未确定总页数
30
+ const firstPageUrl = urlGenerator(1);
31
+ options?.events?.onStart?.(firstPageUrl, 0, -1);
32
+ const firstPageGen = fetcher.fetch([firstPageUrl], fetchOptions);
33
+ const firstPageResult = await firstPageGen.next();
34
+ if (!firstPageResult.value?.success || !firstPageResult.value.content) {
35
+ options?.events?.onError?.(firstPageResult.value, 0, -1);
36
+ return { data: [], totalPages: 1, fetchedPages: 0, failedPages: 1 };
37
+ }
38
+ options?.events?.onSuccess?.(firstPageResult.value, 0, -1);
39
+ // 解析第一页数据
40
+ try {
41
+ const firstParsed = parser(firstPageResult.value.content);
42
+ totalPages = firstParsed.totalPages;
43
+ allData.push(...extractor(firstParsed));
44
+ fetchedPages = 1;
45
+ }
46
+ catch (error) {
47
+ // 第一页解析失败,通知错误并返回空结果
48
+ options?.events?.onError?.({
49
+ url: firstPageUrl,
50
+ success: false,
51
+ content: null,
52
+ error: error instanceof Error ? error : new Error(String(error)),
53
+ statusCode: 0,
54
+ }, 0, -1);
55
+ return { data: [], totalPages: 1, fetchedPages: 0, failedPages: 1 };
56
+ }
57
+ // 单页时直接返回
58
+ if (totalPages <= 1) {
59
+ return { data: allData, totalPages, fetchedPages, failedPages };
60
+ }
61
+ // 生成剩余页 URL 并批量抓取
62
+ const remainingUrls = [];
63
+ for (let page = 2; page <= totalPages; page++) {
64
+ remainingUrls.push(urlGenerator(page));
65
+ }
66
+ let pageIndex = 1; // 从第2页开始,index=1
67
+ for await (const result of fetcher.fetch(remainingUrls, fetchOptions, options?.events)) {
68
+ if (result.success && result.content) {
69
+ try {
70
+ const parsed = parser(result.content);
71
+ allData.push(...extractor(parsed));
72
+ fetchedPages++;
73
+ }
74
+ catch (error) {
75
+ // 单页解析失败,通知错误并继续
76
+ options?.events?.onError?.({
77
+ url: result.url,
78
+ success: false,
79
+ content: null,
80
+ error: error instanceof Error ? error : new Error(String(error)),
81
+ statusCode: 0,
82
+ }, pageIndex, totalPages);
83
+ failedPages++;
84
+ }
85
+ }
86
+ else {
87
+ failedPages++;
88
+ }
89
+ pageIndex++;
90
+ }
91
+ return { data: allData, totalPages, fetchedPages, failedPages };
92
+ }
93
+ //# sourceMappingURL=page-orchestrator.js.map