package cmd import ( "context" "fmt" "slices" "time" "github.com/blang/mpv" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/simonhege/timeago" miniflux "miniflux.app/client" ) var ( titleStyle = lipgloss.NewStyle().MarginLeft(2) itemStyle = lipgloss.NewStyle().PaddingLeft(4) selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) ) type Test uint const ( ViewListCategories = 10 ) type feedEntry struct { Name string Feed string Link string PublishedAt time.Time } type MsgFetchedEntries []feedEntry type MsgFetchedEntriesError error func (fe feedEntry) Title() string { return fe.Name } func (fe feedEntry) Description() string { formattedTimeAgo := timeago.WithMax(timeago.English, time.Hour*7*24, timeago.English.DefaultLayout).Format(fe.PublishedAt) return fmt.Sprintf("%s - Published %s", fe.Feed, formattedTimeAgo) } func (fe feedEntry) FilterValue() string { return fmt.Sprintf("%s - %s", fe.Name, fe.Feed) } type MinifluxPlayer struct { MinifluxClient *miniflux.Client DefaultCategory string MpvPath string // feedEntriesList []feedEntry feedEntriesList list.Model selectedEntry *feedEntry } func (mp *MinifluxPlayer) fetchEntries() tea.Msg { entriesResult, err := mp.MinifluxClient.CategoryEntries(4, nil) if err != nil { return MsgFetchedEntriesError(err) } var feedEntries []feedEntry for _, e := range entriesResult.Entries { feedEntries = append(feedEntries, feedEntry{ Name: e.Title, Feed: e.Feed.Title, Link: e.URL, PublishedAt: e.Date, }) } slices.Reverse(feedEntries) return MsgFetchedEntries(feedEntries) } func (mp *MinifluxPlayer) Init() tea.Cmd { mp.feedEntriesList = list.New([]list.Item{feedEntry{}}, list.NewDefaultDelegate(), 100, 50) mp.feedEntriesList.Title = "Feed" return mp.fetchEntries } func (mp *MinifluxPlayer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: mp.feedEntriesList.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return mp, tea.Quit case "enter": i, ok := mp.feedEntriesList.SelectedItem().(feedEntry) if ok { mp.selectedEntry = &i } return mp, tea.Quit } break case MsgFetchedEntries: var items []list.Item for _, e := range msg { items = append(items, e) } mp.feedEntriesList.SetItems(items) case MsgFetchedEntriesError: break } var cmd tea.Cmd mp.feedEntriesList, cmd = mp.feedEntriesList.Update(msg) return mp, cmd } func (mp *MinifluxPlayer) View() string { if mp.selectedEntry != nil { ipcc := mpv.NewIPCClient("/tmp/mpvsocket") // Lowlevel client c := mpv.NewClient(ipcc) // Highlevel client, can also use RPCClient c.Loadfile(mp.selectedEntry.Link, mpv.LoadFileModeReplace) // c.SetPause(true) // c.Seek(600, mpv.SeekModeAbsolute) // c.SetFullscreen(true) c.SetPause(false) time.Sleep(10 * time.Second) for { pos, _ := c.Position() dur, _ := c.Duration() fmt.Printf("%f/%f\n", pos, dur) if pos >= dur { break } } return quitTextStyle.Render(fmt.Sprintf("Selected entry %s", mp.selectedEntry.Link)) } return mp.feedEntriesList.View() } func (mp *MinifluxPlayer) FetchCategories(ctx context.Context) ([]*miniflux.Category, error) { return mp.MinifluxClient.Categories() }