|
@@ -2259,6 +2259,118 @@ async function collectDouyinAccountInfo() {
|
|
|
* 2. 跳转到笔记管理页,监听 API 获取笔记列表,取 notes 数量
|
|
* 2. 跳转到笔记管理页,监听 API 获取笔记列表,取 notes 数量
|
|
|
* 3. 账号ID使用 xhs_ 前缀
|
|
* 3. 账号ID使用 xhs_ 前缀
|
|
|
*/
|
|
*/
|
|
|
|
|
+function parseXhsMetric(value: unknown): number | undefined {
|
|
|
|
|
+ if (typeof value === 'number') {
|
|
|
|
|
+ return Number.isFinite(value) ? Math.round(value) : undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (typeof value !== 'string') return undefined;
|
|
|
|
|
+
|
|
|
|
|
+ const cleaned = value.trim().replace(/,/g, '').replace(/\s+/g, '');
|
|
|
|
|
+ const match = cleaned.match(/-?\d+(?:\.\d+)?/);
|
|
|
|
|
+ if (!match) return undefined;
|
|
|
|
|
+
|
|
|
|
|
+ let parsed = Number(match[0]);
|
|
|
|
|
+ if (!Number.isFinite(parsed)) return undefined;
|
|
|
|
|
+ const lower = cleaned.toLowerCase();
|
|
|
|
|
+ if (cleaned.includes('万') || lower.includes('w')) parsed *= 10000;
|
|
|
|
|
+ else if (cleaned.includes('亿')) parsed *= 100000000;
|
|
|
|
|
+ return Math.round(parsed);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function firstXhsString(sources: any[], keys: string[]): string | undefined {
|
|
|
|
|
+ for (const source of sources) {
|
|
|
|
|
+ if (!source || typeof source !== 'object') continue;
|
|
|
|
|
+ for (const key of keys) {
|
|
|
|
|
+ const value = source[key];
|
|
|
|
|
+ if (typeof value === 'string' && value.trim()) return value.trim();
|
|
|
|
|
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function firstXhsMetric(sources: any[], keys: string[]): number | undefined {
|
|
|
|
|
+ for (const source of sources) {
|
|
|
|
|
+ if (!source || typeof source !== 'object') continue;
|
|
|
|
|
+ for (const key of keys) {
|
|
|
|
|
+ if (!(key in source)) continue;
|
|
|
|
|
+ const value = parseXhsMetric(source[key]);
|
|
|
|
|
+ if (value !== undefined) return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function extractXhsAccountFields(raw: any): {
|
|
|
|
|
+ name?: string;
|
|
|
|
|
+ avatar?: string;
|
|
|
|
|
+ red_num?: string;
|
|
|
|
|
+ user_id?: string;
|
|
|
|
|
+ fans_count?: number;
|
|
|
|
|
+ note_count?: number;
|
|
|
|
|
+} {
|
|
|
|
|
+ const sources = [
|
|
|
|
|
+ raw?.data?.user_info,
|
|
|
|
|
+ raw?.data?.userInfo,
|
|
|
|
|
+ raw?.data?.user,
|
|
|
|
|
+ raw?.data?.account_info,
|
|
|
|
|
+ raw?.data?.accountInfo,
|
|
|
|
|
+ raw?.data?.author_info,
|
|
|
|
|
+ raw?.data?.authorInfo,
|
|
|
|
|
+ raw?.data?.basic_info,
|
|
|
|
|
+ raw?.data?.basicInfo,
|
|
|
|
|
+ raw?.data?.core_user_info,
|
|
|
|
|
+ raw?.data?.coreUserInfo,
|
|
|
|
|
+ raw?.data?.profile_info,
|
|
|
|
|
+ raw?.data?.profileInfo,
|
|
|
|
|
+ raw?.data?.profile,
|
|
|
|
|
+ raw?.data?.creator,
|
|
|
|
|
+ raw?.data,
|
|
|
|
|
+ raw?.user_info,
|
|
|
|
|
+ raw?.userInfo,
|
|
|
|
|
+ raw?.user,
|
|
|
|
|
+ raw?.account_info,
|
|
|
|
|
+ raw?.accountInfo,
|
|
|
|
|
+ raw?.author_info,
|
|
|
|
|
+ raw?.authorInfo,
|
|
|
|
|
+ raw?.basic_info,
|
|
|
|
|
+ raw?.basicInfo,
|
|
|
|
|
+ raw?.core_user_info,
|
|
|
|
|
+ raw?.coreUserInfo,
|
|
|
|
|
+ raw?.profile_info,
|
|
|
|
|
+ raw?.profileInfo,
|
|
|
|
|
+ raw?.profile,
|
|
|
|
|
+ raw,
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: firstXhsString(sources, ['name', 'nickname', 'nick_name', 'nickName', 'userName', 'user_name']),
|
|
|
|
|
+ avatar: firstXhsString(sources, ['avatar', 'avatarUrl', 'avatar_url', 'image', 'userAvatar', 'user_avatar']),
|
|
|
|
|
+ red_num: firstXhsString(sources, ['red_num', 'redNum', 'red_id', 'redId', 'redid', 'redID']),
|
|
|
|
|
+ user_id: firstXhsString(sources, ['user_id', 'userId']),
|
|
|
|
|
+ fans_count: firstXhsMetric(sources, [
|
|
|
|
|
+ 'fans_count',
|
|
|
|
|
+ 'fansCount',
|
|
|
|
|
+ 'fans',
|
|
|
|
|
+ 'fans_num',
|
|
|
|
|
+ 'fansNum',
|
|
|
|
|
+ 'total_fans',
|
|
|
|
|
+ 'totalFans',
|
|
|
|
|
+ 'follower_count',
|
|
|
|
|
+ 'followers_count',
|
|
|
|
|
+ 'followersCount',
|
|
|
|
|
+ 'followers',
|
|
|
|
|
+ ]),
|
|
|
|
|
+ note_count: firstXhsMetric(sources, ['note_count', 'noteCount', 'notes_count', 'notesCount', 'notes']),
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function normalizeXhsClientAccountId(accountId: string): string {
|
|
|
|
|
+ const raw = String(accountId || '').trim();
|
|
|
|
|
+ if (!raw) return `xhs_${Date.now()}`;
|
|
|
|
|
+ return raw.startsWith('xhs_') ? raw : `xhs_${raw.replace(/^xiaohongshu_/, '')}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
async function collectXiaohongshuAccountInfo() {
|
|
async function collectXiaohongshuAccountInfo() {
|
|
|
const webview = webviewRef.value;
|
|
const webview = webviewRef.value;
|
|
|
if (!webview) return;
|
|
if (!webview) return;
|
|
@@ -2284,7 +2396,10 @@ async function collectXiaohongshuAccountInfo() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 尝试从页面 HTML 提取信息作为备选
|
|
// 尝试从页面 HTML 提取信息作为备选
|
|
|
- let info: any = personalInfo?.data || personalInfo || {};
|
|
|
|
|
|
|
+ let info: any = {
|
|
|
|
|
+ ...(personalInfo?.data || personalInfo || {}),
|
|
|
|
|
+ ...extractXhsAccountFields(personalInfo),
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
if (!info.red_num && !info.name) {
|
|
if (!info.red_num && !info.name) {
|
|
|
console.log('[小红书] API 数据不完整,尝试从页面提取...');
|
|
console.log('[小红书] API 数据不完整,尝试从页面提取...');
|
|
@@ -2306,13 +2421,13 @@ async function collectXiaohongshuAccountInfo() {
|
|
|
return result;
|
|
return result;
|
|
|
})()
|
|
})()
|
|
|
`).catch(() => ({}));
|
|
`).catch(() => ({}));
|
|
|
- info = { ...info, ...pageInfo };
|
|
|
|
|
|
|
+ info = { ...info, ...pageInfo, ...extractXhsAccountFields({ ...info, ...pageInfo }) };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
console.log('[小红书] 个人信息:', info);
|
|
console.log('[小红书] 个人信息:', info);
|
|
|
|
|
|
|
|
// 生成账号ID(如果没有 red_num,使用时间戳)
|
|
// 生成账号ID(如果没有 red_num,使用时间戳)
|
|
|
- const accountId = info.red_num ? `xhs_${info.red_num}` : `xhs_${Date.now()}`;
|
|
|
|
|
|
|
+ let accountId = info.red_num ? normalizeXhsClientAccountId(info.red_num) : `xhs_${Date.now()}`;
|
|
|
|
|
|
|
|
// 获取作品数 - 小红书笔记管理页有额外权限验证,跳转会触发401
|
|
// 获取作品数 - 小红书笔记管理页有额外权限验证,跳转会触发401
|
|
|
// 直接从首页尝试获取,获取不到则使用0,后续通过后台刷新获取
|
|
// 直接从首页尝试获取,获取不到则使用0,后续通过后台刷新获取
|
|
@@ -2365,16 +2480,24 @@ async function collectXiaohongshuAccountInfo() {
|
|
|
cookieData: cookieData.value,
|
|
cookieData: cookieData.value,
|
|
|
});
|
|
});
|
|
|
if (verifyResult?.success && verifyResult.accountInfo) {
|
|
if (verifyResult?.success && verifyResult.accountInfo) {
|
|
|
- worksCount = verifyResult.accountInfo.worksCount || 0;
|
|
|
|
|
- info.fans_count = verifyResult.accountInfo.fansCount || 0;
|
|
|
|
|
|
|
+ if (verifyResult.accountInfo.accountId) {
|
|
|
|
|
+ accountId = normalizeXhsClientAccountId(verifyResult.accountInfo.accountId);
|
|
|
|
|
+ }
|
|
|
|
|
+ if ((verifyResult.accountInfo.worksCount || 0) > 0 || worksCount === 0) {
|
|
|
|
|
+ worksCount = verifyResult.accountInfo.worksCount || 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ const verifiedFans = parseXhsMetric(verifyResult.accountInfo.fansCount);
|
|
|
|
|
+ if (verifiedFans !== undefined && (verifiedFans > 0 || !info.fans_count)) {
|
|
|
|
|
+ info.fans_count = verifiedFans;
|
|
|
|
|
+ }
|
|
|
if (verifyResult.accountInfo.avatarUrl) info.avatar = verifyResult.accountInfo.avatarUrl;
|
|
if (verifyResult.accountInfo.avatarUrl) info.avatar = verifyResult.accountInfo.avatarUrl;
|
|
|
if (verifyResult.accountInfo.accountName) info.name = verifyResult.accountInfo.accountName;
|
|
if (verifyResult.accountInfo.accountName) info.name = verifyResult.accountInfo.accountName;
|
|
|
} else {
|
|
} else {
|
|
|
- worksCount = 0;
|
|
|
|
|
|
|
+ worksCount = worksCount || 0;
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.warn('[小红书] verify-cookie failed:', e);
|
|
console.warn('[小红书] verify-cookie failed:', e);
|
|
|
- worksCount = 0;
|
|
|
|
|
|
|
+ worksCount = worksCount || 0;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2385,7 +2508,7 @@ async function collectXiaohongshuAccountInfo() {
|
|
|
accountId,
|
|
accountId,
|
|
|
accountName: info.name || '小红书用户',
|
|
accountName: info.name || '小红书用户',
|
|
|
avatarUrl: info.avatar || '',
|
|
avatarUrl: info.avatar || '',
|
|
|
- fansCount: info.fans_count || 0,
|
|
|
|
|
|
|
+ fansCount: parseXhsMetric(info.fans_count) || 0,
|
|
|
worksCount,
|
|
worksCount,
|
|
|
};
|
|
};
|
|
|
|
|
|