name: Build and Release Plugin on: push: tags: - 'v*' jobs: release: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: | 9.0.x 10.0.x - name: Build Plugin run: dotnet build JellyfinPlugin/JellyfinPlugin.csproj -c Release - name: Create ZIP Archives run: | cd JellyfinPlugin/bin/Release/ # Zip net9.0 (Jellyfin 10.11) cd net9.0 zip -j ../../../../BlackBarRemover_${{ github.ref_name }}_jf10.11.zip * cd .. # Zip net10.0 (Jellyfin 12.0) cd net10.0 zip -j ../../../../BlackBarRemover_${{ github.ref_name }}_jf12.0.zip * cd .. - name: Generate Checksums id: checksum run: | MD5_SUM_9=$(md5sum BlackBarRemover_${{ github.ref_name }}_jf10.11.zip | awk '{print $1}') MD5_SUM_10=$(md5sum BlackBarRemover_${{ github.ref_name }}_jf12.0.zip | awk '{print $1}') echo "md5_9=$MD5_SUM_9" >> $GITHUB_OUTPUT echo "md5_10=$MD5_SUM_10" >> $GITHUB_OUTPUT - name: Publish Release and Update Manifest env: API_URL: ${{ github.api_url }} REPO: ${{ github.repository }} TOKEN: ${{ github.token }} TAG: ${{ github.ref_name }} MD5_9: ${{ steps.checksum.outputs.md5_9 }} MD5_10: ${{ steps.checksum.outputs.md5_10 }} run: | python3 -c ' import os, json, urllib.request, base64, datetime api_url = os.environ["API_URL"] repo = os.environ["REPO"] token = os.environ["TOKEN"] tag = os.environ["TAG"] md5_9 = os.environ["MD5_9"] md5_10 = os.environ["MD5_10"] headers = { "Authorization": f"token {token}", "Content-Type": "application/json" } # 1. Get or Create Release rel_url = f"{api_url}/repos/{repo}/releases/tags/{tag}" req = urllib.request.Request(rel_url, headers=headers) rel_data = None try: res = urllib.request.urlopen(req) rel_data = json.loads(res.read().decode("utf-8")) except Exception: pass if not rel_data or "id" not in rel_data: is_pre = "beta" in tag or "alpha" in tag or tag.startswith("v0.") create_url = f"{api_url}/repos/{repo}/releases" body = json.dumps({ "tag_name": tag, "name": f"Release {tag}", "body": "Automated release", "draft": False, "prerelease": is_pre }).encode("utf-8") create_req = urllib.request.Request(create_url, data=body, headers=headers) res = urllib.request.urlopen(create_req) rel_data = json.loads(res.read().decode("utf-8")) rel_id = rel_data["id"] # 2. Upload Assets for filename in [f"BlackBarRemover_{tag}_jf10.11.zip", f"BlackBarRemover_{tag}_jf12.0.zip"]: if not os.path.exists(filename): print(f"File {filename} not found, skipping.") continue # Delete existing asset if any for existing_asset in rel_data.get("assets", []) or []: if existing_asset.get("name") == filename: del_url = f"{api_url}/repos/{repo}/releases/{rel_id}/assets/{existing_asset['id']}" del_req = urllib.request.Request(del_url, headers=headers, method="DELETE") try: urllib.request.urlopen(del_req) print(f"Deleted old asset {existing_asset['id']}") except Exception as e: print(f"Delete asset notice: {e}") # Upload asset with open(filename, "rb") as f: file_bytes = f.read() boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" body_data = ( f"--{boundary}\r\n" f"Content-Disposition: form-data; name=\"attachment\"; filename=\"{filename}\"\r\n" f"Content-Type: application/zip\r\n\r\n" ).encode("utf-8") + file_bytes + f"\r\n--{boundary}--\r\n".encode("utf-8") upload_headers = { "Authorization": f"token {token}", "Content-Type": f"multipart/form-data; boundary={boundary}" } upload_url = f"{api_url}/repos/{repo}/releases/{rel_id}/assets" upload_req = urllib.request.Request(upload_url, data=body_data, headers=upload_headers) try: urllib.request.urlopen(upload_req) print(f"Uploaded {filename} successfully.") except Exception as e: print(f"Asset upload error: {e}") # 3. Build manifest.json dynamically from Gitea Releases API rel_list_req = urllib.request.Request(f"{api_url}/repos/{repo}/releases?per_page=100", headers=headers) all_releases = [] try: res_list = urllib.request.urlopen(rel_list_req) all_releases = json.loads(res_list.read().decode("utf-8")) except Exception as e: print(f"Notice fetching releases list: {e}") all_versions = [] for r in all_releases: rel_tag = r.get("tag_name") if not rel_tag: continue clean_v = rel_tag.lstrip("v").split("-")[0] ts = r.get("published_at") or r.get("created_at") or "2026-09-17T20:00:00Z" assets = r.get("assets", []) if rel_tag == tag: all_versions.append({ "version": clean_v, "changelog": f"Release {tag} (Jellyfin 12.0)", "targetAbi": "12.0.0.0", "sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{tag}/BlackBarRemover_{tag}_jf12.0.zip", "checksum": md5_10, "timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") }) all_versions.append({ "version": clean_v, "changelog": f"Release {tag} (Jellyfin 10.11)", "targetAbi": "10.11.0.0", "sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{tag}/BlackBarRemover_{tag}_jf10.11.zip", "checksum": md5_9, "timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") }) else: a12 = next((a for a in assets if "jf12.0" in a.get("name", "")), None) if a12: a12_name = a12.get("name") all_versions.append({ "version": clean_v, "changelog": f"Release {rel_tag} (Jellyfin 12.0)", "targetAbi": "12.0.0.0", "sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{rel_tag}/{a12_name}", "checksum": a12.get("md5") or "", "timestamp": ts }) a10 = next((a for a in assets if "jf10.11" in a.get("name", "")), None) if a10: a10_name = a10.get("name") all_versions.append({ "version": clean_v, "changelog": f"Release {rel_tag} (Jellyfin 10.11)", "targetAbi": "10.11.0.0", "sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{rel_tag}/{a10_name}", "checksum": a10.get("md5") or "", "timestamp": ts }) if not a12 and not a10 and assets: alegacy = assets[0] leg_name = alegacy.get("name") all_versions.append({ "version": clean_v, "changelog": f"Release {rel_tag}", "targetAbi": "10.9.0.0", "sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{rel_tag}/{leg_name}", "checksum": alegacy.get("md5") or "", "timestamp": ts }) manifest_data = [ { "guid": "12345678-1234-1234-1234-1234567890ab", "name": "Black Bar Remover", "description": "Automatically detects and removes black bars from video files.", "overview": "Automatically detects and removes black bars from video files via CSS or FFmpeg.", "owner": "karol", "category": "Playback", "imageUrl": "", "versions": all_versions } ] b64_manifest = base64.b64encode(json.dumps(manifest_data, indent=4).encode("utf-8")).decode("utf-8") # Update on master and manifest branches for target_branch in ["master", "manifest"]: get_mf_url = f"{api_url}/repos/{repo}/contents/manifest.json?ref={target_branch}" req_mf = urllib.request.Request(get_mf_url, headers=headers) file_sha = None try: res_mf = urllib.request.urlopen(req_mf) file_sha = json.loads(res_mf.read().decode("utf-8")).get("sha") except Exception: pass put_payload = { "content": b64_manifest, "message": f"Update manifest.json for {tag}", "branch": target_branch } if file_sha: put_payload["sha"] = file_sha put_url = f"{api_url}/repos/{repo}/contents/manifest.json" put_req = urllib.request.Request(put_url, data=json.dumps(put_payload).encode("utf-8"), headers=headers, method="PUT") try: urllib.request.urlopen(put_req) print(f"manifest.json updated successfully on branch {target_branch}.") except Exception as e: print(f"Notice updating branch {target_branch}: {e}") '