// ==UserScript== // @name 获取Akatsuki玩家的第一名成绩 // @name:en AkatsukiFirstScoresUserpage // @namespace http://tampermonkey.net/ // @version 1.1.1 // @description 给Akatsuki网页添加用户界面第一名的显示 // @description:en Add first scores for Akatsuki Userpage // @author TROU2004 // @match https://akatsuki.pw/*u/* // @connect akatsuki.pw // @grant GM_xmlhttpRequest // @downloadURL https://update.greasyfork.icu/scripts/397211/%E8%8E%B7%E5%8F%96Akatsuki%E7%8E%A9%E5%AE%B6%E7%9A%84%E7%AC%AC%E4%B8%80%E5%90%8D%E6%88%90%E7%BB%A9.user.js // @updateURL https://update.greasyfork.icu/scripts/397211/%E8%8E%B7%E5%8F%96Akatsuki%E7%8E%A9%E5%AE%B6%E7%9A%84%E7%AC%AC%E4%B8%80%E5%90%8D%E6%88%90%E7%BB%A9.meta.js // ==/UserScript== const API_SCORES_FIRST = "https://akatsuki.pw/api/v1/users/scores/first" const SAYOBOT_DOWNLOAD = "https://osu.sayobot.cn/osu.php?s=" const AKATSUKI_REPLAY_DOWNLOAD = "https://akatsuki.pw/web/replays/" var scores var firstScoresTable var eventTimes = 0 var currentPage = 1 var fsNumberPage = 0 var fsNumber = 0 window.addEventListener("load", function() { eventTimes++ if (eventTimes == 2) { return } this.setTimeout(function() {pullScores(1, true);}, 3000); this.setTimeout(function() {beginGetFSNum();}, 3000); }, false) function pullScores(page, apply) { //根据位置生成Request时所需的参数 var args = "?mode=0&l=20&rx=1&id=" + getUserID() + "&p=" + page //开始Request GM_xmlhttpRequest({ method: "GET", url: API_SCORES_FIRST + args, timeout: 5000, onload: function(response) { scores = JSON.parse(response.responseText).scores if (apply) { pushScoresToTable() } } }); } function pushScoresToTable() { if (scores == null) { return } if (firstScoresTable == null) { createTable() } var tBody = firstScoresTable.getElementsByTagName("tbody")[0] for (let score of scores) { var currentTr = document.createElement("tr") var tdScore = document.createElement("td") var tdPP = document.createElement("td") fillTable(score, tdScore, tdPP) //将Score填充到两个td里面 currentTr.appendChild(tdScore) currentTr.appendChild(tdPP) currentTr.className = "score-row" tBody.appendChild(currentTr) } } function fillTable(score, tdScore, tdPP) { //填充tdPP var divPP = document.createElement("div") tdPP.appendChild(divPP) divPP.style.cssText = "display: inline-flex;justify-content: left;align-items: left;position: relative;bottom: auto;padding: 5px 20px 5px 0px;min-width: 100px;flex: none;font-size: 16px;font-weight: 700;" var spanPP = document.createElement("span") divPP.appendChild(spanPP) var spanPPText = document.createElement("span") spanPPText.style.color = "#bfbfbf" spanPPText.innerText = "pp" spanPPText.style.fontSize = "12px" spanPP.title = "Combo: " + score.max_combo + "/" + score.beatmap.max_combo spanPP.innerHTML = score.pp.toFixed(2) + spanPPText.outerHTML spanPP.style.color = "#f2f2f2" var atextDLMap = document.createElement("a") divPP.appendChild(atextDLMap) atextDLMap.className = "downloadstar" atextDLMap.innerHTML = "DLMap" atextDLMap.style.right = "-70px" atextDLMap.style.position = "absolute" atextDLMap.href = SAYOBOT_DOWNLOAD + score.beatmap.beatmapset_id $(document).keydown(function(event) { if (event.keyCode == 17 && atextDLMap.getAttribute("scoreMode") != "true") { atextDLMap.setAttribute("scoreMode", "true") atextDLMap.href = AKATSUKI_REPLAY_DOWNLOAD + score.id atextDLMap.innerHTML = "DLRep" } }) $(document).keyup(function(event) { if (event.keyCode == 17) { atextDLMap.setAttribute("scoreMode", "false") atextDLMap.href = SAYOBOT_DOWNLOAD + score.beatmap.beatmapset_id atextDLMap.innerHTML = "DLMap" } }) //填充tdScore var divScore = document.createElement("div") tdScore.appendChild(divScore) var imgScoreIcon = document.createElement("img") //Grade Icon divScore.appendChild(imgScoreIcon) imgScoreIcon.src = getRankIcon(score.rank) imgScoreIcon.style.cssText = "float: left;display: block;margin-right: 10px;flex: none;width: 20px; position: relative;top: 8px" var divScoreTitle = document.createElement("div") //Title time and difficult divScoreTitle.style.float = "left" divScore.appendChild(divScoreTitle) var atextText = document.createElement("a") //Title divScoreTitle.appendChild(atextText) atextText.href = "https://akatsuki.pw/b/" + score.beatmap.beatmap_id atextText.style.cssText = "color: #fff;font-size: 15px;display: block;align-self: flex-start;max-width: 100%;" var titles = getTitles(score.beatmap.song_name) atextText.innerText = atextText.innerHTML = titles[0] + " " var atextB = atextText.cloneNode(true) atextB.style.fontWeight = "bolder" atextB.style.cssText = "color: #fff;font-size: 15px;max-width: 100%;font-weight: bolder;" atextB.innerText = getScoreMods(score.mods) atextText.appendChild(atextB) var spanDifficult = document.createElement("span") //Difficult divScoreTitle.appendChild(spanDifficult) spanDifficult.innerText = titles[1] spanDifficult.style.color = "#fedfe1" spanDifficult.style.marginTop = "2px" spanDifficult.style.position = "relative" spanDifficult.title = "Star: " + score.beatmap.difficulty.toFixed(2) + "\nAR: " + score.beatmap.ar + "\nOD: " + score.beatmap.od spanDifficult.addEventListener("click", function() { copyToClipboard(score.beatmap.beatmap_id) var that = this; shaking(that,'top') shaking(that,'left') }, false) var spanTime = document.createElement("span") divScoreTitle.appendChild(spanTime) spanTime.innerText = new Date(score.time).toLocaleString('chinese', { hour12: false }); spanTime.title = score.time spanTime.style.marginTop = "2px" spanTime.style.color = "#BDC0BA" spanTime.style.position = "relative" spanTime.style.right = "-20px" spanTime.style.fontWeight = "bold" //Acc var spanAcc = document.createElement("span") var divAcc = document.createElement("div") divAcc.style.cssText = divPP.style.cssText divAcc.style.position = "absolute" divAcc.style.right = "200px" divAcc.appendChild(spanAcc) divScore.appendChild(divAcc) spanAcc.innerText = score.accuracy.toFixed(2) + "%" spanAcc.style.color = "#EFBF3A" spanAcc.style.fontSize = "16px" spanAcc.style.position = "absolute" spanAcc.style.top = "9px" spanAcc.title = "300: " + score.count_300 + "\n100: " + score.count_100 + "\n50: " + score.count_50 + "\nMiss: " + score.count_miss } function createTable() { //根据RecentScores复制一个新的FirstScore标签 var rsUISegment = document.querySelector("#scores-zone > div:nth-child(1) > div > div:nth-child(2)") var fsUISegment = rsUISegment.cloneNode(true) var parentUISegment = rsUISegment.parentElement parentUISegment.appendChild(fsUISegment) //更改需要的东西 fsUISegment.getElementsByClassName("ui header")[0].innerText = "First Places" //Recent scores -> First Places firstScoresTable = fsUISegment.getElementsByClassName("ui table score-table blue no bottom margin")[0] //获取创建好的Table firstScoresTable.removeAttribute("data-type") //删掉后端支持的data-type="recent" $(firstScoresTable.getElementsByTagName("tbody")[0]).empty() //清空tbody里面的表格 document.querySelector("#scores-zone > div:nth-child(1) > div > div:nth-child(3) > table > thead > tr > th:nth-child(2)").style.width = "18%" //设置下面的按钮 var button = document.querySelector("#scores-zone > div:nth-child(1) > div > div:nth-child(3) > table > tfoot > tr > th > div > a") if (scores.length < 20) { button.remove() return } button.addEventListener("click", function() { currentPage++ if (scores == null || scores.length < 20) { button.remove() return } pullScores(currentPage, true) }) //使表格下方按钮使用正常 } function getUserID() { return document.querySelector("#rx-menu > a.\\30 .item").href.substring(22) } function getTitles(song_name) { var titles = new Array(2) titles[0] = song_name.substring(0,song_name.indexOf("[") - 1) titles[1] = song_name.substring(titles[0].length + 2, song_name.length - 1) return titles } function getRankIcon(grade) { var str = grade if (grade == "SH") { str = "SHD" } if (grade == "SSH") { str = "SSHD" } return "https://akatsuki.pw/static/ranking-icons/" + str + ".png" } function beginGetFSNum() { fsNumberPage++ //根据位置生成Request时所需的参数 var args = "?mode=0&l=50&rx=1&id=" + getUserID() + "&p=" + fsNumberPage //开始Request GM_xmlhttpRequest({ method: "GET", url: API_SCORES_FIRST + args, timeout: 5000, onload: function(response) { var fsScores = JSON.parse(response.responseText).scores if (fsScores == null) { callback_getCompleted() // 跳出递归 return } else { fsNumber += fsScores.length beginGetFSNum() //继续递归 } } }); } function callback_getCompleted() { if (fsNumber == 0) { return } var title = document.querySelector("#scores-zone > div:nth-child(1) > div > div:nth-child(3) > h2") if (title == null) { return } title.innerText += " (Total: " + fsNumber + ")" } function copyToClipboard(str) { const input = document.createElement('input'); input.setAttribute('readonly', 'readonly'); input.setAttribute('value', str); document.body.appendChild(input); input.select(); document.execCommand('copy'); console.log(input) document.body.removeChild(input); } //Include //Source code: https://segmentfault.com/a/1190000015993899 // 获取节点对象的样式属性值 function getStyle(obj,attr){ if(obj.currentStyle){ return obj.currentStyle[attr]; }else{ return window.getComputedStyle(obj,false)[attr]; } } //Include //Source code: https://segmentfault.com/a/1190000015993899 function shaking(obj,attr,callback){ var pos = parseInt(getStyle(obj,attr)); var arr = []; var num = 0; for(var i=5;i>0;i-=1){ arr.push(i,-i); } arr.push(0); clearInterval(obj.shaking); obj.shaking = setInterval(function(){ obj.style[attr] = pos + arr[num] + 'px'; num ++; if(num === arr.length){ clearInterval(obj.shaking); callback && callback(); } },50); } //Include from Akatsuki-web //Source code: https://github.com/osuAkatsuki/old-frontend/blob/master/js/user.js function getScoreMods(m) { var r = ''; var hasNightcore = false; if (m & NoFail) { r += 'NF, '; } if (m & Easy) { r += 'EZ, '; } if (m & NoVideo) { r += 'NV, '; } if (m & Hidden) { r += 'HD, '; } if (m & HardRock) { r += 'HR, '; } if (m & SuddenDeath) { r += 'SD, '; } if (m & Nightcore) { r += 'NC, '; hasNightcore = true; } if (!hasNightcore && (m & DoubleTime)) { r += 'DT, '; } if (m & Relax) { r += 'RX, '; } if (m & HalfTime) { r += 'HT, '; } if (m & Flashlight) { r += 'FL, '; } if (m & Autoplay) { r += 'AP, '; } if (m & SpunOut) { r += 'SO, '; } if (m & Relax2) { r += 'AP, '; } if (m & Perfect) { r += 'PF, '; } if (m & Key4) { r += '4K, '; } if (m & Key5) { r += '5K, '; } if (m & Key6) { r += '6K, '; } if (m & Key7) { r += '7K, '; } if (m & Key8) { r += '8K, '; } if (m & keyMod) { r += ''; } if (m & FadeIn) { r += 'FD, '; } if (m & Random) { r += 'RD, '; } if (m & LastMod) { r += 'CN, '; } if (m & Key9) { r += '9K, '; } if (m & Key10) { r += '10K, '; } if (m & Key1) { r += '1K, '; } if (m & Key3) { r += '3K, '; } if (m & Key2) { r += '2K, '; } if (r.length > 0) { return "+ " + r.slice(0, -2); } else { return ''; } } var None = 0; var NoFail = 1; var Easy = 2; var NoVideo = 4; var Hidden = 8; var HardRock = 16; var SuddenDeath = 32; var DoubleTime = 64; var Relax = 128; var HalfTime = 256; var Nightcore = 512; var Flashlight = 1024; var Autoplay = 2048; var SpunOut = 4096; var Relax2 = 8192; var Perfect = 16384; var Key4 = 32768; var Key5 = 65536; var Key6 = 131072; var Key7 = 262144; var Key8 = 524288; var keyMod = 1015808; var FadeIn = 1048576; var Random = 2097152; var LastMod = 4194304; var Key9 = 16777216; var Key10 = 33554432; var Key1 = 67108864; var Key3 = 134217728; var Key2 = 268435456;