Refactor GitCode daily bot
This commit is contained in:
228
tasks/dailyStar.js
Normal file
228
tasks/dailyStar.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 任务:每日Star
|
||||
*
|
||||
* 逻辑:
|
||||
* 1. 找到"每日Star"任务卡片
|
||||
* 2. 点击 toComplete 打开热门列表
|
||||
* 3. 随机选择一个热门项目
|
||||
* 4. 进入项目详情页
|
||||
* 5. 找到 Star/Starred 按钮
|
||||
* 6. 如果已 Starred → 先取消再重新 Star
|
||||
* 7. 如果未 Star → 直接 Star
|
||||
*
|
||||
* 定位器优化:
|
||||
* - 优先文本匹配(Star / Starred)
|
||||
* - 不依赖固定 nth 索引
|
||||
* - 自动打印页面 title 和 URL
|
||||
* - 找不到按钮抛异常
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const logger = require(path.join(__dirname, '..', 'utils', 'logger'));
|
||||
const {
|
||||
openNewPage,
|
||||
openLinkPage,
|
||||
safeClose,
|
||||
getPageInfo,
|
||||
waitForElements,
|
||||
} = require(path.join(__dirname, '..', 'utils', 'page'));
|
||||
|
||||
const PROJECT_CARD_SELECTORS = [
|
||||
'.hot-selection-list a.recommend-card',
|
||||
'.hot-selection .recommend-card',
|
||||
'a.recommend-card',
|
||||
'a[href*="source_module=home_hot_selection"]',
|
||||
'a[href*="source_module=home_recommend"]',
|
||||
'a[href*="/"][href*="?source_module="]',
|
||||
];
|
||||
|
||||
/**
|
||||
* 执行每日Star任务
|
||||
* @param {import('playwright').Page} page - 主页面
|
||||
*/
|
||||
async function dailyStar(page) {
|
||||
logger.info('👉 执行每日Star');
|
||||
|
||||
// 从主页面找到"每日Star"任务卡片
|
||||
const tasks = await page.locator('.novice-task-item-box').all();
|
||||
const starTask = await findTaskByTitle(tasks, '每日Star');
|
||||
|
||||
if (!starTask) {
|
||||
throw new Error('未找到"每日Star"任务卡片');
|
||||
}
|
||||
|
||||
// ── 第一步:打开热门项目列表页 ──
|
||||
const listPage = await openNewPage(
|
||||
page.context(),
|
||||
() => starTask.locator('.toComplete').click()
|
||||
);
|
||||
|
||||
try {
|
||||
const cards = await findProjectCards(listPage);
|
||||
const count = cards.length;
|
||||
logger.info(`热门项目数量: ${count}`);
|
||||
|
||||
if (count === 0) {
|
||||
throw new Error('没有找到热门项目');
|
||||
}
|
||||
|
||||
// 随机选择一个项目
|
||||
const index = Math.floor(Math.random() * count);
|
||||
logger.info(`随机选择项目索引: ${index}`);
|
||||
|
||||
// ── 第二步:打开项目详情页 ──
|
||||
const projectPage = await openLinkPage(
|
||||
listPage.context(),
|
||||
cards[index],
|
||||
{ label: 'Star 项目详情页' }
|
||||
);
|
||||
|
||||
try {
|
||||
// 打印页面信息
|
||||
const info = await getPageInfo(projectPage);
|
||||
logger.info(`项目页面: ${info.title}`);
|
||||
logger.info(`项目URL: ${info.url}`);
|
||||
|
||||
// ── 第三步:找到 Star/Starred 按钮 ──
|
||||
await doStarOperation(projectPage);
|
||||
} finally {
|
||||
await safeClose(projectPage);
|
||||
}
|
||||
} finally {
|
||||
await safeClose(listPage);
|
||||
}
|
||||
}
|
||||
|
||||
async function findProjectCards(page) {
|
||||
const cards = await waitForElements(page, PROJECT_CARD_SELECTORS, {
|
||||
interval: 1000,
|
||||
maxRetries: 15,
|
||||
minCount: 1,
|
||||
});
|
||||
|
||||
if (cards.length > 0) {
|
||||
return cards;
|
||||
}
|
||||
|
||||
const info = await getPageInfo(page);
|
||||
throw new Error(
|
||||
`未找到热门项目卡片,页面结构可能已变化。页面标题: ${info.title}, 页面URL: ${info.url}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 Star/Unstar 操作
|
||||
*
|
||||
* 定位策略:
|
||||
* 1. 在 #repo-header-tab 区域内查找包含 "Star" 或 "Starred" 文本的按钮
|
||||
* 2. 优先匹配 .status-btn 类
|
||||
* 3. 使用文本过滤而非 nth 索引
|
||||
*
|
||||
* @param {import('playwright').Page} page
|
||||
*/
|
||||
async function doStarOperation(page) {
|
||||
// 等待页面加载
|
||||
await page.waitForLoadState('domcontentloaded', { timeout: 30000 }).catch(() => {});
|
||||
logger.info('项目详情页加载完成');
|
||||
|
||||
// 定位 Star 按钮区域
|
||||
const headerTab = page.locator('#repo-header-tab');
|
||||
await headerTab.waitFor({ state: 'attached', timeout: 15000 }).catch(() => {
|
||||
// 如果 #repo-header-tab 不存在,尝试更通用的定位
|
||||
logger.warn('#repo-header-tab 未找到,尝试更通用的定位方式');
|
||||
});
|
||||
|
||||
// 查找 Star/Starred 按钮
|
||||
// 策略1: #repo-header-tab 下的 .status-btn
|
||||
let starBtn = page.locator('#repo-header-tab .status-btn').filter({
|
||||
hasText: /Star/i,
|
||||
});
|
||||
|
||||
let btnCount = await starBtn.count();
|
||||
logger.info(`#repo-header-tab .status-btn 含 Star 文本的按钮数量: ${btnCount}`);
|
||||
|
||||
// 策略2: 如果策略1没找到,全局搜索含 Star 的按钮
|
||||
if (btnCount === 0) {
|
||||
logger.warn('未在 #repo-header-tab 下找到 Star 按钮,全局搜索...');
|
||||
|
||||
starBtn = page.locator('button, a, .status-btn, .btn').filter({
|
||||
hasText: /Star/i,
|
||||
});
|
||||
|
||||
btnCount = await starBtn.count();
|
||||
logger.info(`全局搜索含 Star 文本的按钮数量: ${btnCount}`);
|
||||
}
|
||||
|
||||
// 策略3: 包含 ⭐ 或 svg star 图标
|
||||
if (btnCount === 0) {
|
||||
logger.warn('按文本未找到,尝试通过 aria-label 匹配...');
|
||||
|
||||
starBtn = page.locator('[aria-label*="star" i], [aria-label*="Star" i]');
|
||||
btnCount = await starBtn.count();
|
||||
logger.info(`通过 aria-label 找到的按钮数量: ${btnCount}`);
|
||||
}
|
||||
|
||||
if (btnCount === 0) {
|
||||
// 最终失败:截图并抛异常
|
||||
const pageInfo = await getPageInfo(page);
|
||||
throw new Error(
|
||||
`Star按钮不存在\n` +
|
||||
`页面标题: ${pageInfo.title}\n` +
|
||||
`页面URL: ${pageInfo.url}\n` +
|
||||
`共尝试了3种定位策略均未找到 Star/Starred 按钮`
|
||||
);
|
||||
}
|
||||
|
||||
// 取第一个匹配的按钮
|
||||
const btn = starBtn.nth(btnCount > 1 ? 1 : 0);
|
||||
const btnText = await btn.innerText().catch(() => '(获取文本失败)');
|
||||
logger.info(`找到按钮,文本内容: "${btnText}"`);
|
||||
|
||||
// 判断当前状态
|
||||
const isStarred = btnText.toLowerCase().includes('starred');
|
||||
|
||||
if (isStarred) {
|
||||
// 已 Starred → 先取消
|
||||
logger.info('当前状态: 已 Starred,先取消');
|
||||
await btn.click();
|
||||
|
||||
// 等待按钮文本变为 Star
|
||||
await page.waitForTimeout(800);
|
||||
const newText = await btn.innerText().catch(() => '');
|
||||
logger.info(`取消后按钮文本: "${newText}"`);
|
||||
}
|
||||
|
||||
// 点击 Star
|
||||
logger.info('点击 Star 按钮');
|
||||
await btn.click();
|
||||
|
||||
// 确认操作成功(等待按钮文本变化)
|
||||
await page.waitForTimeout(1000);
|
||||
const finalText = await btn.innerText().catch(() => '');
|
||||
logger.info(`Star操作完成,当前按钮文本: "${finalText}"`);
|
||||
|
||||
if (finalText.toLowerCase().includes('starred')) {
|
||||
logger.info('✅ Star 操作成功确认');
|
||||
} else {
|
||||
logger.warn('Star 操作后按钮状态未确认,但操作已执行');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标题查找任务卡片
|
||||
*/
|
||||
async function findTaskByTitle(tasks, title) {
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
const text = await task.locator('.task-card-title__label-text').innerText();
|
||||
if (text.includes(title)) {
|
||||
return task;
|
||||
}
|
||||
} catch (_) {
|
||||
// 跳过
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = dailyStar;
|
||||
Reference in New Issue
Block a user