import { soundTag } from '@/anki';
|
|
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',
|
|
) {
|
|
const anki = new AnkiConnect();
|
|
return anki
|
|
.api('findNotes', {
|
|
query,
|
|
})
|
|
.then((notes) => anki.api('notesInfo', { notes }))
|
|
.then((notes) => {
|
|
return new Set(notes.flatMap((n) => n.tags));
|
|
});
|
|
}
|
|
|
|
export async function addSoundTag(query: string, fieldNames: string[]) {
|
|
query +=
|
|
' (' + fieldNames.map((f) => `(-${f}: -${f}:[sound:*)`).join(' OR ') + ')';
|
|
|
|
const anki = new AnkiConnect();
|
|
anki
|
|
.api('findNotes', {
|
|
query,
|
|
})
|
|
.then((notes) => anki.api('notesInfo', { notes }))
|
|
.then((notes) => {
|
|
const notesToUpdate: {
|
|
id: number;
|
|
fields: Record<string, string>;
|
|
}[] = [];
|
|
|
|
for (const n of notes) {
|
|
let toUpdate: typeof notesToUpdate[0] | undefined;
|
|
|
|
fieldNames.map((fieldName: string) => {
|
|
const field = n.fields[fieldName];
|
|
if (field) {
|
|
const { value } = field;
|
|
if (!soundTag.is(value)) {
|
|
toUpdate = toUpdate || { id: n.noteId, fields: {} };
|
|
toUpdate.fields[fieldName] = soundTag.make(value);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (toUpdate) {
|
|
notesToUpdate.push(toUpdate);
|
|
}
|
|
}
|
|
|
|
if (!notesToUpdate.length) return;
|
|
|
|
return anki.multi<'updateNoteFields'[]>({
|
|
actions: notesToUpdate.map((note) => ({
|
|
action: 'updateNoteFields',
|
|
params: {
|
|
note,
|
|
},
|
|
})),
|
|
});
|
|
});
|
|
}
|
|
|
|
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('<br>')
|
|
.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']);
|
|
fixCloze('deck:Takoboto', {
|
|
vocabJa: 'Japanese',
|
|
sentenceCloze: 'MeaningQuiz',
|
|
});
|
|
}
|