Skip to main content

GitHub 成就工具

GitHub 个人主页中有一个“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 }
);
await page.waitForTimeout(3000);
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 });
await page.waitForTimeout(1000);

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}`);

// 添加回答讨论的逻辑
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();
})();