feat: add Vercel analytics to website + Play Store review triage script

- 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>
This commit is contained in:
Dennis V
2026-06-23 20:18:15 +00:00
parent 4f10cd4ccf
commit 4c2f6793de
2 changed files with 158 additions and 0 deletions

View File

@@ -9,6 +9,9 @@
<meta name="theme-color" content="#3b82f6" />
<link rel="canonical" href="https://dzianisv.github.io/opencode-mobile/" />
<link rel="icon" type="image/png" href="fdroid/repo/icons/icon.png" />
<!-- Vercel Web Analytics — privacy-first, no cookies -->
<script defer src="https://cdn.vercel-insights.com/v1/speed-insights/script.js"></script>
<script defer src="https://cdn.vercel-insights.com/v1/script.js"></script>
<!-- Open Graph -->
<meta property="og:type" content="website" />

155
scripts/triage-reviews.py Normal file
View File

@@ -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()