Add changelog-only Discord notifications after successful pushes
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Push changelogs
|
||||
|
||||
The user authorized Discord changelogs after new changes are successfully pushed.
|
||||
After each successful push to `origin/main` from this checkout:
|
||||
|
||||
1. Prepare a concise, player-facing changelog covering the commits in that push.
|
||||
Include only the changelog: describe fixes/features accurately, without server,
|
||||
deployment, or Git push status. Do not add operational disclaimers or footers.
|
||||
Phrase gameplay fixes without mentioning the server (for example, "Fixed radio
|
||||
station generation after an empty-session wait").
|
||||
2. Save the message to a temporary file and run
|
||||
`python3 tools/discord_changelog.py --commit <pushed-sha> --message-file <path>`.
|
||||
3. Report a delivery failure honestly. Do not blindly retry a pending delivery;
|
||||
check Discord first to avoid duplicates.
|
||||
|
||||
The webhook credential is in `.local/discord-webhook.url` (ignored, owner-only).
|
||||
Never print it, commit it, or put it into changelogs or command-line arguments.
|
||||
The helper verifies the remote commit, disables mentions, and records delivery in
|
||||
`.local/discord-changelog-state.json`. Do not backfill old commits unless requested.
|
||||
|
||||
This is an assistant workflow, not a server-side Git hook. Notifications for
|
||||
pushes from other checkouts require a Discord push webhook in Gitea repository
|
||||
settings; that server-side automation has not been configured.
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Post a prepared changelog after verifying a successful push to origin/main.
|
||||
|
||||
The webhook is local configuration, never a command-line argument or tracked file.
|
||||
API: https://docs.discord.com/developers/resources/webhook#execute-webhook
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--commit', required=True, help='Commit just pushed to origin/main')
|
||||
parser.add_argument('--message-file', required=True, type=Path, help='Prepared player-facing changelog, at most 2000 characters')
|
||||
args = parser.parse_args()
|
||||
commit = subprocess.check_output(['git', 'rev-parse', '--verify', args.commit + '^{commit}'], cwd=ROOT, text=True).strip()
|
||||
ssh = 'ssh -F ' + shlex.quote(str(Path.home() / '.ssh/config'))
|
||||
remote = subprocess.check_output(['git', '-c', 'core.sshCommand=' + ssh, 'ls-remote', '--exit-code', 'origin', 'refs/heads/main'], cwd=ROOT, text=True).split()[0]
|
||||
if commit != remote:
|
||||
parser.error('Requested commit is not the current origin/main tip; no message sent.')
|
||||
message = args.message_file.read_text().strip()
|
||||
if not message or len(message) > 2000:
|
||||
parser.error('Changelog must contain 1..2000 characters.')
|
||||
state_path = ROOT / '.local/discord-changelog-state.json'
|
||||
state = json.loads(state_path.read_text()) if state_path.exists() else {}
|
||||
if state.get('commit') == commit:
|
||||
print('This commit is already recorded; no duplicate sent. Status: ' + state.get('status', 'unknown'))
|
||||
return
|
||||
url = (ROOT / '.local/discord-webhook.url').read_text().strip()
|
||||
parsed = urllib.parse.urlsplit(url)
|
||||
if parsed.scheme != 'https' or parsed.netloc != 'discord.com' or not parsed.path.startswith('/api/webhooks/') or parsed.query or parsed.fragment:
|
||||
parser.error('Local configuration must contain a Discord HTTPS webhook URL.')
|
||||
payload = json.dumps({'content': message, 'allowed_mentions': {'parse': []}}).encode()
|
||||
request = urllib.request.Request(url + '?wait=true', data=payload, headers={
|
||||
'Content-Type': 'application/json', 'User-Agent': 'EscapistsReintroduced-Changelog/1.0'})
|
||||
# Preserve an uncertain attempt so a timeout cannot silently cause duplicates.
|
||||
state_path.write_text(json.dumps({'commit': commit, 'status': 'pending'}) + '\n')
|
||||
try:
|
||||
with urllib.request.build_opener(NoRedirect()).open(request, timeout=30) as response:
|
||||
result = json.load(response)
|
||||
if not result.get('id'):
|
||||
raise ValueError('Missing message confirmation')
|
||||
except urllib.error.HTTPError as error:
|
||||
print('Discord returned HTTP ' + str(error.code) + '; inspect delivery before retrying.', file=sys.stderr)
|
||||
return 1
|
||||
except (urllib.error.URLError, TimeoutError, ValueError):
|
||||
print('Discord delivery could not be confirmed; inspect delivery before retrying.', file=sys.stderr)
|
||||
return 1
|
||||
state_path.write_text(json.dumps({'commit': commit, 'status': 'sent', 'message_id': result['id']}) + '\n')
|
||||
print('Discord changelog delivered for ' + commit[:7])
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user