diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2516af0d..ff7db3ab 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -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
\ No newline at end of file
diff --git a/.github/workflows/update_sideloading_source.yml b/.github/workflows/update_sideloading_source.yml
new file mode 100644
index 00000000..a56fa244
--- /dev/null
+++ b/.github/workflows/update_sideloading_source.yml
@@ -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
diff --git a/README.md b/README.md
index 6f950d29..1d72b671 100644
--- a/README.md
+++ b/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
+
+
+
+
+
+
+
+
+Note: Only future releases (> 0.5.2) will be signed (and therefore have AltStore/SideStore compatibility).
# Contributing
diff --git a/repo/colours.json b/repo/colours.json
new file mode 100644
index 00000000..564410e2
--- /dev/null
+++ b/repo/colours.json
@@ -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"
+ }
+ }
+ }
+}
diff --git a/repo/config.json b/repo/config.json
new file mode 100644
index 00000000..718498ca
--- /dev/null
+++ b/repo/config.json
@@ -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"
+}
\ No newline at end of file
diff --git a/repo/images/buttons/altstore_button.png b/repo/images/buttons/altstore_button.png
new file mode 100644
index 00000000..48e15b7b
Binary files /dev/null and b/repo/images/buttons/altstore_button.png differ
diff --git a/repo/images/buttons/altstore_button_resized.png b/repo/images/buttons/altstore_button_resized.png
new file mode 100644
index 00000000..7a06cbf2
Binary files /dev/null and b/repo/images/buttons/altstore_button_resized.png differ
diff --git a/repo/images/buttons/feather_button.png b/repo/images/buttons/feather_button.png
new file mode 100644
index 00000000..0bb1f497
Binary files /dev/null and b/repo/images/buttons/feather_button.png differ
diff --git a/repo/images/buttons/feather_button_resized.png b/repo/images/buttons/feather_button_resized.png
new file mode 100644
index 00000000..c1e2d47a
Binary files /dev/null and b/repo/images/buttons/feather_button_resized.png differ
diff --git a/repo/images/buttons/sidestore_button.png b/repo/images/buttons/sidestore_button.png
new file mode 100644
index 00000000..a47e5617
Binary files /dev/null and b/repo/images/buttons/sidestore_button.png differ
diff --git a/repo/images/buttons/sidestore_button_resized.png b/repo/images/buttons/sidestore_button_resized.png
new file mode 100644
index 00000000..ec9e9534
Binary files /dev/null and b/repo/images/buttons/sidestore_button_resized.png differ
diff --git a/repo/images/buttons/url_button.png b/repo/images/buttons/url_button.png
new file mode 100644
index 00000000..52f7e031
Binary files /dev/null and b/repo/images/buttons/url_button.png differ
diff --git a/repo/images/buttons/url_button_resized.png b/repo/images/buttons/url_button_resized.png
new file mode 100644
index 00000000..df9c8544
Binary files /dev/null and b/repo/images/buttons/url_button_resized.png differ
diff --git a/repo/images/headers/readme_header_black.webp b/repo/images/headers/readme_header_black.webp
new file mode 100644
index 00000000..7005c4fc
Binary files /dev/null and b/repo/images/headers/readme_header_black.webp differ
diff --git a/repo/images/headers/readme_header_default.webp b/repo/images/headers/readme_header_default.webp
new file mode 100644
index 00000000..d2d7e329
Binary files /dev/null and b/repo/images/headers/readme_header_default.webp differ
diff --git a/repo/images/headers/source_header_black.webp b/repo/images/headers/source_header_black.webp
new file mode 100644
index 00000000..ecc4cf41
Binary files /dev/null and b/repo/images/headers/source_header_black.webp differ
diff --git a/repo/images/headers/source_header_default.webp b/repo/images/headers/source_header_default.webp
new file mode 100644
index 00000000..9d897046
Binary files /dev/null and b/repo/images/headers/source_header_default.webp differ
diff --git a/repo/images/icons/icon_black.webp b/repo/images/icons/icon_black.webp
new file mode 100644
index 00000000..6d099f85
Binary files /dev/null and b/repo/images/icons/icon_black.webp differ
diff --git a/repo/images/icons/icon_default.webp b/repo/images/icons/icon_default.webp
new file mode 100644
index 00000000..53846866
Binary files /dev/null and b/repo/images/icons/icon_default.webp differ
diff --git a/repo/images/news/available_black.webp b/repo/images/news/available_black.webp
new file mode 100644
index 00000000..f5a02b13
Binary files /dev/null and b/repo/images/news/available_black.webp differ
diff --git a/repo/images/news/available_default.webp b/repo/images/news/available_default.webp
new file mode 100644
index 00000000..3455b177
Binary files /dev/null and b/repo/images/news/available_default.webp differ
diff --git a/repo/images/news/update_black.webp b/repo/images/news/update_black.webp
new file mode 100644
index 00000000..91562293
Binary files /dev/null and b/repo/images/news/update_black.webp differ
diff --git a/repo/images/news/update_default.webp b/repo/images/news/update_default.webp
new file mode 100644
index 00000000..4452574f
Binary files /dev/null and b/repo/images/news/update_default.webp differ
diff --git a/repo/images/screenshots/image_0_black.webp b/repo/images/screenshots/image_0_black.webp
new file mode 100644
index 00000000..1bb50c46
Binary files /dev/null and b/repo/images/screenshots/image_0_black.webp differ
diff --git a/repo/images/screenshots/image_0_default.webp b/repo/images/screenshots/image_0_default.webp
new file mode 100644
index 00000000..26765a35
Binary files /dev/null and b/repo/images/screenshots/image_0_default.webp differ
diff --git a/repo/images/screenshots/image_1_black.webp b/repo/images/screenshots/image_1_black.webp
new file mode 100644
index 00000000..89211189
Binary files /dev/null and b/repo/images/screenshots/image_1_black.webp differ
diff --git a/repo/images/screenshots/image_1_default.webp b/repo/images/screenshots/image_1_default.webp
new file mode 100644
index 00000000..26ff2ff7
Binary files /dev/null and b/repo/images/screenshots/image_1_default.webp differ
diff --git a/repo/images/screenshots/image_2_black.webp b/repo/images/screenshots/image_2_black.webp
new file mode 100644
index 00000000..5bba8ee2
Binary files /dev/null and b/repo/images/screenshots/image_2_black.webp differ
diff --git a/repo/images/screenshots/image_2_default.webp b/repo/images/screenshots/image_2_default.webp
new file mode 100644
index 00000000..fcee2e94
Binary files /dev/null and b/repo/images/screenshots/image_2_default.webp differ
diff --git a/repo/images/templates/Archive.7z b/repo/images/templates/Archive.7z
new file mode 100644
index 00000000..fca48203
Binary files /dev/null and b/repo/images/templates/Archive.7z differ
diff --git a/repo/source.json b/repo/source.json
new file mode 100755
index 00000000..ad300ece
--- /dev/null
+++ b/repo/source.json
@@ -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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/repo/update_source.py b/repo/update_source.py
new file mode 100755
index 00000000..6c56996b
--- /dev/null
+++ b/repo/update_source.py
@@ -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()