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.
154 lines
4.1 KiB
Go
154 lines
4.1 KiB
Go
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/<name>. 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 <name>. 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)
|
||
}
|
||
}
|