@sns-parse/core 0.4.0-alpha.9 → 0.6.0-alpha.1
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/lib/engine/compose.d.ts +57 -0
- package/lib/engine/compose.js +184 -0
- package/lib/engine/fetcher.js +18 -18
- package/lib/engine/flush.d.ts +25 -0
- package/lib/engine/flush.js +163 -0
- package/lib/engine/forward.d.ts +15 -0
- package/lib/engine/forward.js +61 -0
- package/lib/engine/runtime.d.ts +20 -16
- package/lib/engine/runtime.js +18 -19
- package/lib/engine/translate.d.ts +4 -9
- package/lib/engine/translate.js +3 -73
- package/lib/extensions.d.ts +25 -27
- package/lib/extensions.js +21 -0
- package/lib/host.d.ts +3 -3
- package/lib/index.d.ts +5 -4
- package/lib/index.js +5 -4
- package/lib/platform.d.ts +22 -2
- package/lib/registry.d.ts +23 -15
- package/lib/registry.js +56 -32
- package/lib/utils/merge.d.ts +3 -2
- package/lib/utils/merge.js +8 -5
- package/lib/workflow/default.d.ts +3 -0
- package/lib/workflow/default.js +71 -0
- package/lib/workflow/hooks.d.ts +125 -0
- package/lib/workflow/hooks.js +58 -0
- package/package.json +1 -1
package/lib/engine/runtime.js
CHANGED
|
@@ -4,14 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createRuntime = createRuntime;
|
|
7
|
-
exports.createCoreExtensions = createCoreExtensions;
|
|
8
7
|
/**
|
|
9
|
-
*
|
|
8
|
+
* 运行时装配(本文件在 core 内,平台无关)。
|
|
10
9
|
*
|
|
11
|
-
* 平台定义列表(defs
|
|
12
|
-
* - koishi
|
|
13
|
-
* - CLI
|
|
14
|
-
*
|
|
10
|
+
* 平台定义列表(defs)由宿主按已加载平台插件声明注入:
|
|
11
|
+
* - koishi 兼容层:collectPlatformDefinitions(anchor)(聚合包 + 粒度包并集)
|
|
12
|
+
* - CLI:collectPlatformDefinitions()
|
|
13
|
+
* 工作流阶段基线在 createDefaultPipeline(),扩展经 WorkflowExtension.setup(hooks) 注入/替换。
|
|
14
|
+
* core 不依赖任何扩展包、不含扩展实现。
|
|
15
15
|
*/
|
|
16
16
|
const axios_1 = __importDefault(require("axios"));
|
|
17
17
|
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
@@ -20,13 +20,11 @@ const cache_1 = require("../utils/cache");
|
|
|
20
20
|
const field_mapping_1 = require("../utils/field-mapping");
|
|
21
21
|
const platform_config_1 = require("./platform-config");
|
|
22
22
|
const gateway_1 = require("./gateway");
|
|
23
|
-
const
|
|
24
|
-
const gif_1 = require("../utils/gif");
|
|
25
|
-
const translate_1 = require("./translate");
|
|
23
|
+
const default_1 = require("../workflow/default");
|
|
26
24
|
function createRuntime(source, config, opts = {}) {
|
|
27
25
|
const host = (0, host_1.createHost)(source);
|
|
28
26
|
const ctx = host.context;
|
|
29
|
-
const extensions =
|
|
27
|
+
const extensions = [...(opts.extensions || []), ...(host.extensions || [])];
|
|
30
28
|
const dedupCache = new cache_1.SimpleLRUCache(1000, config.deduplicationInterval * 1000);
|
|
31
29
|
const cacheTTL = (config.cacheTTL || 600) * 1000;
|
|
32
30
|
const urlCacheLocal = new cache_1.SimpleLRUCache(500, cacheTTL);
|
|
@@ -64,7 +62,12 @@ function createRuntime(source, config, opts = {}) {
|
|
|
64
62
|
axiosConfig.proxy = false;
|
|
65
63
|
}
|
|
66
64
|
const http = axios_1.default.create(axiosConfig);
|
|
67
|
-
|
|
65
|
+
const pipeline = (0, default_1.createDefaultPipeline)(null);
|
|
66
|
+
for (const ext of extensions) {
|
|
67
|
+
if (ext && typeof ext.setup === 'function')
|
|
68
|
+
ext.setup(pipeline);
|
|
69
|
+
}
|
|
70
|
+
const runtime = {
|
|
68
71
|
host,
|
|
69
72
|
ctx,
|
|
70
73
|
config,
|
|
@@ -76,15 +79,11 @@ function createRuntime(source, config, opts = {}) {
|
|
|
76
79
|
contentDedupCache,
|
|
77
80
|
customPlatforms,
|
|
78
81
|
allRules,
|
|
82
|
+
defs,
|
|
79
83
|
dedicatedApis,
|
|
80
84
|
extensions,
|
|
85
|
+
pipeline,
|
|
81
86
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
function createCoreExtensions() {
|
|
85
|
-
return {
|
|
86
|
-
mergeImages: merge_1.mergeImages,
|
|
87
|
-
mp4ToGif: gif_1.mp4ToGif,
|
|
88
|
-
translate: translate_1.translateText,
|
|
89
|
-
};
|
|
87
|
+
pipeline.rt = runtime;
|
|
88
|
+
return runtime;
|
|
90
89
|
}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* X
|
|
5
|
-
* 仅返回 is_translatable 标志),无公开端点可调,故用通用翻译源兜底。
|
|
6
|
-
* 译文存入 ParsedData.translation,经 ${翻译} 模板变量展示,空值行自动隐藏。
|
|
2
|
+
* 语种决策与展示工具(纯函数,属调度/编排层的共享工具)。
|
|
3
|
+
* 翻译实现(Google gtx + MyMemory)在 @sns-parse/ext-translate;
|
|
4
|
+
* 平台原生翻译(如 X 网页同源 Grok)在对应平台包的 translate 钩子。
|
|
7
5
|
*/
|
|
8
|
-
import type { ParserRuntime } from './runtime';
|
|
9
6
|
import type { TranslateResult } from '../extensions';
|
|
10
7
|
export type { TranslateResult };
|
|
11
|
-
/**
|
|
8
|
+
/** 判断是否需要跳过翻译(目标语种与源语种相同 / 无文本内容) */
|
|
12
9
|
export declare function shouldSkipTranslate(tweetLang: string | undefined, target: string): boolean;
|
|
13
10
|
export declare function langName(code?: string): string;
|
|
14
|
-
/** 翻译文本;失败返回 null(调用方保持无译文继续发送)。gtx 优先,MyMemory 兜底 */
|
|
15
|
-
export declare function translateText(rt: ParserRuntime, text: string, target: string, sourceLang?: string): Promise<TranslateResult | null>;
|
package/lib/engine/translate.js
CHANGED
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.shouldSkipTranslate = shouldSkipTranslate;
|
|
4
4
|
exports.langName = langName;
|
|
5
|
-
|
|
6
|
-
const logger_1 = require("../logger");
|
|
7
|
-
/** 判断是否需要跳过翻译(目标语系与推文语种相同 / 无语言内容) */
|
|
5
|
+
/** 判断是否需要跳过翻译(目标语种与源语种相同 / 无文本内容) */
|
|
8
6
|
function shouldSkipTranslate(tweetLang, target) {
|
|
9
7
|
if (!tweetLang)
|
|
10
8
|
return false;
|
|
11
9
|
const l = tweetLang.toLowerCase();
|
|
12
10
|
if (l === 'zxx' || l === 'und')
|
|
13
|
-
return l === 'zxx'; // zxx
|
|
11
|
+
return l === 'zxx'; // zxx=无文本内容;und=未识别可尝试
|
|
14
12
|
const t = target.toLowerCase();
|
|
15
13
|
if (t === 'zh')
|
|
16
14
|
return l === 'zh' || l.startsWith('zh-');
|
|
@@ -20,7 +18,7 @@ function shouldSkipTranslate(tweetLang, target) {
|
|
|
20
18
|
return l === 'en';
|
|
21
19
|
return l === t;
|
|
22
20
|
}
|
|
23
|
-
/**
|
|
21
|
+
/** 语种代码 → 展示名(未知返回原代码) */
|
|
24
22
|
const LANG_NAMES = {
|
|
25
23
|
fr: '法语', ja: '日语', ko: '韩语', en: '英语', zh: '中文', es: '西班牙语', pt: '葡萄牙语',
|
|
26
24
|
de: '德语', ru: '俄语', ar: '阿拉伯语', it: '意大利语', th: '泰语', vi: '越南语',
|
|
@@ -37,71 +35,3 @@ function langName(code) {
|
|
|
37
35
|
return '';
|
|
38
36
|
return LANG_NAMES[base] || code;
|
|
39
37
|
}
|
|
40
|
-
/** 翻译文本;失败返回 null(调用方保持无译文继续发送)。gtx 优先,MyMemory 兜底 */
|
|
41
|
-
async function translateText(rt, text, target, sourceLang) {
|
|
42
|
-
if (!text)
|
|
43
|
-
return null;
|
|
44
|
-
const out = await viaGtx(rt, text, target);
|
|
45
|
-
if (out)
|
|
46
|
-
return { text: out, provider: 'Google' };
|
|
47
|
-
const backup = await viaMyMemory(rt, text, target, sourceLang);
|
|
48
|
-
if (backup)
|
|
49
|
-
return { text: backup, provider: 'MyMemory' };
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
/** Google gtx 免费端点(无 Key;sl=auto 自动检测源语种) */
|
|
53
|
-
async function viaGtx(rt, text, target) {
|
|
54
|
-
const url = 'https://translate.googleapis.com/translate_a/single' +
|
|
55
|
-
`?client=gtx&sl=auto&dt=t&tl=${encodeURIComponent(target)}` +
|
|
56
|
-
`&q=${encodeURIComponent(text.slice(0, 2000))}`;
|
|
57
|
-
try {
|
|
58
|
-
const res = await rt.http.get(url, {
|
|
59
|
-
timeout: 15000,
|
|
60
|
-
headers: { 'User-Agent': rt.config.userAgent },
|
|
61
|
-
});
|
|
62
|
-
const data = res.data;
|
|
63
|
-
// gtx 响应形如 [[["译文","原文",null,null,10]],null,"fr",...]
|
|
64
|
-
if (Array.isArray(data?.[0])) {
|
|
65
|
-
const out = data[0].map((seg) => (typeof seg?.[0] === 'string' ? seg[0] : '')).join('').trim();
|
|
66
|
-
if (out)
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
(0, logger_1.debugLog)('gtx 翻译响应结构异常,尝试备用通道');
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
catch (e) {
|
|
73
|
-
(0, logger_1.debugLog)(`gtx 翻译失败:${e?.message || e}`);
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/** MyMemory 备用(免费无 Key;需已知源语种,不支持 auto) */
|
|
78
|
-
async function viaMyMemory(rt, text, target, sourceLang) {
|
|
79
|
-
const sl = normalizeLang(sourceLang);
|
|
80
|
-
if (!sl)
|
|
81
|
-
return null;
|
|
82
|
-
const tl = target.toLowerCase();
|
|
83
|
-
const url = 'https://api.mymemory.translated.net/get' +
|
|
84
|
-
`?q=${encodeURIComponent(text.slice(0, 500))}&langpair=${encodeURIComponent(sl + '|' + tl)}`;
|
|
85
|
-
try {
|
|
86
|
-
const res = await rt.http.get(url, { timeout: 15000, headers: { 'User-Agent': rt.config.userAgent } });
|
|
87
|
-
const out = res.data?.responseData?.translatedText;
|
|
88
|
-
if (typeof out === 'string' && out.trim() && !/^MYMEMORY WARNING/i.test(out))
|
|
89
|
-
return out.trim();
|
|
90
|
-
(0, logger_1.debugLog)('MyMemory 翻译响应异常,放弃译文');
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
catch (e) {
|
|
94
|
-
logger_1.logger.info(`推文翻译失败(跳过译文继续发送):${e?.message || e}`);
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function normalizeLang(lang) {
|
|
99
|
-
if (!lang)
|
|
100
|
-
return null;
|
|
101
|
-
const l = lang.toLowerCase();
|
|
102
|
-
if (l === 'und' || l === 'zxx')
|
|
103
|
-
return null;
|
|
104
|
-
if (l.startsWith('zh'))
|
|
105
|
-
return 'zh-CN';
|
|
106
|
-
return l.split('-')[0];
|
|
107
|
-
}
|
package/lib/extensions.d.ts
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 扩展契约:core
|
|
3
|
-
* VideoParserExtensions 调用。宿主可整体替换或按需覆盖单项能力。
|
|
2
|
+
* 扩展契约:core 完整工作流 + 基线行为;扩展经阶段钩子注入/修改。
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* core 不内建任何扩展实现(NSFW/合并/翻译/GIF),也不依赖扩展包;
|
|
5
|
+
* 基线行为保证无扩展时全链路可跑(媒体直通、不合并逐张发、GIF 退化发视频、不翻译)。
|
|
6
|
+
* 扩展导出 WorkflowExtension(含 setup(hooks)),由宿主收集后注入管道。
|
|
7
7
|
*/
|
|
8
|
+
import type { ConfigContribution } from './config';
|
|
8
9
|
import type { ParserRuntimeLike } from './host';
|
|
10
|
+
import type { WorkflowHooks } from './workflow/hooks';
|
|
9
11
|
export type MediaKind = 'cover' | 'image' | 'avatar' | 'music-cover';
|
|
10
12
|
export interface ImageOutcome {
|
|
11
|
-
/** raw=原图 url
|
|
13
|
+
/** raw=原图 url 或 buffer 直发;scrambled=混淆 buffer+token;link=只发链接文本;drop=丢弃 */
|
|
12
14
|
kind: 'raw' | 'scrambled' | 'link' | 'drop';
|
|
13
15
|
url?: string;
|
|
14
16
|
buffer?: Buffer;
|
|
15
17
|
token?: string;
|
|
18
|
+
/** buffer 直发时的 mime(缺省 image/jpeg) */
|
|
19
|
+
mime?: string;
|
|
16
20
|
}
|
|
17
21
|
export interface VideoOutcome {
|
|
18
|
-
/** raw
|
|
22
|
+
/** raw=直发视频;card=群内容易吞 → 暂存+token,无法直发视频;link=只发卡片+原链接;drop=只发卡片 */
|
|
19
23
|
kind: 'raw' | 'card' | 'link' | 'drop';
|
|
20
24
|
url?: string;
|
|
21
25
|
token?: string;
|
|
@@ -29,29 +33,23 @@ export interface GifOptions {
|
|
|
29
33
|
fps: number;
|
|
30
34
|
maxDurationSec: number;
|
|
31
35
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
title?: string;
|
|
43
|
-
author?: string;
|
|
44
|
-
requesterId: string;
|
|
45
|
-
}): Promise<VideoOutcome>;
|
|
46
|
-
/** 合并图审核;返回 null 表示回退逐张处理 */
|
|
47
|
-
processMergedImage?(rt: ParserRuntimeLike, platform: string, buffer: Buffer, refUrl: string): Promise<ImageOutcome | null>;
|
|
48
|
-
/** 视频转 GIF;失败返回 null 回退原视频 */
|
|
49
|
-
mp4ToGif?(rt: ParserRuntimeLike, url: string, durationSec: number, opts: GifOptions): Promise<Buffer | null>;
|
|
50
|
-
/** 文本翻译;失败返回 null */
|
|
51
|
-
translate?(rt: ParserRuntimeLike, text: string, target: string, sourceLang?: string): Promise<TranslateResult | null>;
|
|
52
|
-
/** 内容安全能力状态(启动日志) */
|
|
36
|
+
export interface VideoMeta {
|
|
37
|
+
title?: string;
|
|
38
|
+
author?: string;
|
|
39
|
+
requesterId: string;
|
|
40
|
+
}
|
|
41
|
+
/** 单个扩展片段:能力注册(setup 钩子注入)+ 可选配置贡献/能力位 */
|
|
42
|
+
export interface WorkflowExtension {
|
|
43
|
+
name: string;
|
|
44
|
+
setup(hooks: WorkflowHooks): void;
|
|
45
|
+
configContribution?: ConfigContribution;
|
|
53
46
|
capability?(rt: ParserRuntimeLike): {
|
|
54
47
|
ferret: boolean;
|
|
55
48
|
moderation: string | null;
|
|
56
49
|
};
|
|
57
50
|
}
|
|
51
|
+
/** 汇总各扩展能力位(日志用) */
|
|
52
|
+
export declare function collectCapabilities(extensions: WorkflowExtension[] | undefined, rt: ParserRuntimeLike): {
|
|
53
|
+
ferret: boolean;
|
|
54
|
+
moderation: string | null;
|
|
55
|
+
};
|
package/lib/extensions.js
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectCapabilities = collectCapabilities;
|
|
4
|
+
/** 汇总各扩展能力位(日志用) */
|
|
5
|
+
function collectCapabilities(extensions, rt) {
|
|
6
|
+
let ferret = false;
|
|
7
|
+
let moderation = null;
|
|
8
|
+
for (const e of extensions || []) {
|
|
9
|
+
if (typeof e.capability !== 'function')
|
|
10
|
+
continue;
|
|
11
|
+
try {
|
|
12
|
+
const c = e.capability(rt);
|
|
13
|
+
if (c?.ferret)
|
|
14
|
+
ferret = true;
|
|
15
|
+
if (c?.moderation)
|
|
16
|
+
moderation = c.moderation;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// 能力位探测失败不影响主链路
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { ferret, moderation };
|
|
23
|
+
}
|
package/lib/host.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type LoggerLike } from './logger';
|
|
11
11
|
import type { OutboundSender } from './sender';
|
|
12
|
-
import type {
|
|
12
|
+
import type { WorkflowExtension } from './extensions';
|
|
13
13
|
export interface VideoParserHost {
|
|
14
14
|
logger: LoggerLike;
|
|
15
15
|
/** 数据根目录(如 koishi ctx.baseDir);缺省时持久化走临时目录 */
|
|
@@ -19,7 +19,7 @@ export interface VideoParserHost {
|
|
|
19
19
|
/** 发送层实现(缺省时由宿主适配层注入) */
|
|
20
20
|
sender?: OutboundSender;
|
|
21
21
|
/** 扩展实现(部分覆盖;缺省能力由内置实现补齐) */
|
|
22
|
-
extensions?:
|
|
22
|
+
extensions?: WorkflowExtension[];
|
|
23
23
|
/** 过渡期:底层上下文(koishi Context 等),core 新代码不应依赖 */
|
|
24
24
|
context?: any;
|
|
25
25
|
}
|
|
@@ -31,7 +31,7 @@ export interface ParserRuntimeLike {
|
|
|
31
31
|
config: any;
|
|
32
32
|
http: any;
|
|
33
33
|
host: VideoParserHost;
|
|
34
|
-
extensions:
|
|
34
|
+
extensions: WorkflowExtension[];
|
|
35
35
|
[key: string]: any;
|
|
36
36
|
}
|
|
37
37
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -16,16 +16,17 @@ export * from './utils/concurrency';
|
|
|
16
16
|
export * from './utils/field-mapping';
|
|
17
17
|
export * from './utils/image-size';
|
|
18
18
|
export * from './utils/tls-client';
|
|
19
|
-
export * from './utils/gif';
|
|
20
|
-
export * from './utils/merge';
|
|
21
19
|
export * from './engine/gateway';
|
|
22
20
|
export * from './engine/config';
|
|
23
21
|
export * from './engine/platform-config';
|
|
24
22
|
export * from './engine/translate';
|
|
25
|
-
export * from './engine/twitter';
|
|
26
|
-
export * from './engine/twitter-user';
|
|
27
23
|
export * from './engine/parser';
|
|
28
24
|
export * from './engine/fetcher';
|
|
25
|
+
export * from './engine/compose';
|
|
26
|
+
export * from './engine/forward';
|
|
27
|
+
export * from './engine/flush';
|
|
29
28
|
export * from './engine/runtime';
|
|
30
29
|
export * from './engine/config-io';
|
|
30
|
+
export * from './workflow/hooks';
|
|
31
|
+
export * from './workflow/default';
|
|
31
32
|
export * from './registry';
|
package/lib/index.js
CHANGED
|
@@ -32,16 +32,17 @@ __exportStar(require("./utils/concurrency"), exports);
|
|
|
32
32
|
__exportStar(require("./utils/field-mapping"), exports);
|
|
33
33
|
__exportStar(require("./utils/image-size"), exports);
|
|
34
34
|
__exportStar(require("./utils/tls-client"), exports);
|
|
35
|
-
__exportStar(require("./utils/gif"), exports);
|
|
36
|
-
__exportStar(require("./utils/merge"), exports);
|
|
37
35
|
__exportStar(require("./engine/gateway"), exports);
|
|
38
36
|
__exportStar(require("./engine/config"), exports);
|
|
39
37
|
__exportStar(require("./engine/platform-config"), exports);
|
|
40
38
|
__exportStar(require("./engine/translate"), exports);
|
|
41
|
-
__exportStar(require("./engine/twitter"), exports);
|
|
42
|
-
__exportStar(require("./engine/twitter-user"), exports);
|
|
43
39
|
__exportStar(require("./engine/parser"), exports);
|
|
44
40
|
__exportStar(require("./engine/fetcher"), exports);
|
|
41
|
+
__exportStar(require("./engine/compose"), exports);
|
|
42
|
+
__exportStar(require("./engine/forward"), exports);
|
|
43
|
+
__exportStar(require("./engine/flush"), exports);
|
|
45
44
|
__exportStar(require("./engine/runtime"), exports);
|
|
46
45
|
__exportStar(require("./engine/config-io"), exports);
|
|
46
|
+
__exportStar(require("./workflow/hooks"), exports);
|
|
47
|
+
__exportStar(require("./workflow/default"), exports);
|
|
47
48
|
__exportStar(require("./registry"), exports);
|
package/lib/platform.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
import type { ParsedData } from './types';
|
|
1
3
|
import { type ConfigContribution } from './config';
|
|
4
|
+
/** 平台原生解析上下文(fetcher 透传:HTTP 实例、配置、GraphQL GET 器) */
|
|
5
|
+
export interface PlatformParseContext {
|
|
6
|
+
http: AxiosInstance;
|
|
7
|
+
config: any;
|
|
8
|
+
/** TLS 指纹 GET 器(tlsget-rs;平台实现用于鉴权 GraphQL) */
|
|
9
|
+
getGraphql?: (url: string, opts: any) => Promise<{
|
|
10
|
+
status: number;
|
|
11
|
+
data: any;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
2
14
|
/**
|
|
3
15
|
* 平台定义契约:每个平台的自描述单元,是「每平台一个包」的基础。
|
|
4
16
|
*
|
|
5
|
-
* 一个 PlatformDefinition
|
|
6
|
-
*
|
|
17
|
+
* 一个 PlatformDefinition 自包含该平台的全部静态信息(链接规则、专属 API、
|
|
18
|
+
* 提示等);有原生解析能力的平台(如 X/Twitter 的 syndication/GraphQL 路径)
|
|
19
|
+
* 通过 parse/translate 钩子提供实现——core 只调度,不持有平台实现。
|
|
7
20
|
*/
|
|
8
21
|
export interface PlatformDefinition {
|
|
9
22
|
/** 平台类型标识(与 config.platformEnabled 的键一致) */
|
|
@@ -23,6 +36,13 @@ export interface PlatformDefinition {
|
|
|
23
36
|
label?: string;
|
|
24
37
|
/** 解析失败时的平台特定引导提示 */
|
|
25
38
|
hints?: string[];
|
|
39
|
+
/** 原生解析钩子:不经网关,由平台包自实现(如 X syndication/GraphQL) */
|
|
40
|
+
parse?(url: string, ctx: PlatformParseContext): Promise<ParsedData>;
|
|
41
|
+
/** 原生翻译钩子(如 X 网页同源 Grok 翻译);失败/不支持返回 null 回落通用翻译 */
|
|
42
|
+
translate?(ctx: PlatformParseContext, url: string, text: string, target: string): Promise<{
|
|
43
|
+
text: string;
|
|
44
|
+
sourceLang?: string;
|
|
45
|
+
} | null>;
|
|
26
46
|
}
|
|
27
47
|
/**
|
|
28
48
|
* 由平台定义聚合出「平台相关配置」(平台开关 / 专属 API 优先)。
|
package/lib/registry.d.ts
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 已安装碎片包(平台 @sns-parse/platform-* / 扩展 @sns-parse/ext-*)的动态发现。
|
|
3
|
+
*
|
|
4
|
+
* - 平台:先聚合包(@sns-parse/platforms 的 definitions[]),再逐个候选粒度包,
|
|
5
|
+
* 按 type 去重合并(粒度包覆盖聚合条目);宿主可传自己的 require 锚点
|
|
6
|
+
* (koishi/CLI 包路径),pnpm strict 下也能发现宿主直依赖。
|
|
7
|
+
* - 扩展:loadWorkflowExtensions() 返回 WorkflowExtension[](含 setup 钩子),
|
|
8
|
+
* 未安装的自动缺席(能力/配置贡献同时缺席,不报错)。
|
|
9
|
+
*/
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
1
11
|
import type { PlatformDefinition } from './platform';
|
|
2
12
|
import type { ConfigContribution } from './config';
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
13
|
+
import type { WorkflowExtension } from './extensions';
|
|
14
|
+
type NodeRequire = ReturnType<typeof createRequire>;
|
|
15
|
+
/** 收集已加载平台的定义(聚合包 + 粒度包并集;粒度包覆盖聚合条目) */
|
|
16
|
+
export declare function collectPlatformDefinitions(anchor?: NodeRequire): PlatformDefinition[];
|
|
17
|
+
/** 已加载平台的链接规则(按平台声明) */
|
|
18
|
+
export declare function collectPlatformLinkRules(anchor?: NodeRequire): {
|
|
8
19
|
pattern: RegExp;
|
|
9
20
|
type: string;
|
|
10
21
|
}[];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
export declare function loadExtensionContributions(): ConfigContribution[];
|
|
19
|
-
/** 合并所有已安装扩展的能力片段 */
|
|
20
|
-
export declare function loadExtensionImplementations(): Partial<VideoParserExtensions>;
|
|
22
|
+
/** 探测并实例化全部已安装扩展(WorkflowExtension[];含 setup 钩子) */
|
|
23
|
+
export declare function loadWorkflowExtensions(anchor?: NodeRequire): WorkflowExtension[];
|
|
24
|
+
/** 已安装扩展的配置贡献(settings 动态渲染用) */
|
|
25
|
+
export declare function loadExtensionContributions(anchor?: NodeRequire): ConfigContribution[];
|
|
26
|
+
/** 兼容旧名(已废弃:请用 loadWorkflowExtensions) */
|
|
27
|
+
export declare function loadExtensionImplementations(anchor?: NodeRequire): WorkflowExtension[];
|
|
28
|
+
export {};
|
package/lib/registry.js
CHANGED
|
@@ -2,76 +2,100 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.collectPlatformDefinitions = collectPlatformDefinitions;
|
|
4
4
|
exports.collectPlatformLinkRules = collectPlatformLinkRules;
|
|
5
|
-
exports.
|
|
5
|
+
exports.loadWorkflowExtensions = loadWorkflowExtensions;
|
|
6
6
|
exports.loadExtensionContributions = loadExtensionContributions;
|
|
7
7
|
exports.loadExtensionImplementations = loadExtensionImplementations;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* 已安装碎片包(平台 @sns-parse/platform-* / 扩展 @sns-parse/ext-*)的动态发现。
|
|
10
10
|
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* - 平台:先聚合包(@sns-parse/platforms 的 definitions[]),再逐个候选粒度包,
|
|
12
|
+
* 按 type 去重合并(粒度包覆盖聚合条目);宿主可传自己的 require 锚点
|
|
13
|
+
* (koishi/CLI 包路径),pnpm strict 下也能发现宿主直依赖。
|
|
14
|
+
* - 扩展:loadWorkflowExtensions() 返回 WorkflowExtension[](含 setup 钩子),
|
|
15
|
+
* 未安装的自动缺席(能力/配置贡献同时缺席,不报错)。
|
|
14
16
|
*/
|
|
15
17
|
const node_module_1 = require("node:module");
|
|
16
|
-
const
|
|
18
|
+
const fallbackRequire = (0, node_module_1.createRequire)(typeof __filename !== 'undefined' ? __filename : process.cwd() + '/');
|
|
19
|
+
function req(anchor) {
|
|
20
|
+
return anchor || fallbackRequire;
|
|
21
|
+
}
|
|
17
22
|
const PLATFORM_TYPES = [
|
|
18
23
|
'bilibili', 'douyin', 'kuaishou', 'xiaohongshu', 'weibo', 'xigua', 'youtube',
|
|
19
24
|
'tiktok', 'acfun', 'zhihu', 'weishi', 'huya', 'haokan', 'meipai', 'twitter',
|
|
20
25
|
'instagram', 'doubao', 'doubao_image', 'jimeng', 'oasis', 'wechat_channel',
|
|
21
26
|
'lishi', 'quanmin', 'pipigx', 'pipixia', 'zuiyou', 'toutiao',
|
|
22
27
|
];
|
|
23
|
-
/**
|
|
24
|
-
function collectPlatformDefinitions() {
|
|
25
|
-
const
|
|
28
|
+
/** 收集已加载平台的定义(聚合包 + 粒度包并集;粒度包覆盖聚合条目) */
|
|
29
|
+
function collectPlatformDefinitions(anchor) {
|
|
30
|
+
const nodeRequire = req(anchor);
|
|
31
|
+
const byType = new Map();
|
|
32
|
+
// 1) 聚合包(若安装):一次拿全声明
|
|
33
|
+
try {
|
|
34
|
+
const agg = nodeRequire('@sns-parse/platforms');
|
|
35
|
+
const defs = agg?.definitions || agg?.default?.definitions || [];
|
|
36
|
+
for (const d of defs) {
|
|
37
|
+
if (d && d.type && !byType.has(d.type))
|
|
38
|
+
byType.set(d.type, d);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// 未装聚合包 → 只看粒度包
|
|
43
|
+
}
|
|
44
|
+
// 2) 粒度包(可覆盖聚合条目)
|
|
26
45
|
for (const type of PLATFORM_TYPES) {
|
|
27
46
|
try {
|
|
28
47
|
const mod = nodeRequire(`@sns-parse/platform-${type}`);
|
|
29
48
|
const def = mod.default || mod[type];
|
|
30
49
|
if (def && def.type)
|
|
31
|
-
|
|
50
|
+
byType.set(def.type, def);
|
|
32
51
|
}
|
|
33
52
|
catch {
|
|
34
|
-
//
|
|
53
|
+
// 未装该平台 → 缺席
|
|
35
54
|
}
|
|
36
55
|
}
|
|
37
|
-
return
|
|
56
|
+
return [...byType.values()];
|
|
38
57
|
}
|
|
39
|
-
/**
|
|
40
|
-
function collectPlatformLinkRules() {
|
|
41
|
-
return collectPlatformDefinitions().flatMap(d => d.rules.map(pattern => ({ pattern, type: d.type })));
|
|
58
|
+
/** 已加载平台的链接规则(按平台声明) */
|
|
59
|
+
function collectPlatformLinkRules(anchor) {
|
|
60
|
+
return collectPlatformDefinitions(anchor).flatMap(d => d.rules.map(pattern => ({ pattern, type: d.type })));
|
|
42
61
|
}
|
|
43
62
|
const EXTENSION_SPECS = [
|
|
44
|
-
{ name: 'ext-nsfw',
|
|
63
|
+
{ name: 'ext-nsfw', extensionKey: 'nsfwExtension', contributionKey: 'nsfwConfigContribution' },
|
|
45
64
|
{ name: 'ext-merge', extensionKey: 'mergeExtension' },
|
|
46
65
|
{ name: 'ext-translate', extensionKey: 'translateExtension' },
|
|
47
66
|
{ name: 'ext-gif', extensionKey: 'gifExtension' },
|
|
48
67
|
];
|
|
49
|
-
/**
|
|
50
|
-
function
|
|
68
|
+
/** 探测并实例化全部已安装扩展(WorkflowExtension[];含 setup 钩子) */
|
|
69
|
+
function loadWorkflowExtensions(anchor) {
|
|
70
|
+
const nodeRequire = req(anchor);
|
|
51
71
|
const out = [];
|
|
52
72
|
for (const spec of EXTENSION_SPECS) {
|
|
53
73
|
try {
|
|
54
74
|
const mod = nodeRequire(`@sns-parse/${spec.name}`);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
const factory = mod?.[spec.extensionKey];
|
|
76
|
+
if (typeof factory !== 'function')
|
|
77
|
+
continue;
|
|
78
|
+
const ext = factory();
|
|
79
|
+
if (!ext || typeof ext.setup !== 'function')
|
|
80
|
+
continue;
|
|
81
|
+
if (!ext.configContribution && spec.contributionKey && mod[spec.contributionKey]) {
|
|
82
|
+
ext.configContribution = mod[spec.contributionKey];
|
|
83
|
+
}
|
|
84
|
+
out.push(ext);
|
|
62
85
|
}
|
|
63
86
|
catch {
|
|
64
|
-
//
|
|
87
|
+
// 未安装的扩展 → 缺席(能力与配置贡献同时缺席)
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
return out;
|
|
68
91
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
/** 已安装扩展的配置贡献(settings 动态渲染用) */
|
|
93
|
+
function loadExtensionContributions(anchor) {
|
|
94
|
+
return loadWorkflowExtensions(anchor)
|
|
95
|
+
.map(e => e.configContribution)
|
|
72
96
|
.filter((c) => !!c);
|
|
73
97
|
}
|
|
74
|
-
/**
|
|
75
|
-
function loadExtensionImplementations() {
|
|
76
|
-
return
|
|
98
|
+
/** 兼容旧名(已废弃:请用 loadWorkflowExtensions) */
|
|
99
|
+
function loadExtensionImplementations(anchor) {
|
|
100
|
+
return loadWorkflowExtensions(anchor);
|
|
77
101
|
}
|
package/lib/utils/merge.d.ts
CHANGED
|
@@ -28,8 +28,9 @@ export interface SeamVerdict {
|
|
|
28
28
|
/** 结论文案 */
|
|
29
29
|
reason: string;
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
31
|
+
/** 内容验证:所有接缝(趋势+纹理)均通过才成立;接缝总数 ≥3 时允许至多一条
|
|
32
|
+
* 「边界容差缝」(比值 ∈ [3,5)——平台独立重压缩的典型错位幅度;多条缝相互
|
|
33
|
+
* 印证才可信,2 缝场景证据不足不放行;平坦不可验证缝不参与容差)。
|
|
33
34
|
* 返回最大接缝比值与逐缝证据。 */
|
|
34
35
|
export declare function verifyLayout(layout: MergeLayout, grays: Buffer[]): {
|
|
35
36
|
pass: boolean;
|
package/lib/utils/merge.js
CHANGED
|
@@ -37,10 +37,12 @@ const COL_LEN = 64;
|
|
|
37
37
|
/** 接缝差异 / 内部自然差异 的判定阈值(母图切片 ≈1,无关图片 ≫1;下限 3 防平坦图除零) */
|
|
38
38
|
const BOUNDARY_THRESH = 3.0;
|
|
39
39
|
const MAD_FLOOR = 3.0;
|
|
40
|
-
/** 单缝边界容差:平台(如 X
|
|
41
|
-
*
|
|
40
|
+
/** 单缝边界容差:平台(如 X)对各分片独立重压缩会让个别接缝低频轻微错位。
|
|
41
|
+
* 仅当接缝总数 ≥3(≥4 图,多条缝相互印证)且恰好一条落在
|
|
42
|
+
* [BOUNDARY_THRESH, BORDERLINE_THRESH)、其余全部通过时放行;平坦不可验证缝不参与容差 */
|
|
42
43
|
const BORDERLINE_THRESH = 5.0;
|
|
43
44
|
const BORDERLINE_MAX = 1;
|
|
45
|
+
const BORDERLINE_MIN_SEAMS = 3;
|
|
44
46
|
/** 边界带纹理能量下限:低于此值视为「不可验证接缝」(相似背景/平边照片会骗过亮度对比) */
|
|
45
47
|
const TEXTURE_MIN = 4.0;
|
|
46
48
|
/* ---------- 尺寸候选 ---------- */
|
|
@@ -188,8 +190,9 @@ function seamVerdictH(a, b, from, to) {
|
|
|
188
190
|
[colOf(b, 2), colOf(b, 1)],
|
|
189
191
|
]);
|
|
190
192
|
}
|
|
191
|
-
/**
|
|
192
|
-
*
|
|
193
|
+
/** 内容验证:所有接缝(趋势+纹理)均通过才成立;接缝总数 ≥3 时允许至多一条
|
|
194
|
+
* 「边界容差缝」(比值 ∈ [3,5)——平台独立重压缩的典型错位幅度;多条缝相互
|
|
195
|
+
* 印证才可信,2 缝场景证据不足不放行;平坦不可验证缝不参与容差)。
|
|
193
196
|
* 返回最大接缝比值与逐缝证据。 */
|
|
194
197
|
function verifyLayout(layout, grays) {
|
|
195
198
|
const seams = [];
|
|
@@ -217,7 +220,7 @@ function verifyLayout(layout, grays) {
|
|
|
217
220
|
const failed = seams.filter((v) => !v.ok);
|
|
218
221
|
const borderline = failed.filter((v) => v.score < BORDERLINE_THRESH);
|
|
219
222
|
const pass = seams.every((v) => v.ok)
|
|
220
|
-
|| (borderline.length <= BORDERLINE_MAX && failed.length === borderline.length);
|
|
223
|
+
|| (seams.length >= BORDERLINE_MIN_SEAMS && borderline.length <= BORDERLINE_MAX && failed.length === borderline.length);
|
|
221
224
|
return { pass, score: worst, seams };
|
|
222
225
|
}
|
|
223
226
|
/** 内容识别:候选布局逐一经接缝连续性验证,取通过者中比值最小的一个 */
|