Songsterr Plus Patcher

Trick Songsterr to unlock plus features.

  1. // ==UserScript==
  2. // @name Songsterr Plus Patcher
  3. // @namespace https://github.com/Strikeless
  4. // @version 1.2.1
  5. // @description Trick Songsterr to unlock plus features.
  6. // @license The Unlicense
  7. // @supportURL https://github.com/Strikeless/SongsterrPlusPatcher
  8. // @match http*://*.songsterr.com/*
  9. // @run-at document-start
  10. // @grant unsafeWindow
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. function notifyError(err) {
  15. alert(
  16. "Songsterr Plus Patcher encountered an error."
  17. + "\nfeel free to report this issue at https://github.com/Strikeless/SongsterrPlusPatcher ."
  18. + "\nIf the issue persists, you can disable the userscript and try some of the other userscripts for Songsterr on Greasyfork."
  19. + "\n (alternatively, consider subscribing Songsterr plus, if you have the money to throw and you enjoy their service.)"
  20. + "\n\n" + err
  21. );
  22. }
  23.  
  24. try {
  25. const fetchParent = unsafeWindow || window; // unsafeWindow required to wrap the fetch function in the same context that the actual site uses.
  26. const innerFetch = fetchParent.fetch;
  27.  
  28. function mockProfile(profile) {
  29. if (profile.plan == "plus") {
  30. console.log("Songsterr Plus Patcher: You already have Songsterr plus!");
  31. return profile;
  32. }
  33.  
  34. profile.plan = "plus";
  35. profile.subscription = {
  36. plan: {
  37. id: "plus"
  38. }
  39. };
  40.  
  41. return profile;
  42. }
  43.  
  44. /*
  45. * Wrap the fetch function in our own version that intercepts requests to the profile detail endpoint, mocking plus status.
  46. */
  47. function interceptingFetch(resource, options) {
  48. var resource_url = JSON.stringify(resource); // Not really sure if JSON.stringify is the right tool for the job, but it works. (unlike toString)
  49.  
  50. if (resource_url.includes("/auth/profile")) {
  51. console.log("Songsterr Plus Patcher: Intercepting /auth/profile request to " + resource_url + ".");
  52.  
  53. return innerFetch(resource, options)
  54. .then(response => response.json())
  55. .then(responseProfile => mockProfile(responseProfile))
  56. .then(mockedProfile => new Response(JSON.stringify(mockedProfile)))
  57. .catch(err => notifyError(err));
  58. } else {
  59. return innerFetch(resource, options);
  60. }
  61. }
  62.  
  63. Object.defineProperty(fetchParent, "fetch", {
  64. value: function () {
  65. return interceptingFetch(...arguments);
  66. },
  67. configurable: true,
  68. enumerable: false,
  69. writable: true,
  70. });
  71.  
  72. document.addEventListener("DOMContentLoaded", () => {
  73. try {
  74. /*
  75. * Additionally change user.hasPlus to true and user.profile.plan to "plus" in the state JSON.
  76. */
  77. const stateElement = document.getElementById("state");
  78. const stateJson = JSON.parse(stateElement.innerHTML);
  79.  
  80. stateJson.user.hasPlus = true;
  81. if (stateJson.user.profile != null) {
  82. stateJson.user.profile.plan = "plus";
  83. } else {
  84. // Logged out user, faking a whole profile here to circumvent problems.
  85. stateJson.user.profile = {
  86. id: 100000000,
  87. uid: 100000000,
  88. email: "fakeforplus@example.com",
  89. name: "fakeforplus",
  90. plan: "plus",
  91. permissions: [],
  92. subscription: null,
  93. sra_license: "none",
  94. bonus: {
  95. activeStart: null,
  96. activeEnd: null,
  97. balance: 0,
  98. balanceMinutes: 0
  99. },
  100. bonusPurchasedFeatures: [],
  101. signature: "invalid_signature_with_no_purpose_other_than_to_exist",
  102. created_at: "2025-00-00T00:00:00.000Z",
  103. last_signin_date: "2025-00-00T00:00:00.000Z",
  104. hadPlusBeforeSE: false,
  105. password_change_required: false,
  106. preferencesNotifications: {
  107. notificationsEmails: false,
  108. researchEmails: false
  109. }
  110. };
  111. }
  112. stateElement.innerHTML = JSON.stringify(stateJson);
  113.  
  114. /*
  115. * For some reason when reloading or opening a tab directly via URL, the tab viewer doesn't load the actual tablature.
  116. * This attempts to fix the issue by removing the parent apptab element, hopefully resulting in the site recreating it with the tablature.
  117. */
  118. if (document.getElementById("tablature") == null) {
  119. console.log("Songsterr Plus Patcher: tablature element doesn't exist, attempting to fix by removing entire apptab element and letting site recreate it.");
  120. document.getElementById("apptab").remove();
  121. }
  122. } catch (err) {
  123. notifyError(err);
  124. }
  125. });
  126. } catch (err) {
  127. notifyError(err);
  128. }
  129. })();