#!/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 [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())