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,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微博适配器
|
|
3
|
+
* 认证方式: Cookie
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
6
|
+
export class WeiboAdapter extends BaseAdapter {
|
|
7
|
+
meta = {
|
|
8
|
+
id: 'weibo',
|
|
9
|
+
name: '微博',
|
|
10
|
+
icon: 'https://weibo.com/favicon.ico',
|
|
11
|
+
homepage: 'https://card.weibo.com/article/v5/editor',
|
|
12
|
+
capabilities: ['article', 'draft', 'image_upload'],
|
|
13
|
+
};
|
|
14
|
+
cookieData = null;
|
|
15
|
+
async init() {
|
|
16
|
+
this.cookieData = await this.loadCookies();
|
|
17
|
+
}
|
|
18
|
+
getCookieConfigKey() {
|
|
19
|
+
return 'weibo';
|
|
20
|
+
}
|
|
21
|
+
async checkAuth() {
|
|
22
|
+
if (!this.cookieData) {
|
|
23
|
+
return { isAuthenticated: false, error: '未配置微博 Cookie' };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
// 微博通过访问编辑器页面验证登录状态
|
|
27
|
+
const response = await fetch('https://card.weibo.com/article/v5/editor', {
|
|
28
|
+
credentials: 'include',
|
|
29
|
+
});
|
|
30
|
+
const html = await response.text();
|
|
31
|
+
// 从页面中解析用户配置
|
|
32
|
+
const configMatch = html.match(/config:\s*JSON\.parse\('(.+?)'\)/);
|
|
33
|
+
if (!configMatch) {
|
|
34
|
+
// 尝试另一种方式检测
|
|
35
|
+
if (html.includes('"uid"') && html.includes('"nick"')) {
|
|
36
|
+
return { isAuthenticated: true };
|
|
37
|
+
}
|
|
38
|
+
return { isAuthenticated: false, error: '无法解析用户信息' };
|
|
39
|
+
}
|
|
40
|
+
return { isAuthenticated: true };
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return { isAuthenticated: false, error: err.message };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async publish(article) {
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
if (!this.cookieData) {
|
|
49
|
+
return this.createErrorResult('未配置微博 Cookie', start);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const cookieStr = this.buildCookieString(this.cookieData);
|
|
53
|
+
// 1. 创建草稿
|
|
54
|
+
const createRes = await fetch(`https://card.weibo.com/article/v5/aj/editor/draft/create?_r=${Date.now()}`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
credentials: 'include',
|
|
57
|
+
headers: {
|
|
58
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
59
|
+
'Cookie': cookieStr,
|
|
60
|
+
...this.getCommonHeaders(),
|
|
61
|
+
},
|
|
62
|
+
body: new URLSearchParams({}),
|
|
63
|
+
});
|
|
64
|
+
const createData = await createRes.json();
|
|
65
|
+
if (createData.code !== 100000 || !createData.data?.id) {
|
|
66
|
+
throw new Error(createData.msg || '创建草稿失败');
|
|
67
|
+
}
|
|
68
|
+
const postId = createData.data.id;
|
|
69
|
+
// 2. 处理内容
|
|
70
|
+
let content = article.html || article.markdown || '';
|
|
71
|
+
// 3. 保存草稿
|
|
72
|
+
const saveRes = await fetch(`https://card.weibo.com/article/v5/aj/editor/draft/save?id=${postId}&_r=${Date.now()}`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
credentials: 'include',
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
77
|
+
'Cookie': cookieStr,
|
|
78
|
+
...this.getCommonHeaders(),
|
|
79
|
+
},
|
|
80
|
+
body: new URLSearchParams({
|
|
81
|
+
id: postId,
|
|
82
|
+
title: article.title,
|
|
83
|
+
content,
|
|
84
|
+
status: '0',
|
|
85
|
+
save: '1',
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
const saveData = await saveRes.json();
|
|
89
|
+
if (String(saveData.code) !== '100000') {
|
|
90
|
+
throw new Error('保存草稿失败');
|
|
91
|
+
}
|
|
92
|
+
const draftUrl = `https://card.weibo.com/article/v5/editor#/draft/${postId}`;
|
|
93
|
+
return this.createSuccessResult(postId, draftUrl, true, start);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
return this.createErrorResult(err.message, start);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微信公众号适配器
|
|
3
|
+
* 认证方式: AppID + AppSecret(access_token)
|
|
4
|
+
*/
|
|
5
|
+
import type { IPlatformAdapter, Article, SyncResult, AuthResult, PlatformMeta } from './interface.js';
|
|
6
|
+
import type { RuntimeInterface } from '../runtime/index.js';
|
|
7
|
+
export declare class WeixinAdapter implements IPlatformAdapter {
|
|
8
|
+
readonly meta: PlatformMeta;
|
|
9
|
+
private runtime;
|
|
10
|
+
private publisher;
|
|
11
|
+
init(runtime: RuntimeInterface): Promise<void>;
|
|
12
|
+
checkAuth(): Promise<AuthResult>;
|
|
13
|
+
publish(article: Article): Promise<SyncResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { WechatPublisher } from './wechat-publisher.js';
|
|
2
|
+
export class WeixinAdapter {
|
|
3
|
+
meta = {
|
|
4
|
+
id: 'weixin',
|
|
5
|
+
name: '微信公众号',
|
|
6
|
+
icon: 'https://mp.weixin.qq.com/favicon.ico',
|
|
7
|
+
homepage: 'https://mp.weixin.qq.com',
|
|
8
|
+
capabilities: ['article', 'draft', 'image_upload'],
|
|
9
|
+
};
|
|
10
|
+
runtime;
|
|
11
|
+
publisher;
|
|
12
|
+
async init(runtime) {
|
|
13
|
+
this.runtime = runtime;
|
|
14
|
+
this.publisher = new WechatPublisher(runtime);
|
|
15
|
+
}
|
|
16
|
+
async checkAuth() {
|
|
17
|
+
try {
|
|
18
|
+
const token = await this.publisher.getAccessToken();
|
|
19
|
+
return {
|
|
20
|
+
isAuthenticated: !!token,
|
|
21
|
+
userId: 'authenticated',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26
|
+
return { isAuthenticated: false, error: msg };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async publish(article) {
|
|
30
|
+
const start = Date.now();
|
|
31
|
+
try {
|
|
32
|
+
if (!article.html) {
|
|
33
|
+
throw new Error('article.html is required for WeChat publishing');
|
|
34
|
+
}
|
|
35
|
+
const result = await this.publisher.publishToDraft({
|
|
36
|
+
title: article.title,
|
|
37
|
+
content: article.html,
|
|
38
|
+
cover: article.cover ?? '',
|
|
39
|
+
author: article.author ?? '',
|
|
40
|
+
source_url: article.source_url ?? '',
|
|
41
|
+
});
|
|
42
|
+
const token = await this.publisher.getAccessToken();
|
|
43
|
+
const draftUrl = `https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit&action=edit&type=77&appmsgid=${result.media_id}&token=${token}&lang=zh_CN`;
|
|
44
|
+
return {
|
|
45
|
+
platform: this.meta.id,
|
|
46
|
+
success: true,
|
|
47
|
+
postId: result.media_id,
|
|
48
|
+
postUrl: draftUrl,
|
|
49
|
+
draftOnly: true,
|
|
50
|
+
timestamp: Date.now() - start,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
+
return {
|
|
56
|
+
platform: this.meta.id,
|
|
57
|
+
success: false,
|
|
58
|
+
error: msg,
|
|
59
|
+
timestamp: Date.now() - start,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 人人都是产品经理适配器
|
|
3
|
+
* 认证方式: Cookie
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
6
|
+
import type { Article, AuthResult, PlatformMeta } from './interface.js';
|
|
7
|
+
export declare class WoshipmAdapter extends BaseAdapter {
|
|
8
|
+
readonly meta: PlatformMeta;
|
|
9
|
+
private cookieData;
|
|
10
|
+
init(): Promise<void>;
|
|
11
|
+
protected getCookieConfigKey(): string;
|
|
12
|
+
checkAuth(): Promise<AuthResult>;
|
|
13
|
+
publish(article: Article): Promise<import('./interface.js').SyncResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 人人都是产品经理适配器
|
|
3
|
+
* 认证方式: Cookie
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
6
|
+
export class WoshipmAdapter extends BaseAdapter {
|
|
7
|
+
meta = {
|
|
8
|
+
id: 'woshipm',
|
|
9
|
+
name: '人人都是产品经理',
|
|
10
|
+
icon: 'https://www.woshipm.com/favicon.ico',
|
|
11
|
+
homepage: 'https://www.woshipm.com/',
|
|
12
|
+
capabilities: ['article', 'draft'],
|
|
13
|
+
};
|
|
14
|
+
cookieData = null;
|
|
15
|
+
async init() {
|
|
16
|
+
this.cookieData = await this.loadCookies();
|
|
17
|
+
}
|
|
18
|
+
getCookieConfigKey() {
|
|
19
|
+
return 'woshipm';
|
|
20
|
+
}
|
|
21
|
+
async checkAuth() {
|
|
22
|
+
if (!this.cookieData) {
|
|
23
|
+
return { isAuthenticated: false, error: '未配置人人都是产品经理 Cookie' };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const cookieStr = this.buildCookieString(this.cookieData);
|
|
27
|
+
const res = await fetch('https://www.woshipm.com/user/profile', {
|
|
28
|
+
headers: {
|
|
29
|
+
'Cookie': cookieStr,
|
|
30
|
+
...this.getCommonHeaders(),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
if (res.ok && !res.url.includes('login')) {
|
|
34
|
+
return { isAuthenticated: true };
|
|
35
|
+
}
|
|
36
|
+
return { isAuthenticated: false, error: '未登录或登录已过期' };
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return { isAuthenticated: false, error: err.message };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async publish(article) {
|
|
43
|
+
const start = Date.now();
|
|
44
|
+
if (!this.cookieData) {
|
|
45
|
+
return this.createErrorResult('未配置 Cookie', start);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const cookieStr = this.buildCookieString(this.cookieData);
|
|
49
|
+
const res = await fetch('https://www.woshipm.com/article/save_draft', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
'Cookie': cookieStr,
|
|
54
|
+
...this.getCommonHeaders(),
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
title: article.title,
|
|
58
|
+
content: article.markdown || '',
|
|
59
|
+
draft: true,
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
const data = await res.json();
|
|
63
|
+
if (data.code === 0 && data.data?.id) {
|
|
64
|
+
return this.createSuccessResult(data.data.id, `https://www.woshipm.com/article/edit?id=${data.data.id}`, true, start);
|
|
65
|
+
}
|
|
66
|
+
return this.createErrorResult('发布失败', start);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
return this.createErrorResult(err.message, start);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Article, AuthResult, PlatformMeta, SyncResult } from './interface.js';
|
|
2
|
+
export declare class XiaohongshuAdapter implements IPlatformAdapter {
|
|
3
|
+
readonly meta: PlatformMeta;
|
|
4
|
+
private cookieData;
|
|
5
|
+
init(): Promise<void>;
|
|
6
|
+
checkAuth(): Promise<AuthResult>;
|
|
7
|
+
publish(article: Article): Promise<SyncResult>;
|
|
8
|
+
}
|
|
9
|
+
import type { IPlatformAdapter } from './interface.js';
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 小红书 (Xiaohongshu) 适配器
|
|
3
|
+
* 使用 Playwright 直接操作页面发布
|
|
4
|
+
*/
|
|
5
|
+
import { chromium } from 'playwright';
|
|
6
|
+
import { ConfigStore } from '../config.js';
|
|
7
|
+
export class XiaohongshuAdapter {
|
|
8
|
+
meta = {
|
|
9
|
+
id: 'xiaohongshu',
|
|
10
|
+
name: '小红书',
|
|
11
|
+
icon: 'https://www.xiaohongshu.com/favicon.ico',
|
|
12
|
+
homepage: 'https://creator.xiaohongshu.com/',
|
|
13
|
+
capabilities: ['article', 'draft', 'image_upload'],
|
|
14
|
+
};
|
|
15
|
+
cookieData = null;
|
|
16
|
+
async init() {
|
|
17
|
+
this.cookieData = await ConfigStore.getXiaohongshuCookies();
|
|
18
|
+
}
|
|
19
|
+
async checkAuth() {
|
|
20
|
+
if (!this.cookieData) {
|
|
21
|
+
return { isAuthenticated: false, error: '未配置小红书 Cookie' };
|
|
22
|
+
}
|
|
23
|
+
const hasSession = this.cookieData['web_session'] || this.cookieData['sessionid'];
|
|
24
|
+
if (!hasSession) {
|
|
25
|
+
return { isAuthenticated: false, error: '未登录或登录已过期' };
|
|
26
|
+
}
|
|
27
|
+
return { isAuthenticated: true };
|
|
28
|
+
}
|
|
29
|
+
async publish(article) {
|
|
30
|
+
const start = Date.now();
|
|
31
|
+
if (!this.cookieData) {
|
|
32
|
+
return {
|
|
33
|
+
platform: this.meta.id,
|
|
34
|
+
success: false,
|
|
35
|
+
error: '未配置小红书 Cookie',
|
|
36
|
+
timestamp: Date.now() - start,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const browser = await chromium.launch({
|
|
40
|
+
headless: false,
|
|
41
|
+
args: [
|
|
42
|
+
'--disable-blink-features=AutomationControlled',
|
|
43
|
+
'--disable-devtools-shm-usage',
|
|
44
|
+
'--no-sandbox',
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
const context = await browser.newContext({
|
|
49
|
+
viewport: { width: 1280, height: 900 },
|
|
50
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
51
|
+
});
|
|
52
|
+
const page = await context.newPage();
|
|
53
|
+
// 使用 CDP 隐藏自动化特征
|
|
54
|
+
const cdp = await page.context().newCDPSession(page);
|
|
55
|
+
await cdp.send('Page.addScriptToEvaluateOnNewDocument', {
|
|
56
|
+
source: `
|
|
57
|
+
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
58
|
+
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
|
|
59
|
+
Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh', 'en'] });
|
|
60
|
+
window.chrome = { runtime: {}, loadTimes: () => {}, csi: () => {} };
|
|
61
|
+
`
|
|
62
|
+
});
|
|
63
|
+
// 设置 cookies
|
|
64
|
+
const pageCookies = Object.entries(this.cookieData).map(([name, value]) => ({
|
|
65
|
+
name,
|
|
66
|
+
value,
|
|
67
|
+
domain: '.xiaohongshu.com',
|
|
68
|
+
path: '/',
|
|
69
|
+
secure: true,
|
|
70
|
+
httpOnly: false,
|
|
71
|
+
}));
|
|
72
|
+
await context.addCookies(pageCookies);
|
|
73
|
+
console.log('[xiaohongshu] 已设置 cookies:', Object.keys(this.cookieData));
|
|
74
|
+
console.log('[xiaohongshu] 正在打开小红书...');
|
|
75
|
+
// 先访问主站让 cookie 生效
|
|
76
|
+
await page.goto('https://www.xiaohongshu.com/', {
|
|
77
|
+
waitUntil: 'domcontentloaded',
|
|
78
|
+
timeout: 30000
|
|
79
|
+
});
|
|
80
|
+
await page.waitForTimeout(2000);
|
|
81
|
+
console.log('[xiaohongshu] 正在打开创作者中心...');
|
|
82
|
+
await page.goto('https://creator.xiaohongshu.com/', {
|
|
83
|
+
waitUntil: 'domcontentloaded',
|
|
84
|
+
timeout: 30000
|
|
85
|
+
});
|
|
86
|
+
await page.waitForTimeout(3000);
|
|
87
|
+
// 检查是否跳转到登录页
|
|
88
|
+
const currentUrl = page.url();
|
|
89
|
+
console.log('[xiaohongshu] 当前 URL:', currentUrl);
|
|
90
|
+
if (currentUrl.includes('login') || currentUrl.includes('sign')) {
|
|
91
|
+
return {
|
|
92
|
+
platform: this.meta.id,
|
|
93
|
+
success: false,
|
|
94
|
+
error: '未登录或登录已过期,请重新登录',
|
|
95
|
+
timestamp: Date.now() - start,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// 点击发布笔记按钮
|
|
99
|
+
console.log('[xiaohongshu] 查找发布按钮...');
|
|
100
|
+
try {
|
|
101
|
+
const publishBtn = page.locator('button, div[role="button"]').filter({ hasText: /发布/ }).first();
|
|
102
|
+
if (await publishBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
103
|
+
await publishBtn.click();
|
|
104
|
+
console.log('[xiaohongshu] 已点击发布按钮');
|
|
105
|
+
await page.waitForTimeout(2000);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
console.log('[xiaohongshu] 查找发布按钮失败:', e.message);
|
|
110
|
+
}
|
|
111
|
+
// 填写标题
|
|
112
|
+
console.log('[xiaohongshu] 填写标题...');
|
|
113
|
+
try {
|
|
114
|
+
// 小红书标题可能在 input 或 textarea 中
|
|
115
|
+
const titleInput = page.locator('input[placeholder*="标题"], textarea[placeholder*="标题"]').first();
|
|
116
|
+
if (await titleInput.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
117
|
+
await titleInput.fill(article.title);
|
|
118
|
+
console.log('[xiaohongshu] 已填写标题');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
console.log('[xiaohongshu] 填写标题失败:', e.message);
|
|
123
|
+
}
|
|
124
|
+
// 填写内容
|
|
125
|
+
console.log('[xiaohongshu] 填写内容...');
|
|
126
|
+
try {
|
|
127
|
+
const contentEl = page.locator('div[contenteditable="true"]').first();
|
|
128
|
+
if (await contentEl.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
129
|
+
await contentEl.click();
|
|
130
|
+
await page.keyboard.press('Control+a');
|
|
131
|
+
await page.waitForTimeout(200);
|
|
132
|
+
const textContent = article.markdown || article.html || '';
|
|
133
|
+
await page.evaluate((el) => {
|
|
134
|
+
const div = document.querySelector('[contenteditable="true"]');
|
|
135
|
+
if (div) {
|
|
136
|
+
div.innerHTML = el;
|
|
137
|
+
div.dispatchEvent(new InputEvent('input', { bubbles: true }));
|
|
138
|
+
}
|
|
139
|
+
}, textContent);
|
|
140
|
+
console.log('[xiaohongshu] 已填写内容');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
console.log('[xiaohongshu] 填写内容失败:', e.message);
|
|
145
|
+
}
|
|
146
|
+
// 等待自动保存
|
|
147
|
+
console.log('[xiaohongshu] 等待自动保存...');
|
|
148
|
+
await page.waitForTimeout(10000);
|
|
149
|
+
// 尝试点击保存/发布按钮
|
|
150
|
+
console.log('[xiaohongshu] 尝试保存...');
|
|
151
|
+
try {
|
|
152
|
+
const saveBtn = page.locator('button').filter({ hasText: /保存|发布/ }).first();
|
|
153
|
+
if (await saveBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
154
|
+
await saveBtn.click();
|
|
155
|
+
console.log('[xiaohongshu] 已点击保存按钮');
|
|
156
|
+
await page.waitForTimeout(5000);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
console.log('[xiaohongshu] 保存按钮点击失败:', e.message);
|
|
161
|
+
}
|
|
162
|
+
const finalUrl = page.url();
|
|
163
|
+
console.log('[xiaohongshu] 当前 URL:', finalUrl);
|
|
164
|
+
// 检查是否保存成功
|
|
165
|
+
const idMatch = finalUrl.match(/[?&]id=(\w+)/);
|
|
166
|
+
if (idMatch) {
|
|
167
|
+
console.log('[xiaohongshu] 保存成功,笔记 ID:', idMatch[1]);
|
|
168
|
+
return {
|
|
169
|
+
platform: this.meta.id,
|
|
170
|
+
success: true,
|
|
171
|
+
postId: idMatch[1],
|
|
172
|
+
postUrl: finalUrl,
|
|
173
|
+
draftOnly: true,
|
|
174
|
+
timestamp: Date.now() - start,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
console.log('[xiaohongshu] 草稿已保存');
|
|
178
|
+
return {
|
|
179
|
+
platform: this.meta.id,
|
|
180
|
+
success: true,
|
|
181
|
+
postUrl: finalUrl,
|
|
182
|
+
draftOnly: true,
|
|
183
|
+
timestamp: Date.now() - start,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error('[xiaohongshu] 错误:', err);
|
|
188
|
+
return {
|
|
189
|
+
platform: this.meta.id,
|
|
190
|
+
success: false,
|
|
191
|
+
error: err.message,
|
|
192
|
+
timestamp: Date.now() - start,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
await browser.close();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 雪球 (Xueqiu) 适配器
|
|
3
|
+
* 认证方式: Cookie
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
6
|
+
import type { Article, AuthResult, PlatformMeta } from './interface.js';
|
|
7
|
+
export declare class XueqiuAdapter extends BaseAdapter {
|
|
8
|
+
readonly meta: PlatformMeta;
|
|
9
|
+
private cookieData;
|
|
10
|
+
init(): Promise<void>;
|
|
11
|
+
protected getCookieConfigKey(): string;
|
|
12
|
+
checkAuth(): Promise<AuthResult>;
|
|
13
|
+
publish(article: Article): Promise<import('./interface.js').SyncResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 雪球 (Xueqiu) 适配器
|
|
3
|
+
* 认证方式: Cookie
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
6
|
+
export class XueqiuAdapter extends BaseAdapter {
|
|
7
|
+
meta = {
|
|
8
|
+
id: 'xueqiu',
|
|
9
|
+
name: '雪球',
|
|
10
|
+
icon: 'https://xueqiu.com/favicon.ico',
|
|
11
|
+
homepage: 'https://xueqiu.com/',
|
|
12
|
+
capabilities: ['article', 'draft'],
|
|
13
|
+
};
|
|
14
|
+
cookieData = null;
|
|
15
|
+
async init() {
|
|
16
|
+
this.cookieData = await this.loadCookies();
|
|
17
|
+
}
|
|
18
|
+
getCookieConfigKey() {
|
|
19
|
+
return 'xueqiu';
|
|
20
|
+
}
|
|
21
|
+
async checkAuth() {
|
|
22
|
+
if (!this.cookieData) {
|
|
23
|
+
return { isAuthenticated: false, error: '未配置雪球 Cookie' };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const cookieStr = this.buildCookieString(this.cookieData);
|
|
27
|
+
const res = await fetch('https://xueqiu.com/settings/user', {
|
|
28
|
+
headers: {
|
|
29
|
+
'Cookie': cookieStr,
|
|
30
|
+
...this.getCommonHeaders(),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
const text = await res.text();
|
|
34
|
+
if (res.ok && (text.includes('个人资料') || text.includes('username'))) {
|
|
35
|
+
return { isAuthenticated: true };
|
|
36
|
+
}
|
|
37
|
+
return { isAuthenticated: false, error: '未登录或登录已过期' };
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return { isAuthenticated: false, error: err.message };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async publish(article) {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
if (!this.cookieData) {
|
|
46
|
+
return this.createErrorResult('未配置雪球 Cookie', start);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const cookieStr = this.buildCookieString(this.cookieData);
|
|
50
|
+
// 雪球文章发布 API
|
|
51
|
+
const res = await fetch('https://xueqiu.com/articles/save_draft', {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
'Cookie': cookieStr,
|
|
56
|
+
...this.getCommonHeaders(),
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
title: article.title,
|
|
60
|
+
content: article.markdown || '',
|
|
61
|
+
draft: 1,
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
const data = await res.json();
|
|
65
|
+
if (data.code === 0 && data.id) {
|
|
66
|
+
return this.createSuccessResult(data.id, `https://xueqiu.com/articles/edit?id=${data.id}`, true, start);
|
|
67
|
+
}
|
|
68
|
+
return this.createErrorResult('发布失败', start);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
return this.createErrorResult(err.message, start);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 语雀 (Yuque) 适配器
|
|
3
|
+
* 认证方式: Cookie (Note: 语雀主要使用 OAuth,此处为占位符)
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
6
|
+
import type { Article, AuthResult, PlatformMeta } from './interface.js';
|
|
7
|
+
export declare class YuqueAdapter extends BaseAdapter {
|
|
8
|
+
readonly meta: PlatformMeta;
|
|
9
|
+
init(): Promise<void>;
|
|
10
|
+
protected getCookieConfigKey(): string;
|
|
11
|
+
checkAuth(): Promise<AuthResult>;
|
|
12
|
+
publish(article: Article): Promise<import('./interface.js').SyncResult>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 语雀 (Yuque) 适配器
|
|
3
|
+
* 认证方式: Cookie (Note: 语雀主要使用 OAuth,此处为占位符)
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
6
|
+
export class YuqueAdapter extends BaseAdapter {
|
|
7
|
+
meta = {
|
|
8
|
+
id: 'yuque',
|
|
9
|
+
name: '语雀',
|
|
10
|
+
icon: 'https://www.yuque.com/favicon.ico',
|
|
11
|
+
homepage: 'https://www.yuque.com/',
|
|
12
|
+
capabilities: ['article', 'draft'],
|
|
13
|
+
};
|
|
14
|
+
async init() { }
|
|
15
|
+
getCookieConfigKey() {
|
|
16
|
+
return 'yuque';
|
|
17
|
+
}
|
|
18
|
+
async checkAuth() {
|
|
19
|
+
return { isAuthenticated: false, error: '语雀仅支持 OAuth 登录,请使用浏览器扩展登录' };
|
|
20
|
+
}
|
|
21
|
+
async publish(article) {
|
|
22
|
+
const start = Date.now();
|
|
23
|
+
return this.createErrorResult('语雀需要 OAuth 认证,请使用 wechatsync 浏览器扩展', start);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 知乎适配器
|
|
3
|
+
* 认证方式: Cookie(从 www.zhihu.com 主页提取)
|
|
4
|
+
*
|
|
5
|
+
* API 流程: 创建草稿 → 更新草稿内容
|
|
6
|
+
*/
|
|
7
|
+
import type { IPlatformAdapter, Article, SyncResult, AuthResult, PlatformMeta } from './interface.js';
|
|
8
|
+
import type { RuntimeInterface } from '../runtime/index.js';
|
|
9
|
+
export declare class ZhihuAdapter implements IPlatformAdapter {
|
|
10
|
+
readonly meta: PlatformMeta;
|
|
11
|
+
private runtime;
|
|
12
|
+
private cookieData;
|
|
13
|
+
init(runtime: RuntimeInterface): Promise<void>;
|
|
14
|
+
private getCookiePath;
|
|
15
|
+
private loadCookie;
|
|
16
|
+
private buildCookieString;
|
|
17
|
+
checkAuth(): Promise<AuthResult>;
|
|
18
|
+
publish(article: Article): Promise<SyncResult>;
|
|
19
|
+
/**
|
|
20
|
+
* 知乎内容转换
|
|
21
|
+
*/
|
|
22
|
+
private transformContent;
|
|
23
|
+
}
|