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.
- package/LICENSE +21 -0
- package/README.md +370 -0
- package/dist/adapters/baijiahao.d.ts +14 -0
- package/dist/adapters/baijiahao.js +77 -0
- package/dist/adapters/base-adapter.d.ts +41 -0
- package/dist/adapters/base-adapter.js +84 -0
- package/dist/adapters/bilibili.d.ts +14 -0
- package/dist/adapters/bilibili.js +86 -0
- package/dist/adapters/cnblogs.d.ts +14 -0
- package/dist/adapters/cnblogs.js +90 -0
- package/dist/adapters/csdn.d.ts +29 -0
- package/dist/adapters/csdn.js +174 -0
- package/dist/adapters/cto51.d.ts +14 -0
- package/dist/adapters/cto51.js +72 -0
- package/dist/adapters/douban.d.ts +14 -0
- package/dist/adapters/douban.js +49 -0
- package/dist/adapters/eastmoney.d.ts +14 -0
- package/dist/adapters/eastmoney.js +47 -0
- package/dist/adapters/imooc.d.ts +14 -0
- package/dist/adapters/imooc.js +78 -0
- package/dist/adapters/index.d.ts +26 -0
- package/dist/adapters/index.js +22 -0
- package/dist/adapters/interface.d.ts +44 -0
- package/dist/adapters/interface.js +1 -0
- package/dist/adapters/juejin.d.ts +23 -0
- package/dist/adapters/juejin.js +166 -0
- package/dist/adapters/oschina.d.ts +14 -0
- package/dist/adapters/oschina.js +77 -0
- package/dist/adapters/registry.d.ts +61 -0
- package/dist/adapters/registry.js +146 -0
- package/dist/adapters/segmentfault.d.ts +14 -0
- package/dist/adapters/segmentfault.js +73 -0
- package/dist/adapters/sohu.d.ts +14 -0
- package/dist/adapters/sohu.js +72 -0
- package/dist/adapters/toutiao.d.ts +9 -0
- package/dist/adapters/toutiao.js +199 -0
- package/dist/adapters/wechat-publisher.d.ts +55 -0
- package/dist/adapters/wechat-publisher.js +210 -0
- package/dist/adapters/weibo.d.ts +14 -0
- package/dist/adapters/weibo.js +99 -0
- package/dist/adapters/weixin.d.ts +14 -0
- package/dist/adapters/weixin.js +63 -0
- package/dist/adapters/woshipm.d.ts +14 -0
- package/dist/adapters/woshipm.js +72 -0
- package/dist/adapters/xiaohongshu.d.ts +9 -0
- package/dist/adapters/xiaohongshu.js +199 -0
- package/dist/adapters/xueqiu.d.ts +14 -0
- package/dist/adapters/xueqiu.js +74 -0
- package/dist/adapters/yuque.d.ts +13 -0
- package/dist/adapters/yuque.js +25 -0
- package/dist/adapters/zhihu.d.ts +23 -0
- package/dist/adapters/zhihu.js +142 -0
- package/dist/cli/cookie.d.ts +5 -0
- package/dist/cli/cookie.js +80 -0
- package/dist/cli/credential.d.ts +6 -0
- package/dist/cli/credential.js +32 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +72 -0
- package/dist/cli/login.d.ts +3 -0
- package/dist/cli/login.js +101 -0
- package/dist/cli/platforms.d.ts +1 -0
- package/dist/cli/platforms.js +35 -0
- package/dist/cli/publish-all.d.ts +6 -0
- package/dist/cli/publish-all.js +101 -0
- package/dist/cli/publish.d.ts +7 -0
- package/dist/cli/publish.js +78 -0
- package/dist/cli/render.d.ts +9 -0
- package/dist/cli/render.js +40 -0
- package/dist/commands/login.d.ts +43 -0
- package/dist/commands/login.js +248 -0
- package/dist/commands/publish.d.ts +19 -0
- package/dist/commands/publish.js +275 -0
- package/dist/config.d.ts +177 -0
- package/dist/config.js +333 -0
- package/dist/core/mathjax.d.ts +4 -0
- package/dist/core/mathjax.js +33 -0
- package/dist/core/parser.d.ts +17 -0
- package/dist/core/parser.js +42 -0
- package/dist/core/renderer.d.ts +18 -0
- package/dist/core/renderer.js +30 -0
- package/dist/core/styler.d.ts +8 -0
- package/dist/core/styler.js +11 -0
- package/dist/core/theme.d.ts +9 -0
- package/dist/core/theme.js +420 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/runtime/browser-runtime.d.ts +79 -0
- package/dist/runtime/browser-runtime.js +501 -0
- package/dist/runtime/index.d.ts +12 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/node-runtime.d.ts +5 -0
- package/dist/runtime/node-runtime.js +25 -0
- package/dist/tools/xiaohongshu-sniffer.d.ts +1 -0
- package/dist/tools/xiaohongshu-sniffer.js +54 -0
- package/package.json +62 -0
- package/themes/all-themes-preview.html +522 -0
- package/themes/previews/cyberpunk.png +0 -0
- package/themes/previews/minimal.png +0 -0
- package/themes/previews/nord.png +0 -0
- package/themes/previews/wechat.png +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* publish 命令 - 渲染并发布文章到平台
|
|
3
|
+
*/
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { renderMarkdown } from '../core/renderer.js';
|
|
6
|
+
import { createNodeRuntime } from '../runtime/node-runtime.js';
|
|
7
|
+
import { initAdapterRegistry, getAdapter } from '../adapters/index.js';
|
|
8
|
+
export async function runPublish(options, input) {
|
|
9
|
+
try {
|
|
10
|
+
// 1. 读取内容
|
|
11
|
+
let content;
|
|
12
|
+
if (options.file) {
|
|
13
|
+
if (options.file.startsWith('http://') || options.file.startsWith('https://')) {
|
|
14
|
+
const res = await fetch(options.file);
|
|
15
|
+
if (!res.ok)
|
|
16
|
+
throw new Error(`无法读取 URL: ${options.file}`);
|
|
17
|
+
content = await res.text();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
content = await readFile(options.file, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (input) {
|
|
24
|
+
content = input;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new Error('请提供 -f 选项指定 Markdown 文件');
|
|
28
|
+
}
|
|
29
|
+
// 2. 渲染
|
|
30
|
+
const result = await renderMarkdown(content, {
|
|
31
|
+
theme: options.theme || 'default',
|
|
32
|
+
macStyle: options.macStyle !== false,
|
|
33
|
+
});
|
|
34
|
+
// 3. 选择适配器
|
|
35
|
+
const platformId = options.platform || 'weixin';
|
|
36
|
+
// 初始化适配器注册表
|
|
37
|
+
const runtime = createNodeRuntime();
|
|
38
|
+
await initAdapterRegistry(runtime);
|
|
39
|
+
const adapter = getAdapter(platformId);
|
|
40
|
+
if (!adapter) {
|
|
41
|
+
// 列出所有支持的平台
|
|
42
|
+
const { adapterRegistry } = await import('../adapters/index.js');
|
|
43
|
+
const allAdapters = Array.from(adapterRegistry.getAll().values());
|
|
44
|
+
const supportedPlatforms = allAdapters.map(a => a.meta.id).sort().join(', ');
|
|
45
|
+
throw new Error(`不支持的平台: ${platformId}。支持: ${supportedPlatforms}`);
|
|
46
|
+
}
|
|
47
|
+
// 注入 AppID 到运行时(如果提供了)
|
|
48
|
+
if (options.appId) {
|
|
49
|
+
process.env.WECHAT_APP_ID = options.appId;
|
|
50
|
+
}
|
|
51
|
+
const syncResult = await adapter.publish({
|
|
52
|
+
title: result.title,
|
|
53
|
+
markdown: content,
|
|
54
|
+
html: result.html,
|
|
55
|
+
author: result.author,
|
|
56
|
+
cover: result.cover,
|
|
57
|
+
source_url: result.source_url,
|
|
58
|
+
});
|
|
59
|
+
// 4. 输出结果
|
|
60
|
+
if (syncResult.success) {
|
|
61
|
+
console.log(`✅ 发布成功!`);
|
|
62
|
+
if (syncResult.postUrl) {
|
|
63
|
+
console.log(`📝 草稿链接: ${syncResult.postUrl}`);
|
|
64
|
+
}
|
|
65
|
+
if (syncResult.postId) {
|
|
66
|
+
console.log(`🆔 媒体 ID: ${syncResult.postId}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.error(`❌ 发布失败: ${syncResult.error}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error('[publish]', err.message);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render 命令 - 将 Markdown 渲染为 HTML
|
|
3
|
+
*/
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { renderMarkdown } from '../core/renderer.js';
|
|
6
|
+
export async function runRender(options) {
|
|
7
|
+
try {
|
|
8
|
+
let content;
|
|
9
|
+
if (options.file) {
|
|
10
|
+
if (options.file.startsWith('http://') || options.file.startsWith('https://')) {
|
|
11
|
+
const res = await fetch(options.file);
|
|
12
|
+
if (!res.ok)
|
|
13
|
+
throw new Error(`无法读取 URL: ${options.file}`);
|
|
14
|
+
content = await res.text();
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
content = await readFile(options.file, 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
content = await readFile('/dev/stdin', 'utf-8').catch(() => {
|
|
22
|
+
throw new Error('请提供 -f 选项指定文件');
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
let customCss;
|
|
26
|
+
if (options.customTheme) {
|
|
27
|
+
customCss = await readFile(options.customTheme, 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
const result = await renderMarkdown(content, {
|
|
30
|
+
theme: options.theme,
|
|
31
|
+
customCss,
|
|
32
|
+
macStyle: options.macStyle,
|
|
33
|
+
});
|
|
34
|
+
process.stdout.write(result.html);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error('[render]', err.message);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login 命令
|
|
3
|
+
*
|
|
4
|
+
* 核心问题:如何让用户在浏览器登录一次,然后把 Cookie 传给 CLI?
|
|
5
|
+
*
|
|
6
|
+
* 方案:本地 HTTP 接收服务(参考 browser-extension 架构)
|
|
7
|
+
*
|
|
8
|
+
* ┌─────────────┐ 用户手动登录 ┌─────────────┐
|
|
9
|
+
* │ 浏览器用户 │ ───────────────▶ │ 平台服务器 │
|
|
10
|
+
* │ │ ◀─── Set-Cookie ──│ (知乎等) │
|
|
11
|
+
* └──────┬──────┘ └─────────────┘
|
|
12
|
+
* │
|
|
13
|
+
* │ Content Script 注入:
|
|
14
|
+
* │ document.title = 'COOKIES:' + JSON.stringify(document.cookies)
|
|
15
|
+
* │
|
|
16
|
+
* ▼ 页面加载完成后,URL 变化(标题变化)
|
|
17
|
+
* ┌──────────────────────────────────────────────────────┐
|
|
18
|
+
* │ 本地 HTTP Server (mpub login --platform zhihu) │
|
|
19
|
+
* │ 1. 打开浏览器到登录页 │
|
|
20
|
+
* │ 2. 监听标题变化/Poll document.cookie │
|
|
21
|
+
* │ 3. 收到 Cookie 后写入磁盘文件 │
|
|
22
|
+
* └──────────────────────────────────────────────────────┘
|
|
23
|
+
*
|
|
24
|
+
* 更实用的方案(参考 puppeteer-extra-plugin-stealth 的思路):
|
|
25
|
+
* 用户通过浏览器插件/Browser 操作获取 Cookie,导出为 JSON 文件
|
|
26
|
+
* 然后 mpub cookie:import --platform zhihu --file cookies.json
|
|
27
|
+
*
|
|
28
|
+
* 当前实现(简化版):
|
|
29
|
+
* 1. 启动本地 HTTP server,开放一个 upload 接口
|
|
30
|
+
* 2. 用户在浏览器开发者工具手动执行一段脚本,把 document.cookies 提取出来
|
|
31
|
+
* 并 POST 到 http://localhost:38921/cookies
|
|
32
|
+
* 3. CLI 收到后写入磁盘
|
|
33
|
+
*
|
|
34
|
+
* 后续可扩展为自动方案:
|
|
35
|
+
* - 配合 Chrome Extension(手动触发 Content Script)
|
|
36
|
+
* - 或用 playwright 自动化控制浏览器登录(最可靠)
|
|
37
|
+
*/
|
|
38
|
+
export interface LoginOptions {
|
|
39
|
+
platform: string;
|
|
40
|
+
port: string;
|
|
41
|
+
timeout: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function loginCommand(opts: LoginOptions): Promise<void>;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login 命令
|
|
3
|
+
*
|
|
4
|
+
* 核心问题:如何让用户在浏览器登录一次,然后把 Cookie 传给 CLI?
|
|
5
|
+
*
|
|
6
|
+
* 方案:本地 HTTP 接收服务(参考 browser-extension 架构)
|
|
7
|
+
*
|
|
8
|
+
* ┌─────────────┐ 用户手动登录 ┌─────────────┐
|
|
9
|
+
* │ 浏览器用户 │ ───────────────▶ │ 平台服务器 │
|
|
10
|
+
* │ │ ◀─── Set-Cookie ──│ (知乎等) │
|
|
11
|
+
* └──────┬──────┘ └─────────────┘
|
|
12
|
+
* │
|
|
13
|
+
* │ Content Script 注入:
|
|
14
|
+
* │ document.title = 'COOKIES:' + JSON.stringify(document.cookies)
|
|
15
|
+
* │
|
|
16
|
+
* ▼ 页面加载完成后,URL 变化(标题变化)
|
|
17
|
+
* ┌──────────────────────────────────────────────────────┐
|
|
18
|
+
* │ 本地 HTTP Server (mpub login --platform zhihu) │
|
|
19
|
+
* │ 1. 打开浏览器到登录页 │
|
|
20
|
+
* │ 2. 监听标题变化/Poll document.cookie │
|
|
21
|
+
* │ 3. 收到 Cookie 后写入磁盘文件 │
|
|
22
|
+
* └──────────────────────────────────────────────────────┘
|
|
23
|
+
*
|
|
24
|
+
* 更实用的方案(参考 puppeteer-extra-plugin-stealth 的思路):
|
|
25
|
+
* 用户通过浏览器插件/Browser 操作获取 Cookie,导出为 JSON 文件
|
|
26
|
+
* 然后 mpub cookie:import --platform zhihu --file cookies.json
|
|
27
|
+
*
|
|
28
|
+
* 当前实现(简化版):
|
|
29
|
+
* 1. 启动本地 HTTP server,开放一个 upload 接口
|
|
30
|
+
* 2. 用户在浏览器开发者工具手动执行一段脚本,把 document.cookies 提取出来
|
|
31
|
+
* 并 POST 到 http://localhost:38921/cookies
|
|
32
|
+
* 3. CLI 收到后写入磁盘
|
|
33
|
+
*
|
|
34
|
+
* 后续可扩展为自动方案:
|
|
35
|
+
* - 配合 Chrome Extension(手动触发 Content Script)
|
|
36
|
+
* - 或用 playwright 自动化控制浏览器登录(最可靠)
|
|
37
|
+
*/
|
|
38
|
+
import http from 'node:http';
|
|
39
|
+
import { networkInterfaces } from 'node:os';
|
|
40
|
+
import { CookieStore } from '../runtime/node-runtime.js';
|
|
41
|
+
export async function loginCommand(opts) {
|
|
42
|
+
const port = parseInt(opts.port, 10);
|
|
43
|
+
const timeoutSec = parseInt(opts.timeout, 10);
|
|
44
|
+
console.log(`
|
|
45
|
+
╔══════════════════════════════════════════════════════════╗
|
|
46
|
+
║ multi-publisher Cookie 采集服务 ║
|
|
47
|
+
╠══════════════════════════════════════════════════════════╣
|
|
48
|
+
║ 平台: ${opts.platform.padEnd(47)}║
|
|
49
|
+
║ 端口: ${String(port).padEnd(48)}║
|
|
50
|
+
║ 超时: ${String(timeoutSec + ' 秒').padEnd(48)}║
|
|
51
|
+
╚══════════════════════════════════════════════════════════╝
|
|
52
|
+
`);
|
|
53
|
+
// 获取本机 IP
|
|
54
|
+
const localIp = getLocalIp();
|
|
55
|
+
console.log(`本地地址: http://localhost:${port}`);
|
|
56
|
+
if (localIp) {
|
|
57
|
+
console.log(`局域网地址: http://${localIp}:${port} (同一 WiFi 下手机也能访问)`);
|
|
58
|
+
}
|
|
59
|
+
console.log();
|
|
60
|
+
// 打印各平台登录页
|
|
61
|
+
const loginUrls = {
|
|
62
|
+
zhihu: 'https://www.zhihu.com/signin',
|
|
63
|
+
juejin: 'https://juejin.cn/auth/login',
|
|
64
|
+
csdn: 'https://passport.csdn.net/login',
|
|
65
|
+
oschina: 'https://www.oschina.net/home/login',
|
|
66
|
+
segmentfault: 'https://segmentfault.com/user/login',
|
|
67
|
+
weixin: 'https://mp.weixin.qq.com/',
|
|
68
|
+
bilibili: 'https://passport.bilibili.com/login',
|
|
69
|
+
weibo: 'https://login.sina.com.cn/signup/signin.php',
|
|
70
|
+
toutiao: 'https://mp.toutiao.com/auth/page/login/',
|
|
71
|
+
};
|
|
72
|
+
const loginUrl = loginUrls[opts.platform] ?? `https://www.${opts.platform}.com/`;
|
|
73
|
+
console.log(`请在浏览器访问并登录: ${loginUrl}`);
|
|
74
|
+
console.log();
|
|
75
|
+
// 启动 HTTP Server
|
|
76
|
+
const cookieStore = new CookieStore();
|
|
77
|
+
let state = { cookies: [], resolved: false };
|
|
78
|
+
const server = http.createServer((req, res) => {
|
|
79
|
+
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
|
|
80
|
+
// CORS preflight
|
|
81
|
+
if (req.method === 'OPTIONS') {
|
|
82
|
+
res.writeHead(200, {
|
|
83
|
+
'Access-Control-Allow-Origin': '*',
|
|
84
|
+
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
|
85
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
86
|
+
});
|
|
87
|
+
res.end();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// -------------------------------------------------------------------------
|
|
91
|
+
// POST /cookies - 接收 Cookie
|
|
92
|
+
// -------------------------------------------------------------------------
|
|
93
|
+
if (url.pathname === '/cookies' && req.method === 'POST') {
|
|
94
|
+
let body = '';
|
|
95
|
+
req.on('data', chunk => { body += chunk; });
|
|
96
|
+
req.on('end', () => {
|
|
97
|
+
try {
|
|
98
|
+
const payload = JSON.parse(body);
|
|
99
|
+
if (!Array.isArray(payload.cookies)) {
|
|
100
|
+
throw new Error('cookies 必须是数组');
|
|
101
|
+
}
|
|
102
|
+
state.cookies = payload.cookies;
|
|
103
|
+
state.resolved = true;
|
|
104
|
+
cookieStore.save(opts.platform, payload.cookies);
|
|
105
|
+
console.log(`[${new Date().toISOString()}] ✓ 收到 ${payload.cookies.length} 个 Cookie,已保存`);
|
|
106
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
107
|
+
res.end(JSON.stringify({ success: true, count: payload.cookies.length }));
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
console.error(`[ERROR] 解析 Cookie 失败:`, err);
|
|
111
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
112
|
+
res.end(JSON.stringify({ success: false, error: String(err) }));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// -------------------------------------------------------------------------
|
|
118
|
+
// GET /status - 查询采集状态
|
|
119
|
+
// -------------------------------------------------------------------------
|
|
120
|
+
if (url.pathname === '/status') {
|
|
121
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
122
|
+
res.end(JSON.stringify({
|
|
123
|
+
resolved: state.resolved,
|
|
124
|
+
cookieCount: state.cookies.length,
|
|
125
|
+
platforms: cookieStore.listPlatforms(),
|
|
126
|
+
}));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// -------------------------------------------------------------------------
|
|
130
|
+
// GET / 欢迎页 + 手动说明
|
|
131
|
+
// -------------------------------------------------------------------------
|
|
132
|
+
if (url.pathname === '/' || url.pathname === '') {
|
|
133
|
+
const html = `<!DOCTYPE html>
|
|
134
|
+
<html>
|
|
135
|
+
<head><meta charset="utf-8"><title>mpub login</title></head>
|
|
136
|
+
<body style="font-family: monospace; max-width: 700px; margin: 60px auto; padding: 0 20px;">
|
|
137
|
+
<h2>multi-publisher Cookie 采集服务</h2>
|
|
138
|
+
<p>平台: <b>${opts.platform}</b></p>
|
|
139
|
+
<p>状态: <span id="status" style="color: orange;">等待 Cookie...</span></p>
|
|
140
|
+
<hr/>
|
|
141
|
+
<h3>方法一:浏览器开发者工具(推荐)</h3>
|
|
142
|
+
<ol>
|
|
143
|
+
<li>在浏览器打开 <a href="${loginUrl}" target="_blank">${loginUrl}</a> 并完成登录</li>
|
|
144
|
+
<li>按 <kbd>F12</kbd> 打开开发者工具 → <strong>Console</strong></li>
|
|
145
|
+
<li>粘贴以下代码并回车:</li>
|
|
146
|
+
</ol>
|
|
147
|
+
<pre style="background:#f5f5f5;padding:15px;border-radius:5px;overflow-x:auto">
|
|
148
|
+
const cookies = document.cookie.split(';').map(c => {
|
|
149
|
+
const [name, ...rest] = c.trim().split('=')
|
|
150
|
+
const value = rest.join('=')
|
|
151
|
+
return { name, value, domain: '.${opts.platform.includes('.') ? opts.platform : opts.platform + '.com'}', path: '/', secure: true }
|
|
152
|
+
})
|
|
153
|
+
fetch('http://localhost:${port}/cookies', {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: { 'Content-Type': 'application/json' },
|
|
156
|
+
body: JSON.stringify({ cookies, platform: '${opts.platform}' })
|
|
157
|
+
}).then(r => r.json()).then(d => {
|
|
158
|
+
document.body.innerHTML = '<h2 style="color:green">✓ Cookie 已提交!数量: ' + d.count + '</h2><p>可以关闭此页面了。</p>'
|
|
159
|
+
})
|
|
160
|
+
</pre>
|
|
161
|
+
|
|
162
|
+
<h3>方法二:使用浏览器插件</h3>
|
|
163
|
+
<p>安装 <a href="https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg" target="_blank">EditThisCookie</a>
|
|
164
|
+
或 <a href="https://chrome.google.com/webstore/detail/cookie-manager-by-ptmcli/hgpmkjhfgejjgcpfjekgmlhpegokpaf" target="_blank">Cookie-Editor</a>,
|
|
165
|
+
登录后导出 Cookie 为 JSON,保存到本地文件,然后运行:</p>
|
|
166
|
+
<pre style="background:#f5f5f5;padding:15px;border-radius:5px">
|
|
167
|
+
mpub cookie --import --platform ${opts.platform} --file ./cookies.json
|
|
168
|
+
</pre>
|
|
169
|
+
|
|
170
|
+
<h3>方法三:Playwright 自动登录(最可靠)</h3>
|
|
171
|
+
<pre style="background:#f5f5f5;padding:15px;border-radius:5px">
|
|
172
|
+
# 安装 playwright: npx playwright install chromium
|
|
173
|
+
# 自动打开浏览器,用户手动输入账号密码,登录后自动提取 Cookie
|
|
174
|
+
mpub login --platform ${opts.platform} --auto-playwright
|
|
175
|
+
</pre>
|
|
176
|
+
|
|
177
|
+
<script>
|
|
178
|
+
(async () => {
|
|
179
|
+
const poll = () => fetch('/status').then(r=>r.json()).then(d => {
|
|
180
|
+
const el = document.getElementById('status')
|
|
181
|
+
if (d.resolved) {
|
|
182
|
+
el.textContent = '✓ Cookie 已收到 (' + d.cookieCount + ' 个)'
|
|
183
|
+
el.style.color = 'green'
|
|
184
|
+
} else {
|
|
185
|
+
el.textContent = '等待中 (' + d.cookieCount + ' 个已收)'
|
|
186
|
+
el.style.color = 'orange'
|
|
187
|
+
setTimeout(poll, 2000)
|
|
188
|
+
}
|
|
189
|
+
}).catch(() => setTimeout(poll, 5000))
|
|
190
|
+
poll()
|
|
191
|
+
})()
|
|
192
|
+
</script>
|
|
193
|
+
</body>
|
|
194
|
+
</html>`;
|
|
195
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
196
|
+
res.end(html);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
res.writeHead(404);
|
|
200
|
+
res.end('Not Found');
|
|
201
|
+
});
|
|
202
|
+
// 超时控制
|
|
203
|
+
const timeoutMs = timeoutSec * 1000;
|
|
204
|
+
const timer = setTimeout(() => {
|
|
205
|
+
if (!state.resolved) {
|
|
206
|
+
console.log(`\n[超时] ${timeoutSec} 秒内未收到 Cookie`);
|
|
207
|
+
server.close();
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}, timeoutMs);
|
|
211
|
+
server.listen(port, () => {
|
|
212
|
+
console.log(`服务已启动,等待 Cookie...\n`);
|
|
213
|
+
console.log(`登录后,有三种方式提交 Cookie:\n`);
|
|
214
|
+
console.log(` 1. [推荐] 打开 http://localhost:${port} 查看说明(浏览器开发者工具方式)`);
|
|
215
|
+
console.log(` 2. Cookie 导出插件(EditThisCookie/Cookie-Editor)导出 JSON 后:`);
|
|
216
|
+
console.log(` mpub cookie --import --platform ${opts.platform} --file ./cookies.json`);
|
|
217
|
+
console.log(` 3. Playwright 自动(需安装):`);
|
|
218
|
+
console.log(` mpub login --platform ${opts.platform} --auto-playwright\n`);
|
|
219
|
+
console.log(`按 Ctrl+C 取消\n`);
|
|
220
|
+
});
|
|
221
|
+
// 等待 Cookie 收到
|
|
222
|
+
await new Promise((resolve) => {
|
|
223
|
+
const checkInterval = setInterval(() => {
|
|
224
|
+
if (state.resolved) {
|
|
225
|
+
clearInterval(checkInterval);
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
server.close(() => {
|
|
228
|
+
console.log(`\n✓ ${opts.platform} Cookie 采集完成 (${state.cookies.length} 个)`);
|
|
229
|
+
resolve();
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}, 500);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function getLocalIp() {
|
|
236
|
+
try {
|
|
237
|
+
const nets = networkInterfaces();
|
|
238
|
+
for (const name of Object.keys(nets)) {
|
|
239
|
+
for (const iface of nets[name] ?? []) {
|
|
240
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
241
|
+
return iface.address;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch { }
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* publish 命令
|
|
3
|
+
*
|
|
4
|
+
* 工作流程:
|
|
5
|
+
* 1. 解析 MD 文件(frontmatter + 内容)
|
|
6
|
+
* 2. 构建 Article 对象
|
|
7
|
+
* 3. 为每个目标平台选择适配器
|
|
8
|
+
* 4. 调用适配器发布
|
|
9
|
+
*/
|
|
10
|
+
export interface PublishOptions {
|
|
11
|
+
file: string;
|
|
12
|
+
platforms: string;
|
|
13
|
+
theme: string;
|
|
14
|
+
appId?: string;
|
|
15
|
+
appSecret?: string;
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
draft?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function publishCommand(opts: PublishOptions): Promise<void>;
|