@web-auto/webauto 0.1.17 → 0.1.19

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 (65) hide show
  1. package/README.md +122 -53
  2. package/apps/desktop-console/dist/main/index.mjs +229 -14
  3. package/apps/desktop-console/dist/renderer/index.js +237 -8
  4. package/apps/desktop-console/entry/ui-cli.mjs +290 -21
  5. package/apps/desktop-console/entry/ui-console.mjs +46 -15
  6. package/apps/webauto/entry/account.mjs +126 -27
  7. package/apps/webauto/entry/lib/account-detect.mjs +399 -9
  8. package/apps/webauto/entry/lib/account-store.mjs +201 -109
  9. package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
  10. package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
  11. package/apps/webauto/entry/lib/profilepool.mjs +12 -0
  12. package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
  13. package/apps/webauto/entry/lib/session-init.mjs +227 -0
  14. package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
  15. package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
  16. package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
  17. package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
  18. package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
  19. package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
  20. package/apps/webauto/entry/profilepool.mjs +56 -9
  21. package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
  22. package/apps/webauto/entry/weibo-unified.mjs +84 -11
  23. package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
  24. package/apps/webauto/entry/xhs-unified.mjs +92 -997
  25. package/bin/webauto.mjs +22 -4
  26. package/dist/modules/camo-backend/src/index.js +33 -0
  27. package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
  28. package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
  29. package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
  30. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
  31. package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
  32. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
  33. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
  34. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
  35. package/dist/modules/workflow/src/runner.js +2 -0
  36. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
  37. package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
  38. package/modules/camo-backend/src/index.ts +31 -0
  39. package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
  40. package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
  41. package/modules/camo-backend/src/internal/ws-server.ts +17 -17
  42. package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
  43. package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
  44. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
  45. package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
  46. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
  47. package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
  48. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
  49. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
  50. package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
  51. package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
  52. package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
  53. package/modules/workflow/blocks/EnsureSession.ts +0 -4
  54. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
  55. package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
  56. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
  57. package/modules/workflow/src/runner.ts +2 -0
  58. package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
  59. package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
  60. package/package.json +2 -2
  61. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
  62. package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
  63. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
  64. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
  65. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +0 -135
@@ -2,6 +2,8 @@ import { callAPI } from '../../../../modules/camo-runtime/src/utils/browser-serv
2
2
  import { listAccountProfiles, markProfileInvalid, markProfilePending, upsertProfileAccountState } from './account-store.mjs';
3
3
 
4
4
  const XHS_PROFILE_URL = 'https://www.xiaohongshu.com/user/profile';
5
+ const WEIBO_HOME_URL = 'https://weibo.com';
6
+ const WEIBO_PROFILE_INFO_ENDPOINT = '/ajax/profile/info';
5
7
 
6
8
  function normalizeText(value) {
7
9
  const text = String(value ?? '').trim();
@@ -12,6 +14,78 @@ function sleep(ms) {
12
14
  return new Promise((resolve) => setTimeout(resolve, ms));
13
15
  }
14
16
 
17
+ function extractResult(payload) {
18
+ if (payload && typeof payload === 'object') {
19
+ if (Object.prototype.hasOwnProperty.call(payload, 'result')) return payload.result;
20
+ if (Object.prototype.hasOwnProperty.call(payload, 'data')) return payload.data;
21
+ }
22
+ return payload || {};
23
+ }
24
+
25
+ function normalizeWeiboUid(value) {
26
+ const text = String(value ?? '').trim();
27
+ if (!text) return null;
28
+ const matched = text.match(/\d{4,}/);
29
+ if (!matched || !matched[0]) return null;
30
+ return matched[0];
31
+ }
32
+
33
+ function isDomainCookie(cookie, domain) {
34
+ const cookieDomain = String(cookie?.domain || '').trim().toLowerCase();
35
+ if (!cookieDomain) return false;
36
+ const wanted = String(domain || '').trim().toLowerCase();
37
+ if (!wanted) return false;
38
+ return cookieDomain === wanted || cookieDomain === `.${wanted}` || cookieDomain.endsWith(`.${wanted}`);
39
+ }
40
+
41
+ function isCookieExpired(cookie) {
42
+ const expires = Number(cookie?.expires);
43
+ if (!Number.isFinite(expires)) return false;
44
+ if (expires <= 0) return false;
45
+ return expires * 1000 <= Date.now();
46
+ }
47
+
48
+ function hasCookie(cookies, name, domain) {
49
+ const wantedName = String(name || '').trim().toUpperCase();
50
+ if (!wantedName) return false;
51
+ return (Array.isArray(cookies) ? cookies : []).some((cookie) => (
52
+ String(cookie?.name || '').trim().toUpperCase() === wantedName
53
+ && isDomainCookie(cookie, domain)
54
+ && !isCookieExpired(cookie)
55
+ ));
56
+ }
57
+
58
+ function isTransientSyncError(error) {
59
+ const message = String(error?.message || error || '').toLowerCase();
60
+ if (!message) return false;
61
+ const transientMarkers = [
62
+ 'fetch failed',
63
+ 'failed to fetch',
64
+ 'network error',
65
+ 'networkerror',
66
+ 'socket hang up',
67
+ 'econnrefused',
68
+ 'etimedout',
69
+ 'timed out',
70
+ 'service unavailable',
71
+ 'http 502',
72
+ 'http 503',
73
+ 'http 504',
74
+ 'operation is insecure',
75
+ 'browser service',
76
+ 'connection refused',
77
+ ];
78
+ return transientMarkers.some((marker) => message.includes(marker));
79
+ }
80
+
81
+ async function getProfileCookies(profileId) {
82
+ const payload = await callAPI('getCookies', { profileId });
83
+ const body = extractResult(payload);
84
+ if (Array.isArray(body?.cookies)) return body.cookies;
85
+ if (Array.isArray(body)) return body;
86
+ return [];
87
+ }
88
+
15
89
  function buildDetectScript() {
16
90
  return `(() => {
17
91
  const isVisible = (node) => {
@@ -36,7 +110,7 @@ function buildDetectScript() {
36
110
  .join(' ');
37
111
  const hasLoginText = /登录|扫码|验证码|手机号|请先登录|注册|sign\\s*in/i.test(loginGuardText);
38
112
  const loginUrl = /\\/login|signin|passport|account\\/login/i.test(String(location.href || ''));
39
- const hasGuardSignal = (visibleLoginGuardNodes.length > 0 && hasLoginText) || loginUrl;
113
+ const hasGuardSignalRaw = (visibleLoginGuardNodes.length > 0 && hasLoginText) || loginUrl;
40
114
  const candidates = [];
41
115
  const normalizeAlias = (value) => {
42
116
  const text = String(value || '').replace(/\\s+/g, ' ').trim();
@@ -178,6 +252,8 @@ function buildDetectScript() {
178
252
  if (picked) alias = picked;
179
253
  }
180
254
  const hasAccountSignal = Boolean(best && best.id);
255
+ // If we can reliably resolve the self account id, treat login guard UI as non-blocking.
256
+ const hasGuardSignal = hasAccountSignal ? false : hasGuardSignalRaw;
181
257
  return {
182
258
  url: location.href,
183
259
  hasLoginGuard: hasGuardSignal,
@@ -419,7 +495,7 @@ export async function syncXhsAccountByProfile(profileId, options = {}) {
419
495
  const detected = await detectXhsAccountIdentity(normalizedProfileId, {
420
496
  resolveAlias: shouldResolveAlias,
421
497
  });
422
- if (detected.hasLoginGuard) {
498
+ if (detected.hasLoginGuard && !detected.accountId) {
423
499
  if (pendingWhileLogin) {
424
500
  return markProfilePending(normalizedProfileId, 'waiting_login_guard');
425
501
  }
@@ -440,14 +516,11 @@ export async function syncXhsAccountByProfile(profileId, options = {}) {
440
516
  detectedAt: new Date().toISOString(),
441
517
  });
442
518
  } catch (error) {
443
- const msg = String(error?.message || error || '');
444
- if (msg.toLowerCase().includes('operation is insecure')) {
445
- try {
446
- const existing = listAccountProfiles({ platform: 'xiaohongshu' }).profiles.find((item) => String(item?.profileId || '').trim() === normalizedProfileId);
447
- if (existing && existing.valid) return existing;
448
- } catch {
449
- // ignore fallback lookup
519
+ if (isTransientSyncError(error)) {
520
+ if (pendingWhileLogin) {
521
+ return markProfilePending(normalizedProfileId, `waiting_login_sync:${error?.message || String(error)}`);
450
522
  }
523
+ return markProfileInvalid(normalizedProfileId, `sync_unreachable:${error?.message || String(error)}`);
451
524
  }
452
525
  if (pendingWhileLogin) {
453
526
  return markProfilePending(normalizedProfileId, `waiting_login_sync:${error?.message || String(error)}`);
@@ -465,3 +538,320 @@ export async function syncXhsAccountsByProfiles(profileIds = [], options = {}) {
465
538
  }
466
539
  return out;
467
540
  }
541
+
542
+ function buildWeiboDetectScript() {
543
+ return `(() => {
544
+ const normalize = (value) => String(value || '').replace(/\\s+/g, ' ').trim();
545
+ const toAlias = (value) => {
546
+ const text = normalize(value);
547
+ if (!text) return null;
548
+ if (text === '微博' || text === '首页' || text === '登录') return null;
549
+ return text;
550
+ };
551
+ const isVisible = (node) => {
552
+ if (!(node instanceof HTMLElement)) return false;
553
+ const style = window.getComputedStyle(node);
554
+ if (!style) return false;
555
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
556
+ if (Number(style.opacity || '1') === 0) return false;
557
+ const rect = node.getBoundingClientRect();
558
+ return rect.width > 0 && rect.height > 0;
559
+ };
560
+ const href = String(location.href || '');
561
+ const host = String(location.host || '');
562
+ const loginUrl = (
563
+ host.includes('passport.weibo.com')
564
+ || /\\/signin|\\/login|passport/i.test(href)
565
+ );
566
+ const guardSelectors = [
567
+ '.LoginCard',
568
+ 'div[class*="LoginCard"]',
569
+ 'div[class*="login"]',
570
+ 'form[action*="passport"]',
571
+ 'input[name="username"]',
572
+ 'input[type="password"]'
573
+ ];
574
+ const guardNodes = guardSelectors
575
+ .flatMap((sel) => Array.from(document.querySelectorAll(sel)))
576
+ .filter((node) => isVisible(node));
577
+ const guardText = guardNodes
578
+ .slice(0, 8)
579
+ .map((node) => normalize(node.textContent || ''))
580
+ .join(' ');
581
+ const hasLoginText = /登录|注册|验证码|手机号|扫码|sign\\s*in|password/i.test(guardText);
582
+ const hasLoginGuard = loginUrl || (guardNodes.length > 0 && hasLoginText);
583
+
584
+ const uidCandidates = [];
585
+ const aliasCandidates = [];
586
+ const pushUid = (value, source) => {
587
+ const text = normalize(value);
588
+ if (!text) return;
589
+ uidCandidates.push({ value: text, source: source || null });
590
+ };
591
+ const pushAlias = (value, source) => {
592
+ const text = toAlias(value);
593
+ if (!text) return;
594
+ aliasCandidates.push({ value: text, source: source || null });
595
+ };
596
+
597
+ const config = (typeof window !== 'undefined' && window.$CONFIG) || null;
598
+ if (config && typeof config === 'object') {
599
+ pushUid(config.uid, 'config.uid');
600
+ pushUid(config.oid, 'config.oid');
601
+ pushAlias(config.nick || config.nickName || config.name, 'config.nick');
602
+ }
603
+
604
+ const initialState = (typeof window !== 'undefined' && window.__INITIAL_STATE__) || null;
605
+ if (initialState && typeof initialState === 'object') {
606
+ const me = initialState.user || initialState.login || initialState.loginInfo || initialState.userInfo || null;
607
+ if (me && typeof me === 'object') {
608
+ pushUid(me.uid || me.idstr || me.id || me.userId || null, 'state.user');
609
+ pushAlias(me.screen_name || me.nick || me.nickname || me.name || null, 'state.user');
610
+ }
611
+ }
612
+
613
+ const meLink = Array.from(document.querySelectorAll('a[href*="/u/"], a[href*="/n/"]'))
614
+ .find((node) => {
615
+ const text = normalize(node.textContent || '');
616
+ const title = normalize(node.getAttribute?.('title') || '');
617
+ return text === '我' || text === '我的' || title === '我' || title === '我的';
618
+ });
619
+ if (meLink) {
620
+ const meHref = String(meLink.getAttribute('href') || '').trim();
621
+ const uidMatch = meHref.match(/\\/u\\/(\\d{4,})/);
622
+ if (uidMatch && uidMatch[1]) pushUid(uidMatch[1], 'anchor.self');
623
+ pushAlias(meLink.textContent || '', 'anchor.self');
624
+ }
625
+
626
+ const anchors = Array.from(document.querySelectorAll('a[href*="/u/"], a[href*="/n/"]')).slice(0, 120);
627
+ for (const anchor of anchors) {
628
+ const ahref = String(anchor.getAttribute('href') || '').trim();
629
+ if (!ahref) continue;
630
+ const uidMatch = ahref.match(/\\/u\\/(\\d{4,})/);
631
+ if (uidMatch && uidMatch[1]) pushUid(uidMatch[1], 'anchor.any');
632
+ if (uidMatch && uidMatch[1]) pushAlias(anchor.textContent || '', 'anchor.any');
633
+ }
634
+
635
+ const aliasSelectors = [
636
+ 'h1',
637
+ 'header h1',
638
+ '[class*="ProfileHeader_name"]',
639
+ '[class*="profile"] [class*="name"]',
640
+ '[class*="userinfo"] [class*="name"]',
641
+ 'a[href*="/u/"] span'
642
+ ];
643
+ for (const sel of aliasSelectors) {
644
+ const nodes = Array.from(document.querySelectorAll(sel)).slice(0, 8);
645
+ for (const node of nodes) {
646
+ pushAlias(node.textContent || node.getAttribute?.('title') || '', sel);
647
+ }
648
+ }
649
+ const title = normalize(document.title || '').replace(/\\s*[-|_].*$/, '').trim();
650
+ if (title) pushAlias(title, 'document.title');
651
+
652
+ const uid = uidCandidates[0]?.value || null;
653
+ const alias = aliasCandidates[0]?.value || null;
654
+ return {
655
+ url: href,
656
+ hasLoginGuard,
657
+ uid,
658
+ alias,
659
+ uidCandidates,
660
+ aliasCandidates,
661
+ };
662
+ })()`;
663
+ }
664
+
665
+ function buildWeiboWhoamiScript(uid) {
666
+ return `(async () => {
667
+ const expectedUid = String(${JSON.stringify(String(uid || ''))}).trim();
668
+ if (!expectedUid) {
669
+ return { ok: false, uid: null, reason: 'missing_uid' };
670
+ }
671
+ const endpoint = ${JSON.stringify(WEIBO_PROFILE_INFO_ENDPOINT)};
672
+ const url = endpoint + '?uid=' + encodeURIComponent(expectedUid);
673
+ try {
674
+ const response = await fetch(url, {
675
+ method: 'GET',
676
+ credentials: 'include',
677
+ headers: { 'accept': 'application/json, text/plain, */*' },
678
+ });
679
+ const text = await response.text();
680
+ let json = null;
681
+ try {
682
+ json = JSON.parse(text);
683
+ } catch {
684
+ return {
685
+ ok: false,
686
+ uid: null,
687
+ status: response.status,
688
+ reason: 'invalid_json',
689
+ bodySnippet: String(text || '').slice(0, 120),
690
+ };
691
+ }
692
+ const dataUser = (json && json.data && (json.data.user || json.data)) || {};
693
+ const foundUid = String(
694
+ (dataUser && (dataUser.idstr || dataUser.id || dataUser.uid))
695
+ || json.uid
696
+ || '',
697
+ ).trim();
698
+ const hasData = Boolean(foundUid);
699
+ const code = json && (json.code || json.errno || json.status) ? String(json.code || json.errno || json.status) : null;
700
+ const okFlag = response.ok && (json.ok === 1 || json.ok === true || hasData);
701
+ return {
702
+ ok: okFlag,
703
+ uid: foundUid || null,
704
+ status: response.status,
705
+ code,
706
+ };
707
+ } catch (error) {
708
+ return {
709
+ ok: false,
710
+ uid: null,
711
+ reason: String(error?.message || error || 'whoami_failed'),
712
+ };
713
+ }
714
+ })()`;
715
+ }
716
+
717
+ export async function detectWeiboAccountIdentity(profileId) {
718
+ const normalizedProfileId = String(profileId || '').trim();
719
+ if (!normalizedProfileId) throw new Error('profileId is required');
720
+ const runDetect = async () => {
721
+ const payload = await callAPI('evaluate', {
722
+ profileId: normalizedProfileId,
723
+ script: buildWeiboDetectScript(),
724
+ });
725
+ const result = extractResult(payload);
726
+ const uidCandidates = Array.isArray(result?.uidCandidates) ? result.uidCandidates : [];
727
+ const normalizedCandidates = uidCandidates
728
+ .map((item) => ({
729
+ value: normalizeWeiboUid(item?.value),
730
+ source: normalizeText(item?.source),
731
+ }))
732
+ .filter((item) => Boolean(item.value));
733
+ const accountId = normalizeWeiboUid(result?.uid) || normalizedCandidates[0]?.value || null;
734
+ const alias = normalizeText(result?.alias) || null;
735
+ return {
736
+ profileId: normalizedProfileId,
737
+ url: normalizeText(result?.url) || WEIBO_HOME_URL,
738
+ hasLoginGuard: result?.hasLoginGuard === true,
739
+ accountId,
740
+ alias,
741
+ uidCandidates: normalizedCandidates,
742
+ };
743
+ };
744
+
745
+ let detected = await runDetect();
746
+ if (!detected.accountId || detected.hasLoginGuard) {
747
+ await sleep(1200);
748
+ const retry = await runDetect();
749
+ if (!detected.accountId && retry.accountId) detected.accountId = retry.accountId;
750
+ if (!detected.alias && retry.alias) detected.alias = retry.alias;
751
+ if (detected.hasLoginGuard && !retry.hasLoginGuard) detected.hasLoginGuard = false;
752
+ if (!detected.url && retry.url) detected.url = retry.url;
753
+ if ((!detected.uidCandidates || detected.uidCandidates.length === 0) && retry.uidCandidates?.length) {
754
+ detected.uidCandidates = retry.uidCandidates;
755
+ }
756
+ }
757
+
758
+ let cookies = [];
759
+ try {
760
+ cookies = await getProfileCookies(normalizedProfileId);
761
+ } catch {
762
+ cookies = [];
763
+ }
764
+ const hasSub = hasCookie(cookies, 'SUB', 'weibo.com');
765
+ const hasSubp = hasCookie(cookies, 'SUBP', 'weibo.com');
766
+ const hasCookieAuth = hasSub && hasSubp;
767
+
768
+ let apiVerified = false;
769
+ let apiUid = null;
770
+ let apiReason = null;
771
+ if (detected.accountId && !detected.hasLoginGuard && hasCookieAuth) {
772
+ try {
773
+ const payload = await callAPI('evaluate', {
774
+ profileId: normalizedProfileId,
775
+ script: buildWeiboWhoamiScript(detected.accountId),
776
+ });
777
+ const probe = extractResult(payload);
778
+ apiUid = normalizeWeiboUid(probe?.uid);
779
+ const uidMatched = !apiUid || apiUid === detected.accountId;
780
+ apiVerified = probe?.ok === true && uidMatched;
781
+ if (!apiVerified) {
782
+ apiReason = normalizeText(probe?.reason) || normalizeText(probe?.code) || 'whoami_failed';
783
+ }
784
+ } catch (error) {
785
+ apiVerified = false;
786
+ apiReason = `whoami_failed:${error?.message || String(error)}`;
787
+ }
788
+ } else if (!hasCookieAuth) {
789
+ apiReason = 'cookie_missing';
790
+ }
791
+
792
+ return {
793
+ ...detected,
794
+ hasCookieAuth,
795
+ cookieSignals: {
796
+ SUB: hasSub,
797
+ SUBP: hasSubp,
798
+ },
799
+ apiVerified,
800
+ apiUid,
801
+ apiReason,
802
+ };
803
+ }
804
+
805
+ export async function syncWeiboAccountByProfile(profileId, options = {}) {
806
+ const normalizedProfileId = String(profileId || '').trim();
807
+ if (!normalizedProfileId) throw new Error('profileId is required');
808
+ const pendingWhileLogin = options?.pendingWhileLogin === true;
809
+ try {
810
+ const detected = await detectWeiboAccountIdentity(normalizedProfileId);
811
+ if (detected.hasLoginGuard) {
812
+ if (pendingWhileLogin) return markProfilePending(normalizedProfileId, 'waiting_login_guard', 'weibo');
813
+ return markProfileInvalid(normalizedProfileId, 'login_guard', 'weibo');
814
+ }
815
+ if (!detected.hasCookieAuth) {
816
+ if (pendingWhileLogin) return markProfilePending(normalizedProfileId, 'waiting_cookie_auth', 'weibo');
817
+ return markProfileInvalid(normalizedProfileId, 'cookie_missing', 'weibo');
818
+ }
819
+ if (!detected.accountId) {
820
+ if (pendingWhileLogin) return markProfilePending(normalizedProfileId, 'waiting_account_id', 'weibo');
821
+ return markProfileInvalid(normalizedProfileId, 'missing_account_id', 'weibo');
822
+ }
823
+ if (!detected.apiVerified) {
824
+ if (pendingWhileLogin) return markProfilePending(normalizedProfileId, `waiting_whoami:${detected.apiReason || 'whoami_failed'}`, 'weibo');
825
+ return markProfileInvalid(normalizedProfileId, `whoami_failed:${detected.apiReason || 'unknown'}`, 'weibo');
826
+ }
827
+ return upsertProfileAccountState({
828
+ profileId: normalizedProfileId,
829
+ platform: 'weibo',
830
+ accountId: detected.accountId,
831
+ alias: detected.alias,
832
+ reason: null,
833
+ detectedAt: new Date().toISOString(),
834
+ });
835
+ } catch (error) {
836
+ if (isTransientSyncError(error)) {
837
+ if (pendingWhileLogin) {
838
+ return markProfilePending(normalizedProfileId, `waiting_login_sync:${error?.message || String(error)}`, 'weibo');
839
+ }
840
+ return markProfileInvalid(normalizedProfileId, `sync_unreachable:${error?.message || String(error)}`, 'weibo');
841
+ }
842
+ if (pendingWhileLogin) {
843
+ return markProfilePending(normalizedProfileId, `waiting_login_sync:${error?.message || String(error)}`, 'weibo');
844
+ }
845
+ return markProfileInvalid(normalizedProfileId, `sync_failed:${error?.message || String(error)}`, 'weibo');
846
+ }
847
+ }
848
+
849
+ export async function syncWeiboAccountsByProfiles(profileIds = [], options = {}) {
850
+ const list = Array.from(new Set((Array.isArray(profileIds) ? profileIds : []).map((item) => String(item || '').trim()).filter(Boolean)));
851
+ const out = [];
852
+ for (const profileId of list) {
853
+ const state = await syncWeiboAccountByProfile(profileId, options);
854
+ out.push(state);
855
+ }
856
+ return out;
857
+ }