diff --git a/.github/workflows/close-issues.yml b/.github/workflows/close-issues.yml new file mode 100644 index 00000000..55f4e126 --- /dev/null +++ b/.github/workflows/close-issues.yml @@ -0,0 +1,101 @@ +name: Close Unauthorized Issues + +on: + workflow_dispatch: + issues: + types: [opened] + +jobs: + close-unauthorized-issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + # XXX: This *could* be done in Bash by abusing GitHub's own tool to interact with its API + # but that's too much of a hack, and we'll be adding a layer of abstraction. github-script + # is a workflow that eases interaction with GitHub API in the workflow run context. + - name: "Close 'unauthorized' issues" + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const ALLOWED_USERS = ['vaxerski', 'fufexan', 'NotAShelf']; + const CLOSING_COMMENT = 'Users are no longer allowed to open issues themselves, please open a discussion instead.\n\nPlease see the [wiki](https://wiki.hyprland.org/Contributing-and-Debugging/Issue-Guidelines/) on why this is the case.\n\nWe are volunteers, and we need your cooperation to make the best software we can. Thank you for understanding! ❤️\n\n[Open a discussion here](https://github.com/hyprwm/Hyprland/discussions)'; + + async function closeUnauthorizedIssue(issueNumber, userName) { + if (ALLOWED_USERS.includes(userName)) { + console.log(`Issue #${issueNumber} - Created by authorized user ${userName}`); + return; + } + + console.log(`Issue #${issueNumber} - Unauthorized, closing`); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + state: 'closed', + state_reason: 'not_planned' + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: CLOSING_COMMENT + }); + } + + if (context.eventName === 'issues' && context.payload.action === 'opened') { + // Direct access to the issue that triggered the workflow + const issue = context.payload.issue; + + // Skip if this is a PR + if (issue.pull_request) { + console.log(`Issue #${issue.number} - Skipping, this is a pull request`); + return; + } + + // Process the single issue that triggered the workflow + await closeUnauthorizedIssue(issue.number, issue.user.login); + } else { + // For manual runs, we need to handle pagination + async function* fetchAllOpenIssues() { + let page = 1; + let hasNextPage = true; + + while (hasNextPage) { + const response = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100, + page: page + }); + + if (response.data.length === 0) { + hasNextPage = false; + } else { + for (const issue of response.data) { + yield issue; + } + page++; + } + } + } + + // Process issues one by one + for await (const issue of fetchAllOpenIssues()) { + try { + // Skip pull requests + if (issue.pull_request) { + console.log(`Issue #${issue.number} - Skipping, this is a pull request`); + continue; + } + + await closeUnauthorizedIssue(issue.number, issue.user.login); + } catch (error) { + console.error(`Error processing issue #${issue.number}: ${error.message}`); + } + } + }