# ============================================================================== # Author: Simon-Pierre Boucher # File: creaka/connectors/bio_liens.py # Desc: Connecteur ENRICHISSEMENT « bio-liens » — découverte croisée # inter-plateformes SANS AUCUNE requête : liaison conservatrice à # partir des URLs COMPLÈTES déjà stockées dans les bios publiques. # Palier 1 (§9) — passe en dernier (après link-in-bio). # ============================================================================== """Liaison croisée à partir des bios déjà en base. Une bio publique contient souvent l'URL complète d'un autre compte du même créateur (« youtube.com/@x », « patreon.com/x »…). C'est exactement le signal `cross_link` §12.1 (0.90) : le créateur pointe lui-même vers son autre compte. Passe CONSERVATRICE (vague 2, §12) : uniquement les URLs complètes reconnues par normalize.platform_from_url — jamais de simple « @handle » textuel (ambigu), jamais d'inférence. Aucune requête réseau : tout vient des bios déjà collectées par les autres connecteurs. Cap 5 nouveaux comptes par fiche (garde-fou contre les bios-annuaires). """ from __future__ import annotations import re from ..identity import account from ..normalize import platform_from_url from ..schema import Creator from .base import BaseConnector # URL complète (avec ou sans schéma) — les domaines nus type « exemple.com » # sans chemin ne désignent pas un compte et sont ignorés par platform_from_url _URL_RE = re.compile(r"(?:https?://|www\.)[^\s\"'<>()\[\]{}]+", re.I) MAX_NEW_PER_CREATOR = 5 def extract_bio_accounts(bio: str, existing_keys: set[str]) -> list: """Bio publique → nouveaux comptes cross_link (URLs complètes seulement).""" accounts = [] seen = set(existing_keys) for raw in _URL_RE.findall(bio or ""): url = raw.rstrip(".,;!?…") if not url.lower().startswith("http"): url = "https://" + url hit = platform_from_url(url) if not hit: continue platform, handle = hit key = f"{platform}:{handle}" if key in seen: continue seen.add(key) accounts.append(account(platform, handle, "cross_link", url=url)) if len(accounts) >= MAX_NEW_PER_CREATOR: break return accounts class BioLiensConnector(BaseConnector): source_id = "bio-liens" kind = "enrichment" request_delay = 0.0 # AUCUNE requête réseau def enrich(self, creators: list[Creator]) -> list[Creator]: enriched: list[Creator] = [] for cr in creators: if not cr.bio: continue existing = {a.key for a in cr.platforms} # ne jamais relier une fiche vers son propre handle X/IG déjà connu new_accounts = extract_bio_accounts(cr.bio, existing) if not new_accounts: continue cr.platforms.extend(a.finalize() for a in new_accounts) enriched.append(cr) return enriched