From 5cc5272f77c3467fbfec9e56fee0e80019cb4ec8 Mon Sep 17 00:00:00 2001 From: Pacharapol Withayasakpunt Date: Fri, 29 Apr 2022 02:50:32 +0700 Subject: [PATCH] update cloze maker --- scripts/populate-from-wanikani.ts | 1 + scripts/query-anki.ts | 88 ++++++++++++++++++++++++++++++- src/wanikani.ts | 35 +++++++++--- 3 files changed, 114 insertions(+), 10 deletions(-) diff --git a/scripts/populate-from-wanikani.ts b/scripts/populate-from-wanikani.ts index 87366f6..da89b3f 100644 --- a/scripts/populate-from-wanikani.ts +++ b/scripts/populate-from-wanikani.ts @@ -16,6 +16,7 @@ async function main() { sentenceJa: 'Sentence', sentenceAudio: 'SentenceAudio', sentenceEn: 'SentenceMeaning', + sentenceCloze: 'MeaningQuiz', }, { overwrite: true }, ); diff --git a/scripts/query-anki.ts b/scripts/query-anki.ts index 0f5d503..f817537 100644 --- a/scripts/query-anki.ts +++ b/scripts/query-anki.ts @@ -1,5 +1,5 @@ import { soundTag } from '@/anki'; -import { AnkiConnect } from '@/ankiconnect'; +import { AnkiConnect, IAnkiConnectActions } from '@/ankiconnect'; export async function listTags( query = 'deck:Takoboto -Takoboto: tag:common -tag:reading-challenge -tag:favorites -tag:death-note -tag:ハピネス -tag:https://www.bunka.go.jp/seisaku/bunkashingikai/kokugo/hokoku/pdf/ijidokun_140221.pdf', @@ -63,6 +63,90 @@ export async function addSoundTag(query: string, fieldNames: string[]) { }); } +export async function fixCloze( + query: string, + fields: { vocabJa: string; sentenceCloze: string }, +) { + const anki = new AnkiConnect(); + anki + .api('findNotes', { + query, + }) + .then((notes) => anki.api('notesInfo', { notes })) + .then(async (notes) => { + const notesToUpdate: IAnkiConnectActions['updateNoteFields']['params']['note'][] = + []; + + const badJa: string[] = []; + + for (const n of notes) { + const { value: ja } = n.fields[fields.vocabJa] || {}; + const { value: sent } = n.fields[fields.sentenceCloze] || {}; + if (ja && sent) { + const clozeChar = '__'; + + if ( + sent + .split('
') + .filter((_, i) => i % 2 === 0) + .every((s) => s.includes(clozeChar)) + ) { + continue; + } + + const cleanJa = ja.replace(/\[.+?\]/g, '').replace(/ /g, ''); + let newSent = sent.replace(cleanJa, clozeChar); + + const jaNoOkuri = cleanJa.replace(/[\p{sc=Hiragana}]/gu, ''); + if (cleanJa !== jaNoOkuri && newSent.includes(jaNoOkuri)) { + const s2 = newSent.replace(jaNoOkuri, clozeChar); + if (s2 !== newSent) { + newSent = s2; + } else { + badJa.push(ja); + } + } + + if (!sent.includes(clozeChar) && sent !== newSent) { + notesToUpdate.push({ + id: n.noteId, + fields: { + [fields.sentenceCloze]: newSent, + }, + }); + } + } + } + + while (badJa.length) { + console.log( + query + + ' tag:wanikani card:EJ (' + + badJa + .splice(0, 20) + .map((ja) => `${fields.vocabJa}:${ja}`) + .join(' OR ') + + ')', + ); + } + + if (!notesToUpdate.length) return; + + await anki.multi<'updateNoteFields'[]>({ + actions: notesToUpdate.map((note) => ({ + action: 'updateNoteFields', + params: { + note, + }, + })), + }); + }); +} + if (require.main === module) { - addSoundTag('note:jp.takoboto', ['SentenceAudio', 'JapaneseAudio']); + // addSoundTag('note:jp.takoboto', ['SentenceAudio', 'JapaneseAudio']); + fixCloze('deck:Takoboto', { + vocabJa: 'Japanese', + sentenceCloze: 'MeaningQuiz', + }); } diff --git a/src/wanikani.ts b/src/wanikani.ts index 7b3846e..3200f4d 100644 --- a/src/wanikani.ts +++ b/src/wanikani.ts @@ -413,6 +413,7 @@ export class WaniKani { sentenceJa: string; sentenceAudio?: string; sentenceEn: string; + sentenceCloze?: string; }, opts: { overwrite?: boolean; @@ -428,13 +429,20 @@ export class WaniKani { { ja: string; en: string; + sentences: { + ja: string; + en: string; + }[]; } >(); vocabularies.map((v) => { if (sentenceMap.has(v.data.characters)) return; const sent = v.data.context_sentences[0]; if (!sent || !sent.ja.trim()) return; - sentenceMap.set(v.data.characters, sent); + sentenceMap.set(v.data.characters, { + ...sent, + sentences: v.data.context_sentences, + }); }); if (!sentenceMap.size) return; @@ -459,16 +467,27 @@ export class WaniKani { for (const n of notes) { const { value: ja } = n.fields[fields.vocabJa] || {}; if (ja) { - const sent = sentenceMap.get( - ja.replace(/\[.+?\]/g, '').replace(/ /g, ''), - ); + const cleanJa = ja.replace(/\[.+?\]/g, '').replace(/ /g, ''); + const sent = sentenceMap.get(cleanJa); if (sent) { + const fieldUpdate = { + [fields.sentenceJa]: sent.ja, + [fields.sentenceEn]: sent.en, + }; + + if ( + fields.sentenceCloze && + !n.fields[fields.sentenceCloze]?.value + ) { + fieldUpdate[fields.sentenceCloze] = sent.sentences + .map(({ ja, en }) => `${ja}
${en}`) + .join('
') + .replace(cleanJa, '__'); + } + notesToUpdate.push({ id: n.noteId, - fields: { - [fields.sentenceJa]: sent.ja, - [fields.sentenceEn]: sent.en, - }, + fields: fieldUpdate, }); } }