mirror of
https://git.maid.zone/stuff/soundcloak.git
synced 2025-12-10 13:49:39 +05:00
view what albums/playlists track is in, related tracks
This commit is contained in:
@@ -481,3 +481,57 @@ func RecentTracks(cid string, prefs cfg.Preferences, args string) (*Paginated[*T
|
|||||||
|
|
||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Track) GetRelated(cid string, prefs cfg.Preferences, args string) (*Paginated[*Track], error) {
|
||||||
|
p := Paginated[*Track]{
|
||||||
|
Next: "https://" + api + "/tracks/" + string(t.ID) + "/related" + args,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.Proceed(cid, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range p.Collection {
|
||||||
|
t.Fix(false, false)
|
||||||
|
t.Postfix(prefs, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Track) GetPlaylists(cid string, prefs cfg.Preferences, args string) (*Paginated[*Playlist], error) {
|
||||||
|
p := Paginated[*Playlist]{
|
||||||
|
Next: "https://" + api + "/tracks/" + string(t.ID) + "/playlists_without_albums" + 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 (t *Track) GetAlbums(cid string, prefs cfg.Preferences, args string) (*Paginated[*Playlist], error) {
|
||||||
|
p := Paginated[*Playlist]{
|
||||||
|
Next: "https://" + api + "/tracks/" + string(t.ID) + "/albums" + 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
|
||||||
|
}
|
||||||
|
|||||||
92
main.go
92
main.go
@@ -325,6 +325,11 @@ func main() {
|
|||||||
|
|
||||||
preferences.Load(app)
|
preferences.Load(app)
|
||||||
|
|
||||||
|
// Currently, /:user is the tracks page
|
||||||
|
app.Get("/:user/tracks", func(c *fiber.Ctx) error {
|
||||||
|
return c.Redirect("/" + c.Params("user"))
|
||||||
|
})
|
||||||
|
|
||||||
app.Get("/:user/sets", func(c *fiber.Ctx) error {
|
app.Get("/:user/sets", func(c *fiber.Ctx) error {
|
||||||
prefs, err := preferences.Get(c)
|
prefs, err := preferences.Get(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -612,7 +617,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Set("Content-Type", "text/html")
|
c.Set("Content-Type", "text/html")
|
||||||
return templates.Base(track.Title+" by "+track.Author.Username, templates.Track(prefs, track, stream, displayErr, c.Query("autoplay") == "true", playlist, nextTrack, c.Query("volume"), mode, audio), templates.TrackHeader(prefs, track)).Render(context.Background(), c)
|
return templates.Base(track.Title+" by "+track.Author.Username, templates.Track(prefs, track, stream, displayErr, c.Query("autoplay") == "true", playlist, nextTrack, c.Query("volume"), mode, audio), templates.TrackHeader(prefs, track, true)).Render(context.Background(), c)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/:user", func(c *fiber.Ctx) error {
|
app.Get("/:user", func(c *fiber.Ctx) error {
|
||||||
@@ -711,5 +716,90 @@ func main() {
|
|||||||
return templates.Base(user.Username, templates.UserRelated(prefs, user, r), templates.UserHeader(user)).Render(context.Background(), c)
|
return templates.Base(user.Username, templates.UserRelated(prefs, user, r), templates.UserHeader(user)).Render(context.Background(), c)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// I'd like to make this "related" but keeping it "recommended" to have the same url as soundcloud
|
||||||
|
app.Get("/:user/:track/recommended", func(c *fiber.Ctx) error {
|
||||||
|
prefs, err := preferences.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cid, err := sc.GetClientID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
track, err := sc.GetTrack(cid, c.Params("user")+"/"+c.Params("track"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error getting %s from %s (related): %s\n", c.Params("track"), c.Params("user"), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
track.Postfix(prefs, true)
|
||||||
|
|
||||||
|
r, err := track.GetRelated(cid, prefs, c.Query("pagination", "?limit=20"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error getting %s from %s related tracks: %s\n", c.Params("track"), c.Params("user"), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", "text/html")
|
||||||
|
return templates.Base(track.Title+" by "+track.Author.Username, templates.RelatedTracks(track, r), templates.TrackHeader(prefs, track, false)).Render(context.Background(), c)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/:user/:track/sets", func(c *fiber.Ctx) error {
|
||||||
|
prefs, err := preferences.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cid, err := sc.GetClientID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
track, err := sc.GetTrack(cid, c.Params("user")+"/"+c.Params("track"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error getting %s from %s (sets): %s\n", c.Params("track"), c.Params("user"), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
track.Postfix(prefs, true)
|
||||||
|
|
||||||
|
p, err := track.GetPlaylists(cid, prefs, c.Query("pagination", "?limit=20"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error getting %s from %s sets: %s\n", c.Params("track"), c.Params("user"), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", "text/html")
|
||||||
|
return templates.Base(track.Title+" by "+track.Author.Username, templates.TrackInPlaylists(track, p), templates.TrackHeader(prefs, track, false)).Render(context.Background(), c)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/:user/:track/albums", func(c *fiber.Ctx) error {
|
||||||
|
prefs, err := preferences.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cid, err := sc.GetClientID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
track, err := sc.GetTrack(cid, c.Params("user")+"/"+c.Params("track"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error getting %s from %s (albums): %s\n", c.Params("track"), c.Params("user"), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
track.Postfix(prefs, true)
|
||||||
|
|
||||||
|
p, err := track.GetAlbums(cid, prefs, c.Query("pagination", "?limit=20"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error getting %s from %s albums: %s\n", c.Params("track"), c.Params("user"), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", "text/html")
|
||||||
|
return templates.Base(track.Title+" by "+track.Author.Username, templates.TrackInAlbums(track, p), templates.TrackHeader(prefs, track, false)).Render(context.Background(), c)
|
||||||
|
})
|
||||||
|
|
||||||
log.Fatal(app.Listen(cfg.Addr))
|
log.Fatal(app.Listen(cfg.Addr))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,40 @@ import (
|
|||||||
|
|
||||||
func toExt(audio string) string {
|
func toExt(audio string) string {
|
||||||
switch audio {
|
switch audio {
|
||||||
case cfg.AudioAAC:
|
case cfg.AudioAAC:
|
||||||
return "m4a"
|
return "m4a"
|
||||||
case cfg.AudioOpus:
|
case cfg.AudioOpus:
|
||||||
return "ogg"
|
return "ogg"
|
||||||
case cfg.AudioMP3:
|
case cfg.AudioMP3:
|
||||||
return "mp3"
|
return "mp3"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
templ TrackHeader(prefs cfg.Preferences, t sc.Track) {
|
templ TrackButtons(current string, track sc.Track) {
|
||||||
|
<div class="btns">
|
||||||
|
for _, b := range [...]btn{{"related tracks", "/recommended", false},{"in albums", "/albums", false},{"in playlists", "/sets", false},{"view on soundcloud", "https://soundcloud.com"+track.Href(), true}} {
|
||||||
|
if b.text == current {
|
||||||
|
<a class="btn active">{ b.text }</a>
|
||||||
|
} else {
|
||||||
|
if b.external {
|
||||||
|
<a class="btn" href={ templ.URL(b.href) } referrerpolicy="no-referrer" rel="external nofollow noopener noreferrer" target="_blank">{ b.text }</a>
|
||||||
|
} else {
|
||||||
|
<a class="btn" href={ templ.URL(track.Href() + b.href) }>{ b.text }</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ TrackHeader(prefs cfg.Preferences, t sc.Track, needPlayer bool) {
|
||||||
<meta name="og:site_name" content={ t.Author.Username + " ~ soundcloak" }/>
|
<meta name="og:site_name" content={ t.Author.Username + " ~ soundcloak" }/>
|
||||||
<meta name="og:title" content={ t.Title }/>
|
<meta name="og:title" content={ t.Title }/>
|
||||||
<meta name="og:description" content={ t.FormatDescription() }/>
|
<meta name="og:description" content={ t.FormatDescription() }/>
|
||||||
<meta name="og:image" content={ t.Artwork }/>
|
<meta name="og:image" content={ t.Artwork }/>
|
||||||
<link rel="icon" type="image/x-icon" href={ t.Artwork }/>
|
<link rel="icon" type="image/x-icon" href={ t.Artwork }/>
|
||||||
if *prefs.Player == cfg.HLSPlayer {
|
if needPlayer && *prefs.Player == cfg.HLSPlayer {
|
||||||
<script src="/js/hls.js/hls.light.min.js"></script>
|
<script src="/js/hls.js/hls.light.min.js"></script>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +71,7 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
|
|||||||
if cfg.Restream && *prefs.Player == cfg.RestreamPlayer {
|
if cfg.Restream && *prefs.Player == cfg.RestreamPlayer {
|
||||||
{{ audioPref = *prefs.RestreamAudio }}
|
{{ audioPref = *prefs.RestreamAudio }}
|
||||||
if nextTrack != nil {
|
if nextTrack != nil {
|
||||||
<audio id="track" src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay } data-next={ next(nextTrack, playlist, true, mode, "") } volume={volume}></audio>
|
<audio id="track" src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay } data-next={ next(nextTrack, playlist, true, mode, "") } volume={ volume }></audio>
|
||||||
<script async src="/restream.js"></script>
|
<script async src="/restream.js"></script>
|
||||||
} else {
|
} else {
|
||||||
<audio src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay }></audio>
|
<audio src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay }></audio>
|
||||||
@@ -86,7 +102,6 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
|
|||||||
<p>Only a 30-second snippet is available.</p>
|
<p>Only a 30-second snippet is available.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if *prefs.ShowAudio {
|
if *prefs.ShowAudio {
|
||||||
<div>
|
<div>
|
||||||
if audioPref == cfg.AudioBest {
|
if audioPref == cfg.AudioBest {
|
||||||
@@ -132,8 +147,13 @@ templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string,
|
|||||||
}
|
}
|
||||||
<h1>{ t.Title }</h1>
|
<h1>{ t.Title }</h1>
|
||||||
@TrackPlayer(prefs, t, stream, displayErr, autoplay, nextTrack, playlist, volume, mode, audio)
|
@TrackPlayer(prefs, t, stream, displayErr, autoplay, nextTrack, playlist, volume, mode, audio)
|
||||||
|
if cfg.Restream {
|
||||||
|
<div style="display: flex; margin-bottom: 1rem;">
|
||||||
|
<a class="btn" href={ templ.URL("/_/restream" + t.Href() + "?metadata=true") } download={ t.Permalink + "." + toExt(audio) }>download</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
if t.Genre != "" {
|
if t.Genre != "" {
|
||||||
<a href={templ.URL("/tags/"+t.Genre)}><p class="tag">{ t.Genre }</p></a>
|
<a href={ templ.URL("/tags/" + t.Genre) }><p class="tag">{ t.Genre }</p></a>
|
||||||
}
|
}
|
||||||
if playlist != nil {
|
if playlist != nil {
|
||||||
<details open style="margin-bottom: 1rem;">
|
<details open style="margin-bottom: 1rem;">
|
||||||
@@ -143,22 +163,23 @@ templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string,
|
|||||||
<h2>Next track:</h2>
|
<h2>Next track:</h2>
|
||||||
@TrackItem(nextTrack, true, next(nextTrack, playlist, true, mode, volume))
|
@TrackItem(nextTrack, true, next(nextTrack, playlist, true, mode, volume))
|
||||||
<div style="display: flex; gap: 1rem">
|
<div style="display: flex; gap: 1rem">
|
||||||
<a href={ templ.URL(t.Href()) } class="btn">Stop playlist playback</a>
|
<a href={ templ.URL(t.Href()) } class="btn">Stop playlist playback</a>
|
||||||
if mode != cfg.AutoplayRandom {
|
if mode != cfg.AutoplayRandom {
|
||||||
<a href={ templ.URL(next(&t, playlist, false, cfg.AutoplayRandom, volume)) } class="btn">Switch to random mode</a>
|
<a href={ templ.URL(next(&t, playlist, false, cfg.AutoplayRandom, volume)) } class="btn">Switch to random mode</a>
|
||||||
} else {
|
} else {
|
||||||
<a href={ templ.URL(next(&t, playlist, false, cfg.AutoplayNormal, volume)) } class="btn">Switch to normal mode</a>
|
<a href={ templ.URL(next(&t, playlist, false, cfg.AutoplayNormal, volume)) } class="btn">Switch to normal mode</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
}
|
}
|
||||||
@UserItem(&t.Author)
|
@UserItem(&t.Author)
|
||||||
<div style="display: flex; gap: 1rem">
|
@TrackButtons("", t)
|
||||||
<a class="btn" href={ templ.URL("https://soundcloud.com" + t.Href()) }>view on soundcloud</a>
|
// <div style="display: flex; gap: 1rem">
|
||||||
if cfg.Restream {
|
// <a class="btn" href={ templ.URL("https://soundcloud.com" + t.Href()) }>view on soundcloud</a>
|
||||||
<a class="btn" href={ templ.URL("/_/restream" + t.Href() + "?metadata=true") } download={t.Permalink + "." + toExt(audio)}>download</a>
|
// if cfg.Restream {
|
||||||
}
|
// <a class="btn" href={ templ.URL("/_/restream" + t.Href() + "?metadata=true") } download={t.Permalink + "." + toExt(audio)}>download</a>
|
||||||
</div>
|
// }
|
||||||
|
// </div>
|
||||||
<br/>
|
<br/>
|
||||||
@Description(prefs, t.Description, nil)
|
@Description(prefs, t.Description, nil)
|
||||||
<p>{ strconv.FormatInt(t.Likes, 10) } likes</p>
|
<p>{ strconv.FormatInt(t.Likes, 10) } likes</p>
|
||||||
@@ -212,3 +233,60 @@ templ SearchTracks(p *sc.Paginated[*sc.Track]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templ RelatedTracks(t sc.Track, p *sc.Paginated[*sc.Track]) {
|
||||||
|
if t.Artwork != "" {
|
||||||
|
<img src={ t.Artwork } width="300px"/>
|
||||||
|
}
|
||||||
|
<h1><a href={templ.URL(t.Href())}>{ t.Title }</a></h1>
|
||||||
|
@TrackButtons("related tracks", t)
|
||||||
|
<br/>
|
||||||
|
if len(p.Collection) == 0 {
|
||||||
|
<p>no more results</p>
|
||||||
|
} else {
|
||||||
|
for _, track := range p.Collection {
|
||||||
|
@TrackItem(track, true, "")
|
||||||
|
}
|
||||||
|
if p.Next != "" {
|
||||||
|
<a class="btn" href={ templ.URL("?pagination=" + url.QueryEscape(strings.Split(p.Next, "/related")[1])) } rel="noreferrer">more tracks</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ TrackInAlbums(t sc.Track, p *sc.Paginated[*sc.Playlist]) {
|
||||||
|
if t.Artwork != "" {
|
||||||
|
<img src={ t.Artwork } width="300px"/>
|
||||||
|
}
|
||||||
|
<h1><a href={templ.URL(t.Href())}>{ t.Title }</a></h1>
|
||||||
|
@TrackButtons("in albums", t)
|
||||||
|
<br/>
|
||||||
|
if len(p.Collection) == 0 {
|
||||||
|
<p>no more albums</p>
|
||||||
|
} else {
|
||||||
|
for _, playlist := range p.Collection {
|
||||||
|
@PlaylistItem(playlist, true)
|
||||||
|
}
|
||||||
|
if p.Next != "" {
|
||||||
|
<a class="btn" href={ templ.URL("?pagination=" + url.QueryEscape(strings.Split(p.Next, "/albums")[1])) } rel="noreferrer">more albums</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ TrackInPlaylists(t sc.Track, p *sc.Paginated[*sc.Playlist]) {
|
||||||
|
if t.Artwork != "" {
|
||||||
|
<img src={ t.Artwork } width="300px"/>
|
||||||
|
}
|
||||||
|
<h1><a href={templ.URL(t.Href())}>{ t.Title }</a></h1>
|
||||||
|
@TrackButtons("in playlists", t)
|
||||||
|
<br/>
|
||||||
|
if len(p.Collection) == 0 {
|
||||||
|
<p>no more playlists</p>
|
||||||
|
} else {
|
||||||
|
for _, playlist := range p.Collection {
|
||||||
|
@PlaylistItem(playlist, true)
|
||||||
|
}
|
||||||
|
if p.Next != "" {
|
||||||
|
<a class="btn" href={ templ.URL("?pagination=" + url.QueryEscape(strings.Split(p.Next, "/playlists_without_albums")[1])) } rel="noreferrer">more playlists</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ type btn struct {
|
|||||||
templ UserButtons(current string, user string) {
|
templ UserButtons(current string, user string) {
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
// this part is the tedious one now, because formatting breaks if i space the list out with newlines
|
// this part is the tedious one now, because formatting breaks if i space the list out with newlines
|
||||||
for _, b := range [7]btn{{"tracks", "", false},{"popular tracks", "/popular-tracks", false},{"playlists", "/sets",false},{"albums", "/albums", false},{"reposts","/reposts", false},{"related", "/_/related", false},{"view on soundcloud", "https://soundcloud.com/"+user, true}} {
|
for _, b := range [...]btn{{"tracks", "", false},{"popular tracks", "/popular-tracks", false},{"playlists", "/sets",false},{"albums", "/albums", false},{"reposts","/reposts", false},{"related", "/_/related", false},{"view on soundcloud", "https://soundcloud.com/"+user, true}} {
|
||||||
if b.text == current {
|
if b.text == current {
|
||||||
<a class="btn active">{ b.text }</a>
|
<a class="btn active">{ b.text }</a>
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user