From 16d1307744e592553a2fb457090227bd6cf6a809 Mon Sep 17 00:00:00 2001 From: Pacharapol Withayasakpunt Date: Thu, 28 Apr 2022 15:03:57 +0700 Subject: [PATCH] add tag adder --- scripts/get-kanji-level.ts | 20 +---- scripts/populate-from-wanikani.ts | 13 +-- src/level.ts | 5 ++ src/wanikani.ts | 136 ++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 src/level.ts diff --git a/scripts/get-kanji-level.ts b/scripts/get-kanji-level.ts index 904b4b3..ac7cb38 100644 --- a/scripts/get-kanji-level.ts +++ b/scripts/get-kanji-level.ts @@ -25,24 +25,12 @@ async function makeWaniKaniKanjiLevels( 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 label = WaniKani.getLevelLabel(level); + const category = `${label.range}: ${ + label.ja + } ${label.en.toLocaleUpperCase()}`; const catMap = levelMap[category] || {}; - const levelString = level.toString().padStart(2, '0'); const ks = catMap[levelString] || []; diff --git a/scripts/populate-from-wanikani.ts b/scripts/populate-from-wanikani.ts index 92c0005..7c1aab7 100644 --- a/scripts/populate-from-wanikani.ts +++ b/scripts/populate-from-wanikani.ts @@ -6,14 +6,7 @@ if (require.main === module) { // { ja: 'Japanese', audio: 'JapaneseAudio' }, // { mode: { online: true } }, // ); - new WaniKani().populateSentence( - 'note:jp.takoboto', - { - vocabJa: 'Japanese', - sentenceJa: 'Sentence', - sentenceAudio: 'SentenceAudio', - sentenceEn: 'SentenceMeaning', - }, - { overwrite: true }, - ); + new WaniKani().addTags('note:jp.takoboto', { + ja: 'Japanese', + }); } diff --git a/src/level.ts b/src/level.ts new file mode 100644 index 0000000..f987bab --- /dev/null +++ b/src/level.ts @@ -0,0 +1,5 @@ +export interface ILevelLabel { + range: string; + ja: string; + en: string; +} diff --git a/src/wanikani.ts b/src/wanikani.ts index 07bdb50..80d7df7 100644 --- a/src/wanikani.ts +++ b/src/wanikani.ts @@ -4,6 +4,7 @@ import axios, { AxiosInstance } from 'axios'; import { soundTag } from './anki'; import { AnkiConnect, IAnkiConnectActions } from './ankiconnect'; +import { ILevelLabel } from './level'; import { logger } from './logger'; export class WaniKani { @@ -18,6 +19,48 @@ export class WaniKani { }); } + static getLevelLabel(level: number): ILevelLabel { + if (level < 11) { + return { + range: '01-10', + ja: '快', + en: 'PLEASANT', + }; + } else if (level < 21) { + return { + range: '11-20', + ja: '苦', + en: 'PAINFUL', + }; + } else if (level < 31) { + return { + range: '21-30', + ja: '死', + en: 'DEATH', + }; + } else if (level < 41) { + return { + range: '31-40', + ja: '地獄', + en: 'HELL', + }; + } else if (level < 51) { + return { + range: '41-50', + ja: '天国', + en: 'PARADISE', + }; + } else if (level < 61) { + return { + range: '51-60', + ja: '現実', + en: 'REALITY', + }; + } + + throw new Error(`invalid level: ${level}`); + } + async subjects({ force, }: { @@ -239,6 +282,99 @@ export class WaniKani { }); }); } + + async addTags( + query: string, + fields: { + ja: string; + }, + ) { + const subjects = await this.subjects(); + + const vocabularies = subjects.filter( + (s) => s.object === 'vocabulary', + ) as IVocabulary[]; + const vocabMap = new Map< + string, + { + level: number; + label: ILevelLabel; + } + >(); + vocabularies.map((v) => { + if (vocabMap.has(v.data.characters)) return; + vocabMap.set(v.data.characters, { + level: v.data.level, + label: WaniKani.getLevelLabel(v.data.level), + }); + }); + if (!vocabMap.size) return; + + query += ` -${fields.ja}:`; + + const anki = new AnkiConnect(); + anki + .api('findNotes', { + query, + }) + .then((notes) => anki.api('notesInfo', { notes })) + .then(async (notes) => { + const notesToUpdate: { + id: SubjectID; + tags: string[]; + }[] = []; + + for (const n of notes) { + const { value: ja } = n.fields[fields.ja] || {}; + if (ja) { + const vocab = vocabMap.get( + ja.replace(/\[.+?\]/g, '').replace(/ /g, ''), + ); + if (vocab) { + notesToUpdate.push({ + id: n.noteId, + tags: [ + 'wanikani', + `level-${vocab.level}`, + `wanikani-level-${vocab.level}`, + `wanikani-level-${vocab.label.range}-${vocab.label.ja}`, + `wanikani-level-${ + vocab.label.range + }-${vocab.label.en.toLocaleLowerCase()}`, + ], + }); + } + } + } + + if (!notesToUpdate.length) return; + + const addTagsMap = new Map< + string, + { + notes: SubjectID[]; + tags: string; + } + >(); + + notesToUpdate.map(({ id, tags }) => { + const identifier = JSON.stringify(tags); + const v = addTagsMap.get(identifier) || { + notes: [], + tags: tags.join(' '), + }; + v.notes.push(id); + addTagsMap.set(identifier, v); + }); + + await anki.multi<'addTags'[]>({ + actions: Array.from(addTagsMap.values()).map((params) => ({ + action: 'addTags', + params, + })), + }); + }); + } } type WaniKaniDate = string;