Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)
Python 73.6%
HTML 13.2%
TypeScript 6%
JavaScript 4.5%
CSS 1.7%
Dockerfile 0.6%
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File: src/main.py (ka-fansly)4# Desc: Profils Fansly via l'API publique apiv3.fansly.com/api/v1/account5# (?usernames=) : abonnés (followCount), bio, badge, compteurs de6# médias (images/vidéos), avatar/bannière. Lots de 20 handles/requête.7# ==============================================================================8from __future__ import annotations910import asyncio11import json1213from apify import Actor1415from .net import Fetcher1617API_URL = ("https://apiv3.fansly.com/api/v1/account"18 "?usernames={us}&ngsw-bypass=true")192021def _loc(media: dict | None) -> str | None:22 """Objet média Fansly → URL de la première variante disponible."""23 if not isinstance(media, dict):24 return None25 for variant in ([media] + (media.get("variants") or [])):26 for loc in (variant.get("locations") or []):27 if loc.get("location"):28 return loc["location"]29 return None303132async def main() -> None:33 async with Actor:34 inp = await Actor.get_input() or {}35 usernames = [u.strip().lstrip("@").lower()36 for u in (inp.get("usernames") or []) if u and u.strip()]37 proxy = await Actor.create_proxy_configuration(38 actor_proxy_input=inp.get("proxyConfiguration"))39 fetcher = Fetcher(proxy, delay=float(inp.get("requestDelay") or 1))4041 found: set[str] = set()42 for i in range(0, len(usernames), 20):43 batch = usernames[i:i + 20]44 try:45 resp = await fetcher.get(API_URL.format(us=",".join(batch)),46 session_id=f"b{i}",47 headers={"Accept":48 "application/json"})49 data = (json.loads(resp.text)50 if resp.status_code == 200 else {})51 except Exception as exc:52 for u in batch:53 await Actor.push_data(54 {"kind": "profile", "platform": "fansly",55 "username": u, "found": False,56 "error": str(exc)[:200]})57 continue58 for acc in (data.get("response") or []):59 u = str(acc.get("username") or "").lower()60 if not u:61 continue62 found.add(u)63 stats = acc.get("timelineStats") or {}64 tiers = [{"name": t.get("name"), "price": t.get("price")}65 for t in (acc.get("subscriptionTiers") or [])66 if isinstance(t, dict) and t.get("name")]67 await Actor.push_data({68 "kind": "profile",69 "platform": "fansly",70 "found": True,71 "id": acc.get("id"),72 "username": u,73 "full_name": acc.get("displayName") or u,74 "biography": (acc.get("about") or "")[:1000],75 "followers": acc.get("followCount"),76 "following": acc.get("followingCount"),77 "is_verified": bool((acc.get("statusInfo") or {})78 .get("verified")79 or acc.get("verified")),80 "posts_count": acc.get("postCount")81 or stats.get("postCount"),82 "images_count": stats.get("imageCount"),83 "videos_count": stats.get("videoCount"),84 "likes": acc.get("accountMediaLikes"),85 "avatar": _loc(acc.get("avatar")),86 "banner": _loc(acc.get("banner")),87 "location": acc.get("location"),88 "account_created_at": acc.get("createdAt"),89 "subscription_tiers": tiers[:5] or None,90 "subscription_price": (tiers[0].get("price")91 if tiers else None),92 })93 for u in batch:94 if u not in found:95 await Actor.push_data(96 {"kind": "profile", "platform": "fansly",97 "username": u, "found": False,98 "error": f"not_found_{resp.status_code}"})99 Actor.log.info(f"terminé : {len(usernames)} profils, "100 f"{len(found)} trouvés")101