Songsterr Plus Patcher

Trick Songsterr to unlock plus features.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name Songsterr Plus Patcher
// @namespace https://github.com/Strikeless
// @version 1.2.1
// @description Trick Songsterr to unlock plus features.
// @license The Unlicense
// @supportURL https://github.com/Strikeless/SongsterrPlusPatcher
// @match http*://*.songsterr.com/*
// @run-at document-start
// @grant unsafeWindow
// ==/UserScript==

(function () {
    function notifyError(err) {
        alert(
            "Songsterr Plus Patcher encountered an error."
            + "\nfeel free to report this issue at https://github.com/Strikeless/SongsterrPlusPatcher ."
            + "\nIf the issue persists, you can disable the userscript and try some of the other userscripts for Songsterr on Greasyfork."
            + "\n    (alternatively, consider subscribing Songsterr plus, if you have the money to throw and you enjoy their service.)"
            + "\n\n" + err
        );
    }

    try {
        const fetchParent = unsafeWindow || window; // unsafeWindow required to wrap the fetch function in the same context that the actual site uses.
        const innerFetch = fetchParent.fetch;

        function mockProfile(profile) {
            if (profile.plan == "plus") {
                console.log("Songsterr Plus Patcher: You already have Songsterr plus!");
                return profile;
            }

            profile.plan = "plus";
            profile.subscription = {
                plan: {
                    id: "plus"
                }
            };

            return profile;
        }

        /*
         * Wrap the fetch function in our own version that intercepts requests to the profile detail endpoint, mocking plus status.
         */
        function interceptingFetch(resource, options) {
            var resource_url = JSON.stringify(resource); // Not really sure if JSON.stringify is the right tool for the job, but it works. (unlike toString)

            if (resource_url.includes("/auth/profile")) {
                console.log("Songsterr Plus Patcher: Intercepting /auth/profile request to " + resource_url + ".");

                return innerFetch(resource, options)
                    .then(response => response.json())
                    .then(responseProfile => mockProfile(responseProfile))
                    .then(mockedProfile => new Response(JSON.stringify(mockedProfile)))
                    .catch(err => notifyError(err));
            } else {
                return innerFetch(resource, options);
            }
        }

        Object.defineProperty(fetchParent, "fetch", {
            value: function () {
                return interceptingFetch(...arguments);
            },
            configurable: true,
            enumerable: false,
            writable: true,
        });

        document.addEventListener("DOMContentLoaded", () => {
            try {
                /*
                 * Additionally change user.hasPlus to true and user.profile.plan to "plus" in the state JSON.
                 */
                const stateElement = document.getElementById("state");
                const stateJson = JSON.parse(stateElement.innerHTML);

                stateJson.user.hasPlus = true;
                if (stateJson.user.profile != null) {
                    stateJson.user.profile.plan = "plus";
                } else {
                    // Logged out user, faking a whole profile here to circumvent problems.
                    stateJson.user.profile = {
                        id: 100000000,
                        uid: 100000000,
                        email: "[email protected]",
                        name: "fakeforplus",
                        plan: "plus",
                        permissions: [],
                        subscription: null,
                        sra_license: "none",
                        bonus: {
                            activeStart: null,
                            activeEnd: null,
                            balance: 0,
                            balanceMinutes: 0
                        },
                        bonusPurchasedFeatures: [],
                        signature: "invalid_signature_with_no_purpose_other_than_to_exist",
                        created_at: "2025-00-00T00:00:00.000Z",
                        last_signin_date: "2025-00-00T00:00:00.000Z",
                        hadPlusBeforeSE: false,
                        password_change_required: false,
                        preferencesNotifications: {
                            notificationsEmails: false,
                            researchEmails: false
                        }
                    };
                }
                stateElement.innerHTML = JSON.stringify(stateJson);

                /*
                 * For some reason when reloading or opening a tab directly via URL, the tab viewer doesn't load the actual tablature.
                 * This attempts to fix the issue by removing the parent apptab element, hopefully resulting in the site recreating it with the tablature.
                 */
                if (document.getElementById("tablature") == null) {
                    console.log("Songsterr Plus Patcher: tablature element doesn't exist, attempting to fix by removing entire apptab element and letting site recreate it.");
                    document.getElementById("apptab").remove();
                }
            } catch (err) {
                notifyError(err);
            }
        });
    } catch (err) {
        notifyError(err);
    }
})();