diff --git a/ircbot/commands_impl.go b/ircbot/commands_impl.go index 7ea64533..f6eaa3cd 100644 --- a/ircbot/commands_impl.go +++ b/ircbot/commands_impl.go @@ -468,7 +468,8 @@ func RandomTrackRequest(e Event) error { nickname = nick } - songs, err = e.Storage.Song(e.Ctx).FavoritesOf(nickname) + // TODO(wessie): this limit is artificial, but no one should hit 100k faves + songs, err = e.Storage.Song(e.Ctx).FavoritesOf(nickname, 100000, 0) if err != nil { return errors.E(op, err) } diff --git a/radio.go b/radio.go index 18cb2045..d1f93ee7 100644 --- a/radio.go +++ b/radio.go @@ -652,7 +652,7 @@ type SongStorage interface { // Favorites returns all users that have this song on their favorite list Favorites(Song) ([]string, error) // FavoritesOf returns all songs that are on a users favorite list - FavoritesOf(nick string) ([]Song, error) + FavoritesOf(nick string, limit, offset int64) ([]Song, error) // AddFavorite adds the given song to nicks favorite list AddFavorite(song Song, nick string) (bool, error) // RemoveFavorite removes the given song from nicks favorite list diff --git a/storage/mariadb/track.go b/storage/mariadb/track.go index 02b45e2d..05e59b64 100644 --- a/storage/mariadb/track.go +++ b/storage/mariadb/track.go @@ -318,16 +318,18 @@ JOIN WHERE tracks.usable = 1 AND - enick.nick = ?; + enick.nick = ? +ORDER BY esong.meta ASC +LIMIT ? OFFSET ?; `) // FavoritesOf implements radio.SongStorage -func (ss SongStorage) FavoritesOf(nick string) ([]radio.Song, error) { +func (ss SongStorage) FavoritesOf(nick string, limit, offset int64) ([]radio.Song, error) { const op errors.Op = "mariadb/SongStorage.FavoritesOf" var songs = []radio.Song{} - err := sqlx.Select(ss.handle, &songs, songFavoritesOfQuery, nick) + err := sqlx.Select(ss.handle, &songs, songFavoritesOfQuery, nick, limit, offset) if err != nil { return nil, errors.E(op, err) } diff --git a/streamer/streamer.go b/streamer/streamer.go index 86e217a8..062e57a1 100644 --- a/streamer/streamer.go +++ b/streamer/streamer.go @@ -24,11 +24,6 @@ var ( bufferMP3Size = 1024 * 32 // about 1.3 seconds of audio bufferPCMSize = 1024 * 64 // about 0.4 seconds of audio ) -var ( - httpOK = []byte("HTTP/1.0 200 OK") - httpMountInUse = []byte("HTTP/1.0 403 Mountpoint in use") - httpUnauthorized = []byte("HTTP/1.0 401 Unauthorized") -) // Streamer represents a single icecast stream type Streamer struct { diff --git a/website/middleware/context.go b/website/middleware/context.go index cf4e0232..0ae60c6a 100644 --- a/website/middleware/context.go +++ b/website/middleware/context.go @@ -61,7 +61,7 @@ func UserByDJIDCtx(storage radio.UserStorageService) func(http.Handler) http.Han tmp1 := chi.URLParamFromCtx(ctx, "DJID") tmp2, err := strconv.Atoi(tmp1) if err != nil { - http.Error(w, http.StatusText(404), 404) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } id := radio.DJID(tmp2) diff --git a/website/public/faves.go b/website/public/faves.go index 1abbd082..db52a40f 100644 --- a/website/public/faves.go +++ b/website/public/faves.go @@ -1,20 +1,25 @@ package public import ( + "encoding/json" + "fmt" + "html" "net/http" radio "github.com/R-a-dio/valkyrie" "github.com/R-a-dio/valkyrie/website/middleware" "github.com/R-a-dio/valkyrie/website/shared" + "github.com/go-chi/chi/v5" ) const favesPageSize = 100 type FavesInput struct { middleware.Input - Nickname string - Faves []radio.Song - Page *shared.Pagination + Nickname string + DownloadURL string + Faves []radio.Song + Page *shared.Pagination } func (FavesInput) TemplateBundle() string { @@ -26,17 +31,29 @@ func NewFavesInput(ss radio.SongStorage, r *http.Request) (*FavesInput, error) { if err != nil { return nil, err } - _ = offset - nickname := r.FormValue("nick") - faves, err := ss.FavoritesOf(nickname) + // we support both ?nick= and /faves/ so we need to see which one we have + // try the old format first, and then the GET parameter + nickname := chi.URLParam(r, "Nick") + if nickname == "" { + nickname = r.FormValue("nick") + } + nickname = html.EscapeString(nickname) + + faves, err := ss.FavoritesOf(nickname, favesPageSize, offset) if err != nil { return nil, err } + q := r.URL.Query() + q.Set("dl", "true") + dlUrl := *r.URL + dlUrl.RawQuery = q.Encode() + return &FavesInput{ - Nickname: nickname, - Faves: faves, + Nickname: nickname, + DownloadURL: dlUrl.String(), + Faves: faves, Page: shared.NewPagination( page, shared.PageCount(int64(len(faves)), favesPageSize), r.URL, @@ -52,6 +69,18 @@ func (s State) GetFaves(w http.ResponseWriter, r *http.Request) { return } + // we have an old API that returns your faves as JSON if you use dl=true + // so we need to support that for old users + if r.FormValue("dl") != "" { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s_faves.json", input.Nickname)) + err := json.NewEncoder(w).Encode(NewFaveDownload(input.Faves)) + if err != nil { + s.errorHandler(w, r, err) + } + return + } + err = s.Templates.Execute(w, r, input) if err != nil { s.errorHandler(w, r, err) @@ -61,3 +90,41 @@ func (s State) GetFaves(w http.ResponseWriter, r *http.Request) { func (s State) PostFaves(w http.ResponseWriter, r *http.Request) { } + +type FaveDownloadEntry struct { + ID *radio.TrackID `json:"tracks_id"` + Metadata string `json:"meta"` + LastRequested *int64 `json:"lastrequested"` + LastPlayed *int64 `json:"lastplayed"` + RequestCount *int `json:"requestcount"` +} + +func NewFaveDownloadEntry(song radio.Song) FaveDownloadEntry { + var entry FaveDownloadEntry + + if song.HasTrack() { + if song.TrackID > 0 { + entry.ID = &song.TrackID + } + if !song.LastRequested.IsZero() { + tmp := song.LastRequested.Unix() + entry.LastRequested = &tmp + } + entry.RequestCount = &song.RequestCount + } + + if !song.LastPlayed.IsZero() { + tmp := song.LastPlayed.Unix() + entry.LastPlayed = &tmp + } + + return entry +} + +func NewFaveDownload(songs []radio.Song) []FaveDownloadEntry { + res := make([]FaveDownloadEntry, 0, len(songs)) + for _, song := range songs { + res = append(res, NewFaveDownloadEntry(song)) + } + return res +} diff --git a/website/public/state.go b/website/public/state.go index cae8d4e7..57086e7c 100644 --- a/website/public/state.go +++ b/website/public/state.go @@ -45,6 +45,7 @@ func Route(ctx context.Context, s State) func(chi.Router) { r.Post("/submit", s.PostSubmit) r.Get("/staff", s.GetStaff) r.Get("/faves", s.GetFaves) + r.Get("/faves/{Nick}", s.GetFaves) r.Post("/faves", s.PostFaves) r.Get("/irc", s.GetChat) }