Skip to content

Instantly share code, notes, and snippets.

@tan9
Last active August 22, 2024 06:07
Show Gist options
  • 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.8.12
// @description Learn without pain (and ideally, with plenty of gain...)
// @author tan9, danny, Ray941216
// @downloadURL https://gist.github.com/tan9/41686eab8e704bb18885a24339010d65/raw/ezlearning.user.js
// @updateURL https://gist.github.com/tan9/41686eab8e704bb18885a24339010d65/raw/ezlearning.user.js
// @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();
if (!title) {
title = $('a[title]').filter((_,el)=>/^[a-zA-Z]\d{3}-.*/.test($(el).attr('title'))).first().attr('title');
}
/**
* Firebase Realtime Database 的 key 有些限制,避免資料塞不進去要先做些字元替換處理。
*
* @param key 待轉換的 key 字串。
*/
const normalize = (str) => {
return str.split('').map(char => {
const code = char.charCodeAt(0);
// Handle non-printable characters and other special cases
if (code < 32 || code === 127) {
return `\\u${code.toString(16).padStart(4, '0')}`;
}
// Handle other special characters
switch (char) {
case '&': return '&amp;';
case '.': return '&period;';
case '#': return '&num;';
case '$': return '&dollar;';
case '/': return '&sol;';
case '[': return '&lsqb;';
case ']': return '&rsqb;';
case '\n': return '&NewLine;';
default: return char;
}
}).join('');
};
var removeLeadingSubstrings = (str, substrings) => {
var pattern = new RegExp(`^(${substrings.map(s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')).join('|')})+`);
while (pattern.test(str)) {
str = str.replace(pattern, '');
}
return str;
};
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;
}
.que .content .hint-text::before {
display: block;
content: '💡';
padding-right: .25rem;
}
.que:hover .content .hint-text {
display: flex;
color: #8e662e;
background-color: #fcefdc;
border-color: #fbe8cd;
border: 1 solid transparent;
border-radius: .25rem;
padding: .75rem 1.25rem;
margin-top: 1.25rem;
flex-grow: 1;
}`;
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 = $(".answernumber + div", questionElement).map((idx, answerElement) => $(answerElement).text()).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 = '🤔';
}
$("div.flex-fill", questionElement)[idx].innerHTML += ` <sup class="option-hint">${emoji}</sup>`;
});
} else {
// 沒正確解答,全部都標為不知道
answers.forEach((answer, idx) =>
$("div.flex-fill", 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();
// 逐條解析題目轉成資料物件
var question = $(".qtext", questionElement).text();
var answers = $(".answer .answernumber + div", questionElement).map(answerTextExtractor).get();
var correctAnswers = $("img.questioncorrectnessicon[src$='answer'] + + > .answernumber + div", questionElement).map(answerTextExtractor).get();
var incorrectAnswers = $(".answer > div.incorrect > input:checked + > .answernumber + div", questionElement).map(answerTextExtractor).get();
quiz[normalize(question)] = { answers: {} };
answers.forEach(answer => {
quiz[normalize(question)].answers[normalize(answer)] = { correct: correctAnswers.includes(answer) ? true : false };
});
// 如果這題錯了,盡量解析看看有沒有有用的提示
if ($(questionElement).parent().hasClass('incorrect')) {
var tip = $(".feedback .specificfeedback", questionElement).text();
if (tip.includes("很可惜") && tip.includes("您答錯了")) {
tip = removeLeadingSubstrings(tip, ["很可惜", "您答錯了", ",", "!", "。", "要不要再從頭複習看看?", "這是送分題", " "]);
if (tip) {
quiz[normalize(question)].tip = tip;
}
}
}
});
console.log("quiz:", quiz);
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:", mergedQuiz);
return $.extend(true, {}, existingQuiz, mergedQuiz);
}
}
})();
@Ray941216
Copy link

@Ray941216 謝謝提醒,低調更新 😜

關於自動更新的那個網址,應該改成『https://gist.github.com/tan9/41686eab8e704bb18885a24339010d65/raw/ezlearning.user.js』不指定 commit 才會是最新版(由於 github 有 cdn 快取,所以可能頻繁測試會無法發動成功)

@tan9
Copy link
Author

tan9 commented Jun 4, 2024

關於自動更新的那個網址,應該改成『https://gist.github.com/tan9/41686eab8e704bb18885a24339010d65/raw/ezlearning.user.js』不指定 commit 才會是最新版(由於 github 有 cdn 快取,所以可能頻繁測試會無法發動成功)

網址更新囉,謝謝

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment