from typing import Any from fansifter_common.httpclient.base import HTTPClient from dmp.adapters.google.models import ( AccessToken, AdAccountApiResponse, AdAccountResourceResponse, Audience, AudienceApiResponse, CampaignApiResponse, GoogleGrantType, GoogleTokenRequest, NestedAdAccountApiResponse, RefreshToken, User, ) from dmp.core.types import PlainToken from .exceptions import GoogleClientError class GoogleClient(HTTPClient): exception_class = GoogleClientError api_version = "v22" DEFAULT_REQUEST_TIMEOUT = 10 def __init__( self, client_id: str, client_secret: str, developer_token: str, default_timeout: int | None = None, ) -> None: self.client_id = client_id self.client_secret = client_secret self.developer_token = developer_token super().__init__( client_options={"timeout": default_timeout or self.DEFAULT_REQUEST_TIMEOUT} ) def _request_token[T]( self, client_id: str, client_secret: str, request: GoogleTokenRequest, type: type[T], ) -> T: return self.request( "POST", "https://oauth2.googleapis.com/token", json={ "client_id": client_id, "client_secret": client_secret, **request.model_dump(exclude_unset=True), }, type=type, ) def get_refresh_token( self, auth_code: PlainToken, redirect_uri: str, code_verifier: str, ) -> RefreshToken: request = GoogleTokenRequest( grant_type=GoogleGrantType.AUTHORIZATION_CODE, code=auth_code, redirect_uri=redirect_uri, code_verifier=code_verifier, ) return self._request_token( client_id=self.client_id, client_secret=self.client_secret, request=request, type=RefreshToken, ) def get_access_token( self, refresh_token: PlainToken, ) -> AccessToken: request = GoogleTokenRequest( grant_type=GoogleGrantType.REFRESH_TOKEN, refresh_token=refresh_token, ) return self._request_token( client_id=self.client_id, client_secret=self.client_secret, request=request, type=AccessToken, ) def get_user(self, access_token: PlainToken) -> User: response = self.request( "GET", "https://www.googleapis.com/oauth2/v1/userinfo", type=User, headers={"Authorization": f"Bearer {access_token}"}, ) return response def get_ad_account_resources( self, access_token: PlainToken ) -> AdAccountResourceResponse: response = self.request( "GET", f"https://googleads.googleapis.com/{self.api_version}/customers:listAccessibleCustomers", type=AdAccountResourceResponse, headers={ "Authorization": f"Bearer {access_token}", "developer-token": self.developer_token, }, ) return response def _get_customer_search_results[T]( self, customer_id: int, access_token: PlainToken, json: dict[str, Any], type: type[T], login_customer_id: int | None, ) -> T: headers = { "Authorization": f"Bearer {access_token}", "developer-token": self.developer_token, } if login_customer_id: headers["login-customer-id"] = str(login_customer_id) response = self.request( "POST", f"https://googleads.googleapis.com/{self.api_version}/customers/{customer_id}/googleAds:search", type=type, headers=headers, json=json, ) return response def get_ad_account_details( self, access_token: PlainToken, ad_account_id: int, parent_ad_account_id: int | None, ) -> AdAccountApiResponse: body = { "query": """ SELECT customer.id, customer.descriptive_name, customer.status, customer.test_account, customer_user_access.access_role FROM customer_user_access """ } return self._get_customer_search_results( customer_id=ad_account_id, access_token=access_token, json=body, type=AdAccountApiResponse, login_customer_id=parent_ad_account_id, ) def get_nested_ad_account_details( self, access_token: PlainToken, ad_account_id: int ) -> NestedAdAccountApiResponse: body = { "query": """ SELECT customer_client.id, customer_client.client_customer, customer_client.descriptive_name, customer_client.status, customer_client.test_account FROM customer_client WHERE customer_client.level = 1 """ } return self._get_customer_search_results( customer_id=ad_account_id, access_token=access_token, json=body, type=NestedAdAccountApiResponse, login_customer_id=None, ) def get_campaign_ids( self, access_token: PlainToken, ad_account_id: int, login_customer_id: int | None, ) -> CampaignApiResponse: body = { "query": """ SELECT campaign.id FROM campaign LIMIT 1 """, "searchSettings": { "returnTotalResultsCount": True, }, } return self._get_customer_search_results( customer_id=ad_account_id, access_token=access_token, json=body, type=CampaignApiResponse, login_customer_id=login_customer_id, ) def create_user_list( self, access_token: PlainToken, ad_account_id: str, name: str, description: str | None, login_customer_id: str | None, ) -> Audience: body = { "operations": [ { "create": { "name": name, "description": description or "", "membershipLifeSpan": "540", "crmBasedUserList": { "uploadKeyType": "CONTACT_INFO", "dataSourceType": "FIRST_PARTY", }, } } ] } headers = { "Authorization": f"Bearer {access_token}", "developer-token": self.developer_token, } if login_customer_id: headers["login-customer-id"] = login_customer_id response = self.request( "POST", f"https://googleads.googleapis.com/{self.api_version}/customers/{ad_account_id}/userLists:mutate", type=AudienceApiResponse, headers=headers, json=body, ) return response.results[0]