[Userscript] WK Real Numbers

WaniKani Real Numbers is a userscript written by penx. However, due to updates to WK the original script no longer works, so i’ve created this topic to officially adopt the script. Not all people look beyond the original post and because it still contains a broken and outdated link, a lot of people comment they need help, or that the script isn’t working. Because these problems can be avoided, i’d like this topic to replace the original one.

The main purpose of this user script is to turn this:


into this:



Latest version: v4

Download and more info: https://greasyfork.org/en/scripts/11244-wanikani-real-numbers

Known issues:

  • None!
Credits:
  • Original script: penx
  • Contributions by: srfacanha

If you encounter any problems, please let me know below!

19 Likes

This works on Chrome w/Tampermonkey, but I can’t seem to get this working on Firefox 43.0.4 w/Greasemonkey. My stats show up as blank (I see empty grey circles when the script is enabled).

On Chrome, it looks like I changed the first line of main() from this:

      var apikey = GM_getValue('apikey');

to this (obviously not my real apikey):

      var apikey = '1234567890';

However, when I make the same change to the WK Real Numbers script on Firefox, it doesn’t work.

I’m curious though – how is code like “var apikey = GM_getValue(‘apikey’);” supposed to work? It seems like there should be a way of setting a Greasemonkey variable named apikey through some browser interface. I overwrote the code in Chrome, and it worked, but where are we supposed to set the value of apikey if we didn’t overwrite that first line of code?

Also, I assume the real magic of the script happens here:

	if(numberReviews.innerHTML == "42+" || numberLessons.innerHTML == "42+"){
		
		if(apikey){
					
			if(doneReviews){
			
				$.getJSON('https://www.wanikani.com/api/v1/user/'+ apikey +'/study-queue', function(data){
					setTimeout(function() {
						if(data.error){
							alert("API Error: "+data.error.message);
						}else{
							GM_setValue("numberReviews", data.requested_information.reviews_available);
							GM_setValue("numberLessons", data.requested_information.lessons_available);
							GM_setValue("doneReviews", false);
							displayReal(numberReviews, numberLessons);
						}
					}, 0);
				});
				
			}else{

How can I test each line of this code. For example, is it possible to print a javascript dialogue window with the results of numberReviews? A javascript dialogue box that tells me the operation timed out?

httpbloghellodesigncomwp-contentuploads200807hello-coffeejpg

How are you here caffeine ? I just drank you 5 minutes ago??

But let’s be serious for a second.

GM_* functions are to be avoided as much as possible because in my experience, they have very shitty cross-browser capabilities.
Theoretically, the GM_getValue(…) function would store a value in the local storage of your browser. This way, it only has to store your api key once, which is very convenient. Strangely, the GM_* functions usually work fine in Firefox, and fail in Chrome.

It’s very odd replacing the code only worked in Chrome though. It’s
possible to provide dialogue windows with the output or timeout, but not
practical for end users (unless you’d want to solve the issue yourself,
but I’m guessing that if you could, you’d already know about such dialogue windows).

I’ll try to reproduce your problem and come up with a solution for you. I’ll probably just rewrite the script so it doesn’t use these functions any more so future problems are avoided.

EDIT: I could just go ahead and solve the problem, but I’ll only be able to test it within 12 hours (minimum) because I currently have no reviews.

Hey Mempo!  Thanks for the reply!   I assume my one paragraph of squished-together text actually parses into separate paragraphs for you?   I just see one giant paragraph which I’d be amazed if anybody actually read!

I don’t know a lick of Javascript, but I’m usually pretty quick at picking up new languages.   I managed to isolate the problem, but I don’t know enough to resolve it.   The main function has the structure of:

function main()
{
    var apikey = ‘something’;
    alert(“Script started”)

    if( ! apikey ) {
        alert(“apikey is undefined”)
        // Do stuff if apikey is undefined (cut for brevity)
    }

alert(“A”);
var doneReviews = GM_getValue(“doneReviews”, true);
alert(“B”);
var lastUpdate  = GM_getValue(“lastUpdate”, “0”);
var currentTime = new Date().getTime();
The result?

“Script started” gets printed.  “A” gets printed.    However, “B” doesn’t get printed!

The script doesn’t seem to return from the call to GM_getValue(“doneReviews”, true).   You wrote that GM_getValue(…)  stores info as browser local storage – I did some research.  It gets stored in a SQLite DB that belongs to Greasemonkey.  I’m wondering something…  does this script need to run something like

GM_setValue(“doneReviews”, 0);
in order to instantiate the variable in the SQLite DB before we can the variable back using

GM_getValue(“doneReviews”, true)
What do you think?   While I wait for your reply, I’ll try to find this SQLite DB and see if I can find a utility to allow me to poke through it and look at the values.

I just found the location of the WK Real Numbers scripts:

C:\Users\p\AppData\Roaming\Mozilla\Firefox\Profiles\uc44rzdq.default\gm_scripts\WaniKani_Real_Numbers

I see two files:

  1. WK_Real_Numbers.user.js
  2. jquery-1.8.3.min.js
From my reading, there should be a sqlite DB file in gm_scripts.   I see WaniKanify.db, but I don’t see any file named WaniKani_Real_Numbers.db in gm_scripts.   Should it exist?

A missing DB file would certainly explain a failed call to GM_getValue (although I would’ve hoped that Greasemonkey would issue a major complaint if it tried to grab a DB file that didn’t exist rather than silently fail!)


httplh4ggphtcom-GAoYWtkE4ScVRAmNvSBsgIAAAAAAABUBU9nIwPB70xswcoffee252520I252527ve252520come252520to252520talk252520to252520you252520again_thumb25255B125255Dpngimgmax800

I never wondered about the internal workings of those functions actually.
It’s very strange that the script fails after that function call. If an entry is not found it should return the default value.
For  var doneReviews = GM_getValue(“doneReviews”, true);    that should be ‘true’.

I wouldn’t meddle with that DB too much though. As I said earlier, I’ll just change those parts so it never has to use GM_* functions.

PS: nope, your first post is a block of text. Not easy to read ;p

Replace the entire script with the code below and let me know if it works


// ==UserScript==
// @name        WaniKani Real Numbers
// @namespace   mempo
// @author      Mempo
// @description Replaces 42+ with the real number using WaniKani API
// @include     http://www.wanikani.com/
// @include     https://www.wanikani.com/

// @version     3
// @grant       none
// @run-at    document-end
// ==/UserScript==

function main() {
  console.log(‘START OF WRN’);
  //var apikey = GM_getValue(‘apikey’);
  var apiKey = localStorage.getItem(‘apiKey’);
  if (!apiKey) {
    if (window.location.href.indexOf(‘account’) != - 1) {
      retrieveAPIkey();
      apiKey = localStorage.getItem(‘apiKey’);
    } else {
      var okcancel = confirm(‘WaniKani Real Numbers has no API key entered!\nPress OK to go to your settings page and retrieve your API key!’);
      if (okcancel == true) {
        window.location = ‘https://www.wanikani.com/account’;
      }
    }
  }
 
  var doneReviews = Boolean(localStorage.getItem(‘WRN_doneReviews’) || true);
  var lastUpdate = Number(localStorage.getItem(‘WRN_lastUpdate’) || 0);
  //var doneReviews = GM_getValue(‘doneReviews’, true);
  //var lastUpdate = GM_getValue(‘lastUpdate’, ‘0’);
  var currentTime = new Date().getTime();
  if ((currentTime - lastUpdate) > 120000) {
    localStorage.setItem(‘WRN_lastUpdate’, currentTime.toString());
    doneReviews = true;
  }
  if (window.location.href.indexOf(‘review’) != - 1 || window.location.href.indexOf(‘lesson’) != - 1) {
    localStorage.setItem(‘WRN_doneReviews’, “true”);
  } else {
    var numberReviews = document.getElementsByClassName(‘reviews’) [0].getElementsByTagName(‘span’) [0];
    var numberLessons = document.getElementsByClassName(‘lessons’) [0].getElementsByTagName(‘span’) [0];
    if (numberReviews.innerHTML == ‘42+’ || numberLessons.innerHTML == ‘42+’) {
      if (apiKey) {
        if (doneReviews) {
          $.getJSON(‘https://www.wanikani.com/api/user/’ + apiKey + ‘/study-queue’, function (data) {
            setTimeout(function () {
              if (data.error) {
                alert('API Error: ’ + data.error.message);
              } else {
                localStorage.setItem(‘WRN_numberReviews’, data.requested_information.reviews_available);
                localStorage.setItem(‘WRN_numberLessons’, data.requested_information.lessons_available);
                localStorage.setItem(‘WRN_doneReviews’, “”);
                displayReal(numberReviews, numberLessons);
              }
            }, 0);
          });
        } else {
          displayReal(numberReviews, numberLessons);
        }
      }
    }
  }
}
window.addEventListener(‘load’, main, false);
//GM_registerMenuCommand(‘WaniKani Real Numbers: Manually enter API key’, setAPIkey, null, null, ‘R’);
//GM_registerMenuCommand(‘WaniKani Real Numbers: Reset API key’, resetAPIkey, null, null, ‘e’);
function resetAPIkey() {
  GM_deleteValue(‘apikey’);
  alert(‘WaniKani Real Numbers API key reset!’);
}

function retrieveAPIkey() {
  for(var i=0;i<document.getElementsByClassName(‘span6’).length;i++){
    if(document.getElementsByClassName(‘span6’)[i].getAttribute(‘placeholder’)==“Key has not been generated”)
      apiKey = document.getElementsByClassName(‘span6’) [i].getAttribute(‘value’);
  }
  alert('WaniKani Real Numbers API key set to: ’ + apiKey);
  if (apiKey) {
    localStorage.setItem(‘apiKey’, apiKey);
    localStorage.setItem(‘WRN_doneReviews’, ‘true’);
    //GM_setValue(‘apikey’, apikey);
    //GM_setValue(‘doneReviews’, true);
  }
}

function setAPIkey() {
  var apiKey = prompt(‘Enter API key for WaniKani Real Numbers:’);
  if (apiKey) {
    localStorage.setItem(‘apiKey’, apiKey);
    localStorage.setItem(‘WRN_doneReviews’, true);
    //GM_setValue(‘apikey’, apikey);
    //GM_setValue(‘doneReviews’, true);
  }
}

function displayReal(numberReviews, numberLessons) {
  numberReviews.innerHTML = localStorage.getItem(‘WRN_numberReviews’);
  numberLessons.innerHTML = localStorage.getItem(‘WRN_numberLessons’);
 
  //numberReviews.innerHTML = GM_getValue(‘numberReviews’);
  //numberLessons.innerHTML = GM_getValue(‘numberLessons’);
}

This thread delights me. I have another date with caffeine in just a moment, I think I hear the water boiling.

Subtractem said... This thread delights me. I have another date with caffeine in just a moment, I think I hear the water boiling.
 Jesus... you're right.   That's what I'm missing right now.   It's been a couple of hours since I had my last cup.  I'm gonna need another one before going back to Javascript debugging!   :-)   Thank you for the gentle reminder!

Whoa!   That went way better than expected – flawless!

I installed the script, it automagically got my API key, and just worked right out of the box.

You weren’t kidding when you said that GM_* functions are to be avoided!   Why do they exist when localStorage.(get|set)Item works so flawlessly?

Hey – thank you!!   I’m going back to Tokyo for ~6 months this year.  If I see you there, consider drinks on me!   I’m a very promiscuous person when it comes to web browsers.  I like to cat around between FF and Chrome, and you’ve put them back on equal footing for me.  :)

caffeine said... Whoa!   That went way better than expected -- flawless!

I installed the script, it automagically got my API key, and just worked right out of the box.
Aaah, how beautiful when software does exactly what it's supposed to...

You weren't kidding when you said that GM_* functions are to be avoided!   Why do they exist when localStorage.(get|set)Item works so flawlessly?
The localStorage API is something that was introduced in HTML5 (I think), so it's new compared to the Greasemonkey functions. It's now a standard implemented by all browsers. And those GM functions surely worked at some point (still do in Firefox mostly), but since Chrome had some updates things are a bit wonky. So not the best choice any more if you want to develop for multiple browsers ;p

Hey -- thank you!!   I'm going back to Tokyo for ~6 months this year.  If I see you there, consider drinks on me!   I'm a very promiscuous person when it comes to web browsers.  I like to cat around between FF and Chrome, and you've put them back on equal footing for me.  :)
 ![httpsmediagiphycommedia10aIbqnbAbjX9egiphygif](upload://zOHxKikDLigiwa2BYgbcRt2KWdJ.gif)

Never been to Japan actually ;p
Just glad to help someone!

The GM functions also store values that can be accessed across multiple pages, whereas localstorage (for security reasons, because the web page itself can access them) can’t be accessed from a different site or between http and https.  For that reason, GM is nice for things like global setting.

1 Like

I just stumbled across something:

http://wiki.greasespot.net/@grant

Should there be two lines at the top that say:

// @grant GM_setValue
// @grant GM_getValue

When I add these two lines at the top of the script, I get a little further.   Your change is obviously the right way to go, but I’m sure this is partially to blame for why the script didn’t work for me on Firefox to begin with.   The call to GM_getValue() actually succeeds when I use the proper @grant directive.

In the end, it doesn’t matter.  The only reason why I’m asking is because I’m honestly curious, and I would like to learn how to write Greasemonkey scripts some day.  :-)

BTW, Japan is awesome.  You should definitely try  making it out there.  It’ll blow your head right off, especially if you can manage to see it from the eyes of someone who lives there temporarily, as opposed to a tourist.   Especially as a foreigner.  They are so gracious, and we’re given a lot of slack.   My coworkers say that I’m given “free pass” to behave poorly because it’s expected that foreigners are crude.    By “free pass” they usually mean things like getting to work by 10am, talking (in a low voice) on the trainride home, trying to strike up a conversation with a girl on the street.   It’ll blow your mind how hard they try at everything there.  Everybody – no matter how important or unimportant their job is, they always give 100% and treat their job with dignity and respect.

caffeine said... I just stumbled across something:

http://wiki.greasespot.net/@grant

Should there be two lines at the top that say:

// @grant GM_setValue
// @grant GM_getValue

When I add these two lines at the top of the script, I get a little further.   Your change is obviously the right way to go, but I'm sure this is partially to blame for why the script didn't work for me on Firefox to begin with.   The call to GM_getValue() actually succeeds when I use the proper @grant directive.

In the end, it doesn't matter.  The only reason why I'm asking is because I'm honestly curious, and I would like to learn how to write Greasemonkey scripts some day.  :-)

BTW, Japan is awesome.  You should definitely try  making it out there.  It'll blow your head right off, especially if you can manage to see it from the eyes of someone who lives there temporarily, as opposed to a tourist.   Especially as a foreigner.  They are so gracious, and we're given a lot of slack.   My coworkers say that I'm given "free pass" to behave poorly because it's expected that foreigners are crude.    By "free pass" they usually mean things like getting to work by 10am, talking (in a low voice) on the trainride home, trying to strike up a conversation with a girl on the street.   It'll blow your mind how hard they try at everything there.  Everybody -- no matter how important or unimportant their job is, they always give 100% and treat their job with dignity and respect.
 Of course if you want to use GM functions, you should include the @grant, but I deleted them in the latest version because I no longer used them. But they were there in the original script, so that's not the problem.

And you're saying people aren't even allowed to talk on the train? OK GUYS, I'm going to Japan. Seems like the place for me ;p
1 Like
rfindley said... The GM functions also store values that can be accessed across multiple pages, whereas localstorage (for security reasons, because the web page itself can access them) can't be accessed from a different site or between http and https.  For that reason, GM is nice for things like global setting.
 Yea... That was something really weird. I had several tabs open and decided to check my apiKey value and it didn't work on other websites :/
But hey, as long as it works on wanikani.com/*
As you said, for security reasons, it's maybe even for the better.

This is the first script I am trying to use.  Tampermonkey is installed and the code is in there, but it says it needs my API key.  I have it, but don’t know what to do with it…

Oh, nevermind.  I got it.  :D  Yay!

Added this script. Thank you so much!

Yeah… so… I’ve tried to use this a couple of times and I just can’t get it to work. I’ve tried manually inserting my api key into the code but first, I don’t seem to find the place where it fits and, second, I simply don’t know if there is a particular sintaxis in which to put it. All I get is “API Error: User does not exist.” every time I load a WK page, no prompt to introduce my api nor anything else.

I’m using Firefox 50.1.0 and Greasemonkey 3.9. I also have a couple of other scripts for WK but I see no reason for the to interfere with this one… but of course I wouldn’t really know

 


squashedmos said... Yeah... so... I've tried to use this a couple of times and I just can't get it to work.
You probably have an old API key in you browser storage.  But even if that's not the problem try the following:

Open the Javascript console (press F12 and select the Console tab)
Paste the following command (with your API key inserted, and be sure there aren't any spaces around you API key) and press enter:

localStorage.setItem('apiKey', 'enter_your_apikey_here');