package capture import "strings" // ScoreCfg ranks candidate URLs seen by the MITM so a schedule/EPG/analytics URL // is never mistaken for a stream manifest. Providers supply their own hosts and // noise needles; this package ships only format-level scoring. type ScoreCfg struct { // Deny: any match scores the URL out entirely. Deny []string `yaml:"deny"` // Hosts: known-good manifest hosts for this provider. Hosts []string `yaml:"hosts"` // HostBonus is added when a Hosts entry matches (default 50). HostBonus int `yaml:"host_bonus"` } // DefaultScoreCfg is the provider-neutral baseline: catalog/analytics shapes that // are never a manifest for anyone. func DefaultScoreCfg() ScoreCfg { return ScoreCfg{ Deny: []string{ "schedules", "bylistingtime", "maxlistings", "bycallsign", "/feed.", "playback_config", "config.json", }, HostBonus: 50, } } // Merge overlays a provider's hosts and extra deny needles on the baseline. func (s ScoreCfg) Merge(other ScoreCfg) ScoreCfg { out := s out.Deny = append(append([]string{}, s.Deny...), other.Deny...) out.Hosts = append(append([]string{}, s.Hosts...), other.Hosts...) if other.HostBonus != 0 { out.HostBonus = other.HostBonus } if out.HostBonus == 0 { out.HostBonus = 50 } return out } // Score rates a URL as a live manifest. Zero or negative means "not a manifest". func (s ScoreCfg) Score(u string) int { lu := strings.ToLower(u) path := lu if i := strings.Index(path, "?"); i >= 0 { path = path[:i] } for _, bad := range s.Deny { if bad != "" && strings.Contains(lu, strings.ToLower(bad)) { return -100 } } score := 0 bonus := s.HostBonus if bonus == 0 { bonus = 50 } for _, host := range s.Hosts { if host != "" && strings.Contains(lu, strings.ToLower(host)) { score += bonus break } } // Format-level signals — true for any provider. if strings.Contains(path, "playlist-hls") || strings.Contains(path, "playlist.m3u8") { score += 40 } if strings.Contains(path, "chunklist") { score += 10 } if strings.HasSuffix(path, ".m3u8") { score += 5 } if strings.HasSuffix(path, ".mpd") || strings.Contains(path, "manifest.mpd") { score += 30 } if strings.Contains(path, ".isml") || strings.Contains(path, "/manifest") { score += 20 } return score } // ProxyArgs renders this config as flags for the on-device MITM, which runs on // the phone and cannot read an app module's values file itself. func (s ScoreCfg) ProxyArgs() []string { out := []string{} for _, h := range s.Hosts { if h != "" { out = append(out, "-score-host", h) } } for _, d := range s.Deny { if d != "" { out = append(out, "-score-deny", d) } } return out }