From 97646d1382ca942ceef4103320d8d2bde5f597a4 Mon Sep 17 00:00:00 2001 From: Dymas Date: Tue, 1 Sep 2026 13:00:18 +0200 Subject: [PATCH] Clarify local provider update status --- CHANGELOG.md | 5 +++++ README.md | 2 +- VERSION | 2 +- config_page.py | 2 ++ provider_updates.py | 51 ++++++++++++++++++++++++++++++++++++--------- test_app.py | 20 ++++++++++++++++++ 6 files changed, 70 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 655abef..9c0bdbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.52.19 - 2026-09-01 + +- Changed the provider update check to report newer local provider patches separately from upstream updates, avoiding false update warnings for the patched Anikoto module. +- Updated the Config page provider badge list to show local provider patch versions. + ## 0.52.18 - 2026-08-30 - Fixed Anikoto dub availability detection when episode-list flags report sub-only episodes but the episode server list exposes dub streams. diff --git a/README.md b/README.md index d574a06..e90636e 100644 --- a/README.md +++ b/README.md @@ -116,4 +116,4 @@ The Anikoto, AniNeko, and AnimePahe parser modules in `providers/extensions/Anim - AniNeko `3.0.3` - AnimePahe `4.0.1` -The Config page checks the bundled JavaScript against the upstream `main` branch and reports whether local provider files differ. Kaizoku acts as a local client-side parser/downloader wrapper and does not host media. +The Config page checks the bundled JavaScript against the upstream `main` branch and reports whether upstream updates are available. If Kaizoku carries a newer local provider patch than upstream, such as the patched Anikoto module, the Config page reports it separately as a local change. Kaizoku acts as a local client-side parser/downloader wrapper and does not host media. diff --git a/VERSION b/VERSION index e2783cb..f0f4383 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.52.18 +0.52.19 diff --git a/config_page.py b/config_page.py index 1e964d2..334b888 100644 --- a/config_page.py +++ b/config_page.py @@ -1101,6 +1101,7 @@ CONFIG_HTML = r""" function providerUpdateBadgeClass(status) { if (status === "up_to_date") return "ok"; + if (status === "local_changes") return "ok"; if (status === "updates_available") return "warn"; if (status === "error") return "bad"; return ""; @@ -1117,6 +1118,7 @@ CONFIG_HTML = r""" const files = [ ...(data.updated_files || []).map((item) => item.path), + ...(data.locally_modified_files || []).map((item) => `${item.path} local ${item.local_version || "modified"}`), ...(data.missing_upstream_files || []) ]; if (!files.length && status !== "checking") { diff --git a/provider_updates.py b/provider_updates.py index 99121a9..25ca01b 100644 --- a/provider_updates.py +++ b/provider_updates.py @@ -4,6 +4,7 @@ import hashlib import json +import re import urllib.error import urllib.request from datetime import datetime, timezone @@ -36,6 +37,7 @@ def default_status(status="idle", message="Provider update check has not run yet "upstream_tree_sha": "", "checked_files": 0, "updated_files": [], + "locally_modified_files": [], "missing_upstream_files": [], } @@ -55,6 +57,25 @@ def sha256_bytes(content): return hashlib.sha256(content).hexdigest() +def provider_module_version(content): + match = re.search(rb'version:\s*["\']([^"\']+)["\']', content) + if not match: + return "" + return match.group(1).decode("utf-8", errors="replace") + + +def compare_versions(left, right): + left_parts = [int(part) for part in re.findall(r"\d+", left or "")] + right_parts = [int(part) for part in re.findall(r"\d+", right or "")] + length = max(len(left_parts), len(right_parts)) + for index in range(length): + left_value = left_parts[index] if index < len(left_parts) else 0 + right_value = right_parts[index] if index < len(right_parts) else 0 + if left_value != right_value: + return 1 if left_value > right_value else -1 + return 0 + + def fetch_json(url, timeout=10): request = urllib.request.Request( url, @@ -100,6 +121,7 @@ def check_provider_updates(local_root=LOCAL_PROVIDER_ROOT, timeout=10): upstream_paths, upstream_tree_sha = upstream_provider_js_paths(timeout=timeout) updated_files = [] + locally_modified_files = [] missing_upstream_files = [] checked_files = 0 @@ -114,19 +136,27 @@ def check_provider_updates(local_root=LOCAL_PROVIDER_ROOT, timeout=10): local_hash = sha256_bytes(local_content) remote_hash = sha256_bytes(remote_content) if local_hash != remote_hash: - updated_files.append( - { - "path": relative_path, - "local_sha256": local_hash, - "upstream_sha256": remote_hash, - "upstream_url": remote_url, - } - ) + local_version = provider_module_version(local_content) + upstream_version = provider_module_version(remote_content) + item = { + "path": relative_path, + "local_sha256": local_hash, + "upstream_sha256": remote_hash, + "upstream_url": remote_url, + "local_version": local_version, + "upstream_version": upstream_version, + } + if local_version and upstream_version and compare_versions(local_version, upstream_version) > 0: + locally_modified_files.append(item) + else: + updated_files.append(item) - has_updates = bool(updated_files or missing_upstream_files) - if has_updates: + if updated_files or missing_upstream_files: message = "Provider updates are available from TheYogMehta/extensions." status_name = "updates_available" + elif locally_modified_files: + message = "Bundled provider JavaScript includes local changes ahead of upstream." + status_name = "local_changes" else: message = "Bundled provider JavaScript is up to date." status_name = "up_to_date" @@ -140,6 +170,7 @@ def check_provider_updates(local_root=LOCAL_PROVIDER_ROOT, timeout=10): "upstream_tree_sha": upstream_tree_sha, "checked_files": checked_files, "updated_files": updated_files, + "locally_modified_files": locally_modified_files, "missing_upstream_files": missing_upstream_files, } diff --git a/test_app.py b/test_app.py index 64cb93a..8ba3a2b 100644 --- a/test_app.py +++ b/test_app.py @@ -3479,6 +3479,26 @@ class ProviderUpdateTests(unittest.TestCase): self.assertEqual(status["checked_files"], 1) self.assertEqual([item["path"] for item in status["updated_files"]], ["Anime/pahe.js"]) + def test_provider_update_check_reports_local_provider_patch(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + provider_file = root / "Anime" / "anikoto.js" + provider_file.parent.mkdir(parents=True) + provider_file.write_text('module.exports = { version: "5.0.3" };\n', encoding="utf-8") + + with mock.patch.object( + provider_updates, "upstream_provider_js_paths", return_value=({"Anime/anikoto.js"}, "tree-sha") + ), mock.patch.object( + provider_updates, "fetch_bytes", return_value=b'module.exports = { version: "5.0.2" };\n' + ): + status = provider_updates.check_provider_updates(local_root=root) + + self.assertEqual(status["status"], "local_changes") + self.assertEqual(status["updated_files"], []) + self.assertEqual([item["path"] for item in status["locally_modified_files"]], ["Anime/anikoto.js"]) + self.assertEqual(status["locally_modified_files"][0]["local_version"], "5.0.3") + self.assertEqual(status["locally_modified_files"][0]["upstream_version"], "5.0.2") + def test_provider_update_api_returns_current_status(self): status = provider_updates.default_status("updates_available", "Providers changed.") status["checked_files"] = 3