Skip to content

Commit

Permalink
new voting (#2)
Browse files Browse the repository at this point in the history
* new voting

* added skip
  • Loading branch information
th0rn0 authored Apr 29, 2024
1 parent 4813428 commit 2b6a926
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 24 deletions.
9 changes: 6 additions & 3 deletions api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ CALLBACK_URL=http://localhost:8888/auth/callback
DB_PATH=jukebox.db
FALLBACK_PLAYLIST_URI=
FALLBACK_PLAYLIST_ADD_QUEUED=true
MINIMUM_VOTES_TO_REMOVE=1
MAXIMUM_VOTES_PER_HOUR=20
ADMIN_PASSWORD="changeme"
ADMIN_PASSWORD="changeme"

VOTES_TO_SKIP=5
VOTE_STANDARD_MAXIMUM_PER_HOUR=20
VOTE_STANDARD_MINIMUM_TO_REMOVE=1
VOTE_METHOD=skip
4 changes: 4 additions & 0 deletions api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func getNextSong(excludeUri ...spotify.URI) (Track, error) {
nextTrack.URI = nextSongFromPlayList.Track.Track.URI
nextTrack.FromFallBackPlaylist = true

fallbackPlaylist.Active = true

for _, trackImage := range nextSongFromPlayList.Track.Track.Album.Images {

nextTrack.Images = append(nextTrack.Images, TrackImage{
Expand All @@ -33,6 +35,8 @@ func getNextSong(excludeUri ...spotify.URI) (Track, error) {
URL: trackImage.URL,
})
}
} else {
fallbackPlaylist.Active = false
}
return nextTrack, nil
}
Expand Down
64 changes: 47 additions & 17 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@ import (
)

var (
currentDevice spotify.PlayerDevice
fallbackPlaylist FallbackPlaylist
db *gorm.DB
auth *spotifyauth.Authenticator
minimumVotes int64
currentTrackURI spotify.URI
client *spotify.Client
oauthToken LoginToken
logger zerolog.Logger
rateLimit uint64
adminPassword string
currentDevice spotify.PlayerDevice
fallbackPlaylist FallbackPlaylist
db *gorm.DB
auth *spotifyauth.Authenticator
currentTrackURI spotify.URI
client *spotify.Client
oauthToken LoginToken
logger zerolog.Logger
adminPassword string
voteStandardRateLimit uint64
voteStandardMinimumVotes int64
voteToSkipDefault int64
voteToSkipCurrent int64
voteToSkipEnabled bool
)

var (
Expand Down Expand Up @@ -110,7 +113,16 @@ func init() {
}

// Set Minimum Votes
minimumVotes, _ = strconv.ParseInt(os.Getenv("MINIMUM_VOTES_TO_REMOVE"), 10, 64)
voteStandardMinimumVotes, _ = strconv.ParseInt(os.Getenv("VOTE_STANDARD_MINIMUM_TO_REMOVE"), 10, 64)

voteToSkipDefault, _ = strconv.ParseInt(os.Getenv("VOTES_TO_SKIP"), 10, 64)
voteToSkipCurrent = voteToSkipDefault

// Set Vote Method
voteToSkipEnabled = false
if os.Getenv("VOTE_METHOD") == "skip" {
voteToSkipEnabled = true
}

// Set Fallback Playlist
addToPlaylist, _ := strconv.ParseBool(os.Getenv("FALLBACK_PLAYLIST_ADD_QUEUED"))
Expand All @@ -122,7 +134,7 @@ func init() {
}

// Set Rate Limiting
rateLimit, _ = strconv.ParseUint(os.Getenv("MAXIMUM_VOTES_PER_HOUR"), 10, 32)
voteStandardRateLimit, _ = strconv.ParseUint(os.Getenv("VOTE_STANDARD_MAXIMUM_PER_HOUR"), 10, 32)

// Admin Password
adminPassword = os.Getenv("ADMIN_PASSWORD")
Expand All @@ -136,12 +148,26 @@ func main() {
// Start Listeners and Polling
logger.Info().Msg("Starting GIN Web Server")
// Set Rate Limiting
store := ratelimit.InMemoryStore(&ratelimit.InMemoryOptions{
rateLimitVoteStandardMiddleWareStore := ratelimit.InMemoryStore(&ratelimit.InMemoryOptions{
Rate: time.Hour,
Limit: uint(rateLimit),
Limit: uint(voteStandardRateLimit),
})

rateLimitMiddleWare := ratelimit.RateLimiter(store, &ratelimit.Options{
rateLimitVoteStandardMiddleWare := ratelimit.RateLimiter(rateLimitVoteStandardMiddleWareStore, &ratelimit.Options{
ErrorHandler: func(c *gin.Context, info ratelimit.Info) {
c.JSON(429, "Too many requests. Try again in "+time.Until(info.ResetTime).String())
},
KeyFunc: func(c *gin.Context) string {
return c.ClientIP() + c.Request.UserAgent()
},
})

rateLimitVoteSkipMiddleWareStore := ratelimit.InMemoryStore(&ratelimit.InMemoryOptions{
Rate: time.Minute * 4,
Limit: 1,
})

rateLimitVoteSkipMiddleWare := ratelimit.RateLimiter(rateLimitVoteSkipMiddleWareStore, &ratelimit.Options{
ErrorHandler: func(c *gin.Context, info ratelimit.Info) {
c.JSON(429, "Too many requests. Try again in "+time.Until(info.ResetTime).String())
},
Expand All @@ -164,7 +190,11 @@ func main() {
// Set Routes
r.GET("/search/:searchTerm", handleSearch)

r.POST("/votes/:action", rateLimitMiddleWare, handleVote)
if voteToSkipEnabled {
r.POST("/votes/skip", rateLimitVoteSkipMiddleWare, handleVoteSkip)
} else {
r.POST("/votes/:action", rateLimitVoteStandardMiddleWare, handleVote)
}

r.GET("/auth/callback", handleAuth)
r.GET("/auth/login", serveLoginLink)
Expand Down
9 changes: 6 additions & 3 deletions api/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ func pollSpotify() {
logger.Err(err).Msg("SOMETHING WENT WRONG GETTING PLAYER")
}
if playerState.Playing {
fallbackPlaylist.Active = true
fallbackPlaylist.Active = false
currentTrackURI = playerState.Item.URI
if err := db.First(&track, Track{URI: currentTrackURI}).Error; err != nil {
// Assume we cant find the track so must be from fallback playlist
fallbackPlaylist.Active = true
}
logger.Info().Msg("CONTINUING SONG: " + playerState.Item.Name + " - " + playerState.Item.Artists[0].Name)
} else {
track, _ := getNextSong()
Expand Down Expand Up @@ -178,12 +182,11 @@ func pollSpotify() {
dbDevice.Active = currentDevice.Active
dbDevice.Volume = currentDevice.Volume
db.Save(&dbDevice)
// logger.Info().Msg("DEVICE SET")
// logger.Info().Msg(dbDevice.Name)
}

if playerState.Progress == 0 {
logger.Info().Msg("LOADING NEXT SONG")
voteToSkipCurrent = voteToSkipDefault
// Remove the track
if !fallbackPlaylist.Active {
if fallbackPlaylist.AddToPlaylist {
Expand Down
42 changes: 41 additions & 1 deletion api/votes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,46 @@ import (
"github.com/zmb3/spotify/v2"
)

func handleVoteSkip(c *gin.Context) {
var playerState *spotify.PlayerState
var track Track

ctx := c.Request.Context()
playerState, _ = client.PlayerState(ctx)

if !currentDevice.Active || !playerState.Playing {
c.JSON(http.StatusInternalServerError, "Spotify not Active")
return
}

voteToSkipCurrent = voteToSkipCurrent - 1
if voteToSkipCurrent == 0 {
if !fallbackPlaylist.Active {
if err := db.First(&track, Track{URI: currentTrackURI}).Error; err != nil {
c.JSON(http.StatusInternalServerError, "Track Not Found")
return
}
if err := db.Unscoped().Delete(&Track{}, Track{URI: currentTrackURI}).Error; err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
}

voteToSkipCurrent = voteToSkipDefault
newTrack, _ := getNextSong()
playerOpt := spotify.PlayOptions{
DeviceID: &currentDevice.ID,
URIs: []spotify.URI{newTrack.URI},
}
err := client.PlayOpt(ctx, &playerOpt)
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
}
c.JSON(http.StatusOK, voteToSkipCurrent)
}

func handleVote(c *gin.Context) {
var handleVoteInput HandleVoteInput
var playerState *spotify.PlayerState
Expand Down Expand Up @@ -38,7 +78,7 @@ func handleVote(c *gin.Context) {
case "remove":
playerState, _ = client.PlayerState(ctx)
track.Votes = track.Votes - 1
if track.Votes <= minimumVotes {
if track.Votes <= voteStandardMinimumVotes {
if err := db.Unscoped().Delete(&Track{}, Track{URI: handleVoteInput.URI}).Error; err != nil {
c.JSON(http.StatusInternalServerError, err)
return
Expand Down

0 comments on commit 2b6a926

Please sign in to comment.