/** * 百家号登录服务 * @module services/login/BaijiahaoLoginService * * 登录流程(按用户要求): * 1. 打开 https://baijiahao.baidu.com/builder/theme/bjh/login * 2. 等待用户扫码登录,跳转到 /builder/rc/home 或 AI 识别已登录 * 3. 监听 API /builder/app/appinfo 获取:头像(user.avatar)、昵称(user.name)、百家号ID(user.app_id) * 4. 监听 API /cms-ui/rights/growth/get_info 获取:粉丝数(total_fans) * 5. 跳转到 https://baijiahao.baidu.com/builder/rc/content 作品管理页 * 6. 监听 API /pcui/article/lists 获取作品列表,取 list 数量作为作品数 * 7. 完成后发送事件,账号ID使用 bjh_ 前缀 */ import { logger } from '../../utils/logger.js'; import { BaseLoginService, type ApiInterceptConfig } from './BaseLoginService.js'; import type { AccountInfo, LoginSession } from './types.js'; export class BaijiahaoLoginService extends BaseLoginService { constructor() { super({ platform: 'baijiahao', displayName: '百家号', loginUrl: 'https://baijiahao.baidu.com/builder/theme/bjh/login', successIndicators: ['/builder/rc/home', 'baijiahao.baidu.com/builder/rc'], cookieDomain: '.baidu.com', accountIdPrefix: 'bjh_', }); } /** * API 拦截配置 */ protected override getApiInterceptConfigs(): ApiInterceptConfig[] { return [ // 应用信息接口 - 获取头像、昵称、app_id { urlPattern: '/builder/app/appinfo', dataKey: 'appInfo', handler: (data: any) => { const user = data.data?.user || data.user || {}; return { appId: user.app_id, name: user.name, avatar: user.avatar?.startsWith('http') ? user.avatar : `https:${user.avatar}`, userId: user.userid, }; }, }, // 成长信息接口 - 获取粉丝数 { urlPattern: '/cms-ui/rights/growth/get_info', dataKey: 'growthInfo', handler: (data: any) => ({ totalFans: data.data?.total_fans || data.total_fans || 0, }), }, // 文章列表接口 - 获取作品数 { urlPattern: '/pcui/article/lists', dataKey: 'articleList', handler: (data: any) => { // 处理分散的响应格式 const dataList = data.data?.article_list || data.data?.list || data.list || []; return { list: dataList, count: dataList.length }; }, }, ]; } /** * 收集账号信息 */ protected override async collectAccountInfo(session: LoginSession): Promise { try { // 关键步骤:确保在主页面(首页)获取完整Cookie上下文 logger.info('[百家号] 确保在主页面获取完整Cookie...'); const currentUrl = session.page.url(); if (!currentUrl.includes('/builder/rc/home')) { logger.info(`[百家号] 当前不在主页: ${currentUrl},跳转到主页...`); await session.page.goto('https://baijiahao.baidu.com/builder/rc/home', { waitUntil: 'domcontentloaded', timeout: 30000 }); await new Promise(resolve => setTimeout(resolve, 3000)); // 等待页面完全加载 } // 步骤3: 等待 appinfo API logger.info('[百家号] 等待 appinfo API...'); let appInfo = await this.waitForApiData(session, 'appInfo', 10000); if (!appInfo) { logger.info('[百家号] 未拿到 appinfo,刷新页面重试...'); await session.page.reload({ waitUntil: 'domcontentloaded' }); await new Promise(resolve => setTimeout(resolve, 3000)); // 增加等待时间 appInfo = await this.waitForApiData(session, 'appInfo', 15000); } if (!appInfo?.appId) { logger.error('[百家号] 无法获取百家号ID'); return null; } logger.info('[百家号] 基本信息:', appInfo); // 步骤4: 等待 growth 信息(可能首页已触发) logger.info('[百家号] 等待粉丝数据...'); const growthInfo = await this.waitForApiData(session, 'growthInfo', 5000); const fansCount = growthInfo?.totalFans || 0; logger.info(`[百家号] 粉丝数: ${fansCount}`); // 步骤5+6: 跳转到作品管理页,等待文章列表 API logger.info('[百家号] 跳转到作品管理页...'); const contentUrl = 'https://baijiahao.baidu.com/builder/rc/content'; // 尝试多次获取作品列表(百家号可能有分散认证问题) let articleData = null; for (let attempt = 1; attempt <= 3; attempt++) { try { logger.info(`[百家号] 获取作品列表(第${attempt}次尝试)...`); articleData = await this.navigateAndWaitForApi(session, contentUrl, 'articleList', 15000); if (articleData) { break; } if (attempt < 3) { logger.info(`[百家号] 第${attempt}次尝试失败,等待${attempt * 2}秒后重试...`); await new Promise(resolve => setTimeout(resolve, attempt * 2000)); } } catch (error) { logger.warn(`[百家号] 第${attempt}次尝试获取作品列表失败:`, error); if (attempt < 3) { await new Promise(resolve => setTimeout(resolve, attempt * 2000)); } } } const worksCount = articleData?.count || 0; logger.info(`[百家号] 作品数: ${worksCount}`); // 步骤7: 组装账号信息,使用 bjh_ 前缀 return { accountId: `bjh_${appInfo.appId}`, accountName: appInfo.name || '百家号用户', avatarUrl: appInfo.avatar || '', fansCount, worksCount, }; } catch (error) { logger.error('[百家号] 收集账号信息失败:', error); return null; } } } // 导出单例 export const baijiahaoLoginService = new BaijiahaoLoginService();