#!/usr/bin/env python3 """Writes ios/source.json, an AltStore source (SideStore and Feather read it too), from the ios-v* releases on Gitea. Only releases with a stemify-.ipa are listed, since older ones used a different bundle ID. Set GITEA_TOKEN if the repo is private.""" import json, os, re, urllib.request REPO = "https://git.iamchrisama.com/cdnutter/stemify" API = "https://git.iamchrisama.com/api/v1/repos/cdnutter/stemify" RAW = f"{REPO}/raw/branch/main" HERE = os.path.dirname(os.path.abspath(__file__)) def get(url): req = urllib.request.Request(url) if os.environ.get("GITEA_TOKEN"): req.add_header("Authorization", "token " + os.environ["GITEA_TOKEN"]) with urllib.request.urlopen(req) as r: return json.load(r) yml = open(os.path.join(HERE, "..", "project.yml")).read() # Build numbers: older ones from the last source.json, the current one from project.yml. builds = {} try: for v in json.load(open(os.path.join(HERE, "..", "source.json")))["apps"][0]["versions"]: builds[v["downloadURL"].rsplit("/stemify-", 1)[-1].removesuffix(".ipa")] = v["buildVersion"] except (OSError, ValueError, KeyError, IndexError): pass # Tags and IPAs use the label as a slug ("1.0 RC 1" -> 1.0-rc1); AltStore gets the plain bundle # version, so it matches what's installed, and the label to show. label = re.search(r'VERSION_LABEL: "(.*)"', yml)[1] slug = label.lower().replace(" rc ", "-rc").replace(" ", "-") builds[slug] = re.search(r'CURRENT_PROJECT_VERSION: "(.*)"', yml)[1] versions = [] for rel in get(API + "/releases?limit=50"): v = rel["tag_name"].removeprefix("ios-v") ipa = next((a for a in rel["assets"] if a["name"] == f"stemify-{v}.ipa"), None) # Pre-releases are dev builds (org.cdnutter.stemify-dev), they go to Sideload Developer instead. if not rel["tag_name"].startswith("ios-v") or not ipa or rel["prerelease"]: continue versions.append({ "version": v.split("-")[0], "marketingVersion": v.replace("-rc", " RC ").replace("-", " ") if "-" in v else v, "buildVersion": builds.get(v, v), "date": rel["published_at"], "localizedDescription": rel["body"].strip() or f"Stemify {v}", "downloadURL": ipa["browser_download_url"], "size": ipa["size"], "minOSVersion": "17.0", }) source = { "name": "Stemify", "identifier": "org.cdnutter.stemify.source", "sourceURL": f"{RAW}/ios/source.json", "iconURL": f"{RAW}/ios/Stemify/Resources/Assets.xcassets/AppIcon.appiconset/icon.png", "website": REPO, "apps": [{ "name": "Stemify", "bundleIdentifier": "org.cdnutter.stemify", "developerName": "cdnutter", "subtitle": "Plex music player with live stem mixing.", "localizedDescription": "Plays your Plex library and lets you mute vocals or remix stems on the fly. " "Works offline with downloads, and in the car with CarPlay.", "iconURL": f"{RAW}/ios/Stemify/Resources/Assets.xcassets/AppIcon.appiconset/icon.png", "tintColor": "#FA2D48", "category": "entertainment", "versions": versions, "appPermissions": { "entitlements": ["com.apple.developer.carplay-audio", "com.apple.developer.siri"], "privacy": { "NSLocalNetworkUsageDescription": "Stemify talks to Plex and the stems server on your home network.", "NSSiriUsageDescription": "Ask Siri to play music from your Plex library.", }, }, }], "news": [], } with open(os.path.join(HERE, "..", "source.json"), "w") as f: json.dump(source, f, indent=2) f.write("\n") print(f"source.json: {len(versions)} version(s)")