├── README.md └── osugame_func+.user.js /README.md: -------------------------------------------------------------------------------- 1 | # osugame_func+ 2 | Userscript adding osu! related functionality to /r/osugame 3 | 4 | ## Features 5 | * Turns URLs in user flairs to clickable links 6 | * Displays player name and pp/rank on flair hover 7 | * Adds live twitch streams to the sidebar 8 | * Adds song preview buttons to beatmap /s/ links 9 | * Adds Bloodcat preview buttons to beatmap /b/ links 10 | * Converts beatmap download links to chosen mirror (osu!direct or Bloodcat) 11 | 12 | ## Screenshots 13 |    14 | 15 | ## Installation 16 | 1. You will need a userscript manager add-on for your browser such as Greasemonkey/Scriptish for Firefox, or Tampermonkey for Chrome/Chromium. 17 | 2. To install this script, [click here](https://github.com/v0x76/osugame_funcp/raw/master/osugame_func+.user.js) or open the raw data of the script. 18 | 3. Done! For updates, the same link can be opened from the User Script Commands menu, within the menu of your userscript add-on (click on the icon). 19 | 20 | ## Other info 21 | You can toggle features in the User Script Commands menu of your userscript manager add-on. Here you can also download the latest version of the script. 22 | -------------------------------------------------------------------------------- /osugame_func+.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name osugame_func+ 3 | // @namespace /r/osugame 4 | // @author /u/N3G4 5 | // @description Adds osu! related functionality to /r/osugame 6 | // @include *reddit.com/r/osugame* 7 | // @version 1.5.10 8 | // @require https://openuserjs.org/src/libs/sizzle/GM_config.js 9 | // @run-at document-end 10 | // @grant GM_openInTab 11 | // @grant GM_addStyle 12 | // @grant GM_registerMenuCommand 13 | // @grant GM_getValue 14 | // @grant GM_setValue 15 | // @grant GM_log 16 | // @grant GM_xmlhttpRequest 17 | // ==/UserScript== 18 | 19 | // ~ ~~ Features ~~ ~ 20 | // * Turns URLs in user flairs to clickable links 21 | // * Displays player name and pp/rank on flair hover 22 | // * Adds live twitch streams to the sidebar 23 | // * Adds song preview buttons to beatmap /s/ links 24 | // * Adds Bloodcat preview buttons to beatmap /b/ links 25 | // * Converts beatmap download links to chosen mirror 26 | // 27 | // ~ ~~~~ ~ 28 | 29 | var githubURL = "https://github.com/v0x76/osugame_funcp"; 30 | var mirrors = { 31 | "osu!direct": "osu://dl/", 32 | "Bloodcat": "http://bloodcat.com/osu/s/" 33 | }; 34 | var twitchkey = "nerivr8xh8fff696oyomj0ghlxqnvtb"; 35 | 36 | var g_flairs, g_streams, g_refreshstreams, g_refreshrate, g_songs, g_downloadmirror, g_parallax, g_debug; 37 | 38 | var loadingimg = "data:image/gif;base64,R0lGODlhCQAJAIAAAP///////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgABACwAAAAACQAJAAACCYyPBpvtD6OMBQAh+QQJCgABACwAAAAACQAJAAACCoyPCKl866KctAAAIfkECQoAAQAsAAAAAAkACQAAAgqMj6mrwL4gm7MAACH5BAkKAAEALAAAAAAJAAkAAAIJjI+py73wogsFACH5BAkKAAEALAAAAAAJAAkAAAIJjI+py+0P4gsFACH5BAkKAAEALAAAAAAJAAkAAAIKjI+pyx3QIJPOFAAh+QQJCgABACwAAAAACQAJAAACCoyPqQsbzZycqwAAIfkECQoAAQAsAAAAAAkACQAAAgqMjwapHOuinJQVADs="; 39 | 40 | function setupConfig() { 41 | GM_config.init({ 42 | "id": "ofpconf", 43 | "title": "osugame_func+ settings", 44 | "fields": 45 | { 46 | "flairs": { 47 | "label": "Player info fetching", 48 | "type": "checkbox", 49 | "default": true 50 | }, 51 | 52 | "streams": { 53 | "label": "Live twitch streams", 54 | "type": "checkbox", 55 | "default": true 56 | }, 57 | 58 | "refreshstreams": { 59 | "label": "Automatically refresh stream list", 60 | "type": "checkbox", 61 | "default": false 62 | }, 63 | 64 | "refreshrate": { 65 | "label": "Stream list refresh rate (seconds)", 66 | "type": "int", 67 | "min": 1, 68 | "max": 600, 69 | "default": 20 70 | }, 71 | 72 | "songs": { 73 | "label": "Add preview buttons to beatmap links", 74 | "type": "checkbox", 75 | "default": true 76 | }, 77 | 78 | "downloadmirror": { 79 | "label": "Beatmap download links", 80 | "type": "select", 81 | "options": ["Default"].concat(Object.keys(mirrors)), 82 | "default": "Default" 83 | }, 84 | 85 | "parallax": { 86 | "label": "Parallax effect in header", 87 | "type": "checkbox", 88 | "default": true 89 | }, 90 | 91 | "debug": { 92 | "label": "Debug mode", 93 | "type": "checkbox", 94 | "default": false 95 | } 96 | }, 97 | "css": "#ofpconf { background-color: #F6F6FE !important; }" + 98 | "#ofpconf .config_header { color: #369 !important; }" 99 | }); 100 | 101 | g_flairs = GM_config.get("flairs"); 102 | g_streams = GM_config.get("streams"); 103 | g_refreshstreams = GM_config.get("refreshstreams"); 104 | g_refreshrate = GM_config.get("refreshrate"); 105 | g_songs = GM_config.get("songs"); 106 | g_downloadmirror = GM_config.get("downloadmirror"); 107 | g_parallax = GM_config.get("parallax"); 108 | g_debug = GM_config.get("debug"); 109 | 110 | GM_registerMenuCommand("Open settings", function(){ GM_config.open(); }, "s"); 111 | GM_registerMenuCommand("Open github repo", function(){ GM_openInTab(githubURL); }, "g"); 112 | GM_registerMenuCommand("DL latest version of script", 113 | function(){ GM_openInTab(githubURL + "/raw/master/osugame_func+.user.js"); }, "v"); 114 | } 115 | 116 | function makeStylesheet() { 117 | GM_addStyle( 118 | "#ofpconf { border-radius: 2px !important; border-color: #DDD !important; " + 119 | "box-shadow: 0 3px 12px rgba(85, 85, 85, 0.1); }" + 120 | "#ofp-infobox { position: absolute; padding: 2px 5px; " + 121 | "background-color: #A9A9FF; opacity: 0.9; color: #FFF; " + 122 | "font-size: 11px; }" + 123 | "#ofp-infobox::before { content: ''; position: absolute; " + 124 | "bottom: -8px; font-size: 0; opacity: 0.9; " + 125 | "border-style: solid; border-width: 4px; " + 126 | "border-color: #A9A9FF transparent transparent; }" + 127 | ".ofp-streaminfo { width: 100%; min-height: 45px; " + 128 | "margin: 4px 0; padding: 2px; background: #EEF; " + 129 | "color: #888; }" + 130 | ".ofp-streaminfo img { float: left; margin-right: 4px; }" + 131 | ".ofp-streaminfo strong { color: #369; font-size: 12px; }" + 132 | ".ofp-streaminfo p { margin: 3px 0 0 !important; }" + 133 | ".ofp-streaminfo span { color: #333; }" + 134 | ".ofp-preview { cursor: pointer; font-size: 0.7em; }" + 135 | "#ofp-iframe { border: solid 1px #DDD; border-radius: 2px; " + 136 | "box-shadow: 0 3px 12px rgba(85, 85, 85, 0.1); }" + 137 | "#ofp-iframe-btn { float: right; padding: 1px 4px 1px 6px; " + 138 | "background-color: #F6F6FE; border-style: solid; " + 139 | "border-width: 1px 1px 1px 0; border-color: #DDD; " + 140 | "border-radius: 0 2px 2px 0; font-size: 13px; font-weight: 600; " + 141 | "color: #222; box-shadow: 0 3px 12px rgba(85, 85, 85, 0.1); " + 142 | "cursor: pointer; }" 143 | ); 144 | } 145 | 146 | // take sum of offsets up the node tree 147 | function getXYPos(element) { 148 | var leftval = 0; 149 | var topval = 0; 150 | while(element) { 151 | leftval += element.offsetLeft; 152 | topval += element.offsetTop; 153 | element = element.offsetParent; 154 | } 155 | 156 | return { 157 | x: leftval, 158 | y: topval 159 | }; 160 | } 161 | 162 | function Flairbox() { 163 | var timer; 164 | var waiting = false; 165 | 166 | var clickify = function(flair) { 167 | var flairtext = flair.innerHTML; 168 | flair.innerHTML = flairtext.link(flairtext); 169 | flair.firstChild.title = ""; // prevent tooltip getting in the way 170 | }; 171 | 172 | var createInfoBox = function() { 173 | var html = "
"; 174 | var newelement = document.createElement("div"); 175 | newelement.innerHTML = html; 176 | document.getElementsByTagName("body")[0].appendChild(newelement.firstChild); 177 | }; 178 | 179 | var setHover = function(flair) { 180 | var box = document.getElementById("ofp-infobox"); 181 | flair.firstChild.onmouseover = function(){ 182 | timer = setTimeout(showInfoBox, 200, box, flair); 183 | }; 184 | flair.firstChild.onmouseout = function(){ 185 | clearTimeout(timer); 186 | hideInfoBox(box, flair); 187 | }; 188 | }; 189 | 190 | var showInfoBox = function(box, flair) { 191 | waiting = true; 192 | 193 | reposInfoBox(box, flair); 194 | 195 | var loadel = document.createElement("img"); 196 | loadel.src = loadingimg; 197 | box.innerHTML = loadel.outerHTML; 198 | box.style.display = "block"; // display info box 199 | 200 | GM_xmlhttpRequest({ 201 | method: "GET", 202 | url: flair.firstChild.innerHTML, 203 | onload: function(response){ onUserpageLoad(response, box, flair); }, 204 | onerror: function(){ console.error("Error on HTTP GET request."); } 205 | }); 206 | }; 207 | 208 | var hideInfoBox = function(box) { 209 | waiting = false; 210 | box.style.display = "none"; // hide info box 211 | }; 212 | 213 | var onUserpageLoad = function(response, box, flair) { 214 | var resdom = document.createElement("html"); 215 | resdom.innerHTML = response.responseText; 216 | 217 | var playernameel = resdom.getElementsByClassName("profile-username"); 218 | if(playernameel.length === 0) { 219 | box.innerHTML = "Player not found"; 220 | return; 221 | } else { 222 | var playername = playernameel[0].innerHTML; 223 | } 224 | 225 | var idregex = /^[0-9]+$/; 226 | var userid = response.finalUrl.split("/").pop(); 227 | // need userId to grab rank data 228 | if(userid.search(idregex) === -1) { 229 | // grab from javascript variable 230 | userid = resdom.children[1].getElementsByTagName("script")[0] 231 | .innerHTML.split(";\n")[0].split("= ")[1]; 232 | 233 | // second route 234 | if(userid.search(idregex) === -1) { 235 | // grab from avatar filename 236 | var avatar = resdom.getElementsByClassName("avatar-holder")[0]; 237 | if(avatar) { 238 | userid = avatar.firstChild.src.split("_")[0].split("/").pop(); 239 | } 240 | 241 | // third route 242 | if(!avatar || userid.search(idregex) === -1) { 243 | // grab from friend button 244 | userid = resdom.getElementsByClassName("centrep")[1] 245 | .firstElementChild.getAttribute("href").split(/\/|\?/)[2]; 246 | } 247 | } 248 | } 249 | 250 | var statsurl = "https://osu.ppy.sh/pages/include/profile-general.php?u=" + 251 | userid + "&m=0"; 252 | 253 | if(waiting) { 254 | GM_xmlhttpRequest({ 255 | method: "GET", 256 | url: statsurl, 257 | onload: function(response){ onStatsLoad(response, box, playername); }, 258 | onerror: function(){ console.error("Error on HTTP GET request"); } 259 | }); 260 | } 261 | }; 262 | 263 | var onStatsLoad = function(response, box, infostring) { 264 | if(waiting) { 265 | var resdom = document.createElement("html"); 266 | resdom.innerHTML = response.responseText; 267 | 268 | var playerrank = resdom.getElementsByClassName("profileStatLine")[0] 269 | .firstElementChild.innerHTML.split(": ")[1]; 270 | 271 | infostring = infostring + " | " + playerrank; 272 | box.innerHTML = infostring; 273 | box.style.display = "block"; // display info box 274 | } 275 | }; 276 | 277 | var reposInfoBox = function(box, element) { 278 | const { x, y } = getXYPos(element); 279 | box.style.left = x+"px"; 280 | box.style.top = y-20+"px"; 281 | }; 282 | 283 | createInfoBox(); 284 | 285 | var allflairs = document.getElementsByClassName("flair"); 286 | if(g_debug) console.log("Found " + allflairs.length + " flairs."); 287 | 288 | var flairs = []; 289 | for(let i=allflairs.length-1; i>0; i--) { 290 | var flairtext = allflairs[i].innerHTML; 291 | 292 | // select only flairs with valid URLs 293 | if( flairtext.search("^https?://") !== -1 ) { 294 | if(g_debug) console.log("Found URL: " + flairtext); 295 | flairs.push(allflairs[i]); 296 | clickify(allflairs[i]); 297 | if( g_flairs && flairtext.search("^https?://osu\.ppy\.sh/u/") !== -1 ) { 298 | setHover(allflairs[i]); 299 | } 300 | } 301 | } 302 | 303 | } 304 | 305 | function Streambox() { 306 | var timer; 307 | var refreshing = true; 308 | 309 | var makeSidebarBox = function() { 310 | var refreshtext; 311 | if(g_refreshstreams) { 312 | refreshtext = "stop"; 313 | } else { 314 | refreshtext = "refresh"; 315 | } 316 | 317 | var html = 318 | "" + 323 | "More
" + 403 | streaminfo[i].username + " / " + 404 | streaminfo[i].viewercount + " viewers" + 405 | "