Initial version
This commit is contained in:
102
set-channel.py
Executable file
102
set-channel.py
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Update Twitch channel info (game + title) for the broadcaster account.
|
||||
|
||||
./set-channel.py --game-id 517520 "Slayer is back — Nightmare run"
|
||||
./set-channel.py "Doom Eternal" "..." # exact-name lookup, fails if no exact hit
|
||||
./set-channel.py --dry-run --game-id 517520 "..."
|
||||
|
||||
For free-form / fuzzy game queries, use search-game.py first to resolve the
|
||||
id (loading.sh does this automatically). The /helix/games?name= fallback in
|
||||
this script is exact-and-case-sensitive.
|
||||
|
||||
Loads `.env.ophi118` — see _twitch.py for the env contract. The token MUST
|
||||
belong to the broadcaster (Twitch enforces broadcaster_id == token user_id
|
||||
on PATCH /helix/channels), so this script intentionally targets that file
|
||||
and not `.env` (which holds the chat-bot's account).
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import urllib.parse
|
||||
|
||||
import _twitch as tw
|
||||
|
||||
TITLE_LIMIT = 140
|
||||
|
||||
|
||||
def lookup_game_id(env: dict, name: str) -> str:
|
||||
qs = urllib.parse.urlencode({"name": name})
|
||||
status, body = tw.auth_call("GET", f"{tw.HELIX}/games?{qs}", env)
|
||||
if status != 200:
|
||||
raise RuntimeError(f"games lookup failed (status={status}): {body}")
|
||||
data = (body or {}).get("data") or []
|
||||
if not data:
|
||||
raise RuntimeError(
|
||||
f"no Twitch game matches {name!r} exactly. /helix/games?name= is "
|
||||
f"case-sensitive — use search-game.py for fuzzy lookup."
|
||||
)
|
||||
return data[0]["id"]
|
||||
|
||||
|
||||
def patch_channel(env: dict, game_id: str, title: str) -> None:
|
||||
qs = urllib.parse.urlencode({"broadcaster_id": env["TWITCH_BROADCASTER_ID"]})
|
||||
payload = json.dumps({"game_id": game_id, "title": title})
|
||||
status, body = tw.auth_call("PATCH", f"{tw.HELIX}/channels?{qs}", env, body=payload)
|
||||
if status not in (200, 204):
|
||||
raise RuntimeError(f"channel update failed (status={status}): {body}")
|
||||
|
||||
|
||||
def usage_and_die():
|
||||
print("usage: set-channel.py [--dry-run] (--game-id <id> | <game-name>) <title>",
|
||||
file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
dry = False
|
||||
if args and args[0] == "--dry-run":
|
||||
dry, args = True, args[1:]
|
||||
|
||||
game_id = None
|
||||
if args and args[0] == "--game-id":
|
||||
if len(args) < 3:
|
||||
usage_and_die()
|
||||
game_id, args = args[1], args[2:]
|
||||
|
||||
expected = 1 if game_id else 2
|
||||
if len(args) != expected:
|
||||
usage_and_die()
|
||||
|
||||
if game_id:
|
||||
title = args[0][:TITLE_LIMIT]
|
||||
else:
|
||||
game_name = args[0]
|
||||
title = args[1][:TITLE_LIMIT]
|
||||
|
||||
env = tw.load_env()
|
||||
needed = ("TWITCH_ACCESS_TOKEN", "TWITCH_REFRESH_TOKEN",
|
||||
"TWITCH_CLIENT_ID", "TWITCH_BROADCASTER_ID")
|
||||
missing = [k for k in needed if not env.get(k)]
|
||||
if missing:
|
||||
print(f"[err] missing in {tw.ENV_FILE.name}: {', '.join(missing)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if game_id is None:
|
||||
game_id = lookup_game_id(env, game_name)
|
||||
|
||||
if dry:
|
||||
print(f" ✓ dry-run: would set game_id={game_id}, title={title!r} (no PATCH issued)")
|
||||
return
|
||||
|
||||
patch_channel(env, game_id, title)
|
||||
print(f" ✓ Twitch channel updated → game_id={game_id}, title={title!r}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print(f"[err] {type(e).__name__}: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user