# ============================================================================== # Author: Simon-Pierre Boucher # File: src/main.py (ka-discord) # Desc: Serveurs Discord via l'API PUBLIQUE d'invitation # /api/v9/invites/{code}?with_counts=true : membres approximatifs, # présents en ligne, nom/description/boosts du serveur. Le CODE d'invit # est SENSIBLE À LA CASSE → il vient de l'URL, pas du handle minusculé. # ============================================================================== from __future__ import annotations import asyncio import json from apify import Actor from .net import Fetcher INVITE_API = ("https://discord.com/api/v9/invites/{code}" "?with_counts=true&with_expiration=true") async def main() -> None: async with Actor: inp = await Actor.get_input() or {} # ici « usernames » = codes d'invitation (casse préservée par l'appelant) codes = [c.strip() for c in (inp.get("usernames") or []) if c.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 6)) async def one(code: str) -> None: async with sem: miss = {"kind": "profile", "platform": "discord", "username": code, "found": False} try: resp = await fetcher.get( INVITE_API.format(code=code), session_id=code[:12], 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": "invite_invalide"}) return if resp.status_code != 200: await Actor.push_data( {**miss, "error": f"http_{resp.status_code}"}) return try: d = json.loads(resp.text) except Exception: await Actor.push_data({**miss, "error": "bad_json"}) return guild = d.get("guild") or {} icon = guild.get("icon") gid = guild.get("id") banner_hash = guild.get("banner") splash_hash = guild.get("splash") await Actor.push_data({ "kind": "profile", "platform": "discord", "found": True, "username": code, "guild_id": gid, "full_name": guild.get("name"), "biography": guild.get("description"), "followers": d.get("approximate_member_count"), # membres "members": d.get("approximate_member_count"), "online": d.get("approximate_presence_count"), "boosts": guild.get("premium_subscription_count"), "verified": ("VERIFIED" in (guild.get("features") or [])), "partnered": ("PARTNERED" in (guild.get("features") or [])), "channel": (d.get("channel") or {}).get("name"), "avatar": (f"https://cdn.discordapp.com/icons/{gid}/{icon}.png" if gid and icon else None), "banner": (f"https://cdn.discordapp.com/banners/{gid}/" f"{banner_hash}.png?size=1024" if gid and banner_hash else None), "splash": (f"https://cdn.discordapp.com/splashes/{gid}/" f"{splash_hash}.jpg?size=1024" if gid and splash_hash else None), "vanity_url_code": guild.get("vanity_url_code"), "features": sorted(guild.get("features") or [])[:12], "nsfw_level": guild.get("nsfw_level"), "premium_tier": guild.get("premium_tier"), "invite_expires_at": d.get("expires_at"), "inviter": ((d.get("inviter") or {}).get("username") or None), }) await asyncio.gather(*[one(c) for c in codes]) Actor.log.info(f"terminé : {len(codes)} invitations")