import { existsSync, readFileSync, writeFileSync } from 'fs'; import { IKanji, WaniKani } from '@/wanikani'; import yaml from 'js-yaml'; interface ILevelMap { [category: string]: { [levelString: string]: string[]; }; } async function makeWaniKaniKanjiLevels(opts: { cache?: boolean } = {}) { const FILENAME = 'cache/wanikani-kanji.yaml'; if (opts.cache && existsSync(FILENAME)) { return yaml.load(readFileSync(FILENAME, 'utf-8')) as ILevelMap; } const wkKanji = await new WaniKani() .subjects() .then((vs) => vs.filter((v) => v.object === 'kanji') as IKanji[]); const levelMap: ILevelMap = {}; for (const k of wkKanji) { const { level, characters } = k.data; let category: string; if (level < 11) { category = '01-10: 快 PLEASANT'; } else if (level < 21) { category = '11-21: 苦 PAINFUL'; } else if (level < 31) { category = '21-30: 死 DEATH'; } else if (level < 41) { category = '31-40: 地獄 HELL'; } else if (level < 51) { category = '41-50: 天国 PARADISE'; } else { category = '51-60: 現実 REALITY'; } const catMap = levelMap[category] || {}; const levelString = level.toString().padStart(2, '0'); const ks = catMap[levelString] || []; ks.push(characters); catMap[levelString] = ks; levelMap[category] = catMap; } writeFileSync( FILENAME, yaml.dump(levelMap, { sortKeys: true, flowLevel: 2, }), ); return levelMap; } export async function makeKanjiLevels(opts: { useBeyond: boolean }) { const wk = await makeWaniKaniKanjiLevels({ cache: true }); const beyond = opts.useBeyond ? (yaml.load(readFileSync('assets/beyond.yaml', 'utf-8')) as ILevelMap) : {}; const kanjiToLevel = new Map< string, { level: number; deckName: string; } >(); const setKanjiToLevel = (lvMap: ILevelMap) => { Object.entries(lvMap).map(([cat, map]) => { Object.entries(map).map(([levelString, ks]) => { const level = Number(levelString); ks.map((k) => { let prev = kanjiToLevel.get(k); if (!prev || prev.level > level) { prev = { level, deckName: cat + '::' + levelString, }; kanjiToLevel.set(k, prev); } }); }); }); }; setKanjiToLevel(wk); setKanjiToLevel(beyond); return kanjiToLevel; } export async function repairBeyond() { const beyond = yaml.load( readFileSync('cache/beyond.yaml', 'utf-8'), ) as Record; const levelMap: ILevelMap = {}; for (const [levelString, vs] of Object.entries(beyond)) { const level = Number(levelString); let category: string; if (level < 71) { category = '61-70: 無限 INFINITY'; } else if (level < 76) { category = '71-75: 極度 BEYOND'; } else { category = '76+: 名人 MASTERY'; } const catMap = levelMap[category] || {}; const ks = catMap[levelString] || []; ks.push(...vs); catMap[levelString] = ks; levelMap[category] = catMap; } writeFileSync( 'assets/beyond.yaml', yaml.dump(levelMap, { sortKeys: true, flowLevel: 2, }), ); } if (require.main === module) { repairBeyond(); }