forked from Observal/Observal
-
Notifications
You must be signed in to change notification settings - Fork 0
217 lines (187 loc) · 8.7 KB
/
Copy pathtake-command.yml
File metadata and controls
217 lines (187 loc) · 8.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# SPDX-FileCopyrightText: 2026 Naraen Rammoorthi <naraen13@gmail.com>
# SPDX-FileCopyrightText: 2026 Shaan Narendran <shaannaren06@gmail.com>
# SPDX-License-Identifier: AGPL-3.0-only
name: Issue Commands
on:
issue_comment:
types: [created]
schedule:
# Run daily at midnight UTC to check for stale assignments
- cron: '0 0 * * *'
permissions:
issues: write
jobs:
take:
if: >-
github.event_name == 'issue_comment' &&
github.event.issue.pull_request == null &&
contains(github.event.comment.body, '/take')
runs-on: ubuntu-latest
steps:
- name: Check labels and assign
uses: actions/github-script@v9
with:
script: |
const issue = context.payload.issue;
const commenter = context.payload.comment.user.login;
// Ensure the trimmed comment is exactly "/take"
if (context.payload.comment.body.trim() !== '/take') return;
const labels = issue.labels.map(l => l.name.toLowerCase());
// Block assignment on issues tagged "keep open"
if (labels.includes('keep open')) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `@${commenter} This issue is marked as **keep open** and is not available for assignment. Anyone is welcome to work on it and submit a PR without being assigned.`
});
return;
}
const eligible = labels.includes('good first issue') || labels.includes('help wanted');
if (!eligible) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `@${commenter} This issue is not open for community assignment. The \`/take\` command only works on issues labeled \`good first issue\` or \`help wanted\`.`
});
return;
}
// Check if the contributor already has 2 or more open assigned issues
const { data: assignedIssues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
assignee: commenter,
state: 'open',
per_page: 3
});
if (assignedIssues.length >= 2) {
// Minimise noise — notify the contributor without cluttering the issue thread
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `> **Note to @${commenter}:** You already have ${assignedIssues.length} open issues assigned to you. Please complete or \`/drop\` an existing issue before taking a new one.\n>\n> Your current assignments:\n${assignedIssues.slice(0, 2).map(i => `> - #${i.number} — ${i.title}`).join('\n')}`
});
return;
}
if (issue.assignees.length > 0) {
const currentAssignee = issue.assignees[0].login;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `@${commenter} This issue is already being worked on by @${currentAssignee}. If there has been no activity for a while, a maintainer may reassign it. You can also check other open issues labeled \`good first issue\` or \`help wanted\`.`
});
return;
}
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: [commenter]
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `@${commenter} You've been assigned to this issue. Welcome aboard! 🎉\n\nPlease read our [contributing guide](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/CONTRIBUTING.md) before submitting a PR.`
});
drop:
if: >-
github.event_name == 'issue_comment' &&
github.event.issue.pull_request == null &&
contains(github.event.comment.body, '/drop')
runs-on: ubuntu-latest
steps:
- name: Unassign commenter
uses: actions/github-script@v9
with:
script: |
const issue = context.payload.issue;
const commenter = context.payload.comment.user.login;
// Ensure the trimmed comment is exactly "/drop"
if (context.payload.comment.body.trim() !== '/drop') return;
const isAssigned = issue.assignees.some(a => a.login === commenter);
if (!isAssigned) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `@${commenter} You are not currently assigned to this issue.`
});
return;
}
await github.rest.issues.removeAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: [commenter]
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `@${commenter} You've been unassigned from this issue. The issue is now available for others to pick up.`
});
stale-assignments:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Unassign stale issues
uses: actions/github-script@v9
with:
script: |
const STALE_DAYS = 30;
const now = new Date();
// Fetch open issues that have assignees and qualifying labels
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
});
const assigned = issues.filter(i =>
!i.pull_request &&
i.assignees.length > 0 &&
i.labels.some(l => {
const name = l.name.toLowerCase();
return name === 'good first issue' || name === 'help wanted';
})
);
for (const issue of assigned) {
// Get the most recent activity (comments, events) on the issue
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 1,
direction: 'desc'
});
const { data: events } = await github.rest.issues.listEvents({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 1
});
// Find the latest activity timestamp
const lastCommentDate = comments.length > 0 ? new Date(comments[0].created_at) : new Date(0);
const lastEventDate = events.length > 0 ? new Date(events[events.length - 1].created_at) : new Date(0);
const lastActivity = new Date(Math.max(lastCommentDate, lastEventDate, new Date(issue.updated_at)));
const daysSinceActivity = (now - lastActivity) / (1000 * 60 * 60 * 24);
if (daysSinceActivity >= STALE_DAYS) {
const assignee = issue.assignees[0].login;
await github.rest.issues.removeAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: issue.assignees.map(a => a.login)
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `@${assignee} This issue has been unassigned due to ${STALE_DAYS} days of inactivity. No worries — if you'd like to continue working on it, just comment \`/take\` again. Otherwise, the issue is now available for other contributors.`
});
}
}