GameBoy Emulator 1
Game Boy emulator core and tooling
Loading...
Searching...
No Matches
send_discord_messages.py
1#!/usr/bin/env python3
2
3import argparse
4import json
5import os
6import sys
7import time
8import urllib.error
9import urllib.request
10
11
12def load_messages(path):
13 try:
14 with open(path, "r", encoding="utf-8") as fh:
15 data = json.load(fh)
16 except FileNotFoundError:
17 print(f"[discord] messages file not found: {path}; skipping")
18 return []
19 except json.JSONDecodeError as exc:
20 print(f"[discord] invalid JSON in {path}: {exc}; skipping")
21 return []
22
23 if not isinstance(data, list):
24 print(f"[discord] expected list in {path}; skipping")
25 return []
26
27 filtered = []
28 for entry in data:
29 if isinstance(entry, dict) and isinstance(entry.get("content"), str) and entry["content"].strip():
30 filtered.append({"content": entry["content"]})
31 return filtered
32
33
34def post_message(webhook, payload):
35 body = json.dumps(payload).encode("utf-8")
36 request = urllib.request.Request(
37 webhook,
38 data=body,
39 headers={"Content-Type": "application/json"},
40 method="POST",
41 )
42 with urllib.request.urlopen(request, timeout=20) as response:
43 return response.status
44
45
46def parse_retry_after_seconds(headers, details):
47 retry_after = headers.get("Retry-After") if headers else None
48 if retry_after:
49 try:
50 return max(float(retry_after), 0.0)
51 except ValueError:
52 pass
53
54 try:
55 data = json.loads(details)
56 if isinstance(data, dict) and "retry_after" in data:
57 value = float(data["retry_after"])
58 return max(value, 0.0)
59 except Exception:
60 pass
61
62 return 2.0
63
64
65def main():
66 parser = argparse.ArgumentParser()
67 parser.add_argument("--webhook", required=True)
68 parser.add_argument("--messages-file", required=True)
69 parser.add_argument("--label", default="messages")
70 parser.add_argument("--max-messages", type=int, default=5)
71 parser.add_argument("--delay-seconds", type=float, default=1.5)
72 parser.add_argument("--max-retries", type=int, default=3)
73 args = parser.parse_args()
74
75 block_file = os.environ.get("DISCORD_BLOCK_FILE", ".discord_webhook_blocked")
76 if os.path.exists(block_file):
77 print(f"[discord] webhook is marked blocked ({block_file}); skipping {args.label}")
78 return 0
79
80 messages = load_messages(args.messages_file)
81 if not messages:
82 print(f"[discord] no {args.label} messages to send")
83 return 0
84
85 if args.max_messages > 0 and len(messages) > args.max_messages:
86 print(
87 f"[discord] limiting {args.label} messages from {len(messages)} to {args.max_messages}",
88 )
89 messages = messages[: args.max_messages]
90
91 for idx, payload in enumerate(messages, start=1):
92 attempt = 0
93 while True:
94 attempt += 1
95 try:
96 status = post_message(args.webhook, payload)
97 if status >= 300:
98 print(f"[discord] {args.label} message {idx} failed with status {status}; skipping remainder")
99 return 0
100 break
101 except urllib.error.HTTPError as exc:
102 details = exc.read().decode("utf-8", errors="replace")
103
104 if exc.code in (401, 403, 404):
105 with open(block_file, "w", encoding="utf-8") as fh:
106 fh.write(f"http={exc.code}\n")
107 fh.write(details)
108 print(
109 f"[discord] {args.label} message {idx} HTTP error {exc.code}: {details}; skipping remainder",
110 )
111 return 0
112
113 if exc.code == 429 and attempt <= args.max_retries:
114 wait_seconds = parse_retry_after_seconds(exc.headers, details)
115 print(
116 f"[discord] {args.label} message {idx} rate limited; retrying in {wait_seconds:.2f}s (attempt {attempt}/{args.max_retries})",
117 )
118 time.sleep(wait_seconds)
119 continue
120
121 if 500 <= exc.code < 600 and attempt <= args.max_retries:
122 wait_seconds = min(2.0 * attempt, 8.0)
123 print(
124 f"[discord] {args.label} message {idx} server error {exc.code}; retrying in {wait_seconds:.2f}s (attempt {attempt}/{args.max_retries})",
125 )
126 time.sleep(wait_seconds)
127 continue
128
129 print(
130 f"[discord] {args.label} message {idx} HTTP error {exc.code}: {details}; skipping remainder",
131 )
132 return 0
133 except Exception as exc:
134 if attempt <= args.max_retries:
135 wait_seconds = min(1.5 * attempt, 6.0)
136 print(
137 f"[discord] {args.label} message {idx} error: {exc}; retrying in {wait_seconds:.2f}s (attempt {attempt}/{args.max_retries})",
138 )
139 time.sleep(wait_seconds)
140 continue
141
142 print(f"[discord] {args.label} message {idx} error: {exc}; skipping remainder")
143 return 0
144
145 time.sleep(max(args.delay_seconds, 0.0))
146
147 print(f"[discord] sent {len(messages)} {args.label} message(s)")
148 return 0
149
150
151if __name__ == "__main__":
152 sys.exit(main())