Skip to content

Commit eb9148c

Browse files
committed
[add] GitHub actions of GitHub-reward & Lark-GitHub-bot
1 parent dae9266 commit eb9148c

10 files changed

Lines changed: 636 additions & 2 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: 💰 Reward Task
2+
description: Task issue with Reward
3+
title: '[Reward] '
4+
labels:
5+
- reward
6+
body:
7+
- type: textarea
8+
id: description
9+
attributes:
10+
label: Task description
11+
validations:
12+
required: true
13+
14+
- type: dropdown
15+
id: currency
16+
attributes:
17+
label: Reward currency
18+
options:
19+
- 'USD $'
20+
- 'CAD C$'
21+
- 'AUD A$'
22+
- 'GBP £'
23+
- 'EUR €'
24+
- 'CNY ¥'
25+
- 'HKD HK$'
26+
- 'TWD NT$'
27+
- 'SGD S$'
28+
- 'KRW ₩'
29+
- 'JPY ¥'
30+
- 'INR ₹'
31+
- 'UAH ₴'
32+
validations:
33+
required: true
34+
35+
- type: input
36+
id: amount
37+
attributes:
38+
label: Reward amount
39+
validations:
40+
required: true
41+
42+
- type: input
43+
id: payer
44+
attributes:
45+
label: Reward payer
46+
description: GitHub username of the payer (optional, defaults to issue creator)
47+
validations:
48+
required: false

.github/scripts/count-reward.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { $, YAML } from 'npm:zx';
2+
3+
import { Reward } from './type.ts';
4+
5+
$.verbose = true;
6+
7+
const rawTags = await $`git tag --list "reward-*" --format="%(refname:short) %(creatordate:short)"`;
8+
9+
const lastMonth = new Date();
10+
lastMonth.setMonth(lastMonth.getMonth() - 1);
11+
const lastMonthStr = lastMonth.toJSON().slice(0, 7);
12+
13+
const rewardTags = rawTags.stdout
14+
.split('\n')
15+
.filter(line => line.split(/\s+/)[1] >= lastMonthStr)
16+
.map(line => line.split(/\s+/)[0]);
17+
18+
let rawYAML = '';
19+
20+
for (const tag of rewardTags) rawYAML += (await $`git tag -l --format="%(contents)" ${tag}`) + '\n';
21+
22+
if (!rawYAML.trim()) throw new ReferenceError('No reward data is found for the last month.');
23+
24+
const rewards = YAML.parse(rawYAML) as Reward[];
25+
26+
const groupedRewards = Object.groupBy(rewards, ({ payee }) => payee);
27+
28+
const summaryList = Object.entries(groupedRewards).map(([payee, rewards]) => {
29+
const reward = rewards!.reduce(
30+
(acc, { currency, reward }) => {
31+
acc[currency] ??= 0;
32+
acc[currency] += reward;
33+
return acc;
34+
},
35+
{} as Record<string, number>
36+
);
37+
38+
return {
39+
payee,
40+
reward,
41+
accounts: rewards!.map(({ payee: _, ...account }) => account)
42+
};
43+
});
44+
45+
const summaryText = YAML.stringify(summaryList);
46+
47+
console.log(summaryText);
48+
49+
const tagName = `statistic-${new Date().toJSON().slice(0, 7)}`;
50+
51+
await $`git config user.name "github-actions[bot]"`;
52+
await $`git config user.email "github-actions[bot]@users.noreply.github.com"`;
53+
54+
await $`git tag -a ${tagName} $(git rev-parse HEAD) -m ${summaryText}`;
55+
await $`git push origin --tags --no-verify`;
56+
57+
await $`gh release create ${tagName} --notes ${summaryText}`;

.github/scripts/deno.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"nodeModulesDir": "none"
3+
}

.github/scripts/share-reward.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { components } from 'npm:@octokit/openapi-types';
2+
import { $, argv, YAML } from 'npm:zx';
3+
4+
import { Reward } from './type.ts';
5+
6+
$.verbose = true;
7+
8+
const [
9+
repositoryOwner,
10+
repositoryName,
11+
issueNumber,
12+
payer, // GitHub username of the payer (provided by workflow, defaults to issue creator)
13+
currency,
14+
reward
15+
] = argv._;
16+
17+
interface PRMeta {
18+
author: components['schemas']['simple-user'];
19+
assignees: components['schemas']['simple-user'][];
20+
}
21+
22+
const graphqlQuery = `
23+
query($owner: String!, $name: String!, $number: Int!) {
24+
repository(owner: $owner, name: $name) {
25+
issue(number: $number) {
26+
closedByPullRequestsReferences(first: 10) {
27+
nodes {
28+
url
29+
merged
30+
mergeCommit {
31+
oid
32+
}
33+
}
34+
}
35+
}
36+
}
37+
}
38+
`;
39+
const PR_DATA = await $`gh api graphql \
40+
-f query=${graphqlQuery} \
41+
-f owner=${repositoryOwner} \
42+
-f name=${repositoryName} \
43+
-F number=${issueNumber} \
44+
--jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | select(.merged == true) | {url: .url, mergeCommitSha: .mergeCommit.oid}' | head -n 1`;
45+
46+
const prData = PR_DATA.text().trim();
47+
48+
if (!prData) throw new ReferenceError('No merged PR is found for the given issue number.');
49+
50+
const { url: PR_URL, mergeCommitSha } = JSON.parse(prData);
51+
52+
if (!PR_URL || !mergeCommitSha) throw new Error('Missing required fields in PR data');
53+
54+
console.table({ PR_URL, mergeCommitSha });
55+
56+
const { author, assignees }: PRMeta = await (
57+
await $`gh pr view ${PR_URL} --json author,assignees`
58+
).json();
59+
60+
function isBotUser(login: string) {
61+
const lowerLogin = login.toLowerCase();
62+
return (
63+
lowerLogin.includes('copilot') ||
64+
lowerLogin.includes('[bot]') ||
65+
lowerLogin === 'github-actions[bot]' ||
66+
lowerLogin.endsWith('[bot]')
67+
);
68+
}
69+
70+
// Filter out Bot users from the list
71+
const allUsers = [author.login, ...assignees.map(({ login }) => login)];
72+
const users = allUsers.filter(login => !isBotUser(login));
73+
74+
console.log(`All users: ${allUsers.join(', ')}`);
75+
console.log(`Filtered users (excluding bots): ${users.join(', ')}`);
76+
77+
if (!users[0])
78+
throw new ReferenceError(
79+
'No real users found (all users are bots). Skipping reward distribution.'
80+
);
81+
82+
const rewardNumber = parseFloat(reward);
83+
84+
if (isNaN(rewardNumber) || rewardNumber <= 0)
85+
throw new RangeError(
86+
`Reward amount is not a valid number, can not proceed with reward distribution. Received reward value: ${reward}`
87+
);
88+
89+
const averageReward = (rewardNumber / users.length).toFixed(2);
90+
91+
const list: Reward[] = users.map(login => ({
92+
issue: `#${issueNumber}`,
93+
payer: `@${payer}`,
94+
payee: `@${login}`,
95+
currency,
96+
reward: parseFloat(averageReward)
97+
}));
98+
const listText = YAML.stringify(list);
99+
100+
console.log(listText);
101+
102+
await $`git config user.name "github-actions[bot]"`;
103+
await $`git config user.email "github-actions[bot]@users.noreply.github.com"`;
104+
await $`git tag -a "reward-${issueNumber}" ${mergeCommitSha} -m ${listText}`;
105+
await $`git push origin --tags --no-verify`;
106+
107+
const commentBody = `## Reward data
108+
109+
\`\`\`yml
110+
${listText}
111+
\`\`\`
112+
`;
113+
await $`gh issue comment ${issueNumber} --body ${commentBody}`;

0 commit comments

Comments
 (0)