Refine dedupe matching and recipient warning flow #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Organization Issue/PR Bot | ||
| on: | ||
| issues: | ||
| types: [opened] | ||
| pull_request: | ||
| types: [opened] | ||
| permissions: | ||
| contents: read | ||
| issues: write | ||
| pull-requests: write | ||
| members: read | ||
| jobs: | ||
| welcome-and-notify: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Build event metadata | ||
| id: metadata | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| try { | ||
| const payload = context.payload; | ||
| if (context.eventName === "issues" && payload.issue) { | ||
| core.setOutput("type", "Issue"); | ||
| core.setOutput("number", String(payload.issue.number)); | ||
| core.setOutput("title", payload.issue.title || "(no title)"); | ||
| core.setOutput("url", payload.issue.html_url); | ||
| return; | ||
| } | ||
| if (context.eventName === "pull_request" && payload.pull_request) { | ||
| core.setOutput("type", "Pull Request"); | ||
| core.setOutput("number", String(payload.pull_request.number)); | ||
| core.setOutput("title", payload.pull_request.title || "(no title)"); | ||
| core.setOutput("url", payload.pull_request.html_url); | ||
| return; | ||
| } | ||
| core.setFailed(`Unsupported event payload for "${context.eventName}".`); | ||
| } catch (error) { | ||
| core.setFailed(`Failed to build event metadata: ${error.message}`); | ||
| } | ||
| - name: Post welcome comment | ||
| uses: actions/github-script@v7 | ||
| env: | ||
| BOT_MESSAGE: | | ||
| 👋 Hello! Thanks for your contribution. We will review this and get back to you as soon as possible. — th30d4y | ||
| with: | ||
| script: | | ||
| const message = process.env.BOT_MESSAGE.trim(); | ||
| const issueNumber = context.issue.number; | ||
| const owner = context.repo.owner; | ||
| const repo = context.repo.repo; | ||
| try { | ||
| const comments = await github.paginate(github.rest.issues.listComments, { | ||
| owner, | ||
| repo, | ||
| issue_number: issueNumber, | ||
| per_page: 100 | ||
| }); | ||
| const normalize = (text) => (text || "").replace(/\r\n/g, "\n").trim(); | ||
| const expected = normalize(message); | ||
| const alreadyCommented = comments.some((comment) => { | ||
| if (!comment?.body) return false; | ||
| if (!comment.user?.type?.toLowerCase().includes('bot')) return false; | ||
| return normalize(comment.body) === expected; | ||
| }); | ||
| if (alreadyCommented) { | ||
| core.info(`Skipping comment: matching bot comment already exists on #${issueNumber}`); | ||
| return; | ||
| } | ||
| await github.rest.issues.createComment({ | ||
| owner, | ||
| repo, | ||
| issue_number: issueNumber, | ||
| body: message | ||
| }); | ||
| } catch (error) { | ||
| core.setFailed(`Failed to post welcome comment: ${error.message}`); | ||
| } | ||
| - name: Resolve organization owner recipients | ||
| id: recipients | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| result-encoding: string | ||
| script: | | ||
| const org = context.repo.owner; | ||
| try { | ||
| const admins = await github.paginate(github.rest.orgs.listMembers, { | ||
| org, | ||
| role: "admin", | ||
| per_page: 100 | ||
| }); | ||
| if (!admins.length) { | ||
| core.warning(`No organization owners/admins found for org "${org}".`); | ||
| return ""; | ||
| } | ||
| const recipientSet = new Set( | ||
| admins.map((admin) => `${admin.id}+${admin.login}@users.noreply.github.com`) | ||
| ); | ||
| const recipients = [...recipientSet].join(","); | ||
| core.info(`Resolved ${recipientSet.size} organization owner recipient(s).`); | ||
| return recipients; | ||
| } catch (error) { | ||
| core.setFailed(`Failed to resolve organization owners: ${error.message}`); | ||
| return ""; | ||
| } | ||
| - name: Send owner notification email | ||
| if: ${{ steps.recipients.outputs.result != '' }} | ||
| uses: dawidd6/action-send-mail@v3 | ||
| with: | ||
| server_address: smtp.gmail.com | ||
| server_port: 465 | ||
| secure: true | ||
| username: ${{ secrets.EMAIL_USER }} | ||
| password: ${{ secrets.EMAIL_PASS }} | ||
| subject: "[${{ github.repository }}] New ${{ steps.metadata.outputs.type }} Opened: ${{ steps.metadata.outputs.title }}" | ||
| to: ${{ steps.recipients.outputs.result }} | ||
| from: th30d4y Bot <${{ secrets.EMAIL_USER }}> | ||
| html_body: | | ||
| <h3>📣 New ${{ steps.metadata.outputs.type }} Created</h3> | ||
| <p><strong>Repository:</strong> <code>${{ github.repository }}</code></p> | ||
| <p><strong>Type:</strong> ${{ steps.metadata.outputs.type }}</p> | ||
| <p><strong>Title:</strong> ${{ steps.metadata.outputs.title }}</p> | ||
| <p><strong>URL:</strong> <a href="${{ steps.metadata.outputs.url }}">${{ steps.metadata.outputs.url }}</a></p> | ||
| <hr /> | ||
| <p>Automated notification by <strong>th30d4y</strong>.</p> | ||
| - name: Fail when email was not sent | ||
| if: ${{ steps.recipients.outputs.result == '' }} | ||
| shell: bash | ||
| run: | | ||
| echo "No recipients resolved, email notification was not sent." >&2 | ||
| exit 1 | ||