#!/usr/bin/env python3 import html import json import os import re import sys import urllib.request import urllib.error from datetime import datetime, timezone from zoneinfo import ZoneInfo _TZ_PRAGUE = ZoneInfo("Europe/Prague") def load_env(path=".env"): env = {} try: with open(path) as f: for line in f: line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, val = line.partition("=") env[key.strip()] = val.strip().strip('"').strip("'") except FileNotFoundError: pass return env def api_get(url, token): req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"}) try: with urllib.request.urlopen(req) as resp: return json.loads(resp.read().decode()) except urllib.error.HTTPError as e: print(f"HTTP {e.code} při volání {url}: {e.read().decode()}", file=sys.stderr) raise except urllib.error.URLError as e: print(f"Chyba sítě při volání {url}: {e.reason}", file=sys.stderr) raise def clean_content(content): text = re.sub(r"<[^>]+>", " ", content) text = html.unescape(text) text = re.sub(r"#\s+(\w)", r"#\1", text) return re.sub(r"\s+", " ", text).strip() def fetch_daily_timeline(base_url, token): cutoff = datetime.now(timezone.utc).timestamp() - 86400 all_toots = [] max_id = None for _ in range(10): url = f"{base_url}/api/v1/timelines/public?local=true&limit=40" if max_id: url += f"&max_id={max_id}" toots = api_get(url, token) if not toots: break done = False for toot in toots: created_at = datetime.fromisoformat(toot["created_at"].replace("Z", "+00:00")) if created_at.timestamp() < cutoff: done = True break all_toots.append(toot) if done: break max_id = toots[-1]["id"] return all_toots def main(): env = {**load_env(), **os.environ} for var in ("NOVINKY_TOKEN", "INSTANCE_URL", "STATS_TOKEN"): if not env.get(var): print(f"Chybí proměnná prostředí: {var}", file=sys.stderr) sys.exit(1) token = env["NOVINKY_TOKEN"] stats_token = env["STATS_TOKEN"] base_url = env["INSTANCE_URL"].rstrip("/") try: timeline = fetch_daily_timeline(base_url, token) except Exception: sys.exit(1) tag_counts = {} candidates = [] media_count = {"image": 0, "video": 0, "gifv": 0, "audio": 0, "total": 0} hourly_count = {str(h): 0 for h in range(24)} for toot in timeline: created_at = datetime.fromisoformat(toot["created_at"].replace("Z", "+00:00")) local_hour = str(created_at.astimezone(_TZ_PRAGUE).hour) hourly_count[local_hour] += 1 for att in toot.get("media_attachments", []): att_type = att.get("type", "") media_count["total"] += 1 if att_type in media_count: media_count[att_type] += 1 if toot.get("language") != "cs": continue if "@" in toot.get("account", {}).get("acct", ""): continue text = clean_content(toot.get("content", "")) if len(text) < 10: continue for tag in toot.get("tags", []): name = tag["name"] tag_counts[name] = tag_counts.get(name, 0) + 1 reblogs = toot.get("reblogs_count", 0) favourites = toot.get("favourites_count", 0) candidates.append({ "acct": toot["account"]["acct"], "text": text, "url": toot.get("url", ""), "reblogs": reblogs, "favourites": favourites, "score": reblogs + favourites, }) candidates.sort(key=lambda x: x["score"], reverse=True) top = candidates[:3] tags = [t for t, _ in sorted(tag_counts.items(), key=lambda x: x[1], reverse=True)[:5]] authors_count = {} for c in candidates: authors_count[c["acct"]] = authors_count.get(c["acct"], 0) + 1 newest_account = None try: accounts = api_get( f"{base_url}/api/v1/admin/accounts?order=newest&limit=1", stats_token ) if accounts and accounts[0].get("account", {}).get("discoverable"): newest_account = { "acct": accounts[0]["account"]["acct"], "created_at": accounts[0]["created_at"], } except Exception: pass top_links = [] try: links_data = api_get(f"{base_url}/api/v1/trends/links?limit=3", token) for link in links_data[:3]: top_links.append({ "url": link.get("url", ""), "title": link.get("title", ""), "description": link.get("description", ""), "provider_name": link.get("provider_name", ""), }) except Exception: pass today = datetime.now(timezone.utc).strftime("%Y-%m-%d") os.makedirs("data", exist_ok=True) out_path = os.path.join("data", f"{today}.json") with open(out_path, "w", encoding="utf-8") as f: json.dump({ "date": today, "total_count": len(timeline), "authors_count": authors_count, "newest_account": newest_account, "top": top, "tags": tags, "top_links": top_links, "media_count": media_count, "hourly_count": hourly_count, }, f, ensure_ascii=False, indent=2) print(f"Uloženo: {out_path} ({len(timeline)} tootů načteno, {len(top)} top, {len(tags)} hashtagů)") if __name__ == "__main__": main()