add apk filters, about descriptions, version extraction regex to app configs. fix Cemu versioning. refactor: store additionalSettings as real JSON in source file, stringify at export boundaries in scripts
This commit is contained in:
@@ -193,7 +193,7 @@ def generate_app_entry(
|
||||
"author": author,
|
||||
"name": name,
|
||||
"preferredApkIndex": 0,
|
||||
"additionalSettings": json.dumps(settings, separators=(",", ":")),
|
||||
"additionalSettings": settings,
|
||||
"categories": categories,
|
||||
"allowIdChange": allow_id_change,
|
||||
"overrideSource": source,
|
||||
|
||||
@@ -5,7 +5,7 @@ import json
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from utils import should_include_app
|
||||
from utils import should_include_app, stringify_additional_settings
|
||||
|
||||
|
||||
def minify_json(input_file: str, output_file: str, variant: str = "standard") -> None:
|
||||
@@ -19,6 +19,7 @@ def minify_json(input_file: str, output_file: str, variant: str = "standard") ->
|
||||
if should_include_app(app, variant):
|
||||
app_copy = app.copy()
|
||||
app_copy.pop("meta", None)
|
||||
app_copy["additionalSettings"] = stringify_additional_settings(app_copy)
|
||||
filtered_apps.append(app_copy)
|
||||
data["apps"] = filtered_apps
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from constants import SRC_FILE
|
||||
|
||||
@@ -20,28 +21,83 @@ KEY_ORDER = [
|
||||
"meta",
|
||||
]
|
||||
|
||||
# Canonical key order for additionalSettings - source-specific keys first,
|
||||
# then common keys, grouped logically. Matches DEFAULT_ADDITIONAL_SETTINGS
|
||||
# in add-app.py with source-specific keys prepended.
|
||||
SETTINGS_KEY_ORDER = [
|
||||
# GitHub/Codeberg source-specific
|
||||
"includePrereleases",
|
||||
"fallbackToOlderReleases",
|
||||
"filterReleaseTitlesByRegEx",
|
||||
"filterReleaseNotesByRegEx",
|
||||
"verifyLatestTag",
|
||||
"sortMethodChoice",
|
||||
"useLatestAssetDateAsReleaseDate",
|
||||
"releaseTitleAsVersion",
|
||||
"github-creds",
|
||||
"GHReqPrefix",
|
||||
# HTML source-specific
|
||||
"intermediateLink",
|
||||
"customLinkFilterRegex",
|
||||
"filterByLinkText",
|
||||
"matchLinksOutsideATags",
|
||||
"skipSort",
|
||||
"reverseSort",
|
||||
"sortByLastLinkSegment",
|
||||
"versionExtractWholePage",
|
||||
"requestHeader",
|
||||
"defaultPseudoVersioningMethod",
|
||||
# Common keys
|
||||
"trackOnly",
|
||||
"versionExtractionRegEx",
|
||||
"matchGroupToUse",
|
||||
"versionDetection",
|
||||
"releaseDateAsVersion",
|
||||
"useVersionCodeAsOSVersion",
|
||||
"apkFilterRegEx",
|
||||
"invertAPKFilter",
|
||||
"autoApkFilterByArch",
|
||||
"appName",
|
||||
"appAuthor",
|
||||
"shizukuPretendToBeGooglePlay",
|
||||
"allowInsecure",
|
||||
"exemptFromBackgroundUpdates",
|
||||
"skipUpdateNotifications",
|
||||
"about",
|
||||
"refreshBeforeDownload",
|
||||
"includeZips",
|
||||
"zippedApkFilterRegEx",
|
||||
]
|
||||
|
||||
# Fields to backfill with defaults when missing
|
||||
DEFAULTS: dict[str, object] = {
|
||||
"allowIdChange": False,
|
||||
}
|
||||
|
||||
|
||||
def _order_dict(d: dict[str, Any], key_order: list[str]) -> dict[str, Any]:
|
||||
ordered: dict[str, Any] = {}
|
||||
for key in key_order:
|
||||
if key in d:
|
||||
ordered[key] = d[key]
|
||||
# Preserve any unexpected keys at the end (safety net)
|
||||
for key in d:
|
||||
if key not in ordered:
|
||||
ordered[key] = d[key]
|
||||
return ordered
|
||||
|
||||
|
||||
def normalize_app(app: dict) -> dict:
|
||||
for key, default in DEFAULTS.items():
|
||||
if key not in app:
|
||||
app[key] = default
|
||||
|
||||
ordered: dict[str, object] = {}
|
||||
for key in KEY_ORDER:
|
||||
if key in app:
|
||||
ordered[key] = app[key]
|
||||
# Normalize additionalSettings key order if it's a dict
|
||||
settings = app.get("additionalSettings")
|
||||
if isinstance(settings, dict):
|
||||
app["additionalSettings"] = _order_dict(settings, SETTINGS_KEY_ORDER)
|
||||
|
||||
# Preserve any unexpected keys at the end (safety net)
|
||||
for key in app:
|
||||
if key not in ordered:
|
||||
ordered[key] = app[key]
|
||||
|
||||
return ordered
|
||||
return _order_dict(app, KEY_ORDER)
|
||||
|
||||
|
||||
def normalize(input_path: str) -> int:
|
||||
|
||||
@@ -18,7 +18,7 @@ from urllib.parse import urljoin, urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from help_formatter import StyledHelpFormatter
|
||||
from utils import load_dotenv
|
||||
from utils import get_additional_settings, load_dotenv
|
||||
|
||||
USER_AGENT = (
|
||||
"Mozilla/5.0 (Linux; Android 10; K) "
|
||||
@@ -495,12 +495,11 @@ def _effective_source(app: dict[str, Any]) -> str:
|
||||
def test_app(app: dict[str, Any]) -> TestResult:
|
||||
source = _effective_source(app)
|
||||
|
||||
settings_str = app.get("additionalSettings", "{}")
|
||||
try:
|
||||
settings = json.loads(settings_str) if isinstance(settings_str, str) else {}
|
||||
except json.JSONDecodeError:
|
||||
settings = get_additional_settings(app)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
result = TestResult(app.get("name", "?"), app.get("id", "?"), source, app.get("url", "?"))
|
||||
result.error = "Cannot parse additionalSettings JSON"
|
||||
result.error = "Cannot parse additionalSettings"
|
||||
return result
|
||||
|
||||
start = time.monotonic()
|
||||
|
||||
@@ -46,6 +46,24 @@ def get_application_url(app: dict[str, Any]) -> str:
|
||||
return app.get("meta", {}).get("urlOverride") or app.get("url", "")
|
||||
|
||||
|
||||
def get_additional_settings(app: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return additionalSettings as a dict, whether stored as object or JSON string."""
|
||||
raw = app.get("additionalSettings", {})
|
||||
if isinstance(raw, str):
|
||||
return json.loads(raw) if raw else {}
|
||||
if isinstance(raw, dict):
|
||||
return raw
|
||||
return {}
|
||||
|
||||
|
||||
def stringify_additional_settings(app: dict[str, Any]) -> str:
|
||||
"""Return additionalSettings as a compact JSON string for Obtainium consumption."""
|
||||
raw = app.get("additionalSettings", {})
|
||||
if isinstance(raw, str):
|
||||
return raw
|
||||
return json.dumps(raw, separators=(",", ":"))
|
||||
|
||||
|
||||
def make_obtainium_link(app: dict[str, Any]) -> str:
|
||||
payload = {
|
||||
"id": app["id"],
|
||||
@@ -55,7 +73,7 @@ def make_obtainium_link(app: dict[str, Any]) -> str:
|
||||
"otherAssetUrls": app.get("otherAssetUrls"),
|
||||
"apkUrls": app.get("apkUrls"),
|
||||
"preferredApkIndex": app.get("preferredApkIndex"),
|
||||
"additionalSettings": app.get("additionalSettings"),
|
||||
"additionalSettings": stringify_additional_settings(app),
|
||||
"categories": app.get("categories"),
|
||||
"overrideSource": app.get("overrideSource"),
|
||||
"allowIdChange": app.get("allowIdChange"),
|
||||
|
||||
@@ -15,7 +15,7 @@ from constants import (
|
||||
VALID_SOURCES,
|
||||
VARIANTS,
|
||||
)
|
||||
from utils import should_include_app
|
||||
from utils import get_additional_settings, should_include_app
|
||||
|
||||
REQUIRED_FIELDS = {"id", "url", "author", "name"}
|
||||
|
||||
@@ -155,14 +155,10 @@ def _validate_additional_settings(
|
||||
if raw is None:
|
||||
return errors, warnings
|
||||
|
||||
if not isinstance(raw, str):
|
||||
errors.append(f"{app_name}: 'additionalSettings' should be a JSON string")
|
||||
return errors, warnings
|
||||
|
||||
try:
|
||||
settings = json.loads(raw)
|
||||
except json.JSONDecodeError as e:
|
||||
errors.append(f"{app_name}: 'additionalSettings' contains invalid JSON: {e}")
|
||||
settings = get_additional_settings(app)
|
||||
except (json.JSONDecodeError, TypeError) as e:
|
||||
errors.append(f"{app_name}: 'additionalSettings' is invalid: {e}")
|
||||
return errors, warnings
|
||||
|
||||
if not isinstance(settings, dict):
|
||||
|
||||
Reference in New Issue
Block a user