From cc92bc3e26797002607814976fbdfe20db73eff2 Mon Sep 17 00:00:00 2001 From: pika Date: Mon, 9 Dec 2024 22:46:57 +0100 Subject: [PATCH] addet some fixes and styling --- go.mod | 4 ++ scraper/scraper.go | 96 +++++++++++++++++++++++++++++++++++++++++++++- tui/tui.go | 31 ++++++++++++++- 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e3b7ba1..ca7196f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.23.4 require ( github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbletea v1.2.4 + github.com/disintegration/imaging v1.6.2 + github.com/mattn/go-sixel v0.0.5 google.golang.org/api v0.210.0 ) @@ -35,12 +37,14 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/soniakeys/quant v1.0.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect golang.org/x/crypto v0.29.0 // indirect + golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.9.0 // indirect diff --git a/scraper/scraper.go b/scraper/scraper.go index 34c8e1e..3673ddc 100644 --- a/scraper/scraper.go +++ b/scraper/scraper.go @@ -3,7 +3,10 @@ package scraper import ( "context" "fmt" + "time" // "log" + "html" + "strings" "google.golang.org/api/option" "google.golang.org/api/youtube/v3" @@ -22,6 +25,93 @@ type Video struct { // Replace with your actual API key const API_KEY = "AIzaSyAzsihRkp8mYTOXLOkVN09yTqld9TJ4Nts" +func formatViews(count uint64) string { + switch { + case count >= 1000000000: + return fmt.Sprintf("%.1fB views", float64(count)/1000000000) + case count >= 1000000: + return fmt.Sprintf("%.1fM views", float64(count)/1000000) + case count >= 1000: + return fmt.Sprintf("%.1fK views", float64(count)/1000) + default: + return fmt.Sprintf("%d views", count) + } +} + +func formatDuration(duration string) string { + // Remove PT from the start + duration = strings.TrimPrefix(duration, "PT") + + var result strings.Builder + + // Handle hours + if i := strings.Index(duration, "H"); i != -1 { + result.WriteString(duration[:i]) + result.WriteString(":") + duration = duration[i+1:] + } + + // Handle minutes + if i := strings.Index(duration, "M"); i != -1 { + minutes := duration[:i] + if len(minutes) == 1 { + result.WriteString("0") + } + result.WriteString(minutes) + result.WriteString(":") + duration = duration[i+1:] + } else if result.Len() > 0 { + result.WriteString("00:") + } + + // Handle seconds + if i := strings.Index(duration, "S"); i != -1 { + seconds := duration[:i] + if len(seconds) == 1 { + result.WriteString("0") + } + result.WriteString(seconds) + } else { + result.WriteString("00") + } + + return result.String() +} + +func formatUploadDate(uploadDate string) string { + t, err := time.Parse(time.RFC3339, uploadDate) + if err != nil { + return uploadDate + } + + now := time.Now() + diff := now.Sub(t) + days := int(diff.Hours() / 24) + + formattedDate := t.Format("02-01-2006") + + // If video is less than 30 days old, add "X days ago" + if days < 30 { + var timeAgo string + switch { + case days == 0: + hours := int(diff.Hours()) + if hours == 0 { + timeAgo = "just now" + } else { + timeAgo = fmt.Sprintf("%dh ago", hours) + } + case days == 1: + timeAgo = "1 day ago" + default: + timeAgo = fmt.Sprintf("%d days ago", days) + } + return fmt.Sprintf("%s (%s)", formattedDate, timeAgo) + } + + return formattedDate +} + func FetchVideos(query string) ([]Video, error) { ctx := context.Background() youtubeService, err := youtube.NewService(ctx, option.WithAPIKey(API_KEY)) @@ -70,8 +160,10 @@ func FetchVideos(query string) ([]Video, error) { // Update videos with additional information for i, stat := range statsResponse.Items { if i < len(videos) { - videos[i].Duration = stat.ContentDetails.Duration - videos[i].Views = fmt.Sprintf("%s views", stat.Statistics.ViewCount) + videos[i].Duration = formatDuration(stat.ContentDetails.Duration) + videos[i].Views = formatViews(stat.Statistics.ViewCount) + videos[i].Title = html.UnescapeString(videos[i].Title) + videos[i].UploadDate = formatUploadDate(videos[i].UploadDate) } } diff --git a/tui/tui.go b/tui/tui.go index 720fc9e..ce9b53b 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "strings" + "time" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/textinput" @@ -29,6 +30,7 @@ type model struct { err error width int height int + clock time.Time } // Initial model setup @@ -44,13 +46,19 @@ func initialModel() model { return model{ searchBar: searchBar, results: results, - showSearch: true, // Start in search mode + showSearch: true, + clock: time.Now(), + width: 80, // Default width + height: 24, // Default height } } // Init initializes the Bubble Tea program func (m model) Init() tea.Cmd { - return textinput.Blink + return tea.Batch( + textinput.Blink, + tickCmd(), + ) } // Update handles updates to the model based on user input @@ -112,6 +120,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width m.height = msg.Height m.results.SetSize(msg.Width, msg.Height-3) // Leave space for search bar + + case time.Time: + m.clock = msg + return m, tickCmd() } var cmd tea.Cmd @@ -127,6 +139,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m model) View() string { var s strings.Builder + // Add clock to top-right corner with safety check for window width + clock := m.clock.Format("15:04:05 02-01-2006") + if m.width > len(clock) { + padding := strings.Repeat(" ", m.width-len(clock)) + s.WriteString(padding + clock + "\n\n") + } else { + s.WriteString(clock + "\n\n") + } + // Always show search bar at the top searchText := "Press '/' to search" if m.showSearch { @@ -150,3 +171,9 @@ func Run() error { p := tea.NewProgram(initialModel()) return p.Start() } + +func tickCmd() tea.Cmd { + return tea.Every(time.Second, func(t time.Time) tea.Msg { + return t + }) +}