preference to dynamically load comments and small fixes

This commit is contained in:
Laptop
2025-01-18 01:51:11 +02:00
parent bedb149cce
commit 71012be90e
11 changed files with 171 additions and 51 deletions

View File

@@ -134,6 +134,7 @@ func defaultPreferences() {
DefaultPreferences.ShowAudio = &False DefaultPreferences.ShowAudio = &False
DefaultPreferences.SearchSuggestions = &False DefaultPreferences.SearchSuggestions = &False
DefaultPreferences.DynamicLoadComments = &False
} }
func loadDefaultPreferences(loaded Preferences) { func loadDefaultPreferences(loaded Preferences) {
@@ -216,6 +217,12 @@ func loadDefaultPreferences(loaded Preferences) {
} else { } else {
DefaultPreferences.SearchSuggestions = &False DefaultPreferences.SearchSuggestions = &False
} }
if loaded.DynamicLoadComments != nil {
DefaultPreferences.DynamicLoadComments = loaded.DynamicLoadComments
} else {
DefaultPreferences.DynamicLoadComments = &False
}
} }
func boolean(in string) bool { func boolean(in string) bool {
@@ -240,7 +247,7 @@ func fromEnv() error {
env = os.Getenv("DEFAULT_PREFERENCES") env = os.Getenv("DEFAULT_PREFERENCES")
if env != "" { if env != "" {
var p Preferences var p Preferences
err := json.Unmarshal([]byte(env), &p) err := json.Unmarshal(S2b(env), &p)
if err != nil { if err != nil {
return wrappedError{err, "DEFAULT_PREFERENCES"} return wrappedError{err, "DEFAULT_PREFERENCES"}
} }
@@ -383,7 +390,7 @@ func fromEnv() error {
env = os.Getenv("TRUSTED_PROXIES") env = os.Getenv("TRUSTED_PROXIES")
if env != "" { if env != "" {
var p []string var p []string
err := json.Unmarshal([]byte(env), &p) err := json.Unmarshal(S2b(env), &p)
if err != nil { if err != nil {
return wrappedError{err, "TRUSTED_PROXIES"} return wrappedError{err, "TRUSTED_PROXIES"}
} }

View File

@@ -1,5 +1,7 @@
package cfg package cfg
import "unsafe"
// seems soundcloud has 4 of these (i1, i2, i3, i4) // seems soundcloud has 4 of these (i1, i2, i3, i4)
// they point to the same ip from my observations, and they all serve the same files // they point to the same ip from my observations, and they all serve the same files
const ImageCDN = "i1.sndcdn.com" const ImageCDN = "i1.sndcdn.com"
@@ -67,4 +69,14 @@ type Preferences struct {
ShowAudio *bool // display audio (aac, opus, mpeg etc) under track player ShowAudio *bool // display audio (aac, opus, mpeg etc) under track player
SearchSuggestions *bool // load search suggestions on main page SearchSuggestions *bool // load search suggestions on main page
DynamicLoadComments *bool // dynamic comments loader without leaving track page
}
func B2s(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
func S2b(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
} }

View File

@@ -59,13 +59,17 @@ func Defaults(dst *cfg.Preferences) {
if dst.SearchSuggestions == nil { if dst.SearchSuggestions == nil {
dst.SearchSuggestions = cfg.DefaultPreferences.SearchSuggestions dst.SearchSuggestions = cfg.DefaultPreferences.SearchSuggestions
} }
if dst.DynamicLoadComments == nil {
dst.DynamicLoadComments = cfg.DefaultPreferences.DynamicLoadComments
}
} }
func Get(c fiber.Ctx) (cfg.Preferences, error) { func Get(c fiber.Ctx) (cfg.Preferences, error) {
rawprefs := c.Cookies("prefs", "{}") rawprefs := c.Cookies("prefs", "{}")
var p cfg.Preferences var p cfg.Preferences
err := json.Unmarshal([]byte(rawprefs), &p) err := json.Unmarshal(cfg.S2b(rawprefs), &p)
if err != nil { if err != nil {
return p, err return p, err
} }
@@ -87,6 +91,7 @@ type PrefsForm struct {
DownloadAudio string DownloadAudio string
ShowAudio string ShowAudio string
SearchSuggestions string SearchSuggestions string
DynamicLoadComments string
} }
type Export struct { type Export struct {
@@ -178,6 +183,12 @@ func Load(r *fiber.App) {
old.SearchSuggestions = &cfg.False old.SearchSuggestions = &cfg.False
} }
if p.DynamicLoadComments == "on" {
old.DynamicLoadComments = &cfg.True
} else {
old.DynamicLoadComments = &cfg.False
}
old.Player = &p.Player old.Player = &p.Player
data, err := json.Marshal(old) data, err := json.Marshal(old)
@@ -187,7 +198,7 @@ func Load(r *fiber.App) {
c.Cookie(&fiber.Cookie{ c.Cookie(&fiber.Cookie{
Name: "prefs", Name: "prefs",
Value: string(data), Value: cfg.B2s(data),
Expires: time.Now().Add(400 * 24 * time.Hour), Expires: time.Now().Add(400 * 24 * time.Hour),
HTTPOnly: true, HTTPOnly: true,
SameSite: "strict", SameSite: "strict",
@@ -251,7 +262,7 @@ func Load(r *fiber.App) {
c.Cookie(&fiber.Cookie{ c.Cookie(&fiber.Cookie{
Name: "prefs", Name: "prefs",
Value: string(data), Value: cfg.B2s(data),
Expires: time.Now().Add(400 * 24 * time.Hour), Expires: time.Now().Add(400 * 24 * time.Hour),
HTTPOnly: true, HTTPOnly: true,
SameSite: "strict", SameSite: "strict",

View File

@@ -24,15 +24,15 @@ func Load(r *fiber.App) {
} }
r.Get("/_/proxy/images", func(c fiber.Ctx) error { r.Get("/_/proxy/images", func(c fiber.Ctx) error {
url := c.Query("url") url := c.RequestCtx().QueryArgs().Peek("url")
if url == "" { if len(url) == 0 {
return fiber.ErrBadRequest return fiber.ErrBadRequest
} }
parsed := fasthttp.AcquireURI() parsed := fasthttp.AcquireURI()
defer fasthttp.ReleaseURI(parsed) defer fasthttp.ReleaseURI(parsed)
err := parsed.Parse(nil, []byte(url)) err := parsed.Parse(nil, url)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -13,15 +13,15 @@ import (
func Load(r *fiber.App) { func Load(r *fiber.App) {
r.Get("/_/proxy/streams", func(c fiber.Ctx) error { r.Get("/_/proxy/streams", func(c fiber.Ctx) error {
ur := c.Query("url") ur := c.RequestCtx().QueryArgs().Peek("url")
if ur == "" { if len(ur) == 0 {
return fiber.ErrBadRequest return fiber.ErrBadRequest
} }
parsed := fasthttp.AcquireURI() parsed := fasthttp.AcquireURI()
defer fasthttp.ReleaseURI(parsed) defer fasthttp.ReleaseURI(parsed)
err := parsed.Parse(nil, []byte(ur)) err := parsed.Parse(nil, ur)
if err != nil { if err != nil {
return err return err
} }
@@ -52,15 +52,15 @@ func Load(r *fiber.App) {
}) })
r.Get("/_/proxy/streams/aac", func(c fiber.Ctx) error { r.Get("/_/proxy/streams/aac", func(c fiber.Ctx) error {
ur := c.Query("url") ur := c.RequestCtx().QueryArgs().Peek("url")
if ur == "" { if len(ur) == 0 {
return fiber.ErrBadRequest return fiber.ErrBadRequest
} }
parsed := fasthttp.AcquireURI() parsed := fasthttp.AcquireURI()
defer fasthttp.ReleaseURI(parsed) defer fasthttp.ReleaseURI(parsed)
err := parsed.Parse(nil, []byte(ur)) err := parsed.Parse(nil, ur)
if err != nil { if err != nil {
return err return err
} }
@@ -90,15 +90,15 @@ func Load(r *fiber.App) {
}) })
r.Get("/_/proxy/streams/playlist", func(c fiber.Ctx) error { r.Get("/_/proxy/streams/playlist", func(c fiber.Ctx) error {
ur := c.Query("url") ur := c.RequestCtx().QueryArgs().Peek("url")
if ur == "" { if len(ur) == 0 {
return fiber.ErrBadRequest return fiber.ErrBadRequest
} }
parsed := fasthttp.AcquireURI() parsed := fasthttp.AcquireURI()
defer fasthttp.ReleaseURI(parsed) defer fasthttp.ReleaseURI(parsed)
err := parsed.Parse(nil, []byte(ur)) err := parsed.Parse(nil, ur)
if err != nil { if err != nil {
return err return err
} }
@@ -127,13 +127,13 @@ func Load(r *fiber.App) {
data = resp.Body() data = resp.Body()
} }
var sp = bytes.Split(data, []byte("\n")) var sp = bytes.Split(data, []byte{'\n'})
for i, l := range sp { for i, l := range sp {
if len(l) == 0 || l[0] == '#' { if len(l) == 0 || l[0] == '#' {
continue continue
} }
l = []byte("/_/proxy/streams?url=" + url.QueryEscape(string(l))) l = []byte("/_/proxy/streams?url=" + url.QueryEscape(cfg.B2s(l)))
sp[i] = l sp[i] = l
} }
@@ -141,15 +141,15 @@ func Load(r *fiber.App) {
}) })
r.Get("/_/proxy/streams/playlist/aac", func(c fiber.Ctx) error { r.Get("/_/proxy/streams/playlist/aac", func(c fiber.Ctx) error {
ur := c.Query("url") ur := c.RequestCtx().QueryArgs().Peek("url")
if ur == "" { if len(ur) == 0 {
return fiber.ErrBadRequest return fiber.ErrBadRequest
} }
parsed := fasthttp.AcquireURI() parsed := fasthttp.AcquireURI()
defer fasthttp.ReleaseURI(parsed) defer fasthttp.ReleaseURI(parsed)
err := parsed.Parse(nil, []byte(ur)) err := parsed.Parse(nil, ur)
if err != nil { if err != nil {
return err return err
} }
@@ -186,14 +186,14 @@ func Load(r *fiber.App) {
if l[0] == '#' { if l[0] == '#' {
if bytes.HasPrefix(l, []byte(`#EXT-X-MAP:URI="`)) { if bytes.HasPrefix(l, []byte(`#EXT-X-MAP:URI="`)) {
l = []byte(`#EXT-X-MAP:URI="/_/proxy/streams/aac?url=` + url.QueryEscape(string(l[16:len(l)-1])) + `"`) l = []byte(`#EXT-X-MAP:URI="/_/proxy/streams/aac?url=` + url.QueryEscape(cfg.B2s(l[16:len(l)-1])) + `"`)
sp[i] = l sp[i] = l
} }
continue continue
} }
l = []byte("/_/proxy/streams/aac?url=" + url.QueryEscape(string(l))) l = []byte("/_/proxy/streams/aac?url=" + url.QueryEscape(cfg.B2s(l)))
sp[i] = l sp[i] = l
} }

View File

@@ -209,7 +209,7 @@ func Load(r *fiber.App) {
return err return err
} }
var isDownload = c.Query("metadata") == "true" var isDownload = string(c.RequestCtx().QueryArgs().Peek("metadata")) == "true"
var quality *string var quality *string
if isDownload { if isDownload {
quality = p.DownloadAudio quality = p.DownloadAudio

View File

@@ -142,7 +142,7 @@ func GetClientID() (string, error) {
data = resp.Body() data = resp.Body()
} }
m, _ := verRegex.FindStringMatch(string(data)) m, _ := verRegex.FindStringMatch(cfg.B2s(data))
if m == nil { if m == nil {
return "", ErrVersionNotFound return "", ErrVersionNotFound
} }
@@ -160,7 +160,7 @@ func GetClientID() (string, error) {
} }
if experimental_GetClientID { if experimental_GetClientID {
m, _ = scriptRegex.FindStringMatch(string(data)) m, _ = scriptRegex.FindStringMatch(cfg.B2s(data))
if m != nil { if m != nil {
g = m.GroupByNumber(1) g = m.GroupByNumber(1)
if g != nil { if g != nil {
@@ -175,7 +175,7 @@ func GetClientID() (string, error) {
data = resp.Body() data = resp.Body()
} }
m, _ = clientIdRegex.FindStringMatch(string(data)) m, _ = clientIdRegex.FindStringMatch(cfg.B2s(data))
if m != nil { if m != nil {
g = m.GroupByNumber(1) g = m.GroupByNumber(1)
if g != nil { if g != nil {
@@ -192,7 +192,7 @@ func GetClientID() (string, error) {
ch := make(chan string, 1) ch := make(chan string, 1)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
done := false done := false
m, _ = scriptsRegex.FindStringMatch(string(data)) m, _ = scriptsRegex.FindStringMatch(cfg.B2s(data))
for m != nil { for m != nil {
g = m.GroupByNumber(1) g = m.GroupByNumber(1)
if g != nil { if g != nil {

41
main.go
View File

@@ -260,7 +260,7 @@ Disallow: /`)
//fmt.Println(c.Hostname(), c.Protocol(), c.IPs()) //fmt.Println(c.Hostname(), c.Protocol(), c.IPs())
u, err := url.Parse(string(loc)) u, err := url.Parse(cfg.B2s(loc))
if err != nil { if err != nil {
return err return err
} }
@@ -771,8 +771,45 @@ Disallow: /`)
} }
} }
var downloadAudio *string
if cfg.Restream {
_, audio := track.Media.SelectCompatible(*prefs.DownloadAudio, true)
downloadAudio = &audio
}
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, comments), templates.TrackHeader(prefs, track, true)).Render(c.RequestCtx(), c) return templates.Base(track.Title+" by "+track.Author.Username, templates.Track(prefs, track, stream, displayErr, string(c.RequestCtx().QueryArgs().Peek("autoplay")) == "true", playlist, nextTrack, c.Query("volume"), mode, audio, downloadAudio, comments), templates.TrackHeader(prefs, track, true)).Render(c.RequestCtx(), c)
})
app.Get("/_/partials/comments/:id", func(c fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return fiber.ErrBadRequest
}
pagination := c.RequestCtx().QueryArgs().Peek("pagination")
if len(pagination) == 0 {
return fiber.ErrBadRequest
}
prefs, err := preferences.Get(c)
if err != nil {
return err
}
t := sc.Track{ID: json.Number(id)}
comm, err := t.GetComments("", prefs, cfg.B2s(pagination))
if err != nil {
return err
}
if comm.Next != "" {
c.Set("next", "?pagination="+url.QueryEscape(strings.Split(comm.Next, "/comments")[1]))
} else {
c.Set("next", "done")
}
return templates.Comments(comm).Render(c.RequestCtx(), c)
}) })
app.Get("/:user", func(c fiber.Ctx) error { app.Get("/:user", func(c fiber.Ctx) error {

25
static/assets/comments.js Normal file
View File

@@ -0,0 +1,25 @@
var comm = document.getElementById('comments');
function comments(self) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/_/partials/comments/'+self.getAttribute('data-id')+self.getAttribute('href'), true);
xhr.onerror = function(e) {
alert('Something went wrong. Check console');
console.error(e);
}
xhr.onload = function() {
if (xhr.status != 200) {
alert(xhr.responseText);
return;
}
comm.innerHTML += xhr.responseText;
var next = xhr.getResponseHeader('next');
if (next == 'done') {
self.remove();
} else {
self.setAttribute('href', next);
}
self.textContent = 'more comments';
}
xhr.send();
}

View File

@@ -65,7 +65,7 @@ templ Preferences(prefs cfg.Preferences) {
<label> <label>
Autoplay next track in playlists: Autoplay next track in playlists:
@checkbox("AutoplayNextTrack", *prefs.AutoplayNextTrack) @checkbox("AutoplayNextTrack", *prefs.AutoplayNextTrack)
(requires JS) (requires JS; you need to allow autoplay from this domain!!)
</label> </label>
if *prefs.AutoplayNextTrack { if *prefs.AutoplayNextTrack {
<label> <label>
@@ -81,6 +81,11 @@ templ Preferences(prefs cfg.Preferences) {
@checkbox("SearchSuggestions", *prefs.SearchSuggestions) @checkbox("SearchSuggestions", *prefs.SearchSuggestions)
(requires JS) (requires JS)
</label> </label>
<label>
Dynamically load comments:
@checkbox("DynamicLoadComments", *prefs.DynamicLoadComments)
(requires JS)
</label>
<label> <label>
Player: Player:
@sel("Player", []option{ @sel("Player", []option{

View File

@@ -145,7 +145,7 @@ templ TrackItem(track *sc.Track, showUsername bool, overrideHref string) {
} }
} }
templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string, autoplay bool, playlist *sc.Playlist, nextTrack *sc.Track, volume string, mode string, audio string, comments *sc.Paginated[*sc.Comment]) { templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string, autoplay bool, playlist *sc.Playlist, nextTrack *sc.Track, volume string, mode string, audio string, downloadAudio *string, comments *sc.Paginated[*sc.Comment]) {
if t.Artwork != "" { if t.Artwork != "" {
<img src={ t.Artwork } width="300px"/> <img src={ t.Artwork } width="300px"/>
} }
@@ -153,7 +153,7 @@ templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string,
@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 { if cfg.Restream {
<div style="display: flex; margin-bottom: 1rem;"> <div style="display: flex; margin-bottom: 1rem;">
<a class="btn" href={ templ.SafeURL("/_/restream" + t.Href() + "?metadata=true") } download={ t.Permalink + "." + toExt(audio) }>download</a> <a class="btn" href={ templ.SafeURL("/_/restream" + t.Href() + "?metadata=true") } download={ t.Permalink + "." + toExt(*downloadAudio) }>download</a>
</div> </div>
} }
if t.Genre != "" { if t.Genre != "" {
@@ -197,25 +197,48 @@ templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string,
if t.TagList != "" { if t.TagList != "" {
<p>Tags: { strings.Join(sc.TagListParser(t.TagList), ", ") }</p> <p>Tags: { strings.Join(sc.TagListParser(t.TagList), ", ") }</p>
} }
if comments != nil { <h1>Comments</h1>
<h1>Comments</h1> if *prefs.DynamicLoadComments {
<div> if comments != nil {
for _, c := range comments.Collection { <div id="comments">
<div class="listing"> @Comments(comments)
if c.Author.Avatar != "" { </div>
<img src={ c.Author.Avatar }/> <script async src="/_/static/comments.js"></script>
} else { if comments.Next != "" {
<img src="/_/static/placeholder.jpg"/> <a class="btn" href={ templ.SafeURL("?pagination=" + url.QueryEscape(strings.Split(comments.Next, "/comments")[1])) } rel="noreferrer" onclick="event.preventDefault(); comments(this)" data-id={ string(t.ID) }>more comments</a>
}
<div class="comment">
<h3 class="link"><a href={ templ.SafeURL("/" + c.Author.Permalink) }>{ c.Author.Username }</a></h3>
<p>{ c.Body }</p>
</div>
</div>
} }
</div> } else {
<div id="comments"></div>
<script async src="/_/static/comments.js"></script>
<a class="btn" href="?pagination=%3Flimit%3D20%26threaded%3D1" data-id={ string(t.ID) } onclick="event.preventDefault(); comments(this)">load comments</a>
}
} else { } else {
<a class="btn" href="?pagination=%3Flimit%3D20%26threaded%3D1">load comments</a> if comments != nil {
<div>
@Comments(comments)
</div>
if comments.Next != "" {
<a class="btn" href={ templ.SafeURL("?pagination=" + url.QueryEscape(strings.Split(comments.Next, "/comments")[1])) } rel="noreferrer">more comments</a>
}
} else {
<a class="btn" href="?pagination=%3Flimit%3D20%26threaded%3D1">load comments</a>
}
}
}
templ Comments(comments *sc.Paginated[*sc.Comment]) {
for _, c := range comments.Collection {
<div class="listing">
if c.Author.Avatar != "" {
<img src={ c.Author.Avatar }/>
} else {
<img src="/_/static/placeholder.jpg"/>
}
<div class="comment">
<h3 class="link"><a href={ templ.SafeURL("/" + c.Author.Permalink) }>{ c.Author.Username }</a></h3>
<p>{ c.Body }</p>
</div>
</div>
} }
} }