ScoreSaber OneClick install button

Add OneClick Install for BeatSaber songs on ScoreSaber

  1. // ==UserScript==
  2. // @name ScoreSaber OneClick install button
  3. // @namespace https://github.com/Invertex/
  4. // @version 1.4
  5. // @description Add OneClick Install for BeatSaber songs on ScoreSaber
  6. // @author Invertex
  7. // @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
  8. // @match *://scoresaber.com/*
  9. // @require http://code.jquery.com/jquery-3.4.1.min.js
  10. // @grant GM.xmlHttpRequest
  11. // @connect beatsaver.com
  12. // ==/UserScript==
  13.  
  14. const ocLabel = "OneClick™ Install";
  15.  
  16. let styles = `
  17. .ocListButton {
  18. height: 3.1em;
  19. font-size: 0.6em;
  20. font-weight: 500;
  21. padding-top: 0px;
  22. padding-bottom: 0px;
  23. vertical-align: top;
  24. }
  25.  
  26. .ocLoader {
  27. border: 0.4em solid #f3f3f3; /* Light grey */
  28. border-top: 0.4em solid #ffde1a; /* ScoreSaber Yellow */
  29. border-radius: 50%;
  30. width: 1.6em;
  31. height: 1.6em;
  32. animation: ocLoaderSpin 1s linear infinite;
  33. margin: auto;
  34. }
  35.  
  36. @keyframes ocLoaderSpin {
  37. 0% { transform: rotate(0deg); }
  38. 100% { transform: rotate(360deg); }
  39. }`
  40.  
  41. var styleSheet = document.createElement("style")
  42. styleSheet.type = "text/css"
  43. styleSheet.innerText = styles
  44. document.head.appendChild(styleSheet);
  45.  
  46. (function() {
  47. 'use strict';
  48.  
  49. let location = window.location.href;
  50.  
  51. if(location.includes("leaderboard"))
  52. {
  53. let songInfoElem = document.querySelector("html body div.section div.container div.content div.columns.is-desktop.is-flex-reverse div.column.is-one-third-desktop div.box.has-shadow");
  54. let boldElems = songInfoElem.getElementsByTagName("B");
  55.  
  56. if(boldElems.length > 0)
  57. {
  58. let songIDElem = boldElems[boldElems.length - 1];
  59. GetOneClickLink(songIDElem.innerText, songIDElem, SetupOneClickButton);
  60. }
  61. }
  62. else //Not leaderboard, check if song listings and if so add download buttons on each
  63. {
  64. let songList = document.querySelector("div.ranking.songs table.ranking.songs tbody");
  65. if(songList != null)
  66. {
  67. let songs = songList.getElementsByTagName("tr");
  68. for(var i = 0; i < songs.length; i++)
  69. {
  70. ProcessSongListEntry(songs[i]);
  71. }
  72. }
  73. }
  74. })();
  75.  
  76. function DownloadSong(songUrl)
  77. {
  78. window.location = songUrl;
  79. }
  80.  
  81. function SwapElementVisibility(elem1, elem2)
  82. {
  83. let vis1 = elem1.style.display;
  84. elem1.style.display = elem2.style.display;
  85. elem2.style.display = vis1;
  86. }
  87.  
  88. function ProcessSongListEntry(songElem)
  89. {
  90. let songID = songElem.querySelector("td.song img").src;
  91. songID = songID.substr(songID.lastIndexOf('/') + 1, 40);
  92. let starElem = songElem.querySelector("td.stars");
  93.  
  94. let column = document.createElement("td");
  95. let button = document.createElement("button");
  96. let loader = document.createElement("div");
  97. button.innerText = ocLabel;
  98. button.className = "ocListButton";
  99. loader.className = "ocLoader";
  100. loader.style.display = "none";
  101. column.appendChild(button);
  102. column.appendChild(loader);
  103. songElem.appendChild(column);
  104.  
  105. button.addEventListener("click",
  106. function()
  107. {
  108. GetOneClickLink(songID,
  109. starElem,
  110. DownloadSong,
  111. function() { SwapElementVisibility(button, loader); },
  112. function() { SwapElementVisibility(button, loader); },
  113. )
  114. }, false);
  115. }
  116.  
  117. function GetOneClickLink(songID, modifyElement, onLoadResponse, onStartFunction, onFinishFunction)
  118. {
  119. if(onStartFunction != null) { onStartFunction();}
  120.  
  121. GM.xmlHttpRequest({
  122. method: "GET",
  123. url: "https://beatsaver.com/api/search/text/0?q=" + songID,
  124. responseType: "json",
  125. headers: { "Content-type" : "application/json" },
  126.  
  127. onload: function(e)
  128. {
  129. let results = e.response;
  130.  
  131. if(results.totalDocs > 0)
  132. {
  133. var key = results.docs[0].key;
  134. onLoadResponse("beatsaver://" + key, modifyElement, key);
  135. if(onFinishFunction != null) { onFinishFunction() };
  136. }
  137. },
  138. onerror: function(e) { if(onFinishFunction) { onFinishFunction();} },
  139. onabort: function(e) { if(onFinishFunction) { onFinishFunction();} },
  140. ontimeout: function(e) { if(onFinishFunction) { onFinishFunction();} }
  141. });
  142. }
  143.  
  144. function SetupOneClickButton(clickUrl, idElem, key)
  145. {
  146. var button = document.createElement("button");
  147. button.innerText = ocLabel;
  148. button.addEventListener("click", function() { DownloadSong(clickUrl) }, false);
  149. idElem.parentNode.appendChild(button);
  150. $(idElem).wrap("<a href='http://beatsaver.com/beatmap/" + key + "'></a>");
  151. }