// ==UserScript==
// @name Show Total Lesson Count - WaniKani
// @namespace http://tampermonkey.net/
// @version 0.5.0
// @description Changes the count of lessons on the Today's Lessons tile to show the total number of available lessons in addition to the number selected for you
// @license MIT
// @author LupoMikti
// @match https://www.wanikani.com/*
// @grant none
// @supportURL https://community.wanikani.com/t/userscript-show-total-lesson-count/66776
// ==/UserScript==
(async function() {
'use strict';
/* global wkof */
let scriptId = 'show_total_lesson_count';
let scriptName = 'Show Total Lesson Count';
let initial_load = true;
let todaysLessonsCount;
let settings;
let stateStarting = false;
let todaysLessonsFrameLoaded = false;
let navBarCountFrameLoaded = false;
let debugLogText = `START: ${scriptName} Debug Log:\n`;
let hasOutputLog = false;
let mainSource = '';
const INTERNAL_FORCE_DEBUG_OUTPUT = false;
function addToDebugLog(message) {
debugLogText += `${new Date().toISOString()}: ${message}\n`;
}
function printDebugLog(force = false) {
if (!hasOutputLog || force) { console.log(`${scriptName}: Outputting a debug log to console.debug()`); console.debug(debugLogText); }
if (!force) hasOutputLog = true;
debugLogText = `START: ${scriptName} Debug Log:\n`;
}
if (!window.wkof) {
if (confirm(scriptName + ' requires Wanikani Open Framework.\nDo you want to be forwarded to the installation instructions?')) {
window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
}
return;
}
const wkofTurboEventsScriptUrl = 'https://update.gf.qytechs.cn/scripts/501980/1423213/Wanikani%20Open%20Framework%20Turbo%20Events.user.js';
addToDebugLog(`Attempting to load the TurboEvents library script...`)
await wkof.load_script(wkofTurboEventsScriptUrl, /* use_cache */ true);
addToDebugLog(`Checking if TurboEvents library script is loaded in...`)
let injectedDependency = document.head.querySelector('script[uid*="Turbo"]');
addToDebugLog(`Turbo Events library ${injectedDependency ? 'is' : 'is NOT' } loaded.`);
if (INTERNAL_FORCE_DEBUG_OUTPUT) {
window.addEventListener('turbo:load', () => { console.log(`DEBUG: turbo:load has fired`); });
window.addEventListener('turbo:before-frame-render', (e) => { console.log(`DEBUG: turbo:before-frame-render has fired for '#${e.target.id}'`); });
window.addEventListener('turbo:frame-load', (e) => { console.log(`DEBUG: turbo:frame-load has fired for '#${e.target.id}'`); });
}
const _init = (source) => {
if (stateStarting) { addToDebugLog(`We are already in the starting state, no need to initialize, returning...`); return; }
addToDebugLog(`SOURCE = "${source}" | Setting globals and calling _start()`);
initial_load = stateStarting = true;
hasOutputLog = todaysLessonsFrameLoaded = navBarCountFrameLoaded = false;
_start();
};
wkof.ready('TurboEvents').then(() => {
addToDebugLog(`Start of TurboEvents ready callback`)
let urlList = [wkof.turbo.common.locations.dashboard, wkof.turbo.common.locations.items_pages, /^https:\/\/www\.wanikani\.com\/(settings|level|radicals|kanji|vocabulary)(\/|\?difficulty=).+\/?$/];
wkof.turbo.on.event.load((e) => { _init('turbo:load'); }, { urls: urlList });
wkof.turbo.on.event.before_frame_render((e) => {
addToDebugLog(`turbo:before-frame-render has fired for "#${e.target.id}"`);
if (['todays-lessons-frame', 'lesson-and-review-count-frame'].includes(e.target.id)) {
if (initial_load && !stateStarting) { addToDebugLog(`initial_load is true (no frames were previously retrieved) and we are not already in starting state, starting initialization sequence...`); _init('turbo:before-frame-render'); }
todaysLessonsFrameLoaded = navBarCountFrameLoaded = false;
}
}, { urls: urlList });
wkof.turbo.on.event.frame_load(async (e) => {
addToDebugLog(`turbo:frame-load has fired`);
if (e.target.id === 'todays-lessons-frame') {
addToDebugLog(`turbo:frame-load is for "#todays-lessons-frame"`);
todaysLessonsFrameLoaded = true;
mainSource = `turbo:frame-load for "#todays-lessons-frame"`;
await main();
}
else if (e.target.id === 'lesson-and-review-count-frame') {
addToDebugLog(`turbo:frame-load is for "#lesson-and-review-count-frame"`);
navBarCountFrameLoaded = true;
mainSource = `turbo:frame-load for "#lesson-and-review-count-frame"`;
await main();
}
else {
addToDebugLog(`turbo:frame-load was for "#${e.target.id}", doing nothing...`);
}
mainSource = '';
}, { urls: urlList });
addToDebugLog(`All turbo callbacks have been sent to TurboEvents library to be registered`);
}).catch((err) => { addToDebugLog(`TurboEvents library rejected with error: ${err}`); }).finally(() => { if (INTERNAL_FORCE_DEBUG_OUTPUT) { addToDebugLog(`SOURCE: turbo ready finally`); printDebugLog(INTERNAL_FORCE_DEBUG_OUTPUT); } });
function _start() {
addToDebugLog(`Starting...`);
wkof.include('Settings, Menu, Apiv2');
wkof.ready('Settings, Menu, Apiv2').then(loadSettings).then(insertMenu).then(main).catch((err) => { addToDebugLog(`wkof.ready() rejected with error: ${err}`); }).finally(() => { if (INTERNAL_FORCE_DEBUG_OUTPUT) { addToDebugLog(`SOURCE: wkof modules ready finally`); printDebugLog(INTERNAL_FORCE_DEBUG_OUTPUT); } });
}
function loadSettings() {
addToDebugLog(`Loading settings...`);
let defaults = {
showTotalOnly: false,
setOwnPreferredDaily: false,
preferredDailyAmount: wkof.user.preferences.lessons_batch_size * 3,
enableDebugging: true,
};
return wkof.Settings.load(scriptId, defaults).then(function(wkof_settings) {settings = wkof_settings;});
}
function insertMenu() {
addToDebugLog(`Inserting menu...`);
let config = {
name: scriptId,
submenu: 'Settings',
title: scriptName,
on_click: openSettings
};
wkof.Menu.insert_script_link(config);
mainSource = `_start() -> loadSettings() -> insertMenu()`;
}
function openSettings() {
let config = {
script_id: scriptId,
title: scriptName,
on_save: main,
content: {
showTotalOnly: {
type: 'checkbox',
label: 'Show Only Total Lesson Count',
hover_tip: `Changes display between "<today's lesson count> / <total lesson count>" and just "<total lesson count>"`,
default: false,
},
setOwnPreferredDaily: {
type: 'checkbox',
label: '(DEPRECATED) Set Your Own Daily Lesson Count',
hover_tip: `THIS SETTING HAS BEEN DEPRECATED DUE TO THE OFFICIAL SETTING FROM WANIKANI.`,
default: false,
},
preferredDailyAmount: {
type: 'number',
label: '(DEPRECATED) Preferred Daily Lesson Amount',
hover_tip: `THIS SETTING HAS BEEN DEPRECATED DUE TO THE OFFICIAL SETTING FROM WANIKANI.`,
min: 0,
max: 100,
},
enableDebugging: {
type: 'checkbox',
label: 'Enable console debugging',
hover_tip: `Enable output of debugging info to console.debug()`,
default: true,
}
}
};
let dialog = new wkof.Settings(config);
dialog.open();
}
function getCountContainers() {
let dashboardTileCountContainer = document.querySelector('.todays-lessons__count-text .count-bubble');
let navBarCountContainer = document.querySelector('.lesson-and-review-count__count');
if (initial_load && (dashboardTileCountContainer || navBarCountContainer)) {
let container = dashboardTileCountContainer ?? navBarCountContainer;
todaysLessonsCount = parseInt(container.textContent);
initial_load = false;
}
return [dashboardTileCountContainer, navBarCountContainer];
}
async function main() {
addToDebugLog(`Main function is executing... source of start: ${mainSource}`);
if (!settings) {
addToDebugLog(`We do not have settings, setting timeout on _init()`);
if (!stateStarting) { setTimeout(_init, 50); }
else addToDebugLog(`Did not set timeout due to already being in starting state`);
addToDebugLog(`Main function has finished executing`);
return;
}
addToDebugLog(`We have settings`);
addToDebugLog(`Retrieving summary data via await of the enpoint...`);
let summary_data = await wkof.Apiv2.get_endpoint('summary');
addToDebugLog(`Summary data has been retrieved`);
let totalLessonCount = summary_data.lessons[0].subject_ids.length;
let lessonCountContainers;
if (todaysLessonsFrameLoaded || navBarCountFrameLoaded) {
addToDebugLog(`Frame(s) loaded, retrieving containers in frames containing the counts...`);
lessonCountContainers = getCountContainers();
addToDebugLog(`Count containers have been retrieved`);
}
else {
addToDebugLog(`No frames loaded`);
addToDebugLog(`Main function has finished executing`);
//if (settings.enableDebugging) printDebugLog(INTERNAL_FORCE_DEBUG_OUTPUT);
return;
}
let todaysCountForDisplay = todaysLessonsCount;
if (lessonCountContainers.every(node => node == null)) {
addToDebugLog(`No nodes in containers`);
addToDebugLog(`Main function has finished executing`);
//if (settings.enableDebugging) printDebugLog(INTERNAL_FORCE_DEBUG_OUTPUT);
return;
}
addToDebugLog(`At least one container exists`);
stateStarting = false;
if (isNaN(todaysLessonsCount)) todaysCountForDisplay = 0;
if (lessonCountContainers[0]) {
lessonCountContainers[0].textContent = settings.showTotalOnly ? totalLessonCount : todaysCountForDisplay + ' / ' + totalLessonCount;
addToDebugLog(`Setting display amount for Today's Lessons tile, set to: ${lessonCountContainers[0].textContent}`);
}
if (lessonCountContainers[1]) {
lessonCountContainers[1].textContent = settings.showTotalOnly ? totalLessonCount : todaysCountForDisplay;
addToDebugLog(`Setting display amount for navigation bar, set to: ${lessonCountContainers[1].textContent}`);
}
if (todaysCountForDisplay === 0) {
addToDebugLog(`Hiding start button due to having 0 lessons with configured count source`);
// hide the start button if it is not already, TODO: disable nav bar button if it is not already
let startButton = document.querySelector('.todays-lessons-button--start')
if (startButton && startButton.checkVisibility()) {
startButton.style.display = 'none';
}
}
// hide "Today's" subtitle
let lessonSubtitle = document.querySelector('.todays-lessons__subtitle');
if (lessonSubtitle && lessonSubtitle.checkVisibility()) {
addToDebugLog(`Hiding the "Today's" subtitle on the lesson tile`);
lessonSubtitle.style.display = 'none';
}
addToDebugLog(`Main function has finished executing`);
if (settings.enableDebugging) printDebugLog(INTERNAL_FORCE_DEBUG_OUTPUT);
}
})();