diff --git a/README.md b/README.md index 53c24ff..d8fe2f6 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ wip alternative frontend for soundcloud # Already implemented - Searching for songs, users, playlists -- Basic user overview (songs, playlists, albums, metadata) +- Basic user overview (songs, playlists, albums, reposts, metadata) - Basic song overview (author, metadata) & streaming (requires some JS if instance has `Restream` disabled) - Basic playlist/set/album overview (songs list, author, metadata) - Resolving shortened links (`https://on.soundcloud.com/boiKDP46fayYDoVK9` -> `https://sc.maid.zone/on/boiKDP46fayYDoVK9`) - Content proxying (images, audio) +- View featured tracks, playlists - Users can change their preferences (should proxying be enabled, what method of playing the song should be used etc) ## In the works @@ -25,6 +26,7 @@ An easier way is to navigate to `/_/preferences`. If some features are disabled by the instance, they won't show up there. Available features: +- ParseDescriptions: Highlight `@username`, `https://example.com` and `email@example.com` in text as clickable links - Proxy images: Retrieve images through the instance, instead of going to soundcloud's servers for them - Player: In what way should the track be streamed. Can be Restream (does not require JS, better compatibility, can be a bit buggy client-side) or HLS (requires JS, more stable, less good compatibility (you'll be ok unless you are using a very outdated browser)) - Player-specific settings: They will only show up if you have selected HLS player currently. diff --git a/assets/global.css b/assets/global.css index 4c7d2c9..0b6f978 100644 --- a/assets/global.css +++ b/assets/global.css @@ -156,4 +156,8 @@ select:focus { display: grid; grid-template: auto / auto auto auto; gap: 1rem; +} + +.link { + color: var(--accent); } \ No newline at end of file diff --git a/lib/cfg/init.go b/lib/cfg/init.go index fb59255..82d30f7 100644 --- a/lib/cfg/init.go +++ b/lib/cfg/init.go @@ -32,6 +32,9 @@ type Preferences struct { FullyPreloadTrack *bool ProxyImages *bool + + // Highlight @username, https://example.com and email@example.com in text as clickable links + ParseDescriptions *bool } // // config // // @@ -218,8 +221,10 @@ func init() { // ProxyStreams: same as ProxyStreams in your config (false by default) // FullyPreloadTrack: false // ProxyImages: same as ProxyImages in your config (false by default) + // ParseDescriptions: true if config.DefaultPreferences != nil { var f bool + var t = true if config.DefaultPreferences.Player != nil { DefaultPreferences.Player = config.DefaultPreferences.Player } else { @@ -249,6 +254,12 @@ func init() { } else { DefaultPreferences.ProxyImages = &ProxyImages } + + if config.DefaultPreferences.ParseDescriptions != nil { + DefaultPreferences.ParseDescriptions = config.DefaultPreferences.ParseDescriptions + } else { + DefaultPreferences.ParseDescriptions = &t + } } else { var p string if Restream { @@ -261,8 +272,11 @@ func init() { DefaultPreferences.ProxyStreams = &ProxyStreams var f bool + var t = true DefaultPreferences.FullyPreloadTrack = &f DefaultPreferences.ProxyImages = &ProxyImages + + DefaultPreferences.ParseDescriptions = &t } } diff --git a/lib/preferences/init.go b/lib/preferences/init.go index 1d47396..522d020 100644 --- a/lib/preferences/init.go +++ b/lib/preferences/init.go @@ -26,6 +26,10 @@ func Defaults(dst *cfg.Preferences) { if dst.ProxyImages == nil { dst.ProxyImages = cfg.DefaultPreferences.ProxyImages } + + if dst.ParseDescriptions == nil { + dst.ParseDescriptions = cfg.DefaultPreferences.ParseDescriptions + } } func Get(c *fiber.Ctx) (cfg.Preferences, error) { @@ -43,6 +47,7 @@ func Get(c *fiber.Ctx) (cfg.Preferences, error) { type PrefsForm struct { ProxyImages string + ParseDescriptions string Player string ProxyStreams string FullyPreloadTrack string @@ -96,6 +101,12 @@ func Load(r fiber.Router) { old.ProxyImages = &f } } + + if p.ParseDescriptions == "on" { + old.ParseDescriptions = &t + } else { + old.ParseDescriptions = &f + } old.Player = &p.Player data, err := json.Marshal(old) diff --git a/lib/sc/init.go b/lib/sc/init.go index bcb574a..d207468 100644 --- a/lib/sc/init.go +++ b/lib/sc/init.go @@ -119,13 +119,19 @@ func GetClientID() (string, error) { } func DoWithRetry(httpc *fasthttp.HostClient, req *fasthttp.Request, resp *fasthttp.Response) (err error) { - for i := 0; i < 5; i++ { + for i := 0; i < 10; i++ { err = httpc.Do(req, resp) if err == nil { return nil } - if err != fasthttp.ErrTimeout && err != fasthttp.ErrConnectionClosed && !os.IsTimeout(err) && !errors.Is(err, syscall.EPIPE) { // EPIPE is "broken pipe" error + if err != fasthttp.ErrTimeout && + err != fasthttp.ErrDialTimeout && + err != fasthttp.ErrTLSHandshakeTimeout && + err != fasthttp.ErrConnectionClosed && + !os.IsTimeout(err) && + !errors.Is(err, syscall.EPIPE) && // EPIPE is "broken pipe" error + err.Error() != "timeout" { return } } diff --git a/lib/textparsing/init.go b/lib/textparsing/init.go new file mode 100644 index 0000000..f1f7a53 --- /dev/null +++ b/lib/textparsing/init.go @@ -0,0 +1,42 @@ +package textparsing + +import ( + "fmt" + "html" + "net/url" + "regexp" + "strings" +) + +//var wordre = regexp.MustCompile(`\S+`) + +// var urlre = regexp.MustCompile(`https?:\/\/[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{1,6}[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*`) +// var emailre = regexp.MustCompile(`[-a-zA-Z0-9%._\+~#=]+@[-a-zA-Z0-9%._\+~=&]{2,256}\.[a-z]{1,6}`) +// var usernamere = regexp.MustCompile(`@[a-z0-9\-]+`) +var theregex = regexp.MustCompile(`@[a-z0-9\-]+|(?:https?:\/\/[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{1,6}[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)|(?:[-a-zA-Z0-9%._\+~#=]+@[-a-zA-Z0-9%._\+~=&]{2,256}\.[a-z]{1,6})`) + +func replacer(ent string) string { + if strings.HasPrefix(ent, "@") { + return fmt.Sprintf(`%s`, ent[1:], ent) + } + + if strings.HasPrefix(ent, "https://") || strings.HasPrefix(ent, "http://") { + ent = html.UnescapeString(ent) + parsed, err := url.Parse(ent) + if err == nil { + href := ent + if parsed.Host == "soundcloud.com" || strings.HasSuffix(parsed.Host, ".soundcloud.com") { + href = "/" + strings.Join(strings.Split(ent, "/")[3:], "/") + } + + return fmt.Sprintf(`%s`, href, ent) + } + } + + // Otherwise, it can only be an email + return fmt.Sprintf(`%s`, ent, ent) +} + +func Format(text string) string { + return theregex.ReplaceAllStringFunc(html.EscapeString(text), replacer) +} diff --git a/main.go b/main.go index 7567ca2..68c7289 100644 --- a/main.go +++ b/main.go @@ -243,7 +243,7 @@ func main() { } c.Set("Content-Type", "text/html") - return templates.Base(user.Username, templates.UserPlaylists(user, pl), templates.UserHeader(user)).Render(context.Background(), c) + return templates.Base(user.Username, templates.UserPlaylists(prefs, user, pl), templates.UserHeader(user)).Render(context.Background(), c) }) app.Get("/:user/albums", func(c *fiber.Ctx) error { @@ -265,7 +265,7 @@ func main() { } c.Set("Content-Type", "text/html") - return templates.Base(user.Username, templates.UserAlbums(user, pl), templates.UserHeader(user)).Render(context.Background(), c) + return templates.Base(user.Username, templates.UserAlbums(prefs, user, pl), templates.UserHeader(user)).Render(context.Background(), c) }) app.Get("/:user/reposts", func(c *fiber.Ctx) error { @@ -287,7 +287,7 @@ func main() { } c.Set("Content-Type", "text/html") - return templates.Base(user.Username, templates.UserReposts(user, p), templates.UserHeader(user)).Render(context.Background(), c) + return templates.Base(user.Username, templates.UserReposts(prefs, user, p), templates.UserHeader(user)).Render(context.Background(), c) }) app.Get("/:user/:track", func(c *fiber.Ctx) error { @@ -347,7 +347,7 @@ func main() { //fmt.Println("gettracks", time.Since(h)) c.Set("Content-Type", "text/html") - return templates.Base(usr.Username, templates.User(usr, p), templates.UserHeader(usr)).Render(context.Background(), c) + return templates.Base(usr.Username, templates.User(prefs, usr, p), templates.UserHeader(usr)).Render(context.Background(), c) }) app.Get("/:user/sets/:playlist", func(c *fiber.Ctx) error { @@ -375,7 +375,7 @@ func main() { } c.Set("Content-Type", "text/html") - return templates.Base(playlist.Title+" by "+playlist.Author.Username, templates.Playlist(playlist), templates.PlaylistHeader(playlist)).Render(context.Background(), c) + return templates.Base(playlist.Title+" by "+playlist.Author.Username, templates.Playlist(prefs, playlist), templates.PlaylistHeader(playlist)).Render(context.Background(), c) }) log.Fatal(app.Listen(cfg.Addr)) diff --git a/templates/base.templ b/templates/base.templ index 4ebb5cb..3ffdd94 100644 --- a/templates/base.templ +++ b/templates/base.templ @@ -1,5 +1,10 @@ package templates +import ( + "github.com/maid-zone/soundcloak/lib/cfg" + "github.com/maid-zone/soundcloak/lib/textparsing" +) + templ Base(title string, content templ.Component, head templ.Component) { @@ -22,3 +27,16 @@ templ Base(title string, content templ.Component, head templ.Component) { } + +templ Description(prefs cfg.Preferences, text string) { +
+ Toggle description +

+ if *prefs.ParseDescriptions { + @templ.Raw(textparsing.Format(text)) + } else { + { text } + } +

+
+} diff --git a/templates/playlist.templ b/templates/playlist.templ index 24976b9..c4c977e 100644 --- a/templates/playlist.templ +++ b/templates/playlist.templ @@ -5,6 +5,7 @@ import ( "net/url" "strconv" "strings" + "github.com/maid-zone/soundcloak/lib/cfg" ) templ PlaylistHeader(p sc.Playlist) { @@ -32,7 +33,7 @@ templ PlaylistItem(playlist *sc.Playlist, showUsername bool) { } -templ Playlist(p sc.Playlist) { +templ Playlist(prefs cfg.Preferences, p sc.Playlist) { if p.Artwork != "" { } @@ -43,10 +44,7 @@ templ Playlist(p sc.Playlist) {
if p.Description != "" { -
- Toggle description -

{ p.Description }

-
+ @Description(prefs, p.Description) }

{ strconv.FormatInt(p.TrackCount, 10) } tracks


diff --git a/templates/preferences.templ b/templates/preferences.templ index 49e5e7d..8d1eb18 100644 --- a/templates/preferences.templ +++ b/templates/preferences.templ @@ -11,8 +11,8 @@ templ checkbox(name string, checked bool) { } type option struct { - value string - desc string + value string + desc string disabled bool } @@ -36,6 +36,11 @@ templ sel(name string, options []option, selected string) { templ Preferences(prefs cfg.Preferences) {

Preferences

+ +
if cfg.ProxyImages {