Skip to content

Commit ba15890

Browse files
committed
bounties workflow
1 parent 5f61cc5 commit ba15890

File tree

1 file changed

+93
-0
lines changed

1 file changed

+93
-0
lines changed

.github/workflows/bounty.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Bounty detector
2+
3+
on:
4+
issues:
5+
types: [labeled]
6+
7+
permissions:
8+
issues: write
9+
pull-requests: read
10+
11+
jobs:
12+
notify:
13+
runs-on: ubuntu-latest
14+
if: startsWith(github.event.label.name, 'diff:')
15+
steps:
16+
- name: Comment bounty info
17+
uses: actions/github-script@v7
18+
env:
19+
FORUM_URL: "https://hub.jmonkeyengine.org/t/proposal-experimental-bounty-program-in-jme/49385"
20+
RESERVE_HOURS: "48"
21+
with:
22+
script: |
23+
const issue = context.payload.issue;
24+
const label = context.payload.label?.name ?? "";
25+
const actor = context.actor; // person who applied the label
26+
const issueOwner = issue.user?.login; // original issue author
27+
28+
if (!issueOwner) return;
29+
30+
const forumUrl = process.env.FORUM_URL || "TBD";
31+
const reserveHours = Number(process.env.RESERVE_HOURS || "48");
32+
33+
// "previous contributor" = has at least one merged PR authored in this repo
34+
const repoFull = `${context.repo.owner}/${context.repo.repo}`;
35+
const q = `repo:${repoFull} type:pr author:${issueOwner} is:merged`;
36+
37+
let isPreviousContributor = false;
38+
try {
39+
const search = await github.rest.search.issuesAndPullRequests({ q, per_page: 1 });
40+
isPreviousContributor = (search.data.total_count ?? 0) > 0;
41+
} catch (e) {
42+
isPreviousContributor = false;
43+
}
44+
45+
// Reserve only if previous contributor AND labeler is NOT the issue owner
46+
const shouldReserve = isPreviousContributor && (actor !== issueOwner);
47+
48+
const lines = [];
49+
lines.push(`💰 **This issue has a bounty.**`);
50+
lines.push(`Resolve it to receive a reward.`);
51+
lines.push(`For details (amount, rules, eligibility), see: ${forumUrl}`);
52+
lines.push("");
53+
54+
lines.push(`If you want to start working on this, **comment on this issue** with your intent.`);
55+
lines.push(`If accepted by a maintainer, the issue will be **assigned** to you.`);
56+
lines.push("");
57+
58+
if (shouldReserve) {
59+
const reservedUntil = new Date(Date.now() + reserveHours * 60 * 60 * 1000);
60+
const reservedUntilIso = reservedUntil.toISOString();
61+
62+
lines.push(
63+
`<sub>` +
64+
`⏳ **Temporary reservation for @${issueOwner}:** since they are a previous contributor, they have priority for this bounty for **${reserveHours} hours** ` +
65+
`(until **${reservedUntilIso}**). After that, it is **open to everyone**.` +
66+
`</sub>`
67+
);
68+
lines.push("");
69+
}
70+
71+
// Avoid duplicate comments for the same label
72+
const comments = await github.rest.issues.listComments({
73+
owner: context.repo.owner,
74+
repo: context.repo.repo,
75+
issue_number: issue.number,
76+
per_page: 100,
77+
});
78+
79+
const already = comments.data.some(c =>
80+
c.user?.login === "github-actions[bot]" &&
81+
typeof c.body === "string" &&
82+
c.body.includes("**This issue has a bounty.**") &&
83+
c.body.includes(`see: ${forumUrl}`)
84+
);
85+
86+
if (already) return;
87+
88+
await github.rest.issues.createComment({
89+
owner: context.repo.owner,
90+
repo: context.repo.repo,
91+
issue_number: issue.number,
92+
body: lines.join("\n"),
93+
});

0 commit comments

Comments
 (0)