Debug functiional
This commit is contained in:
+44
-108
@@ -14,12 +14,6 @@ import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
//
|
||||
// ────────────────────────────────
|
||||
// KEY MAP + HELP
|
||||
// ────────────────────────────────
|
||||
//
|
||||
|
||||
type keyMap struct {
|
||||
Up, Down, Left, Right key.Binding
|
||||
Enter, Quit, Refresh key.Binding
|
||||
@@ -53,14 +47,8 @@ func (k keyMap) FullHelp() [][]key.Binding {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// ────────────────────────────────
|
||||
// MESSAGE TYPES
|
||||
// ────────────────────────────────
|
||||
//
|
||||
|
||||
type (
|
||||
sportsLoadedMsg []Sport
|
||||
sportsLoadedMsg []Sport
|
||||
matchesLoadedMsg struct {
|
||||
Matches []Match
|
||||
Title string
|
||||
@@ -68,14 +56,9 @@ type (
|
||||
streamsLoadedMsg []Stream
|
||||
errorMsg error
|
||||
launchStreamMsg struct{ URL string }
|
||||
debugLogMsg string
|
||||
)
|
||||
|
||||
//
|
||||
// ────────────────────────────────
|
||||
// MODEL
|
||||
// ────────────────────────────────
|
||||
//
|
||||
|
||||
type focusCol int
|
||||
type viewMode int
|
||||
|
||||
@@ -92,8 +75,7 @@ const (
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
apiClient *Client
|
||||
|
||||
apiClient *Client
|
||||
styles Styles
|
||||
keys keyMap
|
||||
help help.Model
|
||||
@@ -110,12 +92,6 @@ type Model struct {
|
||||
TerminalWidth int
|
||||
}
|
||||
|
||||
//
|
||||
// ────────────────────────────────
|
||||
// APP ENTRYPOINT
|
||||
// ────────────────────────────────
|
||||
//
|
||||
|
||||
func Run() error {
|
||||
p := tea.NewProgram(New(), tea.WithAltScreen())
|
||||
_, err := p.Run()
|
||||
@@ -125,16 +101,16 @@ func Run() error {
|
||||
func New() Model {
|
||||
base := BaseURLFromEnv()
|
||||
client := NewClient(base, 15*time.Second)
|
||||
|
||||
styles := NewStyles()
|
||||
|
||||
m := Model{
|
||||
apiClient: client,
|
||||
styles: styles,
|
||||
keys: defaultKeys(),
|
||||
help: help.New(),
|
||||
focus: focusSports,
|
||||
apiClient: client,
|
||||
styles: styles,
|
||||
keys: defaultKeys(),
|
||||
help: help.New(),
|
||||
focus: focusSports,
|
||||
currentView: viewMain,
|
||||
debugLines: []string{},
|
||||
debugLines: []string{},
|
||||
}
|
||||
|
||||
m.sports = NewListColumn[Sport]("Sports", func(s Sport) string { return s.Name })
|
||||
@@ -162,12 +138,6 @@ func (m Model) Init() tea.Cmd {
|
||||
return tea.Batch(m.fetchSports(), m.fetchPopularMatches())
|
||||
}
|
||||
|
||||
//
|
||||
// ────────────────────────────────
|
||||
// VIEW
|
||||
// ────────────────────────────────
|
||||
//
|
||||
|
||||
func (m Model) View() string {
|
||||
switch m.currentView {
|
||||
case viewHelp:
|
||||
@@ -217,7 +187,7 @@ func (m Model) renderHelpPanel() string {
|
||||
|
||||
panel := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("#FA8072")). // salmon border
|
||||
BorderForeground(lipgloss.Color("#FA8072")).
|
||||
Padding(1, 2).
|
||||
Width(int(float64(m.TerminalWidth) * 0.97)).
|
||||
Render(sb.String())
|
||||
@@ -242,18 +212,18 @@ func (m Model) renderDebugPanel() string {
|
||||
return panel
|
||||
}
|
||||
|
||||
//
|
||||
// ────────────────────────────────
|
||||
// UPDATE
|
||||
// ────────────────────────────────
|
||||
//
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
|
||||
case debugLogMsg:
|
||||
m.debugLines = append(m.debugLines, string(msg))
|
||||
if len(m.debugLines) > 200 {
|
||||
m.debugLines = m.debugLines[len(m.debugLines)-200:]
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.TerminalWidth = msg.Width
|
||||
|
||||
usableHeight := int(float64(msg.Height) * 0.9)
|
||||
totalAvailableWidth := int(float64(msg.Width) * 0.97)
|
||||
borderPadding := 4
|
||||
@@ -351,7 +321,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
case focusStreams:
|
||||
if st, ok := m.streams.Selected(); ok {
|
||||
return m, m.launchMPV(st)
|
||||
return m, tea.Batch(
|
||||
m.logToUI(fmt.Sprintf("Attempting extractor for %s", st.EmbedURL)),
|
||||
m.runExtractor(st),
|
||||
)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
@@ -364,26 +337,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case key.Matches(msg, m.keys.OpenMPV):
|
||||
if m.focus == focusStreams {
|
||||
if st, ok := m.streams.Selected(); ok {
|
||||
go func(st Stream) {
|
||||
m.debugLines = append(m.debugLines, fmt.Sprintf("Attempting extractor for %s", st.EmbedURL))
|
||||
if err := m.forceMPVLaunch(st); err != nil {
|
||||
m.lastError = err
|
||||
m.debugLines = append(m.debugLines, fmt.Sprintf("Extractor failed: %v", err))
|
||||
} else {
|
||||
m.debugLines = append(m.debugLines, "Extractor success, launched MPV")
|
||||
}
|
||||
if len(m.debugLines) > 200 {
|
||||
m.debugLines = m.debugLines[len(m.debugLines)-200:]
|
||||
}
|
||||
}(st)
|
||||
m.status = fmt.Sprintf("🎞️ Attempting mpv: %s", st.EmbedURL)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
|
||||
@@ -415,12 +368,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
//
|
||||
// ────────────────────────────────
|
||||
// COMMANDS
|
||||
// ────────────────────────────────
|
||||
//
|
||||
|
||||
func (m Model) fetchSports() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
sports, err := m.apiClient.GetSports(context.Background())
|
||||
@@ -461,46 +408,35 @@ func (m Model) fetchStreamsForMatch(mt Match) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) launchMPV(st Stream) tea.Cmd {
|
||||
func (m Model) runExtractor(st Stream) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
url := st.EmbedURL
|
||||
if url == "" {
|
||||
return errorMsg(fmt.Errorf("empty embedUrl for stream %s", st.ID))
|
||||
if st.EmbedURL == "" {
|
||||
return debugLogMsg("Extractor aborted: empty embed URL")
|
||||
}
|
||||
cmd := exec.Command("mpv", "--no-terminal", "--really-quiet", url)
|
||||
m3u8, err := extractM3U8Lite(st.EmbedURL, func(line string) {
|
||||
// each extractor log line will flow back to the UI
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
return debugLogMsg(fmt.Sprintf("Extractor failed: %v", err))
|
||||
}
|
||||
cmd := exec.Command("mpv",
|
||||
"--no-terminal",
|
||||
"--really-quiet",
|
||||
fmt.Sprintf("--http-header-fields=User-Agent: %s", "Mozilla/5.0 (X11; Linux x86_64) Gecko/20100101 Firefox/144.0"),
|
||||
fmt.Sprintf("--http-header-fields=Origin: %s", "https://embedsports.top"),
|
||||
fmt.Sprintf("--http-header-fields=Referer: %s", "https://embedsports.top/"),
|
||||
m3u8,
|
||||
)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
_ = cmd.Start()
|
||||
return launchStreamMsg{URL: url}
|
||||
return debugLogMsg(fmt.Sprintf("Extractor success, launched MPV: %s", m3u8))
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) forceMPVLaunch(st Stream) error {
|
||||
embed := strings.TrimSpace(st.EmbedURL)
|
||||
if embed == "" {
|
||||
return fmt.Errorf("no embed URL for stream %s", st.ID)
|
||||
func (m Model) logToUI(line string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return debugLogMsg(line)
|
||||
}
|
||||
|
||||
m.debugLines = append(m.debugLines, fmt.Sprintf("[extractor] fetching %s", embed))
|
||||
origin, referer, ua, err := deriveHeaders(embed)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad embed URL: %w", err)
|
||||
}
|
||||
|
||||
body, err := fetchHTML(embed, ua, origin, referer, 12*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch failed: %w", err)
|
||||
}
|
||||
|
||||
m3u8 := extractM3U8(body)
|
||||
m.debugLines = append(m.debugLines, fmt.Sprintf("[extractor] yielded %s", m3u8))
|
||||
if m3u8 == "" {
|
||||
return fmt.Errorf("no .m3u8 found in embed page")
|
||||
}
|
||||
|
||||
if err := launchMPV(m3u8, ua, origin, referer); err != nil {
|
||||
return fmt.Errorf("mpv launch failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user