From 4c2f6793de89e6384a8949618e182c4ac4c0f4f3 Mon Sep 17 00:00:00 2001
From: Dennis V <2119348+dzianisv@users.noreply.github.com>
Date: Tue, 23 Jun 2026 20:18:15 +0000
Subject: [PATCH] feat: add Vercel analytics to website + Play Store review
triage script
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- docs-site/index.html: add Vercel Analytics + Speed Insights CDN scripts
(only fires on opencode.agentlabs.cc served via Vercel, not GitHub Pages)
- scripts/triage-reviews.py: fetch recent Play Store reviews via Android
Publisher API, create GitHub issues for ≤3★ reviews not yet tracked
Run review triage manually on VM:
DAYS_BACK=7 GOOGLE_SERVICE_ACCOUNT_JSON=... GH_TOKEN=... python3 scripts/triage-reviews.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
docs-site/index.html | 3 +
scripts/triage-reviews.py | 155 ++++++++++++++++++++++++++++++++++++++
2 files changed, 158 insertions(+)
create mode 100644 scripts/triage-reviews.py
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()