sentimentstream-worker / scripts /fetch_steam_news.py
GitHub Action
deploy: worker release from GitHub
8ff1b66
#!/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())