Clarify local provider update status

This commit is contained in:
Dymas
2026-09-01 13:00:18 +02:00
parent 07f73ea340
commit 97646d1382
6 changed files with 70 additions and 12 deletions
+5
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -1 +1 @@
0.52.18
0.52.19
+2
View File
@@ -1101,6 +1101,7 @@ CONFIG_HTML = r"""<!doctype html>
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"""<!doctype html>
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") {
+41 -10
View File
@@ -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,
}
+20
View File
@@ -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