Skip to content

Instantly share code, notes, and snippets.

@evaneliasyoung
Created September 6, 2022 21:11
Show Gist options
  • Save evaneliasyoung/89ba68585827f8427ef86fdf31951e75 to your computer and use it in GitHub Desktop.
Save evaneliasyoung/89ba68585827f8427ef86fdf31951e75 to your computer and use it in GitHub Desktop.
ZyBooks completion tool
/**
* @file zbc.es6
* @brief ZyBooks completion tool.
*
* @author Evan Elias Young
* @date 2021-09-19
* @date 2022-09-06
* @copyright Copyright 2021-2022 Evan Elias Young. All rights reserved.
*/
/**
* Represents an enhanced HTML element.
*/
class HTMLSuperElement {
/**
* @brief The underlying element.
* @type {HTMLElement}
*/
#el;
/**
* @brief Reference to the base element.
* @returns {HTMLElement} The base element.
*/
get ref() { return this.#el; }
/**
* @brief Creates a new HTMLSuperElement with jQuery inspired methods.
* @returns {HTMLSuperElement} The enhanced HTML element.
*/
constructor(el) { this.#el = el; }
/**
* @brief Returns all element descendants of node that match selectors.
* @param {string} selectors Selectors with which to match elements.
* @returns {NodeListOf<HTMLElement>} All element descendants of node that match selectors.
*/
$ = (selectors) => this.ref.querySelectorAll(selectors);
/**
* @brief Returns the first element that is a descendant of node that matches selectors.
* @param {string} selectors Selectors with which to match elements
* @returns {HTMLElement|null} The first element that is a descendant of node that matches selectors.
*/
$$ = (selectors) => this.ref.querySelector(selectors);
}
/**
* Represents a multiple choice question.
*/
class MultipleChoice extends HTMLSuperElement {
/**
* @brief The index of the attempted answers.
* @type {number}
*/
idx = -1;
/**
* @brief The answers' buttons for the question.
* @returns {NodeListOf<HTMLInputElement>} The answers' buttons.
*/
get $answers() { return this.$('input'); }
/**
* @brief Determines whether or not the question is done.
* @returns {bool} Whether or not the question is done.
*/
get done() { return !!this.$$('[aria-label="Question completed"]'); }
/**
* Creates a new multiple choice question.
* @param {HTMLDivElement} el The DOM element containing the question.
*/
constructor(el) { super(el); this.step(); }
answer(idx) { this.$answers[idx].click(); }
try(idx) { if (!this.done) this.answer(idx); }
step() { if (this.idx++ < this.$answers.length) this.try(this.idx); }
}
/**
* Represents a set of multiple choice questions.
*/
class MultipleChoiceSet extends HTMLSuperElement {
/**
* @brief The multiple choice questions in the set.
* @type {MultipleChoice[]}
*/
questions;
/**
* @brief The questions' containers for the set.
* @returns {NodeListOf<HTMLDivElement>} The questions' containers.
*/
get $questions() { return this.$('.question-set-question.multiple-choice-question'); }
/**
* @brief Determines whether or not the question set is done.
* @returns {bool} Whether or not the question set is done.
*/
get done() { return !!this.$$('[aria-label="Activity completed"]'); }
/**
* Creates a new multiple choice question set.
* @param {HTMLDivElement} el The DOM element containing the question set.
*/
constructor(el) { super(el); }
init() { this.questions = Array.from(this.$questions).map(el => new MultipleChoice(el)); }
answer(idx) { this.questions.forEach(que => que.answer(idx)); }
try(idx) { this.questions.forEach(que => que.try(idx)); }
step() { this.questions.forEach(que => que.step()); }
}
/**
* Represents a walkthrough problem.
*/
class Walkthrough extends HTMLSuperElement {
/**
* @brief The start button for the walkthrough.
* @returns {HTMLButtonElement|null} The start button.
*/
get $start() { return this.$$('button.start-button'); }
/**
* @brief The speed controller for the walkthrough.
* @returns {HTMLInputElement|null} The speed controller.
*/
get $speed() { return this.$$('.speed-control input'); }
/**
* @brief The play button for the walkthrough.
* @returns {HTMLButtonElement|null} The play button.
*/
get $play() { return this.$$('button[aria-label="Play"]'); }
/**
* @brief The pause button for the walkthrough.
* @returns {HTMLButtonElement|null} The pause button.
*/
get $pause() { return this.$$('button[aria-label="Pause"]'); }
/**
* @brief The playback speed setting.
* @returns {0|1|2} The playback speed settings.
*/
get speed() { return this.$speed ? this.$speed.value === 'true' ? 2 : 1 : 0; }
/**
* @brief Whether or not the walkthrough is done.
* @returns {boolean} Whether or not the walkthrough is done.
*/
get done() { return !!this.$$('[aria-label="Activity completed"]'); }
/**
* Creates a new walkthrough.
* @param {HTMLDivElement} el The DOM element containing the walkthrough.
*/
constructor(el) { super(el); }
init() { if (!this.done) { this.double(); this.start(); } }
double() { if (this.speed === 1) this.$speed.click(); }
start() { if (this.$start) this.$start.click(); }
step() { if (this.$play) this.$play.click(); }
}
/**
* Represents a short answer problem.
*/
class ShortAnswer extends HTMLSuperElement {
/**
* @brief The show button for the question.
* @returns {HTMLButtonElement|null} The show button.
*/
get $show() { return this.$$('button.show-answer-button'); }
/**
* @brief The user's response for the question.
* @returns {HTMLTextAreaElement|null} The user's response.
*/
get $response() { return this.$$('textarea'); }
/**
* @brief The check button for the question.
* @returns {HTMLButtonElement|null} The check button.
*/
get $check() { return this.$$('button.check-button'); }
/**
* @brief The solution for the question.
* @returns {HTMLSpanElement|null} The solution.
*/
get $answer() { return this.$$('span.forfeit-answer'); }
/**
* @brief The solution for the question.
* @returns {string|null} The solution.
*/
get answer() { return this.$answer ? this.$answer.textContent : null; }
/**
* @brief Determines whether or not the question is done.
* @returns {bool} Whether or not the question is done.
*/
get done() { return !!this.$$('[aria-label="Question completed"]'); }
/**
* Creates a new short answer question.
* @param {HTMLDivElement} el The DOM element containing the question.
*/
constructor(el) { super(el); }
init() { this.show(); }
show() { this.$show.click(); this.$show.click(); }
copy() { this.$response.value = this.answer || 'null?'; }
}
/**
* Represents the main solver for ZyBook problems.
*/
class ZyBookMan extends HTMLSuperElement {
/**
* @brief The set of multiple choice questions.
* @type {MultipleChoiceSet[]}
*/
mc;
/**
* @brief The set of walkthroughs.
* @type {Walkthrough[]}
*/
wt;
/**
* @brief The question sets' containers.
* @returns {NodeListOf<HTMLDivElement>} The question sets' containers.
*/
get $mc() { return this.$('.interactive-activity-container.multiple-choice-content-resource.participation'); }
/**
* @brief The walkthrough containers.
* @returns {NodeListOf<HTMLDivElement>} The walkthrough containers.
*/
get $wt() { return this.$('.interactive-activity-container.animation-player-content-resource.participation'); }
/**
* @brief Determines whether or not all questions are done.
* @returns {bool} Whether or not all questions are done.
*/
get doneMC() { return this.mc.reduce((allDone, mc) => allDone && mc.done, true); }
/**
* @brief Determines whether or not all walkthroughs are done.
* @returns {bool} Whether or not all walkthroughs are done.
*/
get doneWT() { return this.wt.reduce((allDone, wt) => allDone && wt.done, true); }
/**
* @brief Determines whether or not all problems are done.
* @returns {bool} Whether or not all problems are done.
*/
get done() { return this.doneMC && this.doneWT; }
/**
* @brief Creates a new ZyBookMan to solve questions.
*/
constructor() {
console.log("%cWelcome to ZyBookMan, please initialize...", "color:#bfd730;")
super(document);
}
init() {
console.log("%cInitializing ZyBookMan...", "color:#bfd730;")
this.mc = Array.from(this.$mc).map(el => new MultipleChoiceSet(el));
this.wt = Array.from(this.$wt).map(el => new Walkthrough(el));
[this.mc, this.wt].forEach(klasses => klasses.forEach(klass => klass.init()));
}
step() {
console.log("%cStepping ZyBookMan...", "color:#d19d30;")
if (!this.doneMC) this.stepMC();
if (!this.doneWT) this.stepWT();
}
stepMC() { this.mc.forEach(mc => mc.step()); }
stepWT() { this.wt.forEach(wt => wt.step()); }
}
zbm = new ZyBookMan();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment