import json import time import logging from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import Flow from googleapiclient.discovery import build from google.auth.transport.requests import Request from googleapiclient.errors import HttpError from django.conf import settings from django.utils import timezone from apps.social.utils.google import SCOPES, API_VERSION_MYBUSINESS, API_VERSION_ACCOUNT_MGMT logger = logging.getLogger(__name__) class GoogleAPIError(Exception): pass class GoogleBusinessService: @staticmethod def _get_credentials_object(account): creds_dict = account.credentials_json if 'token' not in creds_dict: raise GoogleAPIError("Missing token.") creds = Credentials.from_authorized_user_info(creds_dict, SCOPES) if creds.expired and creds.refresh_token: try: # FIX: Model field is 'name', not 'account_name' logger.info(f"Refreshing token for {account.name}...") creds.refresh(Request()) account.credentials_json = json.loads(creds.to_json()) account.save() except Exception as e: raise GoogleAPIError(f"Token refresh failed: {e}") return creds @staticmethod def get_service(account, api_name='mybusiness', api_version='v4'): creds = GoogleBusinessService._get_credentials_object(account) return build(api_name, api_version, credentials=creds) @staticmethod def get_auth_url(request): flow = Flow.from_client_secrets_file( settings.GMB_CLIENT_SECRETS_FILE, scopes=SCOPES, redirect_uri=settings.GMB_REDIRECT_URI ) state = request.session.session_key flow.redirect_uri = settings.GMB_REDIRECT_URI auth_url, _ = flow.authorization_url(access_type='offline', prompt='consent', state=state) return auth_url @staticmethod def exchange_code_for_token(code): flow = Flow.from_client_secrets_file( settings.GMB_CLIENT_SECRETS_FILE, scopes=SCOPES, redirect_uri=settings.GMB_REDIRECT_URI ) try: flow.fetch_token(code=code) return json.loads(flow.credentials.to_json()) except Exception as e: raise GoogleAPIError(f"Token exchange failed: {e}") @staticmethod def fetch_locations(account): service = GoogleBusinessService.get_service(account, 'mybusinessaccountmanagement', API_VERSION_ACCOUNT_MGMT) locations = [] page_token = None while True: try: request = service.accounts().listLocations( parent=account.account_id, # Assuming account_id is stored correctly (e.g., "accounts/123") pageSize=100, pageToken=page_token, readMask="name,title,storeCode" ) response = request.execute() locations.extend(response.get('locations', [])) page_token = response.get('nextPageToken') if not page_token: break time.sleep(0.5) except HttpError as e: logger.error(f"Error fetching locations: {e}") break return locations @staticmethod def fetch_reviews_delta(account, location): """ Fetches reviews. 'location' argument here is an instance of SocialContent model. """ service = GoogleBusinessService.get_service(account, 'mybusiness', API_VERSION_MYBUSINESS) reviews = [] next_page_token = None while True: try: request = service.accounts().locations().reviews().list( # FIX: Model field is 'content_id', not 'location_id' parent=location.content_id, pageSize=50, pageToken=next_page_token, orderBy="update_time desc" ) response = request.execute() batch = response.get('reviews', []) for r_data in batch: update_str = r_data.get('updateTime') # Note: You are manually handling 'Z' here. parse_datetime() is safer, # but this works if you prefer it. if update_str.endswith('Z'): update_str = update_str[:-1] + '+00:00' try: r_time = timezone.make_aware(timezone.datetime.strptime(update_str, "%Y-%m-%dT%H:%M:%S%z")) except: r_time = timezone.now() # FIX: Model field is 'last_comment_sync_at', not 'last_review_sync_at' if r_time <= location.last_comment_sync_at: return reviews reviews.append(r_data) next_page_token = response.get('nextPageToken') if not next_page_token: break time.sleep(0.5) except HttpError as e: if e.resp.status == 429: time.sleep(10) continue logger.error(f"API Error fetching reviews: {e}") break return reviews @staticmethod def post_reply(account, review_name, comment_text): service = GoogleBusinessService.get_service(account, 'mybusiness', API_VERSION_MYBUSINESS) try: request = service.accounts().locations().reviews().reply( name=review_name, body={'comment': comment_text} ) return request.execute() except HttpError as e: raise GoogleAPIError(f"Failed to post reply: {e}")