wespy-ts 0.2.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 (86) hide show
  1. package/README.md +146 -0
  2. package/dist/cli/main.d.ts +7 -0
  3. package/dist/cli/main.d.ts.map +1 -0
  4. package/dist/cli/main.js +312 -0
  5. package/dist/cli/main.js.map +1 -0
  6. package/dist/converter/html-to-markdown.d.ts +9 -0
  7. package/dist/converter/html-to-markdown.d.ts.map +1 -0
  8. package/dist/converter/html-to-markdown.js +171 -0
  9. package/dist/converter/html-to-markdown.js.map +1 -0
  10. package/dist/converter/sanitize-html.d.ts +12 -0
  11. package/dist/converter/sanitize-html.d.ts.map +1 -0
  12. package/dist/converter/sanitize-html.js +22 -0
  13. package/dist/converter/sanitize-html.js.map +1 -0
  14. package/dist/core/errors.d.ts +17 -0
  15. package/dist/core/errors.d.ts.map +1 -0
  16. package/dist/core/errors.js +36 -0
  17. package/dist/core/errors.js.map +1 -0
  18. package/dist/core/result.d.ts +26 -0
  19. package/dist/core/result.d.ts.map +1 -0
  20. package/dist/core/result.js +26 -0
  21. package/dist/core/result.js.map +1 -0
  22. package/dist/core/types.d.ts +156 -0
  23. package/dist/core/types.d.ts.map +1 -0
  24. package/dist/core/types.js +29 -0
  25. package/dist/core/types.js.map +1 -0
  26. package/dist/fetcher/http-client.d.ts +31 -0
  27. package/dist/fetcher/http-client.d.ts.map +1 -0
  28. package/dist/fetcher/http-client.js +124 -0
  29. package/dist/fetcher/http-client.js.map +1 -0
  30. package/dist/index.d.ts +14 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +14 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/platforms/detector.d.ts +15 -0
  35. package/dist/platforms/detector.d.ts.map +1 -0
  36. package/dist/platforms/detector.js +30 -0
  37. package/dist/platforms/detector.js.map +1 -0
  38. package/dist/platforms/generic/generic-article.extractor.d.ts +25 -0
  39. package/dist/platforms/generic/generic-article.extractor.d.ts.map +1 -0
  40. package/dist/platforms/generic/generic-article.extractor.js +171 -0
  41. package/dist/platforms/generic/generic-article.extractor.js.map +1 -0
  42. package/dist/platforms/juejin/juejin-article.extractor.d.ts +20 -0
  43. package/dist/platforms/juejin/juejin-article.extractor.d.ts.map +1 -0
  44. package/dist/platforms/juejin/juejin-article.extractor.js +167 -0
  45. package/dist/platforms/juejin/juejin-article.extractor.js.map +1 -0
  46. package/dist/platforms/juejin/juejin.types.d.ts +13 -0
  47. package/dist/platforms/juejin/juejin.types.d.ts.map +1 -0
  48. package/dist/platforms/juejin/juejin.types.js +5 -0
  49. package/dist/platforms/juejin/juejin.types.js.map +1 -0
  50. package/dist/platforms/wechat/wechat-album.extractor.d.ts +25 -0
  51. package/dist/platforms/wechat/wechat-album.extractor.d.ts.map +1 -0
  52. package/dist/platforms/wechat/wechat-album.extractor.js +190 -0
  53. package/dist/platforms/wechat/wechat-album.extractor.js.map +1 -0
  54. package/dist/platforms/wechat/wechat-article.extractor.d.ts +20 -0
  55. package/dist/platforms/wechat/wechat-article.extractor.d.ts.map +1 -0
  56. package/dist/platforms/wechat/wechat-article.extractor.js +132 -0
  57. package/dist/platforms/wechat/wechat-article.extractor.js.map +1 -0
  58. package/dist/platforms/wechat/wechat.types.d.ts +17 -0
  59. package/dist/platforms/wechat/wechat.types.d.ts.map +1 -0
  60. package/dist/platforms/wechat/wechat.types.js +5 -0
  61. package/dist/platforms/wechat/wechat.types.js.map +1 -0
  62. package/dist/sdk/fetch-album-list.d.ts +10 -0
  63. package/dist/sdk/fetch-album-list.d.ts.map +1 -0
  64. package/dist/sdk/fetch-album-list.js +31 -0
  65. package/dist/sdk/fetch-album-list.js.map +1 -0
  66. package/dist/sdk/fetch-album.d.ts +24 -0
  67. package/dist/sdk/fetch-album.d.ts.map +1 -0
  68. package/dist/sdk/fetch-album.js +67 -0
  69. package/dist/sdk/fetch-album.js.map +1 -0
  70. package/dist/sdk/fetch-article.d.ts +24 -0
  71. package/dist/sdk/fetch-article.d.ts.map +1 -0
  72. package/dist/sdk/fetch-article.js +111 -0
  73. package/dist/sdk/fetch-article.js.map +1 -0
  74. package/dist/utils/fs.d.ts +16 -0
  75. package/dist/utils/fs.d.ts.map +1 -0
  76. package/dist/utils/fs.js +26 -0
  77. package/dist/utils/fs.js.map +1 -0
  78. package/dist/utils/text.d.ts +20 -0
  79. package/dist/utils/text.d.ts.map +1 -0
  80. package/dist/utils/text.js +96 -0
  81. package/dist/utils/text.js.map +1 -0
  82. package/dist/utils/url.d.ts +22 -0
  83. package/dist/utils/url.d.ts.map +1 -0
  84. package/dist/utils/url.js +63 -0
  85. package/dist/utils/url.js.map +1 -0
  86. package/package.json +64 -0
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # WeSpy (TypeScript)
2
+
3
+ 文章抓取与 Markdown 转换工具 — TypeScript 版本。
4
+
5
+ ## 支持平台
6
+
7
+ | 平台 | 单篇文章 | 专辑/合集 |
8
+ |------|---------|----------|
9
+ | 微信公众号 (`mp.weixin.qq.com`) | ✅ | ✅ |
10
+ | 掘金 (`juejin.cn`) | ✅ | — |
11
+ | 通用网页 | ✅ | — |
12
+
13
+ ## 安装
14
+
15
+ ### 全局安装
16
+
17
+ ```bash
18
+ npm install -g wespy-ts
19
+ ```
20
+
21
+ ### 项目依赖
22
+
23
+ ```bash
24
+ npm install wespy-ts
25
+ ```
26
+
27
+ ### 本地开发
28
+
29
+ ```bash
30
+ git clone git@github.com:Cuimc/WeSpy-TS.git
31
+ cd WeSpy-TS
32
+ npm install
33
+ npm run build
34
+ ```
35
+
36
+ ## CLI 使用
37
+
38
+ 全局安装后可直接使用 `wespy` 命令,也可以通过 `npx` 运行:
39
+
40
+ ```bash
41
+ # 获取单篇文章(默认输出 Markdown)
42
+ wespy fetch-article https://mp.weixin.qq.com/s/xxxxx
43
+
44
+ # 使用 npx 运行
45
+ npx wespy fetch-article https://mp.weixin.qq.com/s/xxxxx
46
+
47
+ # 指定输出格式和目录
48
+ wespy fetch-article https://mp.weixin.qq.com/s/xxxxx -o ./output --all
49
+
50
+ # 批量下载微信专辑
51
+ wespy fetch-album https://mp.weixin.qq.com/mp/appmsgalbum?... --max 5
52
+
53
+ # 智能模式(自动判断 URL 类型)
54
+ wespy https://mp.weixin.qq.com/s/xxxxx -o ./output --html --json
55
+ ```
56
+
57
+ ### CLI 参数
58
+
59
+ | 参数 | 说明 | 默认值 |
60
+ |------|------|--------|
61
+ | `-o, --output <dir>` | 输出目录 | `articles` |
62
+ | `--html` | 同时保存 HTML 文件 | false |
63
+ | `--json` | 同时保存 JSON 文件 | false |
64
+ | `--all` | 保存所有格式 | false |
65
+ | `--max <n>` | 专辑最大下载数 | 10 |
66
+ | `--timeout <ms>` | 请求超时时间 | 30000 |
67
+ | `--album-only` | 仅获取文章列表 | false |
68
+
69
+ ## SDK API
70
+
71
+ ```typescript
72
+ import { fetchArticle, fetchAlbum } from 'wespy-ts'
73
+
74
+ // 获取单篇文章
75
+ const result = await fetchArticle({
76
+ url: 'https://mp.weixin.qq.com/s/xxxxx',
77
+ format: ['markdown', 'json'],
78
+ outputDir: './output',
79
+ })
80
+
81
+ if (result.ok) {
82
+ console.log(result.article.title)
83
+ console.log(result.article.markdown)
84
+ console.log(result.artifacts)
85
+ } else {
86
+ console.error(result.error.code, result.error.message)
87
+ }
88
+
89
+ // 批量获取专辑
90
+ const albumResult = await fetchAlbum({
91
+ url: 'https://mp.weixin.qq.com/mp/appmsgalbum?...',
92
+ maxArticles: 10,
93
+ format: ['markdown'],
94
+ })
95
+
96
+ if (albumResult.ok) {
97
+ console.log(`获取了 ${albumResult.articles.length} 篇文章`)
98
+ }
99
+ ```
100
+
101
+ ## 输出结构
102
+
103
+ ### ArticleDraft
104
+
105
+ ```typescript
106
+ interface ArticleDraft {
107
+ platform: 'wechat' | 'juejin' | 'generic'
108
+ url: string
109
+ title: string
110
+ author?: string
111
+ publishTime?: string
112
+ contentHtml?: string
113
+ contentText?: string
114
+ markdown?: string
115
+ metadata: Record<string, unknown>
116
+ fetchedAt: string
117
+ warnings: string[]
118
+ }
119
+ ```
120
+
121
+ ### FetchArticleResult
122
+
123
+ ```typescript
124
+ // 成功
125
+ { ok: true, article: ArticleDraft, artifacts: OutputArtifact[], warnings: string[] }
126
+
127
+ // 失败
128
+ { ok: false, error: { code: string, message: string, retryable: boolean } }
129
+ ```
130
+
131
+ ## 测试
132
+
133
+ ```bash
134
+ npm test
135
+ ```
136
+
137
+ ## 开发
138
+
139
+ ```bash
140
+ npm run dev # watch 模式
141
+ npm run lint # 类型检查
142
+ ```
143
+
144
+ ## Python 原版
145
+
146
+ Python 原版代码保留在 `../WeSpy/` 目录中,未被删除。详见 [MIGRATION.md](./MIGRATION.md)。
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * WeSpy CLI 入口
4
+ * 使用 commander 实现,调用 SDK API
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AACA;;;GAGG"}
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * WeSpy CLI 入口
4
+ * 使用 commander 实现,调用 SDK API
5
+ */
6
+ import { createInterface } from 'node:readline';
7
+ import { readFileSync } from 'node:fs';
8
+ import { join, dirname } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { Command } from 'commander';
11
+ import { fetchArticle } from '../sdk/fetch-article.js';
12
+ import { fetchAlbum } from '../sdk/fetch-album.js';
13
+ import { fetchAlbumList } from '../sdk/fetch-album-list.js';
14
+ import { isWechatAlbumUrl } from '../platforms/detector.js';
15
+ import { writeJsonSafe, ensureDir } from '../utils/fs.js';
16
+ // 从 package.json 读取版本号,避免硬编码
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
19
+ function addCommonOptions(cmd) {
20
+ return cmd
21
+ .option('-o, --output <dir>', '输出目录', 'articles')
22
+ .option('--html', '同时保存 HTML 文件')
23
+ .option('--json', '同时保存 JSON 文件')
24
+ .option('--all', '保存所有格式 (HTML + JSON + Markdown)')
25
+ .option('--download-images', '下载图片到本地(当前迁移版本暂未实现)')
26
+ .option('--timeout <ms>', '请求超时时间 (毫秒)', '30000')
27
+ .option('-v, --verbose', '显示详细信息');
28
+ }
29
+ /** 打印文章信息,缺失字段不显示 */
30
+ function printArticleInfo(article) {
31
+ console.log(` 标题: ${article.title}`);
32
+ if (article.author)
33
+ console.log(` 作者: ${article.author}`);
34
+ if (article.publishTime)
35
+ console.log(` 时间: ${article.publishTime}`);
36
+ }
37
+ function resolveFormats(options) {
38
+ if (options.all)
39
+ return ['markdown', 'html', 'json'];
40
+ const formats = ['markdown'];
41
+ if (options.html)
42
+ formats.push('html');
43
+ if (options.json)
44
+ formats.push('json');
45
+ return formats;
46
+ }
47
+ function printVerbose(options, url, extra) {
48
+ const formats = resolveFormats(options);
49
+ console.log('\n[verbose] 配置信息:');
50
+ console.log(` URL: ${url}`);
51
+ console.log(` 输出目录: ${options.output}`);
52
+ console.log(` 输出格式: HTML=${formats.includes('html')}, JSON=${formats.includes('json')}, Markdown=${formats.includes('markdown')}`);
53
+ if (extra) {
54
+ for (const [k, v] of Object.entries(extra)) {
55
+ console.log(` ${k}: ${v}`);
56
+ }
57
+ }
58
+ }
59
+ // ── 交互模式 ──────────────────────────────────────────────
60
+ async function promptInput(question, defaultVal) {
61
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
62
+ return new Promise((resolve) => {
63
+ rl.question(question, (answer) => {
64
+ rl.close();
65
+ resolve(answer.trim() || defaultVal);
66
+ });
67
+ });
68
+ }
69
+ async function runInteractiveMode() {
70
+ console.log('文章获取工具');
71
+ console.log('='.repeat(40));
72
+ const url = await promptInput('请输入文章 URL: ', '');
73
+ if (!url) {
74
+ console.error('URL 不能为空!');
75
+ process.exit(1);
76
+ }
77
+ const outputDir = await promptInput("输出目录 (回车使用默认 'articles'): ", 'articles');
78
+ console.log('\n输出格式选择:');
79
+ console.log('1. 仅 Markdown (默认)');
80
+ console.log('2. Markdown + HTML');
81
+ console.log('3. Markdown + JSON');
82
+ console.log('4. 全部格式 (HTML + JSON + Markdown)');
83
+ const choice = await promptInput('请选择 (1-4, 回车使用默认1): ', '1');
84
+ let formats = ['markdown'];
85
+ if (choice === '2')
86
+ formats = ['markdown', 'html'];
87
+ else if (choice === '3')
88
+ formats = ['markdown', 'json'];
89
+ else if (choice === '4')
90
+ formats = ['markdown', 'html', 'json'];
91
+ const maxArticles = 10;
92
+ // 执行
93
+ if (isWechatAlbumUrl(url)) {
94
+ console.log('\n检测到微信专辑 URL,将批量下载...');
95
+ const result = await fetchAlbum({
96
+ url,
97
+ outputDir,
98
+ format: formats,
99
+ maxArticles,
100
+ timeoutMs: 30000,
101
+ downloadImages: false,
102
+ });
103
+ if (result.ok) {
104
+ console.log(`\n✅ 批量下载完成! 成功: ${result.articles.length} 篇`);
105
+ }
106
+ else {
107
+ console.error(`\n❌ ${result.error.message}`);
108
+ process.exit(1);
109
+ }
110
+ }
111
+ else {
112
+ const result = await fetchArticle({ url, outputDir, format: formats, timeoutMs: 30000, downloadImages: false });
113
+ if (result.ok) {
114
+ if ('article' in result) {
115
+ console.log('\n✅ 成功获取文章!');
116
+ printArticleInfo(result.article);
117
+ }
118
+ else {
119
+ console.log('\n✅ 批量下载完成!');
120
+ console.log(` 成功: ${result.articles.length} 篇`);
121
+ if (result.failedCount)
122
+ console.log(` 失败: ${result.failedCount} 篇`);
123
+ }
124
+ if (result.artifacts.length > 0) {
125
+ console.log(' 产物:');
126
+ for (const a of result.artifacts) {
127
+ console.log(` - [${a.type}] ${a.path}`);
128
+ }
129
+ }
130
+ }
131
+ else {
132
+ console.error(`\n❌ 获取失败: ${result.error.message}`);
133
+ process.exit(1);
134
+ }
135
+ }
136
+ }
137
+ // ── album-only 处理 ──────────────────────────────────────
138
+ async function handleAlbumOnly(url, options) {
139
+ const maxArticles = parseInt(options.max, 10);
140
+ const timeoutMs = parseInt(options.timeout, 10);
141
+ console.log('仅获取专辑文章列表...');
142
+ // 使用 fetchAlbumList —— 仅 API 列表,不下载文章(与 Python 一致)
143
+ const result = await fetchAlbumList({ url, maxArticles, timeoutMs });
144
+ if (result.ok) {
145
+ console.log(`\n获取到 ${result.articles.length} 篇文章:`);
146
+ for (let i = 0; i < result.articles.length; i++) {
147
+ const a = result.articles[i];
148
+ console.log(`${String(i + 1).padStart(2)}. ${a.title}`);
149
+ console.log(` URL: ${a.url}`);
150
+ if (a.createTime)
151
+ console.log(` 时间: ${a.createTime}`);
152
+ if (i < result.articles.length - 1)
153
+ console.log();
154
+ }
155
+ // 保存文章列表到 JSON(包含完整元数据,与 Python 一致)
156
+ await ensureDir(options.output);
157
+ const listFile = join(options.output, `album_articles_${Math.floor(Date.now() / 1000)}.json`);
158
+ await writeJsonSafe(listFile, result.articles);
159
+ console.log(`\n文章列表已保存到: ${listFile}`);
160
+ }
161
+ else {
162
+ console.error(`\n❌ 获取失败: ${result.error.message}`);
163
+ process.exit(1);
164
+ }
165
+ }
166
+ // ── album 批量下载处理 ────────────────────────────────────
167
+ async function handleAlbumDownload(url, options, formats) {
168
+ const maxArticles = parseInt(options.max, 10);
169
+ const timeoutMs = parseInt(options.timeout, 10);
170
+ console.log('检测到微信专辑 URL,将批量下载...');
171
+ const result = await fetchAlbum({
172
+ url,
173
+ outputDir: options.output,
174
+ format: formats,
175
+ maxArticles,
176
+ timeoutMs,
177
+ downloadImages: options.downloadImages,
178
+ });
179
+ if (result.ok) {
180
+ const failed = result.failedCount ?? 0;
181
+ console.log(`\n✅ 批量下载完成!`);
182
+ console.log(` 成功: ${result.articles.length} 篇`);
183
+ if (failed > 0)
184
+ console.log(` 失败: ${failed} 篇`);
185
+ console.log(` 文章保存在: ${options.output}`);
186
+ if (result.summaryFile) {
187
+ console.log(` 汇总: ${result.summaryFile}`);
188
+ }
189
+ }
190
+ else {
191
+ console.error(`\n❌ 下载失败: ${result.error.message}`);
192
+ process.exit(1);
193
+ }
194
+ }
195
+ // ── CLI 程序 ──────────────────────────────────────────────
196
+ const program = new Command();
197
+ program
198
+ .name('wespy')
199
+ .description('文章抓取与 Markdown 转换工具')
200
+ .version(pkg.version);
201
+ // ── fetch-article 子命令 ──────────────────────────────────
202
+ addCommonOptions(program
203
+ .command('fetch-article')
204
+ .description('获取单篇文章')
205
+ .argument('<url>', '文章 URL')).action(async (url, options) => {
206
+ const formats = resolveFormats(options);
207
+ const timeoutMs = parseInt(options.timeout, 10);
208
+ if (options.verbose) {
209
+ printVerbose(options, url);
210
+ }
211
+ const result = await fetchArticle({
212
+ url,
213
+ outputDir: options.output,
214
+ format: formats,
215
+ timeoutMs,
216
+ downloadImages: options.downloadImages,
217
+ });
218
+ if (result.ok) {
219
+ if ('article' in result) {
220
+ console.log('\n✅ 成功获取文章!');
221
+ printArticleInfo(result.article);
222
+ }
223
+ else {
224
+ console.log('\n✅ 批量下载完成!');
225
+ console.log(` 成功: ${result.articles.length} 篇`);
226
+ if (result.failedCount)
227
+ console.log(` 失败: ${result.failedCount} 篇`);
228
+ }
229
+ if (result.artifacts.length > 0) {
230
+ console.log(' 产物:');
231
+ for (const a of result.artifacts) {
232
+ console.log(` - [${a.type}] ${a.path}`);
233
+ }
234
+ }
235
+ }
236
+ else {
237
+ console.error(`\n❌ 获取失败: ${result.error.message}`);
238
+ console.error(` 错误码: ${result.error.code}`);
239
+ process.exit(1);
240
+ }
241
+ });
242
+ // ── fetch-album 子命令 ────────────────────────────────────
243
+ addCommonOptions(program
244
+ .command('fetch-album')
245
+ .description('批量获取微信专辑文章')
246
+ .argument('<url>', '专辑 URL')
247
+ .option('--max <n>', '最大下载文章数', '10')
248
+ .option('--album-only', '仅获取文章列表,不下载内容')).action(async (url, options) => {
249
+ if (options.verbose) {
250
+ printVerbose(options, url, {
251
+ 最大文章数量: parseInt(options.max, 10),
252
+ 仅获取列表: options.albumOnly ?? false,
253
+ });
254
+ }
255
+ if (options.albumOnly) {
256
+ await handleAlbumOnly(url, options);
257
+ return;
258
+ }
259
+ const formats = resolveFormats(options);
260
+ await handleAlbumDownload(url, options, formats);
261
+ });
262
+ // ── 默认命令(智能判断 URL 类型)──────────────────────────
263
+ addCommonOptions(program
264
+ .argument('[url]', '文章或专辑 URL')
265
+ .option('--max <n>', '专辑最大下载文章数', '10')
266
+ .option('--album-only', '仅获取文章列表,不下载内容')).action(async (url, options) => {
267
+ // 无 URL → 交互模式
268
+ if (!url) {
269
+ await runInteractiveMode();
270
+ return;
271
+ }
272
+ const formats = resolveFormats(options);
273
+ const timeoutMs = parseInt(options.timeout, 10);
274
+ const maxArticles = parseInt(options.max, 10);
275
+ if (options.verbose) {
276
+ printVerbose(options, url, { 最大文章数量: maxArticles, 仅获取列表: options.albumOnly ?? false });
277
+ }
278
+ if (isWechatAlbumUrl(url)) {
279
+ if (options.albumOnly) {
280
+ await handleAlbumOnly(url, options);
281
+ return;
282
+ }
283
+ await handleAlbumDownload(url, options, formats);
284
+ }
285
+ else {
286
+ const result = await fetchArticle({
287
+ url,
288
+ outputDir: options.output,
289
+ format: formats,
290
+ timeoutMs,
291
+ downloadImages: options.downloadImages,
292
+ });
293
+ if (result.ok) {
294
+ if ('article' in result) {
295
+ console.log('\n✅ 成功获取文章!');
296
+ printArticleInfo(result.article);
297
+ }
298
+ else {
299
+ console.log('\n✅ 批量下载完成!');
300
+ console.log(` 成功: ${result.articles.length} 篇`);
301
+ if (result.failedCount)
302
+ console.log(` 失败: ${result.failedCount} 篇`);
303
+ }
304
+ }
305
+ else {
306
+ console.error(`\n❌ ${result.error.message}`);
307
+ process.exit(1);
308
+ }
309
+ }
310
+ });
311
+ program.parse();
312
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * HTML → Markdown 转换器
3
+ * 基于 cheerio 解析 + 递归遍历,忠实还原 Python 版行为
4
+ */
5
+ /**
6
+ * 将 HTML 字符串转换为 Markdown
7
+ */
8
+ export declare function htmlToMarkdown(html: string): string;
9
+ //# sourceMappingURL=html-to-markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-to-markdown.d.ts","sourceRoot":"","sources":["../../src/converter/html-to-markdown.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * HTML → Markdown 转换器
3
+ * 基于 cheerio 解析 + 递归遍历,忠实还原 Python 版行为
4
+ */
5
+ import * as cheerio from 'cheerio';
6
+ import { buildProxyImageUrl } from '../utils/url.js';
7
+ import { detectCodeLanguage, cleanCodeContent } from '../utils/text.js';
8
+ /**
9
+ * 将 HTML 字符串转换为 Markdown
10
+ */
11
+ export function htmlToMarkdown(html) {
12
+ if (!html)
13
+ return '';
14
+ const $ = cheerio.load(html, { xmlMode: false });
15
+ return convertNode($, $.root());
16
+ }
17
+ function convertNode($, node) {
18
+ let md = '';
19
+ node.contents().each((_, child) => {
20
+ if (child.type === 'text') {
21
+ const text = $(child).text().trim();
22
+ if (text)
23
+ md += text;
24
+ return;
25
+ }
26
+ if (child.type !== 'tag')
27
+ return;
28
+ const el = $(child);
29
+ const tag = child.tagName?.toLowerCase();
30
+ switch (tag) {
31
+ case 'br':
32
+ md += '\n';
33
+ break;
34
+ case 'p':
35
+ case 'div':
36
+ case 'section': {
37
+ const content = convertNode($, el).trim();
38
+ if (content)
39
+ md += '\n\n' + content + '\n';
40
+ break;
41
+ }
42
+ case 'h1':
43
+ case 'h2':
44
+ case 'h3':
45
+ case 'h4':
46
+ case 'h5':
47
+ case 'h6': {
48
+ const level = parseInt(tag[1], 10);
49
+ const content = convertNode($, el).trim();
50
+ if (content)
51
+ md += '\n' + '#'.repeat(level) + ' ' + content + '\n';
52
+ break;
53
+ }
54
+ case 'strong':
55
+ case 'b': {
56
+ const content = convertNode($, el).trim();
57
+ if (content)
58
+ md += '**' + content + '**';
59
+ break;
60
+ }
61
+ case 'em':
62
+ case 'i': {
63
+ const content = convertNode($, el).trim();
64
+ if (content)
65
+ md += '*' + content + '*';
66
+ break;
67
+ }
68
+ case 'img': {
69
+ const src = el.attr('data-src') ?? el.attr('src') ?? '';
70
+ const alt = el.attr('alt') ?? '';
71
+ if (src) {
72
+ const proxySrc = buildProxyImageUrl(src);
73
+ md += `\n![${alt}](${proxySrc})\n`;
74
+ }
75
+ break;
76
+ }
77
+ case 'a': {
78
+ const href = el.attr('href') ?? '';
79
+ const text = convertNode($, el).trim();
80
+ if (href && text) {
81
+ md += `[${text}](${href})`;
82
+ }
83
+ else if (text) {
84
+ md += text;
85
+ }
86
+ break;
87
+ }
88
+ case 'ul':
89
+ case 'ol': {
90
+ const listContent = convertList($, el, tag === 'ol');
91
+ if (listContent)
92
+ md += '\n' + listContent + '\n';
93
+ break;
94
+ }
95
+ case 'code': {
96
+ // 行内 code(如果父元素是 pre,由 pre 处理)
97
+ const parent = child.parent;
98
+ if (parent && parent.type === 'tag' && parent.tagName?.toLowerCase() === 'pre')
99
+ break;
100
+ const codeContent = el.text().trim();
101
+ if (codeContent)
102
+ md += '`' + codeContent + '`';
103
+ break;
104
+ }
105
+ case 'pre': {
106
+ const codeContent = extractCodeFromPre(el);
107
+ const language = detectPreLanguage($, el);
108
+ if (codeContent) {
109
+ if (language) {
110
+ md += `\n\`\`\`${language}\n${codeContent}\n\`\`\`\n`;
111
+ }
112
+ else {
113
+ md += `\n\`\`\`\n${codeContent}\n\`\`\`\n`;
114
+ }
115
+ }
116
+ break;
117
+ }
118
+ default: {
119
+ md += convertNode($, el);
120
+ }
121
+ }
122
+ });
123
+ return md;
124
+ }
125
+ function convertList($, listEl, ordered) {
126
+ let md = '';
127
+ let index = 1;
128
+ listEl.children('li').each((_, item) => {
129
+ const content = convertNode($, $(item)).trim();
130
+ if (content) {
131
+ if (ordered) {
132
+ md += `${index}. ${content}\n`;
133
+ index++;
134
+ }
135
+ else {
136
+ md += `- ${content}\n`;
137
+ }
138
+ }
139
+ });
140
+ return md;
141
+ }
142
+ function extractCodeFromPre(preEl) {
143
+ const codeEl = preEl.find('code');
144
+ const raw = codeEl.length > 0 ? codeEl.text() : preEl.text();
145
+ return cleanCodeContent(raw);
146
+ }
147
+ function detectPreLanguage(_$, preEl) {
148
+ // 从 pre 的 class 检测
149
+ const preClasses = (preEl.attr('class') ?? '').split(/\s+/).filter(Boolean);
150
+ let lang = detectCodeLanguage(preClasses);
151
+ if (lang)
152
+ return lang;
153
+ // 从内部 code 的 class 检测
154
+ const codeEl = preEl.find('code');
155
+ if (codeEl.length > 0) {
156
+ const codeClasses = (codeEl.attr('class') ?? '').split(/\s+/).filter(Boolean);
157
+ lang = detectCodeLanguage(codeClasses);
158
+ if (lang)
159
+ return lang;
160
+ // data-language 属性
161
+ const dataLang = codeEl.attr('data-language') ?? codeEl.attr('lang');
162
+ if (dataLang)
163
+ return dataLang.toLowerCase();
164
+ }
165
+ // pre 的 data-language 属性
166
+ const preDataLang = preEl.attr('data-language') ?? preEl.attr('lang');
167
+ if (preDataLang)
168
+ return preDataLang.toLowerCase();
169
+ return null;
170
+ }
171
+ //# sourceMappingURL=html-to-markdown.js.map