159 lines
5.9 KiB
Python
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}") |