# ============================================================================== # Author: Simon-Pierre Boucher # File: src/main.py (ka-kick) # Desc: Chaînes Kick détaillées via l'API publique /api/v2/channels/{slug} # (Cloudflare : proxy résidentiel + empreinte Chrome) : abonnés, badge, # live en cours, bio + LIENS SOCIAUX AUTO-DÉCLARÉS (instagram/twitter/ # youtube/tiktok/facebook/discord → cross_link 0.90 côté crea-ka). # ============================================================================== from __future__ import annotations import asyncio import json from apify import Actor from .net import Fetcher API_URL = "https://kick.com/api/v2/channels/{u}" SOCIAL_KEYS = ("instagram", "twitter", "youtube", "tiktok", "facebook", "discord") async def main() -> None: async with Actor: inp = await Actor.get_input() or {} usernames = [u.strip().lstrip("@").lower() for u in (inp.get("usernames") or []) if u and u.strip()] proxy = await Actor.create_proxy_configuration( actor_proxy_input=inp.get("proxyConfiguration")) fetcher = Fetcher(proxy, delay=float(inp.get("requestDelay") or 1)) sem = asyncio.Semaphore(int(inp.get("concurrency") or 3)) async def one(u: str) -> None: async with sem: miss = {"kind": "profile", "platform": "kick", "username": u, "found": False} resp = None for attempt in range(3): # mur Cloudflare → nouvelle IP try: resp = await fetcher.get( API_URL.format(u=u), session_id=f"{u}k{attempt}", headers={"Accept": "application/json"}) except Exception as exc: await Actor.push_data({**miss, "error": str(exc)[:200]}) return if resp.status_code == 404: await Actor.push_data({**miss, "error": "http_404"}) return if resp.status_code == 200 and \ (resp.text or "").startswith("{"): break if resp.status_code != 200 or \ not (resp.text or "").startswith("{"): await Actor.push_data( {**miss, "error": f"http_{resp.status_code}"}) return try: ch = json.loads(resp.text) except Exception: await Actor.push_data({**miss, "error": "bad_json"}) return user = ch.get("user") or {} live = ch.get("livestream") or {} cats = [((c.get("category") or {}).get("name")) for c in (ch.get("recent_categories") or []) if isinstance(c, dict)] socials = {k: (user.get(k) or "").strip() for k in SOCIAL_KEYS if (user.get(k) or "").strip()} await Actor.push_data({ "kind": "profile", "platform": "kick", "found": True, "id": ch.get("id"), "username": ch.get("slug"), "full_name": user.get("username"), "biography": user.get("bio"), "followers": ch.get("followers_count") or ch.get("followersCount"), "is_verified": bool(ch.get("verified")), "is_banned": bool(ch.get("is_banned")), "subscription_enabled": ch.get("subscription_enabled"), "is_live_now": bool(live), "live_viewers": live.get("viewer_count"), "live_started_at": (live.get("start_time") or live.get("created_at")), "live_is_mature": (bool(live.get("is_mature")) if live else None), "live_language": live.get("language"), "live_title": (live.get("session_title") or "")[:200] or None, "live_thumbnail": ((live.get("thumbnail") or {}).get("url") if isinstance(live.get("thumbnail"), dict) else None), "live_category": next( (c.get("name") for c in (live.get("categories") or []) if isinstance(c, dict) and c.get("name")), None), "vod_enabled": ch.get("vod_enabled"), "avatar": user.get("profile_pic"), "banner": ((ch.get("banner_image") or {}).get("url") if isinstance(ch.get("banner_image"), dict) else None), "recent_categories": [c for c in cats if c][:5], "social_links": socials, }) await asyncio.gather(*[one(u) for u in usernames]) Actor.log.info(f"terminé : {len(usernames)} chaînes")