HH/apps/social/services/google.py
2026-02-12 15:09:48 +03:00

159 lines
5.9 KiB
Python

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}")