WaniKani Lesson Cap

Limits the amount of lessons you can do with your current review queue.

// ==UserScript==
// @name         WaniKani Lesson Cap
// @namespace    https://github.com/valeth
// @version      0.6.4
// @description  Limits the amount of lessons you can do with your current review queue.
// @author       valeth
// @match        https://www.wanikani.com/dashboard
// @match        https://www.wanikani.com/lesson
// @match        https://www.wanikani.com/lesson/session
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

/* jshint ignore:start */

const debugging = false;
// Generated by CoffeeScript 2.0.2
(function() {
  // =======[ WKOF Guard ]=======

  // coffeelint: disable=max_line_length
  var btn_continue, calculate_score, confirmed, continue_button, default_settings, get_items, get_or_wait, install_menu, install_settings, log, render_all, render_available, render_results, round2, settings, settings_dialog, srs_levels, start_button, titleize, update;

  if (window.wkof == null) {
    confirmed = confirm('WaniKani Lesson Cap requires the WaniKani Open Framework.\nDo you want to be forwarded to the installation instructions?');
    if (confirmed) {
      window.location.href = "https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549";
    }
    return;
  }

  // coffeelint: enable=max_line_length

  // =======[ Globals ]=======
  btn_continue = void 0;

  settings_dialog = void 0;

  default_settings = {
    max_score: 100
  };

  srs_levels = [
    {
      stage: 1,
      rating: 6,
      ident: 'apprentice1'
    },
    {
      stage: 2,
      rating: 3,
      ident: 'apprentice2'
    },
    {
      stage: 3,
      rating: 1.04,
      ident: 'apprentice3'
    },
    {
      stage: 4,
      rating: 0.51,
      ident: 'apprentice4'
    },
    {
      stage: 5,
      rating: 0.14,
      ident: 'guru1'
    },
    {
      stage: 6,
      rating: 0.07,
      ident: 'guru2'
    },
    {
      stage: 7,
      rating: 0.03,
      ident: 'master'
    },
    {
      stage: 8,
      rating: 0.01,
      ident: 'enlightened'
    }
  ];

  // =======[ Utility functions ]=======
  log = {
    info: function(message) {
      return console.log("[WKLC] " + message);
    },
    error: function(message) {
      return console.error("[WKLC] " + message);
    },
    debug: function(message) {
      if (debugging) {
        return console.log("[WKLC] " + message);
      }
    },
    inspect: function(thing) {
      if (debugging) {
        return console.debug(thing);
      }
    }
  };

  // Format a given string into "titleized" format.
  // Example:
  //   someIdentifier1 -> Some Identifier 1
  titleize = function(string) {
    var char, i, len, ref, tmp;
    tmp = string[0].toUpperCase();
    ref = string.slice(1);
    for (i = 0, len = ref.length; i < len; i++) {
      char = ref[i];
      if (char === char.toUpperCase()) {
        tmp += ` ${char}`;
      } else {
        tmp += char;
      }
    }
    return tmp;
  };

  round2 = function(num) {
    return Math.round(num * 100) / 100;
  };

  get_or_wait = function(key, callback) {
    var value;
    value = wkof.get_state(key);
    if (value != null) {
      return callback(value);
    }
    return wkof.wait_state(key, "*", function(value, _) {
      return callback(value);
    });
  };

  // =======[ Rendering ]=======
  start_button = function(action) {
    var btn_start, lesson_queue_count;
    btn_start = $("#start-session > a");
    if (btn_start[0] == null) {
      return;
    }
    lesson_queue_count = $("#lesson-queue-count");
    switch (action) {
      case "disable":
        log.info("Disabling lesson start button...");
        btn_start.addClass("wklc-force-hidden");
        return lesson_queue_count.addClass("wklc-lesson-count");
      case "enable":
        log.info("Enabling lesson start button...");
        btn_start.removeClass("wklc-force-hidden");
        return lesson_queue_count.removeClass("wklc-lesson-count");
    }
  };

  continue_button = function(action) {
    if (btn_continue == null) {
      btn_continue = $("#lesson-ready-continue");
    }
    if (btn_continue[0] == null) {
      return;
    }
    switch (action) {
      case "disable":
        log.info("Disabling lesson continue button...");
        return btn_continue.remove();
      case "enable":
        log.info("Enabling lesson continue button...");
        return $("#screen-lesson-ready .btn-set").append(btn_continue);
    }
  };

  render_all = function() {
    return get_items(function(score, stats) {
      var batch_size, batches, max_score, remaining_score, score_per_item;
      render_results(score, stats);
      batch_size = $.jStorage.get("l/batchSize");
      max_score = settings("max_score");
      remaining_score = max_score - score;
      score_per_item = batch_size * (srs_levels[0].rating / 2);
      batches = Math.floor(remaining_score / score_per_item);
      if (batches >= 1) {
        start_button("enable");
      }
      if (batches >= 2) {
        continue_button("enable");
      }
      return render_available(batch_size, batches);
    });
  };

  render_results = function(score, stats) {
    var max_score, wklc, wklc_items;
    if ($("#lessons-summary")[0] == null) {
      return;
    }
    log.debug("Rendering results...");
    wklc = $(".wklc");
    if (wklc[0] != null) {
      wklc.remove();
    }
    max_score = settings("max_score");
    wklc_items = stats.reduce(function(acc, stat, index) {
      return acc += `<li class='wklc-item wklc-${srs_levels[index].ident}'\n    title='${titleize(srs_levels[index].ident)}'>\n  <span>${round2(stat.count)}</span>\n  <span>(${round2(stat.score)})</span>\n</li>`;
    }, "");
    return $("#lessons-summary").prepend($(`<div class="wklc">\n  <ul class="wklc-items">${wklc_items}</ul>\n  <div class='wklc-right'>\n    <div class='wklc-item wklc-score'>\n      <span>Score:</span>\n      <span>${round2(score)}</span> / <span>${max_score}</span>\n    </div>\n    <button class='wklc-item wklc-btn-settings'\n            title='Settings'>⚙</button>\n  </div>\n</div>`));
  };

  render_available = function(batch_size, batches) {
    var allowed_lessons, lessons_queue_count, title;
    if ($("#lessons-summary")[0] == null) {
      return;
    }
    allowed_lessons = $(".wklc-allowed-lessons");
    if (allowed_lessons[0] != null) {
      allowed_lessons.remove();
    }
    lessons_queue_count = +$("#lesson-queue-count").text();
    if (lessons_queue_count === 0 && !debugging) {
      return;
    }
    title = `This is the amount of batches of ${batch_size}\nlessons you can do without exceeding the max score.`;
    allowed_lessons = $(`<span class='wklc-allowed-lessons' title='${title}'>\n  ${batches + ' Available'}\n</span>`);
    if (batches < 1) {
      allowed_lessons.addClass("disabled");
      allowed_lessons.attr("title", "Lessons button disabled because you can do\nless than one session before exceeding max score.");
    }
    return $("#start-session").append(allowed_lessons);
  };

  // ======[ API Requests and Calculations ]=======
  get_items = function(callback) {
    return wkof.ItemData.get_items({
      wk_items: {
        options: {
          assignments: true
        },
        filters: {
          srs: {
            value: ["lock", "burn"],
            invert: true
          },
          level: "1 to +0" // from first level to current
        }
      }
    }).then(function(items) {
      var score, stats;
      [score, stats] = calculate_score(items);
      return callback(score, stats);
    });
  };

  calculate_score = function(items) {
    var stats, total_score;
    stats = srs_levels.map(function() {
      return {
        count: 0,
        score: 0.0
      };
    });
    items.forEach(function(cur) {
      var stage;
      stage = cur.assignments.srs_stage - 1;
      if (stage < 0 || stage > 7) {
        return;
      }
      stats[stage].count += 1;
      return stats[stage].score = (stats[stage].count * srs_levels[stage].rating) / 2;
    });
    wkof.set_state("wklc.stats", stats);
    total_score = stats.reduce(function(acc, cur) {
      return acc += round2(cur.score);
    }, 0);
    wkof.set_state("wklc.current_score", total_score);
    return [total_score, stats];
  };

  // =======[ Setup ]=======
  update = function() {
    // need to disable buttons on every update again
    start_button("disable");
    continue_button("disable");
    return render_all();
  };

  install_menu = function() {
    var open_settings;
    open_settings = function() {
      if (settings_dialog != null) {
        return settings_dialog.open();
      }
    };
    wkof.Menu.insert_script_link({
      script_id: "wklc",
      submenu: "Settings",
      title: "WaniKani Lesson Cap",
      on_click: open_settings
    });
    return $(document).on("click", ".wklc-btn-settings", open_settings);
  };

  settings = function(key) {
    return wkof.settings.wklc[key];
  };

  install_settings = function() {
    var save_settings;
    save_settings = function() {
      wkof.Settings.save("wklc");
      update();
      return log.info("Settings saved");
    };
    wkof.Settings.load("wklc", default_settings);
    return settings_dialog = new wkof.Settings({
      script_id: "wklc",
      title: "WaniKani Lesson Cap",
      on_save: save_settings,
      settings: {
        pg_general: {
          type: "page",
          label: "General",
          content: {
            max_score: {
              type: "number",
              label: "Max score"
            }
          }
        }
      }
    });
  };

  $(document).on("click", "#batch-items .active-quiz", update);

  $(document).on("click", "#quiz-ready-continue", update);

  $(document).ready(function() {
    var modules;
    modules = "Apiv2, ItemData, Menu, Settings";
    // coffeelint: disable=max_line_length
    wkof.load_css("https://raw.githubusercontent.com/valeth/wanikani-lesson-cap/wkof/compiled/style.css");
    // coffeelint: enable=max_line_length
    wkof.include(modules);
    wkof.ready("Menu").then(install_menu);
    wkof.ready("Settings").then(install_settings);
    return wkof.ready(modules).then(update);
  });

}).call(this);


/* jshint ignore:end */