Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions .github/workflows/org-bot.yml
Original file line number Diff line number Diff line change
@@ -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: |
<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