b3b6b702d1
- 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
210 lines
7.4 KiB
Python
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()
|