|
|
@@ -84,7 +84,7 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async checkQRCodeStatus(qrcodeKey: string): Promise<LoginStatusResult> {
|
|
|
+ async checkQRCodeStatus(_qrcodeKey: string): Promise<LoginStatusResult> {
|
|
|
try {
|
|
|
if (!this.page) {
|
|
|
return { status: 'expired', message: '二维码已过期' };
|
|
|
@@ -98,7 +98,7 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
await this.closeBrowser();
|
|
|
|
|
|
return {
|
|
|
- status: 'success',
|
|
|
+ status: 'confirmed',
|
|
|
message: '登录成功',
|
|
|
cookies,
|
|
|
};
|
|
|
@@ -110,7 +110,7 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
return { status: 'scanned', message: '需要手机验证' };
|
|
|
}
|
|
|
|
|
|
- return { status: 'pending', message: '等待扫码' };
|
|
|
+ return { status: 'waiting', message: '等待扫码' };
|
|
|
} catch (error) {
|
|
|
logger.error('Baijiahao checkQRCodeStatus error:', error);
|
|
|
return { status: 'expired', message: '检查状态失败' };
|
|
|
@@ -398,49 +398,60 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
|
|
|
onProgress?.(10, '正在上传视频...');
|
|
|
|
|
|
+ // 获取所有 frame(百家号上传 UI 在 iframe 中)
|
|
|
+ const frames = this.page.frames();
|
|
|
+ logger.info(`[Baijiahao Publish] Page has ${frames.length} frames`);
|
|
|
+ for (const frame of frames) {
|
|
|
+ logger.info(`[Baijiahao Publish] Frame: ${frame.url()}`);
|
|
|
+ }
|
|
|
+
|
|
|
// 上传视频 - 优先使用 AI 截图分析找到上传入口
|
|
|
let uploadTriggered = false;
|
|
|
|
|
|
- // 方法1: AI 截图分析找到上传入口
|
|
|
- logger.info('[Baijiahao Publish] Using AI to find upload entry...');
|
|
|
- try {
|
|
|
- const screenshot = await this.screenshotBase64();
|
|
|
- const guide = await aiService.getPageOperationGuide(screenshot, 'baijiahao', '找到视频上传入口并点击上传按钮');
|
|
|
- logger.info(`[Baijiahao Publish] AI analysis result:`, guide);
|
|
|
-
|
|
|
- if (guide.hasAction && guide.targetSelector) {
|
|
|
- logger.info(`[Baijiahao Publish] AI suggested selector: ${guide.targetSelector}`);
|
|
|
+ // 方法1: 直接设置 file input(遍历所有 frame,最可靠的方式)
|
|
|
+ if (!uploadTriggered) {
|
|
|
+ logger.info('[Baijiahao Publish] Trying file input in all frames...');
|
|
|
+ for (const frame of frames) {
|
|
|
+ if (uploadTriggered) break;
|
|
|
try {
|
|
|
- const [fileChooser] = await Promise.all([
|
|
|
- this.page.waitForEvent('filechooser', { timeout: 10000 }),
|
|
|
- this.page.click(guide.targetSelector),
|
|
|
- ]);
|
|
|
- await fileChooser.setFiles(params.videoPath);
|
|
|
- uploadTriggered = true;
|
|
|
- logger.info('[Baijiahao Publish] Upload triggered via AI selector');
|
|
|
+ const fileInputs = await frame.$$('input[type="file"]');
|
|
|
+ logger.info(`[Baijiahao Publish] Frame ${frame.url()} has ${fileInputs.length} file inputs`);
|
|
|
+ for (const fileInput of fileInputs) {
|
|
|
+ try {
|
|
|
+ const accept = await fileInput.getAttribute('accept') || '';
|
|
|
+ // 优先选择接受视频的 file input
|
|
|
+ if (accept && !accept.includes('video') && !accept.includes('*')) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ await fileInput.setInputFiles(params.videoPath);
|
|
|
+ uploadTriggered = true;
|
|
|
+ logger.info(`[Baijiahao Publish] Upload triggered via file input in frame: ${frame.url()}`);
|
|
|
+ break;
|
|
|
+ } catch (e) {
|
|
|
+ logger.warn(`[Baijiahao Publish] File input setFiles failed: ${e}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
} catch (e) {
|
|
|
- logger.warn(`[Baijiahao Publish] AI selector click failed: ${e}`);
|
|
|
+ logger.warn(`[Baijiahao Publish] Frame ${frame.url()} file input search failed: ${e}`);
|
|
|
}
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- logger.warn(`[Baijiahao Publish] AI analysis failed: ${e}`);
|
|
|
}
|
|
|
|
|
|
- // 方法2: 尝试点击常见的上传区域触发 file chooser
|
|
|
+ // 方法2: 尝试点击常见的上传区域触发 file chooser(遍历所有 frame)
|
|
|
if (!uploadTriggered) {
|
|
|
- logger.info('[Baijiahao Publish] Trying common upload selectors...');
|
|
|
+ logger.info('[Baijiahao Publish] Trying common upload selectors in all frames...');
|
|
|
const uploadSelectors = [
|
|
|
// 百家号常见上传区域选择器 - 虚线框拖拽上传区域
|
|
|
- '[class*="drag"]',
|
|
|
- '[class*="drop"]',
|
|
|
+ '[class*="upload-box"]',
|
|
|
+ '[class*="drag-upload"]',
|
|
|
'[class*="upload-area"]',
|
|
|
'[class*="upload-zone"]',
|
|
|
'[class*="upload-wrapper"]',
|
|
|
- '[class*="upload-box"]',
|
|
|
'[class*="upload-btn"]',
|
|
|
'[class*="upload-video"]',
|
|
|
'[class*="video-upload"]',
|
|
|
- '[class*="drag-upload"]',
|
|
|
+ '[class*="drag"]',
|
|
|
+ '[class*="drop"]',
|
|
|
'.upload-container',
|
|
|
'.video-uploader',
|
|
|
'div[class*="uploader"]',
|
|
|
@@ -449,7 +460,6 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
'div:has-text("拖动入此区域")',
|
|
|
'span:has-text("点击上传")',
|
|
|
// 带虚线边框的容器(通常是拖拽上传区域)
|
|
|
- '[style*="dashed"]',
|
|
|
'[class*="dashed"]',
|
|
|
'[class*="border-dashed"]',
|
|
|
// 其他常见选择器
|
|
|
@@ -461,44 +471,61 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
'[class*="trigger"]',
|
|
|
'[class*="picker"]',
|
|
|
];
|
|
|
-
|
|
|
- for (const selector of uploadSelectors) {
|
|
|
+
|
|
|
+ for (const frame of frames) {
|
|
|
if (uploadTriggered) break;
|
|
|
- try {
|
|
|
- const element = this.page.locator(selector).first();
|
|
|
- if (await element.count() > 0 && await element.isVisible()) {
|
|
|
- logger.info(`[Baijiahao Publish] Trying selector: ${selector}`);
|
|
|
- const [fileChooser] = await Promise.all([
|
|
|
- this.page.waitForEvent('filechooser', { timeout: 5000 }),
|
|
|
- element.click(),
|
|
|
- ]);
|
|
|
- await fileChooser.setFiles(params.videoPath);
|
|
|
- uploadTriggered = true;
|
|
|
- logger.info(`[Baijiahao Publish] Upload triggered via selector: ${selector}`);
|
|
|
+ for (const selector of uploadSelectors) {
|
|
|
+ if (uploadTriggered) break;
|
|
|
+ try {
|
|
|
+ const element = frame.locator(selector).first();
|
|
|
+ if (await element.count() > 0 && await element.isVisible()) {
|
|
|
+ logger.info(`[Baijiahao Publish] Trying selector: ${selector} in frame: ${frame.url()}`);
|
|
|
+ const [fileChooser] = await Promise.all([
|
|
|
+ this.page.waitForEvent('filechooser', { timeout: 5000 }),
|
|
|
+ element.click(),
|
|
|
+ ]);
|
|
|
+ await fileChooser.setFiles(params.videoPath);
|
|
|
+ uploadTriggered = true;
|
|
|
+ logger.info(`[Baijiahao Publish] Upload triggered via selector: ${selector} in frame: ${frame.url()}`);
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ // 继续尝试下一个选择器
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- // 继续尝试下一个选择器
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 方法3: 直接设置 file input
|
|
|
+ // 方法3: AI 截图分析找到上传入口
|
|
|
if (!uploadTriggered) {
|
|
|
- logger.info('[Baijiahao Publish] Trying file input method...');
|
|
|
- const fileInputs = await this.page.$$('input[type="file"]');
|
|
|
- logger.info(`[Baijiahao Publish] Found ${fileInputs.length} file inputs`);
|
|
|
- for (const fileInput of fileInputs) {
|
|
|
- try {
|
|
|
- const accept = await fileInput.getAttribute('accept');
|
|
|
- if (!accept || accept.includes('video') || accept.includes('*')) {
|
|
|
- await fileInput.setInputFiles(params.videoPath);
|
|
|
- uploadTriggered = true;
|
|
|
- logger.info('[Baijiahao Publish] Upload triggered via file input');
|
|
|
- break;
|
|
|
+ logger.info('[Baijiahao Publish] Using AI to find upload entry...');
|
|
|
+ try {
|
|
|
+ const screenshot = await this.screenshotBase64();
|
|
|
+ const guide = await aiService.getPageOperationGuide(screenshot, 'baijiahao', '找到视频上传入口并点击上传按钮');
|
|
|
+ logger.info(`[Baijiahao Publish] AI analysis result:`, guide);
|
|
|
+
|
|
|
+ if (guide.hasAction && guide.targetSelector) {
|
|
|
+ logger.info(`[Baijiahao Publish] AI suggested selector: ${guide.targetSelector}`);
|
|
|
+ // 在所有 frame 中查找 AI 返回的选择器
|
|
|
+ for (const frame of frames) {
|
|
|
+ if (uploadTriggered) break;
|
|
|
+ try {
|
|
|
+ const element = frame.locator(guide.targetSelector).first();
|
|
|
+ if (await element.count() > 0 && await element.isVisible()) {
|
|
|
+ const [fileChooser] = await Promise.all([
|
|
|
+ this.page.waitForEvent('filechooser', { timeout: 10000 }),
|
|
|
+ element.click(),
|
|
|
+ ]);
|
|
|
+ await fileChooser.setFiles(params.videoPath);
|
|
|
+ uploadTriggered = true;
|
|
|
+ logger.info(`[Baijiahao Publish] Upload triggered via AI selector in frame: ${frame.url()}`);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ logger.warn(`[Baijiahao Publish] AI selector click failed in frame ${frame.url()}: ${e}`);
|
|
|
+ }
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- logger.warn(`[Baijiahao Publish] File input method failed: ${e}`);
|
|
|
}
|
|
|
+ } catch (e) {
|
|
|
+ logger.warn(`[Baijiahao Publish] AI analysis failed: ${e}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -509,7 +536,7 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
const screenshot = await this.screenshotBase64();
|
|
|
const guide = await aiService.getPageOperationGuide(screenshot, 'baijiahao', '请找到页面中央的虚线框上传区域(有"点击上传或将文件拖动入此区域"文字的区域),返回该区域的中心坐标');
|
|
|
logger.info(`[Baijiahao Publish] AI position analysis:`, guide);
|
|
|
-
|
|
|
+
|
|
|
if (guide.hasAction && guide.targetPosition) {
|
|
|
const { x, y } = guide.targetPosition;
|
|
|
logger.info(`[Baijiahao Publish] Clicking at position: ${x}, ${y}`);
|
|
|
@@ -773,17 +800,17 @@ export class BaijiahaoAdapter extends BasePlatformAdapter {
|
|
|
// Playwright 备用方案结束
|
|
|
}
|
|
|
|
|
|
- async getComments(cookies: string, videoId: string): Promise<CommentData[]> {
|
|
|
+ async getComments(_cookies: string, _videoId: string): Promise<CommentData[]> {
|
|
|
logger.warn('[Baijiahao] getComments not implemented');
|
|
|
return [];
|
|
|
}
|
|
|
|
|
|
- async replyComment(cookies: string, commentId: string, content: string): Promise<boolean> {
|
|
|
+ async replyComment(_cookies: string, _commentId: string, _content: string): Promise<boolean> {
|
|
|
logger.warn('[Baijiahao] replyComment not implemented');
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- async getAnalytics(cookies: string, dateRange: DateRange): Promise<AnalyticsData> {
|
|
|
+ async getAnalytics(_cookies: string, _dateRange: DateRange): Promise<AnalyticsData> {
|
|
|
logger.warn('[Baijiahao] getAnalytics not implemented');
|
|
|
return {
|
|
|
fansCount: 0,
|