您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Upload your Arcaea score to bot.
// ==UserScript== // @name Arcaea Score Update // @namespace arcaea-score-update // @version 1.2.3 // @icon https://chinosk.top/g.jpg // @description Upload your Arcaea score to bot. // @author Chinosk // @match https://arcaea.lowiro.com/* // @grant GM_xmlhttpRequest // @connect * // ==/UserScript== (function() { 'use strict'; let div = document.createElement("div"); div.style.position = "fixed"; div.style.bottom = "30px"; div.style.right = "30px"; div.style.backgroundColor = "#ccc"; div.style.padding = "3px" div.style.maxWidth = "250px" let fromRankRatio = document.createElement("input"); fromRankRatio.type = "radio"; fromRankRatio.name = "inputType"; fromRankRatio.id = "inputFromRankRatio"; fromRankRatio.checked = true; let fromRankLabel = document.createElement("label"); fromRankLabel.htmlFor = "inputFromRankRatio"; fromRankLabel.textContent = "From Rank"; fromRankLabel.style.marginRight = "2px"; let fromPaidRatio = document.createElement("input"); fromPaidRatio.type = "radio"; fromPaidRatio.name = "inputType"; fromPaidRatio.id = "inputFromPaid"; let fromPaidLabel = document.createElement("label") fromPaidLabel.htmlFor = "inputFromPaid"; fromPaidLabel.textContent = "From Purchase"; let syncCookieCheck = document.createElement("input"); syncCookieCheck.type = "checkbox"; syncCookieCheck.id = "syncCookieCheck"; syncCookieCheck.checked = true; let syncCookieLabel = document.createElement("label") syncCookieLabel.htmlFor = "syncCookieCheck"; syncCookieLabel.textContent = "Upload Cookie"; let buttonUpdateMe = document.createElement("button"); buttonUpdateMe.innerHTML = "Update Me"; buttonUpdateMe.style.width = "100%"; buttonUpdateMe.style.height = "40px"; buttonUpdateMe.style.marginBottom = "2px"; let buttonDefaultText = "Update Bests"; let button = document.createElement("button"); button.innerHTML = buttonDefaultText; button.style.width = "100%"; button.style.height = "40px"; button.style.marginBottom = "2px"; let buttonStop = document.createElement("button"); buttonStop.innerHTML = "Stop"; buttonStop.style.width = "100%"; buttonStop.style.height = "40px"; buttonStop.style.marginBottom = "2px"; buttonStop.style.display = "none"; let buttonClose = document.createElement("button"); buttonClose.innerHTML = "Close"; buttonClose.style.width = "100%"; buttonClose.style.height = "40px"; buttonClose.style.marginBottom = "2px"; let querySelfRatio = document.createElement("input"); querySelfRatio.type = "radio"; querySelfRatio.name = "queryType"; querySelfRatio.id = "queryTypeSelf"; querySelfRatio.checked = true; let querySelfLabel = document.createElement("label") querySelfLabel.htmlFor = "queryTypeSelf"; querySelfLabel.textContent = "Update Self Bests"; let queryOthersRatio = document.createElement("input"); queryOthersRatio.type = "radio"; queryOthersRatio.name = "queryType"; queryOthersRatio.id = "queryTypeOthers"; let queryOthersLabel = document.createElement("label") queryOthersLabel.htmlFor = "queryTypeOthers"; queryOthersLabel.textContent = "Update Others Bests"; let queryOtherInput = document.createElement("select"); queryOtherInput.style.width = "100%" queryOtherInput.style.display = "none" div.appendChild(fromRankRatio); div.appendChild(fromRankLabel); div.appendChild(fromPaidRatio); div.appendChild(fromPaidLabel); div.appendChild(document.createElement("br")); div.appendChild(syncCookieCheck); div.appendChild(syncCookieLabel); let syncCookieBr = document.createElement("br"); div.appendChild(syncCookieBr); div.appendChild(querySelfRatio) div.appendChild(querySelfLabel) div.appendChild(document.createElement("br")); div.appendChild(queryOthersRatio) div.appendChild(queryOthersLabel) let queryOthersBr = document.createElement("br"); div.appendChild(queryOthersBr); div.appendChild(queryOtherInput) div.appendChild(buttonUpdateMe); div.appendChild(button); div.appendChild(buttonStop); div.appendChild(buttonClose); let powerLabel = document.createElement("label"); powerLabel.innerHTML = "Powered By @Chinosk"; powerLabel.style.fontSize = "10px"; div.appendChild(powerLabel); document.body.appendChild(div); let stopQueryFlag = false; buttonClose.addEventListener("click", () => { div.style.display = "none"; }) buttonStop.addEventListener("click", () => { stopQueryFlag = true; }) querySelfRatio.addEventListener("change", onQueryRatioChange); queryOthersRatio.addEventListener("change", onQueryRatioChange); fromPaidRatio.addEventListener("change", () => { if (fromPaidRatio.checked) { querySelfRatio.checked = true; onQueryRatioChange(); queryOthersRatio.style.display = "none"; queryOthersLabel.style.display = "none"; queryOthersBr.style.display = "none"; } }) fromRankRatio.addEventListener("change", () => { if (fromRankRatio.checked) { queryOthersRatio.style.display = ""; queryOthersLabel.style.display = ""; queryOthersBr.style.display = ""; } }) function GM_Fetch(details) { let saveResp = null; let det = { method: details.method, redirect: 'follow', credentials: "include" }; if (details.headers) { det.headers = details.headers; } if (details.data) { det.body = details.data; } fetch(details.url, det) .then(response => { saveResp = response; return response.text(); }) .then(result => { let newResp = {}; newResp.responseText = result; newResp.responseHeaders = saveResp.headers; newResp.status = saveResp.status; details.onload(newResp); }) .catch(error => { if (!details.onerror) { console.log("Request error", error); alert("Request error: " + error); } details.onerror(error); }); } const userAgent = navigator.userAgent.toLowerCase(); const isIOS = /iphone|ipad|ipod/.test(userAgent); const notSupportReq = typeof GM_xmlhttpRequest !== 'function'; let RequestFunc; if (isIOS || notSupportReq) { console.log("isIOS", isIOS, "notSupportReq", notSupportReq); syncCookieCheck.checked = false; syncCookieCheck.setAttribute("disabled", "true"); syncCookieLabel.textContent += "(系统限制, 无法同步 Cookie)"; RequestFunc = GM_Fetch; } else { RequestFunc = GM_xmlhttpRequest; } function onQueryRatioChange() { if (queryOthersRatio.checked) { fromRankRatio.checked = true; queryOtherInput.style.display = ""; queryOtherInput.innerHTML = ""; syncCookieCheck.style.display = "none"; syncCookieLabel.style.display = "none"; syncCookieBr.style.display = "none"; RequestFunc({ method: "GET", url: "https://webapi.lowiro.com/webapi/user/me", withCredentials: true, onload: (response) => { let user_me_json = JSON.parse(response.responseText); if (user_me_json.success) { for (const friends of user_me_json.value.friends) { let optionElem = document.createElement('option'); optionElem.value = friends.user_id; optionElem.text = friends.name; queryOtherInput.appendChild(optionElem) } } }, onerror: (response) => { alert("Get friend list failed. " + response.status + " " + response.responseText) } }); } else { queryOtherInput.style.display = "none"; syncCookieCheck.style.display = "" syncCookieLabel.style.display = "" syncCookieBr.style.display = "" } } buttonUpdateMe.addEventListener("click", () => { RequestFunc({ method: "GET", url: "https://webapi.lowiro.com/webapi/user/me", withCredentials: true, onload: (response) => { let user_me_json = JSON.parse(response.responseText); if (user_me_json.success) { uploadUserMeDataToServer(JSON.parse(response.responseText), getSetCookieFromStr(response.responseHeaders)); alert("success."); } }, onerror: (response) => { alert("Get friend list failed. " + response.status + " " + response.responseText) } }); }) button.addEventListener("click", () => { try { button.setAttribute("disabled", "true") RequestFunc({ method: "GET", url: "https://webapi.lowiro.com/webapi/user/me", withCredentials: true, onload: (response) => { let user_me_json = JSON.parse(response.responseText); if (!user_me_json.success) { endQuery(); alert("Get user info failed!"); return; } if (querySelfRatio.checked) { uploadUserMeDataToServer(JSON.parse(response.responseText), getSetCookieFromStr(response.responseHeaders)); if (fromRankRatio.checked) { updateData(user_me_json); } else if (fromPaidRatio.checked) { updateDataFromPurchase(user_me_json); } } else if (queryOthersRatio.checked) { let queryUserId = parseInt(queryOtherInput.value); let friendData = null; for (const friends of user_me_json.value.friends) { if (friends.user_id === queryUserId) { friendData = friends; break; } } if (friendData !== null) { user_me_json.value.rating = friendData.rating; user_me_json.value.user_id = friendData.user_id; user_me_json.value.is_skill_sealed = friendData.is_skill_sealed; user_me_json.value.join_date = friendData.join_date; user_me_json.value.character = friendData.character; user_me_json.value.recent_score = friendData.recent_score; user_me_json.value.name = friendData.name; user_me_json.value.display_name = friendData.name; user_me_json.value.user_id = friendData.user_id; user_me_json.value.user_code = "000000000"; updateData(user_me_json); } else { endQuery(); } } }, onerror: (response) => { endQuery(); } }); } catch (e) { endQuery(); alert(`Error: ${e}`); throw e } }); window.onerror = (message, source, line, column, error) => { endQuery(); const errorMessage = error ? error.message : message; alert(`Error: ${errorMessage}`); console.log(error); }; function endQuery() { button.removeAttribute("disabled"); button.innerHTML = buttonDefaultText; buttonStop.style.display = "none"; } function uploadUserMeDataToServer(user_me_json, cookieSid) { if (!user_me_json.success) { return; } let reqHeaders = { "Content-Type": "application/json" }; if (syncCookieCheck.checked) { reqHeaders["Sync-Sid"] = cookieSid; } RequestFunc({ method: "POST", url: "https://www.chinosk6.cn/arcscore/update/me", headers: reqHeaders, data: JSON.stringify(user_me_json), onload: (response) => { if (response.status !== 200) { throw Error("Upload user info failed."); } } }); } function uploadBestDataToServer(data_json) { console.log("Upload Data", data_json); RequestFunc({ method: "POST", url: "https://www.chinosk6.cn/arcscore/update/bests", headers: { "Content-Type": "application/json" }, data: JSON.stringify(data_json), onload: (response) => { if (response.status !== 200) { throw Error("Upload bests failed."); } else { alert("Upload Success!"); } } }); } function updateDataFromPurchase(user_me_json) { let user_id = user_me_json["value"]["user_id"]; let user_name = user_me_json["value"]["name"]; let user_character = user_me_json["value"]["character"]; let user_is_skill_sealed = user_me_json["value"]["is_skill_sealed"]; // let user_rating = user_me_json["value"]["rating"] RequestFunc({ method: "GET", url: "https://webapi.lowiro.com/webapi/score/rating/me", onload: (response) => { let data = JSON.parse(response.responseText) if ((response.status === 200) && data.success) { let postData = [] for (let i of data["value"]["best_rated_scores"]) { postData.push({ "user_id": user_id, "song_id": i.song_id, "difficulty": i.difficulty, "score": i.score, "shiny_perfect_count": 0, "perfect_count": 0, "near_count": 0, "miss_count": 0, "health": 100, "modifier": i.modifier, "time_played": Math.round(new Date().getTime()), "best_clear_type": i.clear_type, "clear_type": i.clear_type, "name": user_name, "character": user_character, "is_skill_sealed": user_is_skill_sealed, "is_char_uncapped": true, "rank": 1 }) } uploadBestDataToServer(postData); } else { alert(`Get rating failed (${response.status}).\n${response.responseText}`); } endQuery(); }, onerror: (response) => { endQuery(); } }); } function updateData(user_me_json) { let user_id = user_me_json.value.user_id; let user_rating = user_me_json.value.rating; RequestFunc({ method: "GET", url: "https://www.chinosk6.cn/arcscore/get_slst", onload: (response) => { if (response.status !== 200) { throw Error("Get song list failed."); } let songs = JSON.parse(response.responseText) songs.sort((a, b) => b.rating - a.rating); startCalcB30(songs, user_rating / 100, user_id) .then((results) => { if (results !== null) { if (results.length > 0) { button.innerHTML = "Uploading..."; uploadBestDataToServer(results); } } endQuery(); }) } }); } async function startCalcB30(songs, user_rating, user_id) { function calcSongRating(sid, difficultyIndex, score) { let result = 0; for (const songsKey in songs) { if ((songs[songsKey].sid === sid) && (songs[songsKey].difficulty === difficultyIndex)) { const rating = songs[songsKey].rating / 10; if (score < 9800000) { result = rating + (score - 9500000) / 300000 } else if (9800000 <= score <= 10000000) { result = rating + 1 + (score - 9800000) / 200000 } else if (score <= 10020000) { result = rating + 2 } else { result = rating + 1 + (score - 9800000) / 200000 } break; } } if (result < 0) result = 0; return result; } let retData = [] function getRetMinRating (maxCount=40) { retData.sort((a, b) => {return b.rating - a.rating;}); retData = retData.slice(0, maxCount); return retData[retData.length - 1].rating; } let querySongs = [] for (let n in songs) { if (songs[n].rating <= 0) { continue; } querySongs.push(songs[n]); } querySongs.sort((a, b) => {return b.rating - a.rating;}) button.innerHTML = `Query song (0)`; buttonStop.style.display = "" let endPoint = "https://webapi.lowiro.com/webapi/score/song/friend" if (querySelfRatio.checked) { endPoint = "https://webapi.lowiro.com/webapi/score/song/me" } for (let n in querySongs) { let songId = songs[n].sid; let songDifficulty = songs[n].difficulty; if (stopQueryFlag) { buttonStop.style.display = "none"; stopQueryFlag = false; return null; } button.innerHTML = `Query song (${parseInt(n) + 1})...` const response = await new Promise((resolve, reject) => { RequestFunc({ method: "GET", url: `${endPoint}?song_id=${songId}&difficulty=${songDifficulty}&start=0&limit=30`, onload: (response) => { resolve(response); }, onerror: (error) => reject(error), }); }); let currData = getScoreFromFriendRank(response.responseText, user_id); if (currData !== null) { currData.rating = calcSongRating(currData.song_id, currData.difficulty, currData.score) console.log(currData) if (retData.length >= 40) { let minRt = getRetMinRating(); if (querySongs[n].rating / 10 + 2 < minRt) { break; } } retData.push(currData); } } buttonStop.style.display = "none" return retData; } function getScoreFromFriendRank(responseText, user_id) { let data = JSON.parse(responseText); if (data.success) { if (data.value && (data.value.length > 0)) { for (const i of data.value) { if (i.user_id.toString() === user_id.toString()) { return i; } } } } else { console.log("Query bests failed: ", responseText); if (data.error_code === 1) { responseText += "\n访问被拒绝,可能需要订阅 Arcaea Online." } alert(`Query bests failed:\n${responseText}`); stopQueryFlag = true; } return null; } function getSetCookieFromStr(cookieStr) { if (!(typeof cookieStr === "string")) { return null; } let setCookie = cookieStr.match(/set-cookie\s*:\s*([\s\S]+?)\n/i); if (setCookie) { for (let spData of setCookie[1].split(";")) { spData = spData.trim(); if (spData.startsWith("sid=")) { return spData.trim(); } } } return null; } })();