import axios from 'axios'
|
|
import rateLimit, { RateLimitedAxiosInstance } from 'axios-rate-limit'
|
|
|
|
/**
|
|
* https://docs.api.wanikani.com/20170710/#rate-limit
|
|
*
|
|
* Requests per minute 60
|
|
*
|
|
* @param apiKey @default process.env['WANIKANI_API_KEY']
|
|
* @returns
|
|
*/
|
|
export function makeWanikani(
|
|
apiKey = process.env['WANIKANI_API_KEY']!
|
|
): WaniKaniAxiosInstance {
|
|
const wkApi = rateLimit(
|
|
axios.create({
|
|
baseURL: 'https://api.wanikani.com/v2/',
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`
|
|
},
|
|
validateStatus: function () {
|
|
return true
|
|
}
|
|
}),
|
|
{
|
|
/**
|
|
* Per second
|
|
*/
|
|
maxRequests: 1,
|
|
perMilliseconds: 1000
|
|
}
|
|
)
|
|
|
|
return Object.assign(wkApi, {
|
|
async *kanji(params = {}) {
|
|
let nextUrl = '/subjects'
|
|
|
|
while (true) {
|
|
const r = await wkApi.get<
|
|
ICollection<
|
|
IResource<{
|
|
characters: string
|
|
level: number
|
|
}>
|
|
>
|
|
>(nextUrl, {
|
|
params: {
|
|
...params,
|
|
types: 'kanji'
|
|
}
|
|
})
|
|
|
|
for (const d of r.data.data) {
|
|
yield {
|
|
id: d.id,
|
|
level: d.data.level,
|
|
characters: d.data.characters
|
|
}
|
|
}
|
|
|
|
console.error(r.data.url)
|
|
|
|
nextUrl = r.data.pages.next_url || ''
|
|
if (!nextUrl) {
|
|
break
|
|
}
|
|
}
|
|
},
|
|
async *vocabulary(params = {}) {
|
|
let nextUrl = '/subjects'
|
|
|
|
while (true) {
|
|
const r = await wkApi.get<
|
|
ICollection<
|
|
IResource<{
|
|
characters: string
|
|
level: number
|
|
context_sentences: {
|
|
ja: string
|
|
en: string
|
|
}[]
|
|
}>
|
|
>
|
|
>(nextUrl, {
|
|
params: {
|
|
...params,
|
|
types: 'vocabulary'
|
|
}
|
|
})
|
|
|
|
for (const d of r.data.data) {
|
|
yield {
|
|
id: d.id,
|
|
level: d.data.level,
|
|
characters: d.data.characters,
|
|
sentences: d.data.context_sentences
|
|
}
|
|
}
|
|
|
|
console.error(r.data.url)
|
|
|
|
nextUrl = r.data.pages.next_url || ''
|
|
if (!nextUrl) {
|
|
break
|
|
}
|
|
}
|
|
},
|
|
async *subjects<T = any>(params = {}) {
|
|
let nextUrl = '/subjects'
|
|
|
|
while (true) {
|
|
const r = await wkApi.get<
|
|
ICollection<
|
|
IResource<any> & {
|
|
id: number
|
|
object: string
|
|
data_updated_at: string
|
|
url: string
|
|
data: T
|
|
}
|
|
>
|
|
>(nextUrl, { params })
|
|
|
|
for (const d of r.data.data) {
|
|
yield {
|
|
id: d.id,
|
|
data_updated_at: d.data_updated_at,
|
|
object: d.object,
|
|
url: d.url,
|
|
data: d.data
|
|
}
|
|
}
|
|
|
|
console.error(r.data.url)
|
|
|
|
nextUrl = r.data.pages.next_url || ''
|
|
if (!nextUrl) {
|
|
break
|
|
}
|
|
}
|
|
},
|
|
async *assignments(
|
|
params = {
|
|
unlocked: 'true'
|
|
}
|
|
) {
|
|
let nextUrl = '/assignments'
|
|
|
|
while (true) {
|
|
const r = await wkApi.get<
|
|
ICollection<
|
|
IResource<{
|
|
subject_id: number
|
|
srs_stage: number
|
|
}>
|
|
>
|
|
>(nextUrl, {
|
|
params
|
|
})
|
|
|
|
console.error(r.data.url)
|
|
|
|
for (const d of r.data.data) {
|
|
yield {
|
|
id: d.data.subject_id,
|
|
srsLevel: d.data.srs_stage
|
|
}
|
|
}
|
|
|
|
nextUrl = r.data.pages.next_url || ''
|
|
if (!nextUrl) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
export interface WaniKaniAxiosInstance extends RateLimitedAxiosInstance {
|
|
kanji(params?: any): AsyncGenerator<{
|
|
id: number
|
|
level: number
|
|
characters: string
|
|
}>
|
|
vocabulary(params?: any): AsyncGenerator<{
|
|
id: number
|
|
level: number
|
|
characters: string
|
|
sentences: {
|
|
ja: string
|
|
en: string
|
|
}[]
|
|
}>
|
|
subjects<T = any>(
|
|
params?: any
|
|
): AsyncGenerator<{
|
|
id: number
|
|
object: string
|
|
data_updated_at: string
|
|
url: string
|
|
data: T
|
|
}>
|
|
assignments(params?: any): AsyncGenerator<{
|
|
id: number
|
|
srsLevel: number
|
|
}>
|
|
}
|
|
|
|
export interface IResource<T = any> {
|
|
id: number
|
|
url: string
|
|
data_updated_at: string // Date
|
|
data: T
|
|
}
|
|
|
|
export interface ICollection<T = any> {
|
|
object: string
|
|
url: string
|
|
pages: {
|
|
next_url?: string
|
|
previous_url?: string
|
|
per_page: number
|
|
}
|
|
total_count: number
|
|
data_updated_at: string // Date
|
|
data: T[]
|
|
}
|
|
|
|
export interface IError {
|
|
error: string
|
|
code: number
|
|
}
|