您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fix weird issues with the levelups on wkstats that are assumed to be caused by new additions of kanji to levels
- // ==UserScript==
- // @name WKStats Levelup Fix
- // @namespace https://greasyfork.org/en/users/11878
- // @version 2.0.2
- // @description Fix weird issues with the levelups on wkstats that are assumed to be caused by new additions of kanji to levels
- // @author Inserio
- // @match https://www.wkstats.com/progress/dashboard
- // @match https://www.wkstats.com/progress/level-up
- // @match https://www.wkstats.com/progress/projections
- // @match https://www.wkstats.com/
- // @icon https://www.google.com/s2/favicons?sz=64&domain=wkstats.com
- // @license MIT; http://opensource.org/licenses/MIT
- // @run-at document-start
- // @grant none
- // ==/UserScript==
- /*global wkof, wkdata, wkstats, calc_levelups, log, yyyymmdd, duration, wklogs*/
- (function() {
- 'use strict';
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", init);
- } else {
- init();
- }
- function init() {
- const calc_stats = wkof.support_files['calc_stats.js'];
- if (calc_stats === undefined || calc_stats === null || !calc_stats.includes('1.0.7')) // only run on this version
- return;
- wkof.wait_state(/* state_var */ "wkof.wkstats.levelups",/* value */ "ready",/* callback */ overwriteFunction,/* persistent */ false);
- }
- function overwriteFunction() {
- console.log('Overriding the calc_levelups() function definition');
- // eslint-disable-next-line no-global-assign
- calc_levelups = function() {
- wklogs['levelups'].length = 0; // clear existing logs (comment out if you want to compare results with the default version, which gets run first)
- log('levelups', 'Overriding previous data of calc_levelups() with modified version');
- let level_times = wkstats.level_times = [];
- // For each level, initialize a valid range of possible level times (initial = any time)
- for (let level = 1; level <= wkof.user.subscription.max_level_granted; level++) {
- level_times[level] = {
- min: new Date(0),
- max: wkdata.load_time,
- dates: [],
- source: 'unknown',
- };
- }
- // Using level resets, throw out old level start times (by marking the min start time)
- for (let reset_idx = 0; reset_idx < wkdata.resets.length; reset_idx++) {
- let reset = wkdata.resets[reset_idx];
- let reset_time = new Date(reset.confirmed_at);
- for (let level = reset.target_level; level <= wkof.user.level; level++) {
- let level_time = level_times[level];
- // Ignore resets that happened before this level-up.
- if (reset_time < level_time.min) continue;
- // Update the min start time.
- level_time.min = reset_time;
- delete level_times[level].reset_time;
- }
- level_times[reset.target_level].reset_time = reset_time;
- }
- // Using the newest levelup record for each level, set known start times.
- let oldest_levelup = {index: -1, time: new Date('2999-01-01')};
- for (let levelup_idx = 0; levelup_idx < wkdata.levelups.length; levelup_idx++) {
- let levelup = wkdata.levelups[levelup_idx];
- let level = levelup.level;
- if (level > wkof.user.level) continue;
- let unlocked_time = new Date(levelup.unlocked_at);
- let level_time = level_times[level];
- // Check if this is the oldest recorded level-up, which may be invalid.
- if (unlocked_time < oldest_levelup.time) {
- oldest_levelup = {index: levelup_idx, time: unlocked_time, level: level};
- }
- // Ignore levelups that were invalidated by a reset.
- if (unlocked_time < level_time.min) continue;
- // Update the level start time.
- level_time.min = unlocked_time;
- level_time.source = 'APIv2 level_progressions';
- if (!levelup.abandoned_at && levelup.passed_at) {
- level_time.max = new Date(levelup.passed_at);
- } else if (level === wkof.user.level) {
- level_time.max = new Date();
- }
- }
- let items = wkdata.items;
- let level_progressions = wkdata.levelups;
- let first_recorded_date = level_progressions[Math.min(...Object.keys(level_progressions))].unlocked_at;
- // Find indefinite level ups by looking at lesson history
- // Sort lessons by level then unlocked date
- items.forEach((item) => {
- if (
- (item.object !== 'kanji' && item.object !== 'radical') ||
- !item.assignments ||
- !item.assignments.unlocked_at ||
- item.assignments.unlocked_at >= first_recorded_date
- )
- return;
- let date = new Date(item.assignments.unlocked_at);
- if (!level_times[item.data.level]) {
- level_times[item.data.level] = {};
- }
- if (!level_times[item.data.level].dates[date.toDateString()]) {
- level_times[item.data.level].dates[date.toDateString()] = [date];
- }
- else {
- level_times[item.data.level].dates[date.toDateString()].push(date);
- }
- });
- // Discard dates with less than 10 unlocked
- // then discard levels with no dates
- // then keep earliest date for each level
- for (let [level, {min, max, dates, source}] of Object.entries(level_times)) {
- for (let [date, data] of Object.entries(dates)) {
- if (data.length < 10)
- delete dates[date];
- }
- if (Object.keys(level_times[level].dates).length === 0) {
- delete level_times[level].dates;
- continue;
- }
- //level_times[level].min = Object.values(dates).reduce((low, curr) => (low < curr ? low : curr), Date.now()).sort((a, b) => (a.getTime() - b.getTime()))[0];
- level_times[level].min = Object.values(dates).reduce((acc,item)=>{let smallest=item.reduce((a,b)=>a<b?a:b);return acc<smallest ? acc : smallest;}, new Date());
- }
- // Map to array of [[level0, date0], [level1, date1], ...] Format
- //levels = Object.entries(levels).map(([level, date]) => [Number(level), date]);
- // Add definite level ups from API
- Object.values(level_progressions).forEach(lev => {
- if (level_times[lev.level].source === 'APIv2 level_progressions') return;
- level_times[lev.level] = {
- min: new Date(lev.unlocked_at),
- max: (lev.passed_at ? new Date(lev.passed_at) : wkdata.load_time),
- source: 'APIv2 level_progressions'
- };});
- for (let level = 1; level <= wkof.user.level; level++) {
- let level_data = level_times[level];
- if (level_data.source === 'APIv2 level_progressions') continue;
- if (level < level_times.length - 1) {
- let next_level_data = level_times[level+1];
- if (level_data.max.getTime() === wkdata.load_time.getTime())
- level_data.max = next_level_data.min;
- }
- }
- // Calculate durations
- let durations = wkstats.level_durations = [];
- for (let level = 1; level <= wkof.user.level; level++) {
- let level_time = level_times[level];
- durations[level] = (level_time.max - level_time.min) / 86400000;
- }
- log('levelups','--[ Level-ups ]----------------');
- let level_durations = wkstats.level_durations;
- // Log the current level statuses.
- log('levelups','Started: '+yyyymmdd(wkof.user.started_at));
- if (wkof.user.restarted_at) {
- log('levelups','Restarted: '+yyyymmdd(wkof.user.restarted_at));
- }
- for (let level = 1; level <= wkof.user.level; level++) {
- let level_time = level_times[level];
- let level_duration = level_durations[level];
- if (level_time.reset_time) {
- log('levelups','Reset');
- }
- // Flag any unusual level durations.
- if (level < wkof.user.level && (level_duration < 3.0 || level_duration > 2000))
- log('levelups','###################');
- log('levelups','Level '+level+': ('+yyyymmdd(level_time.min)+' - '+yyyymmdd(level_time.max)+') - '+
- duration(level_duration)+' (source: '+level_time.source+')');
- }
- wkof.set_state('wkof.wkstats.levelups', 'ready');
- };
- // Immediately run in order to overwrite all of the configurations from the default run
- calc_levelups();
- }
- })();