multi-publisher 1.1.2 → 1.1.3
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 +28 -22
- package/dist/adapters/csdn.js +24 -8
- package/dist/adapters/interface.d.ts +10 -0
- package/dist/adapters/qq.d.ts +19 -0
- package/dist/adapters/qq.js +185 -0
- package/dist/adapters/registry.js +2 -0
- package/dist/adapters/toutiao.d.ts +4 -0
- package/dist/adapters/toutiao.js +163 -49
- package/dist/adapters/wechat-publisher.d.ts +6 -1
- package/dist/adapters/wechat-publisher.js +58 -17
- package/dist/adapters/weixin.d.ts +5 -0
- package/dist/adapters/weixin.js +41 -5
- package/dist/cli/index.js +1 -0
- package/dist/cli/login.js +3 -0
- package/dist/cli/platforms.js +2 -1
- package/dist/cli/publish-all.js +1 -1
- package/dist/cli/publish.d.ts +1 -0
- package/dist/cli/publish.js +29 -5
- package/dist/config.d.ts +8 -0
- package/dist/config.js +11 -0
- package/dist/core/parser.js +9 -1
- package/dist/core/renderer.d.ts +9 -1
- package/dist/core/renderer.js +86 -11
- package/dist/core/theme.d.ts +1 -1
- package/dist/core/theme.js +91 -336
- package/dist/runtime/browser-runtime.js +31 -2
- package/dist/tools/capture.js +28 -4
- package/dist/tools/cover-fetcher.d.ts +10 -0
- package/dist/tools/cover-fetcher.js +100 -0
- package/dist/tools/imgbb-uploader.d.ts +18 -0
- package/dist/tools/imgbb-uploader.js +89 -0
- package/package.json +1 -1
- package/themes/previews/cyberpunk.png +0 -0
- package/themes/previews/nord.png +0 -0
|
@@ -351,3 +351,103 @@ export async function cleanupCoverFile(localPath) {
|
|
|
351
351
|
}
|
|
352
352
|
catch { }
|
|
353
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* 下载远程封面图片到本地临时目录
|
|
356
|
+
* @param coverUrl 远程图片 URL
|
|
357
|
+
* @param options 配置选项
|
|
358
|
+
* @returns 本地临时文件路径
|
|
359
|
+
*/
|
|
360
|
+
export async function downloadCoverUrl(coverUrl, options = {}) {
|
|
361
|
+
const { timeout = 15000, outputDir = tmpdir(), } = options;
|
|
362
|
+
if (!coverUrl || !coverUrl.startsWith('http')) {
|
|
363
|
+
return { success: false, error: '无效的 URL' };
|
|
364
|
+
}
|
|
365
|
+
let browser = null;
|
|
366
|
+
try {
|
|
367
|
+
console.log(`[cover-fetcher] 下载封面: ${coverUrl.substring(0, 80)}...`);
|
|
368
|
+
browser = await chromium.launch({ headless: true });
|
|
369
|
+
const context = await browser.newContext({
|
|
370
|
+
viewport: { width: 1280, height: 900 },
|
|
371
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
372
|
+
});
|
|
373
|
+
const page = await context.newPage();
|
|
374
|
+
// 绕过反爬检测
|
|
375
|
+
await page.addInitScript(() => {
|
|
376
|
+
Object.defineProperty(navigator, 'webdriver', { get: () => false });
|
|
377
|
+
});
|
|
378
|
+
let buffer = null;
|
|
379
|
+
let actualContentType = '';
|
|
380
|
+
// 方法1:直接下载
|
|
381
|
+
try {
|
|
382
|
+
const response = await page.request.get(coverUrl, {
|
|
383
|
+
headers: {
|
|
384
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
385
|
+
'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*',
|
|
386
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
387
|
+
},
|
|
388
|
+
timeout: timeout,
|
|
389
|
+
});
|
|
390
|
+
if (response.ok()) {
|
|
391
|
+
buffer = await response.body();
|
|
392
|
+
actualContentType = response.headers()['content-type'] || '';
|
|
393
|
+
console.log(`[cover-fetcher] 下载成功,大小: ${(buffer.length / 1024).toFixed(1)} KB`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
console.log(`[cover-fetcher] 直接下载失败: ${e.message}`);
|
|
398
|
+
}
|
|
399
|
+
// 方法2:如果失败,尝试通过浏览器加载
|
|
400
|
+
if (!buffer || buffer.length < 1000) {
|
|
401
|
+
try {
|
|
402
|
+
await page.goto(coverUrl, { waitUntil: 'networkidle', timeout: 15000 });
|
|
403
|
+
const finalUrl = page.url();
|
|
404
|
+
if (finalUrl !== coverUrl) {
|
|
405
|
+
const response2 = await page.request.get(finalUrl, { timeout: timeout });
|
|
406
|
+
if (response2.ok()) {
|
|
407
|
+
buffer = await response2.body();
|
|
408
|
+
actualContentType = response2.headers()['content-type'] || '';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
console.log(`[cover-fetcher] 浏览器加载失败: ${e.message}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (!buffer || buffer.length < 1000) {
|
|
417
|
+
return { success: false, error: '无法下载图片' };
|
|
418
|
+
}
|
|
419
|
+
// 检测图片类型
|
|
420
|
+
const detectedExt = detectImageType(buffer);
|
|
421
|
+
const fromHeader = getExtensionFromContentType(actualContentType);
|
|
422
|
+
const ext = detectedExt || fromHeader || getExtension(coverUrl);
|
|
423
|
+
const filename = generateFilename(ext);
|
|
424
|
+
const localPath = path.join(outputDir, filename);
|
|
425
|
+
await fs.writeFile(localPath, buffer);
|
|
426
|
+
// WebP 转换为 PNG
|
|
427
|
+
if (ext === '.webp') {
|
|
428
|
+
try {
|
|
429
|
+
const convertedPath = await convertWebpToPng(localPath, outputDir);
|
|
430
|
+
return { success: true, localPath: convertedPath, imageUrl: coverUrl };
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
console.warn(`[cover-fetcher] WebP 转换失败,保留原文件`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
success: true,
|
|
438
|
+
localPath,
|
|
439
|
+
imageUrl: coverUrl
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
return {
|
|
444
|
+
success: false,
|
|
445
|
+
error: error.message
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
finally {
|
|
449
|
+
if (browser) {
|
|
450
|
+
await browser.close().catch(() => { });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上传本地图片到图床(自动重试多个服务)
|
|
3
|
+
* 返回公开 URL
|
|
4
|
+
*/
|
|
5
|
+
export declare function uploadImageToPublicUrl(filePath: string): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* 上传到 Litterbox(临时,1小时有效期,无需认证)
|
|
8
|
+
*/
|
|
9
|
+
declare function uploadToLitterbox(filePath: string): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* 上传到 Catbox.moe(永久,无需认证)
|
|
12
|
+
*/
|
|
13
|
+
declare function uploadToCatbox(filePath: string): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* 上传到 0x0.st(匿名,无需 API key)- 已禁用
|
|
16
|
+
*/
|
|
17
|
+
export declare function uploadTo0x0st(filePath: string): Promise<string>;
|
|
18
|
+
export { uploadToLitterbox, uploadToCatbox };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图片上传工具 - 提供公共 URL 供各平台使用
|
|
3
|
+
* 尝试多个图床服务,按优先级 fallback
|
|
4
|
+
*/
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import FormData from 'form-data';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
/**
|
|
10
|
+
* 上传本地图片到图床(自动重试多个服务)
|
|
11
|
+
* 返回公开 URL
|
|
12
|
+
*/
|
|
13
|
+
export async function uploadImageToPublicUrl(filePath) {
|
|
14
|
+
const services = [
|
|
15
|
+
() => uploadToLitterbox(filePath),
|
|
16
|
+
() => uploadToCatbox(filePath),
|
|
17
|
+
];
|
|
18
|
+
for (const attempt of services) {
|
|
19
|
+
try {
|
|
20
|
+
const url = await attempt();
|
|
21
|
+
return url;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.warn(`[imgbb-uploader] 上传失败: ${err.message},尝试下一个服务`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
throw new Error('所有图床上传服务均失败');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 上传到 Litterbox(临时,1小时有效期,无需认证)
|
|
31
|
+
*/
|
|
32
|
+
async function uploadToLitterbox(filePath) {
|
|
33
|
+
const form = new FormData();
|
|
34
|
+
form.append('reqtype', 'fileupload');
|
|
35
|
+
form.append('time', '1h');
|
|
36
|
+
form.append('fileToUpload', readFileSync(filePath), {
|
|
37
|
+
filename: path.basename(filePath),
|
|
38
|
+
contentType: 'image/png',
|
|
39
|
+
});
|
|
40
|
+
const res = await axios.post('https://litterbox.catbox.moe/resources/internals/api.php', form, {
|
|
41
|
+
headers: form.getHeaders(),
|
|
42
|
+
timeout: 30000,
|
|
43
|
+
});
|
|
44
|
+
const url = res.data.trim();
|
|
45
|
+
if (!url.startsWith('http')) {
|
|
46
|
+
throw new Error(`Litterbox 上传失败: ${url}`);
|
|
47
|
+
}
|
|
48
|
+
return url;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 上传到 Catbox.moe(永久,无需认证)
|
|
52
|
+
*/
|
|
53
|
+
async function uploadToCatbox(filePath) {
|
|
54
|
+
const form = new FormData();
|
|
55
|
+
form.append('reqtype', 'fileupload');
|
|
56
|
+
form.append('fileToUpload', readFileSync(filePath), {
|
|
57
|
+
filename: path.basename(filePath),
|
|
58
|
+
contentType: 'image/png',
|
|
59
|
+
});
|
|
60
|
+
const res = await axios.post('https://catbox.moe/user/api.php', form, {
|
|
61
|
+
headers: form.getHeaders(),
|
|
62
|
+
timeout: 30000,
|
|
63
|
+
});
|
|
64
|
+
const url = res.data;
|
|
65
|
+
if (!url.startsWith('http')) {
|
|
66
|
+
throw new Error(`Catbox 上传失败: ${url}`);
|
|
67
|
+
}
|
|
68
|
+
return url;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 上传到 0x0.st(匿名,无需 API key)- 已禁用
|
|
72
|
+
*/
|
|
73
|
+
export async function uploadTo0x0st(filePath) {
|
|
74
|
+
const form = new FormData();
|
|
75
|
+
form.append('file', readFileSync(filePath), {
|
|
76
|
+
filename: path.basename(filePath),
|
|
77
|
+
contentType: 'image/png',
|
|
78
|
+
});
|
|
79
|
+
const res = await axios.post('https://0x0.st', form, {
|
|
80
|
+
headers: form.getHeaders(),
|
|
81
|
+
timeout: 30000,
|
|
82
|
+
});
|
|
83
|
+
const url = res.data.trim();
|
|
84
|
+
if (!url.startsWith('http')) {
|
|
85
|
+
throw new Error(`0x0.st 上传失败: ${url}`);
|
|
86
|
+
}
|
|
87
|
+
return url;
|
|
88
|
+
}
|
|
89
|
+
export { uploadToLitterbox, uploadToCatbox };
|
package/package.json
CHANGED
|
Binary file
|
package/themes/previews/nord.png
DELETED
|
Binary file
|