summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/source/frontend/index.js167
1 files changed, 167 insertions, 0 deletions
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 <dt> 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 <dd> inside this
+ // field? Weird but OK.
+ return;
+ }
+
+ // Look for an <a> tag inside the <dd>.
+ 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 <dd> 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);
+ }
+});