This means it won't send a dummy post if no changelog entries are found. The old one just sent it and didn't check if it worked.
149 lines
4.4 KiB
Python
Executable File
149 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
#
|
|
# Sends updates to a Discord webhook for new changelog entries since the last GitHub Actions publish run.
|
|
# Automatically figures out the last run and changelog contents with the GitHub API.
|
|
#
|
|
|
|
import io
|
|
import itertools
|
|
import os
|
|
import requests
|
|
import yaml
|
|
from typing import Any, Iterable
|
|
|
|
GITHUB_API_URL = os.environ.get("GITHUB_API_URL", "https://api.github.com")
|
|
GITHUB_REPOSITORY = os.environ["GITHUB_REPOSITORY"]
|
|
GITHUB_RUN = os.environ["GITHUB_RUN_ID"]
|
|
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
|
|
|
|
DISCORD_WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL")
|
|
|
|
CHANGELOG_FILE = "Resources/Changelog/Changelog.yml"
|
|
|
|
TYPES_TO_EMOJI = {
|
|
"Fix": "🐛",
|
|
"Add": "🆕",
|
|
"Remove": "❌",
|
|
"Tweak": "⚒️"
|
|
}
|
|
|
|
ChangelogEntry = dict[str, Any]
|
|
|
|
def main():
|
|
if not DISCORD_WEBHOOK_URL:
|
|
return
|
|
|
|
session = requests.Session()
|
|
session.headers["Authorization"] = f"Bearer {GITHUB_TOKEN}"
|
|
session.headers["Accept"] = "Accept: application/vnd.github+json"
|
|
session.headers["X-GitHub-Api-Version"] = "2022-11-28"
|
|
|
|
most_recent = get_most_recent_workflow(session)
|
|
last_sha = most_recent['head_commit']['id']
|
|
print(f"Last successful publish job was {most_recent['id']}: {last_sha}")
|
|
last_changelog = yaml.safe_load(get_last_changelog(session, last_sha))
|
|
with open(CHANGELOG_FILE, "r") as f:
|
|
cur_changelog = yaml.safe_load(f)
|
|
|
|
diff = diff_changelog(last_changelog, cur_changelog)
|
|
send_to_discord(diff)
|
|
|
|
|
|
def get_most_recent_workflow(sess: requests.Session) -> Any:
|
|
workflow_run = get_current_run(sess)
|
|
past_runs = get_past_runs(sess, workflow_run)
|
|
for run in past_runs['workflow_runs']:
|
|
# First past successful run that isn't our current run.
|
|
if run["id"] == workflow_run["id"]:
|
|
continue
|
|
|
|
return run
|
|
|
|
|
|
def get_current_run(sess: requests.Session) -> Any:
|
|
resp = sess.get(f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/actions/runs/{GITHUB_RUN}")
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
|
|
|
|
def get_past_runs(sess: requests.Session, current_run: Any) -> Any:
|
|
"""
|
|
Get all successful workflow runs before our current one.
|
|
"""
|
|
params = {
|
|
"status": "success",
|
|
"created": f"<={current_run['created_at']}"
|
|
}
|
|
resp = sess.get(f"{current_run['workflow_url']}/runs", params=params)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
|
|
|
|
def get_last_changelog(sess: requests.Session, sha: str) -> str:
|
|
"""
|
|
Use GitHub API to get the previous version of the changelog YAML (Actions builds are fetched with a shallow clone)
|
|
"""
|
|
params = {
|
|
"ref": sha,
|
|
}
|
|
headers = {
|
|
"Accept": "application/vnd.github.raw"
|
|
}
|
|
|
|
resp = sess.get(f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/contents/{CHANGELOG_FILE}", headers=headers, params=params)
|
|
resp.raise_for_status()
|
|
return resp.text
|
|
|
|
|
|
def diff_changelog(old: dict[str, Any], cur: dict[str, Any]) -> Iterable[ChangelogEntry]:
|
|
"""
|
|
Find all new entries not present in the previous publish.
|
|
"""
|
|
old_entry_ids = {e["id"] for e in old["Entries"]}
|
|
return (e for e in cur["Entries"] if e["id"] not in old_entry_ids)
|
|
|
|
|
|
def send_to_discord(entries: Iterable[ChangelogEntry]) -> None:
|
|
if not DISCORD_WEBHOOK_URL:
|
|
print(f"No discord webhook URL found, skipping discord send")
|
|
return
|
|
|
|
content = io.StringIO()
|
|
count: int = 0
|
|
|
|
for name, group in itertools.groupby(entries, lambda x: x["author"]):
|
|
content.write(f"**{name}** updated:\n")
|
|
for entry in group:
|
|
for change in entry["changes"]:
|
|
emoji = TYPES_TO_EMOJI.get(change['type'], "❓")
|
|
message = change['message']
|
|
url = entry.get("url")
|
|
count += 1
|
|
if url and url.strip():
|
|
content.write(f"{emoji} [-]({url}) {message}\n")
|
|
else:
|
|
content.write(f"{emoji} - {message}\n")
|
|
|
|
if count == 0:
|
|
print("Skipping discord push as no changelog entries found")
|
|
return
|
|
|
|
print(f"Posting {count} changelog entries to discord webhook")
|
|
|
|
body = {
|
|
"content": content.getvalue(),
|
|
# Do not allow any mentions.
|
|
"allowed_mentions": {
|
|
"parse": []
|
|
},
|
|
# SUPPRESS_EMBEDS
|
|
"flags": 1 << 2
|
|
}
|
|
|
|
response = requests.post(DISCORD_WEBHOOK_URL, json=body)
|
|
response.raise_for_status()
|
|
|
|
|
|
main()
|