22"""Post one GitHub release announcement to a Discord webhook.
33
44The release workflow publishes releases with GitHub's built-in GITHUB_TOKEN.
5- GitHub deliberately does not emit a second workflow run for most events created
6- by that token, so a separate ``release: published`` workflow is not reliable.
7- This script is called directly by the release workflow and by a manual backfill
8- workflow.
5+ GitHub deliberately suppresses most follow-up events created by that token, so
6+ the publisher explicitly dispatches the same workflow used for external release
7+ events and manual backfills.
98"""
109
1110from __future__ import annotations
@@ -129,6 +128,8 @@ def mark_release_announced(
129128) -> None :
130129 body = release .get ("body" ) or ""
131130 marker = announcement_marker (tag )
131+ if marker in body :
132+ return
132133 updated_body = f"{ body .rstrip ()} \n \n { marker } \n "
133134 github_request (
134135 f"https://api.github.com/repos/{ repository } /releases/{ release ['id' ]} " ,
@@ -151,6 +152,45 @@ def required(value: str | None, description: str) -> str:
151152 raise SystemExit (f"Missing { description } " )
152153
153154
155+ def announce_release (
156+ * , repository : str , tag : str , token : str , webhook_url : str
157+ ) -> str | None :
158+ release = fetch_release (repository = repository , tag = tag , token = token )
159+ if release .get ("draft" ):
160+ raise RuntimeError (f"Refusing to announce draft release { tag } " )
161+
162+ body = release .get ("body" ) or ""
163+ if already_announced (body , tag ):
164+ print (f"Discord announcement for { tag } is already recorded; skipping" )
165+ return None
166+
167+ content = format_message (
168+ tag = tag ,
169+ name = release .get ("name" ) or tag ,
170+ body = body ,
171+ url = release ["html_url" ],
172+ )
173+ message = post_to_discord (webhook_url = webhook_url , content = content )
174+ message_id = str (message ["id" ])
175+ print (f"Posted { tag } to Discord as message { message_id } " )
176+
177+ # Re-fetch immediately before patching so an edit made while the Discord
178+ # request was in flight is not overwritten with the initially fetched body.
179+ latest_release = fetch_release (repository = repository , tag = tag , token = token )
180+ try :
181+ mark_release_announced (
182+ repository = repository , release = latest_release , tag = tag , token = token
183+ )
184+ except Exception as error : # noqa: BLE001 - best-effort after public side effect
185+ # A marker write failure must not turn a successful public post into a
186+ # failed workflow that an operator is likely to rerun and duplicate.
187+ print (
188+ f"::warning::Discord post succeeded, but the release marker failed: { error } " ,
189+ file = sys .stderr ,
190+ )
191+ return message_id
192+
193+
154194def main () -> int :
155195 args = parse_args ()
156196 tag = required (args .tag , "release tag (--tag or GITHUB_REF_NAME)" )
@@ -165,38 +205,20 @@ def main() -> int:
165205 )
166206
167207 try :
168- release = fetch_release (repository = repository , tag = tag , token = token )
169- body = release .get ("body" ) or ""
170- if already_announced (body , tag ):
171- print (f"Discord announcement for { tag } is already recorded; skipping" )
172- return 0
173-
174- content = format_message (
208+ announce_release (
209+ repository = repository ,
175210 tag = tag ,
176- name = release .get ("name" ) or tag ,
177- body = body ,
178- url = release ["html_url" ],
211+ token = token ,
212+ webhook_url = webhook_url ,
179213 )
180- message = post_to_discord (webhook_url = webhook_url , content = content )
181- print (f"Posted { tag } to Discord as message { message ['id' ]} " )
182-
183- # The marker makes workflow reruns and manual backfills idempotent. A
184- # marker write failure must not turn a successful public post into a
185- # failed workflow that an operator is likely to rerun and duplicate.
186- try :
187- mark_release_announced (
188- repository = repository , release = release , tag = tag , token = token
189- )
190- except Exception as error : # noqa: BLE001 - best-effort after public side effect
191- print (
192- f"::warning::Discord post succeeded, but the release marker failed: { error } " ,
193- file = sys .stderr ,
194- )
195214 return 0
196215 except urllib .error .HTTPError as error :
197216 detail = error .read ().decode ("utf-8" , errors = "replace" )
198217 print (f"HTTP { error .code } while announcing { tag } : { detail } " , file = sys .stderr )
199218 return 1
219+ except (RuntimeError , urllib .error .URLError ) as error :
220+ print (f"Could not announce { tag } : { error } " , file = sys .stderr )
221+ return 1
200222
201223
202224if __name__ == "__main__" :
0 commit comments