From 00e58c60cd59882199a7e378416a279de2b5212d Mon Sep 17 00:00:00 2001 From: tobi Date: Thu, 22 May 2025 12:34:39 +0200 Subject: [PATCH] [feature] Add ListenBrainz functionality on the web view (#4184) This pull request adds a very simple ad-hoc ListenBrainz widget to the frontend web view, with progressive enhancement (in all fail states it just falls back to rendering the field as normal). This necessitated adding the ListenBrainz API endpoint to the `connect-src` part of our Content-Security-Policy header. We might want to tweak this to only add that endpoint to `connect-src` for profiles, and then only for profiles that include a ListenBrainz field, but this would require significant dicking about with the middleware, and checks inside the app logic, such that it might not be worthwhile (after all, we control all the scripts right now anyway). Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4184 Co-authored-by: tobi Co-committed-by: tobi --- .../user-settings-listenbrainz-fields.png | Bin 0 -> 34371 bytes .../public/user-settings-listenbrainz.png | Bin 0 -> 35869 bytes docs/user_guide/settings.md | 13 ++ internal/middleware/contentsecuritypolicy.go | 16 +- .../middleware/contentsecuritypolicy_test.go | 12 +- web/source/frontend/index.js | 167 ++++++++++++++++++ 6 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 docs/overrides/public/user-settings-listenbrainz-fields.png create mode 100644 docs/overrides/public/user-settings-listenbrainz.png diff --git a/docs/overrides/public/user-settings-listenbrainz-fields.png b/docs/overrides/public/user-settings-listenbrainz-fields.png new file mode 100644 index 0000000000000000000000000000000000000000..a54f2a232d62f2684c84ab544fc193a22bc4b757 GIT binary patch literal 34371 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 xfB-Tv6aWCAPW<F;Y5x8CBJ%y$1{o49Zmus164Q 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

U|s`64a& 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 literal 0 HcmV?d00001 diff --git a/docs/user_guide/settings.md b/docs/user_guide/settings.md index ab095288a..96cebe911 100644 --- a/docs/user_guide/settings.md +++ b/docs/user_guide/settings.md @@ -98,6 +98,19 @@ Some examples: - Pronouns : she/her - My other account : @someone@somewhere.com +!!! Tip "ListenBrainz integration" + If you set the key of one of your profile fields to "ListenBrainz" and the value to the URL of your ListenBrainz profile (something like `https://listenbrainz.org/user/your_listenbrainz_username/` -- the slash at the end is important!), then the field will be replaced on the web frontend with whatever you're currently listening to! + + This only applies to the web view of your GoToSocial profile, for visitors with Javascript enabled; the "currently listening" value doesn't federate to other servers, only your ListenBrainz URL. + + How to set it: + + ![Field filled with a ListenBrainz URL.](../public/user-settings-listenbrainz-fields.png) + + How it looks on the web when you're listening to something: + + ![The "Now listening to" widget on the web view.](../public/user-settings-listenbrainz.png) + ### Visibility and Privacy #### Visibility Level of Posts to Show on Your Profile diff --git a/internal/middleware/contentsecuritypolicy.go b/internal/middleware/contentsecuritypolicy.go index fb35c3a08..eb5168376 100644 --- a/internal/middleware/contentsecuritypolicy.go +++ b/internal/middleware/contentsecuritypolicy.go @@ -37,6 +37,7 @@ func ContentSecurityPolicy(extraURIs ...string) gin.HandlerFunc { func BuildContentSecurityPolicy(extraURIs ...string) string { const ( defaultSrc = "default-src" + connectSrc = "connect-src" objectSrc = "object-src" imgSrc = "img-src" mediaSrc = "media-src" @@ -48,7 +49,7 @@ func BuildContentSecurityPolicy(extraURIs ...string) string { ) // CSP values keyed by directive. - values := make(map[string][]string, 4) + values := make(map[string][]string, 5) /* default-src @@ -69,6 +70,16 @@ func BuildContentSecurityPolicy(extraURIs ...string) string { } } + /* + connect-src + https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src + */ + + // Restrictive default policy, but + // include ListenBrainz API for fields. + const listenBrains = "https://api.listenbrainz.org/1/user/" + values[connectSrc] = append(values[defaultSrc], listenBrains) //nolint + /* object-src https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src @@ -118,9 +129,10 @@ func BuildContentSecurityPolicy(extraURIs ...string) string { // Iterate through an ordered slice rather than // iterating through the map, since we want these // policyDirectives in a determinate order. - policyDirectives := make([]string, 4) + policyDirectives := make([]string, 5) for i, directive := range []string{ defaultSrc, + connectSrc, objectSrc, imgSrc, mediaSrc, diff --git a/internal/middleware/contentsecuritypolicy_test.go b/internal/middleware/contentsecuritypolicy_test.go index a337763df..ef6dc2bf8 100644 --- a/internal/middleware/contentsecuritypolicy_test.go +++ b/internal/middleware/contentsecuritypolicy_test.go @@ -32,38 +32,38 @@ func TestBuildContentSecurityPolicy(t *testing.T) { for _, test := range []cspTest{ { extraURLs: nil, - expected: "default-src 'self'; object-src 'none'; img-src 'self' blob:; media-src 'self'", + expected: "default-src 'self'; connect-src 'self' https://api.listenbrainz.org/1/user/; object-src 'none'; img-src 'self' blob:; media-src 'self'", }, { extraURLs: []string{ "https://some-bucket-provider.com", }, - expected: "default-src 'self'; object-src 'none'; img-src 'self' blob: https://some-bucket-provider.com; media-src 'self' https://some-bucket-provider.com", + expected: "default-src 'self'; connect-src 'self' https://api.listenbrainz.org/1/user/; object-src 'none'; img-src 'self' blob: https://some-bucket-provider.com; media-src 'self' https://some-bucket-provider.com", }, { extraURLs: []string{ "https://some-bucket-provider.com:6969", }, - expected: "default-src 'self'; object-src 'none'; img-src 'self' blob: https://some-bucket-provider.com:6969; media-src 'self' https://some-bucket-provider.com:6969", + expected: "default-src 'self'; connect-src 'self' https://api.listenbrainz.org/1/user/; object-src 'none'; img-src 'self' blob: https://some-bucket-provider.com:6969; media-src 'self' https://some-bucket-provider.com:6969", }, { extraURLs: []string{ "http://some-bucket-provider.com:6969", }, - expected: "default-src 'self'; object-src 'none'; img-src 'self' blob: http://some-bucket-provider.com:6969; media-src 'self' http://some-bucket-provider.com:6969", + expected: "default-src 'self'; connect-src 'self' https://api.listenbrainz.org/1/user/; object-src 'none'; img-src 'self' blob: http://some-bucket-provider.com:6969; media-src 'self' http://some-bucket-provider.com:6969", }, { extraURLs: []string{ "https://s3.nl-ams.scw.cloud", }, - expected: "default-src 'self'; object-src 'none'; img-src 'self' blob: https://s3.nl-ams.scw.cloud; media-src 'self' https://s3.nl-ams.scw.cloud", + expected: "default-src 'self'; connect-src 'self' https://api.listenbrainz.org/1/user/; object-src 'none'; img-src 'self' blob: https://s3.nl-ams.scw.cloud; media-src 'self' https://s3.nl-ams.scw.cloud", }, { extraURLs: []string{ "https://s3.nl-ams.scw.cloud", "https://s3.somewhere.else.example.org", }, - expected: "default-src 'self'; object-src 'none'; img-src 'self' blob: https://s3.nl-ams.scw.cloud https://s3.somewhere.else.example.org; media-src 'self' https://s3.nl-ams.scw.cloud https://s3.somewhere.else.example.org", + expected: "default-src 'self'; connect-src 'self' https://api.listenbrainz.org/1/user/; object-src 'none'; img-src 'self' blob: https://s3.nl-ams.scw.cloud https://s3.somewhere.else.example.org; media-src 'self' https://s3.nl-ams.scw.cloud https://s3.somewhere.else.example.org", }, } { csp := middleware.BuildContentSecurityPolicy(test.extraURLs...) diff --git a/web/source/frontend/index.js b/web/source/frontend/index.js index a1c2ca74b..25c948795 100644 --- a/web/source/frontend/index.js +++ b/web/source/frontend/index.js @@ -267,3 +267,170 @@ document.body.addEventListener("click", (e) => { // stats elements, close it. openStats.removeAttribute("open"); }); + +// Scan for the first ListenBrainz profile field and replace +// its value with currently listening track if available. +// +// ListenBrainz allows a lot of leeway in usernames so be gentle here: +// +// See: +// +// - https://github.com/metabrainz/musicbrainz-server/blob/master/lib/MusicBrainz/Server/Form/Utils.pm#L264-L288 +// - https://regex101.com/r/k5ij9F/1 +const listenbrainzRe = new RegExp(/^https:\/\/listenbrainz\.org\/user\/([^/]+)\/$/, "u"); +let calledListenBrainz = false; +document.querySelectorAll("div#profile-fields dl div.field").forEach((field) => { + // If we called ListenBrainz once + // already this page load, bail. + if (calledListenBrainz) { + return; + } + + const k = field.querySelector("dt"); + if (!k) { + // No

inside this + // field? Weird but OK. + return; + } + + const kText = k.textContent; + if (kText === null) { + // Also strange but + // let's just bail. + return; + } + + // Check if key == "ListenBrainz" (case insensitive). + if (kText.localeCompare("ListenBrainz", undefined, { sensitivity: "base" }) !== 0) { + // Not interested. + return; + } + + // Get the value. + const v = field.querySelector("dd"); + if (!v) { + // No
inside this + // field? Weird but OK. + return; + } + + // Look for an tag inside the
. + const oldAs = v.getElementsByTagName("a"); + if (oldAs.length !== 1) { + // Nothing + // in here. + return; + } + + const oldA = oldAs[0]; + const profileURL = oldA.textContent; + if (!profileURL) { + // Also strange but + // let's just bail. + return; + } + + // We're looking for a listenbrainz URL. + const match = profileURL.match(listenbrainzRe); + if (match.length !== 2) { + // Not a match. + return; + } + const lbUsername = match[1]; + + try { + // MusicBrainz/ListenBrainz is very permissive + // re: usernames so make sure to encode the URI + // when doing the fetch, to avoid any shenanigans. + const apiURL = encodeURI(`https://api.listenbrainz.org/1/user/${lbUsername}/playing-now`); + fetch(apiURL).then(res => { + // Mark that we + // called LB already. + calledListenBrainz = true; + + // Check result... + if (!res.ok) { + throw new Error(`Response status: ${res.status}`); + } + + return res.json(); + }).then(json => { + // Parse out the object. + const payload = json.payload; + if (!payload) { + // Can't do anything + // with no payload. + return; + } + + const listens = payload.listens; + if (!listens || !Array.isArray(listens) || listens.length !== 1) { + // Can't do anything + // with no listens. + return; + } + + const listen = listens[0]; + const trackMetadata = listen.track_metadata; + if (!trackMetadata) { + // Can't do anything + // with no track metadata. + return; + } + + const artistName = trackMetadata.artist_name; + const trackName = trackMetadata.track_name; + if (artistName === undefined || trackName === undefined) { + // Can't display + // this track. + return; + } + + // We can work with this. + // + // Rewrite the existing
with the + // current listening song, and keep the + // link to the user's ListenBrainz profile. + const vNew = document.createElement("dd"); + + // Lil music note icon. + const i = document.createElement("i"); + i.ariaHidden = "true"; + i.className = "fa fa-fw fa-music"; + vNew.appendChild(i); + + vNew.appendChild(document.createTextNode(" Now listening to: ")); + vNew.appendChild(document.createElement("br")); + + // Build the new link, taking + // the href from the old link. + const a = document.createElement("a"); + a.href = oldA.href; + a.rel = "nofollow noreferrer noopener"; + a.target = "_blank"; + + // Add track name in bold. + const trackNameE = document.createElement("b"); + trackNameE.textContent = trackName; + a.appendChild(trackNameE); + + // Add joiner in normal font. + a.appendChild(document.createTextNode(" by ")); + + // Add artist name in bold. + const artistNameE = document.createElement("b"); + artistNameE.textContent = artistName; + a.appendChild(artistNameE); + + // Put the link + // in the definish. + vNew.appendChild(a); + + // Do the replacement. + field.replaceChild(vNew, v); + }); + } catch (error) { + // eslint-disable-next-line no-console + console.error(error.message); + } +});