Refactor GitCode daily bot

This commit is contained in:
2026-07-06 09:34:39 +08:00
parent 120ae7d3f9
commit 77a07aae0a
19 changed files with 2251 additions and 533 deletions

185
utils/login.js Normal file
View File

@@ -0,0 +1,185 @@
/**
* 登录模块
* 核心函数 ensureLogin(page, context)
*
* 流程:
* 1. 访问 积分中心页面
* 2. 检测 .g-user-avatar 是否存在
* 3. 如果已登录 → 返回 true
* 4. 如果未登录 → 执行账号密码登录 → 保存 state.json
* 5. 登录失败 → 抛出异常,阻止后续任务
*
* 【日志规范】每个 await 前后都有日志,定位卡顿时精确到行
*/
const config = require('../config');
const logger = require('./logger');
const { saveState } = require('./browser');
const { safeGoto } = require('./page');
/**
* 确保已登录,若未登录则执行登录流程
* @param {import('playwright').Page} page
* @param {import('playwright').BrowserContext} context
* @returns {Promise<boolean>} 登录是否成功
* @throws {Error} 登录失败时抛出异常
*/
async function ensureLogin(page, context) {
logger.divider();
logger.info('===== 开始检查登录状态 =====');
// ── 步骤1: 访问积分中心页面 ──
logger.info('[step 1/8] 正在访问积分中心页面...');
const pageReady = await safeGoto(page, config.urls.points, {
label: '积分中心',
timeout: config.navigationTimeout,
waitFor: [
'.g-user-avatar',
'text=密码登录',
],
});
if (!pageReady) {
logger.warn('[step 1/8] ⚠️ 关键元素未全部出现,尝试继续...');
} else {
logger.info('[step 1/8] ✅ 积分中心页面加载完成');
}
// ── 步骤2: 打印当前页面信息 ──
logger.info('[step 2/8] 获取当前页面信息...');
try {
const currentUrl = page.url();
const pageTitle = await page.title();
logger.info(` → 当前URL: ${currentUrl}`);
logger.info(` → 页面标题: ${pageTitle}`);
} catch (err) {
logger.warn(` → 获取页面信息失败: ${err.message}`);
}
// ── 步骤3: 检测登录状态 ──
logger.info('[step 3/8] 检测登录状态(查找 .g-user-avatar...');
let isLoggedIn = false;
try {
const avatarLocator = page.locator('.g-user-avatar');
logger.info(' → 调用 isVisible()...');
isLoggedIn = await avatarLocator.isVisible();
logger.info(` → isVisible() 返回: ${isLoggedIn}`);
} catch (err) {
logger.warn(` → 检测登录状态时出错: ${err.message}`);
isLoggedIn = false;
}
if (isLoggedIn) {
logger.info('✅ 已登录,无需执行登录流程');
return true;
}
// ── 步骤4: 未登录,开始登录 ──
logger.info('❌ 未登录,开始执行登录流程');
logger.info('[step 4/8] 等待页面稳定 (waitForTimeout 1000ms)...');
await page.waitForTimeout(1000);
try {
// ── 步骤4a: 点击密码登录 ──
logger.info('[step 4a] 点击"密码登录"标签...');
const pwdLoginBtn = page.getByText('密码登录');
logger.info(' → 等待密码登录按钮可见...');
await pwdLoginBtn.waitFor({ state: 'visible', timeout: 10000 });
logger.info(' → 点击密码登录按钮...');
await pwdLoginBtn.click();
logger.info('✅ 已切换到密码登录方式');
// ── 步骤4b: 填写用户名 ──
logger.info('[step 4b] 填写用户名...');
const usernameInput = page.getByPlaceholder('请填写手机号/用户名/邮箱');
logger.info(' → 等待输入框可见...');
await usernameInput.waitFor({ state: 'visible', timeout: 10000 });
logger.info(` → 输入用户名: ${config.login.username}`);
await usernameInput.fill(config.login.username);
logger.info('✅ 用户名已填写');
// ── 步骤4c: 填写密码 ──
logger.info('[step 4c] 填写密码...');
const passwordInput = page.getByPlaceholder('请填写密码');
logger.info(' → 等待密码输入框可见...');
await passwordInput.waitFor({ state: 'visible', timeout: 10000 });
logger.info(' → 输入密码 (已隐藏)');
await passwordInput.fill(config.login.password);
logger.info('✅ 密码已填写');
// ── 步骤4d: 勾选协议 ──
logger.info('[step 4d] 勾选同意协议...');
const agree1 = page.getByText('登录或注册完成代表你同意');
logger.info(' → 点击第一个勾选项...');
await agree1.click();
const agree2 = page.getByText('我同意将账号、组织、仓库等信息提供给AtomGit进行 ');
logger.info(' → 点击第二个勾选项...');
await agree2.click();
logger.info('✅ 协议已勾选');
// ── 步骤4e: 点击登录按钮 ──
logger.info('[step 4e] 点击登录按钮...');
const loginBtn = page.locator('button:has-text("登 录")');
logger.info(' → 等待登录按钮可见...');
await loginBtn.waitFor({ state: 'visible', timeout: 10000 });
logger.info(' → 点击登录按钮...');
await loginBtn.click();
logger.info('✅ 登录按钮已点击,等待跳转...');
// ── 步骤4f: 等待跳转 ──
logger.info('[step 4f] 等待页面跳转到积分中心...');
logger.info(` → waitForURL('**/setting/points'), timeout: ${config.navigationTimeout}ms`);
await page.waitForURL('**/setting/points', {
timeout: config.navigationTimeout,
});
logger.info('✅ 页面已跳转到积分中心');
// ── 步骤4g: 验证登录成功 ──
logger.info('[step 4g] 验证登录成功(查找 .g-user-avatar...');
logger.info(` → waitForSelector('.g-user-avatar'), timeout: ${config.navigationTimeout}ms`);
await page.waitForSelector('.g-user-avatar', {
timeout: config.navigationTimeout,
});
logger.info('✅ 登录成功,用户头像已出现');
// ── 步骤5: 保存状态 ──
logger.info('[step 5/8] 保存登录状态到 state.json...');
await saveState(context);
logger.info('✅ state.json 已保存');
return true;
} catch (err) {
// 登录失败:截图并抛异常
logger.error(`❌ 登录流程异常: ${err.message}`);
const screenshotPath = await takeScreenshot(page, 'login-failed');
logger.info(`截图已保存: ${screenshotPath}`);
throw new Error(`登录失败,无法继续执行任务: ${err.message}`);
}
}
/**
* 页面截图辅助
* @param {import('playwright').Page} page
* @param {string} label 截图标签
* @returns {Promise<string>} 截图文件路径
*/
async function takeScreenshot(page, label) {
const fs = require('fs');
const path = require('path');
const config = require('../config');
const dir = path.resolve(config.dirs.screenshots);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const filename = `error-${label}-${Date.now()}.png`;
const filepath = path.join(dir, filename);
logger.info(' → 正在截图...');
await page.screenshot({ path: filepath, fullPage: true }).catch(() => { });
return filepath;
}
module.exports = {
ensureLogin,
};