Greasy Fork

Greasy Fork is available in English.

scRYMble

Visit a release page on rateyourmusic.com and scrobble the songs you see!

当前为 2024-07-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
// ==/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 = "<span style=\"float:left;\"><input type=\"checkbox\" class=\"scrymblechk\" id=\"chktrackNUM\" checked=\"checked\"></span>";
        $.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 = "<table border='0' cellpadding='0' cellspacing='2'><tr><td  width='105' ><input type='checkbox' name='allornone' id='allornone' style='vertical-align:middle' checked='checked'>&nbsp;<label for='allornone' style='font-size:60%'>select&nbsp;all/none</label><br/><table border='2' cellpadding='0' cellspacing='0'><tr><td style='height:50px;width:103px;background:url(http://cdn.last.fm/flatness/logo_black.3.png) no-repeat;color:#fff'><marquee scrollamount='3' scrolldelay='200' behavior='alternate' style='font-size:80%;font-family:sans-serif;position:relative;top:17px' id='scrymblemarquee'>&nbsp;</marquee></td></tr><tr><td style='background-color:#000033'><div style='position:relative;background-color:#ff0000;width:0%;max-height:5px;left:0px;top:0px;' id='progbar'>&nbsp;</div></td></tr></table></td><td>user: <input type='text' size='16' id='scrobbleusername' value = '" + GM_getValue("user", "") + "' /><br />pass: <input type='password' size='16' id='scrobblepassword' value = '" + GM_getValue("pass", "") + "'></input><br /><input type='button' id='scrobblenow' value = 'Scrobble in real-time' /> <input type='button' id='scrobblethen' value = 'Scrobble a previous play' /></td></tr></table>";
        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("<a title=\"[Artist") === 0 && $(title).text().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("&nbsp;");
    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);
})();