@web-auto/webauto 0.1.18 → 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.
- package/README.md +122 -53
- package/apps/desktop-console/dist/main/index.mjs +227 -12
- package/apps/desktop-console/dist/renderer/index.js +237 -8
- package/apps/desktop-console/entry/ui-cli.mjs +282 -16
- package/apps/desktop-console/entry/ui-console.mjs +46 -15
- package/apps/webauto/entry/account.mjs +126 -27
- package/apps/webauto/entry/lib/account-detect.mjs +399 -9
- package/apps/webauto/entry/lib/account-store.mjs +201 -109
- package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
- package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
- package/apps/webauto/entry/lib/profilepool.mjs +12 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
- package/apps/webauto/entry/lib/session-init.mjs +227 -0
- package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
- package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
- package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
- package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
- package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
- package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
- package/apps/webauto/entry/profilepool.mjs +56 -9
- package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
- package/apps/webauto/entry/weibo-unified.mjs +84 -11
- package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
- package/apps/webauto/entry/xhs-unified.mjs +92 -997
- package/bin/webauto.mjs +22 -4
- package/dist/modules/camo-backend/src/index.js +33 -0
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
- package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
- package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
- package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
- package/dist/modules/workflow/src/runner.js +2 -0
- package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
- package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
- package/modules/camo-backend/src/index.ts +31 -0
- package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
- package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
- package/modules/camo-backend/src/internal/ws-server.ts +17 -17
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
- package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
- package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
- package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
- package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
- package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
- package/modules/workflow/blocks/EnsureSession.ts +0 -4
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
- package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
- package/modules/workflow/src/runner.ts +2 -0
- package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
- package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
- package/package.json +2 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
- 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
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
+
}
|