// Package provider holds the plumbing every app module shares: loading its local // values file, channel id normalization and aliasing, and the boilerplate half of // app.App. Module packages embed Base and add their own navigation and catalog. package provider import ( "fmt" "os" "path/filepath" "sort" "strings" "drmdecryption/capture" "drmdecryption/modcfg" "drmdecryption/phonecap" "drmdecryption/repo" ) // Common is the part of a module.yaml that every module has. Modules embed it in // their own config struct with `yaml:",inline"`. type Common struct { Package string `yaml:"package"` LicenseURL string `yaml:"license_url"` ProxyBin string `yaml:"proxy_bin"` CAHash string `yaml:"ca_hash"` Aliases map[string]string `yaml:"aliases"` Score capture.ScoreCfg `yaml:"score"` } // Base implements the boilerplate half of app.App. type Base struct { name string common Common labels map[string]string // canonical channel id -> label cacheDir string cfg modcfg.Result } // New builds a Base. labels maps canonical channel ids to display labels. func New(name string, common Common, labels map[string]string, cfg modcfg.Result) *Base { root := repo.Root() cacheDir := filepath.Join(root, ".cache", "ui-"+name) _ = os.MkdirAll(cacheDir, 0o755) return &Base{name: name, common: common, labels: labels, cacheDir: cacheDir, cfg: cfg} } func (b *Base) Name() string { return b.name } func (b *Base) Package() string { return b.common.Package } func (b *Base) LicenseURL() string { return b.common.LicenseURL } func (b *Base) CacheDir() string { return b.cacheDir } // ConfigPath / ConfigLoaded report where this module's values came from. func (b *Base) ConfigPath() string { return b.cfg.Path } func (b *Base) ConfigLoaded() bool { return b.cfg.Loaded } // ProxyBin resolves the on-device MITM binary, relative paths against repo root. func (b *Base) ProxyBin() string { if b.common.ProxyBin != "" { return modcfg.Resolve(b.common.ProxyBin) } root := repo.Root() for _, p := range []string{ filepath.Join(root, "bin", "proxy-android-arm64"), filepath.Join(root, "apps", "proxy", "proxy-android-arm64"), } { if st, err := os.Stat(p); err == nil && !st.IsDir() { return p } } return filepath.Join(root, "bin", "proxy-android-arm64") } func (b *Base) CAHash() string { if b.common.CAHash == "" { return phonecap.DefaultCAHash } return b.common.CAHash } // Score is this module's manifest-URL scoring, merged onto the neutral baseline. func (b *Base) Score() capture.ScoreCfg { return capture.DefaultScoreCfg().Merge(b.common.Score) } // Hints returns device-layout hints with this module's scoring applied. func (b *Base) Hints(require ...string) capture.Hints { h := capture.DefaultHints() h.Require = require h.Score = b.Score() return h } // NormalizeID folds a channel id to its comparison form. func NormalizeID(id string) string { id = strings.ToLower(strings.TrimSpace(id)) for _, ch := range []string{"-", "_", " "} { id = strings.ReplaceAll(id, ch, "") } return id } // Resolve maps any spelling or alias of a channel to its canonical id. func (b *Base) Resolve(id string) (string, error) { want := NormalizeID(id) for canon := range b.labels { if NormalizeID(canon) == want { return canon, nil } } for alias, canon := range b.common.Aliases { if NormalizeID(alias) == want { if _, ok := b.labels[canon]; ok { return canon, nil } } } known := make([]string, 0, len(b.labels)) for k := range b.labels { known = append(known, k) } return "", fmt.Errorf("unknown channel %q for %s (known: %s) — add it to %s", id, b.name, strings.Join(known, "|"), b.cfg.Path) } func (b *Base) HasChannel(id string) bool { _, err := b.Resolve(id) return err == nil } // Label returns a channel's display label, falling back to its id. func (b *Base) Label(canonID string) string { if l := b.labels[canonID]; l != "" { return l } return canonID } // ChannelIDs lists canonical channel ids, sorted so listings and API responses // are stable (map iteration order is not). func (b *Base) ChannelIDs() []string { out := make([]string, 0, len(b.labels)) for k := range b.labels { out = append(out, k) } sort.Strings(out) return out }