// ==UserScript==
// @name Show Total Lesson Count - WaniKani
// @namespace http://tampermonkey.net/
// @version 0.4.8
// @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 wkBatchSize = 0;
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) {
console.log(`${scriptName}: Outputting a debug log to console.debug()`)
if (!hasOutputLog || force) console.debug(debugLogText);
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.greasyfork.org/scripts/501980/1422360/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', () => { console.log(`DEBUG: turbo:before-frame-render has fired`); });
window.addEventListener('turbo:frame-load', () => { console.log(`DEBUG: turbo:frame-load has fired`); });
}
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.common.urls(() => {
addToDebugLog(`turbo:load has fired, setting globals and calling _start()`)
initial_load = stateStarting = true;
hasOutputLog = todaysLessonsFrameLoaded = navBarCountFrameLoaded = false;
_start();
}, 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)) {
todaysLessonsFrameLoaded = navBarCountFrameLoaded = false;
}
}, { urls: urlList, noTimeout: true });
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`)
if (INTERNAL_FORCE_DEBUG_OUTPUT) printDebugLog(INTERNAL_FORCE_DEBUG_OUTPUT);
});
if (INTERNAL_FORCE_DEBUG_OUTPUT) 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);
}
function loadSettings() {
addToDebugLog(`Loading settings...`)
wkBatchSize = wkof.user.preferences.lessons_batch_size;
let defaults = {
showTotalOnly: false,
setOwnPreferredDaily: false,
preferredDailyAmount: wkBatchSize * 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: 'Set Your Own Daily Lesson Count',
hover_tip: `Choose whether to display the value you set as your daily lesson count or not`,
default: false,
},
preferredDailyAmount: {
type: 'number',
label: 'Preferred Daily Lesson Amount',
hover_tip: `The number you want displayed for "Today's Lessons". If you use the Wanikani setting, set this to match. Maximum of 100. NOTE: this does not actually change the number of available lessons.`,
default: wkBatchSize * 3,
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();
}
async 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 _start()');
if (!stateStarting) { stateStarting = true; setTimeout(_start, 50); }
else addToDebugLog(`Did not set timeout due to already being in starting state`);
return;
}
addToDebugLog(`We have settings`)
stateStarting = false;
let summary_data = await wkof.Apiv2.get_endpoint('summary');
let totalLessonCount = summary_data.lessons[0].subject_ids.length;
let lessonCountContainers;
if (todaysLessonsFrameLoaded || navBarCountFrameLoaded) {
addToDebugLog(`Frame(s) loaded`)
lessonCountContainers = await getCountContainers();
}
else {
addToDebugLog('No frames loaded')
return;
}
let todaysCountForDisplay = todaysLessonsCount;
if (lessonCountContainers.every(node => node == null)) {
addToDebugLog('No nodes in containers')
return;
}
addToDebugLog('At least one container exists')
if (isNaN(todaysLessonsCount)) {
todaysCountForDisplay = 0;
}
else {
if (settings.setOwnPreferredDaily) todaysCountForDisplay = todaysLessonsCount - (wkBatchSize * 3 - settings.preferredDailyAmount);
}
addToDebugLog(`Setting display amount for Today's Lessons count, set to: ${todaysCountForDisplay}`)
if (lessonCountContainers[0]) lessonCountContainers[0].textContent = settings.showTotalOnly ? totalLessonCount : todaysCountForDisplay + ' / ' + totalLessonCount;
if (lessonCountContainers[1]) lessonCountContainers[1].textContent = settings.showTotalOnly ? totalLessonCount : todaysCountForDisplay;
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';
}
}
addToDebugLog(`Hiding the "Today's" subtitle on the lesson tile`)
// hide "Today's" subtitle
let lessonSubtitle = document.querySelector('.todays-lessons__subtitle');
if (lessonSubtitle && lessonSubtitle.checkVisibility()) {
lessonSubtitle.style.display = 'none';
}
addToDebugLog(`Main function has finished executing`)
if (settings.enableDebugging) printDebugLog();
}
})();