| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- /**
- * 检查抖音账号同步问题 - 诊断脚本
- * 用法: tsx src/scripts/check-douyin-account.ts <accountId或account_id>
- */
- import { initDatabase, AppDataSource, PlatformAccount } from '../models/index.js';
- import { logger } from '../utils/logger.js';
- import { headlessBrowserService } from '../services/HeadlessBrowserService.js';
- import { CookieManager } from '../automation/cookie.js';
- async function main() {
- const accountIdOrAccountId = process.argv[2];
- if (!accountIdOrAccountId) {
- logger.error('请提供账号ID或account_id');
- logger.info('用法: tsx src/scripts/check-douyin-account.ts <accountId或account_id>');
- process.exit(1);
- }
- try {
- await initDatabase();
- const accountRepository = AppDataSource.getRepository(PlatformAccount);
- // 先尝试作为数据库主键ID查询(仅当参数是纯数字时)
- const maybeId = Number(accountIdOrAccountId);
- let account: PlatformAccount | null = null;
- if (!Number.isNaN(maybeId) && Number.isInteger(maybeId) && maybeId > 0) {
- account = await accountRepository.findOne({
- where: { id: maybeId },
- });
- }
- // 如果没找到,尝试作为account_id(字符串)查询
- if (!account) {
- account = await accountRepository.findOne({
- where: { accountId: accountIdOrAccountId },
- });
- }
- // 如果还是没找到,尝试模糊匹配(dy_开头)
- if (!account && accountIdOrAccountId.startsWith('dy_')) {
- const accounts = await accountRepository.find({
- where: { platform: 'douyin' },
- });
- account = accounts.find(
- (a) =>
- a.accountId?.includes(accountIdOrAccountId.replace('dy_', '')) ||
- a.accountId === accountIdOrAccountId
- );
- }
- if (!account) {
- logger.error(`未找到账号: ${accountIdOrAccountId}`);
- logger.info('可用的抖音账号:');
- const allAccounts = await accountRepository.find({
- where: { platform: 'douyin' },
- });
- allAccounts.forEach((a) => {
- logger.info(
- ` ID=${a.id}, accountId=${a.accountId}, name=${a.accountName}, status=${a.status}`
- );
- });
- process.exit(1);
- }
- logger.info(`找到账号: ID=${account.id}, accountId=${account.accountId}, name=${account.accountName}, status=${account.status}`);
- if (!account.cookieData) {
- logger.error('账号没有 cookie 数据');
- process.exit(1);
- }
- // 解密 Cookie
- let decryptedCookies: string;
- try {
- decryptedCookies = CookieManager.decrypt(account.cookieData);
- logger.info('Cookie 解密成功');
- } catch {
- decryptedCookies = account.cookieData;
- logger.info('使用原始 cookie 数据');
- }
- // 解析 Cookie(支持 JSON 和 \"name=value; name2=value2\" 两种格式)
- let cookieList: { name: string; value: string; domain: string; path: string }[];
- try {
- cookieList = JSON.parse(decryptedCookies);
- logger.info(`从 JSON 解析了 ${cookieList.length} 个 cookie`);
- } catch {
- // 回退解析字符串格式的 cookie(与 WorkService.parseCookieString 保持一致)
- logger.warn('Cookie 不是 JSON,尝试按字符串格式解析...');
- const domain = '.douyin.com';
- const cookies: { name: string; value: string; domain: string; path: string }[] = [];
- const pairs = decryptedCookies.split(';');
- for (const pair of pairs) {
- const trimmed = pair.trim();
- if (!trimmed) continue;
- const eqIndex = trimmed.indexOf('=');
- if (eqIndex === -1) continue;
- const name = trimmed.substring(0, eqIndex).trim();
- const value = trimmed.substring(eqIndex + 1).trim();
- if (!name || !value) continue;
- cookies.push({ name, value, domain, path: '/' });
- }
- cookieList = cookies;
- logger.info(`从字符串解析了 ${cookieList.length} 个 cookie`);
- if (cookieList.length === 0) {
- logger.error('Cookie 字符串解析失败,未能获取任何 cookie');
- logger.info('Cookie 前100字符:', decryptedCookies.substring(0, 100));
- process.exit(1);
- }
- }
- // 调用 fetchAccountInfo
- logger.info('开始获取账号信息和作品列表...');
- logger.info('注意:这可能需要一些时间,请耐心等待...');
-
- let accountInfo;
- try {
- accountInfo = await headlessBrowserService.fetchAccountInfo('douyin', cookieList, {
- onWorksFetchProgress: (info) => {
- logger.info(
- `[进度] 已获取 ${info.totalSoFar} 个作品,总计: ${info.declaredTotal || '?'}, 当前页: ${info.page}`
- );
- },
- });
- } catch (error) {
- logger.error('获取账号信息时出错:', error);
- throw error;
- }
- logger.info('\n=== 账号信息 ===');
- logger.info(`accountId: ${accountInfo.accountId}`);
- logger.info(`accountName: ${accountInfo.accountName}`);
- logger.info(`avatarUrl: ${accountInfo.avatarUrl ? '有' : '无'}`);
- logger.info(`fansCount: ${accountInfo.fansCount}`);
- logger.info(`worksCount: ${accountInfo.worksCount}`);
- logger.info(`worksList.length: ${accountInfo.worksList?.length || 0}`);
- logger.info(`source: ${accountInfo.source || 'unknown'}`);
- logger.info(`pythonAvailable: ${accountInfo.pythonAvailable ? 'yes' : 'no'}`);
-
- if (accountInfo.worksCount > 0 && (!accountInfo.worksList || accountInfo.worksList.length === 0)) {
- logger.warn('\n⚠️ 警告:worksCount > 0 但 worksList 为空!');
- logger.warn('这可能表示:');
- logger.warn('1. API 返回了总数,但实际列表为空');
- logger.warn('2. 作品列表在解析过程中丢失');
- logger.warn('3. 分页逻辑有问题,没有正确获取所有作品');
- }
- if (accountInfo.worksList && accountInfo.worksList.length > 0) {
- logger.info('\n=== 作品列表 ===');
- accountInfo.worksList.forEach((work, idx) => {
- logger.info(
- `${idx + 1}. ${work.title} (videoId: ${work.videoId}, playCount: ${work.playCount}, likeCount: ${work.likeCount})`
- );
- });
- } else {
- logger.warn('\n=== 未获取到作品列表 ===');
- logger.warn(`worksCount: ${accountInfo.worksCount}`);
- logger.warn(`worksList.length: ${accountInfo.worksList?.length || 0}`);
- logger.warn(`source: ${accountInfo.source || 'unknown'}`);
- logger.warn(`pythonAvailable: ${accountInfo.pythonAvailable ? 'yes' : 'no'}`);
-
- if (accountInfo.worksCount > 0 && (!accountInfo.worksList || accountInfo.worksList.length === 0)) {
- logger.error('\n⚠️ 严重问题:worksCount > 0 但 worksList 为空!');
- logger.error('这表示 API 返回了作品总数,但实际列表为空');
- logger.error('可能的原因:');
- logger.error('1. API 分页逻辑有问题');
- logger.error('2. 作品列表在解析过程中丢失');
- logger.error('3. API 返回格式变化');
- } else if (accountInfo.worksCount === 0) {
- logger.warn('\n可能的原因:');
- logger.warn('1. API 调用失败或超时(检查上面的错误日志)');
- logger.warn('2. 账号确实没有作品');
- logger.warn('3. Cookie 已过期(检查是否跳转到登录页)');
- logger.warn('4. 账号权限不足(无法访问作品列表)');
- logger.warn('5. Python API 返回空列表,Playwright 也失败');
- }
- }
- // 检查账号ID匹配
- logger.info('\n=== 账号ID匹配检查 ===');
- logger.info(`数据库 accountId: ${account.accountId}`);
- logger.info(`API 返回 accountId: ${accountInfo.accountId}`);
- if (account.accountId !== accountInfo.accountId) {
- logger.warn('⚠️ 账号ID不匹配!这可能导致同步时无法正确匹配账号');
- logger.warn('建议: 更新数据库中的 accountId 为 API 返回的值');
- } else {
- logger.info('✓ 账号ID匹配');
- }
- process.exit(0);
- } catch (e) {
- logger.error('执行失败:', e);
- process.exit(1);
- }
- }
- void main();
|