@sns-parse/core 0.4.0-alpha.2 → 0.4.0-alpha.4

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/config.d.ts CHANGED
@@ -22,6 +22,8 @@ export interface ConfigField {
22
22
  /** koishi 的 role:多行文本 / 密钥 */
23
23
  role?: 'textarea' | 'secret';
24
24
  hidden?: boolean;
25
+ /** 宿主 Schema 生成时的必填标记(仅 UI 层语义) */
26
+ required?: boolean;
25
27
  min?: number;
26
28
  max?: number;
27
29
  step?: number;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 引擎层配置声明(中立 DSL):fetcher/parser/compose/sender/gateway 等引擎
3
+ * 自身读取的配置项,由 core 统一声明;koishi / CLI 兼容层据此生成 Schema 与默认值。
4
+ *
5
+ * 组序即 UI 展示序(koishi 兼容层在「发送策略」后插入扩展与平台的动态声明)。
6
+ */
7
+ import { type ConfigContribution } from '../config';
8
+ /** 引擎层配置声明(有序;组名即 UI 分组标题) */
9
+ export declare function engineConfigContributions(): ConfigContribution[];
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.engineConfigContributions = engineConfigContributions;
4
+ /**
5
+ * 引擎层配置声明(中立 DSL):fetcher/parser/compose/sender/gateway 等引擎
6
+ * 自身读取的配置项,由 core 统一声明;koishi / CLI 兼容层据此生成 Schema 与默认值。
7
+ *
8
+ * 组序即 UI 展示序(koishi 兼容层在「发送策略」后插入扩展与平台的动态声明)。
9
+ */
10
+ const config_1 = require("../config");
11
+ const PROXY_FIELDS = [
12
+ { key: 'enabled', type: 'boolean', default: false, description: '启用代理' },
13
+ {
14
+ key: 'protocol', type: 'union', default: 'http', description: '协议',
15
+ values: [
16
+ { value: 'http', description: 'HTTP' },
17
+ { value: 'https', description: 'HTTPS' },
18
+ ],
19
+ },
20
+ { key: 'host', type: 'string', default: '127.0.0.1', description: '地址' },
21
+ { key: 'port', type: 'number', default: 7890, description: '端口' },
22
+ {
23
+ key: 'auth', type: 'object', description: '认证',
24
+ fields: [
25
+ { key: 'username', type: 'string', default: '', description: '用户名' },
26
+ { key: 'password', type: 'string', default: '', description: '密码' },
27
+ ],
28
+ },
29
+ ];
30
+ const AUTH_HEADER_FIELDS = [
31
+ {
32
+ key: 'authHeaderType', type: 'union', default: 'Bearer', description: '认证头类型',
33
+ values: [
34
+ { value: 'Bearer', description: 'Bearer' },
35
+ { value: 'X-API-Key', description: 'X-API-Key' },
36
+ { value: 'Custom', description: '自定义' },
37
+ ],
38
+ },
39
+ { key: 'customHeaderName', type: 'string', default: 'X-API-Key', description: '自定义头名称' },
40
+ ];
41
+ const FIELD_MAPPING_FIELD = {
42
+ key: 'fieldMapping', type: 'string', role: 'textarea',
43
+ default: '{}', description: '字段映射 JSON',
44
+ };
45
+ const API_PLATFORM_VALUES = [
46
+ { value: 'bilibili', description: '哔哩哔哩' },
47
+ { value: 'douyin', description: '抖音' },
48
+ { value: 'kuaishou', description: '快手' },
49
+ { value: 'xiaohongshu', description: '小红书' },
50
+ { value: 'weibo', description: '微博' },
51
+ { value: 'xigua', description: '西瓜视频' },
52
+ { value: 'youtube', description: 'YouTube' },
53
+ { value: 'tiktok', description: 'TikTok' },
54
+ { value: 'acfun', description: 'AcFun(A站)' },
55
+ { value: 'zhihu', description: '知乎' },
56
+ { value: 'weishi', description: '微视' },
57
+ { value: 'huya', description: '虎牙' },
58
+ { value: 'haokan', description: '好看视频' },
59
+ { value: 'meipai', description: '美拍' },
60
+ { value: 'twitter', description: 'Twitter/X' },
61
+ { value: 'instagram', description: 'Instagram' },
62
+ { value: 'doubao', description: '豆包' },
63
+ { value: 'doubao_image', description: '豆包图片' },
64
+ { value: 'jimeng', description: '即梦' },
65
+ { value: 'oasis', description: '绿洲' },
66
+ { value: 'wechat_channel', description: '视频号' },
67
+ { value: 'lishi', description: '梨视频' },
68
+ { value: 'quanmin', description: '全民直播' },
69
+ { value: 'pipigx', description: '皮皮搞笑' },
70
+ { value: 'pipixia', description: '皮皮虾' },
71
+ { value: 'zuiyou', description: '最右' },
72
+ { value: 'toutiao', description: '今日头条' },
73
+ ];
74
+ const GLOBAL_FIELD_MAPPING_DEFAULT = [
75
+ '{',
76
+ ' "title": "data.title",',
77
+ ' "desc": "data.description",',
78
+ ' "author": "data.author.name",',
79
+ ' "uid": "data.author.id",',
80
+ ' "avatar": "data.author.avatar",',
81
+ ' "cover": "data.cover_url",',
82
+ ' "video": "data.video_url",',
83
+ ' "video_backup": "data.video_qualities",',
84
+ ' "videos": "data.videos",',
85
+ ' "type": "data.type",',
86
+ ' "like": "data.statistics.likes",',
87
+ ' "comment": "data.statistics.comments",',
88
+ ' "collect": "data.statistics.favorites",',
89
+ ' "share": "data.statistics.shares",',
90
+ ' "play": "data.statistics.plays",',
91
+ ' "duration": "data.duration",',
92
+ ' "publishTime": "data.create_time",',
93
+ ' "music_title": "data.music.title",',
94
+ ' "music_author": "data.music.author",',
95
+ ' "music_cover": "data.music.cover",',
96
+ ' "music_url": "data.music.url"',
97
+ '}',
98
+ ].join('\n');
99
+ /** 引擎层配置声明(有序;组名即 UI 分组标题) */
100
+ function engineConfigContributions() {
101
+ return [
102
+ (0, config_1.defineConfig)({
103
+ group: '消息格式',
104
+ fields: [
105
+ {
106
+ key: 'unifiedMessageFormat', type: 'string', role: 'textarea',
107
+ default: '标题:${标题}\n作者:${作者}\n简介:${简介}\n翻译(${翻译提供方},${原文语言}):${翻译}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}',
108
+ description: '消息文字模板,可用变量:${标题} ${作者} ${简介} ${翻译} ${翻译提供方} ${原文语言} ${视频时长} ${点赞数} ${收藏数} ${转发数} ${播放数} ${评论数} ${发布时间} ${图片数量} ${作者ID} ${音乐标题} ${音乐作者}(空行自动隐藏)',
109
+ },
110
+ ],
111
+ }),
112
+ (0, config_1.defineConfig)({
113
+ group: '媒体发送',
114
+ fields: [
115
+ { key: 'showImageText', type: 'boolean', default: true, description: '发送文字内容' },
116
+ { key: 'showCoverImage', type: 'boolean', default: true, description: '发送封面图片' },
117
+ { key: 'showCoverFile', type: 'boolean', default: true, description: '封面以图片形式发送(关闭则只发链接)' },
118
+ { key: 'showCoverText', type: 'boolean', default: true, description: '封面图前显示文字提示' },
119
+ { key: 'coverText', type: 'string', default: '封面:', description: '封面提示文字' },
120
+ { key: 'showImageFileNew', type: 'boolean', default: true, description: '图片以图片形式发送(关闭则只发链接)' },
121
+ { key: 'showAuthorAvatar', type: 'boolean', default: true, description: '发送作者头像' },
122
+ { key: 'showAuthorAvatarFile', type: 'boolean', default: true, description: '头像以图片形式发送(关闭则只发链接)' },
123
+ { key: 'showAuthorAvatarText', type: 'boolean', default: true, description: '头像前显示文字提示' },
124
+ { key: 'authorAvatarText', type: 'string', default: '作者头像:', description: '头像提示文字' },
125
+ { key: 'showMusicCover', type: 'boolean', default: true, description: '发送音乐封面' },
126
+ { key: 'showVideoFile', type: 'boolean', default: true, description: '视频以视频形式发送(关闭则只发链接)' },
127
+ { key: 'sendLiveMessage', type: 'boolean', default: true, description: '直播作品发文字消息(不发视频)' },
128
+ { key: 'mergeSameOriginImages', type: 'boolean', default: true, description: '同源切图内容识别合并(纯像素内容裁决,不依赖链接/文件名/色调风格):网格/竖堆/横拼三种布局各自经接缝连续性验证(低频趋势延续+纹理能量可验证性),取证据最强者合并;均不过回退逐张;需 ffmpeg' },
129
+ ],
130
+ }),
131
+ (0, config_1.defineConfig)({
132
+ group: '音乐语音(需 silk 和 ffmpeg)',
133
+ fields: [
134
+ { key: 'showMusicVoice', type: 'boolean', default: false, description: '音乐链接以语音形式发送' },
135
+ { key: 'showMusicVoiceFile', type: 'boolean', default: true, description: '音乐是否以语音形式发送(关闭则只发送链接)' },
136
+ ],
137
+ }),
138
+ (0, config_1.defineConfig)({
139
+ group: 'GIF 转换',
140
+ fields: [
141
+ { key: 'gifConvertEnabled', type: 'boolean', default: true, description: '推文动图转 GIF 发送(ffmpeg 随插件自动安装,失败回退原视频)' },
142
+ { key: 'gifMaxWidth', type: 'number', min: 120, max: 1080, step: 1, default: 480, description: 'GIF 宽度 (px)' },
143
+ { key: 'gifFps', type: 'number', min: 5, max: 30, step: 1, default: 15, description: 'GIF 帧率' },
144
+ { key: 'gifMaxDurationSec', type: 'number', min: 1, max: 60, step: 1, default: 15, description: 'GIF 时长上限 (s)' },
145
+ ],
146
+ }),
147
+ (0, config_1.defineConfig)({
148
+ group: '性能与限制',
149
+ fields: [
150
+ { key: 'maxDescLength', type: 'number', min: 0, step: 1, default: 200, description: '简介长度上限' },
151
+ { key: 'maxConcurrent', type: 'number', min: 1, step: 1, default: 3, description: '解析最大并发数' },
152
+ ],
153
+ }),
154
+ (0, config_1.defineConfig)({
155
+ group: '发送策略',
156
+ fields: [
157
+ {
158
+ key: 'sendStrategy', type: 'union', default: 'single', description: '发送策略',
159
+ values: [
160
+ { value: 'single', description: '单条整合(默认;图片过多/含视频自动回退合并转发)' },
161
+ { value: 'split', description: '逐条分开发送(旧版行为)' },
162
+ ],
163
+ },
164
+ { key: 'singleSendMaxImages', type: 'number', min: 1, default: 10, description: '单条消息最大图片数(超出回退合并转发)' },
165
+ ],
166
+ }),
167
+ (0, config_1.defineConfig)({
168
+ group: '网络与请求',
169
+ fields: [
170
+ { key: 'timeout', type: 'number', min: 0, step: 1, default: 180000, description: 'API 请求超时 (ms)' },
171
+ { key: 'videoSendTimeout', type: 'number', min: 0, step: 1, default: 180000, description: '消息发送超时 (ms)' },
172
+ { key: 'userAgent', type: 'string', default: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', description: 'User-Agent' },
173
+ { key: 'proxy', type: 'object', description: 'HTTP/HTTPS 代理', fields: PROXY_FIELDS },
174
+ {
175
+ key: 'customHeaders', type: 'array', default: [], description: '自定义请求头',
176
+ itemFields: [
177
+ { key: 'name', type: 'string', required: true, description: '头名称' },
178
+ { key: 'value', type: 'string', required: true, description: '头值' },
179
+ ],
180
+ },
181
+ ],
182
+ }),
183
+ (0, config_1.defineConfig)({
184
+ group: '发送与重试',
185
+ fields: [
186
+ { key: 'ignoreSendError', type: 'boolean', default: true, description: '忽略发送失败' },
187
+ { key: 'retryTimes', type: 'number', min: 0, default: 3, description: '重试次数' },
188
+ { key: 'retryInterval', type: 'number', min: 0, default: 1000, description: '重试间隔 (ms)' },
189
+ { key: 'enableForward', type: 'boolean', default: false, description: '合并转发(OneBot/Satori)' },
190
+ ],
191
+ }),
192
+ (0, config_1.defineConfig)({
193
+ group: '缓存与临时文件',
194
+ fields: [
195
+ { key: 'deduplicationInterval', type: 'number', min: 0, default: 180, description: '去重间隔 (s)' },
196
+ { key: 'enableDeduplication', type: 'boolean', default: true, description: '启用重复解析检测与提示' },
197
+ { key: 'cacheTTL', type: 'number', min: 0, default: 600, description: '缓存时间 (s)' },
198
+ ],
199
+ }),
200
+ (0, config_1.defineConfig)({
201
+ group: 'API 与平台',
202
+ fields: [
203
+ { key: 'primaryApiUrl', type: 'string', default: 'https://api.bugpk.com/api/short_videos', hidden: true },
204
+ { key: 'backupApiUrl', type: 'string', default: 'https://api.bugpk.com/api/svparse', hidden: true },
205
+ {
206
+ key: 'customApis', type: 'array', default: [], description: '覆盖内置平台 API',
207
+ itemFields: [
208
+ { key: 'platform', type: 'union', required: true, description: '平台', values: API_PLATFORM_VALUES },
209
+ { key: 'apiUrl', type: 'string', description: 'API 地址' },
210
+ { key: 'apiKey', type: 'string', default: '', description: 'API Key' },
211
+ ...AUTH_HEADER_FIELDS.map(f => ({ ...f })),
212
+ FIELD_MAPPING_FIELD,
213
+ ],
214
+ },
215
+ {
216
+ key: 'customPlatforms', type: 'array', default: [], description: '自定义新平台',
217
+ itemFields: [
218
+ { key: 'name', type: 'string', required: true, description: '平台名称' },
219
+ { key: 'exampleUrl', type: 'string', description: '示例链接' },
220
+ { key: 'keywords', type: 'string', required: true, description: '关键词(逗号分隔)' },
221
+ { key: 'apiUrl', type: 'string', required: true, description: '解析 API' },
222
+ { key: 'apiKey', type: 'string', default: '', description: 'API Key' },
223
+ ...AUTH_HEADER_FIELDS.map(f => ({ ...f })),
224
+ FIELD_MAPPING_FIELD,
225
+ { key: 'proxy', type: 'object', description: '独立代理(覆盖全局代理)', fields: PROXY_FIELDS },
226
+ ],
227
+ },
228
+ {
229
+ key: 'globalFieldMapping', type: 'string', role: 'textarea',
230
+ default: GLOBAL_FIELD_MAPPING_DEFAULT, description: '全局字段映射 JSON',
231
+ },
232
+ { key: 'twitterAuthToken', type: 'string', default: '', role: 'secret', description: 'X 登录态 auth_token(解析需登录推文用,受 Cloudflare 指纹限制可能 403)' },
233
+ { key: 'twitterCt0', type: 'string', default: '', role: 'secret', description: 'X 登录态 ct0(与 auth_token 配对)' },
234
+ { key: 'tweetTranslateEnabled', type: 'boolean', default: true, description: '外语推文自动翻译(配置 X 登录态时用网页同源 Grok 翻译,否则通用翻译;附译文行)' },
235
+ {
236
+ key: 'tweetTranslateLang', type: 'union', default: 'zh', description: '翻译目标语言',
237
+ values: [
238
+ { value: 'zh', description: '简体中文' },
239
+ { value: 'zh-TW', description: '繁體中文' },
240
+ { value: 'en', description: 'English' },
241
+ { value: 'ja', description: '日本語' },
242
+ { value: 'ko', description: '한국어' },
243
+ { value: 'fr', description: 'Français' },
244
+ { value: 'de', description: 'Deutsch' },
245
+ { value: 'ru', description: 'Русский' },
246
+ { value: 'es', description: 'Español' },
247
+ { value: 'pt', description: 'Português' },
248
+ ],
249
+ },
250
+ { key: 'apiKey', type: 'string', default: '', role: 'secret', description: '新网关 API Key(https://api-new.ifphp.com/auth/login 注册获取;留空用旧网关)' },
251
+ ],
252
+ }),
253
+ (0, config_1.defineConfig)({
254
+ group: '界面文本',
255
+ fields: [
256
+ { key: 'waitingTipText', type: 'string', default: '正在解析视频,请稍候...', description: '等待提示' },
257
+ { key: 'unsupportedPlatformText', type: 'string', default: '不支持该平台链接', description: '不支持提示' },
258
+ { key: 'invalidLinkText', type: 'string', default: '无效的视频链接', description: '无效链接提示' },
259
+ { key: 'parseErrorPrefix', type: 'string', default: '❌ 解析失败:', description: '错误前缀' },
260
+ { key: 'parseErrorItemFormat', type: 'string', default: '【${url}】: ${msg}', description: '错误格式' },
261
+ { key: 'deduplicationTipText', type: 'string', default: '链接 ${url} 在最近 ${interval} 秒内已解析过,已跳过。', description: '重复解析提示,支持变量 ${url} ${interval}' },
262
+ ],
263
+ }),
264
+ ];
265
+ }
@@ -274,7 +274,8 @@ function mapGraphql(rawResult) {
274
274
  const legacy = result.legacy || {};
275
275
  const user = result.core?.user_results?.result;
276
276
  const ulegacy = user?.legacy || {};
277
- const text = String(pick(legacy.full_text, result.note_tweet?.note_results?.note?.text, ''));
277
+ // 长推(note tweet):legacy.full_text 是截断版,全文在 note_tweet.note_results.note.text
278
+ const text = String(pick(result.note_tweet?.note_results?.note?.text, legacy.full_text, ''));
278
279
  const p = baseParsed();
279
280
  const media = Array.isArray(legacy.entities?.media) ? legacy.entities.media : [];
280
281
  const photos = [];
@@ -387,7 +388,19 @@ async function parseTwitter(url, http, creds, getGraphql) {
387
388
  });
388
389
  const tw = res.data;
389
390
  if (tw && tw.__typename === 'Tweet' && tw.user) {
390
- return mapSyndication(tw);
391
+ const p = mapSyndication(tw);
392
+ // 长推:syndication 现仅返回 note_tweet 的 id 引用(无 text),tw.text 被
393
+ // display_text_range 截断。有登录态时改走 GraphQL 取全文;失败回退截断结果。
394
+ const noteTruncated = tw.note_tweet && !tw.note_tweet.text;
395
+ if (noteTruncated && creds && creds.authToken && creds.ct0) {
396
+ try {
397
+ return await fetchGraphqlTweet(id, creds, getGraphql || tls_client_1.tlsGet);
398
+ }
399
+ catch {
400
+ return p;
401
+ }
402
+ }
403
+ return p;
391
404
  }
392
405
  // 2) tombstone(需登录):回退到鉴权 GraphQL(TLS 指纹模拟)
393
406
  if (creds && creds.authToken && creds.ct0) {
package/lib/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export * from './utils/tls-client';
19
19
  export * from './utils/gif';
20
20
  export * from './utils/merge';
21
21
  export * from './engine/gateway';
22
+ export * from './engine/config';
22
23
  export * from './engine/platform-config';
23
24
  export * from './engine/translate';
24
25
  export * from './engine/twitter';
package/lib/index.js CHANGED
@@ -35,6 +35,7 @@ __exportStar(require("./utils/tls-client"), exports);
35
35
  __exportStar(require("./utils/gif"), exports);
36
36
  __exportStar(require("./utils/merge"), exports);
37
37
  __exportStar(require("./engine/gateway"), exports);
38
+ __exportStar(require("./engine/config"), exports);
38
39
  __exportStar(require("./engine/platform-config"), exports);
39
40
  __exportStar(require("./engine/translate"), exports);
40
41
  __exportStar(require("./engine/twitter"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sns-parse/core",
3
- "version": "0.4.0-alpha.2+upstream.1.6.7",
3
+ "version": "0.4.0-alpha.4+upstream.1.6.7",
4
4
  "description": "sns-parse 核心:契约、宿主抽象、配置 DSL 与解析引擎(fetcher/parser/翻译/X 原生解析/合并/GIF/TLS 指纹客户端)",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",