add ipa signing; integrate repo; add entitlements
36
.github/workflows/release.yml
vendored
|
|
@ -9,6 +9,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
ZSIGN_VERSION: '0.7'
|
||||
|
||||
jobs:
|
||||
build-and-release-android-apks:
|
||||
permissions:
|
||||
|
|
@ -77,8 +80,12 @@ jobs:
|
|||
runs-on: macos-latest
|
||||
steps:
|
||||
# Checkout branch
|
||||
- name: checkout branch
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }}
|
||||
path: .
|
||||
|
||||
- name: setup flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
|
|
@ -94,16 +101,27 @@ jobs:
|
|||
- name: flutter pub get
|
||||
run: flutter pub get
|
||||
|
||||
- name: setup signing environment
|
||||
env:
|
||||
P12_BASE64: ${{ secrets.P12_CERTIFICATE }}
|
||||
PROVISIONING_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE }}
|
||||
run: |
|
||||
echo "$P12_BASE64" | base64 -d > certificate.p12
|
||||
echo "$PROVISIONING_PROFILE_BASE64" | base64 -d > profile.mobileprovision
|
||||
curl -L -o zsign.zip "https://github.com/zhlynn/zsign/releases/download/v${{ env.ZSIGN_VERSION }}/zsign-v${{ env.ZSIGN_VERSION }}-macos-x64.zip"
|
||||
unzip zsign.zip
|
||||
chmod +x zsign || chmod +x zsign-*
|
||||
|
||||
- name: build ios
|
||||
env:
|
||||
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
run: |
|
||||
flutter build ios --release --no-codesign
|
||||
mkdir -p build/ios/iphoneos/Payload
|
||||
ln -s ../Runner.app build/ios/iphoneos/Payload/Runner.app
|
||||
./zsign -f -k ./certificate.p12 -p "$P12_PASSWORD" -m ./profile.mobileprovision ./build/ios/iphoneos/Payload/Runner.app
|
||||
cd build/ios/iphoneos
|
||||
mkdir Payload
|
||||
cd Payload
|
||||
ln -s ../Runner.app
|
||||
cd ..
|
||||
zip -r app-release.ipa Payload
|
||||
mv app-release.ipa Mangayomi-${{ github.ref_name }}-ios.ipa
|
||||
zip -r ./Mangayomi-${{ github.ref_name }}-ios.ipa Payload
|
||||
|
||||
- name: upload artifact ios ipa
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
@ -402,3 +420,7 @@ jobs:
|
|||
with:
|
||||
artifacts: dist/Mangayomi-*.deb
|
||||
allowUpdates: true
|
||||
|
||||
ios-source-build:
|
||||
needs: build-and-release-ios-ipa
|
||||
uses: ./.github/workflows/update_sideloading_source.yml
|
||||
44
.github/workflows/update_sideloading_source.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
name: Update Sideloading Source
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch: # Allow manual triggering of this workflow
|
||||
|
||||
jobs:
|
||||
update-source:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
|
||||
- name: Get Latest Release Tag
|
||||
run: echo "RELEASE_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install Python Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
- name: Run source updater
|
||||
run: python repo/update_source.py
|
||||
|
||||
- name: Commit and Push changes
|
||||
run: |
|
||||
git config --global user.name 'GitHub Action'
|
||||
git config --global user.email 'action@github.com'
|
||||
git add repo/source.json
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes detected, skipping commit."
|
||||
else
|
||||
git commit -m "source update: $RELEASE_TAG"
|
||||
git push
|
||||
fi
|
||||
11
README.md
|
|
@ -32,7 +32,16 @@ Features include:
|
|||
## Download
|
||||
Get the app from our [releases page](https://github.com/kodjodevf/mangayomi/releases).
|
||||
|
||||
iOS / ipadOS users can also get the latest app from AltStore / Feather / SideStore via [Dominics GitHub repository](https://github.com/tanakrit-d/mangayomi-source).
|
||||
## iOS Sideloading Sources
|
||||
<a href="https://intradeus.github.io/http-protocol-redirector?r=altstore://source?url=https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/source.json"><img alt="AltStore Source" src="repo/images/buttons/altstore_button.png" width="150"></a>
|
||||
|
||||
<a href="https://intradeus.github.io/http-protocol-redirector?r=feather://source/https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/source.json"><img alt="Feather Source" src="repo/images/buttons/feather_button.png" width="150"></a>
|
||||
|
||||
<a href="https://intradeus.github.io/http-protocol-redirector?r=sidestore://source?url=https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/source.json"><img alt="Sidestore Source" src="repo/images/buttons/sidestore_button.png" width="150"></a>
|
||||
|
||||
<a href="https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/source.json"><img alt="Direct URL Source" src="repo/images/buttons/url_button.png" width="150"></a>
|
||||
|
||||
Note: Only future releases (> 0.5.2) will be signed (and therefore have AltStore/SideStore compatibility).
|
||||
|
||||
# Contributing
|
||||
|
||||
|
|
|
|||
20
repo/colours.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"Themes": {
|
||||
"Default": {
|
||||
"Tint": "#EF4444",
|
||||
"Primary": "#EF4444",
|
||||
"Gradient": {
|
||||
"Primary": "#EF4444",
|
||||
"Secondary": "#991B1B"
|
||||
}
|
||||
},
|
||||
"Black": {
|
||||
"Tint": "#71717A",
|
||||
"Primary": "#18181B",
|
||||
"Gradient": {
|
||||
"Primary": "#3F3F46",
|
||||
"Secondary": "#18181B"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
repo/config.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"repo_url": "kodjodevf/mangayomi",
|
||||
"json_file": "repo/source.json",
|
||||
"app_id": "com.kodjodevf.mangayomi",
|
||||
"app_name": "Mangayomi",
|
||||
"caption": "Update for Mangayomi now available!",
|
||||
"tint_colour": "EF4444",
|
||||
"image_url": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/news/update.webp"
|
||||
}
|
||||
BIN
repo/images/buttons/altstore_button.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
repo/images/buttons/altstore_button_resized.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
repo/images/buttons/feather_button.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
repo/images/buttons/feather_button_resized.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
repo/images/buttons/sidestore_button.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
repo/images/buttons/sidestore_button_resized.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
repo/images/buttons/url_button.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
repo/images/buttons/url_button_resized.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
repo/images/headers/readme_header_black.webp
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
repo/images/headers/readme_header_default.webp
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
repo/images/headers/source_header_black.webp
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
repo/images/headers/source_header_default.webp
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
repo/images/icons/icon_black.webp
Normal file
|
After Width: | Height: | Size: 528 KiB |
BIN
repo/images/icons/icon_default.webp
Normal file
|
After Width: | Height: | Size: 854 KiB |
BIN
repo/images/news/available_black.webp
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
repo/images/news/available_default.webp
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
repo/images/news/update_black.webp
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
repo/images/news/update_default.webp
Normal file
|
After Width: | Height: | Size: 369 KiB |
BIN
repo/images/screenshots/image_0_black.webp
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
repo/images/screenshots/image_0_default.webp
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
repo/images/screenshots/image_1_black.webp
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
repo/images/screenshots/image_1_default.webp
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
repo/images/screenshots/image_2_black.webp
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
repo/images/screenshots/image_2_default.webp
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
repo/images/templates/Archive.7z
Normal file
86
repo/source.json
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"name": "Mangayomi",
|
||||
"identifier": "com.kodjodevf.mangayomi",
|
||||
"headerURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/headers/source_header_default.webp",
|
||||
"website": "https://github.com/kodjodevf/mangayomi",
|
||||
"iconURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/icons/icon_default.webp",
|
||||
"subtitle": "An experience like no other.",
|
||||
"description": "This is the official source for Mangayomi.\n\nFor full details, check the GitHub repository:\nhttps://github.com/kodjodevf/mangayomi",
|
||||
"tintColor": "EF4444",
|
||||
"apps": [
|
||||
{
|
||||
"beta": false,
|
||||
"name": "Mangayomi",
|
||||
"bundleIdentifier": "com.kodjodevf.mangayomi",
|
||||
"developerName": "Moustapha Kodjo Amadou",
|
||||
"subtitle": "Read manga, novels, and watch anime",
|
||||
"version": "0.5.2",
|
||||
"versionDate": "2025-02-23T16:08:37Z",
|
||||
"versionDescription": "What's Changed\r\n* Update quarkuc extractor by @yxxyun in https://github.com/kodjodevf/mangayomi/pull/393\r\n* added long press for 2x playback speed by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/396\r\n* fix broken category \r\n* add confirmation dialog for adding repositories\r\n\r\n\r\n**Full Changelog**: https://github.com/kodjodevf/mangayomi/compare/v0.5.1...v0.5.2",
|
||||
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.5.2/Mangayomi-v0.5.2-ios.ipa",
|
||||
"localizedDescription": "Mangayomi is an open-source Flutter app for reading manga, novels, and watching anime across multiple platforms.",
|
||||
"iconURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/icons/icon_default.webp",
|
||||
"tintColor": "EF4444",
|
||||
"category": "entertainment",
|
||||
"size": 56680831,
|
||||
"screenshotURLs": [
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_0_default.webp",
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_1_default.webp",
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_2_default.webp"
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.5.2",
|
||||
"date": "2025-02-23T16:08:37Z",
|
||||
"localizedDescription": "What's Changed\r\n* Update quarkuc extractor by @yxxyun in https://github.com/kodjodevf/mangayomi/pull/393\r\n* added long press for 2x playback speed by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/396\r\n* fix broken category \r\n* add confirmation dialog for adding repositories\r\n\r\n\r\n**Full Changelog**: https://github.com/kodjodevf/mangayomi/compare/v0.5.1...v0.5.2",
|
||||
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.5.2/Mangayomi-v0.5.2-ios.ipa",
|
||||
"size": 56680831
|
||||
},
|
||||
{
|
||||
"version": "0.5.1",
|
||||
"date": "2025-02-20T14:29:54Z",
|
||||
"localizedDescription": "What's Changed\r\n* added deep links for repo urls by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/379\r\n* enhanced sync feature by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/380\r\n* optimized sync and bug fixes by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/381\r\n\r\n\r\n**Full Changelog**: https://github.com/kodjodevf/mangayomi/compare/v0.5.0...v0.5.1",
|
||||
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.5.1/Mangayomi-v0.5.1-ios.ipa",
|
||||
"size": 56754892
|
||||
},
|
||||
{
|
||||
"version": "0.5.0",
|
||||
"date": "2025-02-12T11:16:16Z",
|
||||
"localizedDescription": "\u2022 improve chapter download\r\n\u2022 improve custom navigation settings\r\n\u2022 remove embedded URLs from extensions\r\n\u2022 co\u2022written with @Schnitzel5 : update source fetching methods to utilize the given repositories",
|
||||
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.5.0/Mangayomi-v0.5.0-ios.ipa",
|
||||
"size": 56707308
|
||||
}
|
||||
],
|
||||
"appPermissions": {
|
||||
"entitlements": [
|
||||
{
|
||||
"name": "com.apple.security.application-groups"
|
||||
},
|
||||
{
|
||||
"name": "com.apple.developer.associated-domains"
|
||||
},
|
||||
{
|
||||
"name": "keychain-access-groups"
|
||||
},
|
||||
{
|
||||
"name": "aps-environment"
|
||||
}
|
||||
],
|
||||
"privacy": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"news": [
|
||||
{
|
||||
"appID": "com.kodjodevf.mangayomi",
|
||||
"title": "0.5.2 - 23 Feb",
|
||||
"identifier": "release-0.5.2",
|
||||
"caption": "Update for Mangayomi now available!",
|
||||
"date": "2025-02-23T16:08:37Z",
|
||||
"tintColor": "EF4444",
|
||||
"imageURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/news/update_default.webp",
|
||||
"notify": true,
|
||||
"url": "https://github.com/kodjodevf/mangayomi/releases/tag/v0.5.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
385
repo/update_source.py
Executable file
|
|
@ -0,0 +1,385 @@
|
|||
import json
|
||||
import re
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any, TypedDict
|
||||
|
||||
|
||||
class ReleaseAsset(TypedDict):
|
||||
browser_download_url: str
|
||||
name: str
|
||||
size: int
|
||||
|
||||
|
||||
class GitHubRelease(TypedDict):
|
||||
tag_name: str
|
||||
published_at: str
|
||||
body: str
|
||||
assets: List[ReleaseAsset]
|
||||
|
||||
|
||||
class VersionEntry(TypedDict):
|
||||
version: str
|
||||
date: str
|
||||
localizedDescription: str
|
||||
downloadURL: Optional[str]
|
||||
size: Optional[int]
|
||||
|
||||
|
||||
class AppInfo(TypedDict):
|
||||
versions: List[VersionEntry]
|
||||
version: str
|
||||
versionDate: str
|
||||
versionDescription: str
|
||||
downloadURL: Optional[str]
|
||||
size: Optional[int]
|
||||
|
||||
|
||||
class NewsEntry(TypedDict):
|
||||
appID: str
|
||||
title: str
|
||||
identifier: str
|
||||
caption: str
|
||||
date: str
|
||||
tintColor: str
|
||||
imageURL: str
|
||||
notify: bool
|
||||
url: str
|
||||
|
||||
|
||||
class AppData(TypedDict):
|
||||
apps: List[Dict[str, Any]]
|
||||
news: List[NewsEntry]
|
||||
|
||||
|
||||
class AppConfig(TypedDict):
|
||||
repo_url: str
|
||||
json_file: str
|
||||
app_id: str
|
||||
app_name: str
|
||||
caption: str
|
||||
tint_colour: str
|
||||
image_url: str
|
||||
|
||||
|
||||
def load_config(config_path: str) -> AppConfig:
|
||||
"""
|
||||
Load repo configuration values.
|
||||
"""
|
||||
try:
|
||||
with open(config_path, 'r') as config_file:
|
||||
config_data = json.load(config_file)
|
||||
|
||||
return {
|
||||
"repo_url": config_data["repo_url"],
|
||||
"json_file": config_data["json_file"],
|
||||
"app_id": config_data["app_id"],
|
||||
"app_name": config_data["app_name"],
|
||||
"caption": config_data["caption"],
|
||||
"tint_colour": config_data["tint_colour"],
|
||||
"image_url": config_data["image_url"],
|
||||
}
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Configuration file not found at {config_path}")
|
||||
raise
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
print(f"Error parsing configuration: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def fetch_all_releases(repo_url: str) -> List[GitHubRelease]:
|
||||
"""
|
||||
Fetch all GitHub releases for the repository, sorted by published date (oldest first).
|
||||
|
||||
Returns:
|
||||
List[GitHubRelease]: List of all releases sorted by publication date
|
||||
"""
|
||||
api_url: str = f"https://api.github.com/repos/{repo_url}/releases"
|
||||
headers: Dict[str, str] = {"Accept": "application/vnd.github+json"}
|
||||
|
||||
response = requests.get(api_url, headers=headers)
|
||||
response.raise_for_status() # Raise exception for HTTP errors
|
||||
|
||||
releases: List[GitHubRelease] = response.json()
|
||||
sorted_releases = sorted(releases, key=lambda x: x["published_at"], reverse=False)
|
||||
|
||||
return sorted_releases
|
||||
|
||||
|
||||
def fetch_latest_release(repo_url: str) -> GitHubRelease:
|
||||
"""
|
||||
Fetch the latest GitHub release for the repository.
|
||||
|
||||
Returns:
|
||||
GitHubRelease: The latest release
|
||||
|
||||
Raises:
|
||||
ValueError: If no releases are found
|
||||
"""
|
||||
api_url: str = f"https://api.github.com/repos/{repo_url}/releases"
|
||||
headers: Dict[str, str] = {"Accept": "application/vnd.github+json"}
|
||||
|
||||
response = requests.get(api_url, headers=headers)
|
||||
response.raise_for_status() # Raise exception for HTTP errors
|
||||
|
||||
releases: List[GitHubRelease] = response.json()
|
||||
sorted_releases = sorted(releases, key=lambda x: x["published_at"], reverse=True)
|
||||
|
||||
if sorted_releases:
|
||||
return sorted_releases[0]
|
||||
|
||||
raise ValueError("No release found.")
|
||||
|
||||
|
||||
# 2025-03-25: Reimplement this at a later date (@tanakrit-d)
|
||||
# def purge_old_news(data: AppData, fetched_versions: List[str]) -> None:
|
||||
# """
|
||||
# Remove news entries for versions that no longer exist.
|
||||
|
||||
# Args:
|
||||
# data: The app data dictionary
|
||||
# fetched_versions: List of valid version strings
|
||||
# """
|
||||
# if "news" not in data:
|
||||
# return
|
||||
|
||||
# valid_identifiers: Set[str] = {f"release-{version}" for version in fetched_versions}
|
||||
# data["news"] = [
|
||||
# entry for entry in data["news"] if entry["identifier"] in valid_identifiers
|
||||
# ]
|
||||
|
||||
|
||||
def format_description(description: str) -> str:
|
||||
"""
|
||||
Format release description by removing HTML tags and replacing certain characters.
|
||||
|
||||
Args:
|
||||
description: The raw description text
|
||||
|
||||
Returns:
|
||||
str: Cleaned description text
|
||||
"""
|
||||
formatted = re.sub(r"<[^<]+?>", "", description) # HTML tags
|
||||
formatted = re.sub(r"#{1,6}\s?", "", formatted) # Markdown header tags
|
||||
formatted = formatted.replace(r"\*{2}", "").replace("-", "•").replace("`", '"')
|
||||
|
||||
return formatted
|
||||
|
||||
|
||||
def find_download_url_and_size(
|
||||
release: GitHubRelease,
|
||||
) -> tuple[Optional[str], Optional[int]]:
|
||||
"""
|
||||
Find the download URL and size for a release's IPA file.
|
||||
|
||||
Args:
|
||||
release: The GitHub release
|
||||
|
||||
Returns:
|
||||
tuple: (download_url, size) or (None, None) if not found
|
||||
"""
|
||||
for asset in release["assets"]:
|
||||
if asset["name"].endswith(".ipa"):
|
||||
return asset["browser_download_url"], asset["size"]
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def normalize_version(version: str) -> str:
|
||||
"""
|
||||
Strip the version tag (e.g., -hotfix) from a version string.
|
||||
|
||||
Args:
|
||||
version: Version string (e.g., v0.5.2-hotfix, 0.5.2-beta)
|
||||
|
||||
Returns:
|
||||
Normalized version string without the tag (e.g., 0.5.2)
|
||||
"""
|
||||
version = version.lstrip("v")
|
||||
|
||||
match = re.search(r"(\d+\.\d+\.\d+)", version)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return version
|
||||
|
||||
|
||||
def process_versions(versions_data: List[VersionEntry]) -> List[VersionEntry]:
|
||||
"""
|
||||
Process the versions list to remove duplicate versions, keeping the newest version.
|
||||
|
||||
Args:
|
||||
versions_data (List[VersionEntry]): List of version dictionaries containing:
|
||||
version: str
|
||||
date: str
|
||||
localizedDescription: str
|
||||
downloadURL: Optional[str]
|
||||
size: Optional[int]
|
||||
|
||||
Returns:
|
||||
List[VersionEntry]: Processed list with only the newest versions.
|
||||
"""
|
||||
# Create a list to store unique versions with their details
|
||||
version_entries: List[VersionEntry] = []
|
||||
|
||||
# Iterate through the versions in the order they appear
|
||||
for version in versions_data:
|
||||
# Parse the date for comparison
|
||||
current_date = datetime.fromisoformat(version["date"].replace("Z", "+00:00"))
|
||||
|
||||
# Check if this version already exists in unique_versions
|
||||
existing_version_index = next(
|
||||
(
|
||||
index
|
||||
for index, v in enumerate(version_entries)
|
||||
if v["version"] == version["version"]
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if existing_version_index is not None:
|
||||
# Compare dates and keep the newer version
|
||||
existing_date = datetime.fromisoformat(
|
||||
version_entries[existing_version_index]["date"].replace("Z", "+00:00")
|
||||
)
|
||||
|
||||
if current_date > existing_date:
|
||||
version_entries[existing_version_index] = version
|
||||
else:
|
||||
# If no duplicate found, add to unique versions
|
||||
version_entries.append(version)
|
||||
|
||||
return version_entries
|
||||
|
||||
|
||||
def update_json_file(
|
||||
config: AppConfig,
|
||||
json_file: str,
|
||||
fetched_data_all: List[GitHubRelease],
|
||||
fetched_data_latest: GitHubRelease,
|
||||
) -> None:
|
||||
"""
|
||||
Update the apps.json file with the fetched GitHub releases.
|
||||
|
||||
Args:
|
||||
json_file: Path to the JSON file
|
||||
fetched_data_all: List of all GitHub releases
|
||||
fetched_data_latest: The latest GitHub release
|
||||
"""
|
||||
with open(json_file, "r") as file:
|
||||
data: AppData = json.load(file)
|
||||
|
||||
app = data["apps"][0]
|
||||
|
||||
releases = []
|
||||
|
||||
# Process all releases
|
||||
for release in fetched_data_all:
|
||||
full_version = release["tag_name"].lstrip("v")
|
||||
version_match = re.search(r"(\d+\.\d+\.\d+)(?:-([a-zA-Z0-9]+))?", full_version)
|
||||
|
||||
if not version_match:
|
||||
continue
|
||||
|
||||
version_date = release["published_at"]
|
||||
|
||||
# Get base version without tags
|
||||
base_version = normalize_version(full_version)
|
||||
|
||||
# Clean up description
|
||||
description = release["body"]
|
||||
keyword = "{APP_NAME} Release Information"
|
||||
if keyword in description:
|
||||
description = description.split(keyword, 1)[1].strip()
|
||||
description = format_description(description)
|
||||
|
||||
# Find download URL and size
|
||||
download_url, size = find_download_url_and_size(release)
|
||||
|
||||
# Skip release entries without a download URL
|
||||
if not download_url:
|
||||
continue
|
||||
|
||||
# Create version entry
|
||||
version_entry: VersionEntry = {
|
||||
"version": base_version,
|
||||
"date": version_date,
|
||||
"localizedDescription": description,
|
||||
"downloadURL": download_url,
|
||||
"size": size,
|
||||
}
|
||||
|
||||
releases.append(version_entry)
|
||||
|
||||
deduplicated_versions = process_versions(releases)
|
||||
app["versions"] = []
|
||||
for i in deduplicated_versions:
|
||||
app["versions"].insert(0, i)
|
||||
app["versions"] = sorted(
|
||||
app["versions"], key=lambda x: x.get("date", ""), reverse=True
|
||||
)
|
||||
|
||||
# Update app info with latest release
|
||||
latest_version = fetched_data_latest["tag_name"].lstrip("v")
|
||||
tag = fetched_data_latest["tag_name"]
|
||||
version_match = re.search(r"(\d+\.\d+\.\d+)(?:-([a-zA-Z0-9]+))?", latest_version)
|
||||
|
||||
if not version_match:
|
||||
raise ValueError("Invalid version format")
|
||||
|
||||
app["version"] = normalize_version(full_version)
|
||||
app["versionDate"] = fetched_data_latest["published_at"]
|
||||
app["versionDescription"] = format_description(fetched_data_latest["body"])
|
||||
|
||||
# Find latest download URL and size
|
||||
download_url, size = find_download_url_and_size(fetched_data_latest)
|
||||
app["downloadURL"] = download_url
|
||||
app["size"] = size
|
||||
|
||||
# Update news entries
|
||||
# 2025-03-25: Reimplement this at a later date (@tanakrit-d)
|
||||
# purge_old_news(data, fetched_versions)
|
||||
|
||||
if "news" not in data:
|
||||
data["news"] = []
|
||||
|
||||
# Add news entry for the latest version if it doesn't exist
|
||||
news_identifier = f"release-{latest_version}"
|
||||
if not any(item["identifier"] == news_identifier for item in data["news"]):
|
||||
formatted_date = datetime.strptime(
|
||||
fetched_data_latest["published_at"], "%Y-%m-%dT%H:%M:%SZ"
|
||||
).strftime("%d %b")
|
||||
|
||||
news_entry: NewsEntry = {
|
||||
"appID": config["app_id"],
|
||||
"title": f"{latest_version} - {formatted_date}",
|
||||
"identifier": news_identifier,
|
||||
"caption": config["caption"],
|
||||
"date": fetched_data_latest["published_at"],
|
||||
"tintColor": config["tint_colour"],
|
||||
"imageURL": config["image_url"],
|
||||
"notify": True,
|
||||
"url": f"https://github.com/{config["repo_url"]}/releases/tag/{tag}",
|
||||
}
|
||||
data["news"].append(news_entry)
|
||||
|
||||
with open(json_file, "w") as file:
|
||||
json.dump(data, file, indent=2)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Entrypoint for GitHub workflow action.
|
||||
"""
|
||||
try:
|
||||
config = load_config("repo/config.json")
|
||||
fetched_data_all = fetch_all_releases(config["repo_url"])
|
||||
fetched_data_latest = fetch_latest_release(config["repo_url"])
|
||||
update_json_file(config, "repo/source.json", fetched_data_all, fetched_data_latest)
|
||||
print("Successfully updated repo/source.json with latest releases.")
|
||||
except Exception as e:
|
||||
print(f"Error updating releases: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||