您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Cheeses the AP® Exam scores on the College Board website by allowing modification.
// ==UserScript== // @name AP® Score Cheeser // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description Cheeses the AP® Exam scores on the College Board website by allowing modification. // @author Samathingamajig // @match https://apstudents.collegeboard.org/view-scores* // @icon https://www.google.com/s2/favicons?domain=collegeboard.org // @grant GM_setValue // @grant GM_getValue // AP® is a trademark registered by the College Board, which is not affiliated with, and does not endorse, this product. // ==/UserScript== (async function () { "use strict"; const sidebarBodies = { passing: `<div class="align-self-center"><p class="apscores-intro">Most U.S. colleges accept your score for credit and placement.</p><p><a class="cb-btn cb-btn-black cb-margin-sm-up-right-16 cb-margin-xs-bottom-8" href="https://apstudents.collegeboard.org/getting-credit-placement/search-policies/course/2">Find College Credit</a><a id="score-22-2" class="cb-padding-xs-top-4 display-xs-block-only cb-font-size-small" href="https://apstudents.collegeboard.org/about-ap-scores">About your <span class="sr-only">AP Biology</span> score</a></p></div>`, failing: `<div class="align-self-center"><p class="apscores-intro">You challenged yourself with college-level coursework.</p><p><a class="cb-btn cb-btn-black cb-margin-sm-up-right-16 cb-margin-xs-bottom-8" href="https://apcentral.collegeboard.org/about-ap/ap-a-glance/discover-benefits">Benefits of Taking AP</a><a id="score-22-2" class="cb-padding-xs-top-4 display-xs-block-only cb-font-size-small" href="https://apstudents.collegeboard.org/about-ap-scores">About your <span class="sr-only">AP Biology</span> score</a></p></div>`, }; const clamp = (num, min, max) => Math.min(Math.max(num, min), max); // const snobserver = new MutationObserver((records) => { // for (const record of records) { // if (record.target.classList.contains("align-self-center")) { // console.log(record.target); // } // // if (record.type == "characterData") { // // console.log(record.oldValue, target, record); // // } // } // }); // snobserver.observe(document.body, { // childList: true, // subtree: true, // characterData: true, // attributes: true, // attributeOldValue: true, // }); // Grab all of the boxes that contain scores document.body.style.opacity = "0%"; // Hide the entire page until we can hide the scores themselves let ccontainers = []; let counter = 0; while ((ccontainers = document.querySelectorAll(".apscores-card-col-left.display-flex")).length == 0) { await new Promise((res) => setTimeout(res, 10)); // Wait 10 ms if (++counter >= 500) { document.body.style.opacity = "100%"; return; // Exit program after 5 seconds of loading } } // Wait for page to load const changeScoreElement = (container, score) => { // Replace the old score class with the new one, this is the buildings and stuff you see below a score container.classList.forEach( (cls) => /apscores-badge-score-\d/.test(cls) && container.classList.replace(cls, `apscores-badge-score-${clamp(score, 1, 5)}`), ); container.childNodes[1].nodeValue = score; // Set the text box that holds the score number container.parentNode.parentNode.nextSibling.innerHTML = sidebarBodies[score >= 3 ? "passing" : "failing"]; // Set the sidebar text }; // await new Promise((res) => setTimeout(res, 1000)); await new Promise((res) => setTimeout(res, 1000)); // changeScoreElement(ccontainers[0].querySelector(".apscores-badge.apscores-badge-score"), 2); console.log(ccontainers[0].querySelector(".apscores-badge.apscores-badge-score")); const defaultScore = await GM.getValue("all"); for (let i = 0; i < ccontainers.length; i++) { // Iterate through all the score boxes const ccontainer = ccontainers[i]; const container = ccontainer.querySelector(".apscores-badge.apscores-badge-score"); if (!container) continue; // Might've accidentally selected an award const courseName = ccontainer.parentNode.parentNode.querySelector("h4").innerText; // Grab the course name const scoreNode = container.childNodes[1]; // Grab the text box that holds the score number const savedScore = await GM.getValue(courseName); // if score is explicitly "null", then don't change the score, otherwise change it to the saved score, or the default score as backup, otherwise the current score const targetScore = savedScore === null ? Number(scoreNode.nodeValue) : savedScore ?? defaultScore ?? Number(scoreNode.nodeValue); changeScoreElement(container, targetScore); // Change the score to the target score const clickListener = async (e) => { e.stopPropagation(); const shouldEdit = await new Promise((res) => { const timeout = setTimeout(() => res(true), 2000); // Wait for a 2 second hold const releaseListener = () => { res(false); clearTimeout(timeout); window.removeEventListener("mouseup", releaseListener, true); }; window.addEventListener("mouseup", releaseListener, true); }); if (!shouldEdit) { return; // If the user didn't hold down for 2 seconds, don't edit the score } const container = ccontainer.querySelector(".apscores-badge.apscores-badge-score"); // Have to do this again because reference gets messed const scoreNode = container.childNodes[1]; // Grab the text box that holds the score number let newScore = Number(scoreNode.nodeValue); let all = false; while (true) { // Keep asking for a new score until the user enters a valid one let newScoreTemp = prompt( `Enter a new score for ${courseName} (1-5) - If you type 'reset' it will reset this score, - 'all n' where n is 1-5 will set all scores, - 'reset all' or 'all reset' will reset all scores.`.replace(/^\s+/gm, ""), scoreNode.nodeValue, ) ?.trim() .toLowerCase(); // Prompt the user for a new score if (newScoreTemp == null) return; // If the user cancelled, don't do anything if (newScoreTemp == "reset") { await GM.setValue(courseName, null); window.location.reload(); return; } if (newScoreTemp == "reset all" || newScoreTemp == "all reset") { await Promise.all((await GM.listValues()).map((v) => v.startsWith("AP ") && GM.setValue(v, null))); await GM.deleteValue("all"); window.location.reload(); return; } if (!/^(?:all )?[+-]?\d+$/.test(newScoreTemp)) continue; // If the user didn't enter a number, ask again. if (newScoreTemp.startsWith("all ")) all = true; newScore = Number(newScoreTemp.replace("all ", "")); // Grab the new score await GM.setValue("all", newScore); break; } // Prompt the user for a new score if (all) { await Promise.all( Array.from(ccontainers).map(async (c) => { const container = c.querySelector(".apscores-badge.apscores-badge-score"); if (!container) return; const courseName = c.parentNode.parentNode.querySelector("h4").innerText; changeScoreElement(container, newScore); await GM.setValue(courseName, newScore); }), ); } else { changeScoreElement(container, newScore); // Change the score to the new score await GM.setValue(courseName, newScore); // Save the new score } }; ccontainer.addEventListener("mousedown", clickListener); // Listen for a "mousedown" event on each container in this loop } let latestResize; window.addEventListener("resize", async (e) => { latestResize = e; ccontainers.forEach((c) => (c.parentNode.style.opacity = "0%")); await new Promise((res) => setTimeout(res, 100)); if (latestResize !== e) return; await Promise.all( Array.from(ccontainers).map(async (c) => { const container = c.querySelector(".apscores-badge.apscores-badge-score"); if (!container) return; const courseName = c.parentNode.parentNode.querySelector("h4").innerText; const scoreNode = container.childNodes[1]; const targetScore = (await GM.getValue(courseName)) ?? Number(scoreNode.nodeValue); changeScoreElement(container, targetScore); return true; }), ); ccontainers.forEach((c) => (c.parentNode.style.opacity = "100%")); }); document.body.style.opacity = "100%"; // Reshow the page after hiding the scores and adding the click listeners })();