Null-DRM-Official/apps/modules/provider/provider.go
404errordeveloper 2fa8f2435f Initial commit: Null DRM Official
Capture, decrypt, and restream toolkit with compiled-in app modules
(RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www.
BBC module.yaml is published (clear streams); other module values stay local.
2026-10-06 00:25:35 +02:00

147 lines
4.2 KiB
Go

// 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
}