WaniKani Google Sheet—Pull data from API version 2 to Sheet

The example I gave was more about how to use the new API than it was a concrete implementation of the exact routine you’re trying to write.

A review in the API is a combination of a meaning and reading review on WaniKani. So if you answer both the meaning and reading for an item, it’ll show up in this set.

There are a lot of reviews, and the API doesn’t return all of them in one go (that would be one enormous reply to send). Instead it returns a number of them at a time, and you can use the pagination options (e.g using the next_url field present in the returned object) to get the next part of the set if there is more data availlable.

They don’t, for as far as I know, you have to accumulate these yourself if you want to find out the numbers. You can either use the assignment endpoint for the current state or the reviews endpoint to compute the history over time. They both have srs stage-related fields giving you this information.

This one seems to me like it was designed to report the SRS intervals, not the items itself.

For the current state, you can use the assignments endpoint and look at the srs_stage field, which gives you information on which stage the items are currently in. You can also use this as a query parameter to filter out just the Guru items. It even has a subject_types filter which you can use to only get radicals. Combining the two should give you what you want.

Thank you for your in depth reply. I’m getting closer. I really do appreciate your patience. I’m not a Javascript coder, although I do code in Matlab and used to in Java. But I would like to learn.

Thanks for pointing me in the right direction. So if I wanted to filter out and only grab assignments that are radicals and at the Guru level, my Get call would look like below? Does that seem right?

var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=5&srs_stages=6&subject_types=radical', {
    method: 'GET',
    headers: {
        'Wanikani-Revision': '20170710',
        'Authorization': 'Bearer ' + apiKey
    }
})

If yes, could you point me towards how to get info from the raw variable I have now? If I just want to know how many returned, is that something like this?

var radGuru = raw.totalcount

Thanks again!

No need to specify srs_stages twice, you can just give multiple values in one go by separating them by commas, thus srs_stages=5,6.

After that, you’ll need to actually make the request and parse the result as a JSON object like you did in your original code before you can actually use it.

Alright! I made perhaps the worst Javascript code of all time, but I did it! I had to do a lot of Get calls since I had to filter for Radical/Apprentice, Radical/Guru, etc, all the way to Vocab/Burned. But I was able to run it and get my code to export to my spreadsheet. Lovely!

However, there seem to be some inaccuracies. I’m not sure why, but it returns numbers that are off by +/- 1. Here are the results below. It compares what the code outputs to what I manually see. You can see that Guru is good, but Apprentice, Master, Enlightenment, and Burned are off. For example, it says I have 331 apprentice items, but Wanikani.com shows 330 apprentice items.

Spreadsheet snapshot:

Wanikani.com snapshot:

Any ideas of why it might be off? Happy to share my (terrible) code if that’s helpful.

It’s hard to tell just from this. If the code is correct there shouldn’t be a difference.

Is there any reason you’re writing a script for this? I’m pretty sure there are already a number of scripts available that can just export this information as a csv, which would probably be a lot easier. @prouleau, the Item Inspector can export as a csv, right? I think that would be exactly the functionality you’re looking for.

The script is automatic though. It runs on it’s own. That’s why it’s so useful to me. It auto-fills the excel each day, in the exact format I want.

Here’s the full code, terribleness and all, if that helps. You can see that I ask to print to the log the total radicals that I’ve burned. In the log it says 135 radicals, even though my Wanikani website says 134. So whatever’s happening is happening right after I fetch it. Maybe I can try to find out exactly what it’s exporting and why there are 135 in the total count, while WaniKani says there should be 134 radicals But any insight is helpful!

var apiKey = '[My API Code]';

function fetchData2() {

  //Radicals  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=1,2,3,4&subject_types=radical', {
      method: 'GET',
      headers: {
          'Wanikani-Revision': '20170710',
          'Authorization': 'Bearer ' + apiKey
      }
  })
    var parsed = JSON.parse(raw);
    var radA = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=5,6&subject_types=radical', {
      method: 'GET',
      headers: {
          'Wanikani-Revision': '20170710',
          'Authorization': 'Bearer ' + apiKey
      }
  })
  var parsed = JSON.parse(raw);
  var radG = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=7&subject_types=radical', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var radM = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=8&subject_types=radical', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var radE = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=9&subject_types=radical', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var radB = parsed.total_count;
  console.log(radB);
  
  
  //Kanji 
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=1,2,3,4&subject_types=kanji', {
      method: 'GET',
      headers: {
          'Wanikani-Revision': '20170710',
          'Authorization': 'Bearer ' + apiKey
      }
  })
    var parsed = JSON.parse(raw);
    var kanjiA = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=5,6&subject_types=kanji', {
      method: 'GET',
      headers: {
          'Wanikani-Revision': '20170710',
          'Authorization': 'Bearer ' + apiKey
      }
  })
  var parsed = JSON.parse(raw);
  var kanjiG = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=7&subject_types=kanji', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var kanjiM = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=8&subject_types=kanji', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var kanjiE = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=9&subject_types=kanji', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var kanjiB = parsed.total_count;
  
  //Vocab
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=1,2,3,4&subject_types=vocabulary', {
      method: 'GET',
      headers: {
          'Wanikani-Revision': '20170710',
          'Authorization': 'Bearer ' + apiKey
      }
  })
    var parsed = JSON.parse(raw);
    var vocabA = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=5,6&subject_types=vocabulary', {
      method: 'GET',
      headers: {
          'Wanikani-Revision': '20170710',
          'Authorization': 'Bearer ' + apiKey
      }
  })
  var parsed = JSON.parse(raw);
  var vocabG = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=7&subject_types=vocabulary', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var vocabM = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=8&subject_types=vocabulary', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var vocabE = parsed.total_count;
  
  var raw = UrlFetchApp.fetch('https://api.wanikani.com/v2/assignments?srs_stages=9&subject_types=vocabulary', {
    method: 'GET',
    headers: {
      'Wanikani-Revision': '20170710',
      'Authorization': 'Bearer ' + apiKey
    }
  })
  var parsed = JSON.parse(raw);
  var vocabB = parsed.total_count;

 var totalA = radA+kanjiA+vocabA;
 var totalG = radG+kanjiG+vocabG;
 var totalM = radM+kanjiM+vocabM;
 var totalE = radE+kanjiE+vocabE;
 var totalB = radB+kanjiB+vocabB;

 //Write to Sheet
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName('Raw data');


  var cell = sheet.getRange(sheet.getLastRow(), 1);
  var col = 0;
  function appendCell(data) {
    cell.offset(1, col++).setValue(data);
  }
    
  appendCell(new Date());
  
  appendCell('Blank');
  
  appendCell(totalA);
  appendCell(totalG);
  appendCell(totalM);
  appendCell(totalE);
  appendCell(totalB);
  
  appendCell(radA);
  appendCell(radG);
  appendCell(radM);
  appendCell(radE);
  appendCell(radB);
  
  appendCell(kanjiA);
  appendCell(kanjiG);
  appendCell(kanjiM);
  appendCell(kanjiE);
  appendCell(kanjiB);
  
  appendCell(vocabA);
  appendCell(vocabG);
  appendCell(vocabM);
  appendCell(vocabE);
  appendCell(vocabB);
  
  appendCell(radA + radG + radM + radE + radB);
  
  appendCell(kanjiA + kanjiG + kanjiM + kanjiE + kanjiB);
  
  appendCell(vocabA + vocabG + vocabM + vocabE + vocabB);
    
  appendCell(totalA + totalG + totalM + totalE + totalB);

}


Interestingly enough, when I run Viet’s code from above, it also says I have 135 burned radicals, just like my code does. But the WaniKani website says 134 burned radicals.

So it’s not my code, it seems to be a general error. @viet - Maybe you can shed some light here?

Viet’s Code:
image

My Code:
image

WaniKani Website:
image

I’ve put my detective hat on.

So Viet’s code, my code, and also Wkstats.com says that I have 135 radicals burned. The Wanikani website says that I have 134 burned radicals. Where’s this mystery radical coming from?

On wkstats, I can see the discrepancy. It says I have one radical burned on Level 29, the radical Hills. Since I’m level 13 right now, it seems unlikely that I should have this burned. The WaniKani website agrees. Seems like this is a bigger bug than just my code. Any idea how to report this?

image

The likely source of the count discrepancy is the idea of hidden subjects. We “hide” subjects when we no longer want to expose it to the user. Assignments have a “hidden” field to help identify these. The API returns both hidden and not hidden data by default.

If you wish to match the counts on the WaniKani app then you’ll need to filter out the hidden assignments. The assignment endpoint has a query filter parameter for this, which is outlined in the documentation.


Reading your last post more carefully…

The subjects can be moved around to different levels if our content team decides it makes sense to do so. We don’t reset user progress for the subject when this happens.

Here is the log where hills radical was moved from level 8 to 29

For this case, if you want the counts to match with the app, then you’ll need to filter out assignments where their associated subject’s level is greater than your user level.

The assignments endpoint has a query filter for levels, which can help you accomplish the goal. For the levels filter pass in a comma delimited list of every level up to your current user level.

3 Likes

Yes Item Inspector export to spreadsheet. But I wonder how OP autofill works. There are security in a browser that prevent writing into a user file.

Edit: Now I see. The data goes to Googlesheet which is web based. This give ideas for Item Inspector.

1 Like

Hi Viet -

Thanks for your reply. I will look into filtering out the hidden items.

As for the Hills radical, even though the Wanikani website does not show it as burned in my list of burned radicals, if I go directly to the Hills radical page, it shows it as burned. That’s a bit strange. I also don’t remember hills, so I figured I would resurrect it. However that button is missing from the Hills radical page. Interesting.

I was able to get my code updated and I wrote a summary topic here. Thank you all so much for your help with this code. It was a doozy, but it was fun to learn. :slight_smile:

I’ve had my sheet pulling data for nearly three years now and today is the first time I’ve had a failure notification. With the recent changes to the all reviews endpoint, I was curious if anyone else was having issues?

Exception: Request failed for https://api.wanikani.com returned code 503. Truncated server response: <!DOCTYPE html>
	<html>
	  <head>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<meta charset="utf-8">
		<title>Applicat... (use muteHttpExceptions option to examine full response)
    at fetchData_(Code:240:34)
    at generateDataFromAssignments(Code:166:24)
    at generateNewDailyEntry_(Code:251:3)

Looking at the gist, I can’t see it calls that Get All Reviews endpoint, so I’m hoping it’s just a bug.

This script stopped working for me a couple of days ago. I am assuming that it’s due to all the Wanikani backend changes going on. It is failing on the function “generateNewDailyEntry_” with the error “TypeError: Cannot read properties of undefined (reading ‘srsStages’)”

@Joeni (or anyone), are you still using this script? Are you having any problems with it, and if so, are there any easy solutions?

1 Like

I have had the last two days fail, aye

Haven’t had time to look at what’s actually happening. I suspect it would be a simple tweak if @viet could advise

1 Like

Okay, am looking now and it started failing since the API changes (@tofugu-scott)

Specifically it’s failing on this segment:

TypeError: Cannot read properties of undefined (reading 'srsStages')
    at [unknown function](Code:171:29)
    at generateDataFromAssignments(Code:168:21)
    at generateNewDailyEntry_(Code:251:3)

Which is this line:

counts[subject_type]['srsStages'][srs_stage]++

I suspect it’s iterating over subject_types and is getting an extra response and therefore failing.

It’s a pain debugging these scripts, but I suspect the changes need to be like this:

Replace likes 12 to 55 with this:

headers: [    'Date (YYYY-MM-DD)',    'Apprentice radicals',    'Apprentice kanji',    'Apprentice vocabulary',    'Apprentice kana vocabulary',    'Guru radicals',    'Guru kanji',    'Guru vocabulary',    'Guru kana vocabulary',    'Master radicals',    'Master kanji',    'Master vocabulary',    'Master kana vocabulary',    'Enlightened radicals',    'Enlightened kanji',    'Enlightened vocabularies',    'Enlightend kana vocabularies',    'Burned radicals',    'Burned kanji',    'Burned vocabularies',    'Burned kana vocabularies',    'Unlocked radicals',    'Unlocked kanji',    'Unlocked vocabulary',    'Unlocked kana vocabulary',    'Started radicals',    'Started kanji',    'Started vocabulary',    'Started kana vocabulary',    'Passed radicals',    'Passed above kanji',    'Passed above vocabulary',    'Passed kana vocabulary',    'Burned (event) radicals',    'Burned (event) kanji',    'Burned (event) vocabulary',    'Burned (event) kana vocabulary',    'Resurrected radicals',    'Resurrected kanji',    'Resurrected vocabulary',    'Resurreced kana vocabulary',    'Lesson radicals',    'Lesson kanji',    'Lesson vocabulary',    'Lesson kana vocabulary',    'Review radicals',    'Review kanji',    'Review vocabulary',    'Review kana vocabulary',    'Total available radicals',    'Total available kanji',    'Total available vocabulary',    'Total available kana vocabulary',    'Current level',    'Days on current level',  ],

Add this on like 173, after the },

kana_vocabulary: {
      lessons: 0,
      reviews: 0,
      unlocked: 0,
      started: 0,
      passed: 0,
      burned: 0,
      resurrected: 0,
      totalAvailable: 0,
      srsStages: {
        0: 0,
        1: 0,
        2: 0,
        3: 0,
        4: 0,
        5: 0,
        6: 0,
        7: 0,
        8: 0,
        9: 0, 
      }
    },

Alternately, here’s the whole script:

const onOpen = () => {
  const spreadsheet = SpreadsheetApp.getActive()
  const menuItems = [
    { name: 'Prepare sheets...', functionName: 'prepareSheets_' },
    { name: 'Add new daily entry...', functionName: 'generateNewDailyEntry_' }
  ]
  spreadsheet.addMenu('Daily Status', menuItems)
}

const dailyEntriesSheetDetails = {
  name: 'Daily Entries',
   headers: [
    'Date (YYYY-MM-DD)',
    'Apprentice radicals',
    'Apprentice kanji',
    'Apprentice vocabulary',
    'Apprentice kana vocabulary',
    'Guru radicals',
    'Guru kanji',
    'Guru vocabulary',
    'Guru kana vocabulary',
    'Master radicals',
    'Master kanji',
    'Master vocabulary',
    'Master kana vocabulary',
    'Enlightened radicals',
    'Enlightened kanji',
    'Enlightened vocabularies',
    'Enlightend kana vocabularies',
    'Burned radicals',
    'Burned kanji',
    'Burned vocabularies',
    'Burned kana vocabularies',
    'Unlocked radicals',
    'Unlocked kanji',
    'Unlocked vocabulary',
    'Unlocked kana vocabulary',
    'Started radicals',
    'Started kanji',
    'Started vocabulary',
    'Started kana vocabulary',
    'Passed radicals',
    'Passed above kanji',
    'Passed above vocabulary',
    'Passed kana vocabulary',
    'Burned (event) radicals',
    'Burned (event) kanji',
    'Burned (event) vocabulary',
    'Burned (event) kana vocabulary',
    'Resurrected radicals',
    'Resurrected kanji',
    'Resurrected vocabulary',
    'Resurreced kana vocabulary',
    'Lesson radicals',
    'Lesson kanji',
    'Lesson vocabulary',
    'Lesson kana vocabulary',
    'Review radicals',
    'Review kanji',
    'Review vocabulary',
    'Review kana vocabulary',
    'Total available radicals',
    'Total available kanji',
    'Total available vocabulary',
    'Total available kana vocabulary',
    'Current level',
    'Days on current level',
  ],
}

const apiSheetDetails = {
  name: 'API',
  headers: [
    'API token (version 2)'
  ],
}

const prepareSheets_ = () => {
  prepareSheet_(dailyEntriesSheetDetails)
  prepareSheet_(apiSheetDetails)
  getSheetByName_(apiSheetDetails.name).getRange('A2').setValue('(Replace this cell with your API token)')
}

const prepareSheet_ = ({name, headers}) => {
  const sheet = insertSheet_().setName(name)
  
  if (sheet.getMaxColumns() < headers.length) {
    sheet.insertColumnsAfter(sheet.getMaxColumns() - 1, headers.length - sheet.getMaxColumns())
  }
  
  if (sheet.getMaxColumns() > headers.length) {
    sheet.deleteColumns(headers.length, sheet.getMaxColumns() - headers.length)
  }
  
  sheet.deleteRows(2, sheet.getMaxRows() - 2)
  sheet.getRange("A1:1").setValues([headers]).setFontWeight('bold')
  sheet.setFrozenRows(1)
  sheet.autoResizeColumns(1, headers.length)
}

const generateNewDailyEntry_ = () => {
  const level = {
    current: null,
    numberOfDaysOnCurrent: null
  }
  const counts = {
    radical: {
      lessons: 0,
      reviews: 0,
      unlocked: 0,
      started: 0,
      passed: 0,
      burned: 0,
      resurrected: 0,
      totalAvailable: 0,
      srsStages: {
        0: 0,
        1: 0,
        2: 0,
        3: 0,
        4: 0,
        5: 0,
        6: 0,
        7: 0,
        8: 0,
        9: 0, 
      }
    },
    kanji: {
      lessons: 0,
      reviews: 0,
      unlocked: 0,
      started: 0,
      passed: 0,
      burned: 0,
      resurrected: 0,
      totalAvailable: 0,
      srsStages: {
        0: 0,
        1: 0,
        2: 0,
        3: 0,
        4: 0,
        5: 0,
        6: 0,
        7: 0,
        8: 0,
        9: 0, 
      }
    },
    vocabulary: {
      lessons: 0,
      reviews: 0,
      unlocked: 0,
      started: 0,
      passed: 0,
      burned: 0,
      resurrected: 0,
      totalAvailable: 0,
      srsStages: {
        0: 0,
        1: 0,
        2: 0,
        3: 0,
        4: 0,
        5: 0,
        6: 0,
        7: 0,
        8: 0,
        9: 0, 
      }
    },
    kana_vocabulary: {
      lessons: 0,
      reviews: 0,
      unlocked: 0,
      started: 0,
      passed: 0,
      burned: 0,
      resurrected: 0,
      totalAvailable: 0,
      srsStages: {
        0: 0,
        1: 0,
        2: 0,
        3: 0,
        4: 0,
        5: 0,
        6: 0,
        7: 0,
        8: 0,
        9: 0, 
      }
    },
  }

  const generateDataFromAssignments = () => {
    let assignmentsEndpoint = 'https://api.wanikani.com/v2/assignments?hidden=false'
  
    while (assignmentsEndpoint) {
      const response = fetchData_(assignmentsEndpoint)
    
      response.data.forEach(({ data }) => {
        const { subject_type, srs_stage, unlocked_at, started_at, passed_at, burned_at, resurrected_at } = data
                            
        counts[subject_type]['srsStages'][srs_stage]++
        if (unlocked_at) { counts[subject_type]['unlocked']++ }    
        if (started_at) { counts[subject_type]['started']++ }
        if (passed_at) { counts[subject_type]['passed']++ }
        if (burned_at) { counts[subject_type]['burned']++ }  
        if (resurrected_at) { counts[subject_type]['resurrected']++ }
        if (unlocked_at && !started_at) { counts[subject_type]['lessons']++ }
        if (started_at && (!burned_at || resurrected_at)) { counts[subject_type]['reviews']++ }
      })
                            
      assignmentsEndpoint = response.pages.next_url
    }
  }
  
  const generateDataFromSubjects = () => {
    let subjectsEndpoint = 'https://api.wanikani.com/v2/subjects?hidden=false'

    while (subjectsEndpoint) {
      const response = fetchData_(subjectsEndpoint)
    
      response.data.forEach(({ object }) => {
        counts[object]['totalAvailable']++
      })
                            
      subjectsEndpoint = response.pages.next_url
    }
  }
        
  const generateDataFromLevelProgressions = () => {
    let levelProgressionsEndpoint = 'https://api.wanikani.com/v2/level_progressions'
    let levelProgressions = []

    while (levelProgressionsEndpoint) {
      const response = fetchData_(levelProgressionsEndpoint)
    
      response.data.forEach(({ data }) => {
        levelProgressions = [...levelProgressions, data]
      })
                            
      levelProgressionsEndpoint = response.pages.next_url
    }
                            
    const latestLevelProgression = levelProgressions[levelProgressions.length - 1]
        
    if (latestLevelProgression) {
      level.current = latestLevelProgression.level
      level.numberOfDaysOnCurrent = numberOfDaysBetweenDates(new Date(latestLevelProgression.unlocked_at), new Date())   
    }
  }

  const srsStagesByStageName = {
    apprentice: [1, 2, 3, 4],
    guru: [5, 6],
    master: [7],
    enlightened: [8],
    burned: [9],
  }
      
  const todayDate_ = () => Utilities.formatDate(new Date(), getActiveSpreadsheet_().getSpreadsheetTimeZone(), 'yyyy-MM-dd')

  const numberOfDaysBetweenDates = (earlyDate, laterDate) => (laterDate.getTime() - earlyDate.getTime()) / (1000 * 3600 * 24)
                            
  const totalSrsStagesCountBySubjectType_ = (counts, srsStages, subjectType) => {
    return srsStages.reduce((totalCount, srsStage) => {
      return totalCount + counts[subjectType]['srsStages'][srsStage]
    }, 0)
  }
                            
  const fetchData_ = (apiEndpoint) => {
    const response = UrlFetchApp.fetch(apiEndpoint, {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${getApiToken_()}`,
        'Content-Type': 'application/json',
      },
    })

    return JSON.parse(response.getContentText())
  }
        
  generateDataFromAssignments()
  generateDataFromSubjects()
  generateDataFromLevelProgressions()
                            
  getSheetByName_(dailyEntriesSheetDetails.name).appendRow([
    todayDate_(),
    ...Object.keys(srsStagesByStageName).map((srsStageName) => {
      return Object.keys(counts).map((subjectType) => totalSrsStagesCountBySubjectType_(counts, srsStagesByStageName[srsStageName], [subjectType]))
    }).flat(),
    ...['unlocked', 'started', 'passed', 'burned', 'resurrected', 'lessons', 'reviews', 'totalAvailable'].map((countType) => {
      return Object.keys(counts).map((subjectType) => counts[subjectType][countType])
    }).flat(),
    level.current,
    level.numberOfDaysOnCurrent,
  ])
}

const getActiveSpreadsheet_ = () => SpreadsheetApp.getActiveSpreadsheet()
const getApiToken_ = () => getSheetByName_(apiSheetDetails.name).getRange('A2').getValue()
const getSheetByName_ = (sheetName) => getActiveSpreadsheet_().getSheetByName(sheetName)
const insertSheet_ = () => getActiveSpreadsheet_().insertSheet()

Hopefully that’ll work, but I can’t confirm right now.

2 Likes

I take it that this broke because of the introduction of Kana Only Vocabulary and not be cause of the API updates? If it is actually a bug with the API, could you please raise a ticket by sending an email to support.

I know Viet originally wrote the script out of the kindness of his heart, but as it falls into the third party apps category it is subject to the same rules as other scripts that are unsupported and could break at any time. By all means continue to use the script and update it to fit your own needs, but please be aware of the risks associated with changes that might prevent this from working in the future. (*I think it is best to make sure this is clear, to avoid confusion)

If there are issues that you encounter that are related to the API or wanikani (i.e. not user scripts) could you please send emails to hello@wanikani.com instead of @mentioning me as this will ensure the issue gets logged correctly and also gets the attention it deserves. I just wanted to point out that I don’t actively monitor the forum, and I want to make sure I don’t miss something because it has only been posted here.

Sorry, yes - that was my half-asleep brain last night as I wrote that post in bed :sweat_smile:

Was only the work of a few minutes to tweak it, so no big deal, and it’s possible there’s only a couple dozen of us using the script, but I don’t think it’s entirely fair to categorise this as third-party given the context; unsupported would be more accurate, I feel.

If a script is posted by a representative of Tofugu from their official account, there’s a tacit implication of authenticity behind it and I certainly remember thinking of it, at the time, as being an official offering.

Noted - sorry, I’m aware I keep bothering you with this stuff via this channel :sweat_smile:

I understand. I just wanted to clear up (for the sake of confusion) that it is not supported / no longer supported and should be considered the same way all third party content is considered. That is, use at your own risk and feel free to update it for your own benefit.

It is no bother, I just want to make sure that if you do actually need support the correct channel is used as it helps keep track us keep track of issues and their resolutions, and your issue doesn’t mistakenly get overlooked.

2 Likes

Yes, it worked! At first glance, I thought it screwed up the table, then I realized that I just had to add all the new “kana vocab” columns, and everything lined back up again.

Thank you so much @Joeni!

1 Like