Files
BSN-Chatsystem/fetch_ks_kw26.py
T
Hermes Agent b3b6b702d1 chore: vollständiger Workspace-Snapshot 03.07.2026
- publish_gatekeeper.py (neues Gatekeeper-System)
- 4 Premium-Artikel: Veggie Match, Berlin 1960, Wild Tiled West, Scream Park
- Alle kumulierten Artikel, Scripts, Medien und Caches
2026-07-03 11:35:39 +02:00

210 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""
Final: Fetch ALL KW26 data and save comprehensive JSON.
Combines known article campaigns + additional KW26-ending campaigns from KS discover API.
"""
import sys
import json
import time
import re
from datetime import datetime, timezone
sys.path.insert(0, "/home/hermes/workspace/bsn-chatbot")
from kickstarter_fetcher import fetch_campaign, status_report, FLARESOLVERR_URL
import requests
# ── Known campaigns from the article ──────────────────────────
CAMPAIGNS = [
("Cult of the Lamb", "https://www.kickstarter.com/projects/paperfortgames/cult-of-the-lamb-the-board-game"),
("Monolith", "https://www.kickstarter.com/projects/plaidhatgames/monolith-1"),
("Yumegari", "https://www.kickstarter.com/projects/lucky-duck-games/yumegari"),
("Defenders of Hogwarts", "https://www.kickstarter.com/projects/minalima/defenders-of-hogwarts"),
("CARTA IMPERIA", "https://www.kickstarter.com/projects/guntowergames/carta-imperia"),
("Headliner: Encore", "https://www.kickstarter.com/projects/chriscouch/headliner-encore-campaign"),
("OrganATTACK: Below the Belt", "https://www.kickstarter.com/projects/theawkwardyeti/organattack-below-the-belt-edition"),
("Fictitious", "https://www.kickstarter.com/projects/11017881/fictitious-a-mythical-card-game-of-creature-crafting"),
]
def fetch_discover_kw26():
"""Fetch all KW26-ending tabletop game campaigns from KS discover API."""
kw26_start = datetime(2026, 6, 23, tzinfo=timezone.utc)
kw26_end = datetime(2026, 6, 29, 23, 59, 59, tzinfo=timezone.utc)
all_projects = []
for page in range(1, 8):
r = requests.post(
FLARESOLVERR_URL,
json={
"cmd": "request.get",
"url": f"https://www.kickstarter.com/discover/advanced?category_id=34&sort=end_date&state=live&page={page}&format=json",
"maxTimeout": 60000,
},
timeout=90,
)
data = r.json()
html = data.get("solution", {}).get("response", "")
json_match = re.search(r"<pre>(.*?)</pre>", html, re.DOTALL)
if not json_match:
break
discover = json.loads(json_match.group(1))
projects = discover.get("projects", [])
if not projects:
break
for p in projects:
deadline_ts = p.get("deadline", 0)
if not deadline_ts:
continue
dt = datetime.fromtimestamp(deadline_ts, tz=timezone.utc)
if kw26_start <= dt <= kw26_end:
all_projects.append(p)
time.sleep(1)
return all_projects
def campaign_to_dict(c, list_name="", extra=None):
"""Convert KickstarterCampaign to a JSON-serializable dict."""
if c is None:
base = {"error": "fetch failed"}
if extra:
base.update(extra)
base["list_name"] = list_name
return base
pct = None
if c.pledged and c.goal:
try:
pledged_num = float(c.pledged.replace("$", "").replace(",", "").replace("", "").replace("£", ""))
goal_num = float(c.goal.replace("$", "").replace(",", "").replace("", "").replace("£", ""))
if goal_num > 0:
pct = round(pledged_num / goal_num * 100, 1)
except:
pass
d = {
"name": c.title,
"url": c.url,
"pledged": c.pledged,
"goal": c.goal,
"percent_funded": pct,
"backers": c.backers,
"days_left": c.days_left,
"status": c.status,
"description": (c.description or "")[:200],
"source": c.source,
"fetched_at": c.fetched_at,
"list_name": list_name,
}
if extra:
d.update(extra)
return d
def main():
results = []
print("=" * 60)
print("KW26 Kickstarter Board Game Campaigns — LIVE Data")
print("=" * 60)
# ── Step 1: Fetch known campaigns ──
print("\n── 8 Known Campaigns from Article ──")
for name, url in CAMPAIGNS:
print(f"\n [{name}] {url}")
c = fetch_campaign(url, timeout=90)
print(f" {status_report(c)}")
results.append(campaign_to_dict(c, list_name=name))
time.sleep(2)
# ── Step 2: Fetch additional KW26 campaigns from discover API ──
print("\n── Searching KS Discover API for KW26 Tabletop Games ──")
additional = fetch_discover_kw26()
print(f" Found {len(additional)} additional KW26 campaigns")
known_urls = {url for _, url in CAMPAIGNS}
for p in additional:
url = p.get("urls", {}).get("web", {}).get("project", "")
if not url or url in known_urls:
continue
name = p.get("name", "?")
currency = p.get("currency", "USD")
pledged = p.get("pledged", 0)
goal = p.get("goal", 0)
backers = p.get("backers_count", 0)
deadline_ts = p.get("deadline", 0)
state = p.get("state", "?")
blurb = p.get("blurb", "")
dt = datetime.fromtimestamp(deadline_ts, tz=timezone.utc)
days_left = max(0, (dt - datetime.now(timezone.utc)).days)
end_date = dt.strftime("%Y-%m-%d")
pct = round(pledged / goal * 100, 1) if goal > 0 else 0
# Format amounts with currency
if currency == "EUR":
pledged_str = f"{pledged:,.0f}"
goal_str = f"{goal:,.0f}"
elif currency == "GBP":
pledged_str = f"£{pledged:,.0f}"
goal_str = f"£{goal:,.0f}"
elif currency == "MXN":
pledged_str = f"MX${pledged:,.0f}"
goal_str = f"MX${goal:,.0f}"
else:
pledged_str = f"${pledged:,.0f}"
goal_str = f"${goal:,.0f}"
print(f"\n [{name[:60]}] {url}")
print(f" {pledged_str} / {goal_str} ({pct}%) | {backers} backers | {days_left}d left | ends {end_date} | {state}")
results.append({
"name": name,
"url": url,
"pledged": pledged_str,
"goal": goal_str,
"percent_funded": pct,
"backers": backers,
"days_left": days_left,
"end_date": end_date,
"status": state,
"description": (blurb or "")[:200],
"source": "discover_api",
"fetched_at": time.strftime("%Y-%m-%d %H:%M:%S"),
"list_name": "additional_kw26",
})
# ── Step 3: Save ──
output_path = "/home/hermes/workspace/ks_daten_kw26.json"
with open(output_path, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"\n{'=' * 60}")
print(f"Saved {len(results)} campaigns to {output_path}")
# ── Summary table ──
print(f"\n{'' * 90}")
print(f"{'Name':<38} {'Pledged':>14} {'Goal':>14} {'%':>8} {'Backers':>8} {'Days':>5} {'Status':>12}")
print(f"{'' * 90}")
for r in results:
name = (r.get("list_name") or r.get("name", ""))[:37]
pledged = r.get("pledged", "?")
goal = r.get("goal", "?")
pct = f"{r['percent_funded']}%" if r.get("percent_funded") is not None else "?"
backers = f"{r.get('backers', 0):,}" if r.get("backers") else "?"
days = str(r.get("days_left", "?"))
status = r.get("status", "?")
print(f"{name:<38} {pledged:>14} {goal:>14} {pct:>8} {backers:>8} {days:>5} {status:<12}")
print(f"{'' * 90}")
if __name__ == "__main__":
main()