adb-toolkit/scripts/gen_debloat_db.py

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())