Merge 284500e760c4041ba92d008bd5e5c7dbc770231a into 6b9f85efcc42aab1156ada175a18390f5cb0fbb2

This commit is contained in:
Voten641 2026-07-12 20:10:06 -04:00 committed by GitHub
commit 660f41ebe6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 90 additions and 0 deletions

View File

@ -8,6 +8,12 @@ type Scrobble struct {
SubmissionTime time.Time
}
type HistoryEntry struct {
MediaFile
PlayedAt time.Time `json:"playedAt"`
}
type ScrobbleRepository interface {
RecordScrobble(mediaFileID string, submissionTime time.Time) error
GetHistory(offset, count int) ([]HistoryEntry, error)
}

View File

@ -32,3 +32,48 @@ func (r *scrobbleRepository) RecordScrobble(mediaFileID string, submissionTime t
_, err := r.executeSQL(insert)
return err
}
type dbHistoryEntry struct {
dbMediaFile
SubmissionTime int64 `structs:"-"`
}
func (h *dbHistoryEntry) PostScan() error {
return h.dbMediaFile.PostScan()
}
func (r *scrobbleRepository) GetHistory(offset, count int) ([]model.HistoryEntry, error) {
if offset < 0 {
offset = 0
}
if count <= 0 {
count = 50
}
userID := loggedUser(r.ctx).ID
sq := Select("m.*", "s.submission_time").
From(r.tableName+" s").
LeftJoin("media_file m ON m.id = s.media_file_id").
Where(Eq{"s.user_id": userID}).
OrderBy("s.submission_time DESC").
Offset(uint64(offset)).
Limit(uint64(count))
var rows []dbHistoryEntry
if err := r.queryAll(sq, &rows); err != nil {
return nil, err
}
entries := make([]model.HistoryEntry, 0, len(rows))
for i := range rows {
if rows[i].MediaFile == nil {
continue
}
entries = append(entries, model.HistoryEntry{
MediaFile: *rows[i].MediaFile,
PlayedAt: time.Unix(rows[i].SubmissionTime, 0),
})
}
return entries, nil
}
var _ model.ScrobbleRepository = (*scrobbleRepository)(nil)

View File

@ -228,6 +228,30 @@ func (api *Router) GetNowPlaying(r *http.Request) (*responses.Subsonic, error) {
return response, nil
}
func (api *Router) GetSongHistory(r *http.Request) (*responses.Subsonic, error) {
p := req.Params(r)
count := min(p.IntOr("count", 50), 500)
offset := p.IntOr("offset", 0)
ctx := r.Context()
entries, err := api.ds.Scrobble(ctx).GetHistory(offset, count)
if err != nil {
log.Error(r, "Error retrieving song history", err)
return nil, err
}
response := newResponse()
response.SongHistory = &responses.SongHistory{
Song: slice.Map(entries, func(e model.HistoryEntry) responses.SongHistoryEntry {
return responses.SongHistoryEntry{
Child: childFromMediaFile(ctx, e.MediaFile),
PlayedAt: e.PlayedAt.Unix(),
}
}),
}
return response, nil
}
func (api *Router) GetRandomSongs(r *http.Request) (*responses.Subsonic, error) {
p := req.Params(r)
size := min(p.IntOr("size", 10), 500)

View File

@ -136,6 +136,7 @@ func (api *Router) routes() http.Handler {
h(r, "getStarred", api.GetStarred)
h(r, "getStarred2", api.GetStarred2)
h(r, "getNowPlaying", api.GetNowPlaying)
h(r, "getSongHistory", api.GetSongHistory)
h(r, "getRandomSongs", api.GetRandomSongs)
h(r, "getSongsByGenre", api.GetSongsByGenre)
})

View File

@ -63,6 +63,7 @@ type Subsonic struct {
PlayQueueByIndex *PlayQueueByIndex `xml:"playQueueByIndex,omitempty" json:"playQueueByIndex,omitempty"`
TranscodeDecision *TranscodeDecision `xml:"transcodeDecision,omitempty" json:"transcodeDecision,omitempty"`
SonicMatches *Array[SonicMatch] `xml:"sonicMatch,omitempty" json:"sonicMatch,omitempty"`
SongHistory *SongHistory `xml:"songHistory,omitempty" json:"songHistory,omitempty"`
}
const (
@ -374,6 +375,15 @@ type NowPlaying struct {
Entry []NowPlayingEntry `xml:"entry" json:"entry,omitempty"`
}
type SongHistoryEntry struct {
Child
PlayedAt int64 `xml:"playedAt,attr" json:"playedAt"`
}
type SongHistory struct {
Song []SongHistoryEntry `xml:"song,omitempty" json:"song,omitempty"`
}
type User struct {
Username string `xml:"username,attr" json:"username"`
Email string `xml:"email,attr,omitempty" json:"email,omitempty"`

View File

@ -22,3 +22,7 @@ func (m *MockScrobbleRepo) RecordScrobble(fileID string, submissionTime time.Tim
})
return nil
}
func (m *MockScrobbleRepo) GetHistory(offset, count int) ([]model.HistoryEntry, error) {
return nil, nil
}