package tg4 import ( "fmt" "strings" "time" "drmdecryption/adb" "drmdecryption/uiflow" ) // openLivePage navigates to the Live page: if a menu entry is visible tap it, // otherwise open the side drawer first. Ported from the Starlark hook this module // used to carry, with every selector and coordinate now coming from config. func (a *App) openLivePage(c *adb.Client) error { c.DismissShadeIfFocused() nodes, err := c.DumpUI(a.CacheDir()) if err != nil { return err } if a.onLivePage(nodes) { fmt.Println("[*] already on Live") return nil } if live := a.findMenuLive(nodes); live != nil { fmt.Printf("[*] tap Live @ %d,%d\n", live.CX(), live.CY()) _ = c.Tap(live.CX(), live.CY()) time.Sleep(time.Duration(a.cfg.Timeouts.AfterLiveTap)) return nil } if !a.openDrawer(c) { fmt.Printf("[*] drawer fallback tap %d,%d\n", a.cfg.UI.DrawerFallbackX, a.cfg.UI.DrawerFallbackY) _ = c.Tap(a.cfg.UI.DrawerFallbackX, a.cfg.UI.DrawerFallbackY) time.Sleep(time.Duration(a.cfg.Timeouts.AfterDrawer)) } deadline := time.Now().Add(time.Duration(a.cfg.Timeouts.LiveNav)) for time.Now().Before(deadline) { nodes, err := c.DumpUI(a.CacheDir()) if err == nil { if live := a.findMenuLive(nodes); live != nil { fmt.Printf("[*] tap Live @ %d,%d\n", live.CX(), live.CY()) _ = c.Tap(live.CX(), live.CY()) time.Sleep(time.Duration(a.cfg.Timeouts.AfterLiveTap)) break } a.openDrawer(c) } time.Sleep(800 * time.Millisecond) } deadline = time.Now().Add(time.Duration(a.cfg.Timeouts.LiveNav)) for time.Now().Before(deadline) { if nodes, err := c.DumpUI(a.CacheDir()); err == nil && a.onLivePage(nodes) { fmt.Printf("[*] Live page ready\n") return nil } time.Sleep(800 * time.Millisecond) } return fmt.Errorf("Live page did not open") } // onLivePage is true when the Live title is showing and at least one content card // is on screen. func (a *App) onLivePage(nodes []adb.Node) bool { hasTitle := false for _, n := range nodes { if !strings.EqualFold(strings.TrimSpace(n.Text), a.cfg.UI.LiveLabel) { continue } if strings.Contains(n.ResourceID, a.cfg.UI.LiveTitleResource) || (n.Y1 < a.cfg.UI.TitleMaxY && !n.Clickable) { hasTitle = true break } } if !hasTitle { return false } return len(uiflow.Cards(nodes, a.cfg.Cards)) > 0 } // openDrawer opens the side menu. Returns false when the drawer button could not // be identified, so the caller can fall back to a fixed tap. func (a *App) openDrawer(c *adb.Client) bool { nodes, err := c.DumpUI(a.CacheDir()) if err != nil { return false } // Already open? for _, n := range nodes { desc := strings.TrimSpace(n.Desc) for _, d := range a.cfg.UI.DrawerDesc { if desc == d && d == "Closed" { return true } } if strings.EqualFold(strings.TrimSpace(n.Text), a.cfg.UI.LiveLabel) && n.Clickable && n.X2 < a.cfg.UI.MenuMaxX { return true } } ham := a.findDrawerButton(nodes) if ham == nil { return false } fmt.Printf("[*] tap menu %q @ %d,%d\n", ham.Desc, ham.CX(), ham.CY()) _ = c.Tap(ham.CX(), ham.CY()) time.Sleep(time.Duration(a.cfg.Timeouts.AfterDrawer)) return true } func (a *App) findDrawerButton(nodes []adb.Node) *adb.Node { for i := range nodes { n := &nodes[i] if !n.Clickable { continue } desc := strings.TrimSpace(n.Desc) for _, d := range a.cfg.UI.DrawerDesc { if desc == d { return n } } } // Geometric fallback: a small clickable node in the top-left corner. for i := range nodes { n := &nodes[i] if n.Clickable && n.X1 < 50 && n.Y1 < 200 && n.X2 < 200 && n.Y2 < 300 { return n } } return nil } // findMenuLive locates the drawer's Live entry. func (a *App) findMenuLive(nodes []adb.Node) *adb.Node { for i := range nodes { n := &nodes[i] if !n.Clickable { continue } if !strings.EqualFold(strings.TrimSpace(n.Text), a.cfg.UI.LiveLabel) { continue } if n.X2 <= a.cfg.UI.MenuMaxX && n.Y1 > a.cfg.UI.MenuMinY && n.Y1 < a.cfg.UI.MenuMaxY { return n } } return nil }