174 lines
6.3 KiB
Python
174 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate frontend/src/lib/debloat_db.ts from UAD-ng's uad_lists.json.
|
|
|
|
Usage:
|
|
python3 scripts/gen_debloat_db.py <path/to/uad_lists.json> [output.ts]
|
|
|
|
UAD-ng (github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation)
|
|
ships its package database as resources/assets/uad_lists.json: a flat object keyed
|
|
by package name, each value having {list, description, dependencies, neededBy,
|
|
labels, removal}.
|
|
|
|
ATK keeps a richer per-manufacturer categorisation than upstream (UAD-ng collapsed
|
|
all OEM packages into a single "Oem" list). To preserve ATK's UX we re-derive the
|
|
manufacturer from the package-name prefix for the Oem bucket.
|
|
|
|
Mappings:
|
|
removal -> safety: Recommended->safe, Advanced->caution, Expert->caution, Unsafe->keep
|
|
list -> category: Google->Google, Carrier->Carriers, Aosp->AOSP, Misc->Misc,
|
|
Oem-> inferred by prefix (Samsung/Xiaomi/.../Other OEM)
|
|
"""
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
REMOVAL_TO_SAFETY = {
|
|
"Recommended": "safe",
|
|
"Advanced": "caution",
|
|
"Expert": "caution",
|
|
"Unsafe": "keep",
|
|
}
|
|
|
|
# Emit categories in this order; empty categories are skipped.
|
|
CATEGORY_ORDER = [
|
|
"Google", "Samsung", "Xiaomi", "Huawei", "OnePlus/Oppo", "Sony",
|
|
"Motorola", "LG", "Nokia/HMD", "Asus", "Carriers", "AOSP", "Misc", "Other OEM",
|
|
]
|
|
|
|
# Prefix rules for splitting UAD's "Oem" bucket back into manufacturers.
|
|
# First match wins; checked against the lower-cased package name.
|
|
OEM_RULES = [
|
|
("Samsung", ["com.samsung", "com.sec.android", "com.sec.", ".samsung."]),
|
|
("Xiaomi", ["com.xiaomi", "com.miui", "com.mi.", ".miui.", "com.redmi"]),
|
|
("Huawei", ["com.huawei", "com.hihonor", ".huawei.", ".hihonor."]),
|
|
("OnePlus/Oppo", ["com.oneplus", "com.oppo", "com.coloros", "com.nearme",
|
|
"com.heytap", "com.realme", "com.oplus"]),
|
|
("Sony", ["com.sonymobile", "com.sony", ".sonyericsson"]),
|
|
("Motorola", ["com.motorola", "com.moto", ".motorola."]),
|
|
("LG", ["com.lge", "com.lg.", ".lge."]),
|
|
("Nokia/HMD", ["com.hmdglobal", "com.nokia", "com.evenwell", ".hmd"]),
|
|
("Asus", ["com.asus", ".asus."]),
|
|
]
|
|
|
|
|
|
def categorize(pkg: str, uad_list: str) -> str:
|
|
if uad_list == "Google":
|
|
return "Google"
|
|
if uad_list == "Carrier":
|
|
return "Carriers"
|
|
if uad_list == "Aosp":
|
|
return "AOSP"
|
|
if uad_list == "Misc":
|
|
return "Misc"
|
|
# "Oem" (or anything unexpected) -> infer manufacturer from the prefix.
|
|
p = pkg.lower()
|
|
for cat, subs in OEM_RULES:
|
|
if any(s in p for s in subs):
|
|
return cat
|
|
return "Other OEM"
|
|
|
|
|
|
def make_label(pkg: str) -> str:
|
|
seg = pkg.split(".")[-1] or pkg
|
|
return seg[:1].upper() + seg[1:] if seg else pkg
|
|
|
|
|
|
def clean_desc(desc: str, pkg: str) -> str:
|
|
if not desc or not desc.strip():
|
|
return f"Package: {pkg}"
|
|
return re.sub(r"\s+", " ", desc).strip()
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 2:
|
|
print(__doc__)
|
|
return 1
|
|
src = sys.argv[1]
|
|
out = sys.argv[2] if len(sys.argv) > 2 else "frontend/src/lib/debloat_db.ts"
|
|
|
|
with open(src, encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
buckets = {name: [] for name in CATEGORY_ORDER}
|
|
for pkg, meta in data.items():
|
|
removal = meta.get("removal", "Unsafe")
|
|
safety = REMOVAL_TO_SAFETY.get(removal, "keep")
|
|
cat = categorize(pkg, meta.get("list", "Oem"))
|
|
entry = {
|
|
"pkg": pkg,
|
|
"label": make_label(pkg),
|
|
"description": clean_desc(meta.get("description", ""), pkg),
|
|
"safety": safety,
|
|
}
|
|
deps = meta.get("dependencies") or []
|
|
needed = meta.get("neededBy") or []
|
|
if deps:
|
|
entry["deps"] = deps
|
|
if needed:
|
|
entry["neededBy"] = needed
|
|
buckets[cat].append(entry)
|
|
|
|
total = sum(len(v) for v in buckets.values())
|
|
n_cats = sum(1 for v in buckets.values() if v)
|
|
|
|
lines = []
|
|
lines.append("// Auto-generated from UAD package list (Universal Android Debloater)")
|
|
lines.append("// Source: github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation")
|
|
lines.append(f"// Generated by scripts/gen_debloat_db.py - {total} packages across {n_cats} categories")
|
|
lines.append("// Do not edit by hand; re-run the generator against a fresh uad_lists.json instead.")
|
|
lines.append("")
|
|
lines.append("export type Safety = 'safe' | 'caution' | 'keep'")
|
|
lines.append("")
|
|
lines.append("export interface DebloatPackage {")
|
|
lines.append(" pkg: string")
|
|
lines.append(" label: string")
|
|
lines.append(" description: string")
|
|
lines.append(" safety: Safety")
|
|
lines.append(" deps?: string[]")
|
|
lines.append(" neededBy?: string[]")
|
|
lines.append("}")
|
|
lines.append("")
|
|
lines.append("export interface Category {")
|
|
lines.append(" name: string")
|
|
lines.append(" packages: DebloatPackage[]")
|
|
lines.append("}")
|
|
lines.append("")
|
|
lines.append("export const DEBLOAT_CATEGORIES: Category[] = [")
|
|
|
|
for name in CATEGORY_ORDER:
|
|
pkgs = buckets[name]
|
|
if not pkgs:
|
|
continue
|
|
pkgs.sort(key=lambda e: e["pkg"])
|
|
lines.append(" {")
|
|
lines.append(f" name: {json.dumps(name)},")
|
|
lines.append(" packages: [")
|
|
for e in pkgs:
|
|
parts = [
|
|
f"pkg: {json.dumps(e['pkg'])}",
|
|
f"label: {json.dumps(e['label'])}",
|
|
f"description: {json.dumps(e['description'], ensure_ascii=False)}",
|
|
f"safety: {json.dumps(e['safety'])}",
|
|
]
|
|
if "deps" in e:
|
|
parts.append(f"deps: {json.dumps(e['deps'], ensure_ascii=False)}")
|
|
if "neededBy" in e:
|
|
parts.append(f"neededBy: {json.dumps(e['neededBy'], ensure_ascii=False)}")
|
|
lines.append(" { " + ", ".join(parts) + " },")
|
|
lines.append(" ],")
|
|
lines.append(" },")
|
|
lines.append("]")
|
|
lines.append("")
|
|
|
|
with open(out, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(lines))
|
|
|
|
print(f"Wrote {out}: {total} packages, {n_cats} categories")
|
|
for name in CATEGORY_ORDER:
|
|
if buckets[name]:
|
|
print(f" {name:14} {len(buckets[name])}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|