Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moved many blocks of code from player.go to new files #69

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading