@@ -112,14 +112,29 @@ class OptIn extends Base {
112112 cursor = stargazers . pageInfo . endCursor ;
113113 }
114114
115- const uniqueLogins = [ ...new Set ( optedInLogins ) ] ;
115+ // 2. Check Issues
116+ const issueResults = await this . processIssues ( ) ;
117+
118+ let uniqueLogins = [ ...new Set ( optedInLogins ) ] ;
119+ let loginsToReAdd = [ ...uniqueLogins ] ; // Stars can reverse blocklist
120+ let issuesToClose = [ ] ;
121+
122+ if ( issueResults ) {
123+ uniqueLogins . push ( ...issueResults . selfLogins ) ;
124+ uniqueLogins . push ( ...issueResults . othersLogins ) ;
125+ loginsToReAdd . push ( ...issueResults . selfLogins ) ; // ONLY self issues reverse blocklist
126+ issuesToClose . push ( ...issueResults . issuesToClose ) ;
127+
128+ uniqueLogins = [ ...new Set ( uniqueLogins ) ] ;
129+ loginsToReAdd = [ ...new Set ( loginsToReAdd ) ] ;
130+ }
116131
117132 if ( uniqueLogins . length > 0 ) {
118133 console . log ( `[OptIn] Found ${ uniqueLogins . length } new opt-in requests:` , uniqueLogins ) ;
119134
120- // 1. Remove from blocklist if they are there
135+ // 1. Remove from blocklist if they are there (ONLY stargazers and self-issues)
121136 const blocklist = await Storage . getBlocklist ( ) ;
122- const blockedUsersToReAdd = uniqueLogins . filter ( login => blocklist . has ( login . toLowerCase ( ) ) ) ;
137+ const blockedUsersToReAdd = loginsToReAdd . filter ( login => blocklist . has ( login . toLowerCase ( ) ) ) ;
123138
124139 if ( blockedUsersToReAdd . length > 0 ) {
125140 console . log ( `[OptIn] Removing from blocklist:` , blockedUsersToReAdd ) ;
@@ -133,24 +148,181 @@ class OptIn extends Base {
133148 const tracker = await Storage . getTracker ( ) ;
134149 const existingTracker = new Set ( tracker . map ( t => t . login . toLowerCase ( ) ) ) ;
135150
151+ // We must also ensure we don't add "othersLogins" that are on the blocklist,
152+ // since we didn't remove them above.
153+ const currentBlocklist = await Storage . getBlocklist ( ) ;
154+
136155 const toAdd = uniqueLogins . filter ( login => {
137156 const lLogin = login . toLowerCase ( ) ;
138- return ! existingUsers . has ( lLogin ) && ! existingTracker . has ( lLogin ) ;
157+ return ! existingUsers . has ( lLogin ) && ! existingTracker . has ( lLogin ) && ! currentBlocklist . has ( lLogin ) ;
139158 } ) ;
140159
141160 if ( toAdd . length > 0 ) {
142161 console . log ( `[OptIn] Adding to tracker:` , toAdd ) ;
143162 const trackerUpdates = toAdd . map ( login => ( { login, lastUpdate : null } ) ) ;
144163 await Storage . updateTracker ( trackerUpdates ) ;
145164 } else {
146- console . log ( `[OptIn] All opted-in users are already tracked or indexed .` ) ;
165+ console . log ( `[OptIn] All opted-in users are already tracked, indexed, or blocked .` ) ;
147166 }
148167
149168 await Storage . saveOptInSync ( { lastCheck : newLastCheck } ) ;
150169 console . log ( `[OptIn] Processed opt-ins and updated sync state to ${ newLastCheck } .` ) ;
151170 } else {
152171 console . log ( '[OptIn] No new opt-in requests found.' ) ;
153172 }
173+
174+ // 3. Close Issues and Leave Comment
175+ if ( issuesToClose . length > 0 ) {
176+ await this . closeIssues ( issuesToClose ) ;
177+ }
178+ }
179+
180+ async processIssues ( ) {
181+ console . log ( '[OptIn] Checking for new opt-in issue requests...' ) ;
182+ let hasNextPage = true ;
183+ let cursor = null ;
184+ let selfLogins = [ ] ;
185+ let othersLogins = [ ] ;
186+ let issuesToClose = [ ] ;
187+
188+ while ( hasNextPage ) {
189+ const query = `
190+ query($owner: String!, $name: String!, $cursor: String) {
191+ repository(owner: $owner, name: $name) {
192+ issues(first: 100, states: OPEN, labels: ["devindex-opt-in"], after: $cursor) {
193+ pageInfo {
194+ hasNextPage
195+ endCursor
196+ }
197+ nodes {
198+ id
199+ number
200+ title
201+ body
202+ author {
203+ login
204+ }
205+ }
206+ }
207+ }
208+ }` ;
209+
210+ const variables = {
211+ owner : this . optInRepoOwner ,
212+ name : this . optInRepoName ,
213+ cursor : cursor
214+ } ;
215+
216+ let data ;
217+ try {
218+ data = await GitHub . query ( query , variables , 3 , 'OptIn Issues' ) ;
219+ } catch ( err ) {
220+ if ( err . message . includes ( 'NOT_FOUND' ) || err . message . includes ( 'Could not resolve' ) ) {
221+ return null ;
222+ }
223+ throw err ;
224+ }
225+
226+ const issues = data ?. repository ?. issues ;
227+ if ( ! issues ) break ;
228+
229+ const nodes = issues . nodes || [ ] ;
230+
231+ for ( const issue of nodes ) {
232+ if ( issue . title . includes ( 'Opt-In Request:' ) ) {
233+ if ( issue . author && issue . author . login ) {
234+ selfLogins . push ( issue . author . login ) ;
235+ issuesToClose . push ( { id : issue . id , type : 'self' , logins : [ issue . author . login ] } ) ;
236+ }
237+ } else if ( issue . title . includes ( 'Opt-In Nomination:' ) ) {
238+ const match = issue . body . match ( / # # # G i t H u b U s e r n a m e s \s * ( [ \s \S ] * ?) (?: # # # | $ ) / ) ;
239+ if ( match ) {
240+ const text = match [ 1 ] ;
241+ const usernames = text . split ( '\n' )
242+ . map ( u => u . trim ( ) )
243+ . filter ( u => u && ! u . startsWith ( '-' ) && ! u . startsWith ( '[' ) ) ;
244+
245+ const validUsernames = [ ] ;
246+ const invalidUsernames = [ ] ;
247+
248+ for ( const uname of usernames ) {
249+ try {
250+ await GitHub . rest ( `users/${ uname } ` ) ;
251+ validUsernames . push ( uname ) ;
252+ othersLogins . push ( uname ) ;
253+ } catch ( e ) {
254+ invalidUsernames . push ( uname ) ;
255+ }
256+ }
257+
258+ issuesToClose . push ( {
259+ id : issue . id ,
260+ type : 'others' ,
261+ validLogins : validUsernames ,
262+ invalidLogins : invalidUsernames
263+ } ) ;
264+ } else {
265+ issuesToClose . push ( { id : issue . id , type : 'invalid' } ) ;
266+ }
267+ }
268+ }
269+
270+ hasNextPage = issues . pageInfo . hasNextPage ;
271+ cursor = issues . pageInfo . endCursor ;
272+ }
273+
274+ return { selfLogins, othersLogins, issuesToClose } ;
275+ }
276+
277+ async closeIssues ( issues ) {
278+ for ( const issue of issues ) {
279+ try {
280+ let commentBody = "" ;
281+ if ( issue . type === 'self' ) {
282+ commentBody = `Thank you for opting in! @${ issue . logins [ 0 ] } has been added to our tracking queue.\n\n*This issue has been automatically closed.*` ;
283+ } else if ( issue . type === 'others' ) {
284+ if ( issue . validLogins && issue . validLogins . length > 0 ) {
285+ commentBody = `Thank you for your nominations!\n\n` ;
286+ commentBody += `**Successfully Added:**\n${ issue . validLogins . map ( u => `- @${ u } ` ) . join ( '\n' ) } \n\n` ;
287+ if ( issue . invalidLogins && issue . invalidLogins . length > 0 ) {
288+ commentBody += `**Failed Validation (Not Found):**\n${ issue . invalidLogins . map ( u => `- ${ u } ` ) . join ( '\n' ) } \n\n` ;
289+ }
290+ } else if ( issue . invalidLogins && issue . invalidLogins . length > 0 ) {
291+ commentBody = `We could not validate any of the provided usernames. Please double-check them and submit a new request if needed.\n\n` ;
292+ commentBody += `**Failed Validation (Not Found):**\n${ issue . invalidLogins . map ( u => `- ${ u } ` ) . join ( '\n' ) } \n\n` ;
293+ } else {
294+ commentBody = `We could not parse any usernames from this issue. Please ensure you follow the issue template format.\n\n` ;
295+ }
296+ commentBody += `*This issue has been automatically closed.*` ;
297+ } else {
298+ commentBody = `We could not parse the usernames from this issue. Please ensure you follow the issue template format.\n\n*This issue has been automatically closed.*` ;
299+ }
300+
301+ // Add Comment
302+ const commentQuery = `
303+ mutation($subjectId: ID!, $body: String!) {
304+ addComment(input: {subjectId: $subjectId, body: $body}) {
305+ clientMutationId
306+ }
307+ }` ;
308+ await GitHub . query ( commentQuery , {
309+ subjectId : issue . id ,
310+ body : commentBody
311+ } , 3 , `OptIn Comment ${ issue . id } ` ) ;
312+
313+ // Close Issue
314+ const closeQuery = `
315+ mutation($issueId: ID!) {
316+ closeIssue(input: {issueId: $issueId}) {
317+ clientMutationId
318+ }
319+ }` ;
320+ await GitHub . query ( closeQuery , { issueId : issue . id } , 3 , `OptIn Close ${ issue . id } ` ) ;
321+ console . log ( `[OptIn] Closed issue ${ issue . id } ` ) ;
322+ } catch ( err ) {
323+ console . error ( `[OptIn] Failed to close issue ${ issue . id } :` , err . message ) ;
324+ }
325+ }
154326 }
155327}
156328
0 commit comments