- Generated PKCS12 keystore (production-release.jks, alias: upload) - Updated GitHub secrets: KEYSTORE_BASE64/PASSWORD/KEY_ALIAS/KEY_PASSWORD - Added keystores/ to .gitignore - Added distribution/PLAY_CONSOLE_SETUP.md: step-by-step guide to create GCP service account, link to Play Console, first manual upload, and configure PLAY_STORE_SERVICE_ACCOUNT_JSON secret - Installed android-playstore-setup skills (sub-skills) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
222 lines
7.5 KiB
Python
222 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Validate Google Play Store API connection.
|
|
Tests that service account has proper access to Play Console.
|
|
|
|
Usage:
|
|
python validate-playstore.py <service-account.json> <package-name>
|
|
|
|
Example:
|
|
python validate-playstore.py service-account.json com.example.app
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
def validate_service_account_json(json_path):
|
|
"""Validate service account JSON file format and required fields."""
|
|
print("Validating service account JSON...")
|
|
|
|
# Check file exists
|
|
if not Path(json_path).exists():
|
|
print(f"❌ File not found: {json_path}")
|
|
return False
|
|
|
|
# Load and parse JSON
|
|
try:
|
|
with open(json_path) as f:
|
|
data = json.load(f)
|
|
except json.JSONDecodeError as e:
|
|
print(f"❌ Invalid JSON format: {e}")
|
|
return False
|
|
|
|
# Check required fields
|
|
required_fields = {
|
|
"type": "service_account",
|
|
"project_id": str,
|
|
"private_key_id": str,
|
|
"private_key": str,
|
|
"client_email": str,
|
|
"client_id": str
|
|
}
|
|
|
|
for field, expected_type in required_fields.items():
|
|
if field not in data:
|
|
print(f"❌ Missing required field: {field}")
|
|
return False
|
|
|
|
if field == "type" and data[field] != expected_type:
|
|
print(f"❌ Invalid type: {data[field]} (expected 'service_account')")
|
|
return False
|
|
|
|
# Validate email format
|
|
email = data["client_email"]
|
|
if not email.endswith(".iam.gserviceaccount.com"):
|
|
print(f"⚠️ Warning: Unexpected email format: {email}")
|
|
print(f" Expected format: name@project.iam.gserviceaccount.com")
|
|
|
|
# Validate private key format
|
|
private_key = data["private_key"]
|
|
if not private_key.startswith("-----BEGIN PRIVATE KEY-----"):
|
|
print(f"❌ Invalid private_key format")
|
|
return False
|
|
|
|
print(f"✅ Service account JSON is valid")
|
|
print(f" Email: {email}")
|
|
print(f" Project: {data['project_id']}")
|
|
|
|
return True
|
|
|
|
|
|
def test_api_connection(json_path, package_name):
|
|
"""Test connection to Google Play Developer API."""
|
|
print(f"\nTesting API connection for package: {package_name}...")
|
|
|
|
# Check if required packages are installed
|
|
try:
|
|
from google.oauth2 import service_account
|
|
from googleapiclient.discovery import build
|
|
from googleapiclient.errors import HttpError
|
|
except ImportError:
|
|
print("❌ Required packages not installed")
|
|
print("\n Install with:")
|
|
print(" pip install google-auth google-api-python-client")
|
|
return False
|
|
|
|
try:
|
|
# Authenticate with service account
|
|
credentials = service_account.Credentials.from_service_account_file(
|
|
json_path,
|
|
scopes=['https://www.googleapis.com/auth/androidpublisher']
|
|
)
|
|
|
|
print("✅ Service account credentials loaded")
|
|
|
|
# Build API service
|
|
service = build('androidpublisher', 'v3', credentials=credentials)
|
|
|
|
print("✅ Play Developer API service created")
|
|
|
|
# Try to create an edit (validates API access and app existence)
|
|
edit_request = service.edits().insert(body={}, packageName=package_name)
|
|
edit_result = edit_request.execute()
|
|
edit_id = edit_result['id']
|
|
|
|
print(f"✅ Successfully connected to Play Developer API")
|
|
print(f" Can access package: {package_name}")
|
|
|
|
# Try to get tracks info
|
|
try:
|
|
tracks_response = service.edits().tracks().list(
|
|
packageName=package_name,
|
|
editId=edit_id
|
|
).execute()
|
|
|
|
tracks = [track['track'] for track in tracks_response.get('tracks', [])]
|
|
if tracks:
|
|
print(f" Available tracks: {', '.join(tracks)}")
|
|
else:
|
|
print(f" No releases yet (normal for new apps)")
|
|
|
|
except Exception as e:
|
|
print(f" ⚠️ Could not list tracks: {str(e)}")
|
|
|
|
# Clean up edit
|
|
service.edits().delete(packageName=package_name, editId=edit_id).execute()
|
|
|
|
return True
|
|
|
|
except HttpError as e:
|
|
error_details = str(e)
|
|
|
|
if "404" in error_details:
|
|
print(f"❌ App not found in Play Console")
|
|
print(f"\n Possible causes:")
|
|
print(f" → Package name is incorrect: {package_name}")
|
|
print(f" → App hasn't been created in Play Console yet")
|
|
print(f" → Service account doesn't have access to this app")
|
|
print(f"\n Fix:")
|
|
print(f" → Verify package name matches exactly")
|
|
print(f" → Create app in Play Console first")
|
|
print(f" → Check service account permissions")
|
|
|
|
elif "403" in error_details:
|
|
print(f"❌ Permission denied")
|
|
print(f"\n Possible causes:")
|
|
print(f" → Service account not linked in Play Console")
|
|
print(f" → Service account lacks required permissions")
|
|
print(f"\n Fix:")
|
|
print(f" → Go to Play Console → Setup → API access")
|
|
print(f" → Grant 'Release' permission to service account")
|
|
print(f" → Wait 5-10 minutes for permissions to propagate")
|
|
|
|
elif "401" in error_details:
|
|
print(f"❌ Authentication failed")
|
|
print(f"\n Possible causes:")
|
|
print(f" → Service account JSON is invalid")
|
|
print(f" → Service account has been deleted")
|
|
print(f"\n Fix:")
|
|
print(f" → Re-download service account JSON from Google Cloud")
|
|
print(f" → Verify JSON file is complete and valid")
|
|
|
|
else:
|
|
print(f"❌ API error: {error_details}")
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"❌ Unexpected error: {str(e)}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main validation function."""
|
|
if len(sys.argv) < 3:
|
|
print("Usage: python validate-playstore.py <service-account.json> <package-name>")
|
|
print("\nExample:")
|
|
print(" python validate-playstore.py service-account.json com.example.app")
|
|
sys.exit(1)
|
|
|
|
json_path = sys.argv[1]
|
|
package_name = sys.argv[2]
|
|
|
|
print("=" * 60)
|
|
print("Play Store API Validation")
|
|
print("=" * 60)
|
|
print()
|
|
|
|
# Step 1: Validate JSON file
|
|
if not validate_service_account_json(json_path):
|
|
print("\n" + "=" * 60)
|
|
print("❌ Validation FAILED - JSON file is invalid")
|
|
print("=" * 60)
|
|
sys.exit(1)
|
|
|
|
# Step 2: Test API connection
|
|
if not test_api_connection(json_path, package_name):
|
|
print("\n" + "=" * 60)
|
|
print("❌ Validation FAILED - API connection failed")
|
|
print("=" * 60)
|
|
sys.exit(1)
|
|
|
|
# Success!
|
|
print("\n" + "=" * 60)
|
|
print("✅ All validations passed!")
|
|
print("=" * 60)
|
|
print("\nNext steps:")
|
|
print("1. Add SERVICE_ACCOUNT_JSON to GitHub Secrets")
|
|
print(" → Repository → Settings → Secrets → Actions")
|
|
print(" → New repository secret")
|
|
print(" → Name: SERVICE_ACCOUNT_JSON")
|
|
print(" → Value: [paste entire JSON file contents]")
|
|
print("\n2. Run android-playstore-publishing skill")
|
|
print(" → Generate GitHub Actions workflow")
|
|
print("\n3. Deploy your app!")
|
|
print(" → Push to repository or trigger workflow manually")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|