package sc import ( "net/url" "strconv" "strings" "sync" "time" "github.com/maid-zone/soundcloak/lib/cfg" ) var PlaylistsCache = map[string]cached[Playlist]{} var playlistsCacheLock = &sync.RWMutex{} // Functions/structures related to playlists type Playlist struct { Artwork string `json:"artwork_url"` CreatedAt string `json:"created_at"` Description string `json:"description"` Kind string `json:"kind"` // should always be "playlist"! LastModified string `json:"last_modified"` Likes int64 `json:"likes_count"` Permalink string `json:"permalink"` //ReleaseDate string `json:"release_date"` TagList string `json:"tag_list"` Title string `json:"title"` Type string `json:"set_type"` Album bool `json:"is_album"` Author User `json:"user"` Tracks []Track `json:"tracks"` TrackCount int64 `json:"track_count"` MissingTracks string `json:"-"` } func GetPlaylist(cid string, permalink string) (Playlist, error) { playlistsCacheLock.RLock() if cell, ok := PlaylistsCache[permalink]; ok && cell.Expires.After(time.Now()) { playlistsCacheLock.RUnlock() return cell.Value, nil } playlistsCacheLock.RUnlock() var p Playlist var err error if cid == "" { cid, err = GetClientID() if err != nil { return p, err } } err = Resolve(cid, permalink, &p) if err != nil { return p, err } if p.Kind != "playlist" { return p, ErrKindNotCorrect } err = p.Fix(cid, true, true) if err != nil { return p, err } playlistsCacheLock.Lock() PlaylistsCache[permalink] = cached[Playlist]{Value: p, Expires: time.Now().Add(cfg.PlaylistTTL)} playlistsCacheLock.Unlock() return p, nil } func SearchPlaylists(cid string, prefs cfg.Preferences, args string) (*Paginated[*Playlist], error) { p := Paginated[*Playlist]{Next: "https://" + api + "/search/playlists" + args} err := p.Proceed(cid, true) if err != nil { return nil, err } for _, p := range p.Collection { p.Fix("", false, false) p.Postfix(prefs, false, false) } return &p, nil } func (p *Playlist) Fix(cid string, cached bool, fixAuthor bool) error { if cached { err := p.GetMissingTracks(cid) if err != nil { return err } for i, t := range p.Tracks { t.Fix(false, false) p.Tracks[i] = t } p.Artwork = strings.Replace(p.Artwork, "-large.", "-t500x500.", 1) } else { p.Artwork = strings.Replace(p.Artwork, "-large.", "-t200x200.", 1) } if fixAuthor { p.Author.Fix(false) } return nil } func (p *Playlist) Postfix(prefs cfg.Preferences, fixTracks bool, fixAuthor bool) []Track { if cfg.ProxyImages && *prefs.ProxyImages && p.Artwork != "" { p.Artwork = "/_/proxy/images?url=" + url.QueryEscape(p.Artwork) } if fixAuthor { p.Author.Postfix(prefs) } if fixTracks { var fixed = make([]Track, len(p.Tracks)) for i, t := range p.Tracks { t.Postfix(prefs, false) fixed[i] = t } return fixed } return nil } func (p Playlist) FormatDescription() string { desc := p.Description if p.Description != "" { desc += "\n\n" } desc += strconv.FormatInt(int64(len(p.Tracks)), 10) + " tracks" desc += "\n" + strconv.FormatInt(p.Likes, 10) + " ❤️" desc += "\nCreated: " + p.CreatedAt desc += "\nLast modified: " + p.LastModified if len(p.TagList) != 0 { desc += "\nTags: " + strings.Join(TagListParser(p.TagList), ", ") } return desc } type MissingTrack struct { ID string Index int } func JoinMissingTracks(missing []MissingTrack) (st string) { for i, track := range missing { st += track.ID if i != len(missing)-1 { st += "," } } return } func GetMissingTracks(cid string, missing []MissingTrack) (res []Track, next []MissingTrack, err error) { if len(missing) > 50 { next = missing[50:] missing = missing[:50] } res, err = GetTracks(cid, JoinMissingTracks(missing)) return } func GetNextMissingTracks(cid string, raw string) (res []Track, next []string, err error) { missing := strings.Split(raw, ",") if len(missing) > 50 { next = missing[50:] missing = missing[:50] } res, err = GetTracks(cid, strings.Join(missing, ",")) return } func (p *Playlist) GetMissingTracks(cid string) error { missing := []MissingTrack{} for i, track := range p.Tracks { if track.Title == "" { missing = append(missing, MissingTrack{ID: string(track.ID), Index: i}) } } if len(missing) == 0 { return nil } res, next, err := GetMissingTracks(cid, missing) if err != nil { return err } for _, oldTrack := range missing { for _, newTrack := range res { if string(newTrack.ID) == string(oldTrack.ID) { p.Tracks[oldTrack.Index] = newTrack } } } p.MissingTracks = JoinMissingTracks(next) return nil } func (p Playlist) Href() string { return "/" + p.Author.Permalink + "/sets/" + p.Permalink }