1+ /*
2+ ==============================================================================
3+ Executes When:
4+ - Triggered by GitHub Actions workflow on event: 'issue_comment' (created).
5+ - Target: Issues specifically labeled with "beginner".
6+
7+ Goal:
8+ It acts as an automated onboarding assistant for "beginner" issues. It allows
9+ contributors to self-assign using a command and nudges new contributors who
10+ express interest but forget to assign themselves, while preventing spam.
11+
12+ ------------------------------------------------------------------------------
13+ Flow: Basic Idea
14+ 1. Listens for comments on issues.
15+ 2. Ignores Pull Requests, Bots, and issues missing the "beginner" label.
16+ 3. Detects if the user typed "/assign".
17+ - If YES: Assigns the user to the issue (if currently unassigned).
18+ - If NO: Checks if the user is an external contributor expressing interest.
19+ If so, it replies with instructions on how to use the assign command.
20+
21+ ------------------------------------------------------------------------------
22+ Flow: Detailed Technical Steps
23+
24+ 1️⃣ Validation & filtering
25+ - Checks payload to ensure it is an Issue Comment (not a PR).
26+ - Checks if the commenter is a BOT (e.g., github-actions). If so, exits.
27+ - Checks if the issue has the specific label "beginner". If not, exits.
28+
29+ 2️⃣ Collaborator Check (isRepoCollaborator)
30+ - Action: Checks if the user is a repository collaborator.
31+ - Logic:
32+ * Collaborator (204) -> Treated as Team Member (Bot ignores them).
33+ * Non-Collaborator (404) -> Treated as External Contributor (Bot helps them).
34+
35+ 3️⃣ Logic Branch A: The "/assign" Command
36+ - Trigger: User comment matches regex /(^|\s)\/assign(\s|$)/i.
37+ - Check: Is the issue already assigned?
38+ * Yes -> Alert the user that it is taken.
39+ * No -> API Call: Add commenter to 'assignees'.
40+
41+ 4️⃣ Logic Branch B: The Helper Reminder
42+ - Trigger: Generic comment (e.g., "I want to work on this").
43+ - Condition 1: Issue must be unassigned.
44+ - Condition 2: Commenter must NOT be a Repo Collaborator.
45+ - Condition 3: Duplicate Check.
46+ * Scans previous comments for a hidden marker: "<!-- beginner assign reminder -->".
47+ * If found -> Exits to avoid spamming the thread.
48+ - Action: Posts a comment with the hidden marker and instructions.
49+
50+ ------------------------------------------------------------------------------
51+ Parameters:
52+ - { github, context }: Standard objects provided by 'actions/github-script'.
53+ ==============================================================================
54+ */
55+ module . exports = async ( { github, context } ) => {
56+ try {
57+ const { payload } = context ;
58+ const issue = payload . issue ;
59+ const comment = payload . comment ;
60+ const repo = payload . repository ;
61+
62+ // 1. Basic Validation
63+ if ( ! issue || ! comment || ! repo || issue . pull_request ) {
64+ console . log ( "[Beginner Bot] Invalid payload or PR comment. Exiting." ) ;
65+ return ;
66+ }
67+
68+ // 1.1 Bot Check (Fix 2: Defensive Check)
69+ if ( comment . user ?. type === "Bot" ) {
70+ console . log ( `[Beginner Bot] Commenter @${ comment . user . login } is a bot. Exiting.` ) ;
71+ return ;
72+ }
73+
74+ // 2. Label Check (Fix 2: Defensive Check)
75+ const hasBeginnerLabel = Array . isArray ( issue . labels ) && issue . labels . some ( ( label ) => label . name === "beginner" ) ;
76+ if ( ! hasBeginnerLabel ) {
77+ console . log ( `[Beginner Bot] Issue #${ issue . number } does not have 'beginner' label. Exiting.` ) ;
78+ return ;
79+ }
80+
81+ // 3. Collaborator Check Helper
82+ async function isRepoCollaborator ( username ) {
83+ try {
84+ if ( username === repo . owner . login ) {
85+ console . log ( `[Beginner Bot] User @${ username } is the repo owner.` ) ;
86+ return true ;
87+ }
88+
89+ await github . rest . repos . checkCollaborator ( {
90+ owner : repo . owner . login ,
91+ repo : repo . name ,
92+ username : username ,
93+ } ) ;
94+
95+ console . log ( `[Beginner Bot] User @${ username } is a confirmed repo collaborator.` ) ;
96+ return true ;
97+ } catch ( error ) {
98+ if ( error . status === 404 ) {
99+ console . log ( `[Beginner Bot] User @${ username } is NOT a collaborator (External Contributor).` ) ;
100+ return false ;
101+ }
102+ console . log ( `[Beginner Bot] Error checking collaborator status for @${ username } : ${ error . message } ` ) ;
103+ return false ;
104+ }
105+ }
106+
107+ const commenter = comment . user . login ;
108+
109+ // Fix 3: Validate comment body
110+ if ( ! comment . body ) {
111+ console . log ( "[Beginner Bot] Comment body is empty. Exiting." ) ;
112+ return ;
113+ }
114+
115+ const commentBody = comment . body . toLowerCase ( ) ;
116+ const isAssignCommand = / ( ^ | \s ) \/ a s s i g n ( \s | $ ) / i. test ( commentBody ) ;
117+
118+ // 4. Logic Branch
119+ if ( isAssignCommand ) {
120+ // --- ASSIGNMENT LOGIC ---
121+ if ( issue . assignees && issue . assignees . length > 0 ) {
122+ const currentAssignee = issue . assignees [ 0 ] . login ;
123+ console . log ( `[Beginner Bot] Issue #${ issue . number } is already assigned. Ignoring /assign command.` ) ;
124+
125+ // Fix 4: Granular Try/Catch for Comment API
126+ try {
127+ await github . rest . issues . createComment ( {
128+ owner : repo . owner . login ,
129+ repo : repo . name ,
130+ issue_number : issue . number ,
131+ body : `👋 Hi @${ commenter } , thanks for your interest! This issue is already assigned to @${ currentAssignee } , but we'd love your help on another one. You can find more "beginner" issues [here](https://github.com/hiero-ledger/hiero-sdk-python/issues?q=is%3Aissue%20state%3Aopen%20label%3Abeginner%20no%3Aassignee).` ,
132+ } ) ;
133+ } catch ( error ) {
134+ console . error ( `[Beginner Bot] Failed to post already-assigned comment: ${ error . message } ` ) ;
135+ }
136+ return ; // Exit after warning
137+ }
138+
139+ console . log ( `[Beginner Bot] Assigning issue #${ issue . number } to @${ commenter } ...` ) ;
140+
141+ // Fix 4: Granular Try/Catch for Assign API
142+ try {
143+ await github . rest . issues . addAssignees ( {
144+ owner : repo . owner . login ,
145+ repo : repo . name ,
146+ issue_number : issue . number ,
147+ assignees : [ commenter ] ,
148+ } ) ;
149+ console . log ( `[Beginner Bot] Successfully assigned.` ) ;
150+ } catch ( error ) {
151+ console . error ( `[Beginner Bot] Failed to assign issue: ${ error . message } ` ) ;
152+ }
153+
154+ } else {
155+ // --- REMINDER LOGIC ---
156+
157+ if ( issue . assignees && issue . assignees . length > 0 ) {
158+ console . log ( `[Beginner Bot] Issue #${ issue . number } is already assigned. Skipping reminder.` ) ;
159+ return ;
160+ }
161+
162+ if ( await isRepoCollaborator ( commenter ) ) {
163+ console . log ( `[Beginner Bot] Commenter @${ commenter } is a repo collaborator. Skipping reminder.` ) ;
164+ return ;
165+ }
166+
167+ // Fix 5: Updated Marker Text
168+ const REMINDER_MARKER = "<!-- beginner assign reminder -->" ;
169+ // FIX 6: Granular Try/Catch for List Comments API
170+ let comments ;
171+ try {
172+ const { data } = await github . rest . issues . listComments ( {
173+ owner : repo . owner . login ,
174+ repo : repo . name ,
175+ issue_number : issue . number ,
176+ } ) ;
177+ comments = data ;
178+ } catch ( error ) {
179+ console . error ( `[Beginner Bot] Failed to list comments: ${ error . message } ` ) ;
180+ return ; // Exit gracefully if we can't check for duplicates
181+ }
182+
183+ if ( comments . some ( ( c ) => c . body . includes ( REMINDER_MARKER ) ) ) {
184+ console . log ( "[Beginner Bot] Reminder already exists on this issue. Skipping." ) ;
185+ return ;
186+ }
187+
188+ console . log ( `[Beginner Bot] Posting help reminder for @${ commenter } ...` ) ;
189+
190+ const reminderBody = `${ REMINDER_MARKER } \n👋 Hi @${ commenter } ! If you'd like to work on this issue, please comment \`/assign\` to get assigned.` ;
191+
192+ // FIX 6: Granular Try/Catch for Create Comment API
193+ try {
194+ await github . rest . issues . createComment ( {
195+ owner : repo . owner . login ,
196+ repo : repo . name ,
197+ issue_number : issue . number ,
198+ body : reminderBody ,
199+ } ) ;
200+ console . log ( "[Beginner Bot] Reminder posted successfully." ) ;
201+ } catch ( error ) {
202+ console . error ( `[Beginner Bot] Failed to post reminder: ${ error . message } ` ) ;
203+ }
204+ }
205+
206+ } catch ( error ) {
207+ // Fix 1: Top-level error handling
208+ console . error ( "[Beginner Bot] Unexpected error:" , {
209+ message : error . message ,
210+ status : error . status ,
211+ issue : context . payload ?. issue ?. number ,
212+ comment : context . payload ?. comment ?. id
213+ } ) ;
214+ }
215+ } ;
0 commit comments