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.
147 lines
4.2 KiB
Go
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
|
|
}
|