#!/usr/bin/env python3 """Minimal App Store Connect API helper for screenshots. Auth pulls the key from `pass` (appstore/api-key-id, appstore/issuer-id, appstore/api-key-p8). Read-only `status` command lists the editable iOS version and the screenshot sets for a locale so we know what exists before uploading. Usage: python asc.py status [locale] # default en-US python asc.py upload ... # replaces that set """ import sys, time, json, hashlib, subprocess import jwt, requests BASE = "https://api.appstoreconnect.apple.com/v1" BUNDLE = "com.alexandrevazquez.PortfolioJournal" def _pass(k): return subprocess.check_output(["pass", "show", k]).decode().strip() def token(): kid = _pass("appstore/api-key-id") iss = _pass("appstore/issuer-id") key = _pass("appstore/api-key-p8") payload = {"iss": iss, "iat": int(time.time()), "exp": int(time.time()) + 1100, "aud": "appstoreconnect-v1"} return jwt.encode(payload, key, algorithm="ES256", headers={"kid": kid, "typ": "JWT"}) def H(): return {"Authorization": f"Bearer {token()}", "Content-Type": "application/json"} def get(path, **params): r = requests.get(f"{BASE}{path}", headers=H(), params=params) r.raise_for_status() return r.json() def app_id(): d = get("/apps", **{"filter[bundleId]": BUNDLE}) return d["data"][0]["id"] def editable_version(aid): d = get(f"/apps/{aid}/appStoreVersions", **{"filter[platform]": "IOS", "limit": 10}) for v in d["data"]: st = v["attributes"]["appStoreState"] if st in ("PREPARE_FOR_SUBMISSION", "DEVELOPER_REJECTED", "REJECTED", "METADATA_REJECTED", "WAITING_FOR_REVIEW", "READY_FOR_REVIEW"): return v return d["data"][0] if d["data"] else None def loc_id(version_id, locale): d = get(f"/appStoreVersions/{version_id}/appStoreVersionLocalizations", **{"limit": 50}) for l in d["data"]: if l["attributes"]["locale"] == locale: return l["id"] return None def sets_for_loc(lid): return get(f"/appStoreVersionLocalizations/{lid}/appScreenshotSets", **{"limit": 50})["data"] def status(locale="en-US"): aid = app_id() v = editable_version(aid) print("app id:", aid) print("version:", v["attributes"]["versionString"], "| state:", v["attributes"]["appStoreState"], "| id:", v["id"]) lid = loc_id(v["id"], locale) print(f"locale {locale} localization id:", lid) if not lid: return for s in sets_for_loc(lid): dt = s["attributes"]["screenshotDisplayType"] shots = get(f"/appScreenshotSets/{s['id']}/appScreenshots", **{"limit": 50})["data"] print(f" set {dt}: {len(shots)} screenshots (set id {s['id']})") if __name__ == "__main__": cmd = sys.argv[1] if len(sys.argv) > 1 else "status" if cmd == "status": status(sys.argv[2] if len(sys.argv) > 2 else "en-US")