Greasy Fork is available in English.
巴哈動漫電玩通題庫與解答系統,蒐集題庫中~
// ==UserScript==// @name 巴哈動漫電玩通題庫與解答系統// @namespace https://home.gamer.com.tw/moontai0724// @version 5.1.0// @description 巴哈動漫電玩通題庫與解答系統,蒐集題庫中~// @author moontai0724// @match https://forum.gamer.com.tw/B.php*// @supportURL https://home.gamer.com.tw/creationDetail.php?sn=3924920// @grant GM_getResourceText// @grant GM_getValue// @grant GM_setValue// @grant GM_deleteValue// @connect script.google.com// @connect script.googleusercontent.com// @resource quizrp https://raw.githubusercontent.com/moontai0724/bahamut-quiz-script/master/right-box.html// @license MIT// ==/UserScript==/** @class */class Database {/** @type {String} */static URL = "https://script.google.com/macros/s/AKfycbxYKwsjq6jB2Oo0xwz4bmkd3-5hdguopA6VJ5KD/exec";constructor() {throw new Error("New a Database instance is not needed.");}/*** Check is quiz exists in database* @param {Quiz} quiz* @returns {Boolean}*/static async exists(quiz) {let exists = await this.fetch("checkExisting", quiz.sn).catch(() => false);return exists ? true : false;}/*** Find answer of quiz in database* @param {Quiz} quiz*/static async answerOf(quiz) {let data = await this.fetch("answer", quiz.sn);return Promise.resolve(data);}/*** Find hint of quiz in database* @param {Quiz} quiz*/static async hintOf(quiz) {let data = await this.fetch("hint", quiz.sn);return Promise.resolve(data);}/*** Fetch (get) data from database* @param {String} type type of action* @param {Number} sn serial number of quiz*/static async fetch(type, sn) {const data = new URLSearchParams({ type: type, sn: sn });const response = await fetch(`${Database.URL}?${data.toString()}`).then(response => response.json());if (response.success)return Promise.resolve(response.data);return Promise.reject(response);}/*** Submit (post) data to database* @param {Quiz} quiz quiz infos* @param {Number} answer answer of quiz* @param {Boolean} correctness correctness of answer*/static async submit(quiz, answer, correctness) {const data = {"version": GM_info.script.version,"sn": quiz.sn,"question": quiz.question,"options": quiz.options,"bsn": quiz.bsn,"author": quiz.author,"this_answered": answer,"correctness": correctness,};const response = await fetch(Database.URL, {method: "POST",cache: "no-cache",headers: {"content-type": "text/plain;charset=utf-8",},body: JSON.stringify(data),});return await response.json();}}/** @class */class Quiz {/** @type {Number} */sn;/** @type {String} */question;/** @type {Array<String>} */options;/** @type {String} */author;/** @type {Number} */answer;/** @type {Number} */bsn;/** @type {Boolean} */answered = false;constructor() {let qabox = document.querySelector(".BH-qabox1");this.sn = qabox.getAttribute("data-quiz-sn");this.question = encodeURIComponent(qabox.innerText.split("\n").shift());this.options = Array.from(qabox.querySelectorAll("li a")).map(element => encodeURIComponent(element.innerText));this.author = new URL("https://" + qabox.querySelector("span>a").getAttribute("href")).pathname.split("/").pop();this.bsn = new URL(location.href).searchParams.get("bsn");}/*** try and submit answer to database* @param {User} user user to get CSRFToken* @param {View} view* @param {Number|null} answer if provided, will test answer correctness*/async submitAnswer(user, view, answer = undefined) {let token = await user.getCSRFToken();if (!answer || !(await this.attemptAnswer(answer, token, view)))answer = await this.getAnswer(token);let response = await Database.submit(this, answer, true);view.setSubmitR###lt(response);}/*** Try answer correctness and return correct answer* @param {String} token CSRFToken* @param {Number} tryAnswer answer index to try submit*/async getAnswer(token, tryAnswer = 1) {if (tryAnswer > 4) return Promise.reject();const correctness = await this.attemptAnswer(tryAnswer, token);if (correctness) {this.answer = tryAnswer;return tryAnswer;}return await this.getAnswer(token, ++tryAnswer);}/*** Submit quiz answer to Bahamut* @param {Number} answer answer number, valid from 1 to 4* @param {String} token CSRFToken* @param {View} view if provided, will update response into view*/async attemptAnswer(answer, token, view = undefined) {const data = new URLSearchParams({ sn: this.sn, o: answer, token: token });const response = await fetch(`/ajax/quiz_answer.php?${data.toString()}`).then(response => response.text());if (view)view.setResponse(response);return response.includes("答對");}toString() {let data = { sn: this.sn, question: this.question, options: this.options, author: this.author, answer: this.answer, bsn: this.bsn };return JSON.stringify(data);}}/** @class */class User {/** @type {String|undefined} */id;constructor() {this.initializeId();}initializeId() {try {this.id = BAHAID;} catch (error) {let cookie = document.cookie.split("; ").filter(cookie => cookie.startsWith("BAHAID")).shift();this.id = cookie ? cookie.split("=").pop() : undefined;}}async getCSRFToken() {const response = await fetch("/ajax/getCSRFToken.php");return await response.text();}}/** @class */class View {/** @type {HTMLElement} */window;/** @type {HTMLElement} */qabox;/*** @param {User} user* @param {Quiz} quiz*/constructor(user, quiz) {this.window = document.querySelector("#quizrp");this.qabox = document.querySelector(".BH-qabox1");this.window.querySelector(".version span").innerHTML = GM_info.script.version;this.initOptions(user, quiz);this.initAutoAnswer(user, quiz);this.window.querySelector(".hint button").addEventListener("click", event => this.showHint(quiz));this.window.querySelector(".original button").addEventListener("click", event => this.showQuiz(quiz));}/*** @param {User} user* @param {Quiz} quiz*/initAutoAnswer(user, quiz) {if (GM_getValue("auto-answer", false)) {this.window.querySelector(".auto span").classList.add("enable");setTimeout(() => quiz.submitAnswer(user, this), 2000);}this.window.querySelector(".auto button").addEventListener("click", event => this.toggleAutoAnswer(user, quiz))}/*** @param {User} user* @param {Quiz} quiz*/initOptions(user, quiz) {this.qabox.querySelectorAll("li a").forEach(async (element, index, array) => {element.removeAttribute("href");element.addEventListener("click", async event => quiz.submitAnswer(user, this, index + 1, true));});}/*** Toggle and trigger auto answer* @param {User} user* @param {Quiz} quiz*/toggleAutoAnswer(user, quiz) {let autoAnswerStatus = this.window.querySelector(".auto span");autoAnswerStatus.classList.toggle("enable");if (!autoAnswerStatus.classList.contains("enable")) {GM_deleteValue("auto-answer");return;}GM_setValue("auto-answer", true);quiz.submitAnswer(user, this);}/*** Show quiz* @param {Quiz} quiz*/async showQuiz(quiz) {let original = this.window.querySelector(".original div");original.innerHTML = `題目編號:${quiz.sn}<br>原題目:${decodeURIComponent(quiz.question)}<ol><li>${quiz.options.map(decodeURIComponent).join("</li><li>")}</li></ol>`;original.classList.remove("hide");this.window.querySelector(".original button").classList.add("hide");let answer = await Database.answerOf(quiz).catch(err => null);let options = Array.from(this.window.querySelectorAll(".original li"));if (answer)options.forEach((element, index) => element.classList.add(index + 1 == answer ? "correct" : "wrong"));}/*** Show hint* @param {Quiz} quiz*/async showHint(quiz) {let view = this.window.querySelector(".hint span");let hint = await Database.hintOf(quiz).catch(err => null);this.window.querySelector(".hint button").classList.add("hide");if (!hint) {view.innerHTML = "題庫中無資料。";return;}view.innerHTML = "提示已獲取!";view.classList.add("success");this.qabox.querySelector(`li:nth-child(${hint}) a`).classList.add("wrong");}/*** Set response of database to view* @param {JSON} response*/setSubmitR###lt(response) {let messages = {"DATA_INVALID": "無法送出答案,請檢查是否有更新,或通知作者。","DATA_APPENDED": "資料新增了!感謝提供~","DATA_UPDATED": "資料更新了!感謝提供~","IGNORED": "題庫中已經有資料啦~",};const statusDisplay = this.window.querySelector(".report span");statusDisplay.innerHTML = messages[response.message];if (response.success)statusDisplay.classList.add("success");}/*** Set native baha response of quiz into view* @param {String} html*/setResponse(html) {this.qabox.style["text-align"] = "center";this.qabox.innerHTML = html;}}(function () {'use strict';document.querySelector(".BH-qabox1").outerHTML += GM_getResourceText("quizrp");let user = new User();let quiz = new Quiz();let view = new View(user, quiz);})();