|
|
@@ -1434,7 +1434,10 @@ function handleDomReady() {
|
|
|
|
|
|
// 管理模式下不自动检测登录状态
|
|
|
if (!isAdminMode.value) {
|
|
|
- // 注入 API 拦截脚本
|
|
|
+ // 优先使用 CDP 网络拦截(更底层更可靠)
|
|
|
+ enableCDPNetworkIntercept();
|
|
|
+
|
|
|
+ // 同时保留 JS 注入作为备用(某些情况下 CDP 可能不可用)
|
|
|
injectApiInterceptScript();
|
|
|
// 设置 console-message 监听器来接收 API 数据
|
|
|
setupApiDataListener();
|
|
|
@@ -1554,7 +1557,7 @@ function getApiPatternsForPlatform(): Array<{match: string, key: string}> {
|
|
|
return patterns[platform.value] || [];
|
|
|
}
|
|
|
|
|
|
-// 设置 console-message 监听器来接收 API 拦截数据
|
|
|
+// 设置 console-message 监听器来接收 API 拦截数据(JS 注入方式的备用)
|
|
|
function setupApiDataListener() {
|
|
|
const webview = webviewRef.value;
|
|
|
if (!webview) return;
|
|
|
@@ -1574,6 +1577,90 @@ function setupApiDataListener() {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+// ========== CDP 网络拦截(更底层的方式)==========
|
|
|
+let cdpInterceptEnabled = false;
|
|
|
+
|
|
|
+// 启用 CDP 网络拦截
|
|
|
+async function enableCDPNetworkIntercept() {
|
|
|
+ const webview = webviewRef.value;
|
|
|
+ if (!webview || cdpInterceptEnabled) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const webContentsId = webview.getWebContentsId();
|
|
|
+ if (!webContentsId) {
|
|
|
+ console.warn('[CDP] 无法获取 webContentsId');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const patterns = getApiPatternsForPlatform();
|
|
|
+ if (patterns.length === 0) {
|
|
|
+ console.log('[CDP] 当前平台无需拦截的 API');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = await window.electronAPI?.enableNetworkIntercept(webContentsId, patterns);
|
|
|
+ if (result) {
|
|
|
+ cdpInterceptEnabled = true;
|
|
|
+ console.log('[CDP] 网络拦截已启用,webContentsId:', webContentsId);
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('[CDP] 启用网络拦截失败:', err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 禁用 CDP 网络拦截
|
|
|
+async function disableCDPNetworkIntercept() {
|
|
|
+ const webview = webviewRef.value;
|
|
|
+ if (!webview || !cdpInterceptEnabled) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const webContentsId = webview.getWebContentsId();
|
|
|
+ if (webContentsId) {
|
|
|
+ await window.electronAPI?.disableNetworkIntercept(webContentsId);
|
|
|
+ }
|
|
|
+ cdpInterceptEnabled = false;
|
|
|
+ console.log('[CDP] 网络拦截已禁用');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('[CDP] 禁用网络拦截失败:', err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 更新 CDP 拦截的 patterns
|
|
|
+async function updateCDPPatterns() {
|
|
|
+ const webview = webviewRef.value;
|
|
|
+ if (!webview || !cdpInterceptEnabled) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const webContentsId = webview.getWebContentsId();
|
|
|
+ const patterns = getApiPatternsForPlatform();
|
|
|
+ if (webContentsId && patterns.length > 0) {
|
|
|
+ await window.electronAPI?.updateNetworkPatterns(webContentsId, patterns);
|
|
|
+ console.log('[CDP] patterns 已更新');
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('[CDP] 更新 patterns 失败:', err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 设置 CDP 数据监听器
|
|
|
+function setupCDPDataListener() {
|
|
|
+ window.electronAPI?.onNetworkInterceptData((data) => {
|
|
|
+ const webview = webviewRef.value;
|
|
|
+ if (!webview) return;
|
|
|
+
|
|
|
+ const webContentsId = webview.getWebContentsId();
|
|
|
+ if (data.webContentsId === webContentsId) {
|
|
|
+ console.log('[CDP] 收到 API 数据:', data.key, data.url);
|
|
|
+ apiResponseData.value[data.key] = data.data;
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 清理 CDP 监听器
|
|
|
+function cleanupCDPListener() {
|
|
|
+ window.electronAPI?.removeNetworkInterceptListener();
|
|
|
+}
|
|
|
+
|
|
|
// 拦截自定义协议链接,防止弹出系统对话框
|
|
|
function setupProtocolInterception() {
|
|
|
const webview = webviewRef.value;
|
|
|
@@ -2284,9 +2371,9 @@ async function collectWeixinVideoAccountInfo() {
|
|
|
|
|
|
/**
|
|
|
* 百家号登录流程:
|
|
|
- * 1. 跳转到账号设置页面从 HTML 提取基本信息(头像、昵称、ID)
|
|
|
+ * 1. 监听 API /builder/app/appinfo 获取基本信息(头像、昵称、ID)
|
|
|
* 2. 监听 API /cms-ui/rights/growth/get_info 获取粉丝数
|
|
|
- * 3. 跳转到作品管理页,监听 API /pcui/article/lists 获取作品数
|
|
|
+ * 3. 跳转到作品管理页获取作品数
|
|
|
* 4. 账号ID使用 bjh_ 前缀
|
|
|
*/
|
|
|
async function collectBaijiahaoAccountInfo() {
|
|
|
@@ -2297,121 +2384,150 @@ async function collectBaijiahaoAccountInfo() {
|
|
|
console.log('[百家号] 步骤0: 获取 Cookie...');
|
|
|
await getCookieData();
|
|
|
|
|
|
- console.log('[百家号] 步骤1: 跳转到账号设置页面获取基本信息...');
|
|
|
+ console.log('[百家号] 步骤1: 获取账号基本信息 (appinfo API)...');
|
|
|
|
|
|
- // 跳转到账号设置页面获取头像、昵称、ID
|
|
|
- const settingsUrl = 'https://baijiahao.baidu.com/builder/rc/settings/accountSet';
|
|
|
- console.log('[百家号] 正在跳转到:', settingsUrl);
|
|
|
+ // 先检查是否已有 appinfo 数据(可能由 CDP 拦截到)
|
|
|
+ let appInfo = apiResponseData.value['appInfo'];
|
|
|
|
|
|
- // 使用 Promise 等待导航完成
|
|
|
- await new Promise<void>((resolve) => {
|
|
|
- const onNavigate = () => {
|
|
|
- console.log('[百家号] 导航开始...');
|
|
|
- };
|
|
|
- const onLoad = () => {
|
|
|
- console.log('[百家号] 页面加载完成,当前URL:', webview.getURL());
|
|
|
- webview.removeEventListener('did-start-loading', onNavigate);
|
|
|
- webview.removeEventListener('did-stop-loading', onLoad);
|
|
|
- resolve();
|
|
|
- };
|
|
|
- webview.addEventListener('did-start-loading', onNavigate);
|
|
|
- webview.addEventListener('did-stop-loading', onLoad);
|
|
|
- webview.loadURL(settingsUrl);
|
|
|
+ if (!appInfo) {
|
|
|
+ // 刷新首页以触发 appinfo API
|
|
|
+ apiResponseData.value = {};
|
|
|
+ webview.loadURL('https://baijiahao.baidu.com/builder/rc/home');
|
|
|
+ await waitForPageLoad(webview, 5000);
|
|
|
+ await sleep(1000);
|
|
|
|
|
|
- // 超时保护
|
|
|
- setTimeout(() => {
|
|
|
- webview.removeEventListener('did-start-loading', onNavigate);
|
|
|
- webview.removeEventListener('did-stop-loading', onLoad);
|
|
|
- resolve();
|
|
|
- }, 10000);
|
|
|
- });
|
|
|
-
|
|
|
- await sleep(3000); // 额外等待页面渲染
|
|
|
+ // 等待 appinfo API 数据
|
|
|
+ appInfo = await waitForApiData('appInfo', 10000).catch(() => null);
|
|
|
+ }
|
|
|
|
|
|
- const currentUrl = webview.getURL();
|
|
|
- console.log('[百家号] 当前页面 URL:', currentUrl);
|
|
|
+ console.log('[百家号] appInfo 数据:', appInfo);
|
|
|
|
|
|
- // 验证是否跳转成功
|
|
|
- if (!currentUrl.includes('settings') && !currentUrl.includes('accountSet')) {
|
|
|
- console.warn('[百家号] 跳转失败,尝试重新跳转...');
|
|
|
- webview.loadURL(settingsUrl);
|
|
|
- await sleep(5000);
|
|
|
- console.log('[百家号] 重试后 URL:', webview.getURL());
|
|
|
- }
|
|
|
+ // 如果 API 获取失败,回退到页面提取
|
|
|
+ let user: { name?: string; avatar?: string; app_id?: string } = {};
|
|
|
|
|
|
- const pageInfo = await webview.executeJavaScript(`
|
|
|
- (function() {
|
|
|
- const result = {};
|
|
|
-
|
|
|
- // 用户名:class 包含 userName
|
|
|
- const nameEl = document.querySelector('[class*="userName"]');
|
|
|
- if (nameEl) {
|
|
|
- const text = nameEl.childNodes[0]?.textContent?.trim();
|
|
|
- if (text) result.name = text;
|
|
|
- }
|
|
|
-
|
|
|
- // 头像:class 包含 userImg
|
|
|
- const avatarEl = document.querySelector('[class*="userImg"]:not([class*="Mask"]):not([class*="Hover"]):not([class*="Box"])');
|
|
|
- if (avatarEl?.src) result.avatar = avatarEl.src;
|
|
|
-
|
|
|
- // 百家号 ID:class 包含 idName
|
|
|
- const idEl = document.querySelector('[class*="idName"]');
|
|
|
- if (idEl) {
|
|
|
- const idText = idEl.textContent?.trim();
|
|
|
- if (idText && /^\\d+$/.test(idText)) {
|
|
|
- result.app_id = idText;
|
|
|
+ if (appInfo?.data?.user) {
|
|
|
+ user = appInfo.data.user;
|
|
|
+ console.log('[百家号] 从 API 获取到用户信息:', user);
|
|
|
+ } else {
|
|
|
+ console.log('[百家号] API 获取失败,尝试从页面提取...');
|
|
|
+
|
|
|
+ // 跳转到账号设置页面
|
|
|
+ webview.loadURL('https://baijiahao.baidu.com/builder/rc/settings/accountSet');
|
|
|
+ await waitForPageLoad(webview, 8000);
|
|
|
+ await sleep(2000);
|
|
|
+
|
|
|
+ const pageInfo = await webview.executeJavaScript(`
|
|
|
+ (function() {
|
|
|
+ const result = {};
|
|
|
+
|
|
|
+ // 用户名
|
|
|
+ const nameEl = document.querySelector('[class*="userName"]');
|
|
|
+ if (nameEl) {
|
|
|
+ const text = nameEl.childNodes[0]?.textContent?.trim();
|
|
|
+ if (text) result.name = text;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- // 备选:从"更多信息"区域提取 ID
|
|
|
- if (!result.app_id) {
|
|
|
- const morInfoItems = document.querySelectorAll('[class*="morInfoItem"]');
|
|
|
- for (const item of morInfoItems) {
|
|
|
- const label = item.querySelector('[class*="morInfoLabel"]');
|
|
|
- const value = item.querySelector('[class*="morInfoValue"]');
|
|
|
- if (label?.textContent?.includes('ID') && value) {
|
|
|
- const match = value.textContent?.match(/(\\d{10,})/);
|
|
|
- if (match) result.app_id = match[1];
|
|
|
+
|
|
|
+ // 头像
|
|
|
+ const avatarEl = document.querySelector('[class*="userImg"]:not([class*="Mask"]):not([class*="Hover"]):not([class*="Box"])');
|
|
|
+ if (avatarEl?.src) result.avatar = avatarEl.src;
|
|
|
+
|
|
|
+ // ID
|
|
|
+ const idEl = document.querySelector('[class*="idName"]');
|
|
|
+ if (idEl) {
|
|
|
+ const idText = idEl.textContent?.trim();
|
|
|
+ if (idText && /^\\d+$/.test(idText)) {
|
|
|
+ result.app_id = idText;
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- console.log('[百家号] 从页面提取的信息:', result);
|
|
|
- return result;
|
|
|
- })()
|
|
|
- `).catch(() => ({}));
|
|
|
-
|
|
|
- console.log('[百家号] 页面提取结果:', pageInfo);
|
|
|
+
|
|
|
+ if (!result.app_id) {
|
|
|
+ const morInfoItems = document.querySelectorAll('[class*="morInfoItem"]');
|
|
|
+ for (const item of morInfoItems) {
|
|
|
+ const label = item.querySelector('[class*="morInfoLabel"]');
|
|
|
+ const value = item.querySelector('[class*="morInfoValue"]');
|
|
|
+ if (label?.textContent?.includes('ID') && value) {
|
|
|
+ const match = value.textContent?.match(/(\\d{10,})/);
|
|
|
+ if (match) result.app_id = match[1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ })()
|
|
|
+ `).catch(() => ({}));
|
|
|
+
|
|
|
+ if (pageInfo && (pageInfo.name || pageInfo.app_id)) {
|
|
|
+ user = pageInfo;
|
|
|
+ console.log('[百家号] 从页面提取到用户信息:', user);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (!pageInfo || (!pageInfo.name && !pageInfo.app_id)) {
|
|
|
+ if (!user.name && !user.app_id) {
|
|
|
throw new Error('无法获取百家号账号信息');
|
|
|
}
|
|
|
|
|
|
- const user = pageInfo;
|
|
|
console.log('[百家号] 基本信息:', user);
|
|
|
|
|
|
- // 步骤2: 返回首页获取粉丝数
|
|
|
- console.log('[百家号] 步骤2: 跳转首页获取粉丝数...');
|
|
|
- apiResponseData.value = {}; // 清空 API 数据
|
|
|
- webview.loadURL('https://baijiahao.baidu.com/builder/rc/home');
|
|
|
- await waitForPageLoad(webview, 5000);
|
|
|
- injectApiInterceptScript();
|
|
|
+ // 步骤2: 获取粉丝数
|
|
|
+ console.log('[百家号] 步骤2: 获取粉丝数...');
|
|
|
+
|
|
|
+ // 检查是否已有 growthInfo 数据
|
|
|
+ let growthInfo = apiResponseData.value['growthInfo'];
|
|
|
+
|
|
|
+ if (!growthInfo) {
|
|
|
+ // 确保在首页
|
|
|
+ const currentUrl = webview.getURL();
|
|
|
+ if (!currentUrl.includes('/builder/rc/home')) {
|
|
|
+ apiResponseData.value = {};
|
|
|
+ webview.loadURL('https://baijiahao.baidu.com/builder/rc/home');
|
|
|
+ await waitForPageLoad(webview, 5000);
|
|
|
+ await sleep(1000);
|
|
|
+ }
|
|
|
+ growthInfo = await waitForApiData('growthInfo', 8000).catch(() => null);
|
|
|
+ }
|
|
|
|
|
|
- const growthInfo = await waitForApiData('growthInfo', 8000).catch(() => null);
|
|
|
const fansCount = growthInfo?.data?.total_fans || growthInfo?.total_fans || 0;
|
|
|
console.log('[百家号] 粉丝数:', fansCount);
|
|
|
|
|
|
- // 步骤3: 跳转到作品管理页获取作品数
|
|
|
- console.log('[百家号] 步骤3: 跳转到作品管理页获取作品数...');
|
|
|
- apiResponseData.value = {}; // 清空 API 数据
|
|
|
- webview.loadURL('https://baijiahao.baidu.com/builder/rc/content');
|
|
|
- await waitForPageLoad(webview, 5000);
|
|
|
- injectApiInterceptScript();
|
|
|
+ // 步骤3: 直接发起 API 请求获取作品数
|
|
|
+ console.log('[百家号] 步骤3: 发起 API 请求获取作品数...');
|
|
|
|
|
|
- const worksCount = await waitForApiData('articleList', 15000).then(data => {
|
|
|
- const list = data?.data?.list || data?.list || [];
|
|
|
- return list.length;
|
|
|
- }).catch(() => 0);
|
|
|
+ // 直接在 webview 中发起 API 请求(会自动携带 cookie)
|
|
|
+ const worksCount = await webview.executeJavaScript(`
|
|
|
+ (async function() {
|
|
|
+ try {
|
|
|
+ const apiUrl = 'https://baijiahao.baidu.com/pcui/article/lists?currentPage=1&pageSize=10&search=&type=&collection=&startDate=&endDate=&clearBeforeFetch=false&dynamic=0';
|
|
|
+ console.log('[百家号] 发起 API 请求:', apiUrl);
|
|
|
+
|
|
|
+ const response = await fetch(apiUrl, {
|
|
|
+ method: 'GET',
|
|
|
+ credentials: 'include', // 携带 cookie
|
|
|
+ headers: {
|
|
|
+ 'Accept': 'application/json, text/plain, */*',
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+ console.log('[百家号] API 响应:', JSON.stringify(data).substring(0, 200));
|
|
|
+
|
|
|
+ if (data.errno === 0 && data.data) {
|
|
|
+ const list = data.data.list || [];
|
|
|
+ console.log('[百家号] 从 API 获取作品数:', list.length);
|
|
|
+ return list.length;
|
|
|
+ } else {
|
|
|
+ console.log('[百家号] API 返回错误:', data.errno, data.errmsg);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('[百家号] API 请求失败:', err);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ `).catch((err) => {
|
|
|
+ console.error('[百家号] executeJavaScript 失败:', err);
|
|
|
+ return 0;
|
|
|
+ });
|
|
|
|
|
|
console.log('[百家号] 作品数:', worksCount);
|
|
|
|
|
|
@@ -3684,6 +3800,9 @@ onMounted(async () => {
|
|
|
console.log('[BrowserTab] isAdminMode:', isAdminMode.value);
|
|
|
console.log('[BrowserTab] initialUrl:', initialUrl.value);
|
|
|
|
|
|
+ // 设置 CDP 网络拦截数据监听器
|
|
|
+ setupCDPDataListener();
|
|
|
+
|
|
|
// 检查 AI 服务可用性(非管理模式)
|
|
|
if (!isAdminMode.value) {
|
|
|
await checkAIAvailability();
|
|
|
@@ -3739,6 +3858,8 @@ async function setPresetCookies(cookieDataStr: string) {
|
|
|
kuaishou: '.kuaishou.com',
|
|
|
weixin_video: '.qq.com',
|
|
|
bilibili: '.bilibili.com',
|
|
|
+ baijiahao: '.baidu.com',
|
|
|
+ toutiao: '.toutiao.com',
|
|
|
};
|
|
|
const rootDomain = platformDomainMap[platform.value] || '';
|
|
|
|
|
|
@@ -3812,6 +3933,10 @@ onUnmounted(() => {
|
|
|
stopAutoCheck();
|
|
|
stopAIAnalysis();
|
|
|
|
|
|
+ // 清理 CDP 网络拦截
|
|
|
+ disableCDPNetworkIntercept();
|
|
|
+ cleanupCDPListener();
|
|
|
+
|
|
|
// 清理 webview session
|
|
|
if (window.electronAPI?.clearWebviewCookies) {
|
|
|
window.electronAPI.clearWebviewCookies(webviewPartition.value);
|