multi-publisher 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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +370 -0
  3. package/dist/adapters/baijiahao.d.ts +14 -0
  4. package/dist/adapters/baijiahao.js +77 -0
  5. package/dist/adapters/base-adapter.d.ts +41 -0
  6. package/dist/adapters/base-adapter.js +84 -0
  7. package/dist/adapters/bilibili.d.ts +14 -0
  8. package/dist/adapters/bilibili.js +86 -0
  9. package/dist/adapters/cnblogs.d.ts +14 -0
  10. package/dist/adapters/cnblogs.js +90 -0
  11. package/dist/adapters/csdn.d.ts +29 -0
  12. package/dist/adapters/csdn.js +174 -0
  13. package/dist/adapters/cto51.d.ts +14 -0
  14. package/dist/adapters/cto51.js +72 -0
  15. package/dist/adapters/douban.d.ts +14 -0
  16. package/dist/adapters/douban.js +49 -0
  17. package/dist/adapters/eastmoney.d.ts +14 -0
  18. package/dist/adapters/eastmoney.js +47 -0
  19. package/dist/adapters/imooc.d.ts +14 -0
  20. package/dist/adapters/imooc.js +78 -0
  21. package/dist/adapters/index.d.ts +26 -0
  22. package/dist/adapters/index.js +22 -0
  23. package/dist/adapters/interface.d.ts +44 -0
  24. package/dist/adapters/interface.js +1 -0
  25. package/dist/adapters/juejin.d.ts +23 -0
  26. package/dist/adapters/juejin.js +166 -0
  27. package/dist/adapters/oschina.d.ts +14 -0
  28. package/dist/adapters/oschina.js +77 -0
  29. package/dist/adapters/registry.d.ts +61 -0
  30. package/dist/adapters/registry.js +146 -0
  31. package/dist/adapters/segmentfault.d.ts +14 -0
  32. package/dist/adapters/segmentfault.js +73 -0
  33. package/dist/adapters/sohu.d.ts +14 -0
  34. package/dist/adapters/sohu.js +72 -0
  35. package/dist/adapters/toutiao.d.ts +9 -0
  36. package/dist/adapters/toutiao.js +199 -0
  37. package/dist/adapters/wechat-publisher.d.ts +55 -0
  38. package/dist/adapters/wechat-publisher.js +210 -0
  39. package/dist/adapters/weibo.d.ts +14 -0
  40. package/dist/adapters/weibo.js +99 -0
  41. package/dist/adapters/weixin.d.ts +14 -0
  42. package/dist/adapters/weixin.js +63 -0
  43. package/dist/adapters/woshipm.d.ts +14 -0
  44. package/dist/adapters/woshipm.js +72 -0
  45. package/dist/adapters/xiaohongshu.d.ts +9 -0
  46. package/dist/adapters/xiaohongshu.js +199 -0
  47. package/dist/adapters/xueqiu.d.ts +14 -0
  48. package/dist/adapters/xueqiu.js +74 -0
  49. package/dist/adapters/yuque.d.ts +13 -0
  50. package/dist/adapters/yuque.js +25 -0
  51. package/dist/adapters/zhihu.d.ts +23 -0
  52. package/dist/adapters/zhihu.js +142 -0
  53. package/dist/cli/cookie.d.ts +5 -0
  54. package/dist/cli/cookie.js +80 -0
  55. package/dist/cli/credential.d.ts +6 -0
  56. package/dist/cli/credential.js +32 -0
  57. package/dist/cli/index.d.ts +5 -0
  58. package/dist/cli/index.js +72 -0
  59. package/dist/cli/login.d.ts +3 -0
  60. package/dist/cli/login.js +101 -0
  61. package/dist/cli/platforms.d.ts +1 -0
  62. package/dist/cli/platforms.js +35 -0
  63. package/dist/cli/publish-all.d.ts +6 -0
  64. package/dist/cli/publish-all.js +101 -0
  65. package/dist/cli/publish.d.ts +7 -0
  66. package/dist/cli/publish.js +78 -0
  67. package/dist/cli/render.d.ts +9 -0
  68. package/dist/cli/render.js +40 -0
  69. package/dist/commands/login.d.ts +43 -0
  70. package/dist/commands/login.js +248 -0
  71. package/dist/commands/publish.d.ts +19 -0
  72. package/dist/commands/publish.js +275 -0
  73. package/dist/config.d.ts +177 -0
  74. package/dist/config.js +333 -0
  75. package/dist/core/mathjax.d.ts +4 -0
  76. package/dist/core/mathjax.js +33 -0
  77. package/dist/core/parser.d.ts +17 -0
  78. package/dist/core/parser.js +42 -0
  79. package/dist/core/renderer.d.ts +18 -0
  80. package/dist/core/renderer.js +30 -0
  81. package/dist/core/styler.d.ts +8 -0
  82. package/dist/core/styler.js +11 -0
  83. package/dist/core/theme.d.ts +9 -0
  84. package/dist/core/theme.js +420 -0
  85. package/dist/index.d.ts +8 -0
  86. package/dist/index.js +7 -0
  87. package/dist/runtime/browser-runtime.d.ts +79 -0
  88. package/dist/runtime/browser-runtime.js +501 -0
  89. package/dist/runtime/index.d.ts +12 -0
  90. package/dist/runtime/index.js +1 -0
  91. package/dist/runtime/node-runtime.d.ts +5 -0
  92. package/dist/runtime/node-runtime.js +25 -0
  93. package/dist/tools/xiaohongshu-sniffer.d.ts +1 -0
  94. package/dist/tools/xiaohongshu-sniffer.js +54 -0
  95. package/package.json +62 -0
  96. package/themes/all-themes-preview.html +522 -0
  97. package/themes/previews/cyberpunk.png +0 -0
  98. package/themes/previews/minimal.png +0 -0
  99. package/themes/previews/nord.png +0 -0
  100. package/themes/previews/wechat.png +0 -0
@@ -0,0 +1,501 @@
1
+ /**
2
+ * Playwright 浏览器运行时
3
+ * 用于自动登录获取 Cookie
4
+ */
5
+ import { chromium } from 'playwright';
6
+ /**
7
+ * 浏览器运行时管理器
8
+ */
9
+ export class BrowserRuntime {
10
+ browser = null;
11
+ page = null;
12
+ /**
13
+ * 打开浏览器并导航到登录页面
14
+ * 使用用户默认浏览器配置文件,保持登录状态
15
+ */
16
+ async open(loginConfig) {
17
+ console.log(`正在启动浏览器...`);
18
+ this.browser = await chromium.launch({
19
+ headless: false, // 需要可见浏览器让用户登录
20
+ channel: 'chromium', // 使用用户的默认 Chrome 配置
21
+ });
22
+ // 不创建新context,让Playwright使用默认context,保持已登录状态
23
+ this.page = await this.browser.newPage();
24
+ // 注意:不清理 cookies,使用用户已有的登录状态
25
+ console.log(`打开登录页面: ${loginConfig.loginUrl}`);
26
+ await this.page.goto(loginConfig.loginUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
27
+ }
28
+ /**
29
+ * 等待用户完成登录
30
+ */
31
+ async waitForLogin(config) {
32
+ if (!this.page) {
33
+ return { success: false, cookies: {}, error: '浏览器未启动' };
34
+ }
35
+ const { successCondition, extraCookieDomains = [] } = config;
36
+ const allDomains = [new URL(config.loginUrl).hostname, ...extraCookieDomains];
37
+ try {
38
+ // 先检查是否已经登录(cookies已存在)
39
+ if (successCondition.cookieName) {
40
+ const existingCookies = await this.page.context().cookies();
41
+ const hasCookie = existingCookies.some(c => c.name === successCondition.cookieName);
42
+ if (hasCookie) {
43
+ console.log(`检测到已登录(Cookie ${successCondition.cookieName} 已存在)`);
44
+ }
45
+ }
46
+ if (successCondition.waitMs) {
47
+ // 方式1:等待特定 Cookie 出现,最多等待 waitMs 时间
48
+ console.log(`等待 ${successCondition.waitMs / 1000} 秒让用户完成登录...`);
49
+ await this.waitForCookieOrTimeout(successCondition.cookieName, successCondition.waitMs);
50
+ }
51
+ else if (successCondition.selector) {
52
+ // 方式2:等待 DOM 元素出现
53
+ console.log(`等待元素: ${successCondition.selector}`);
54
+ await this.page.waitForSelector(successCondition.selector, { timeout: 0 });
55
+ }
56
+ else if (successCondition.urlPattern) {
57
+ // 方式3:等待 URL 变化
58
+ const currentUrl = this.page.url();
59
+ console.log(`等待登录完成,当前 URL: ${currentUrl}`);
60
+ await this.page.waitForURL(successCondition.urlPattern, { timeout: 0 });
61
+ }
62
+ else if (successCondition.cookieName) {
63
+ // 方式4:等待特定 Cookie 出现(无限等待)
64
+ console.log(`等待 Cookie: ${successCondition.cookieName}`);
65
+ await this.waitForCookie(successCondition.cookieName);
66
+ }
67
+ else {
68
+ // 默认:等待页面稳定
69
+ console.log(`等待页面稳定...`);
70
+ await this.page.waitForLoadState('networkidle');
71
+ }
72
+ // 获取所有相关域名的 cookies
73
+ // 先获取整个 context 的所有 cookies(不过滤域名),因为 cookie 可能被设置在各种子域上
74
+ const allCookies = await this.page.context().cookies();
75
+ console.log(`[调试] 共获取到 ${allCookies.length} 个 cookies(不过滤域名)`);
76
+ for (const c of allCookies) {
77
+ console.log(` - ${c.name}=${c.value.substring(0, 20)}... (domain=${c.domain}, path=${c.path})`);
78
+ }
79
+ // 过滤出目标域名的 cookies
80
+ const cookies = {};
81
+ for (const cookie of allCookies) {
82
+ // 匹配主域名和其子域名
83
+ const cookieDomain = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain;
84
+ const matchesMainDomain = allDomains.some(d => {
85
+ const main = d.startsWith('.') ? d.slice(1) : d;
86
+ return cookieDomain === main || cookieDomain.endsWith('.' + main);
87
+ });
88
+ if (matchesMainDomain || cookieDomain.includes('zhihu') || cookieDomain.includes('juejin')) {
89
+ cookies[cookie.name] = cookie.value;
90
+ }
91
+ }
92
+ console.log(`[调试] 过滤后剩余 ${Object.keys(cookies).length} 个 cookies`);
93
+ // 检查是否获取到了有效的 cookies
94
+ const hasCookies = Object.keys(cookies).length > 0;
95
+ const expectedCookieFound = successCondition.cookieName ? !!cookies[successCondition.cookieName] : hasCookies;
96
+ if (!hasCookies || !expectedCookieFound) {
97
+ return {
98
+ success: false,
99
+ cookies,
100
+ error: hasCookies ? `未找到预期的登录 Cookie (${successCondition.cookieName})` : '未获取到任何 Cookie'
101
+ };
102
+ }
103
+ return { success: true, cookies };
104
+ }
105
+ catch (err) {
106
+ return { success: false, cookies: {}, error: err.message };
107
+ }
108
+ }
109
+ /**
110
+ * 等待特定 Cookie 出现
111
+ */
112
+ async waitForCookie(cookieName) {
113
+ if (!this.page)
114
+ return;
115
+ const checkCookie = async () => {
116
+ const cookies = await this.page.context().cookies();
117
+ return cookies.some(c => c.name === cookieName);
118
+ };
119
+ while (!(await checkCookie())) {
120
+ await this.page.waitForTimeout(1000);
121
+ console.log('.');
122
+ }
123
+ }
124
+ /**
125
+ * 等待特定 Cookie 出现,带超时
126
+ */
127
+ async waitForCookieOrTimeout(cookieName, timeoutMs) {
128
+ if (!this.page)
129
+ return false;
130
+ const startTime = Date.now();
131
+ const checkCookie = async () => {
132
+ const cookies = await this.page.context().cookies();
133
+ return cookies.some(c => c.name === cookieName);
134
+ };
135
+ while (!(await checkCookie())) {
136
+ if (Date.now() - startTime > timeoutMs) {
137
+ console.log(`\n等待超时(${timeoutMs / 1000} 秒),继续尝试获取 Cookie...`);
138
+ return false;
139
+ }
140
+ await this.page.waitForTimeout(1000);
141
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
142
+ console.log(`已等待 ${elapsed} 秒,等待登录完成...`);
143
+ }
144
+ return true;
145
+ }
146
+ /**
147
+ * 关闭浏览器
148
+ */
149
+ async close() {
150
+ if (this.browser) {
151
+ await this.browser.close();
152
+ this.browser = null;
153
+ this.page = null;
154
+ }
155
+ }
156
+ /**
157
+ * 获取当前页面(供外部使用)
158
+ */
159
+ getPage() {
160
+ return this.page;
161
+ }
162
+ /**
163
+ * 获取当前页面的 cookies
164
+ */
165
+ async getCookies(domains) {
166
+ if (!this.page)
167
+ return {};
168
+ const cookies = {};
169
+ const targets = domains || [new URL(this.page.url()).hostname];
170
+ for (const domain of targets) {
171
+ try {
172
+ const pageCookies = await this.page.context().cookies(domain);
173
+ for (const cookie of pageCookies) {
174
+ cookies[cookie.name] = cookie.value;
175
+ }
176
+ }
177
+ catch {
178
+ // 忽略无效域名
179
+ }
180
+ }
181
+ return cookies;
182
+ }
183
+ /**
184
+ * 检测是否已登录(通过检查是否存在登录 Cookie)
185
+ */
186
+ async checkLogin(config) {
187
+ if (!this.page)
188
+ return false;
189
+ const { cookieName } = config.successCondition;
190
+ if (!cookieName)
191
+ return false;
192
+ const cookies = await this.getCookies([new URL(config.loginUrl).hostname]);
193
+ return !!cookies[cookieName];
194
+ }
195
+ }
196
+ /**
197
+ * 预配置的平台登录信息
198
+ */
199
+ export const PLATFORM_LOGIN_CONFIGS = {
200
+ zhihu: {
201
+ id: 'zhihu',
202
+ name: '知乎',
203
+ loginUrl: 'https://www.zhihu.com/',
204
+ successCondition: {
205
+ cookieName: 'z_c0',
206
+ waitMs: 60000, // 等待 60 秒让用户完成操作(扫码等)
207
+ },
208
+ extraCookieDomains: ['.zhihu.com', 'api.zhihu.com'],
209
+ },
210
+ juejin: {
211
+ id: 'juejin',
212
+ name: '掘金',
213
+ loginUrl: 'https://juejin.cn/',
214
+ successCondition: {
215
+ cookieName: 'uid_tt',
216
+ waitMs: 60000,
217
+ },
218
+ extraCookieDomains: ['.juejin.cn', 'api.juejin.cn'],
219
+ },
220
+ csdn: {
221
+ id: 'csdn',
222
+ name: 'CSDN',
223
+ loginUrl: 'https://www.csdn.net/',
224
+ successCondition: {
225
+ cookieName: 'UserName',
226
+ waitMs: 60000,
227
+ },
228
+ extraCookieDomains: ['.csdn.net', 'bizapi.csdn.net'],
229
+ },
230
+ jianshu: {
231
+ id: 'jianshu',
232
+ name: '简书',
233
+ loginUrl: 'https://www.jianshu.com/',
234
+ successCondition: {
235
+ cookieName: 'remember_user_token',
236
+ waitMs: 60000,
237
+ },
238
+ extraCookieDomains: ['.jianshu.com'],
239
+ },
240
+ weibo: {
241
+ id: 'weibo',
242
+ name: '微博',
243
+ loginUrl: 'https://weibo.com/',
244
+ successCondition: {
245
+ cookieName: 'SUB',
246
+ waitMs: 60000,
247
+ },
248
+ extraCookieDomains: ['.weibo.com', '.sina.com.cn'],
249
+ },
250
+ xiaohongshu: {
251
+ id: 'xiaohongshu',
252
+ name: '小红书',
253
+ loginUrl: 'https://creator.xiaohongshu.com/',
254
+ successCondition: {
255
+ cookieName: 'galaxy_creator_session_id',
256
+ waitMs: 60000,
257
+ },
258
+ extraCookieDomains: ['.xiaohongshu.com', '.edith.xiaohongshu.com'],
259
+ },
260
+ toutiao: {
261
+ id: 'toutiao',
262
+ name: '头条号',
263
+ loginUrl: 'https://mp.toutiao.com/',
264
+ successCondition: {
265
+ cookieName: 'sessionid',
266
+ waitMs: 60000,
267
+ },
268
+ extraCookieDomains: ['.toutiao.com', '.douyin.com'],
269
+ },
270
+ baijiahao: {
271
+ id: 'baijiahao',
272
+ name: '百家号',
273
+ loginUrl: 'https://baijiahao.baidu.com/',
274
+ successCondition: {
275
+ cookieName: 'BDUSS',
276
+ waitMs: 60000,
277
+ },
278
+ extraCookieDomains: ['.baidu.com', '.hao123.com'],
279
+ },
280
+ bilibili: {
281
+ id: 'bilibili',
282
+ name: 'B站',
283
+ loginUrl: 'https://www.bilibili.com/',
284
+ successCondition: {
285
+ cookieName: 'SESSDATA',
286
+ waitMs: 60000,
287
+ },
288
+ extraCookieDomains: ['.bilibili.com', 'api.bilibili.com'],
289
+ },
290
+ segmentfault: {
291
+ id: 'segmentfault',
292
+ name: '思否',
293
+ loginUrl: 'https://segmentfault.com/',
294
+ successCondition: {
295
+ cookieName: 'sa_user',
296
+ waitMs: 60000,
297
+ },
298
+ extraCookieDomains: ['.segmentfault.com'],
299
+ },
300
+ cnblogs: {
301
+ id: 'cnblogs',
302
+ name: '博客园',
303
+ loginUrl: 'https://www.cnblogs.com/',
304
+ successCondition: {
305
+ cookieName: '.CNBlogsCookie',
306
+ waitMs: 60000,
307
+ },
308
+ extraCookieDomains: ['.cnblogs.com'],
309
+ },
310
+ oschina: {
311
+ id: 'oschina',
312
+ name: '开源中国',
313
+ loginUrl: 'https://www.oschina.net/',
314
+ successCondition: {
315
+ cookieName: 'oscid',
316
+ waitMs: 60000,
317
+ },
318
+ extraCookieDomains: ['.oschina.net'],
319
+ },
320
+ imooc: {
321
+ id: 'imooc',
322
+ name: '慕课网',
323
+ loginUrl: 'https://www.imooc.com/',
324
+ successCondition: {
325
+ cookieName: 'imooc_uuid',
326
+ waitMs: 60000,
327
+ },
328
+ extraCookieDomains: ['.imooc.com'],
329
+ },
330
+ xueqiu: {
331
+ id: 'xueqiu',
332
+ name: '雪球',
333
+ loginUrl: 'https://xueqiu.com/',
334
+ successCondition: {
335
+ cookieName: 'xq_a_token',
336
+ waitMs: 60000,
337
+ },
338
+ extraCookieDomains: ['.xueqiu.com'],
339
+ },
340
+ woshipm: {
341
+ id: 'woshipm',
342
+ name: '人人都是产品经理',
343
+ loginUrl: 'https://www.woshipm.com/',
344
+ successCondition: {
345
+ cookieName: 'woshipm_user',
346
+ waitMs: 60000,
347
+ },
348
+ extraCookieDomains: ['.woshipm.com'],
349
+ },
350
+ douban: {
351
+ id: 'douban',
352
+ name: '豆瓣',
353
+ loginUrl: 'https://www.douban.com/',
354
+ successCondition: {
355
+ cookieName: 'ck',
356
+ waitMs: 60000,
357
+ },
358
+ extraCookieDomains: ['.douban.com', '.douban.fm'],
359
+ },
360
+ sohu: {
361
+ id: 'sohu',
362
+ name: '搜狐号',
363
+ loginUrl: 'https://mp.sohu.com/',
364
+ successCondition: {
365
+ cookieName: 'SUV',
366
+ waitMs: 60000,
367
+ },
368
+ extraCookieDomains: ['.sohu.com'],
369
+ },
370
+ eastmoney: {
371
+ id: 'eastmoney',
372
+ name: '东方财富',
373
+ loginUrl: 'https://www.eastmoney.com/',
374
+ successCondition: {
375
+ cookieName: 'qgqp_b_id',
376
+ waitMs: 60000,
377
+ },
378
+ extraCookieDomains: ['.eastmoney.com'],
379
+ },
380
+ yuque: {
381
+ id: 'yuque',
382
+ name: '语雀',
383
+ loginUrl: 'https://www.yuque.com/',
384
+ successCondition: {
385
+ cookieName: 'yuque_token',
386
+ waitMs: 60000,
387
+ },
388
+ extraCookieDomains: ['.yuque.com'],
389
+ },
390
+ cto51: {
391
+ id: 'cto51',
392
+ name: '51CTO',
393
+ loginUrl: 'https://blog.51cto.com/',
394
+ successCondition: {
395
+ cookieName: 'app_id',
396
+ waitMs: 60000,
397
+ },
398
+ extraCookieDomains: ['.51cto.com', '.cto51.com'],
399
+ },
400
+ };
401
+ /**
402
+ * 执行平台登录
403
+ */
404
+ export async function loginPlatform(platformId) {
405
+ const config = PLATFORM_LOGIN_CONFIGS[platformId];
406
+ if (!config) {
407
+ return { success: false, cookies: {}, error: `不支持的平台: ${platformId}` };
408
+ }
409
+ // 先检查配置中是否已有保存的 cookies(从之前登录获取)
410
+ const savedCookies = loadSavedCookies(platformId);
411
+ if (savedCookies && Object.keys(savedCookies).length > 0) {
412
+ // 验证 cookies 是否仍然有效
413
+ console.log(`检测到已保存的 ${Object.keys(savedCookies).length} 个 cookies,正在验证...`);
414
+ const runtime = new BrowserRuntime();
415
+ try {
416
+ await runtime.open(config);
417
+ // 设置已有 cookies 到浏览器上下文
418
+ const cookies = Object.entries(savedCookies).map(([name, value]) => ({
419
+ name,
420
+ value,
421
+ domain: new URL(config.loginUrl).hostname,
422
+ path: '/',
423
+ }));
424
+ await runtime.getPage()?.context().addCookies(cookies);
425
+ // 检查是否仍然有效
426
+ const isLoggedIn = await runtime.checkLogin(config);
427
+ if (isLoggedIn) {
428
+ console.log(`✅ ${config.name} 已经登录有效!`);
429
+ await runtime.close();
430
+ return { success: true, cookies: savedCookies };
431
+ }
432
+ else {
433
+ console.log(`⚠️ 已保存的 cookies 已过期,需要重新登录...`);
434
+ await runtime.close();
435
+ }
436
+ }
437
+ catch {
438
+ await runtime.close();
439
+ }
440
+ }
441
+ // 没有有效 cookies,需要用户登录
442
+ const runtime = new BrowserRuntime();
443
+ try {
444
+ // 打开浏览器
445
+ await runtime.open(config);
446
+ console.log(`
447
+ ========================================
448
+ 请在打开的浏览器中完成登录操作!
449
+
450
+ 平台: ${config.name}
451
+ 提示: ${config.successCondition.waitMs ? `等待 ${config.successCondition.waitMs / 1000} 秒后超时(足够完成扫码登录)` : '等待登录成功后自动完成'}
452
+ 提示: 如果已经是登录状态,请手动刷新页面
453
+ ========================================
454
+ `);
455
+ // 等待登录完成
456
+ const result = await runtime.waitForLogin(config);
457
+ if (result.success) {
458
+ console.log(`✅ ${config.name} 登录成功!`);
459
+ }
460
+ else {
461
+ console.log(`❌ ${config.name} 登录失败: ${result.error}`);
462
+ }
463
+ return result;
464
+ }
465
+ finally {
466
+ await runtime.close();
467
+ }
468
+ }
469
+ /**
470
+ * 从配置文件加载保存的 cookies
471
+ */
472
+ function loadSavedCookies(platformId) {
473
+ try {
474
+ const { ConfigStore } = require('../config.js');
475
+ switch (platformId) {
476
+ case 'zhihu': return ConfigStore.getZhihuCookies();
477
+ case 'juejin': return ConfigStore.getJuejinCookies();
478
+ case 'csdn': return ConfigStore.getCSDNCookies();
479
+ case 'jianshu': return ConfigStore.getJianshuCookies();
480
+ case 'weibo': return ConfigStore.getWeiboCookies();
481
+ case 'xiaohongshu': return ConfigStore.getXiaohongshuCookies();
482
+ case 'toutiao': return ConfigStore.getToutiaoCookies();
483
+ case 'baijiahao': return ConfigStore.getBaijiahaoCookies();
484
+ case 'bilibili': return ConfigStore.getBilibiliCookies();
485
+ case 'segmentfault': return ConfigStore.getSegmentfaultCookies();
486
+ case 'cnblogs': return ConfigStore.getCnblogsCookies();
487
+ case 'oschina': return ConfigStore.getOschinaCookies();
488
+ case 'imooc': return ConfigStore.getImoocCookies();
489
+ case 'xueqiu': return ConfigStore.getYueqiuCookies();
490
+ case 'woshipm': return ConfigStore.getWoshipmCookies();
491
+ case 'douban': return ConfigStore.getDoubanCookies();
492
+ case 'sohu': return ConfigStore.getSohuCookies();
493
+ case 'eastmoney': return ConfigStore.getEastmoneyCookies();
494
+ case 'cto51': return ConfigStore.getCto51Cookies();
495
+ default: return null;
496
+ }
497
+ }
498
+ catch {
499
+ return null;
500
+ }
501
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Runtime Interface - 抽象 HTTP/fetch 能力,让适配器可测试
3
+ */
4
+ export interface RuntimeInterface {
5
+ fetch(url: string, init?: RequestInit): Promise<Response>;
6
+ readFile(path: string): Promise<string>;
7
+ readFileBuffer(path: string): Promise<Buffer>;
8
+ getConfigDir(): string;
9
+ }
10
+ export interface RuntimeOptions {
11
+ fetchImpl?: typeof fetch;
12
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Node.js 运行时实现
3
+ */
4
+ import type { RuntimeInterface } from './index.js';
5
+ export declare function createNodeRuntime(): RuntimeInterface;
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ let _configDir = null;
5
+ function getConfigDir() {
6
+ if (!_configDir) {
7
+ const env = process.env.XDG_CONFIG_HOME || process.env.APPDATA || os.homedir();
8
+ _configDir = path.join(env, '.config', 'multi-publisher');
9
+ }
10
+ return _configDir;
11
+ }
12
+ export function createNodeRuntime() {
13
+ return {
14
+ async fetch(url, init) {
15
+ return globalThis.fetch(url, init);
16
+ },
17
+ async readFile(filePath) {
18
+ return fs.readFile(filePath, 'utf-8');
19
+ },
20
+ async readFileBuffer(filePath) {
21
+ return fs.readFile(filePath);
22
+ },
23
+ getConfigDir,
24
+ };
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 小红书 抓包工具 - 只抓保存草稿 API
3
+ */
4
+ import { chromium } from 'playwright';
5
+ async function sniffXiaohongshu() {
6
+ const browser = await chromium.launch({ headless: false });
7
+ const context = await browser.newContext({
8
+ viewport: { width: 1280, height: 900 },
9
+ });
10
+ const page = await context.newPage();
11
+ // 监听所有小红书 API 请求
12
+ page.on('request', (req) => {
13
+ const url = req.url();
14
+ if (!url.includes('xiaohongshu.com'))
15
+ return;
16
+ const method = req.method();
17
+ const body = req.postData();
18
+ // 只打印 POST 请求(通常是数据提交)
19
+ if (method === 'POST' && body) {
20
+ console.log('\n========== POST 请求 ==========');
21
+ console.log(`URL: ${url}`);
22
+ console.log(`Body: ${body}`);
23
+ }
24
+ });
25
+ // 监听响应
26
+ page.on('response', (resp) => {
27
+ const url = resp.url();
28
+ if (!url.includes('xiaohongshu.com'))
29
+ return;
30
+ if (resp.status() >= 200 && resp.status() < 300) {
31
+ resp.text().then(body => {
32
+ if (body && body.length < 500) {
33
+ console.log(`响应 [${resp.status()}]: ${body}`);
34
+ }
35
+ }).catch(() => { });
36
+ }
37
+ });
38
+ console.log('========================================');
39
+ console.log('小红书 抓包工具 - 保存草稿');
40
+ console.log('========================================');
41
+ console.log('步骤:');
42
+ console.log('1. 打开 https://creator.xiaohongshu.com/');
43
+ console.log('2. 手动登录');
44
+ console.log('3. 点击"发布笔记" -> "图文"');
45
+ console.log('4. 填写标题和内容');
46
+ console.log('5. 点击"保存草稿"按钮');
47
+ console.log('6. 观察下方输出的 POST 请求');
48
+ console.log('========================================\n');
49
+ await page.goto('https://creator.xiaohongshu.com/', { waitUntil: 'networkidle' });
50
+ console.log('等待操作...\n');
51
+ // 等待手动停止
52
+ await new Promise(() => { });
53
+ }
54
+ sniffXiaohongshu().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "multi-publisher",
3
+ "version": "1.0.0",
4
+ "description": "Markdown → 微信公众号 / 知乎 / 掘金等多平台 CLI 发布工具",
5
+ "type": "module",
6
+ "bin": {
7
+ "mpub": "./dist/cli/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/cli/index.ts",
13
+ "typecheck": "tsc --noEmit",
14
+ "preview": "npx serve themes"
15
+ },
16
+ "keywords": [
17
+ "markdown",
18
+ "publish",
19
+ "wechat",
20
+ "zhihu",
21
+ "juejin",
22
+ "csdn",
23
+ "cli",
24
+ "multi-platform",
25
+ "content-publishing"
26
+ ],
27
+ "author": "",
28
+ "license": "Apache-2.0",
29
+ "files": [
30
+ "dist",
31
+ "themes"
32
+ ],
33
+ "dependencies": {
34
+ "@inquirer/input": "^5.0.0",
35
+ "@inquirer/password": "^5.0.0",
36
+ "axios": "^1.15.0",
37
+ "commander": "^14.0.0",
38
+ "form-data": "^4.0.5",
39
+ "form-data-encoder": "^4.0.0",
40
+ "formdata-node": "^6.0.0",
41
+ "front-matter": "^4.0.0",
42
+ "highlight.js": "^11.10.0",
43
+ "js-md5": "^0.8.3",
44
+ "jsdom": "^27.0.0",
45
+ "juice": "^10.0.0",
46
+ "marked": "^15.0.0",
47
+ "marked-highlight": "^2.2.0",
48
+ "mathjax-full": "^3.2.0",
49
+ "playwright": "^1.59.1",
50
+ "yaml": "^2.8.3"
51
+ },
52
+ "devDependencies": {
53
+ "@types/form-data": "^2.2.1",
54
+ "@types/jsdom": "^28.0.1",
55
+ "@types/node": "^22.0.0",
56
+ "tsx": "^4.21.0",
57
+ "typescript": "^5.7.0"
58
+ },
59
+ "engines": {
60
+ "node": ">=18.0.0"
61
+ }
62
+ }