@sns-parse/core 0.4.0-alpha.3 → 0.4.0-alpha.5
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/twitter.js +52 -8
- package/package.json +1 -1
package/lib/engine/twitter.js
CHANGED
|
@@ -138,6 +138,36 @@ function pick(...vals) {
|
|
|
138
138
|
return v;
|
|
139
139
|
return '';
|
|
140
140
|
}
|
|
141
|
+
/** BFS 深取子树中第一个非空的指定键值(X GraphQL 用户结构随版本漂移:legacy ↔ core/avatar) */
|
|
142
|
+
function deepPick(node, key) {
|
|
143
|
+
const queue = [node];
|
|
144
|
+
while (queue.length) {
|
|
145
|
+
const cur = queue.shift();
|
|
146
|
+
if (!cur || typeof cur !== 'object')
|
|
147
|
+
continue;
|
|
148
|
+
const v = cur[key];
|
|
149
|
+
if (v !== undefined && v !== null && v !== '')
|
|
150
|
+
return v;
|
|
151
|
+
for (const k of Object.keys(cur))
|
|
152
|
+
queue.push(cur[k]);
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
/** BFS 找子树中最长的 text 字符串(笔记正文位置随版本漂移,取最长以避开截断副本) */
|
|
157
|
+
function deepFindLongestText(node) {
|
|
158
|
+
let best;
|
|
159
|
+
const queue = [node];
|
|
160
|
+
while (queue.length) {
|
|
161
|
+
const cur = queue.shift();
|
|
162
|
+
if (!cur || typeof cur !== 'object')
|
|
163
|
+
continue;
|
|
164
|
+
if (typeof cur.text === 'string' && cur.text && (!best || cur.text.length > best.length))
|
|
165
|
+
best = cur.text;
|
|
166
|
+
for (const k of Object.keys(cur))
|
|
167
|
+
queue.push(cur[k]);
|
|
168
|
+
}
|
|
169
|
+
return best;
|
|
170
|
+
}
|
|
141
171
|
/** 清理描述中的 t.co 短链(Twitter 自动附加的截断 URL,无实际内容价值) */
|
|
142
172
|
function cleanDesc(text) {
|
|
143
173
|
if (!text)
|
|
@@ -273,8 +303,10 @@ function mapGraphql(rawResult) {
|
|
|
273
303
|
}
|
|
274
304
|
const legacy = result.legacy || {};
|
|
275
305
|
const user = result.core?.user_results?.result;
|
|
276
|
-
const
|
|
277
|
-
|
|
306
|
+
const ures = user || {};
|
|
307
|
+
// 长推(note tweet):legacy.full_text 是截断版,全文在 note_tweet 子树(位置随版本漂移,深取最长 text)
|
|
308
|
+
// 用户字段同理:legacy ↔ core/avatar/profile_bio 漂移,深取键值
|
|
309
|
+
const text = String(pick(deepFindLongestText(result.note_tweet), legacy.full_text, ''));
|
|
278
310
|
const p = baseParsed();
|
|
279
311
|
const media = Array.isArray(legacy.entities?.media) ? legacy.entities.media : [];
|
|
280
312
|
const photos = [];
|
|
@@ -318,9 +350,9 @@ function mapGraphql(rawResult) {
|
|
|
318
350
|
p.title = cleaned.slice(0, 100);
|
|
319
351
|
p.desc = cleaned;
|
|
320
352
|
p.lang = legacy.lang ? String(legacy.lang) : undefined;
|
|
321
|
-
p.author = String(pick(
|
|
322
|
-
p.uid = String(pick(
|
|
323
|
-
p.avatar = String(pick(
|
|
353
|
+
p.author = String(pick(deepPick(ures, 'name'), ''));
|
|
354
|
+
p.uid = String(pick(deepPick(ures, 'screen_name'), user?.rest_id, ''));
|
|
355
|
+
p.avatar = String(pick(deepPick(ures, 'profile_image_url_https'), deepPick(ures, 'image_url'), ''));
|
|
324
356
|
p.like = Number(pick(legacy.favorite_count, 0)) || 0;
|
|
325
357
|
p.comment = Number(pick(legacy.reply_count, 0)) || 0;
|
|
326
358
|
p.share = Number(pick(legacy.retweet_count, 0)) || 0;
|
|
@@ -331,8 +363,8 @@ function mapGraphql(rawResult) {
|
|
|
331
363
|
if (!isNaN(t))
|
|
332
364
|
p.publishTime = t;
|
|
333
365
|
}
|
|
334
|
-
p.author_followers = Number(pick(
|
|
335
|
-
p.author_signature = String(pick(
|
|
366
|
+
p.author_followers = Number(pick(deepPick(ures, 'followers_count'), 0)) || 0;
|
|
367
|
+
p.author_signature = String(pick(deepPick(ures, 'description'), ''));
|
|
336
368
|
if (p.title && p.desc && p.desc.startsWith(p.title))
|
|
337
369
|
p.title = '';
|
|
338
370
|
return p;
|
|
@@ -387,7 +419,19 @@ async function parseTwitter(url, http, creds, getGraphql) {
|
|
|
387
419
|
});
|
|
388
420
|
const tw = res.data;
|
|
389
421
|
if (tw && tw.__typename === 'Tweet' && tw.user) {
|
|
390
|
-
|
|
422
|
+
const p = mapSyndication(tw);
|
|
423
|
+
// 长推:syndication 现仅返回 note_tweet 的 id 引用(无 text),tw.text 被
|
|
424
|
+
// display_text_range 截断。有登录态时改走 GraphQL 取全文;失败回退截断结果。
|
|
425
|
+
const noteTruncated = tw.note_tweet && !tw.note_tweet.text;
|
|
426
|
+
if (noteTruncated && creds && creds.authToken && creds.ct0) {
|
|
427
|
+
try {
|
|
428
|
+
return await fetchGraphqlTweet(id, creds, getGraphql || tls_client_1.tlsGet);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return p;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return p;
|
|
391
435
|
}
|
|
392
436
|
// 2) tombstone(需登录):回退到鉴权 GraphQL(TLS 指纹模拟)
|
|
393
437
|
if (creds && creds.authToken && creds.ct0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sns-parse/core",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.5+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",
|