GitHub 成就工具
GitHub 个人主页中有一个“Achievements”板块。
参考资料
- Personalizing your profile - GitHub Docs
- drknzz/GitHub-Achievements: 🔥 A Complete List of GitHub Profile Badges and Achievements 🔥
自动化脚本(Playwright)
npm install playwright
npx playwright install
Login 脚本
login.js
const { chromium } = require("playwright");
async function loginWithAccount(accountName) {
const storageFile = `${accountName}.json`;
console.log(`正在为账户 ${accountName} 启动登录流程...`);
const browser = await chromium.launch({ headless: false });
let context;
try {
context = await browser.newContext({ storageState: storageFile });
console.log(`加载 ${accountName} 的已有登录缓存...`);
} catch (error) {
context = await browser.newContext();
console.log(`创建 ${accountName} 的新登录会话...`);
}
const page = await context.newPage();
await page.goto("https://github.com");
console.log(`请在浏览器中操作,60秒后自动保存 ${accountName} 的登录状态`);
await page.waitForTimeout(60000);
await context.storageState({ path: storageFile });
console.log(`${accountName} 的登录状态已自动保存到 ${storageFile}`);
await browser.close();
}
// 主程序
(async () => {
// 从命令行参数获取账户名,默认为 account1
const accountName = process.argv[2] || "account1";
// 验证账户名格式
if (!accountName.match(/^account\d+$/)) {
console.error("错误:账户名格式应为 accountN(如 account1, account2)");
process.exit(1);
}
try {
await loginWithAccount(accountName);
} catch (error) {
console.error("登录过程中出现错误:", error);
process.exit(1);
}
})();
Pull Shark
pull_shark.js
const { chromium } = require("playwright");
const repoUrl = "https://github.com/lailai0916/test";
async function autoCreatePullRequest(page, k) {
console.log("打开仓库");
await page.goto(repoUrl, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(1000);
console.log("点击 Switch branches/tags");
await page.waitForSelector("#branch-picker-repos-header-ref-selector", {
timeout: 5000,
});
await page.click("#branch-picker-repos-header-ref-selector");
await page.waitForTimeout(1000);
const branchName = `patch-${String(k).padStart(3, "0")}`;
console.log("输入分支名");
await page.waitForSelector(
'input[placeholder="Find or create a branch..."]',
{ timeout: 5000 }
);
await page.fill(
'input[placeholder="Find or create a branch..."]',
branchName
);
await page.waitForTimeout(1000);
console.log("点击创建分支按钮");
await page.waitForSelector(
`button:has-text("Create branch ${branchName} from main")`,
{ timeout: 5000 }
);
await page.click(`button:has-text("Create branch ${branchName} from main")`);
await page.waitForTimeout(3000);
await page.goto(`${repoUrl}/edit/${branchName}/README.md`, {
waitUntil: "domcontentloaded",
});
await page.waitForSelector('.cm-content[role="textbox"]', { timeout: 5000 });
await page.click('.cm-content[role="textbox"]');
await page.waitForTimeout(1000);
console.log("输入内容到编辑框");
await page.keyboard.press("Meta+A");
await page.keyboard.press("Backspace");
await page.keyboard.type(`t-${String(k).padStart(3, "0")}`);
await page.waitForTimeout(1000);
console.log("点击 Commit changes...");
await page.waitForSelector('button:has-text("Commit changes...")', {
timeout: 5000,
});
await page.click('button:has-text("Commit changes...")');
await page.waitForTimeout(3000);
console.log("点击 Commit changes");
await page.waitForSelector(
'div[role="dialog"] >> button:has-text("Commit changes")',
{ timeout: 5000 }
);
const confirmButton = page.locator(
'div[role="dialog"] >> button:has-text("Commit changes")'
);
await confirmButton.click();
await page.waitForTimeout(3000);
await page.goto(`${repoUrl}/compare/${branchName}?expand=1`, {
waitUntil: "domcontentloaded",
});
await page.waitForTimeout(1000);
const createPRButton = page.locator("button.hx_create-pr-button");
await createPRButton.waitFor({ timeout: 5000 });
await page.waitForTimeout(1000);
console.log("点击 Create pull request");
await createPRButton.click();
await page.waitForTimeout(3000);
await page.reload({ waitUntil: "domcontentloaded" });
await page.waitForTimeout(1000);
const mergeButton = page.locator('button:has-text("Merge pull request")');
await mergeButton.waitFor({ timeout: 5000 });
console.log("点击 Merge pull request");
await mergeButton.click();
await page.waitForTimeout(1000);
const confirmMergeButton = page.locator('button:has-text("Confirm merge")');
await confirmMergeButton.waitFor({ timeout: 5000 });
console.log("点击 Confirm merge");
await confirmMergeButton.click();
await page.waitForTimeout(1000);
await page.goto(repoUrl, { waitUntil: "domcontentloaded" });
console.log(`分支 ${branchName} 已完成合并`);
}
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({ storageState: "account1.json" });
const page = await context.newPage();
for (let i = 1; i < 100; i++) {
await autoCreatePullRequest(page, i);
await page.waitForTimeout(3000 + Math.random() * 2000);
}
await browser.close();
})();
Galaxy Brain
galaxy_brain.js
const { chromium } = require("playwright");
const repoUrl = "https://github.com/lailai0916/test";
async function autoCreateDiscussionAnswer(page, k) {
const newDiscussionUrl = `${repoUrl}/discussions/new?category=q-a`;
console.log("打开创建新讨论页面");
await page.goto(newDiscussionUrl, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(2000);
const discussionTitle = `t-${String(k).padStart(3, "0")}`;
console.log("输入讨论标题");
await page.waitForSelector("#js-discussion-title", { timeout: 5000 });
await page.fill("#js-discussion-title", discussionTitle);
await page.waitForTimeout(1000);
const discussionBody = `d-${String(k).padStart(3, "0")}`;
console.log("输入讨论内容");
await page.waitForSelector("#discussion_body", { timeout: 5000 });
await page.fill("#discussion_body", discussionBody);
await page.waitForTimeout(1000);
console.log("点击 Start discussion");
await page.waitForSelector('button:has-text("Start discussion")', {
timeout: 5000,
});
await page.click('button:has-text("Start discussion")');
await page.waitForTimeout(3000);
// 获取创建的讨论URL
const discussionUrl = page.url();
console.log(`讨论已创建,URL: ${discussionUrl}`);
// TODO: 添加回答讨论的逻辑
console.log(
`执行第 ${k} 次 Galaxy Brain 操作 - 标题: ${discussionTitle}, 内容: ${discussionBody}`
);
await page.waitForTimeout(2000);
console.log(`第 ${k} 次 Galaxy Brain 操作完成`);
return discussionUrl;
}
async function autoAnswerDiscussion(page, discussionUrl, k) {
console.log("用account1打开讨论URL");
await page.goto(discussionUrl, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(2000);
// 在回复输入框中输入内容
const answerContent = `a-${String(k).padStart(3, "0")}`;
console.log(`输入回复内容: ${answerContent}`);
await page.waitForSelector("#new_comment_field", { timeout: 5000 });
await page.fill("#new_comment_field", answerContent);
await page.waitForTimeout(1000);
// 点击发送按钮
console.log("点击 Comment 按钮");
await page.waitForSelector('button:has-text("Comment")', { timeout: 5000 });
await page.click('button:has-text("Comment")');
await page.waitForTimeout(2000);
console.log(`第 ${k} 次回复操作完成`);
}
(async () => {
const browser = await chromium.launch({ headless: false });
// 创建account2的上下文用于创建讨论
const context2 = await browser.newContext({ storageState: "account2.json" });
const page2 = await context2.newPage();
// 创建account1的上下文用于访问讨论
const context1 = await browser.newContext({ storageState: "account1.json" });
const page1 = await context1.newPage();
for (let i = 30; i < 100; i++) {
// 用account2创建讨论
const discussionUrl = await autoCreateDiscussionAnswer(page2, i);
// 用account1回复讨论
await autoAnswerDiscussion(page1, discussionUrl, i);
// 用account2重新打开讨论页面
console.log("用account2重新打开讨论页面");
await page2.goto(discussionUrl, { waitUntil: "domcontentloaded" });
await page2.waitForTimeout(2000);
// 点击标记答案按钮
console.log("点击标记答案按钮");
await page2.waitForSelector('button:has-text("Mark as answer")', {
timeout: 5000,
});
await page2.click('button:has-text("Mark as answer")');
await page2.waitForTimeout(2000);
await page1.waitForTimeout(3000 + Math.random() * 2000);
}
await browser.close();
})();