Skip to content

Commit

Permalink
Merge pull request #70 from the-eduardo/dev
Browse files Browse the repository at this point in the history
(Quality of Life) Moved many blocks of code from player.go to new files
  • Loading branch information
alvarorichard authored Jan 4, 2025
2 parents a6d57d3 + 4747359 commit 7105210
Show file tree
Hide file tree
Showing 10 changed files with 624 additions and 774 deletions.
3 changes: 0 additions & 3 deletions build/buildlinux.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@



#!/bin/bash

# Exit immediately if a command exits with a non-zero status
Expand Down
4 changes: 0 additions & 4 deletions build/buildmacos.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@




#!/bin/bash

# Exit immediately if a command exits with a non-zero status
Expand Down
2 changes: 0 additions & 2 deletions build/buildwindows.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


#!/bin/bash

# Sai imediatamente se um comando falhar
Expand Down
21 changes: 10 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
module github.com/alvarorichard/Goanime

go 1.23.3
go 1.23.4

require (
github.com/PuerkitoBio/goquery v1.10.0
github.com/PuerkitoBio/goquery v1.10.1
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.2.3
github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/lipgloss v1.0.0
github.com/hugolgst/rich-go v0.0.0-20240715122152-74618cc1ace2
github.com/ktr0731/go-fuzzyfinder v0.8.0
github.com/manifoldco/promptui v0.9.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
golang.org/x/net v0.31.0
golang.org/x/net v0.33.0
)

require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.5.2 // indirect
github.com/charmbracelet/x/ansi v0.6.0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand All @@ -39,10 +38,10 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.26.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

Expand Down
110 changes: 41 additions & 69 deletions go.sum

Large diffs are not rendered by default.

56 changes: 0 additions & 56 deletions internal/api/discord.go

This file was deleted.

138 changes: 138 additions & 0 deletions internal/player/discord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package player

import (
"fmt"
"github.com/alvarorichard/Goanime/internal/api"
"github.com/alvarorichard/Goanime/internal/util"
"github.com/hugolgst/rich-go/client"
"log"
"sync"
"time"
)

type RichPresenceUpdater struct {
anime *api.Anime
isPaused *bool
animeMutex *sync.Mutex
updateFreq time.Duration
done chan bool
wg sync.WaitGroup
startTime time.Time // Start time of playback
episodeDuration time.Duration // Total duration of the episode
episodeStarted bool // Whether the episode has started
socketPath string // Path to mpv IPC socket
}

func NewRichPresenceUpdater(anime *api.Anime, isPaused *bool, animeMutex *sync.Mutex, updateFreq time.Duration, episodeDuration time.Duration, socketPath string) *RichPresenceUpdater {
return &RichPresenceUpdater{
anime: anime,
isPaused: isPaused,
animeMutex: animeMutex,
updateFreq: updateFreq, // Make sure updateFreq is actually used in the struct
done: make(chan bool),
startTime: time.Now(),
episodeDuration: episodeDuration,
episodeStarted: false,
socketPath: socketPath,
}
}

func (rpu *RichPresenceUpdater) GetCurrentPlaybackPosition() (time.Duration, error) {
position, err := mpvSendCommand(rpu.socketPath, []interface{}{"get_property", "time-pos"})
if err != nil {
return 0, err
}

// Convert position to float64 and then to time.Duration
posSeconds, ok := position.(float64)
if !ok {
return 0, fmt.Errorf("failed to parse playback position")
}

return time.Duration(posSeconds) * time.Second, nil
}

// Start begins the periodic Rich Presence updates.
func (rpu *RichPresenceUpdater) Start() {
rpu.wg.Add(1)
go func() {
defer rpu.wg.Done()
ticker := time.NewTicker(rpu.updateFreq)
defer ticker.Stop()

for {
select {
case <-ticker.C:
go rpu.updateDiscordPresence() // Run update asynchronously
case <-rpu.done:
if util.IsDebug {
log.Println("Rich Presence updater received stop signal.")
}
return
}
}
}()
if util.IsDebug {
log.Println("Rich Presence updater started.")
}
}

// Stop signals the updater to stop and waits for the goroutine to finish.
func (rpu *RichPresenceUpdater) Stop() {
close(rpu.done)
rpu.wg.Wait()
if util.IsDebug {
log.Println("Rich Presence updater stopped.")

}
}

func (rpu *RichPresenceUpdater) updateDiscordPresence() {
rpu.animeMutex.Lock()
defer rpu.animeMutex.Unlock()

currentPosition, err := rpu.GetCurrentPlaybackPosition()
if err != nil {
if util.IsDebug {
log.Printf("Error fetching playback position: %v\n", err)
}
return
}

// Debug log to check episode duration
if util.IsDebug {
log.Printf("Episode Duration in updateDiscordPresence: %v seconds (%v minutes)\n", rpu.episodeDuration.Seconds(), rpu.episodeDuration.Minutes())

}

// Convert episode duration to minutes and seconds format
totalMinutes := int(rpu.episodeDuration.Minutes())
totalSeconds := int(rpu.episodeDuration.Seconds()) % 60 // Remaining seconds after full minutes

// Format the current playback position as minutes and seconds
timeInfo := fmt.Sprintf("%02d:%02d / %02d:%02d",
int(currentPosition.Minutes()), int(currentPosition.Seconds())%60,
totalMinutes, totalSeconds,
)

// Create the activity with updated Details
activity := client.Activity{
Details: fmt.Sprintf("%s | Episode %s | %s / %d min", rpu.anime.Details.Title.Romaji, rpu.anime.Episodes[0].Number, timeInfo, totalMinutes),
State: "Watching",
LargeImage: rpu.anime.ImageURL,
LargeText: rpu.anime.Details.Title.Romaji,
Buttons: []*client.Button{
{Label: "View on AniList", Url: fmt.Sprintf("https://anilist.co/anime/%d", rpu.anime.AnilistID)},
{Label: "View on MAL", Url: fmt.Sprintf("https://myanimelist.net/anime/%d", rpu.anime.MalID)},
},
}

// Set the activity in Discord Rich Presence
if err := client.SetActivity(activity); err != nil {
if util.IsDebug {
log.Printf("Error updating Discord Rich Presence: %v\n", err)
} else {
log.Printf("Discord Rich Presence updated with elapsed time: %s\n", timeInfo)
}
}
}
116 changes: 116 additions & 0 deletions internal/player/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package player

import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"strings"
"time"
)

// Update handles updates to the Bubble Tea model.
//
// This function processes incoming messages (`tea.Msg`) and updates the model's state accordingly.
// It locks the model's mutex to ensure thread safety, especially when modifying shared data like
// `m.received`, `m.totalBytes`, and other stateful properties.
//
// The function processes different message types, including:
//
// 1. `tickMsg`: A periodic message that triggers the progress update. If the download is complete
// (`m.done` is `true`), the program quits. Otherwise, it calculates the percentage of bytes received
// and updates the progress bar. It then schedules the next tick.
//
// 2. `statusMsg`: Updates the status string in the model, which can be used to display custom messages
// to the user, such as "Downloading..." or "Download complete".
//
// 3. `progress.FrameMsg`: Handles frame updates for the progress bar. It delegates the update to the
// internal `progress.Model` and returns any commands necessary to refresh the UI.
//
// 4. `tea.KeyMsg`: Responds to key events, such as quitting the program when "Ctrl+C" is pressed.
// If the user requests to quit, the program sets `m.done` to `true` and returns the quit command.
//
// For unhandled message types, it returns the model unchanged.
//
// Returns:
// - Updated `tea.Model` representing the current state of the model.
// - A `tea.Cmd` that specifies the next action the program should perform.
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.mu.Lock()
defer m.mu.Unlock()

switch msg := msg.(type) {
case tickMsg:
if m.done {
return m, tea.Quit
}
if m.totalBytes > 0 {
cmd := m.progress.SetPercent(float64(m.received) / float64(m.totalBytes))
return m, tea.Batch(cmd, tickCmd())
}
return m, tickCmd()

case statusMsg:
m.status = string(msg)
return m, nil

case progress.FrameMsg:
var cmd tea.Cmd
var newModel tea.Model
newModel, cmd = m.progress.Update(msg)
m.progress = newModel.(progress.Model)
return m, cmd

case tea.KeyMsg:
if key.Matches(msg, m.keys.quit) {
m.done = true
return m, tea.Quit
}
return m, nil

default:
return m, nil
}
}

// View renders the Bubble Tea model
// View renders the user interface for the Bubble Tea model.
//
// This function generates the visual output that is displayed to the user. It includes the status message,
// the progress bar, and a quit instruction. The layout is formatted with padding for proper alignment.
//
// Steps:
// 1. Adds padding to each line using spaces.
// 2. Styles the status message (m.status) with an orange color (#FFA500).
// 3. Displays the progress bar using the progress model.
// 4. Shows a message instructing the user to press "Ctrl+C" to quit.
//
// Returns:
// - A formatted string that represents the UI for the current state of the model.
func (m *model) View() string {
// Creates padding spaces for consistent layout
pad := strings.Repeat(" ", padding)

// Styles the status message with an orange color
statusStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))

// Returns the UI layout: status message, progress bar, and quit instruction
return "\n" +
pad + statusStyle.Render(m.status) + "\n\n" + // Render the styled status message
pad + m.progress.View() + "\n\n" + // Render the progress bar
pad + "Press Ctrl+C to quit" // Show quit instruction
}

// tickCmd returns a command that triggers a "tick" every 100 milliseconds.
//
// This function sets up a recurring event (tick) that fires every 100 milliseconds.
// Each tick sends a `tickMsg` with the current time (`t`) as a message, which can be
// handled by the update function to trigger actions like updating the progress bar.
//
// Returns:
// - A `tea.Cmd` that schedules a tick every 100 milliseconds and sends a `tickMsg`.
func tickCmd() tea.Cmd {
return tea.Tick(time.Millisecond*100, func(t time.Time) tea.Msg {
return tickMsg(t)
})
}
Loading

0 comments on commit 7105210

Please sign in to comment.