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.
This commit is contained in:
404errordeveloper 2026-10-06 00:25:35 +02:00
commit 2fa8f2435f
121 changed files with 17802 additions and 0 deletions

View file

@ -0,0 +1,111 @@
package poller
import (
"fmt"
"log"
"strings"
"time"
"drmdecryption/apps/agent/internal/client"
"drmdecryption/apps/agent/internal/queue"
)
type Poller struct {
API *client.Client
Queue *queue.Store
MaxAttempts int
Interval time.Duration
Log *log.Logger
}
func (p *Poller) Once() error {
streams, err := p.API.ListStreams()
if err != nil {
return err
}
for _, st := range streams {
if !st.Enabled {
continue
}
reason := needsRefresh(st)
if reason == "" {
continue
}
j, added, err := p.Queue.EnqueueIfIdle(st.ID, st.Name, st.App, st.Channel, reason, p.MaxAttempts)
if err != nil {
return err
}
if added {
p.logf("enqueued job=%d stream=%s reason=%s", j.ID, st.Name, reason)
continue
}
if active, ok, err := p.Queue.ActiveJobForStream(st.ID); err == nil && ok {
extra := ""
if active.Status == queue.StatusQueued && !active.NotBefore.IsZero() && active.NotBefore.After(time.Now().UTC()) {
extra = fmt.Sprintf(" backoff_until=%s", active.NotBefore.Format(time.RFC3339))
}
p.logf("stream %s needs %s but job=%d already %s (attempts %d/%d)%s",
st.Name, reason, active.ID, active.Status, active.Attempts, active.MaxAttempts, extra)
}
}
return nil
}
func (p *Poller) Loop(stop <-chan struct{}) {
t := time.NewTicker(p.Interval)
defer t.Stop()
_ = p.Once()
for {
select {
case <-stop:
return
case <-t.C:
if err := p.Once(); err != nil {
p.logf("poll error: %v", err)
}
}
}
}
func needsRefresh(st client.Stream) string {
if strings.TrimSpace(st.MPD) == "" || strings.TrimSpace(st.Key) == "" {
return "missing_credentials"
}
if !validKeyPair(st.Key) {
return "invalid_credentials"
}
h := strings.ToLower(strings.TrimSpace(st.Health))
switch h {
case "down", "error", "failed":
return "down"
}
return ""
}
// validKeyPair accepts KID:KEY as 32-hex:32-hex (colons/dashes in KID ignored).
func validKeyPair(key string) bool {
parts := strings.SplitN(strings.TrimSpace(key), ":", 2)
if len(parts) != 2 {
return false
}
hexOnly := func(s string) string {
var b strings.Builder
for _, r := range s {
switch {
case r >= '0' && r <= '9', r >= 'a' && r <= 'f', r >= 'A' && r <= 'F':
b.WriteRune(r)
}
}
return b.String()
}
kid, k := hexOnly(parts[0]), hexOnly(parts[1])
return len(kid) == 32 && len(k) == 32
}
func (p *Poller) logf(format string, args ...any) {
if p.Log != nil {
p.Log.Printf(format, args...)
return
}
log.Printf(format, args...)
}