From 1c362c0c1c4de0b88389d64c82cf48c8e7131bfe Mon Sep 17 00:00:00 2001 From: Pacharapol Withayasakpunt Date: Wed, 27 Apr 2022 13:07:04 +0700 Subject: [PATCH] add beyond --- assets/beyond.yaml | 23 +++++++ scripts/get-kanji-level.ts | 124 +++++++++++++++++++++++++++++++++++++ scripts/sort-anki.ts | 30 ++++++--- src/wanikani.ts | 2 +- 4 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 assets/beyond.yaml create mode 100644 scripts/get-kanji-level.ts diff --git a/assets/beyond.yaml b/assets/beyond.yaml new file mode 100644 index 0000000..ab77122 --- /dev/null +++ b/assets/beyond.yaml @@ -0,0 +1,23 @@ +'61-70: 無限 INFINITY': + '61': [乞, 仄, 佇, 侶, 俄, 俯, 倣, 僻, 儚, 冴, 凄, 几, 凧, 凭, 剃, 剥, 勾, 匂, 匙, 叩, 只, 叶, 吃, 吊, 咎, 咳, 唸, 唾, 喉, 喧, 喩, 嗅, 嘘, 嘩, 嘲, 嘴, 噂, 噛, 嚇, 囀] + '62': [垢, 堆, 塞, 壺, 夥, 奢, 妬, 姑, 姪, 婉, 嫉, 嬉, 孕, 宛, 宥, 尤, 屓, 嵌, 庇, 弄, 彙, 彷, 徨, 悉, 惚, 惧, 愁, 慌, 戚, 拭, 拶, 挨, 挫, 挽, 捗, 捧, 捲, 捻, 掟, 掬] + '63': [掴, 揉, 摺, 撰, 敲, 斡, 斥, 旁, 昏, 昧, 晒, 暈, 暢, 曖, 杖, 杜, 柿, 栗, 桁, 桶, 棘, 棲, 椀, 楊, 楕, 楯, 楷, 槌, 樽, 橙, 檻, 歪, 氾, 汲, 洒, 浚, 涎, 渚, 溜, 溢] + '64': [溺, 滲, 漑, 潰, 澱, 濡, 濫, 灌, 炒, 焜, 煉, 煌, 煎, 煽, 爺, 璧, 瓦, 甥, 甦, 痒, 痣, 痩, 痺, 瘤, 癌, 皰, 眉, 眩, 碗, 禿, 稽, 窄, 窪, 窶, 竿, 箋, 簾, 籠, 籤, 絨] + '65': [綴, 綻, 緞, 縊, 縋, 縞, 縺, 繕, 罠, 罵, 羨, 耗, 耽, 聊, 肘, 股, 脛, 腫, 膝, 臆, 舅, 舐, 舵, 艶, 苛, 茸, 茣, 莫, 萎, 蒔, 蓋, 蓙, 蔓, 薩, 藁, 蛉, 蛛, 蜘, 蜻, 蝉] + '66': [蝕, 螺, 衷, 袖, 袴, 裾, 褄, 褪, 襖, 訛, 訣, 詣, 詫, 誂, 諄, 諦, 謳, 讐, 貌, 貪, 貶, 賑, 賦, 贔, 跨, 跪, 踉, 蹌, 蹲, 蹴, 躇, 躊, 躱, 躾, 輿, 辻, 辿, 迦, 這, 逞] + '67': [遡, 遽, 醒, 釘, 銜, 鋸, 錆, 錨, 閃, 閏, 隈, 雀, 雛, 霞, 靡, 鞄, 頓, 頬, 頷, 顎, 餃, 餅, 餌, 餡, 饉, 馴, 骸, 鬘, 鯛, 鯵, 鰯, 鰻, 鱈, 鹸, 麒, 麓, 麟, 黴] + '68': [冶, 繭, 但, 頒, 肢, 侯, 遵, 謄, 采, 弐, 朕, 詔, 壱, 丙, 儒, 旺, 嗣, 抄, 嫡, 畝, 虞, 痘, 爵, 墾, 塑, 吏, 附, 宵, 逐, 褐, 楼, 勅, 硝, 逓, 翁, 薫, 厘, 斤, 薪] + '69': [汎, 窟, 痕, 串, 刹, 慄, 詮, 曽, 麺, 腎, 沃, 憬, 柵, 僅, 錮, 辣, 踪, 諧, 釜, 舷, 羞, 璽, 淫, 毀, 瘍, 拉, 咽, 緻, 畏, 訃, 冥, 恣, 填, 摯, 斑, 怨, 膳, 臼, 捉, 妖, 蔽, 畿] + '70': [迄, 糞, 賜, 猪, 啜, 珈, 琲, 撫, 箒, 叛, 兜, 髭, 鼠, 兎, 稍, 丑, 寅, 卯, 辰, 巳, 酉, 戌, 亥, 蚕, 埃, 伍, 萬] +'71-75: 極度 BEYOND': + '71': [樺, 脩, 橘, 巴, 渥, 惟, 禎, 苑, 惣, 圭, 祐, 倭, 肇, 漱, 楠, 笹, 晃, 鷹, 耀, 浩, 匡, 晋, 尭, 朋, 喬, 於, 榛, 嵯, 鮎, 絢, 蕉, 巽, 啄, 槙, 彬, 椿, 磯, 怜, 淳] + '72': [毅, 彦, 弘, 鴻, 李, 亘, 佑, 鳳, 綜, 悌, 柚, 穣, 碧, 邑, 秦, 皓, 彪, 舜, 允, 偲, 黎, 伽, 朔, 汐, 凱, 甫, 惇, 禄, 皐, 稀, 桐, 琢, 翠, 欽, 慧] + '73': [馨, 芹, 孟, 魁, 暉, 毬, 稜, 琉, 槻, 峻, 巌, 洲, 亨, 桂, 玲, 茅, 欣, 郁, 洸, 紘, 稔, 鵬, 敦, 蔦, 芙, 宏, 萩, 嶺, 黛, 旭, 蘭] + '74': [眸, 麿, 銑, 鞠, 茉, 燿, 脹, 詢, 蕗, 倖, 嵩, 滉, 伶, 玖, 莞, 錘, 捺, 凜, 裟, 碩, 勺, 頌, 菫, 赳, 彗, 晟, 迪, 袈, 捷, 熙, 柾, 昂, 奎, 丞, 絃, 茄, 胤, 紬, 叡] + '75': [椋, 洵, 菖, 勁, 誼, 蓉, 亦, 燎, 瑚, 恕, 耶, 梢, 凪, 衿, 匁, 澪, 梧, 琳, 燦, 晨, 綸, 晏, 昴, 爾, 笙, 侑, 椰, 崚, 侃, 紗, 竣, 柊, 瑶] +'76+: 名人 MASTERY': + '76': [澤, 嶋, 篠, 幌, 筑, 芦, 幡, 柏, 堺, 梶, 眞, 條, 蘇, 俣, 廣, 磐, 荻, 葛, 蒲, 冨, 國, 栖, 粟, 諏, 函, 淵, 渕, 牟, 畠, 鴨, 宍, 梁, 榊, 樫, 橿, 苅, 釧, 鷲, 杵] + '77': [榎, 樋, 櫻, 珂, 砺, 箕, 舘, 苫, 奄, 峯, 廼, 播, 檜, 湘, 濱, 瀧, 瑳, 砥, 祢, 薙, 蛯, 襄, 諫, 讃, 逍, 逗, 鄭, 銚, 騨, 鵜, 齋] + '78': [喋, 屁, 繋, 檎, 蛙, 儲, 騙, 揃, 苺, 瀕, 仔, 吠, 牢, 狼, 疹, 萄, 葡, 蕎, 薇, 薔, 覗, 諺, 貰, 飩, 飴, 饂, 茹, 仇, 囁, 晰, 淋, 焚, 葦, 馳, 鮭] + '79': [劫, 厭, 呑, 喘, 嚢, 姦, 娶, 娼, 屍, 怯, 恍, 惹, 憊, 捏, 撒, 撼, 擢, 晦, 梗, 梟, 梯, 梳, 棺, 浣, 淹, 漕, 爬, 牝, 牽, 狸, 珊, 癇, 皺, 睨, 瞑, 瞼, 礫] + '80': [祷, 窺, 竪, 筏, 糊, 糠, 絆, 罹, 膏, 臥, 苔, 虔, 蛋, 蛭, 蛾, 蝸, 蟻, 蠅, 蠣, 褌, 諜, 踵, 轟, 轢, 鎧, 鞘, 餞, 駝, 駱, 髯, 鮫, 鶯] diff --git a/scripts/get-kanji-level.ts b/scripts/get-kanji-level.ts new file mode 100644 index 0000000..407bb5b --- /dev/null +++ b/scripts/get-kanji-level.ts @@ -0,0 +1,124 @@ +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() { + const wk = await makeWaniKaniKanjiLevels({ cache: true }); + const wkKanji = new Set( + Object.values(wk).flatMap((v) => Object.values(v).flat()), + ); + + const beyond = yaml.load( + readFileSync('assets/beyond.yaml', 'utf-8'), + ) as ILevelMap; + + for (const [cat, map] of Object.entries(beyond)) { + const catMap = wk[cat] || {}; + Object.entries(map).map(([levelString, ks]) => { + catMap[levelString] = ks.filter((k) => !wkKanji.has(k)); + }); + wk[cat] = catMap; + } + + return wk; +} + +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(); +} diff --git a/scripts/sort-anki.ts b/scripts/sort-anki.ts index 863a649..c654cf7 100644 --- a/scripts/sort-anki.ts +++ b/scripts/sort-anki.ts @@ -41,6 +41,20 @@ async function main() { ); }) .then((rs) => { + const cardsByTemplateName = { + data: new Map(), + async get(templateName: string) { + let v = this.data.get(templateName); + if (!v) { + v = await anki.api('findCards', { + query: `deck:${DECK} card:${templateName}`, + }); + } + this.data.set(templateName, v); + return v; + }, + }; + return Promise.all( rs .flat() @@ -51,16 +65,12 @@ async function main() { })), ) .map(({ templateName, subdeck, cardIdsSet }) => - anki - .api('findCards', { - query: `deck:${DECK} card:${templateName}`, - }) - .then((cards) => - anki.api('changeDeck', { - cards: cards.filter((c) => cardIdsSet.has(c)), - deck: `${DECK}::${subdeck}::${templateName}`, - }), - ), + cardsByTemplateName.get(templateName).then((cards) => + anki.api('changeDeck', { + cards: cards.filter((c) => cardIdsSet.has(c)), + deck: `${DECK}::${subdeck}::${templateName}`, + }), + ), ), ); }), diff --git a/src/wanikani.ts b/src/wanikani.ts index 13437bc..c9fab42 100644 --- a/src/wanikani.ts +++ b/src/wanikani.ts @@ -21,7 +21,7 @@ export class WaniKani { } = {}) { const subjects = { data: [] as (IKanji | IRadical)[], - filename: 'wanikani.json', + filename: 'cache/wanikani.json', load() { this.data = JSON.parse(fs.readFileSync(this.filename, 'utf-8')); return this.data;