diff --git a/.github/workflows/org-bot.yml b/.github/workflows/org-bot.yml new file mode 100644 index 0000000..15abf34 --- /dev/null +++ b/.github/workflows/org-bot.yml @@ -0,0 +1,148 @@ +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: | +
Repository: ${{ github.repository }}
Type: ${{ steps.metadata.outputs.type }}
+Title: ${{ steps.metadata.outputs.title }}
+URL: ${{ steps.metadata.outputs.url }}
+Automated notification by th30d4y.
+ + - 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