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 +AltStore Source +  +Feather Source +  +Sidestore Source +  +Direct URL Source + +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()