koishi-plugin-xlon 1.0.1 → 1.0.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.
Files changed (2) hide show
  1. package/lib/index.js +61 -14
  2. package/package.json +37 -37
package/lib/index.js CHANGED
@@ -114,12 +114,12 @@ async function expandShortLink(ctx, url) {
114
114
  maxRedirects: 0,
115
115
  validateStatus: /* @__PURE__ */ __name(() => true, "validateStatus")
116
116
  });
117
- const location = res?.headers?.location;
118
- return location || url;
117
+ const location2 = res?.headers?.location;
118
+ return location2 || url;
119
119
  } catch (err) {
120
120
  try {
121
- const location = err?.response?.headers?.location;
122
- return location || url;
121
+ const location2 = err?.response?.headers?.location;
122
+ return location2 || url;
123
123
  } catch (_) {
124
124
  return url;
125
125
  }
@@ -439,20 +439,67 @@ function buildVxApiUrl(tweetUrl) {
439
439
  return tweetUrl.replace(/(twitter\.com|x\.com)/i, "api.vxtwitter.com");
440
440
  }
441
441
  __name(buildVxApiUrl, "buildVxApiUrl");
442
+ async function diagnosePage(page) {
443
+ return await page.evaluate(() => {
444
+ const url = location.href;
445
+ const title = document.title || "";
446
+ const bodyText = (document.body && document.body.innerText || "").slice(0, 600);
447
+ let reason = "页面未渲染出推文(可能网络缓慢或 X 页面结构变化)";
448
+ const isLoginWall = /\/i\/flow\/login|\/login|\/i\/flow\/single-sign-on/i.test(url) || !!document.querySelector('input[autocomplete="username"]') || /^(log in|登录|登入|sign in)/i.test(title.trim());
449
+ if (isLoginWall) {
450
+ reason = "Cookie(auth_token) 可能已失效,被重定向到登录页,请更新 cookies 配置";
451
+ } else if (/rate limit|too many requests|请求过于频繁|尝试次数过多/i.test(bodyText)) {
452
+ reason = "触发限流(Rate limit),请调大 updateInterval 或更换账号";
453
+ } else if (/account doesn.?t exist|账号不存在|這個帳號不存在|user not found/i.test(bodyText)) {
454
+ reason = "账号不存在或用户名拼写有误";
455
+ } else if (/suspended|账号已被冻结|帳號已遭凍結/i.test(bodyText)) {
456
+ reason = "账号已被冻结(suspended)";
457
+ } else if (/something went wrong|出错了|发生了一些错误|重试/i.test(bodyText)) {
458
+ reason = "X 返回错误页(Something went wrong),通常为临时故障或限流";
459
+ }
460
+ return { url, reason };
461
+ });
462
+ }
463
+ __name(diagnosePage, "diagnosePage");
464
+ async function waitForArticle(page, timeout = 3e4) {
465
+ const outcome = await Promise.race([
466
+ page.waitForSelector("article", { timeout }).then(() => "article").catch(() => null),
467
+ page.waitForFunction(
468
+ () => /\/i\/flow\/login|\/login|\/i\/flow\/single-sign-on/i.test(location.href) || !!document.querySelector('input[autocomplete="username"]'),
469
+ { timeout }
470
+ ).then(() => "login").catch(() => null)
471
+ ]);
472
+ if (outcome === "article") return;
473
+ const diag = await diagnosePage(page).catch(() => null);
474
+ throw new Error(
475
+ diag ? `未加载到推文(${diag.reason})| URL: ${diag.url}` : "未加载到推文(页面状态未知)"
476
+ );
477
+ }
478
+ __name(waitForArticle, "waitForArticle");
442
479
  async function screenshotTweetArticle(page, selector = 'article[data-testid="tweet"]') {
443
480
  const element = await page.waitForSelector(selector, { timeout: 15e3 });
444
481
  if (!element) throw new Error("未能找到推文容器");
445
- await page.waitForFunction(
482
+ const mediaLoadResult = await page.waitForFunction(
446
483
  (sel) => {
447
- const a = document.querySelector(sel);
448
- if (!a) return false;
449
- const imgs = Array.from(a.querySelectorAll("img"));
450
- return imgs.every((img) => img.complete && img.naturalWidth > 0);
484
+ const article = document.querySelector(sel);
485
+ if (!article) return false;
486
+ const imgs = Array.from(article.querySelectorAll("img"));
487
+ const imgsLoaded = imgs.every((img) => img.complete && img.naturalWidth > 0);
488
+ const videos = Array.from(article.querySelectorAll("video"));
489
+ const videosLoaded = videos.every((video) => video.readyState >= 2);
490
+ return imgsLoaded && videosLoaded;
451
491
  },
452
- { timeout: 8e3 },
492
+ { timeout: 2e4 },
493
+ // 增加到 20 秒
453
494
  selector
454
- ).catch(() => {
495
+ ).catch((err) => {
496
+ logger6.warn(`媒体加载超时: ${err.message}`);
497
+ return null;
455
498
  });
499
+ if (!mediaLoadResult) {
500
+ logger6.warn("部分媒体可能未完全加载,额外等待 2 秒");
501
+ await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 2e3)));
502
+ }
456
503
  await page.evaluate(() => {
457
504
  const column = document.querySelector('div[data-testid="primaryColumn"]');
458
505
  if (!column) return;
@@ -547,7 +594,7 @@ var XFetcher = class {
547
594
  });
548
595
  await preparePage(page, this.config.cookies);
549
596
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
550
- await page.waitForSelector("article", { timeout: 3e4 });
597
+ await waitForArticle(page, 3e4);
551
598
  const result = await page.evaluate(() => {
552
599
  const articles = Array.from(document.querySelectorAll("article"));
553
600
  const collected = [];
@@ -630,8 +677,8 @@ var XFetcher = class {
630
677
  else req.continue();
631
678
  });
632
679
  await preparePage(page, this.config.cookies);
633
- await page.goto(tweetUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
634
- await page.waitForSelector("article", { timeout: 3e4 });
680
+ await page.goto(tweetUrl, { waitUntil: "networkidle2", timeout: 6e4 });
681
+ await waitForArticle(page, 3e4);
635
682
  protectedAccount = await page.evaluate(() => {
636
683
  return !!(document.querySelector('[data-testid="icon-lock"]') || document.querySelector('[aria-label*="rotect"]') || document.querySelector('[aria-label*="保护"]') || document.querySelector('[aria-label*="保護"]'));
637
684
  });
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
1
  {
2
- "name": "koishi-plugin-xlon",
3
- "description": "x subscriber",
4
- "version": "1.0.1",
5
- "contributors": [
6
- "Logthm <logthm@outlook.com>"
7
- ],
8
- "main": "lib/index.js",
9
- "typings": "lib/index.d.ts",
10
- "files": [
11
- "lib",
12
- "dist"
13
- ],
14
- "license": "MIT",
15
- "keywords": [
16
- "chatbot",
17
- "koishi",
18
- "plugin"
19
- ],
20
- "koishi": {
21
- "service": {
22
- "required": [
23
- "database"
24
- ],
25
- "optional": [
26
- "puppeteer"
27
- ]
28
- }
29
- },
30
- "peerDependencies": {
31
- "koishi": "^4.18.10"
32
- },
33
- "devDependencies": {
34
- "koishi-plugin-puppeteer": "^3.9.0",
35
- "typescript": "^5.9.3"
36
- },
37
- "dependencies": {
38
- "yml-register": "^1.2.5"
2
+ "name": "koishi-plugin-xlon",
3
+ "description": "x subscriber",
4
+ "version": "1.0.3",
5
+ "contributors": [
6
+ "Logthm <logthm@outlook.com>"
7
+ ],
8
+ "main": "lib/index.js",
9
+ "typings": "lib/index.d.ts",
10
+ "files": [
11
+ "lib",
12
+ "dist"
13
+ ],
14
+ "license": "MIT",
15
+ "keywords": [
16
+ "chatbot",
17
+ "koishi",
18
+ "plugin"
19
+ ],
20
+ "koishi": {
21
+ "service": {
22
+ "required": [
23
+ "database"
24
+ ],
25
+ "optional": [
26
+ "puppeteer"
27
+ ]
39
28
  }
29
+ },
30
+ "peerDependencies": {
31
+ "koishi": "^4.18.11"
32
+ },
33
+ "devDependencies": {
34
+ "koishi-plugin-puppeteer": "^3.9.0",
35
+ "typescript": "^5.9.3"
36
+ },
37
+ "dependencies": {
38
+ "yml-register": "^1.2.5"
39
+ }
40
40
  }