// ==UserScript== // @name scRYMble // @version 2.20240707021902 // @license MIT // @description Visit a release page on rateyourmusic.com and scrobble the songs you see! // @author fidwell // @icon https://e.snmc.io/2.5/img/sonemic.png // @namespace https://github.com/fidwell/scRYMble // @include https://rateyourmusic.com/release/* // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @require http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js // @require https://update.greasyfork.icu/scripts/130/10066/Portable%20MD5%20Function.js // @downloadURL none // ==/UserScript== 'use strict'; var ScrobbleRecord = /** @class */ (function () { function ScrobbleRecord(trackName, artist, duration) { this.artist = artist; this.trackName = trackName; var durastr = duration.trim(); var colon = durastr.indexOf(":"); if (colon !== -1) { var minutes = parseInt(durastr.substring(0, colon)); var seconds = parseInt(durastr.substring(colon + 1)); this.duration = minutes * 60 + seconds; } else { this.duration = 180; } this.time = 0; } return ScrobbleRecord; }()); var HttpResponse = /** @class */ (function () { function HttpResponse(raw) { this.status = raw.status; this.statusText = raw.statusText; this.responseText = raw.responseText; this.lines = raw.responseText.split("\n"); } Object.defineProperty(HttpResponse.prototype, "isOkStatus", { get: function () { return this.lines[0] === "OK"; }, enumerable: false, configurable: true }); Object.defineProperty(HttpResponse.prototype, "sessionId", { get: function () { return this.lines[1]; }, enumerable: false, configurable: true }); Object.defineProperty(HttpResponse.prototype, "nowPlayingUrl", { get: function () { return this.lines[2]; }, enumerable: false, configurable: true }); Object.defineProperty(HttpResponse.prototype, "submitUrl", { get: function () { return this.lines[3]; }, enumerable: false, configurable: true }); return HttpResponse; }()); function httpGet(url, onload) { GM_xmlhttpRequest({ method: "GET", url: url, headers: { "User-agent": "Mozilla/4.0 (compatible) Greasemonkey" }, onload: function (responseRaw) { return onload(new HttpResponse(responseRaw)); } }); } function httpPost(url, data, onload) { GM_xmlhttpRequest({ method: "POST", url: url, data: data, headers: { "User-agent": "Mozilla/4.0 (compatible) Greasemonkey", "Content-type": "application/x-www-form-urlencoded" }, onload: function (responseRaw) { return onload(new HttpResponse(responseRaw)); } }); } function fetch_unix_timestamp() { return parseInt(new Date().getTime().toString().substring(0, 10)); } function handshake(callback) { var _a, _b, _c, _d; var user = (_b = (_a = $("#scrobbleusername").val()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ""; var password = (_d = (_c = $("#scrobblepassword").val()) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : ""; GM_setValue("user", user); GM_setValue("pass", password); var timestamp = fetch_unix_timestamp(); var auth = hex_md5("".concat(hex_md5(password)).concat(timestamp)); var handshakeURL = "http://post.audioscrobbler.com/?hs=true&p=1.2&c=scr&v=1.0&u=".concat(user, "&t=").concat(timestamp, "&a=").concat(auth); httpGet(handshakeURL, callback); } var scRYMbleUi = /** @class */ (function () { function scRYMbleUi() { this.enabled = false; this.eleTrackTable = $("#tracks"); if (this.eleTrackTable.children().length === 0) { console.log("scRYMble: No track list found."); } else { this.enabled = true; this.createCheckboxes(); this.createControls(); } } Object.defineProperty(scRYMbleUi.prototype, "isEnabled", { get: function () { return this.enabled; }, enumerable: false, configurable: true }); scRYMbleUi.prototype.createCheckboxes = function () { var n = 0; var chkbox = ""; $.each($("#tracks > .track > .tracklist_line"), function () { if ($(this).find(".tracklist_num:eq(0)").text() !== "\n \n ") { n++; $(this).prepend(chkbox.replace("NUM", "".concat(n))); } }); }; scRYMbleUi.prototype.createControls = function () { var _this = this; var eleButtonDiv = document.createElement("div"); eleButtonDiv.innerHTML = "
 
 
 
user:
pass:
"; eleButtonDiv.style.textAlign = "right"; this.eleTrackTable.after(eleButtonDiv); var eleAllOrNone = document.getElementById("allornone"); eleAllOrNone === null || eleAllOrNone === void 0 ? void 0 : eleAllOrNone.addEventListener("click", function () { return _this.allOrNoneClick(); }, true); }; scRYMbleUi.prototype.hookUpScrobbleNow = function (startScrobble) { var eleScrobbleNow = document.getElementById("scrobblenow"); eleScrobbleNow === null || eleScrobbleNow === void 0 ? void 0 : eleScrobbleNow.addEventListener("click", startScrobble, true); }; scRYMbleUi.prototype.hookUpScrobbleThen = function (handshakeBatch) { var _a; (_a = document.getElementById("scrobblethen")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", handshakeBatch, true); }; scRYMbleUi.prototype.setMarquee = function (value) { var marquee = document.getElementById("scrymblemarquee"); if (marquee !== null) { marquee.innerHTML = value; } }; scRYMbleUi.prototype.setProgressBar = function (percentage) { var progbar = document.getElementById("progbar"); if (progbar !== null && percentage >= 0 && percentage <= 100) { progbar.style.width = "".concat(percentage, "%"); } }; scRYMbleUi.prototype.allOrNoneClick = function () { var _this = this; window.setTimeout(function () { return _this.allOrNoneAction(); }, 10); }; scRYMbleUi.prototype.allOrNoneAction = function () { $.each($(".scrymblechk"), function () { $(this).prop("checked", $("#allornone").is(":checked")); }); }; scRYMbleUi.prototype.elementsOnAndOff = function (state) { $("#scrobblenow").prop("disabled", !state); $("#scrobblepassword").prop("disabled", !state); $("#scrobbleusername").prop("disabled", !state); $("#scrobblepassword").prop("disabled", !state); $.each($(".scrymblechk"), function () { try { $(this).prop("disabled", !state); } catch (e) { console.log(e); } }); }; scRYMbleUi.prototype.elementsOff = function () { this.elementsOnAndOff(false); }; scRYMbleUi.prototype.elementsOn = function () { this.elementsOnAndOff(true); }; return scRYMbleUi; }()); var ui = new scRYMbleUi(); var toScrobble = []; var currentlyScrobbling = -1; var sessID = ""; var submitURL = ""; var npURL = ""; var currTrackDuration = 0; var currTrackPlayTime = 0; function confirmBrowseAway(oEvent) { if (currentlyScrobbling !== -1) { oEvent.preventDefault(); return "You are currently scrobbling a record. Leaving the page now will prevent future tracks from this release from scrobbling."; } return ""; } function getPageArtist() { var byartist = $("span[itemprop=\"byArtist\"]"); var art_cred = $(byartist).find(".credited_name:eq(0) > span[itemprop=\"name\"]"); if ($(art_cred).length > 0) { return $(art_cred).text(); } else { return $(byartist).text(); } } function getAlbum() { var _a, _b; return (_b = (_a = $(".release_page meta[itemprop=\"name\"]").attr("content")) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : ""; } function isVariousArtists() { var artist = getPageArtist(); return artist.indexOf("Various Artists") > -1 || artist.indexOf(" / ") > -1; } function acceptSubmitResponse(responseDetails, isBatch) { if (responseDetails.status === 200) { if (!responseDetails.isOkStatus) { alertSubmitFailed(responseDetails); } } else { alertSubmitFailed(responseDetails); } if (isBatch) { ui.setMarquee("Scrobbled OK!"); } else { scrobbleNextSong(); } } function alertSubmitFailed(responseDetails) { alert("Track submit failed: ".concat(responseDetails.status, " ").concat(responseDetails.statusText, "\n\nData:\n").concat(responseDetails.responseText)); } function acceptSubmitResponseSingle(responseDetails) { acceptSubmitResponse(responseDetails, false); } function acceptSubmitResponseBatch(responseDetails) { acceptSubmitResponse(responseDetails, true); } function acceptNPResponse(responseDetails) { if (responseDetails.status === 200) { if (!responseDetails.isOkStatus) { alertSubmitFailed(responseDetails); } } else { alertSubmitFailed(responseDetails); } } function buildListOfSongsToScrobble() { toScrobble = []; $.each($(".scrymblechk"), function () { if ($(this).is(":checked")) { var song = $(this).parent().parent(); var songTitle = $(song).find("span[itemprop=\"name\"]").text(); var artist = getPageArtist(); var length_1 = $(song).find(".tracklist_duration").text(); if (isVariousArtists()) { var firstDash = songTitle.indexOf(" - "); if (firstDash === -1) { // no dash exists! must be a single artist with " / " in the name or v/a with unscrobbleable list artist = getPageArtist(); if (artist.indexOf("Various Artists") > -1) { artist = $(".album_title:eq(0)").text(); } } else { artist = songTitle.substring(0, firstDash); songTitle = songTitle.substring(firstDash + 3); } } else { artist = getPageArtist(); var title = $(song).find("span[itemprop=\"name\"]"); if ($(title).html().indexOf(" 0) { var firstDash = songTitle.indexOf(" - "); artist = songTitle.substring(0, firstDash); songTitle = songTitle.substring(firstDash + 3); } } if (songTitle.toLowerCase() === "untitled" || songTitle.toLowerCase() === "untitled track" || songTitle === "") { songTitle = "[untitled]"; } while (songTitle.indexOf(" ") > 0) { songTitle = songTitle.replace(" ", " "); } toScrobble[toScrobble.length] = new ScrobbleRecord(songTitle, artist, length_1); } }); } function submitTracksBatch(sessID, submitURL) { buildListOfSongsToScrobble(); if (toScrobble === null) return; var currTime = fetch_unix_timestamp(); var hoursFudgeStr = prompt("How many hours ago did you listen to this?"); if (hoursFudgeStr !== null) { var album = getAlbum(); var hoursFudge = parseFloat(hoursFudgeStr); if (!isNaN(hoursFudge)) { currTime = currTime - hoursFudge * 60 * 60; } for (var i = toScrobble.length - 1; i >= 0; i--) { currTime = currTime * 1 - toScrobble[i].duration * 1; toScrobble[i].time = currTime; } var outstr = "Artist: ".concat(getPageArtist(), "\nAlbum: ").concat(album, "\n"); for (var _i = 0, toScrobble_1 = toScrobble; _i < toScrobble_1.length; _i++) { var song = toScrobble_1[_i]; outstr = "".concat(outstr).concat(song.trackName, " (").concat(song.duration, ")\n"); } var postdata = {}; for (var i = 0; i < toScrobble.length; i++) { postdata["a[".concat(i, "]")] = toScrobble[i].artist; postdata["t[".concat(i, "]")] = toScrobble[i].trackName; postdata["b[".concat(i, "]")] = album; postdata["n[".concat(i, "]")] = "".concat(i + 1); postdata["l[".concat(i, "]")] = "".concat(toScrobble[i].duration); postdata["i[".concat(i, "]")] = "".concat(toScrobble[i].time); postdata["o[".concat(i, "]")] = "P"; postdata["r[".concat(i, "]")] = ""; postdata["m[".concat(i, "]")] = ""; } postdata["s"] = sessID; var postdataStr = ""; var firstTime = true; for (var currKey in postdata) { if (firstTime) { firstTime = false; } else { postdataStr = "".concat(postdataStr, "&"); } postdataStr = "".concat(postdataStr).concat(encodeURIComponent(currKey), "=").concat(encodeURIComponent(postdata[currKey])); } httpPost(submitURL, postdataStr, acceptSubmitResponseBatch); } } function startScrobble() { currentlyScrobbling = -1; currTrackDuration = 0; currTrackPlayTime = 0; ui.elementsOff(); buildListOfSongsToScrobble(); scrobbleNextSong(); } function resetScrobbler() { currentlyScrobbling = -1; currTrackDuration = 0; currTrackPlayTime = 0; ui.setMarquee(" "); ui.setProgressBar(0); toScrobble = []; ui.elementsOn(); } function scrobbleNextSong() { currentlyScrobbling++; if (currentlyScrobbling === toScrobble.length) { resetScrobbler(); } else { window.setTimeout(timertick, 10); handshake(acceptHandshakeSingle); } } function submitThisTrack() { var postdata = {}; var i = 0; var currTime = fetch_unix_timestamp(); postdata["a".concat(i)] = toScrobble[currentlyScrobbling].artist; postdata["t".concat(i)] = toScrobble[currentlyScrobbling].trackName; postdata["b".concat(i)] = getAlbum(); postdata["n".concat(i)] = "".concat(currentlyScrobbling + 1); postdata["l".concat(i)] = "".concat(toScrobble[currentlyScrobbling].duration); postdata["i".concat(i)] = "".concat(currTime - toScrobble[currentlyScrobbling].duration); postdata["o".concat(i)] = "P"; postdata["r".concat(i)] = ""; postdata["m".concat(i)] = ""; postdata["s"] = sessID; var postdataStr = ""; var firstTime = true; for (var currKey in postdata) { if (firstTime) { firstTime = false; } else { postdataStr = "".concat(postdataStr, "&"); } postdataStr = "".concat(postdataStr).concat(encodeURIComponent(currKey), "=").concat(encodeURIComponent(postdata[currKey])); } httpPost(submitURL, postdataStr, acceptSubmitResponseSingle); } function npNextTrack() { var postdata = {}; postdata["a"] = toScrobble[currentlyScrobbling].artist; postdata["t"] = toScrobble[currentlyScrobbling].trackName; postdata["b"] = getAlbum(); postdata["n"] = "".concat(currentlyScrobbling + 1); postdata["l"] = "".concat(toScrobble[currentlyScrobbling].duration); postdata["m"] = ""; postdata["s"] = sessID; currTrackDuration = toScrobble[currentlyScrobbling].duration; currTrackPlayTime = 0; ui.setMarquee(toScrobble[currentlyScrobbling].trackName); var postdataStr = ""; var firstTime = true; for (var currKey in postdata) { if (firstTime) { firstTime = false; } else { postdataStr = "".concat(postdataStr, "&"); } postdataStr = postdataStr + encodeURIComponent(currKey) + "=" + encodeURIComponent(postdata[currKey]); } httpPost(npURL, postdataStr, acceptNPResponse); } function timertick() { var again = true; if (currentlyScrobbling !== -1) { if (currTrackDuration !== 0) { ui.setProgressBar(100 * currTrackPlayTime / currTrackDuration); } currTrackPlayTime++; if (currTrackPlayTime === currTrackDuration) { submitThisTrack(); again = false; } } if (again) { window.setTimeout(timertick, 1000); } } function acceptHandshakeSingle(responseDetails) { acceptHandshake(responseDetails, false); } function acceptHandshakeBatch(responseDetails) { acceptHandshake(responseDetails, true); } function acceptHandshake(responseDetails, isBatch) { if (responseDetails.status === 200) { if (!responseDetails.isOkStatus) { alertHandshakeFailed(responseDetails); } else { sessID = responseDetails.sessionId; npURL = responseDetails.nowPlayingUrl; submitURL = responseDetails.submitUrl; if (isBatch) { submitTracksBatch(sessID, submitURL); } else { npNextTrack(); } } } else { alertHandshakeFailed(responseDetails); } } function alertHandshakeFailed(responseDetails) { alert("Handshake failed: ".concat(responseDetails.status, " ").concat(responseDetails.statusText, "\n\nData:\n").concat(responseDetails.responseText)); } function handshakeBatch() { handshake(acceptHandshakeBatch); } (function () { if (!ui.isEnabled) { return; } ui.hookUpScrobbleNow(startScrobble); ui.hookUpScrobbleThen(handshakeBatch); window.addEventListener("beforeunload", confirmBrowseAway, true); })();