@skrillex1224/chrome-article-publish-extension 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/README.md +226 -0
- package/dist/adapters/index.d.ts +1125 -0
- package/dist/adapters/index.js +1900 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/chunk-7Q3KKWUB.js +602 -0
- package/dist/chunk-7Q3KKWUB.js.map +1 -0
- package/dist/chunk-IUAZAMKF.js +123 -0
- package/dist/chunk-IUAZAMKF.js.map +1 -0
- package/dist/chunk-QM2T4VGM.js +6688 -0
- package/dist/chunk-QM2T4VGM.js.map +1 -0
- package/dist/chunk-UO6CDRC5.js +322 -0
- package/dist/chunk-UO6CDRC5.js.map +1 -0
- package/dist/converter/index.d.ts +2 -0
- package/dist/converter/index.html +10 -0
- package/dist/converter/index.js +432 -0
- package/dist/converter/index.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +192 -0
- package/dist/index.js.map +1 -0
- package/dist/interface-BlLeWXzS.d.ts +265 -0
- package/dist/preprocessor/index.html +10 -0
- package/dist/runtime/index.d.ts +90 -0
- package/dist/runtime/index.js +9 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/types-BAnNlLpy.d.ts +140 -0
- package/package.json +54 -0
|
@@ -0,0 +1,1900 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaijiahaoAdapter,
|
|
3
|
+
BilibiliAdapter,
|
|
4
|
+
CSDNAdapter,
|
|
5
|
+
CnblogsAdapter,
|
|
6
|
+
CodeAdapter,
|
|
7
|
+
Cto51Adapter,
|
|
8
|
+
DouyinAdapter,
|
|
9
|
+
JuejinAdapter,
|
|
10
|
+
NeteaseAdapter,
|
|
11
|
+
SohuAdapter,
|
|
12
|
+
TencentAdapter,
|
|
13
|
+
ToutiaoAdapter,
|
|
14
|
+
WeixinAdapter
|
|
15
|
+
} from "../chunk-QM2T4VGM.js";
|
|
16
|
+
import {
|
|
17
|
+
markdownToDraft
|
|
18
|
+
} from "../chunk-IUAZAMKF.js";
|
|
19
|
+
import {
|
|
20
|
+
createLogger,
|
|
21
|
+
htmlToMarkdown,
|
|
22
|
+
parseMarkdownImages
|
|
23
|
+
} from "../chunk-7Q3KKWUB.js";
|
|
24
|
+
|
|
25
|
+
// src/adapters/types.ts
|
|
26
|
+
var DEFAULT_PREPROCESS_CONFIG = {
|
|
27
|
+
outputFormat: "html",
|
|
28
|
+
removeIframes: true,
|
|
29
|
+
removeComments: true,
|
|
30
|
+
removeSpecialTags: true,
|
|
31
|
+
removeSvgImages: true,
|
|
32
|
+
processCodeBlocks: true,
|
|
33
|
+
processLazyImages: true,
|
|
34
|
+
removeEmptyElements: true,
|
|
35
|
+
removeDataAttributes: true,
|
|
36
|
+
removeSrcset: true,
|
|
37
|
+
removeSizes: true
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/adapters/base.ts
|
|
41
|
+
var BaseAdapter = class {
|
|
42
|
+
runtime;
|
|
43
|
+
context = {};
|
|
44
|
+
async init(runtime) {
|
|
45
|
+
this.runtime = runtime;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 发送请求
|
|
49
|
+
*/
|
|
50
|
+
async request(url, options = {}) {
|
|
51
|
+
const response = await this.runtime.fetch(url, {
|
|
52
|
+
...options,
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
...options.headers
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
60
|
+
}
|
|
61
|
+
const contentType = response.headers.get("content-type");
|
|
62
|
+
if (contentType?.includes("application/json")) {
|
|
63
|
+
return response.json();
|
|
64
|
+
}
|
|
65
|
+
return response.text();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 带重试的请求
|
|
69
|
+
*/
|
|
70
|
+
async requestWithRetry(url, options = {}, maxRetries = 3) {
|
|
71
|
+
let lastError = null;
|
|
72
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
73
|
+
try {
|
|
74
|
+
return await this.request(url, options);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
lastError = error;
|
|
77
|
+
if (i < maxRetries - 1) {
|
|
78
|
+
await this.delay(1e3 * (i + 1));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw lastError;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 延迟
|
|
86
|
+
*/
|
|
87
|
+
delay(ms) {
|
|
88
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 创建同步结果
|
|
92
|
+
*/
|
|
93
|
+
createResult(success, data) {
|
|
94
|
+
return {
|
|
95
|
+
platform: this.meta.id,
|
|
96
|
+
success,
|
|
97
|
+
timestamp: Date.now(),
|
|
98
|
+
...data
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/adapters/registry.ts
|
|
104
|
+
var AdapterRegistry = class {
|
|
105
|
+
adapters = /* @__PURE__ */ new Map();
|
|
106
|
+
instances = /* @__PURE__ */ new Map();
|
|
107
|
+
runtime;
|
|
108
|
+
/**
|
|
109
|
+
* 设置运行时
|
|
110
|
+
*/
|
|
111
|
+
setRuntime(runtime) {
|
|
112
|
+
this.runtime = runtime;
|
|
113
|
+
this.instances.clear();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 注册适配器
|
|
117
|
+
*/
|
|
118
|
+
register(entry) {
|
|
119
|
+
this.adapters.set(entry.meta.id, entry);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 批量注册
|
|
123
|
+
*/
|
|
124
|
+
registerAll(entries) {
|
|
125
|
+
entries.forEach((entry) => this.register(entry));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 获取适配器实例
|
|
129
|
+
*/
|
|
130
|
+
async get(platformId) {
|
|
131
|
+
if (this.instances.has(platformId)) {
|
|
132
|
+
return this.instances.get(platformId);
|
|
133
|
+
}
|
|
134
|
+
const entry = this.adapters.get(platformId);
|
|
135
|
+
if (!entry) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (!this.runtime) {
|
|
139
|
+
throw new Error("Runtime not set. Call setRuntime() first.");
|
|
140
|
+
}
|
|
141
|
+
const adapter = entry.factory(this.runtime);
|
|
142
|
+
await adapter.init(this.runtime);
|
|
143
|
+
this.instances.set(platformId, adapter);
|
|
144
|
+
return adapter;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 获取所有平台元信息
|
|
148
|
+
*/
|
|
149
|
+
getAllMeta() {
|
|
150
|
+
return Array.from(this.adapters.values()).map((entry) => entry.meta);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 检查平台是否已注册
|
|
154
|
+
*/
|
|
155
|
+
has(platformId) {
|
|
156
|
+
return this.adapters.has(platformId);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 获取已注册的平台 ID 列表
|
|
160
|
+
*/
|
|
161
|
+
getRegisteredIds() {
|
|
162
|
+
return Array.from(this.adapters.keys());
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 清空注册
|
|
166
|
+
*/
|
|
167
|
+
clear() {
|
|
168
|
+
this.adapters.clear();
|
|
169
|
+
this.instances.clear();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 获取平台的预处理配置
|
|
173
|
+
*/
|
|
174
|
+
getPreprocessConfig(platformId) {
|
|
175
|
+
const entry = this.adapters.get(platformId);
|
|
176
|
+
return {
|
|
177
|
+
...DEFAULT_PREPROCESS_CONFIG,
|
|
178
|
+
...entry?.preprocessConfig || {}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 获取多个平台的预处理配置
|
|
183
|
+
*/
|
|
184
|
+
getPreprocessConfigs(platformIds) {
|
|
185
|
+
const configs = {};
|
|
186
|
+
for (const id of platformIds) {
|
|
187
|
+
configs[id] = this.getPreprocessConfig(id);
|
|
188
|
+
}
|
|
189
|
+
return configs;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var adapterRegistry = new AdapterRegistry();
|
|
193
|
+
function registerAdapter(entry) {
|
|
194
|
+
adapterRegistry.register(entry);
|
|
195
|
+
}
|
|
196
|
+
async function getAdapter(platformId) {
|
|
197
|
+
return adapterRegistry.get(platformId);
|
|
198
|
+
}
|
|
199
|
+
function getPreprocessConfig(platformId) {
|
|
200
|
+
return adapterRegistry.getPreprocessConfig(platformId);
|
|
201
|
+
}
|
|
202
|
+
function getPreprocessConfigs(platformIds) {
|
|
203
|
+
return adapterRegistry.getPreprocessConfigs(platformIds);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/adapters/platforms/douban.ts
|
|
207
|
+
var logger = createLogger("Douban");
|
|
208
|
+
var DoubanAdapter = class extends CodeAdapter {
|
|
209
|
+
meta = {
|
|
210
|
+
id: "douban",
|
|
211
|
+
name: "\u8C46\u74E3",
|
|
212
|
+
icon: "https://www.douban.com/favicon.ico",
|
|
213
|
+
homepage: "https://www.douban.com/note/create",
|
|
214
|
+
capabilities: ["article", "draft", "image_upload"]
|
|
215
|
+
};
|
|
216
|
+
/** 预处理配置: 豆瓣使用 Markdown 格式 (转换为 Draft.js) */
|
|
217
|
+
preprocessConfig = {
|
|
218
|
+
outputFormat: "markdown"
|
|
219
|
+
};
|
|
220
|
+
username = "";
|
|
221
|
+
avatar = "";
|
|
222
|
+
formData = null;
|
|
223
|
+
postParams = null;
|
|
224
|
+
/** 豆瓣 API 需要的 Header 规则 */
|
|
225
|
+
HEADER_RULES = [
|
|
226
|
+
{
|
|
227
|
+
urlFilter: "*://www.douban.com/*",
|
|
228
|
+
headers: {
|
|
229
|
+
"Origin": "https://www.douban.com",
|
|
230
|
+
"Referer": "https://www.douban.com"
|
|
231
|
+
},
|
|
232
|
+
resourceTypes: ["xmlhttprequest"]
|
|
233
|
+
}
|
|
234
|
+
];
|
|
235
|
+
async checkAuth() {
|
|
236
|
+
try {
|
|
237
|
+
const response = await this.runtime.fetch(
|
|
238
|
+
"https://www.douban.com/note/create",
|
|
239
|
+
{
|
|
240
|
+
method: "GET",
|
|
241
|
+
credentials: "include"
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
const html = await response.text();
|
|
245
|
+
const userNameMatch = html.match(/_USER_NAME\s*=\s*['"]([^'"]+)['"]/);
|
|
246
|
+
const userAvatarMatch = html.match(/_USER_AVATAR\s*=\s*['"]([^'"]+)['"]/);
|
|
247
|
+
const noteIdMatch = html.match(/name="note_id"\s+value="(\d+)"/);
|
|
248
|
+
const ckMatch = html.match(/name="ck"\s+value="([^"]+)"/);
|
|
249
|
+
const postParamsMatch = html.match(/_POST_PARAMS\s*=\s*(\{[\s\S]*?\});/);
|
|
250
|
+
if (!userNameMatch || !noteIdMatch || !ckMatch) {
|
|
251
|
+
return { isAuthenticated: false };
|
|
252
|
+
}
|
|
253
|
+
this.username = userNameMatch[1];
|
|
254
|
+
this.avatar = userAvatarMatch ? userAvatarMatch[1] : "";
|
|
255
|
+
this.formData = {
|
|
256
|
+
note_id: noteIdMatch[1],
|
|
257
|
+
ck: ckMatch[1]
|
|
258
|
+
};
|
|
259
|
+
if (postParamsMatch) {
|
|
260
|
+
try {
|
|
261
|
+
const siteCookieMatch = postParamsMatch[1].match(/siteCookie[^}]*value\s*:\s*['"]([^'"]+)['"]/);
|
|
262
|
+
if (siteCookieMatch) {
|
|
263
|
+
this.postParams = {
|
|
264
|
+
siteCookie: { value: siteCookieMatch[1] }
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
} catch (e) {
|
|
268
|
+
logger.warn("Failed to parse _POST_PARAMS:", e);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
logger.debug("Auth info:", {
|
|
272
|
+
username: this.username,
|
|
273
|
+
noteId: this.formData.note_id,
|
|
274
|
+
hasPostParams: !!this.postParams
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
isAuthenticated: true,
|
|
278
|
+
userId: this.username,
|
|
279
|
+
username: this.username,
|
|
280
|
+
avatar: this.avatar
|
|
281
|
+
};
|
|
282
|
+
} catch (error) {
|
|
283
|
+
logger.debug("checkAuth: not logged in -", error);
|
|
284
|
+
return { isAuthenticated: false, error: error.message };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async publish(article, options) {
|
|
288
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
289
|
+
logger.info("Starting publish...");
|
|
290
|
+
if (!this.formData) {
|
|
291
|
+
const auth = await this.checkAuth();
|
|
292
|
+
if (!auth.isAuthenticated) {
|
|
293
|
+
throw new Error("\u8BF7\u5148\u767B\u5F55\u8C46\u74E3");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
let content = article.markdown || "";
|
|
297
|
+
const imageDataMap = /* @__PURE__ */ new Map();
|
|
298
|
+
content = await this.processImages(
|
|
299
|
+
content,
|
|
300
|
+
async (src) => {
|
|
301
|
+
const result = await this.uploadImageWithFullData(src);
|
|
302
|
+
imageDataMap.set(result.url, result.imageData);
|
|
303
|
+
return result;
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
skipPatterns: ["doubanio.com", "douban.com"],
|
|
307
|
+
onProgress: options?.onImageProgress
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
const draftContent = markdownToDraft(content, imageDataMap);
|
|
311
|
+
const response = await this.runtime.fetch(
|
|
312
|
+
"https://www.douban.com/j/note/autosave",
|
|
313
|
+
{
|
|
314
|
+
method: "POST",
|
|
315
|
+
credentials: "include",
|
|
316
|
+
headers: {
|
|
317
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
318
|
+
},
|
|
319
|
+
body: new URLSearchParams({
|
|
320
|
+
is_rich: "1",
|
|
321
|
+
note_id: this.formData.note_id,
|
|
322
|
+
note_title: article.title,
|
|
323
|
+
note_text: draftContent,
|
|
324
|
+
introduction: "",
|
|
325
|
+
note_privacy: "P",
|
|
326
|
+
cannot_reply: "",
|
|
327
|
+
author_tags: "",
|
|
328
|
+
accept_donation: "",
|
|
329
|
+
donation_notice: "",
|
|
330
|
+
is_original: "",
|
|
331
|
+
ck: this.formData.ck
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
const res = await response.json();
|
|
336
|
+
logger.debug("Save response:", res);
|
|
337
|
+
const draftUrl = "https://www.douban.com/note/create";
|
|
338
|
+
return this.createResult(true, {
|
|
339
|
+
postId: this.formData.note_id,
|
|
340
|
+
postUrl: draftUrl
|
|
341
|
+
});
|
|
342
|
+
}).catch((error) => this.createResult(false, {
|
|
343
|
+
error: error.message
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* 上传图片并返回完整数据
|
|
348
|
+
*/
|
|
349
|
+
async uploadImageWithFullData(src) {
|
|
350
|
+
if (!this.formData || !this.postParams) {
|
|
351
|
+
throw new Error("\u672A\u83B7\u53D6\u4E0A\u4F20\u51ED\u8BC1");
|
|
352
|
+
}
|
|
353
|
+
const imageResponse = await fetch(src);
|
|
354
|
+
if (!imageResponse.ok) {
|
|
355
|
+
throw new Error("\u56FE\u7247\u4E0B\u8F7D\u5931\u8D25: " + src);
|
|
356
|
+
}
|
|
357
|
+
const imageBlob = await imageResponse.blob();
|
|
358
|
+
const formData = new FormData();
|
|
359
|
+
formData.append("note_id", this.formData.note_id);
|
|
360
|
+
formData.append("image_file", imageBlob, "image.jpg");
|
|
361
|
+
formData.append("ck", this.formData.ck);
|
|
362
|
+
formData.append("upload_auth_token", this.postParams.siteCookie.value);
|
|
363
|
+
const uploadResponse = await this.runtime.fetch(
|
|
364
|
+
"https://www.douban.com/j/note/add_photo",
|
|
365
|
+
{
|
|
366
|
+
method: "POST",
|
|
367
|
+
credentials: "include",
|
|
368
|
+
body: formData
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
const res = await uploadResponse.json();
|
|
372
|
+
logger.debug("Image upload response:", res);
|
|
373
|
+
if (!res.photo?.url) {
|
|
374
|
+
throw new Error("\u56FE\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
375
|
+
}
|
|
376
|
+
const photo = res.photo;
|
|
377
|
+
return {
|
|
378
|
+
url: photo.url,
|
|
379
|
+
imageData: {
|
|
380
|
+
id: photo.id,
|
|
381
|
+
url: photo.url,
|
|
382
|
+
thumb: photo.thumb,
|
|
383
|
+
width: photo.width,
|
|
384
|
+
height: photo.height,
|
|
385
|
+
file_name: photo.file_name,
|
|
386
|
+
file_size: photo.file_size
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/adapters/platforms/xueqiu.ts
|
|
393
|
+
import { Remarkable } from "remarkable";
|
|
394
|
+
var logger2 = createLogger("Xueqiu");
|
|
395
|
+
var XueqiuAdapter = class extends CodeAdapter {
|
|
396
|
+
meta = {
|
|
397
|
+
id: "xueqiu",
|
|
398
|
+
name: "\u96EA\u7403",
|
|
399
|
+
icon: "https://xqdoc.imedao.com/17aebcfb84a145d33fc18679.ico",
|
|
400
|
+
homepage: "https://mp.xueqiu.com/writeV2",
|
|
401
|
+
capabilities: ["article", "draft", "image_upload"]
|
|
402
|
+
};
|
|
403
|
+
/** 预处理配置: 雪球使用 Markdown 格式 */
|
|
404
|
+
preprocessConfig = {
|
|
405
|
+
outputFormat: "markdown",
|
|
406
|
+
// doPreFilter + processDocCode (旧版)
|
|
407
|
+
removeSpecialTags: true,
|
|
408
|
+
removeSpecialTagsWithParent: true,
|
|
409
|
+
processCodeBlocks: true
|
|
410
|
+
};
|
|
411
|
+
currentUser = null;
|
|
412
|
+
/** 雪球 API 需要的 Header 规则 */
|
|
413
|
+
HEADER_RULES = [
|
|
414
|
+
{
|
|
415
|
+
urlFilter: "*://mp.xueqiu.com/xq/*",
|
|
416
|
+
headers: {
|
|
417
|
+
"Origin": "https://mp.xueqiu.com",
|
|
418
|
+
"Referer": "https://mp.xueqiu.com/"
|
|
419
|
+
},
|
|
420
|
+
resourceTypes: ["xmlhttprequest"]
|
|
421
|
+
}
|
|
422
|
+
];
|
|
423
|
+
async checkAuth() {
|
|
424
|
+
try {
|
|
425
|
+
const response = await this.runtime.fetch(
|
|
426
|
+
"https://mp.xueqiu.com/writeV2",
|
|
427
|
+
{
|
|
428
|
+
method: "GET",
|
|
429
|
+
credentials: "include"
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
const html = await response.text();
|
|
433
|
+
const userMatch = html.match(/window\.UOM_CURRENTUSER\s*=\s*(\{[\s\S]*?\})\s*<\/script>/);
|
|
434
|
+
if (!userMatch) {
|
|
435
|
+
return { isAuthenticated: false };
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const state = JSON.parse(userMatch[1]);
|
|
439
|
+
const { currentUser } = state;
|
|
440
|
+
if (!currentUser?.id) {
|
|
441
|
+
return { isAuthenticated: false };
|
|
442
|
+
}
|
|
443
|
+
this.currentUser = currentUser;
|
|
444
|
+
const avatar = currentUser.photo_domain && currentUser.profile_image_url ? `https:${currentUser.photo_domain}${currentUser.profile_image_url.split(",")[0]}` : "";
|
|
445
|
+
return {
|
|
446
|
+
isAuthenticated: true,
|
|
447
|
+
userId: String(currentUser.id),
|
|
448
|
+
username: currentUser.screen_name,
|
|
449
|
+
avatar
|
|
450
|
+
};
|
|
451
|
+
} catch (e) {
|
|
452
|
+
logger2.error(" Failed to parse user data:", e);
|
|
453
|
+
return { isAuthenticated: false };
|
|
454
|
+
}
|
|
455
|
+
} catch (error) {
|
|
456
|
+
logger2.debug("checkAuth: not logged in -", error);
|
|
457
|
+
return { isAuthenticated: false, error: error.message };
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async publish(article, options) {
|
|
461
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
462
|
+
logger2.info("Starting publish...");
|
|
463
|
+
if (!this.currentUser) {
|
|
464
|
+
const auth = await this.checkAuth();
|
|
465
|
+
if (!auth.isAuthenticated) {
|
|
466
|
+
throw new Error("\u8BF7\u5148\u767B\u5F55\u96EA\u7403");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
let markdown = article.markdown || "";
|
|
470
|
+
markdown = await this.processImages(
|
|
471
|
+
markdown,
|
|
472
|
+
(src) => this.uploadImageByUrl(src),
|
|
473
|
+
{
|
|
474
|
+
skipPatterns: ["xueqiu.com", "imedao.com"],
|
|
475
|
+
onProgress: options?.onImageProgress
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
const md = new Remarkable({
|
|
479
|
+
html: true,
|
|
480
|
+
breaks: true
|
|
481
|
+
});
|
|
482
|
+
md.renderer.rules.heading_open = () => "<h4>";
|
|
483
|
+
md.renderer.rules.heading_close = () => "</h4>";
|
|
484
|
+
md.renderer.rules.strong_open = () => "<b>";
|
|
485
|
+
md.renderer.rules.strong_close = () => "</b>";
|
|
486
|
+
md.renderer.rules.em_open = () => "<i>";
|
|
487
|
+
md.renderer.rules.em_close = () => "</i>";
|
|
488
|
+
md.renderer.rules.bullet_list_open = () => "";
|
|
489
|
+
md.renderer.rules.bullet_list_close = () => "";
|
|
490
|
+
md.renderer.rules.ordered_list_open = () => "";
|
|
491
|
+
md.renderer.rules.ordered_list_close = () => "";
|
|
492
|
+
md.renderer.rules.list_item_open = () => "";
|
|
493
|
+
md.renderer.rules.list_item_close = () => "";
|
|
494
|
+
md.renderer.rules.hr = () => "";
|
|
495
|
+
md.renderer.rules.image = (tokens, idx) => {
|
|
496
|
+
const src = tokens[idx].src || "";
|
|
497
|
+
const alt = tokens[idx].alt || "";
|
|
498
|
+
return `<img src="${src}" alt="${alt}" class="ke_img">`;
|
|
499
|
+
};
|
|
500
|
+
let rendered = md.render(markdown);
|
|
501
|
+
rendered = rendered.replace(/<p>\s*<\/p>/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
502
|
+
const content = rendered;
|
|
503
|
+
const formData = new URLSearchParams({
|
|
504
|
+
text: content,
|
|
505
|
+
title: article.title,
|
|
506
|
+
cover_pic: "",
|
|
507
|
+
flags: "false",
|
|
508
|
+
original_event: "",
|
|
509
|
+
status_id: "",
|
|
510
|
+
legal_user_visible: "false",
|
|
511
|
+
is_private: "false"
|
|
512
|
+
});
|
|
513
|
+
const response = await this.runtime.fetch(
|
|
514
|
+
"https://mp.xueqiu.com/xq/statuses/draft/save.json",
|
|
515
|
+
{
|
|
516
|
+
method: "POST",
|
|
517
|
+
credentials: "include",
|
|
518
|
+
headers: {
|
|
519
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
520
|
+
},
|
|
521
|
+
body: formData
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
const res = await response.json();
|
|
525
|
+
logger2.debug(" Save response:", res);
|
|
526
|
+
if (!res.id) {
|
|
527
|
+
throw new Error(res.error_description || "\u4FDD\u5B58\u5931\u8D25");
|
|
528
|
+
}
|
|
529
|
+
const postId = res.id;
|
|
530
|
+
const draftUrl = `https://mp.xueqiu.com/write/draft/${postId}`;
|
|
531
|
+
return this.createResult(true, {
|
|
532
|
+
postId: String(postId),
|
|
533
|
+
postUrl: draftUrl
|
|
534
|
+
});
|
|
535
|
+
}).catch((error) => this.createResult(false, {
|
|
536
|
+
error: error.message
|
|
537
|
+
}));
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* 通过 URL 上传图片
|
|
541
|
+
*/
|
|
542
|
+
async uploadImageByUrl(src) {
|
|
543
|
+
const imageResponse = await fetch(src);
|
|
544
|
+
if (!imageResponse.ok) {
|
|
545
|
+
throw new Error("\u56FE\u7247\u4E0B\u8F7D\u5931\u8D25: " + src);
|
|
546
|
+
}
|
|
547
|
+
const imageBlob = await imageResponse.blob();
|
|
548
|
+
const formData = new FormData();
|
|
549
|
+
formData.append("file", imageBlob, "image.jpg");
|
|
550
|
+
const uploadResponse = await this.runtime.fetch(
|
|
551
|
+
"https://mp.xueqiu.com/xq/photo/upload.json",
|
|
552
|
+
{
|
|
553
|
+
method: "POST",
|
|
554
|
+
credentials: "include",
|
|
555
|
+
body: formData
|
|
556
|
+
}
|
|
557
|
+
);
|
|
558
|
+
const res = await uploadResponse.json();
|
|
559
|
+
logger2.debug(" Image upload response:", res);
|
|
560
|
+
if (!res.url || !res.filename) {
|
|
561
|
+
throw new Error("\u56FE\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
562
|
+
}
|
|
563
|
+
const fullUrl = res.url.startsWith("//") ? `https:${res.url}/${res.filename}` : `${res.url}/${res.filename}`;
|
|
564
|
+
return {
|
|
565
|
+
url: fullUrl
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// src/adapters/platforms/woshipm.ts
|
|
571
|
+
var logger3 = createLogger("Woshipm");
|
|
572
|
+
var WoshipmAdapter = class extends CodeAdapter {
|
|
573
|
+
meta = {
|
|
574
|
+
id: "woshipm",
|
|
575
|
+
name: "\u4EBA\u4EBA\u90FD\u662F\u4EA7\u54C1\u7ECF\u7406",
|
|
576
|
+
icon: "https://www.woshipm.com/favicon.ico",
|
|
577
|
+
homepage: "https://www.woshipm.com",
|
|
578
|
+
capabilities: ["article", "draft", "image_upload"]
|
|
579
|
+
};
|
|
580
|
+
/** 预处理配置: 人人都是产品经理使用 HTML 格式 */
|
|
581
|
+
preprocessConfig = {
|
|
582
|
+
outputFormat: "html",
|
|
583
|
+
removeEmptyLines: true
|
|
584
|
+
};
|
|
585
|
+
jltoken = "";
|
|
586
|
+
/** 人人都是产品经理 API 需要的 Header 规则 */
|
|
587
|
+
HEADER_RULES = [
|
|
588
|
+
{
|
|
589
|
+
urlFilter: "*://woshipm.com/wp-admin/admin-ajax.php*",
|
|
590
|
+
headers: { "X-Requested-With": "XMLHttpRequest" },
|
|
591
|
+
resourceTypes: ["xmlhttprequest"]
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
urlFilter: "*://woshipm.com/api2/*",
|
|
595
|
+
headers: { "X-Requested-With": "XMLHttpRequest" },
|
|
596
|
+
resourceTypes: ["xmlhttprequest"]
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
urlFilter: "*://woshipm.com/tensorflow/upyun/upload*",
|
|
600
|
+
headers: { "X-Requested-With": "XMLHttpRequest" },
|
|
601
|
+
resourceTypes: ["xmlhttprequest"]
|
|
602
|
+
}
|
|
603
|
+
];
|
|
604
|
+
async checkAuth() {
|
|
605
|
+
try {
|
|
606
|
+
const pageResponse = await this.runtime.fetch("https://www.woshipm.com/writing", {
|
|
607
|
+
method: "GET",
|
|
608
|
+
credentials: "include"
|
|
609
|
+
});
|
|
610
|
+
const pageText = await pageResponse.text();
|
|
611
|
+
const jltokenMatch = pageText.match(/"jltoken"\s*:\s*"([^"]+)"/);
|
|
612
|
+
if (jltokenMatch) {
|
|
613
|
+
this.jltoken = jltokenMatch[1];
|
|
614
|
+
logger3.debug("Found jltoken");
|
|
615
|
+
}
|
|
616
|
+
const uidMatch = pageText.match(/var\s+userSettings\s*=\s*\{[^}]*"uid"\s*:\s*"(\d+)"/);
|
|
617
|
+
if (!uidMatch) {
|
|
618
|
+
return { isAuthenticated: false };
|
|
619
|
+
}
|
|
620
|
+
const uid = uidMatch[1];
|
|
621
|
+
const response = await this.runtime.fetch(
|
|
622
|
+
`https://www.woshipm.com/api2/user/profile?uid=${uid}`,
|
|
623
|
+
{
|
|
624
|
+
method: "GET",
|
|
625
|
+
credentials: "include",
|
|
626
|
+
headers: {
|
|
627
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
const data = await response.json();
|
|
632
|
+
if (data.CODE === 200 && data.RESULT?.userInfoVo?.uid) {
|
|
633
|
+
return {
|
|
634
|
+
isAuthenticated: true,
|
|
635
|
+
userId: String(data.RESULT.userInfoVo.uid),
|
|
636
|
+
username: data.RESULT.userInfoVo.nickName,
|
|
637
|
+
avatar: data.RESULT.userInfoVo.avartar
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return { isAuthenticated: false };
|
|
641
|
+
} catch (error) {
|
|
642
|
+
logger3.debug("checkAuth: not logged in -", error);
|
|
643
|
+
return { isAuthenticated: false, error: error.message };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async publish(article, options) {
|
|
647
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
648
|
+
logger3.info("Starting publish...");
|
|
649
|
+
let content = article.html || "";
|
|
650
|
+
content = await this.processImages(
|
|
651
|
+
content,
|
|
652
|
+
(src) => this.uploadImageByUrl(src),
|
|
653
|
+
{
|
|
654
|
+
skipPatterns: ["woshipm.com", "image.woshipm.com"],
|
|
655
|
+
onProgress: options?.onImageProgress
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
const createResponse = await this.runtime.fetch(
|
|
659
|
+
"https://www.woshipm.com/wp-admin/admin-ajax.php",
|
|
660
|
+
{
|
|
661
|
+
method: "POST",
|
|
662
|
+
credentials: "include",
|
|
663
|
+
headers: {
|
|
664
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
665
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
666
|
+
},
|
|
667
|
+
body: new URLSearchParams({
|
|
668
|
+
action: "add_draft",
|
|
669
|
+
post_title: article.title,
|
|
670
|
+
post_content: content
|
|
671
|
+
})
|
|
672
|
+
}
|
|
673
|
+
);
|
|
674
|
+
const responseText = await createResponse.text();
|
|
675
|
+
logger3.debug("Create draft response:", createResponse.status, responseText.substring(0, 300));
|
|
676
|
+
if (!createResponse.ok) {
|
|
677
|
+
throw new Error(`\u521B\u5EFA\u8349\u7A3F\u5931\u8D25: ${createResponse.status} - ${responseText}`);
|
|
678
|
+
}
|
|
679
|
+
let createData;
|
|
680
|
+
try {
|
|
681
|
+
createData = JSON.parse(responseText);
|
|
682
|
+
} catch {
|
|
683
|
+
throw new Error(`\u521B\u5EFA\u8349\u7A3F\u5931\u8D25: \u54CD\u5E94\u4E0D\u662F\u6709\u6548 JSON - ${responseText.substring(0, 100)}`);
|
|
684
|
+
}
|
|
685
|
+
if (!createData.post_id) {
|
|
686
|
+
throw new Error(createData.error || "\u521B\u5EFA\u8349\u7A3F\u5931\u8D25: \u65E0\u6548\u54CD\u5E94");
|
|
687
|
+
}
|
|
688
|
+
const draftId = String(createData.post_id);
|
|
689
|
+
const draftUrl = createData.url || `https://www.woshipm.com/writing?pid=${draftId}`;
|
|
690
|
+
logger3.debug("Draft created:", draftId);
|
|
691
|
+
return this.createResult(true, {
|
|
692
|
+
postId: draftId,
|
|
693
|
+
postUrl: draftUrl
|
|
694
|
+
});
|
|
695
|
+
}).catch((error) => this.createResult(false, {
|
|
696
|
+
error: error.message
|
|
697
|
+
}));
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* 通过 Blob 上传图片(覆盖基类方法)
|
|
701
|
+
*/
|
|
702
|
+
async uploadImage(file, filename) {
|
|
703
|
+
return this.uploadImageBinaryInternal(file, filename || "image.png");
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* 通过 URL 上传图片
|
|
707
|
+
*/
|
|
708
|
+
async uploadImageByUrl(src) {
|
|
709
|
+
try {
|
|
710
|
+
const imageResponse = await this.runtime.fetch(src, {
|
|
711
|
+
credentials: "omit"
|
|
712
|
+
});
|
|
713
|
+
if (!imageResponse.ok) {
|
|
714
|
+
throw new Error(`Failed to fetch image: ${imageResponse.status}`);
|
|
715
|
+
}
|
|
716
|
+
const blob = await imageResponse.blob();
|
|
717
|
+
const url = await this.uploadImageBinaryInternal(blob, this.getFilenameFromUrl(src));
|
|
718
|
+
return { url };
|
|
719
|
+
} catch (error) {
|
|
720
|
+
logger3.warn("Failed to upload image by URL:", src, error);
|
|
721
|
+
return { url: src };
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* 上传图片 (二进制方式) - 内部使用
|
|
726
|
+
*/
|
|
727
|
+
async uploadImageBinaryInternal(file, filename) {
|
|
728
|
+
const formData = new FormData();
|
|
729
|
+
formData.append("action", "wpuf_insert_image");
|
|
730
|
+
formData.append("name", filename);
|
|
731
|
+
formData.append("files", file, filename);
|
|
732
|
+
const headers = {
|
|
733
|
+
"Origin": "https://www.woshipm.com",
|
|
734
|
+
"Referer": "https://www.woshipm.com/writing"
|
|
735
|
+
};
|
|
736
|
+
if (this.jltoken) {
|
|
737
|
+
headers["jlstar"] = `Bearer ${this.jltoken}`;
|
|
738
|
+
}
|
|
739
|
+
const response = await this.runtime.fetch("https://www.woshipm.com/tensorflow/upyun/upload", {
|
|
740
|
+
method: "POST",
|
|
741
|
+
credentials: "include",
|
|
742
|
+
headers,
|
|
743
|
+
body: formData
|
|
744
|
+
});
|
|
745
|
+
const data = await response.json();
|
|
746
|
+
if (data.data && data.data.length > 0 && data.data[0].url) {
|
|
747
|
+
logger3.debug("Uploaded image:", filename, "->", data.data[0].url);
|
|
748
|
+
return data.data[0].url;
|
|
749
|
+
}
|
|
750
|
+
throw new Error(data.error || "Failed to upload image");
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* 从 URL 提取文件名
|
|
754
|
+
*/
|
|
755
|
+
getFilenameFromUrl(url) {
|
|
756
|
+
try {
|
|
757
|
+
const pathname = new URL(url).pathname;
|
|
758
|
+
const filename = pathname.split("/").pop() || "image.png";
|
|
759
|
+
return filename;
|
|
760
|
+
} catch {
|
|
761
|
+
return "image.png";
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
// src/adapters/platforms/yuque.ts
|
|
767
|
+
var logger4 = createLogger("Yuque");
|
|
768
|
+
var YuqueAdapter = class extends CodeAdapter {
|
|
769
|
+
meta = {
|
|
770
|
+
id: "yuque",
|
|
771
|
+
name: "\u8BED\u96C0",
|
|
772
|
+
icon: "https://gw.alipayobjects.com/zos/rmsportal/UTjFYEzMSYVwzxIGVhMu.png",
|
|
773
|
+
homepage: "https://www.yuque.com/dashboard",
|
|
774
|
+
capabilities: ["article", "draft", "image_upload"]
|
|
775
|
+
};
|
|
776
|
+
/** 预处理配置: 语雀使用 Markdown 格式 (转换为 lake) */
|
|
777
|
+
preprocessConfig = {
|
|
778
|
+
outputFormat: "markdown",
|
|
779
|
+
// doPreFilter + processDocCode (旧版)
|
|
780
|
+
removeSpecialTags: true,
|
|
781
|
+
removeSpecialTagsWithParent: true,
|
|
782
|
+
processCodeBlocks: true
|
|
783
|
+
};
|
|
784
|
+
userInfo = null;
|
|
785
|
+
bookId = null;
|
|
786
|
+
csrfToken = "";
|
|
787
|
+
currentPostId = null;
|
|
788
|
+
/** 语雀 API 需要的 Header 规则 */
|
|
789
|
+
HEADER_RULES = [
|
|
790
|
+
{
|
|
791
|
+
urlFilter: "*://www.yuque.com/api/*",
|
|
792
|
+
headers: {
|
|
793
|
+
"Origin": "https://www.yuque.com",
|
|
794
|
+
"Referer": "https://www.yuque.com/dashboard"
|
|
795
|
+
},
|
|
796
|
+
resourceTypes: ["xmlhttprequest"]
|
|
797
|
+
}
|
|
798
|
+
];
|
|
799
|
+
async getCsrfToken() {
|
|
800
|
+
if (this.runtime.getCookie) {
|
|
801
|
+
const value = await this.runtime.getCookie(".yuque.com", "yuque_ctoken");
|
|
802
|
+
if (!value) {
|
|
803
|
+
throw new Error("\u8BF7\u5148\u767B\u5F55\u8BED\u96C0");
|
|
804
|
+
}
|
|
805
|
+
return value;
|
|
806
|
+
}
|
|
807
|
+
throw new Error("\u8BF7\u5148\u767B\u5F55\u8BED\u96C0");
|
|
808
|
+
}
|
|
809
|
+
async checkAuth() {
|
|
810
|
+
try {
|
|
811
|
+
this.csrfToken = await this.getCsrfToken();
|
|
812
|
+
const response = await this.runtime.fetch(
|
|
813
|
+
"https://www.yuque.com/api/mine/common_used",
|
|
814
|
+
{
|
|
815
|
+
method: "GET",
|
|
816
|
+
credentials: "include",
|
|
817
|
+
headers: {
|
|
818
|
+
"x-csrf-token": this.csrfToken
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
const res = await response.json();
|
|
823
|
+
logger4.debug("checkAuth response:", res);
|
|
824
|
+
if (res.data?.books && res.data.books.length > 0) {
|
|
825
|
+
const firstBook = res.data.books[0];
|
|
826
|
+
this.userInfo = firstBook.user;
|
|
827
|
+
this.bookId = firstBook.target_id;
|
|
828
|
+
return {
|
|
829
|
+
isAuthenticated: true,
|
|
830
|
+
userId: String(firstBook.user.id),
|
|
831
|
+
username: firstBook.user.name,
|
|
832
|
+
avatar: firstBook.user.avatar_url
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return { isAuthenticated: false };
|
|
836
|
+
} catch (error) {
|
|
837
|
+
logger4.debug("checkAuth: not logged in -", error.message);
|
|
838
|
+
return { isAuthenticated: false, error: error.message };
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async publish(article, options) {
|
|
842
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
843
|
+
logger4.info("Starting publish...");
|
|
844
|
+
if (!this.userInfo || !this.bookId) {
|
|
845
|
+
const auth = await this.checkAuth();
|
|
846
|
+
if (!auth.isAuthenticated) {
|
|
847
|
+
throw new Error("\u8BF7\u5148\u767B\u5F55\u8BED\u96C0");
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
const createResponse = await this.runtime.fetch(
|
|
851
|
+
"https://www.yuque.com/api/docs",
|
|
852
|
+
{
|
|
853
|
+
method: "POST",
|
|
854
|
+
credentials: "include",
|
|
855
|
+
headers: {
|
|
856
|
+
"Content-Type": "application/json",
|
|
857
|
+
"x-csrf-token": this.csrfToken
|
|
858
|
+
},
|
|
859
|
+
body: JSON.stringify({
|
|
860
|
+
title: article.title,
|
|
861
|
+
type: "Doc",
|
|
862
|
+
format: "lake",
|
|
863
|
+
book_id: this.bookId,
|
|
864
|
+
status: 0
|
|
865
|
+
})
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
const createRes = await createResponse.json();
|
|
869
|
+
logger4.debug("Create doc response:", createRes);
|
|
870
|
+
if (!createRes.data?.id) {
|
|
871
|
+
throw new Error(createRes.message || "\u521B\u5EFA\u6587\u6863\u5931\u8D25");
|
|
872
|
+
}
|
|
873
|
+
const postId = createRes.data.id;
|
|
874
|
+
this.currentPostId = postId;
|
|
875
|
+
let markdown = article.markdown || "";
|
|
876
|
+
markdown = await this.processImages(
|
|
877
|
+
markdown,
|
|
878
|
+
(src) => this.uploadImageByUrl(src),
|
|
879
|
+
{
|
|
880
|
+
skipPatterns: ["yuque.com", "cdn.nlark.com"],
|
|
881
|
+
onProgress: options?.onImageProgress
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
const convertResponse = await this.runtime.fetch(
|
|
885
|
+
"https://www.yuque.com/api/docs/convert",
|
|
886
|
+
{
|
|
887
|
+
method: "POST",
|
|
888
|
+
credentials: "include",
|
|
889
|
+
headers: {
|
|
890
|
+
"Content-Type": "application/json",
|
|
891
|
+
"x-csrf-token": this.csrfToken
|
|
892
|
+
},
|
|
893
|
+
body: JSON.stringify({
|
|
894
|
+
from: "markdown",
|
|
895
|
+
to: "lake",
|
|
896
|
+
content: markdown
|
|
897
|
+
})
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
const convertRes = await convertResponse.json();
|
|
901
|
+
if (!convertRes.data?.content) {
|
|
902
|
+
throw new Error("\u5185\u5BB9\u8F6C\u6362\u5931\u8D25");
|
|
903
|
+
}
|
|
904
|
+
const lakeContent = convertRes.data.content;
|
|
905
|
+
const saveResponse = await this.runtime.fetch(
|
|
906
|
+
`https://www.yuque.com/api/docs/${postId}/content`,
|
|
907
|
+
{
|
|
908
|
+
method: "PUT",
|
|
909
|
+
credentials: "include",
|
|
910
|
+
headers: {
|
|
911
|
+
"Content-Type": "application/json",
|
|
912
|
+
"x-csrf-token": this.csrfToken
|
|
913
|
+
},
|
|
914
|
+
body: JSON.stringify({
|
|
915
|
+
format: "lake",
|
|
916
|
+
body_asl: lakeContent,
|
|
917
|
+
body: `<div class="lake-content" typography="traditional">${lakeContent}</div>`,
|
|
918
|
+
body_html: `<div class="lake-content" typography="traditional">${lakeContent}</div>`,
|
|
919
|
+
draft_version: 0,
|
|
920
|
+
sync_dynamic_data: false,
|
|
921
|
+
save_type: "auto",
|
|
922
|
+
edit_type: "Lake"
|
|
923
|
+
})
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
const saveRes = await saveResponse.json();
|
|
927
|
+
logger4.debug("Save response:", saveRes);
|
|
928
|
+
const draftUrl = `https://www.yuque.com/go/doc/${postId}/edit`;
|
|
929
|
+
return this.createResult(true, {
|
|
930
|
+
postId: String(postId),
|
|
931
|
+
postUrl: draftUrl
|
|
932
|
+
});
|
|
933
|
+
}).catch((error) => this.createResult(false, {
|
|
934
|
+
error: error.message
|
|
935
|
+
}));
|
|
936
|
+
}
|
|
937
|
+
async uploadImageByUrl(src) {
|
|
938
|
+
if (!this.currentPostId) {
|
|
939
|
+
throw new Error("\u6587\u6863 ID \u672A\u8BBE\u7F6E");
|
|
940
|
+
}
|
|
941
|
+
const imageResponse = await fetch(src);
|
|
942
|
+
if (!imageResponse.ok) {
|
|
943
|
+
throw new Error("\u56FE\u7247\u4E0B\u8F7D\u5931\u8D25: " + src);
|
|
944
|
+
}
|
|
945
|
+
const imageBlob = await imageResponse.blob();
|
|
946
|
+
const formData = new FormData();
|
|
947
|
+
formData.append("file", imageBlob, "image.jpg");
|
|
948
|
+
const uploadUrl = `https://www.yuque.com/api/upload/attach?attachable_type=Doc&attachable_id=${this.currentPostId}&type=image`;
|
|
949
|
+
const uploadResponse = await this.runtime.fetch(uploadUrl, {
|
|
950
|
+
method: "POST",
|
|
951
|
+
credentials: "include",
|
|
952
|
+
headers: {
|
|
953
|
+
"x-csrf-token": this.csrfToken
|
|
954
|
+
},
|
|
955
|
+
body: formData
|
|
956
|
+
});
|
|
957
|
+
const res = await uploadResponse.json();
|
|
958
|
+
logger4.debug("Image upload response:", res);
|
|
959
|
+
if (!res.data?.url) {
|
|
960
|
+
throw new Error("\u56FE\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
961
|
+
}
|
|
962
|
+
return {
|
|
963
|
+
url: res.data.url
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// src/adapters/platforms/imooc.ts
|
|
969
|
+
var ImoocAdapter = class extends CodeAdapter {
|
|
970
|
+
meta = {
|
|
971
|
+
id: "imooc",
|
|
972
|
+
name: "\u6155\u8BFE\u624B\u8BB0",
|
|
973
|
+
icon: "https://www.imooc.com/favicon.ico",
|
|
974
|
+
homepage: "https://www.imooc.com/article",
|
|
975
|
+
capabilities: ["article", "draft", "image_upload"]
|
|
976
|
+
};
|
|
977
|
+
/** 预处理配置: 慕课网使用 Markdown 格式 */
|
|
978
|
+
preprocessConfig = {
|
|
979
|
+
outputFormat: "markdown"
|
|
980
|
+
};
|
|
981
|
+
/** 慕课网 API 需要的 Header 规则 */
|
|
982
|
+
HEADER_RULES = [
|
|
983
|
+
{
|
|
984
|
+
urlFilter: "*://www.imooc.com/article/*",
|
|
985
|
+
headers: {
|
|
986
|
+
Origin: "https://www.imooc.com",
|
|
987
|
+
Referer: "https://www.imooc.com/"
|
|
988
|
+
},
|
|
989
|
+
resourceTypes: ["xmlhttprequest"]
|
|
990
|
+
}
|
|
991
|
+
];
|
|
992
|
+
/**
|
|
993
|
+
* 检查登录状态
|
|
994
|
+
*/
|
|
995
|
+
async checkAuth() {
|
|
996
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
997
|
+
const response = await this.runtime.fetch("https://www.imooc.com/u/card", {
|
|
998
|
+
credentials: "include"
|
|
999
|
+
});
|
|
1000
|
+
let text = await response.text();
|
|
1001
|
+
text = text.replace("jsonpcallback(", "").replace("})", "}");
|
|
1002
|
+
const result = JSON.parse(text);
|
|
1003
|
+
if (result.result !== 0) {
|
|
1004
|
+
return { isAuthenticated: false, error: result.msg || "\u672A\u767B\u5F55" };
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
isAuthenticated: true,
|
|
1008
|
+
userId: result.data.uid,
|
|
1009
|
+
username: result.data.nickname,
|
|
1010
|
+
avatar: result.data.img
|
|
1011
|
+
};
|
|
1012
|
+
}).catch((error) => ({ isAuthenticated: false, error: error.message }));
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* 上传图片
|
|
1016
|
+
*/
|
|
1017
|
+
async uploadImageByUrl(url) {
|
|
1018
|
+
const imageResponse = await this.runtime.fetch(url);
|
|
1019
|
+
const blob = await imageResponse.blob();
|
|
1020
|
+
const formData = new FormData();
|
|
1021
|
+
const filename = `${Date.now()}.jpg`;
|
|
1022
|
+
const file = new File([blob], filename, { type: blob.type || "image/jpeg" });
|
|
1023
|
+
formData.append("photo", file, filename);
|
|
1024
|
+
formData.append("type", file.type);
|
|
1025
|
+
formData.append("id", "WU_FILE_0");
|
|
1026
|
+
formData.append("name", filename);
|
|
1027
|
+
formData.append("lastModifiedDate", (/* @__PURE__ */ new Date()).toString());
|
|
1028
|
+
formData.append("size", String(file.size));
|
|
1029
|
+
const response = await this.runtime.fetch(
|
|
1030
|
+
"https://www.imooc.com/article/ajaxuploadimg",
|
|
1031
|
+
{
|
|
1032
|
+
method: "POST",
|
|
1033
|
+
credentials: "include",
|
|
1034
|
+
body: formData
|
|
1035
|
+
}
|
|
1036
|
+
);
|
|
1037
|
+
const res = await response.json();
|
|
1038
|
+
if (res.result !== 0) {
|
|
1039
|
+
throw new Error(res.msg || "\u56FE\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
1040
|
+
}
|
|
1041
|
+
let imgUrl = res.data.imgpath;
|
|
1042
|
+
if (imgUrl.startsWith("//")) {
|
|
1043
|
+
imgUrl = "https:" + imgUrl;
|
|
1044
|
+
}
|
|
1045
|
+
return { url: imgUrl };
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* 发布文章
|
|
1049
|
+
*/
|
|
1050
|
+
async publish(article) {
|
|
1051
|
+
const now = Date.now();
|
|
1052
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
1053
|
+
let content = article.markdown || article.html || "";
|
|
1054
|
+
content = await this.processImages(content, (src) => this.uploadImageByUrl(src));
|
|
1055
|
+
const response = await this.runtime.fetch("https://www.imooc.com/article/savedraft", {
|
|
1056
|
+
method: "POST",
|
|
1057
|
+
credentials: "include",
|
|
1058
|
+
headers: {
|
|
1059
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1060
|
+
},
|
|
1061
|
+
body: new URLSearchParams({
|
|
1062
|
+
editor: "0",
|
|
1063
|
+
draft_id: "0",
|
|
1064
|
+
title: article.title,
|
|
1065
|
+
content
|
|
1066
|
+
})
|
|
1067
|
+
});
|
|
1068
|
+
const res = await response.json();
|
|
1069
|
+
if (!res.data) {
|
|
1070
|
+
throw new Error("\u53D1\u5E03\u5931\u8D25");
|
|
1071
|
+
}
|
|
1072
|
+
return {
|
|
1073
|
+
platform: this.meta.id,
|
|
1074
|
+
success: true,
|
|
1075
|
+
postId: res.data,
|
|
1076
|
+
postUrl: `https://www.imooc.com/article/draft/id/${res.data}`,
|
|
1077
|
+
timestamp: now
|
|
1078
|
+
};
|
|
1079
|
+
}).catch((error) => ({
|
|
1080
|
+
platform: this.meta.id,
|
|
1081
|
+
success: false,
|
|
1082
|
+
error: error.message,
|
|
1083
|
+
timestamp: now
|
|
1084
|
+
}));
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
// src/adapters/platforms/oschina.ts
|
|
1089
|
+
var OschinaAdapter = class extends CodeAdapter {
|
|
1090
|
+
meta = {
|
|
1091
|
+
id: "oschina",
|
|
1092
|
+
name: "\u5F00\u6E90\u4E2D\u56FD",
|
|
1093
|
+
icon: "https://www.oschina.net/favicon.ico",
|
|
1094
|
+
homepage: "https://my.oschina.net",
|
|
1095
|
+
capabilities: ["article", "draft", "image_upload"]
|
|
1096
|
+
};
|
|
1097
|
+
/** 预处理配置: 开源中国使用 Markdown 格式 */
|
|
1098
|
+
preprocessConfig = {
|
|
1099
|
+
outputFormat: "markdown"
|
|
1100
|
+
};
|
|
1101
|
+
userId = null;
|
|
1102
|
+
/** 开源中国 API 需要的 Header 规则 */
|
|
1103
|
+
HEADER_RULES = [
|
|
1104
|
+
{
|
|
1105
|
+
urlFilter: "*://apiv1.oschina.net/oschinapi/*",
|
|
1106
|
+
headers: {
|
|
1107
|
+
Origin: "https://my.oschina.net",
|
|
1108
|
+
Referer: "https://my.oschina.net/"
|
|
1109
|
+
},
|
|
1110
|
+
resourceTypes: ["xmlhttprequest"]
|
|
1111
|
+
}
|
|
1112
|
+
];
|
|
1113
|
+
/**
|
|
1114
|
+
* 检查登录状态
|
|
1115
|
+
*/
|
|
1116
|
+
async checkAuth() {
|
|
1117
|
+
try {
|
|
1118
|
+
const response = await this.runtime.fetch("https://apiv1.oschina.net/oschinapi/user/myDetails", {
|
|
1119
|
+
credentials: "include"
|
|
1120
|
+
});
|
|
1121
|
+
const data = await response.json();
|
|
1122
|
+
if (!data.success || !data.result?.userId) {
|
|
1123
|
+
return { isAuthenticated: false, error: "\u672A\u767B\u5F55" };
|
|
1124
|
+
}
|
|
1125
|
+
this.userId = String(data.result.userId);
|
|
1126
|
+
return {
|
|
1127
|
+
isAuthenticated: true,
|
|
1128
|
+
userId: this.userId,
|
|
1129
|
+
username: data.result.userVo?.name || this.userId,
|
|
1130
|
+
avatar: data.result.userVo?.portraitUrl
|
|
1131
|
+
};
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
return { isAuthenticated: false, error: error.message };
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* 上传图片
|
|
1138
|
+
*/
|
|
1139
|
+
async uploadImageByUrl(url) {
|
|
1140
|
+
if (!this.userId) {
|
|
1141
|
+
await this.checkAuth();
|
|
1142
|
+
}
|
|
1143
|
+
const imageResponse = await this.runtime.fetch(url);
|
|
1144
|
+
const blob = await imageResponse.blob();
|
|
1145
|
+
const filename = this.getFilenameFromUrl(url) || "image";
|
|
1146
|
+
const formData = new FormData();
|
|
1147
|
+
formData.append("file", blob, filename);
|
|
1148
|
+
const response = await this.runtime.fetch(
|
|
1149
|
+
"https://apiv1.oschina.net/oschinapi/ai/creation/project/uploadDetail",
|
|
1150
|
+
{
|
|
1151
|
+
method: "POST",
|
|
1152
|
+
credentials: "include",
|
|
1153
|
+
body: formData
|
|
1154
|
+
}
|
|
1155
|
+
);
|
|
1156
|
+
const res = await response.json();
|
|
1157
|
+
if (!res.success || !res.result) {
|
|
1158
|
+
throw new Error(res.message || "\u56FE\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
1159
|
+
}
|
|
1160
|
+
return { url: res.result };
|
|
1161
|
+
}
|
|
1162
|
+
getFilenameFromUrl(url) {
|
|
1163
|
+
try {
|
|
1164
|
+
const pathname = new URL(url).pathname;
|
|
1165
|
+
const name = pathname.split("/").pop();
|
|
1166
|
+
return name && name.trim() ? name : null;
|
|
1167
|
+
} catch {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* 发布文章
|
|
1173
|
+
*/
|
|
1174
|
+
async publish(article) {
|
|
1175
|
+
const now = Date.now();
|
|
1176
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
1177
|
+
if (!this.userId) {
|
|
1178
|
+
const auth = await this.checkAuth();
|
|
1179
|
+
if (!auth.isAuthenticated) {
|
|
1180
|
+
throw new Error("\u672A\u767B\u5F55");
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const rawMarkdown = article.markdown || "";
|
|
1184
|
+
const rawHtml = article.html || "";
|
|
1185
|
+
const useMarkdown = rawMarkdown.trim().length > 0;
|
|
1186
|
+
let content = useMarkdown ? rawMarkdown : rawHtml;
|
|
1187
|
+
content = await this.processImages(content, (src) => this.uploadImageByUrl(src));
|
|
1188
|
+
const response = await this.runtime.fetch(
|
|
1189
|
+
"https://apiv1.oschina.net/oschinapi/api/draft/save_draft",
|
|
1190
|
+
{
|
|
1191
|
+
method: "POST",
|
|
1192
|
+
credentials: "include",
|
|
1193
|
+
headers: {
|
|
1194
|
+
"Content-Type": "application/json"
|
|
1195
|
+
},
|
|
1196
|
+
body: JSON.stringify({
|
|
1197
|
+
title: article.title,
|
|
1198
|
+
user: Number(this.userId),
|
|
1199
|
+
content,
|
|
1200
|
+
contentType: useMarkdown ? 1 : 2,
|
|
1201
|
+
// 1=markdown, 2=html
|
|
1202
|
+
catalog: 0,
|
|
1203
|
+
originUrl: "",
|
|
1204
|
+
privacy: true,
|
|
1205
|
+
disableComment: false
|
|
1206
|
+
})
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
const res = await response.json();
|
|
1210
|
+
if (!res.success || !res.result?.id) {
|
|
1211
|
+
throw new Error(res.message || "\u53D1\u5E03\u5931\u8D25");
|
|
1212
|
+
}
|
|
1213
|
+
const draftId = String(res.result.id);
|
|
1214
|
+
return {
|
|
1215
|
+
platform: this.meta.id,
|
|
1216
|
+
success: true,
|
|
1217
|
+
postId: draftId,
|
|
1218
|
+
postUrl: `https://my.oschina.net/u/${this.userId}/blog/write/draft/${draftId}`,
|
|
1219
|
+
timestamp: now
|
|
1220
|
+
};
|
|
1221
|
+
}).catch((error) => ({
|
|
1222
|
+
platform: this.meta.id,
|
|
1223
|
+
success: false,
|
|
1224
|
+
error: error.message,
|
|
1225
|
+
timestamp: now
|
|
1226
|
+
}));
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
// src/adapters/platforms/segmentfault.ts
|
|
1231
|
+
var SegmentfaultAdapter = class extends CodeAdapter {
|
|
1232
|
+
meta = {
|
|
1233
|
+
id: "segmentfault",
|
|
1234
|
+
name: "\u601D\u5426",
|
|
1235
|
+
icon: "https://imgcache.iyiou.com/Company/2016-05-11/cf-segmentfault.jpg",
|
|
1236
|
+
homepage: "https://segmentfault.com/user/draft",
|
|
1237
|
+
capabilities: ["article", "draft", "image_upload"]
|
|
1238
|
+
};
|
|
1239
|
+
/** 预处理配置: 思否使用 Markdown 格式 */
|
|
1240
|
+
preprocessConfig = {
|
|
1241
|
+
outputFormat: "markdown"
|
|
1242
|
+
};
|
|
1243
|
+
sessionToken = null;
|
|
1244
|
+
/** 思否 API 需要的 Header 规则 */
|
|
1245
|
+
HEADER_RULES = [
|
|
1246
|
+
{
|
|
1247
|
+
urlFilter: "*://segmentfault.com/gateway/*",
|
|
1248
|
+
headers: {
|
|
1249
|
+
Origin: "https://segmentfault.com",
|
|
1250
|
+
Referer: "https://segmentfault.com/"
|
|
1251
|
+
},
|
|
1252
|
+
resourceTypes: ["xmlhttprequest"]
|
|
1253
|
+
}
|
|
1254
|
+
];
|
|
1255
|
+
/**
|
|
1256
|
+
* 检查登录状态
|
|
1257
|
+
*/
|
|
1258
|
+
async checkAuth() {
|
|
1259
|
+
try {
|
|
1260
|
+
const response = await this.runtime.fetch("https://segmentfault.com/user/settings", {
|
|
1261
|
+
credentials: "include"
|
|
1262
|
+
});
|
|
1263
|
+
const html = await response.text();
|
|
1264
|
+
const userLinkMatch = html.match(/href="\/u\/([^"]+)"/);
|
|
1265
|
+
if (!userLinkMatch) {
|
|
1266
|
+
return { isAuthenticated: false, error: "\u672A\u767B\u5F55" };
|
|
1267
|
+
}
|
|
1268
|
+
const uid = userLinkMatch[1];
|
|
1269
|
+
const avatarMatch = html.match(/src="(https:\/\/avatar-static\.segmentfault\.com\/[^"]+)"/);
|
|
1270
|
+
const avatar = avatarMatch ? avatarMatch[1] : void 0;
|
|
1271
|
+
return {
|
|
1272
|
+
isAuthenticated: true,
|
|
1273
|
+
userId: uid,
|
|
1274
|
+
username: uid,
|
|
1275
|
+
avatar
|
|
1276
|
+
};
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
return { isAuthenticated: false, error: error.message };
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* 获取 session token
|
|
1283
|
+
*/
|
|
1284
|
+
async getSessionToken() {
|
|
1285
|
+
const response = await this.runtime.fetch("https://segmentfault.com/write", {
|
|
1286
|
+
credentials: "include"
|
|
1287
|
+
});
|
|
1288
|
+
const html = await response.text();
|
|
1289
|
+
const tokenMatch = html.match(/serverData":\s*\{\s*"Token"\s*:\s*"([^"]+)"/);
|
|
1290
|
+
if (tokenMatch) {
|
|
1291
|
+
return tokenMatch[1];
|
|
1292
|
+
}
|
|
1293
|
+
const markStr = "window.g_initialProps = ";
|
|
1294
|
+
const authIndex = html.indexOf(markStr);
|
|
1295
|
+
if (authIndex === -1) {
|
|
1296
|
+
throw new Error("\u83B7\u53D6 session token \u5931\u8D25");
|
|
1297
|
+
}
|
|
1298
|
+
const endIndex = html.indexOf(";\n </script>", authIndex);
|
|
1299
|
+
if (endIndex === -1) {
|
|
1300
|
+
throw new Error("\u89E3\u6790 session token \u5931\u8D25");
|
|
1301
|
+
}
|
|
1302
|
+
const configStr = html.substring(authIndex + markStr.length, endIndex);
|
|
1303
|
+
try {
|
|
1304
|
+
const config = JSON.parse(configStr);
|
|
1305
|
+
const token = config?.global?.sessionInfo?.key;
|
|
1306
|
+
if (!token) {
|
|
1307
|
+
throw new Error("session token \u4E3A\u7A7A");
|
|
1308
|
+
}
|
|
1309
|
+
return token;
|
|
1310
|
+
} catch (e) {
|
|
1311
|
+
throw new Error("\u89E3\u6790 session token \u5931\u8D25: " + e.message);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* 上传图片
|
|
1316
|
+
*/
|
|
1317
|
+
async uploadImageByUrl(url) {
|
|
1318
|
+
if (!this.sessionToken) {
|
|
1319
|
+
throw new Error("\u672A\u83B7\u53D6 token");
|
|
1320
|
+
}
|
|
1321
|
+
const imageResponse = await this.runtime.fetch(url);
|
|
1322
|
+
const blob = await imageResponse.blob();
|
|
1323
|
+
const formData = new FormData();
|
|
1324
|
+
formData.append("image", blob);
|
|
1325
|
+
const response = await this.runtime.fetch(
|
|
1326
|
+
"https://segmentfault.com/gateway/image",
|
|
1327
|
+
{
|
|
1328
|
+
method: "POST",
|
|
1329
|
+
credentials: "include",
|
|
1330
|
+
headers: {
|
|
1331
|
+
token: this.sessionToken
|
|
1332
|
+
},
|
|
1333
|
+
body: formData
|
|
1334
|
+
}
|
|
1335
|
+
);
|
|
1336
|
+
const text = await response.text();
|
|
1337
|
+
if (text === "Unauthorized" || text.includes("\u7981\u8A00") || text.includes("\u9501\u5B9A")) {
|
|
1338
|
+
throw new Error(text === "Unauthorized" ? "\u672A\u6388\u6743" : text);
|
|
1339
|
+
}
|
|
1340
|
+
let res;
|
|
1341
|
+
try {
|
|
1342
|
+
res = JSON.parse(text);
|
|
1343
|
+
} catch {
|
|
1344
|
+
throw new Error("\u56FE\u7247\u4E0A\u4F20\u5931\u8D25: " + text);
|
|
1345
|
+
}
|
|
1346
|
+
const imageUrl = res.result || (Array.isArray(res) ? res[0] === 1 ? null : res[1] || `https://image-static.segmentfault.com/${res[2]}` : null);
|
|
1347
|
+
if (!imageUrl) {
|
|
1348
|
+
throw new Error(Array.isArray(res) ? res[1] || "\u56FE\u7247\u4E0A\u4F20\u5931\u8D25" : "\u56FE\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
1349
|
+
}
|
|
1350
|
+
return { url: imageUrl };
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* 发布文章
|
|
1354
|
+
*/
|
|
1355
|
+
async publish(article) {
|
|
1356
|
+
const now = Date.now();
|
|
1357
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
1358
|
+
this.sessionToken = await this.getSessionToken();
|
|
1359
|
+
let content = article.markdown || article.html || "";
|
|
1360
|
+
content = await this.processImages(content, (src) => this.uploadImageByUrl(src));
|
|
1361
|
+
const postData = {
|
|
1362
|
+
title: article.title,
|
|
1363
|
+
tags: [],
|
|
1364
|
+
text: content,
|
|
1365
|
+
object_id: "",
|
|
1366
|
+
type: "article"
|
|
1367
|
+
};
|
|
1368
|
+
const response = await this.runtime.fetch("https://segmentfault.com/gateway/draft", {
|
|
1369
|
+
method: "POST",
|
|
1370
|
+
credentials: "include",
|
|
1371
|
+
headers: {
|
|
1372
|
+
"Content-Type": "application/json",
|
|
1373
|
+
token: this.sessionToken,
|
|
1374
|
+
accept: "*/*"
|
|
1375
|
+
},
|
|
1376
|
+
body: JSON.stringify(postData)
|
|
1377
|
+
});
|
|
1378
|
+
const text = await response.text();
|
|
1379
|
+
if (text === "Unauthorized" || text.includes("\u7981\u8A00") || text.includes("\u9501\u5B9A")) {
|
|
1380
|
+
throw new Error(text === "Unauthorized" ? "\u672A\u6388\u6743" : text);
|
|
1381
|
+
}
|
|
1382
|
+
let res;
|
|
1383
|
+
try {
|
|
1384
|
+
res = JSON.parse(text);
|
|
1385
|
+
} catch {
|
|
1386
|
+
throw new Error("\u53D1\u5E03\u5931\u8D25: " + text);
|
|
1387
|
+
}
|
|
1388
|
+
if (Array.isArray(res)) {
|
|
1389
|
+
if (res[0] === 1) {
|
|
1390
|
+
throw new Error(res[1] || "\u53D1\u5E03\u5931\u8D25");
|
|
1391
|
+
}
|
|
1392
|
+
const data = res[1];
|
|
1393
|
+
if (data?.id) {
|
|
1394
|
+
return {
|
|
1395
|
+
platform: this.meta.id,
|
|
1396
|
+
success: true,
|
|
1397
|
+
postId: data.id,
|
|
1398
|
+
postUrl: `https://segmentfault.com/write?draftId=${data.id}`,
|
|
1399
|
+
timestamp: now
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
if (!res.id) {
|
|
1404
|
+
const errorMsg = res.message || res.msg || res.error || res.errMsg || JSON.stringify(res);
|
|
1405
|
+
throw new Error(errorMsg);
|
|
1406
|
+
}
|
|
1407
|
+
return {
|
|
1408
|
+
platform: this.meta.id,
|
|
1409
|
+
success: true,
|
|
1410
|
+
postId: res.id,
|
|
1411
|
+
postUrl: `https://segmentfault.com/write?draftId=${res.id}`,
|
|
1412
|
+
timestamp: now
|
|
1413
|
+
};
|
|
1414
|
+
}).catch((error) => ({
|
|
1415
|
+
platform: this.meta.id,
|
|
1416
|
+
success: false,
|
|
1417
|
+
error: error.message,
|
|
1418
|
+
timestamp: now
|
|
1419
|
+
}));
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
// src/adapters/platforms/zip-download.ts
|
|
1424
|
+
import JSZip from "jszip";
|
|
1425
|
+
var logger5 = createLogger("ZipDownload");
|
|
1426
|
+
var ZipDownloadAdapter = class extends CodeAdapter {
|
|
1427
|
+
meta = {
|
|
1428
|
+
id: "zip-download",
|
|
1429
|
+
name: "Markdown \u538B\u7F29\u5305",
|
|
1430
|
+
icon: "https://cdn-icons-png.flaticon.com/512/337/337946.png",
|
|
1431
|
+
homepage: "",
|
|
1432
|
+
capabilities: ["article"]
|
|
1433
|
+
};
|
|
1434
|
+
/**
|
|
1435
|
+
* 本地导出不需要认证
|
|
1436
|
+
*/
|
|
1437
|
+
async checkAuth() {
|
|
1438
|
+
return {
|
|
1439
|
+
isAuthenticated: true,
|
|
1440
|
+
username: "\u672C\u5730\u4E0B\u8F7D"
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* 导出为 ZIP 压缩包
|
|
1445
|
+
*/
|
|
1446
|
+
async publish(article, options) {
|
|
1447
|
+
try {
|
|
1448
|
+
const zip = new JSZip();
|
|
1449
|
+
const imgFolder = zip.folder("images");
|
|
1450
|
+
let markdown = article.markdown || htmlToMarkdown(article.html || "");
|
|
1451
|
+
if (!markdown.trim()) {
|
|
1452
|
+
return this.createResult(false, {
|
|
1453
|
+
error: "\u6587\u7AE0\u5185\u5BB9\u4E3A\u7A7A"
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
const title = article.title || "\u672A\u547D\u540D\u6587\u7AE0";
|
|
1457
|
+
if (!markdown.startsWith("# ")) {
|
|
1458
|
+
markdown = `# ${title}
|
|
1459
|
+
|
|
1460
|
+
${markdown}`;
|
|
1461
|
+
}
|
|
1462
|
+
const { processedMarkdown, imageCount } = await this.processImagesForZip(
|
|
1463
|
+
markdown,
|
|
1464
|
+
imgFolder,
|
|
1465
|
+
options?.onImageProgress
|
|
1466
|
+
);
|
|
1467
|
+
zip.file("article.md", processedMarkdown);
|
|
1468
|
+
const blob = await zip.generateAsync({
|
|
1469
|
+
type: "blob",
|
|
1470
|
+
compression: "DEFLATE",
|
|
1471
|
+
compressionOptions: { level: 6 }
|
|
1472
|
+
});
|
|
1473
|
+
const filename = this.sanitizeFilename(title) + ".zip";
|
|
1474
|
+
if (!this.runtime.downloads) {
|
|
1475
|
+
return this.createResult(false, {
|
|
1476
|
+
error: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u4E0B\u8F7D\u529F\u80FD"
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
const downloadId = await this.runtime.downloads.download(blob, filename, true);
|
|
1480
|
+
logger5.info(`Download started: ${filename}, id: ${downloadId}`);
|
|
1481
|
+
return this.createResult(true, {
|
|
1482
|
+
postId: String(downloadId),
|
|
1483
|
+
postUrl: "",
|
|
1484
|
+
// 本地下载,无链接
|
|
1485
|
+
message: `\u5DF2\u4E0B\u8F7D ${filename}\uFF08${imageCount} \u5F20\u56FE\u7247\uFF09`
|
|
1486
|
+
});
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
logger5.error("ZIP download failed:", error);
|
|
1489
|
+
return this.createResult(false, {
|
|
1490
|
+
error: error.message
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* 处理 Markdown 中的图片:下载并添加到 ZIP
|
|
1496
|
+
*/
|
|
1497
|
+
async processImagesForZip(markdown, imgFolder, onProgress) {
|
|
1498
|
+
const matches = parseMarkdownImages(markdown);
|
|
1499
|
+
if (matches.length === 0) {
|
|
1500
|
+
return { processedMarkdown: markdown, imageCount: 0 };
|
|
1501
|
+
}
|
|
1502
|
+
logger5.info(`Found ${matches.length} images to process`);
|
|
1503
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1504
|
+
let processedMarkdown = markdown;
|
|
1505
|
+
let imageIndex = 0;
|
|
1506
|
+
let completed = 0;
|
|
1507
|
+
const urlToFilename = /* @__PURE__ */ new Map();
|
|
1508
|
+
const uniqueUrls = [];
|
|
1509
|
+
for (const { src } of matches) {
|
|
1510
|
+
if (src.startsWith("data:")) {
|
|
1511
|
+
logger5.debug("Skipping data URI image");
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
if (!urlToFilename.has(src)) {
|
|
1515
|
+
urlToFilename.set(src, "");
|
|
1516
|
+
uniqueUrls.push(src);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (uniqueUrls.length === 0) {
|
|
1520
|
+
return { processedMarkdown: markdown, imageCount: 0 };
|
|
1521
|
+
}
|
|
1522
|
+
const downloadOne = async (src) => {
|
|
1523
|
+
try {
|
|
1524
|
+
const response = await this.runtime.fetch(src, {
|
|
1525
|
+
credentials: "omit"
|
|
1526
|
+
});
|
|
1527
|
+
if (!response.ok) {
|
|
1528
|
+
logger5.warn(`Failed to download image: ${src}, status: ${response.status}`);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
const blob = await response.blob();
|
|
1532
|
+
const ext = this.getImageExtension(src, blob.type);
|
|
1533
|
+
imageIndex++;
|
|
1534
|
+
const filename = `image_${timestamp}_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
1535
|
+
imgFolder.file(filename, blob);
|
|
1536
|
+
urlToFilename.set(src, filename);
|
|
1537
|
+
logger5.debug(`Downloaded image: ${filename}`);
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
logger5.warn(`Failed to download image: ${src}`, error);
|
|
1540
|
+
} finally {
|
|
1541
|
+
completed++;
|
|
1542
|
+
onProgress?.(completed, uniqueUrls.length);
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
const runPool = async (limit) => {
|
|
1546
|
+
let cursor = 0;
|
|
1547
|
+
const workers = Array.from({ length: Math.min(limit, uniqueUrls.length) }, async () => {
|
|
1548
|
+
while (cursor < uniqueUrls.length) {
|
|
1549
|
+
const current = cursor;
|
|
1550
|
+
cursor++;
|
|
1551
|
+
await downloadOne(uniqueUrls[current]);
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
await Promise.all(workers);
|
|
1555
|
+
};
|
|
1556
|
+
await runPool(4);
|
|
1557
|
+
for (const { full, alt, src } of matches) {
|
|
1558
|
+
const filename = urlToFilename.get(src);
|
|
1559
|
+
if (filename) {
|
|
1560
|
+
processedMarkdown = processedMarkdown.replace(full, ``);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
processedMarkdown,
|
|
1565
|
+
imageCount: Array.from(urlToFilename.values()).filter(Boolean).length
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* 获取图片扩展名
|
|
1570
|
+
*/
|
|
1571
|
+
getImageExtension(url, mimeType) {
|
|
1572
|
+
const mimeMap = {
|
|
1573
|
+
"image/jpeg": "jpg",
|
|
1574
|
+
"image/png": "png",
|
|
1575
|
+
"image/gif": "gif",
|
|
1576
|
+
"image/webp": "webp",
|
|
1577
|
+
"image/svg+xml": "svg",
|
|
1578
|
+
"image/bmp": "bmp",
|
|
1579
|
+
"image/x-icon": "ico"
|
|
1580
|
+
};
|
|
1581
|
+
if (mimeType && mimeMap[mimeType]) {
|
|
1582
|
+
return mimeMap[mimeType];
|
|
1583
|
+
}
|
|
1584
|
+
const urlMatch = url.match(/\.(png|jpe?g|gif|webp|svg|bmp|ico)(?:\?|$)/i);
|
|
1585
|
+
if (urlMatch) {
|
|
1586
|
+
return urlMatch[1].toLowerCase().replace("jpeg", "jpg");
|
|
1587
|
+
}
|
|
1588
|
+
return "png";
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* 清理文件名,移除非法字符
|
|
1592
|
+
*/
|
|
1593
|
+
sanitizeFilename(name) {
|
|
1594
|
+
return name.replace(/[<>:"/\\|?*]/g, "_").replace(/[\x00-\x1f]/g, "").replace(/\.+$/, "").trim().slice(0, 200) || "article";
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
// src/adapters/platforms/eastmoney.ts
|
|
1599
|
+
var logger6 = createLogger("Eastmoney");
|
|
1600
|
+
var EastmoneyAdapter = class extends CodeAdapter {
|
|
1601
|
+
meta = {
|
|
1602
|
+
id: "eastmoney",
|
|
1603
|
+
name: "\u4E1C\u65B9\u8D22\u5BCC",
|
|
1604
|
+
icon: "https://mp.eastmoney.com/collect/pc_article/favicon.ico",
|
|
1605
|
+
homepage: "https://mp.eastmoney.com",
|
|
1606
|
+
capabilities: ["article", "draft", "image_upload", "cover"]
|
|
1607
|
+
};
|
|
1608
|
+
/** 预处理配置 */
|
|
1609
|
+
preprocessConfig = {
|
|
1610
|
+
outputFormat: "html",
|
|
1611
|
+
removeComments: true,
|
|
1612
|
+
removeSpecialTags: true,
|
|
1613
|
+
processCodeBlocks: true,
|
|
1614
|
+
convertSectionToDiv: true,
|
|
1615
|
+
removeEmptyLines: true,
|
|
1616
|
+
removeEmptyDivs: true,
|
|
1617
|
+
removeNestedEmptyContainers: true,
|
|
1618
|
+
unwrapSingleChildContainers: true,
|
|
1619
|
+
unwrapNestedFigures: true,
|
|
1620
|
+
removeTrailingBr: true,
|
|
1621
|
+
removeDataAttributes: true,
|
|
1622
|
+
removeSrcset: true,
|
|
1623
|
+
removeSizes: true,
|
|
1624
|
+
compactHtml: true
|
|
1625
|
+
};
|
|
1626
|
+
ctoken = "";
|
|
1627
|
+
utoken = "";
|
|
1628
|
+
deviceId = "";
|
|
1629
|
+
/** 获取或生成持久化 deviceId(32位大写 hex) */
|
|
1630
|
+
async getDeviceId() {
|
|
1631
|
+
if (this.deviceId) return this.deviceId;
|
|
1632
|
+
const stored = await this.runtime.storage.get("eastmoney_deviceId");
|
|
1633
|
+
if (stored) {
|
|
1634
|
+
this.deviceId = stored;
|
|
1635
|
+
return this.deviceId;
|
|
1636
|
+
}
|
|
1637
|
+
const bytes = new Uint8Array(16);
|
|
1638
|
+
crypto.getRandomValues(bytes);
|
|
1639
|
+
this.deviceId = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join("");
|
|
1640
|
+
await this.runtime.storage.set("eastmoney_deviceId", this.deviceId);
|
|
1641
|
+
return this.deviceId;
|
|
1642
|
+
}
|
|
1643
|
+
/** API Header 规则 */
|
|
1644
|
+
HEADER_RULES = [
|
|
1645
|
+
{
|
|
1646
|
+
urlFilter: "*://mp.eastmoney.com/*",
|
|
1647
|
+
headers: {
|
|
1648
|
+
Origin: "https://mp.eastmoney.com",
|
|
1649
|
+
HOST: "emfront.eastmoney.com"
|
|
1650
|
+
},
|
|
1651
|
+
resourceTypes: ["xmlhttprequest"]
|
|
1652
|
+
}
|
|
1653
|
+
];
|
|
1654
|
+
async checkAuth() {
|
|
1655
|
+
try {
|
|
1656
|
+
await this.fetchToken();
|
|
1657
|
+
const response = await this.runtime.fetch(
|
|
1658
|
+
`https://caifuhaoapi.eastmoney.com/api/v2/getauthorinfo?platform=&ctoken=${this.ctoken}&utoken=${this.utoken}`,
|
|
1659
|
+
{
|
|
1660
|
+
method: "GET",
|
|
1661
|
+
credentials: "include",
|
|
1662
|
+
headers: { "x-requested-with": "fetch" }
|
|
1663
|
+
}
|
|
1664
|
+
);
|
|
1665
|
+
const data = await response.json();
|
|
1666
|
+
if (data.Success === 1 && data.Result?.accountId) {
|
|
1667
|
+
return {
|
|
1668
|
+
isAuthenticated: true,
|
|
1669
|
+
userId: data.Result.accountId,
|
|
1670
|
+
username: data.Result.accountName,
|
|
1671
|
+
avatar: data.Result.portrait
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
return { isAuthenticated: false };
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
logger6.debug("checkAuth: not logged in -", error);
|
|
1677
|
+
return { isAuthenticated: false, error: error.message };
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
/** 从 cookie 读取 token */
|
|
1681
|
+
async fetchToken() {
|
|
1682
|
+
if (!this.runtime.getCookie) {
|
|
1683
|
+
throw new Error("Cookie API \u4E0D\u53EF\u7528\uFF0C\u8BF7\u5148\u767B\u5F55\u4E1C\u65B9\u8D22\u5BCC");
|
|
1684
|
+
}
|
|
1685
|
+
const ctoken = await this.runtime.getCookie(".eastmoney.com", "ct");
|
|
1686
|
+
const utoken = await this.runtime.getCookie(".eastmoney.com", "ut");
|
|
1687
|
+
if (!ctoken || !utoken) {
|
|
1688
|
+
throw new Error("\u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8BF7\u5148\u767B\u5F55\u4E1C\u65B9\u8D22\u5BCC");
|
|
1689
|
+
}
|
|
1690
|
+
this.ctoken = ctoken;
|
|
1691
|
+
this.utoken = utoken;
|
|
1692
|
+
}
|
|
1693
|
+
async publish(article, options) {
|
|
1694
|
+
return this.withHeaderRules(this.HEADER_RULES, async () => {
|
|
1695
|
+
await this.fetchToken();
|
|
1696
|
+
logger6.info("Starting publish to eastmoney...");
|
|
1697
|
+
const draftId = await this.createDraft(article.title);
|
|
1698
|
+
logger6.debug("Draft created:", draftId);
|
|
1699
|
+
const content = await this.processImages(
|
|
1700
|
+
article.html || "",
|
|
1701
|
+
(src) => this.uploadImageByUrl(src),
|
|
1702
|
+
{
|
|
1703
|
+
skipPatterns: ["gbres.dfcfw.com"],
|
|
1704
|
+
onProgress: options?.onImageProgress
|
|
1705
|
+
}
|
|
1706
|
+
);
|
|
1707
|
+
await this.updateDraft(draftId, article.title, content);
|
|
1708
|
+
logger6.debug("Draft updated");
|
|
1709
|
+
const draftUrl = `https://mp.eastmoney.com/collect/pc_article/index.html#/?id=${draftId}`;
|
|
1710
|
+
return this.createResult(true, {
|
|
1711
|
+
postId: draftId,
|
|
1712
|
+
postUrl: draftUrl
|
|
1713
|
+
});
|
|
1714
|
+
}).catch(
|
|
1715
|
+
(error) => this.createResult(false, {
|
|
1716
|
+
error: error.message
|
|
1717
|
+
})
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
/** 构造 API 参数 */
|
|
1721
|
+
async buildParm(params) {
|
|
1722
|
+
const deviceid = await this.getDeviceId();
|
|
1723
|
+
return [
|
|
1724
|
+
{ ip: "$IP$" },
|
|
1725
|
+
{ deviceid },
|
|
1726
|
+
{ version: "100" },
|
|
1727
|
+
{ plat: "web" },
|
|
1728
|
+
{ product: "CFH" },
|
|
1729
|
+
{ ctoken: this.ctoken },
|
|
1730
|
+
{ utoken: this.utoken },
|
|
1731
|
+
{ draftid: params.draftid ?? "" },
|
|
1732
|
+
{ drafttype: "0" },
|
|
1733
|
+
{ type: "0" },
|
|
1734
|
+
{ title: encodeURIComponent(params.title) },
|
|
1735
|
+
{ text: encodeURIComponent(params.text) },
|
|
1736
|
+
{ columns: "2" },
|
|
1737
|
+
{ cover: "" },
|
|
1738
|
+
{ issimplevideo: "0" },
|
|
1739
|
+
{ videos: "" },
|
|
1740
|
+
{ vods: "" },
|
|
1741
|
+
{ isoriginal: "0" },
|
|
1742
|
+
{ tgProduct: "" },
|
|
1743
|
+
{ spcolumns: "" },
|
|
1744
|
+
{ textsource: "0" },
|
|
1745
|
+
{ replyauthority: "" },
|
|
1746
|
+
{ modules: encodeURIComponent("[]") }
|
|
1747
|
+
];
|
|
1748
|
+
}
|
|
1749
|
+
/** 调用草稿 API */
|
|
1750
|
+
async callDraftApi(parm, draftId) {
|
|
1751
|
+
const pageUrl = draftId ? `https://mp.eastmoney.com/collect/pc_article/index.html#/?id=${draftId}` : "https://mp.eastmoney.com/collect/pc_article/index.html#/";
|
|
1752
|
+
const body = JSON.stringify({
|
|
1753
|
+
pageUrl,
|
|
1754
|
+
path: "draft/api/Article/SaveDraft",
|
|
1755
|
+
parm: JSON.stringify(parm)
|
|
1756
|
+
});
|
|
1757
|
+
const response = await this.runtime.fetch(
|
|
1758
|
+
"https://emfront.eastmoney.com/apifront/Tran/GetData?platform=",
|
|
1759
|
+
{
|
|
1760
|
+
method: "POST",
|
|
1761
|
+
credentials: "include",
|
|
1762
|
+
headers: { "Content-Type": "application/json" },
|
|
1763
|
+
body
|
|
1764
|
+
}
|
|
1765
|
+
);
|
|
1766
|
+
const responseText = await response.text();
|
|
1767
|
+
logger6.debug(
|
|
1768
|
+
"Draft API response:",
|
|
1769
|
+
response.status,
|
|
1770
|
+
responseText.substring(0, 200)
|
|
1771
|
+
);
|
|
1772
|
+
if (!response.ok) {
|
|
1773
|
+
throw new Error(`\u8349\u7A3F API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
|
|
1774
|
+
}
|
|
1775
|
+
let rawData;
|
|
1776
|
+
try {
|
|
1777
|
+
rawData = JSON.parse(responseText);
|
|
1778
|
+
} catch {
|
|
1779
|
+
throw new Error("\u8349\u7A3F API \u54CD\u5E94\u4E0D\u662F\u6709\u6548 JSON");
|
|
1780
|
+
}
|
|
1781
|
+
if (!rawData.RRquestSuccess || rawData.RCode !== 200) {
|
|
1782
|
+
throw new Error(`\u8349\u7A3F API \u9519\u8BEF: ${rawData.RMsg || "\u672A\u77E5\u9519\u8BEF"}`);
|
|
1783
|
+
}
|
|
1784
|
+
let innerData;
|
|
1785
|
+
try {
|
|
1786
|
+
innerData = JSON.parse(rawData.RData);
|
|
1787
|
+
} catch {
|
|
1788
|
+
throw new Error("\u65E0\u6CD5\u89E3\u6790\u8349\u7A3F\u54CD\u5E94\u6570\u636E");
|
|
1789
|
+
}
|
|
1790
|
+
if (innerData.error_code !== 0) {
|
|
1791
|
+
throw new Error(`\u8349\u7A3F\u4E1A\u52A1\u9519\u8BEF: ${innerData.me || "\u672A\u77E5\u9519\u8BEF"}`);
|
|
1792
|
+
}
|
|
1793
|
+
return innerData;
|
|
1794
|
+
}
|
|
1795
|
+
async createDraft(title) {
|
|
1796
|
+
const parm = await this.buildParm({
|
|
1797
|
+
title,
|
|
1798
|
+
text: '<div class="xeditor_content cfh_web"></div>'
|
|
1799
|
+
});
|
|
1800
|
+
const result = await this.callDraftApi(parm);
|
|
1801
|
+
if (!result.draft_id) {
|
|
1802
|
+
throw new Error("\u521B\u5EFA\u8349\u7A3F\u5931\u8D25: \u54CD\u5E94\u7F3A\u5C11 draft_id");
|
|
1803
|
+
}
|
|
1804
|
+
return result.draft_id;
|
|
1805
|
+
}
|
|
1806
|
+
async updateDraft(draftId, title, content) {
|
|
1807
|
+
const parm = await this.buildParm({
|
|
1808
|
+
draftid: draftId,
|
|
1809
|
+
title,
|
|
1810
|
+
text: `<div class="xeditor_content cfh_web">${content}</div>`
|
|
1811
|
+
});
|
|
1812
|
+
await this.callDraftApi(parm, draftId);
|
|
1813
|
+
}
|
|
1814
|
+
/** URL 上传图片 */
|
|
1815
|
+
async uploadImageByUrl(src) {
|
|
1816
|
+
if (src.startsWith("data:")) {
|
|
1817
|
+
logger6.debug("Detected data URI, using binary upload");
|
|
1818
|
+
const blob = await this.dataUriToBlob(src);
|
|
1819
|
+
return this.uploadImageBlob(blob);
|
|
1820
|
+
}
|
|
1821
|
+
const response = await this.runtime.fetch(
|
|
1822
|
+
"https://gbapi.eastmoney.com/iimage/image/byLink?platform=",
|
|
1823
|
+
{
|
|
1824
|
+
method: "PUT",
|
|
1825
|
+
credentials: "include",
|
|
1826
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1827
|
+
body: new URLSearchParams({
|
|
1828
|
+
noinlist: "1",
|
|
1829
|
+
linkUrl: src,
|
|
1830
|
+
ctoken: this.ctoken,
|
|
1831
|
+
utoken: this.utoken
|
|
1832
|
+
})
|
|
1833
|
+
}
|
|
1834
|
+
);
|
|
1835
|
+
const res = await response.json();
|
|
1836
|
+
if (res.code === 200 && res.data?.url) {
|
|
1837
|
+
return { url: res.data.url };
|
|
1838
|
+
}
|
|
1839
|
+
throw new Error(
|
|
1840
|
+
`\u56FE\u7247\u4E0A\u4F20\u5931\u8D25: ${res.message || "\u672A\u77E5\u9519\u8BEF"} (code: ${res.code})`
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
/** 上传图片 Blob */
|
|
1844
|
+
async uploadImageBlob(file) {
|
|
1845
|
+
const ext = file.type.split("/")[1] || "png";
|
|
1846
|
+
const filename = `${Date.now()}.${ext}`;
|
|
1847
|
+
const formData = new FormData();
|
|
1848
|
+
formData.append("file", file, filename);
|
|
1849
|
+
formData.append("noinlist", "1");
|
|
1850
|
+
formData.append("utoken", this.utoken);
|
|
1851
|
+
formData.append("ctoken", this.ctoken);
|
|
1852
|
+
const response = await this.runtime.fetch(
|
|
1853
|
+
"https://gbapi.eastmoney.com/iimage/image?platform=",
|
|
1854
|
+
{
|
|
1855
|
+
method: "POST",
|
|
1856
|
+
credentials: "include",
|
|
1857
|
+
body: formData
|
|
1858
|
+
}
|
|
1859
|
+
);
|
|
1860
|
+
const res = await response.json();
|
|
1861
|
+
if (res.code === 200 && res.data?.url) {
|
|
1862
|
+
return { url: res.data.url };
|
|
1863
|
+
}
|
|
1864
|
+
throw new Error(
|
|
1865
|
+
`\u56FE\u7247\u4E0A\u4F20\u5931\u8D25: ${res.message || "\u672A\u77E5\u9519\u8BEF"} (code: ${res.code})`
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
export {
|
|
1870
|
+
BaijiahaoAdapter,
|
|
1871
|
+
BaseAdapter,
|
|
1872
|
+
BilibiliAdapter,
|
|
1873
|
+
CSDNAdapter,
|
|
1874
|
+
CnblogsAdapter,
|
|
1875
|
+
CodeAdapter,
|
|
1876
|
+
Cto51Adapter,
|
|
1877
|
+
DEFAULT_PREPROCESS_CONFIG,
|
|
1878
|
+
DoubanAdapter,
|
|
1879
|
+
DouyinAdapter,
|
|
1880
|
+
EastmoneyAdapter,
|
|
1881
|
+
ImoocAdapter,
|
|
1882
|
+
JuejinAdapter,
|
|
1883
|
+
NeteaseAdapter,
|
|
1884
|
+
OschinaAdapter,
|
|
1885
|
+
SegmentfaultAdapter,
|
|
1886
|
+
SohuAdapter,
|
|
1887
|
+
TencentAdapter,
|
|
1888
|
+
ToutiaoAdapter,
|
|
1889
|
+
WeixinAdapter,
|
|
1890
|
+
WoshipmAdapter,
|
|
1891
|
+
XueqiuAdapter,
|
|
1892
|
+
YuqueAdapter,
|
|
1893
|
+
ZipDownloadAdapter,
|
|
1894
|
+
adapterRegistry,
|
|
1895
|
+
getAdapter,
|
|
1896
|
+
getPreprocessConfig,
|
|
1897
|
+
getPreprocessConfigs,
|
|
1898
|
+
registerAdapter
|
|
1899
|
+
};
|
|
1900
|
+
//# sourceMappingURL=index.js.map
|