diff --git a/docs-site/index.html b/docs-site/index.html index 94e3d00..972850c 100644 --- a/docs-site/index.html +++ b/docs-site/index.html @@ -9,6 +9,9 @@ + + + diff --git a/scripts/triage-reviews.py b/scripts/triage-reviews.py new file mode 100644 index 0000000..cf80b41 --- /dev/null +++ b/scripts/triage-reviews.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +""" +Play Store review triage script. + +Fetches recent Play Store reviews via the Android Publisher API and creates +GitHub issues for low-rated or bug-report reviews so they enter the normal +fix cycle without manual monitoring. + +Required env vars: + GOOGLE_SERVICE_ACCOUNT_JSON — full JSON key for the service account + GH_TOKEN — GitHub token with issues:write scope + PACKAGE_NAME — Android package (cc.agentlabs.opencode) + DAYS_BACK — how many days back to look (default 7) +""" + +import json +import os +import subprocess +import sys +import time +from datetime import datetime, timezone, timedelta + +import google.auth +from google.oauth2 import service_account +from googleapiclient.discovery import build + +REPO = os.environ.get("GITHUB_REPOSITORY", "dzianisv/opencode-mobile") +PACKAGE_NAME = os.environ.get("PACKAGE_NAME", "cc.agentlabs.opencode") +DAYS_BACK = int(os.environ.get("DAYS_BACK", "7")) +GH_TOKEN = os.environ.get("GH_TOKEN", "") + +SCOPES = ["https://www.googleapis.com/auth/androidpublisher"] + + +def get_service(): + raw = os.environ.get("GOOGLE_SERVICE_ACCOUNT_JSON", "") + if not raw: + print("GOOGLE_SERVICE_ACCOUNT_JSON not set — skipping.") + sys.exit(0) + info = json.loads(raw) + creds = service_account.Credentials.from_service_account_info(info, scopes=SCOPES) + return build("androidpublisher", "v3", credentials=creds, cache_discovery=False) + + +def fetch_reviews(service): + cutoff = datetime.now(timezone.utc) - timedelta(days=DAYS_BACK) + results = [] + token = None + while True: + resp = service.reviews().list( + packageName=PACKAGE_NAME, + maxResults=100, + **({"token": token} if token else {}), + ).execute() + for review in resp.get("reviews", []): + comment = review.get("comments", [{}])[0].get("userComment", {}) + ts = int(comment.get("lastModified", {}).get("seconds", 0)) + if not ts: + continue + dt = datetime.fromtimestamp(ts, tz=timezone.utc) + if dt < cutoff: + continue + results.append({ + "review_id": review.get("reviewId", ""), + "author": review.get("authorName", "anonymous"), + "rating": comment.get("starRating", 0), + "text": comment.get("text", ""), + "date": dt.strftime("%Y-%m-%d"), + "lang": comment.get("reviewerLanguage", "en"), + }) + token = resp.get("tokenPagination", {}).get("nextPageToken") + if not token: + break + return results + + +def issue_exists(title_prefix: str) -> bool: + """Check if a GitHub issue with this title prefix already exists.""" + result = subprocess.run( + [ + "gh", "issue", "list", + "--repo", REPO, + "--search", title_prefix, + "--state", "open", + "--json", "title", + ], + capture_output=True, + text=True, + env={**os.environ, "GH_TOKEN": GH_TOKEN}, + ) + if result.returncode != 0: + return False + issues = json.loads(result.stdout or "[]") + return any(title_prefix.lower() in i["title"].lower() for i in issues) + + +def create_issue(review: dict): + stars = "⭐" * review["rating"] + title = f"[Play Store Review] {review['rating']}★ from {review['author']} on {review['date']}" + body = f"""**Rating**: {stars} ({review['rating']}/5) +**Author**: {review['author']} +**Date**: {review['date']} +**Language**: {review['lang']} +**Review ID**: `{review['review_id']}` + +--- + +> {review['text']} + +--- + +*This issue was automatically created by the [monitor-reviews workflow](/.github/workflows/monitor-reviews.yml). +Reply to the user in Play Console if applicable.* + +**Labels**: `user-feedback`, `play-store-review` +""" + subprocess.run( + [ + "gh", "issue", "create", + "--repo", REPO, + "--title", title, + "--body", body, + "--label", "user-feedback", + ], + check=True, + env={**os.environ, "GH_TOKEN": GH_TOKEN}, + ) + print(f" ✅ Created issue: {title}") + + +def main(): + print(f"Fetching Play Store reviews for {PACKAGE_NAME} (last {DAYS_BACK} days)...") + service = get_service() + reviews = fetch_reviews(service) + print(f"Found {len(reviews)} review(s) in window.") + + # Triage: create issues for 1-3 star reviews (potential bugs/problems) + actionable = [r for r in reviews if r["rating"] <= 3] + print(f"Actionable (≤3★): {len(actionable)}") + + for review in actionable: + title_prefix = f"Play Store Review] {review['rating']}★ from {review['author']} on {review['date']}" + if issue_exists(title_prefix): + print(f" ⏭ Already exists: {review['author']} {review['date']}") + continue + create_issue(review) + time.sleep(1) # avoid GitHub API rate limit + + # Summary + avg = sum(r["rating"] for r in reviews) / len(reviews) if reviews else 0 + print(f"\nSummary: {len(reviews)} reviews, avg rating {avg:.1f}★, {len(actionable)} issues created/checked.") + + +if __name__ == "__main__": + main()