Skip to content

Instantly share code, notes, and snippets.

@tan9
Last active January 17, 2024 01:27
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tan9/41686eab8e704bb18885a24339010d65 to your computer and use it in GitHub Desktop.
Save tan9/41686eab8e704bb18885a24339010d65 to your computer and use it in GitHub Desktop.
CHT e-Learning Assistant
// ==UserScript==
// @name CHT e-Learning Assistant
// @source https://gist.github.com/tan9/41686eab8e704bb18885a24339010d65
// @version 0.7.2
// @description Learn with no pain (and hopefully not no gain...)
// @author tan9, danny
// @downloadURL https://gist.githubusercontent.com/tan9/41686eab8e704bb18885a24339010d65/raw/
// @updateURL https://gist.githubusercontent.com/tan9/41686eab8e704bb18885a24339010d65/raw/
// @require https://www.gstatic.com/firebasejs/4.9.0/firebase.js
// @require https://code.jquery.com/jquery-1.12.4.min.js
// @match https://plearn.elearning.cht.com.tw/mod/quiz/*
// @match https://ilearn.elearning.cht.com.tw/mod/quiz/*
// @icon https://web-eshop.cdn.hinet.net/eshop/img/favicon.ico
// @grant none
// ==/UserScript==
(function () {
'use strict';
// Initialize Firebase
var config = {
apiKey: "AIzaSyCYyoclF3BBHMNackHbhD2YWt3e5wkqZSM",
authDomain: "cht-ezlearning.firebaseapp.com",
databaseURL: "https://cht-ezlearning.firebaseio.com",
projectId: "cht-ezlearning",
storageBucket: "",
messagingSenderId: "86945052698"
};
firebase.initializeApp(config);
var title = $("h1").text();
/**
* Firebase Realtime Database 的 key 有些限制,避免資料塞不進去要先做些字元替換處理。
*
* @param key 待轉換的 key 字串。
*/
var normalize = (key) => key.trim().replace(/&/g, '&').replace(/\./g, '.').replace(/#/g, "#").replace(/\$/g, "$").replace(/\//g, "/").replace(/\[/g, "[").replace(/\]/g, "]").replace(/\n/g, "
");
var quizKey = normalize(title);
if (title && location.pathname === "/mod/quiz/attempt.php") {
// 在測驗頁,先加入預設 style
const style = document.createElement('style');
style.textContent = `.answer .option-hint {
opacity: 0;
}
.answer div:hover .option-hint {
opacity: 0.6;
}
.que .content .hint-text {
display: none;
color: firebrick;
font-weight: bold;
}
.que .content .hint-text::before {
content: '💡';
}
.que:hover .content .hint-text {
display: block;
}`;
document.head.append(style);
// 查資料庫啦
firebase.database().ref(quizKey).once("value", (snapshot) => {
var quiz = snapshot.val();
if (quiz) {
// 資料庫裡有資料了,將題庫取出
console.log(`Quiz data loaded, ${Object.keys(quiz).length} questions found.`);
// 將這次測驗的內容逐題與資料庫比對
$(".qtext").parent().parent().each((idx, questionElement) => {
var question = $(".qtext", questionElement).text();
var answers = $(".answer label", questionElement).map((idx, answerElement) => $(answerElement).text().substring(3)).get();
// 如果有提示就顯示提示
if (quiz[normalize(question)]) {
if (quiz[normalize(question)].tip) {
$(".qtext", questionElement).parent().append(`<div class="hint-text">${quiz[normalize(question)].tip}</div>`);
}
}
// 現在檢討頁答錯的題目並不會給正確的解答,因此有可能題庫資料裡的答案是錯誤的
let hasAnswer = answers.some((answer) =>
quiz[normalize(question)] && quiz[normalize(question)].answers[normalize(answer)] && quiz[normalize(question)].answers[normalize(answer)].correct === true
);
if (hasAnswer) {
// 有正確解答逐一選項標示
answers.forEach((answer, idx) => {
var emoji;
if (quiz[normalize(question)] && quiz[normalize(question)].answers[normalize(answer)] && quiz[normalize(question)].answers[normalize(answer)].correct === true) {
emoji = '✔️';
} else if (quiz[normalize(question)] && quiz[normalize(question)].answers[normalize(answer)] && quiz[normalize(question)].answers[normalize(answer)].correct === false) {
emoji = '❌';
} else {
emoji = '🤔';
}
$("label", questionElement)[idx].innerHTML += ` <sup class="option-hint">${emoji}</sup>`;
});
} else {
// 沒正確解答,全部都標為不知道
answers.forEach((answer, idx) =>
$("label", questionElement)[idx].innerHTML += ` <sup class="option-hint">🤔</sup>`
);
}
});
// 避免大家太早按送出而被抓包,加個倒數
var submit = $("input[type=submit]") && $("input[type=submit]")[0];
if (submit && $(".qtext").length > 5) {
var originalText = submit.value;
var countDownSeconds = 60 + Math.round(Math.random() * 60);
submit.disabled = true;
submit.value = `${originalText} (${countDownSeconds})`;
var countDown = setInterval(() => {
if (countDownSeconds > 0) {
countDownSeconds--;
submit.value = `${originalText} (${countDownSeconds})`;
} else {
submit.value = originalText;
submit.disabled = false;
clearInterval(countDown);
}
}, 1000)
}
} else {
console.log("No quiz data in firebase.");
$(".answer label").each((idx, label) => label.innerHTML += ` <sup class="option-hint">🤔</sup>`);
}
});
}
if (title && location.pathname === "/mod/quiz/review.php") {
// 在檢討頁裡,用力來 parse 資料補完資料庫!
var quiz = {};
$(".qtext").parent().parent().each((idx, questionElement) => {
var answerTextExtractor = (idx, answerElement) => $(answerElement).text().substring(3);
// 逐條解析題目轉成資料物件
var question = $(".qtext", questionElement).text();
var answers = $(".answer label", questionElement).map(answerTextExtractor).get();
var correctAnswers = $(".answer > div.correct > input:checked + label", questionElement).map(answerTextExtractor).get();
var incorrectAnswers = $(".answer > div.incorrect > input:checked + label", questionElement).map(answerTextExtractor).get();
quiz[normalize(question)] = { answers: {} };
if ($('input[type=radio]', questionElement).length) {
// 單選題
if (correctAnswers.length) {
// 有選到正確答案,那就知道其他都是錯的
answers.forEach(answer => {
quiz[normalize(question)].answers[normalize(answer)] = { correct: correctAnswers.includes(answer) };
});
} else {
// 只知道錯誤答案,就等待善心人事再來貢獻啦
answers.forEach(answer => {
quiz[normalize(question)].answers[normalize(answer)] = { correct: incorrectAnswers.includes(answer) ? true : null };
});
}
} else {
// 多選題,因為只有選取的選項會知道正確與否,所以不知道的選項都標為 correct: null
answers.forEach(answer => {
quiz[normalize(question)].answers[normalize(answer)] = { correct: correctAnswers.includes(answer) ? true : (incorrectAnswers.includes(answer) ? false : null) };
});
}
var tip = $(".feedback .specificfeedback", questionElement).text();
if (tip.startsWith("很可惜您答錯了!")) {
quiz[normalize(question)].tip = tip.substring("很可惜您答錯了!".length);
}
});
console.log("quiz:\n" + JSON.stringify(quiz, null, 2));
firebase.database().ref(quizKey).once("value", (snapshot) => {
var persistent = snapshot.val();
if (persistent) {
// 已經有舊資料了,合併題庫內容
var mergedQuiz = mergeQuiz(persistent, quiz);
// 如果題庫有擴充,就回寫回去
if (JSON.stringify(mergedQuiz) !== JSON.stringify(persistent)) {
firebase.database().ref(quizKey).update(
mergedQuiz,
() => console.log("Quiz updated to firebase.")
);
} else {
console.log("Quiz database up-to-date, no new questions has been found.");
}
} else {
firebase.database().ref(quizKey).set(
quiz,
() => console.log("Quiz set to firebase.")
);
}
});
function mergeQuiz(existingQuiz, newQuiz) {
var mergedQuiz = Object.entries(newQuiz)
.filter(
([question, questionMeta]) =>
// 新的問題,或是有更詳細解答的才要合併
!existingQuiz[question] || Object.entries(questionMeta.answers).some(([answerText, answerMeta]) => answerMeta.correct !== null)
).reduce((accumulator, [question, questionMeta]) => {
var mergedMeta = {};
if (existingQuiz[question]) {
// 舊題目,好好合併
mergedMeta = {...existingQuiz[question], ...questionMeta};
mergedMeta.answers = {...existingQuiz[question].answers,
...Object.fromEntries(
Object.entries(questionMeta.answers)
.filter(([answerText, answerMeta]) =>
// 有更明確解答,或是舊題庫沒有的答案才合併
answerMeta.correct !== null || !existingQuiz[question].answers[answerText]
)
)
}
} else {
// 新題目,直接用新的
mergedMeta = questionMeta;
}
accumulator[question] = mergedMeta;
return accumulator;
}, {});
console.log("merged quiz:\n" + JSON.stringify(mergedQuiz, null, 2));
return $.extend(true, {}, existingQuiz, mergedQuiz);
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment