Extra contents beyond WaniKani
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

157 lines
3.3 KiB

import { existsSync, readFileSync, writeFileSync } from 'fs';
import axios, { AxiosInstance } from 'axios';
export class WaniKani {
$axios: AxiosInstance;
constructor(public apiKey = process.env['WANIKANI_API_KEY']!) {
this.$axios = axios.create({
baseURL: 'https://api.wanikani.com/v2/',
headers: {
Authorization: `Bearer ${process.env['WANIKANI_API_KEY']}`,
},
});
}
async subjects({
force,
}: {
force?: boolean;
} = {}) {
const subjects = {
data: [] as ISubject[],
filename: 'cache/wanikani.json',
load() {
this.data = existsSync(this.filename)
? JSON.parse(readFileSync(this.filename, 'utf-8'))
: [];
return this.data;
},
dump(d: ISubject[]) {
this.data = d;
this.finalize();
},
finalize() {
writeFileSync(this.filename, JSON.stringify(this.data));
},
};
let data = subjects.load();
if (data.length && !force) {
return data;
}
data = [];
let nextURL = '/subjects';
while (nextURL) {
const r = await this.$axios
.get<{
pages: {
next_url?: string;
};
data: ISubject[];
}>(nextURL)
.then((r) => r.data);
data.push(...r.data);
console.info(r.pages.next_url);
nextURL = r.pages.next_url || '';
}
subjects.dump(data);
return data;
}
}
type WaniKaniDate = string;
type Integer = number;
type Level = Integer;
type SubjectID = Integer;
interface IMeaning {
meaning: string;
primary: boolean;
}
interface ISubjectBase<
T extends 'radical' | 'kanji' | 'vocabulary',
Data extends {},
> {
id: SubjectID;
object: T;
url: string;
data_updated_at: WaniKaniDate;
data: {
auxillary_meanings: IMeaning[];
characters: string;
created_at: WaniKaniDate;
document_url: string;
hidden_at: WaniKaniDate | null;
lesson_position: Integer;
level: Level;
meaning_mnemonic: string;
meanings: IMeaning[];
slug: string;
spaced_repetition_system_id: Integer;
} & Data;
}
export type IRadical = ISubjectBase<
'radical',
{
character_images: {
url: string;
metadata: {
inline_styles?: boolean;
dimensions?: string;
};
content_type: string;
}[];
amalgamation_subject_ids: SubjectID[];
}
>;
export type IKanji = ISubjectBase<
'kanji',
{
readings: {
reading: string;
primary: boolean;
type: 'kunyomi' | 'onyomi';
}[];
component_subject_ids: SubjectID[];
visually_similar_subject_ids: SubjectID[];
}
>;
export type IVocabulary = ISubjectBase<
'vocabulary',
{
component_subject_ids: SubjectID[];
context_sentences: {
en: string;
ja: string;
}[];
part_of_speech: string[];
pronunciation_audios: {
url: string;
metadata: {
gender: 'male' | 'female';
source_id: Integer;
pronunciation: string;
voice_actor_id: Integer;
voice_actor_name: string;
voice_description: string;
};
content_type: 'audio/mpeg' | 'audio/ogg';
}[];
readings: {
accepted_answer: boolean;
primary: boolean;
reading: string;
}[];
reading_mnemonic: string;
}
>;
export type ISubject = IRadical | IKanji | IVocabulary;