Add Discord changelog workflow

This commit is contained in:
Solvely-Colin
2026-05-24 14:33:49 -04:00
parent d011a6fac2
commit 3d82761f9a

301
.github/workflows/discord-changelog.yml vendored Normal file
View File

@@ -0,0 +1,301 @@
name: Discord changelog
on:
push:
branches:
- main
- master
pull_request:
types:
- opened
- reopened
- synchronize
- closed
release:
types:
- published
create:
delete:
workflow_dispatch:
inputs:
release_tag:
description: Optional release tag to test. Leave blank to use the latest release.
required: false
type: string
permissions:
contents: read
pull-requests: read
jobs:
discord-changelog:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Post changelog to Discord
uses: actions/github-script@v7
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_CHANGELOG_WEBHOOK }}
RELEASE_TAG: ${{ inputs.release_tag }}
with:
script: |
const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
if (!webhookUrl) {
core.setFailed("Missing DISCORD_CHANGELOG_WEBHOOK secret.");
return;
}
let eventName = context.eventName;
let event = context.payload;
const repoFullName = `${context.repo.owner}/${context.repo.repo}`;
const repoUrl = `https://github.com/${repoFullName}`;
if (eventName === "workflow_dispatch") {
const releaseTag = process.env.RELEASE_TAG;
const response = releaseTag
? await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: releaseTag,
})
: await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo,
});
eventName = "release";
event = {
action: "published",
repository: {
full_name: repoFullName,
html_url: repoUrl,
},
sender: {
login: context.actor,
},
release: response.data,
};
}
const discordPayload = buildDiscordPayload(eventName, event);
const discordResponse = await fetch(webhookUrl, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(discordPayload),
});
if (!discordResponse.ok) {
core.setFailed(
`Discord webhook failed with ${discordResponse.status}: ${await discordResponse.text()}`,
);
}
function buildDiscordPayload(eventName, event) {
return {
username: "Repo Changelog",
content: buildContent(eventName, event),
embeds: [
{
...buildEmbed(eventName, event),
footer: {
text: `GitHub ${eventName} event`,
},
timestamp: new Date().toISOString(),
},
],
};
}
function buildContent(eventName, event) {
const repo = getRepoName(event);
if (eventName === "release") {
const tag = event.release?.tag_name || event.release?.name || "release";
const action = event.action === "published" ? "published" : event.action || "updated";
return `**${repo}** ${action} **${tag}**`;
}
return `**${repo}** update from ${getActor(event)}`;
}
function buildEmbed(eventName, event) {
if (eventName === "release") {
return buildReleaseEmbed(event);
}
if (eventName === "push") {
return buildPushEmbed(event);
}
if (eventName === "pull_request") {
return buildPullRequestEmbed(event);
}
if (eventName === "create" || eventName === "delete") {
return buildRefEmbed(eventName, event);
}
return {
title: `GitHub ${eventName} event`,
description: `Received a ${eventName} event for ${getRepoName(event)}.`,
url: event.repository?.html_url,
color: 0x99aab5,
};
}
function buildReleaseEmbed(event) {
const release = event.release || {};
const tag = release.tag_name || "release";
const titleTag = release.name || tag;
const author = release.author?.login || event.sender?.login || "unknown";
return {
title: `${titleTag} Released`,
description: formatReleaseNotes(release.body || "") || "No release notes provided.",
url: release.html_url || event.repository?.html_url,
color: 0x57f287,
fields: [
{
name: "Repository",
value: link(getRepoName(event), event.repository?.html_url),
inline: true,
},
{
name: "Tag",
value: tag,
inline: true,
},
{
name: "Published by",
value: author,
inline: true,
},
],
};
}
function buildPushEmbed(event) {
const repo = getRepoName(event);
const branch = trimRef(event.ref);
const commits = Array.isArray(event.commits) ? event.commits : [];
const commitLines = commits.slice(0, 8).map(formatCommit).join("\n");
const overflow = commits.length > 8 ? `\n...and ${commits.length - 8} more.` : "";
const commitWord = commits.length === 1 ? "commit" : "commits";
return {
title: `${commits.length} ${commitWord} pushed to ${branch}`,
description: commitLines
? `${commitLines}${overflow}`
: "No commit list was included in this event.",
url: event.compare || event.repository?.html_url,
color: 0x5865f2,
fields: [
{
name: "Repository",
value: link(repo, event.repository?.html_url),
inline: true,
},
{
name: "Branch",
value: branch,
inline: true,
},
],
};
}
function buildPullRequestEmbed(event) {
const pr = event.pull_request || {};
const action =
event.action === "closed" && pr.merged ? "merged" : event.action || "updated";
const number = pr.number ? `#${pr.number}` : "";
return {
title: `Pull request ${number} ${action}`,
description: link(pr.title || "Pull request", pr.html_url),
url: pr.html_url || event.repository?.html_url,
color: pr.merged ? 0x57f287 : 0xfee75c,
fields: [
{
name: "Author",
value: pr.user?.login || event.sender?.login || "unknown",
inline: true,
},
{
name: "Branch",
value: `${pr.head?.ref || "head"} -> ${pr.base?.ref || "base"}`,
inline: true,
},
],
};
}
function buildRefEmbed(eventName, event) {
const action = eventName === "create" ? "created" : "deleted";
const type = event.ref_type || "ref";
const ref = event.ref || "unknown";
return {
title: `${capitalize(type)} ${action}: ${ref}`,
description: `${getActor(event)} ${action} ${type} \`${ref}\`.`,
url: event.repository?.html_url,
color: eventName === "create" ? 0x57f287 : 0xed4245,
};
}
function formatCommit(commit) {
const shortSha = commit.id ? commit.id.slice(0, 7) : "commit";
const message = truncate(oneLine(commit.message || "No commit message"), 140);
const author = commit.author?.name ? ` - ${commit.author.name}` : "";
return `- ${link(shortSha, commit.url)} ${message}${author}`;
}
function formatReleaseNotes(value) {
return truncate(
value
.replace(/\r\n/g, "\n")
.trim()
.split("\n")
.map((line) => {
const heading = line.match(/^#{1,6}\s+(.+)$/);
return heading ? `**${heading[1].trim()}**` : line;
})
.join("\n")
.replace(/\n{3,}/g, "\n\n")
.trim(),
3900,
);
}
function getRepoName(event) {
return event.repository?.full_name || repoFullName;
}
function getActor(event) {
return event.sender?.login || event.pusher?.name || context.actor || "GitHub";
}
function link(label, url) {
return url ? `[${label}](${url})` : label;
}
function trimRef(ref) {
return (ref || "unknown").replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "");
}
function oneLine(value) {
return value.replace(/\s+/g, " ").trim();
}
function truncate(value, maxLength) {
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 3)}...`;
}
function capitalize(value) {
return `${value.slice(0, 1).toUpperCase()}${value.slice(1)}`;
}