Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Diagnostic helper for inspecting public Steam News API payloads. | |
| Defaults to Going Medieval (appid 1029780), which recently had a SteamDB patch | |
| note marked as "Major". The goal is to inspect what fields are actually exposed | |
| by the public ISteamNews/GetNewsForApp endpoint. | |
| Examples: | |
| python scripts/fetch_steam_news.py | |
| python scripts/fetch_steam_news.py --appid 1029780 --count 20 --raw | |
| python scripts/fetch_steam_news.py --appid 1029780 --contains "1.0" | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from collections import Counter | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| import httpx | |
| STEAM_NEWS_API_URL = "https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/" | |
| DEFAULT_APP_ID = 1029780 | |
| DEFAULT_COUNT = 20 | |
| def _parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser( | |
| description="Inspect recent Steam News API payloads for a game." | |
| ) | |
| parser.add_argument( | |
| "--appid", | |
| default=str(DEFAULT_APP_ID), | |
| help=f"Steam appid to inspect (default: {DEFAULT_APP_ID} for Going Medieval).", | |
| ) | |
| parser.add_argument( | |
| "--count", | |
| type=int, | |
| default=DEFAULT_COUNT, | |
| help=f"Number of news items to fetch (default: {DEFAULT_COUNT}).", | |
| ) | |
| parser.add_argument( | |
| "--maxlength", | |
| type=int, | |
| default=0, | |
| help="Steam API maxlength parameter (default: 0).", | |
| ) | |
| parser.add_argument( | |
| "--contains", | |
| default=None, | |
| help="Only print items whose title or contents contain this substring.", | |
| ) | |
| parser.add_argument( | |
| "--raw", | |
| action="store_true", | |
| help="Dump full raw JSON response after the human-readable summary.", | |
| ) | |
| parser.add_argument( | |
| "--output", | |
| default=None, | |
| help="Optional path to save the raw JSON response.", | |
| ) | |
| return parser.parse_args() | |
| def _format_timestamp(value: object) -> str | None: | |
| if not isinstance(value, int) or value <= 0: | |
| return None | |
| return datetime.fromtimestamp(value, tz=timezone.utc).isoformat() | |
| def _matches_filter(item: dict, needle: str | None) -> bool: | |
| if not needle: | |
| return True | |
| haystacks = [ | |
| str(item.get("title", "")), | |
| str(item.get("contents", "")), | |
| str(item.get("feedlabel", "")), | |
| str(item.get("feedname", "")), | |
| ] | |
| lowered = needle.lower() | |
| return any(lowered in text.lower() for text in haystacks) | |
| def _print_summary(app_id: str, news_items: list[dict]) -> None: | |
| print("=" * 80) | |
| print(f"Steam News API inspection for appid {app_id}") | |
| print(f"Items returned: {len(news_items)}") | |
| all_keys = sorted({key for item in news_items for key in item.keys()}) | |
| print(f"Observed keys: {', '.join(all_keys) if all_keys else '(none)'}") | |
| feedlabels = Counter(str(item.get("feedlabel", "")) for item in news_items) | |
| feednames = Counter(str(item.get("feedname", "")) for item in news_items) | |
| print(f"feedlabel values: {dict(feedlabels)}") | |
| print(f"feedname values: {dict(feednames)}") | |
| print("=" * 80) | |
| def _print_items(news_items: list[dict]) -> None: | |
| for index, item in enumerate(news_items, start=1): | |
| title = item.get("title", "") | |
| date_iso = _format_timestamp(item.get("date")) | |
| print(f"[{index}] {title}") | |
| print(f" date: {date_iso}") | |
| print(f" gid: {item.get('gid')}") | |
| print(f" url: {item.get('url')}") | |
| print(f" author: {item.get('author')}") | |
| print(f" feedlabel: {item.get('feedlabel')}") | |
| print(f" feedname: {item.get('feedname')}") | |
| print(f" tags: {item.get('tags')}") | |
| if "feed_type" in item: | |
| print(f" feed_type: {item.get('feed_type')}") | |
| if "announcement_body" in item: | |
| print(" announcement_body: present") | |
| contents = str(item.get("contents", "")) | |
| preview = contents[:220].replace("\n", " ").strip() | |
| print(f" contents_preview: {preview}") | |
| print(f" contents_length: {len(contents)}") | |
| print(f" keys: {sorted(item.keys())}") | |
| print() | |
| def main() -> int: | |
| args = _parse_args() | |
| params = { | |
| "appid": args.appid, | |
| "count": args.count, | |
| "maxlength": args.maxlength, | |
| } | |
| try: | |
| with httpx.Client(timeout=30.0) as client: | |
| response = client.get(STEAM_NEWS_API_URL, params=params) | |
| response.raise_for_status() | |
| payload = response.json() | |
| except Exception as exc: # deliberate broad catch for diagnostics | |
| print(f"Steam News request failed: {exc}", file=sys.stderr) | |
| return 1 | |
| appnews = payload.get("appnews", {}) | |
| news_items = appnews.get("newsitems", []) | |
| filtered_items = [item for item in news_items if _matches_filter(item, args.contains)] | |
| _print_summary(str(args.appid), news_items) | |
| if args.contains: | |
| print(f"Filter substring: {args.contains!r}") | |
| print(f"Filtered items: {len(filtered_items)}") | |
| print("-" * 80) | |
| _print_items(filtered_items) | |
| if args.output: | |
| output_path = Path(args.output) | |
| output_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8") | |
| print(f"Raw JSON saved to: {output_path}") | |
| if args.raw: | |
| print("=" * 80) | |
| print("Raw JSON") | |
| print("=" * 80) | |
| print(json.dumps(payload, indent=2, ensure_ascii=False)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |