import logging from collections.abc import Iterator from urllib.parse import urljoin from fansifter_common.httpclient.base import HTTPClient from pydantic import TypeAdapter from app.app_types import PlainToken from app.models import CustomAudience from .models import Response logger = logging.getLogger(__name__) CustomAudienceList = TypeAdapter(list[CustomAudience]) class FacebookClient(HTTPClient): base_url = "https://graph.facebook.com" graph_api_version = "v21.0" sensitive_params = { "access_token", } DEFAULT_REQUEST_TIMEOUT = 10 DEFAULT_LIMIT = 1000 def __init__(self, default_timeout: int | None = None) -> None: super().__init__( client_options={"timeout": default_timeout or self.DEFAULT_REQUEST_TIMEOUT} ) def build_url(self, path: str) -> str: if not path.startswith("/"): path = f"/{path}" return urljoin(self.base_url, f"{self.graph_api_version}{path}") def get_audiences( self, ad_account: str, user_access_token: PlainToken ) -> list[CustomAudience]: return list(self.iter_audiences(ad_account, user_access_token)) def iter_audiences( self, ad_account: str, user_access_token: PlainToken, after: str | None = None ) -> Iterator[CustomAudience]: response_obj = self.request( "GET", self.build_url(f"/{ad_account}/customaudiences"), type=Response[list[CustomAudience]], params={ "access_token": user_access_token, "fields": ",".join(["id", "time_created"]), "limit": self.DEFAULT_LIMIT, "after": after, }, ) yield from response_obj.data if response_obj.paging and response_obj.paging.next: yield from self.iter_audiences( ad_account=ad_account, user_access_token=user_access_token, after=response_obj.paging.cursors.after, ) def delete_audiences( self, _ad_account: str, audiences: list[CustomAudience], user_access_token: PlainToken, ) -> None: params = {"access_token": user_access_token} for audience in audiences: self.request( "DELETE", self.build_url(f"/{audience.id}"), params=params, )