package app import ( "flag" "fmt" "sort" "sync" "drmdecryption/adb" "drmdecryption/capture" "drmdecryption/mpd" "drmdecryption/session" ) // Channel is a named live target inside an app (e.g. rteone). type Channel struct { ID string Label string } // App is a per-streaming-app plugin, compiled in from apps/modules/. The // capture core stays generic: an app supplies its package name, UI automation, // capture hints, key mode and optional MPD rewrite. type App interface { Name() string Package() string LicenseURL() string Launch(c *adb.Client) error AutoPlay(c *adb.Client, channel string) error CaptureHints() capture.Hints MPDRewriter() mpd.Rewriter Channels() []Channel HasChannel(id string) bool // ProxyBin is the on-device MITM binary to push. ProxyBin() string // CAHash is the MITM CA subject hash to reinject. CAHash() string // KeyMode selects the wvkey mode: "modulardrm" or "raw". KeyMode() string } // Catalog is implemented by apps whose channels can be resolved without a phone // (a public playback catalog). Drives the `catalog` subcommand. type Catalog interface { // Resolve returns streamd-ready credentials for a channel id. Resolve(channel string) (session.Stream, error) // EnrichWithKeys fills PSSH + KID:KEY using the local CDM. EnrichWithKeys(info *session.Stream, python, wvd string) error } // CatalogRow is one channel in a catalog listing. type CatalogRow struct { ID string CatalogID string Label string } // CatalogLister is the optional richer listing a catalog app may provide: each // channel's resolved catalog id plus whatever feature flags the provider // publishes alongside it. type CatalogLister interface { ListChannels() (rows []CatalogRow, flags map[string]bool, err error) } // StreamDefaults is implemented by apps that need particular downloader settings. // It replaces name-sniffing in the media supervisor. type StreamDefaults interface { // VideoSelect / AudioSelect are N_m3u8DL-RE selector expressions. VideoSelect() string AudioSelect() string // RewriterName is the registered mpd rewriter to run, or "none". RewriterName() string // LiveWaitSeconds is the downloader's live refresh wait. LiveWaitSeconds() int // TSReadyBytes is how much muxed output to buffer before probing. TSReadyBytes() int64 } // TransparentMITM is implemented by apps that must use iptables REDIRECT capture // (no Wi‑Fi http_proxy) so a phone VPN (e.g. NordVPN UK for BBC) can stay on. type TransparentMITM interface { UseTransparentMITM() bool } var ( mu sync.RWMutex registry = map[string]func() (App, error){} ) // Register adds a constructor for --app . Called from each module's init(). func Register(name string, ctor func() (App, error)) { mu.Lock() defer mu.Unlock() registry[name] = ctor } // Get constructs a registered app by name. func Get(name string) (App, error) { mu.RLock() ctor, ok := registry[name] mu.RUnlock() if !ok { return nil, fmt.Errorf("unknown app %q — compiled-in apps: %v", name, Names()) } return ctor() } // Names lists registered app ids, sorted. func Names() []string { mu.RLock() defer mu.RUnlock() out := make([]string, 0, len(registry)) for k := range registry { out = append(out, k) } sort.Strings(out) return out } // All constructs every registered app, skipping those that fail to build. func All() []App { out := []App{} for _, n := range Names() { a, err := Get(n) if err != nil { continue } out = append(out, a) } return out } // flagHooks are registered from each module's init() so overrides can be bound // to the module's config holder BEFORE any app is constructed. var flagHooks []func(*flag.FlagSet) // RegisterFlags records a module's flag binder. Called from init(). func RegisterFlags(bind func(*flag.FlagSet)) { mu.Lock() defer mu.Unlock() flagHooks = append(flagHooks, bind) } // BindFlags lets every compiled-in module register its value overrides on fs. // Call this before fs.Parse, and construct apps only afterwards. func BindFlags(fs *flag.FlagSet) { mu.RLock() hooks := append([]func(*flag.FlagSet){}, flagHooks...) mu.RUnlock() for _, h := range hooks { h(fs) } }