Browse Source

add how to populate from wanikani

main
parent
commit
45232b33fc
4 changed files with 130 additions and 28 deletions
  1. +89
    -0
      scripts/populate-from-wanikani.ts
  2. +1
    -20
      scripts/query-anki.ts
  3. +20
    -0
      src/anki.ts
  4. +20
    -8
      src/wanikani.ts

+ 89
- 0
scripts/populate-from-wanikani.ts View File

@ -0,0 +1,89 @@
import { soundTag } from '@/anki';
import { AnkiConnect, IAnkiConnectActions } from '@/ankiconnect';
import { ISubject, IVocabulary, WaniKani } from '@/wanikani';
export async function populateSounds(
query: string,
jaField: string,
audioField: string,
subjects: ISubject[],
) {
const vocabularies = subjects.filter(
(s) => s.object === 'vocabulary',
) as IVocabulary[];
const audioMap = new Map<
string,
{
url: string;
filename: string;
}
>();
vocabularies.map((v) => {
if (audioMap.has(v.data.characters)) return;
const audio = v.data.pronunciation_audios[0];
if (!audio) return;
audioMap.set(v.data.characters, {
url: audio.url,
filename: `wanikani_${v.data.characters}_${audio.metadata.source_id}${
audio.content_type === 'audio/ogg' ? '.ogg' : '.mp3'
}`,
});
});
if (!audioMap.size) return;
query += ` -${jaField}: ${audioField}:`;
const anki = new AnkiConnect();
anki
.api('findNotes', {
query,
})
.then((notes) => anki.api('notesInfo', { notes }))
.then(async (notes) => {
const notesToUpdate: IAnkiConnectActions['updateNoteFields']['params']['note'][] =
[];
for (const n of notes) {
const { value: ja } = n.fields[jaField] || {};
if (ja) {
const audio = audioMap.get(
ja.replace(/\[.+?\]/g, '').replace(/ /g, ''),
);
if (audio) {
notesToUpdate.push({
id: n.noteId,
fields: {
[audioField]: soundTag.add(audio.filename),
},
audio: [
{
url: audio.url,
filename: audio.filename,
fields: [audioField],
},
],
});
}
}
}
if (!notesToUpdate.length) return;
while (notesToUpdate.length) {
await anki.multi<'updateNoteFields'[]>({
actions: notesToUpdate.splice(0, 100).map((note) => ({
action: 'updateNoteFields',
params: {
note,
},
})),
});
}
});
}
if (require.main === module) {
new WaniKani().subjects().then((subjects) => {
populateSounds('note:jp.takoboto', 'Japanese', 'JapaneseAudio', subjects);
});
}

+ 1
- 20
scripts/query-anki.ts View File

@ -1,3 +1,4 @@
import { soundTag } from '@/anki';
import { AnkiConnect } from '@/ankiconnect';
export async function listTags(
@ -14,26 +15,6 @@ export async function listTags(
});
}
const soundTag = {
pre: '[sound:',
post: ']',
is(src: string) {
return src.startsWith(this.pre) && src.endsWith(this.post);
},
add(src: string) {
if (soundTag.is(src)) return src;
return this.pre + src + this.post;
},
remove(src: string) {
if (!soundTag.is(src)) return src;
if (this.isEmpty(src)) return src;
return src.substring(this.pre.length, src.length - this.post.length);
},
isEmpty(src: string) {
return src === this.pre + this.post;
},
};
export async function addSoundTag(query: string, fieldNames: string[]) {
query +=
' (' + fieldNames.map((f) => `(-${f}: -${f}:[sound:*)`).join(' OR ') + ')';

+ 20
- 0
src/anki.ts View File

@ -46,3 +46,23 @@ export interface IAnkiTemplate {
export interface IAnkiDeckConfig {
id: number;
}
export const soundTag = {
pre: '[sound:',
post: ']',
is(src: string) {
return src.startsWith(this.pre) && src.endsWith(this.post);
},
add(src: string) {
if (soundTag.is(src)) return src;
return this.pre + src + this.post;
},
remove(src: string) {
if (!soundTag.is(src)) return src;
if (this.isEmpty(src)) return src;
return src.substring(this.pre.length, src.length - this.post.length);
},
isEmpty(src: string) {
return src === this.pre + this.post;
},
};

+ 20
- 8
src/wanikani.ts View File

@ -99,14 +99,25 @@ interface ISubjectBase<
export type IRadical = ISubjectBase<
'radical',
{
character_images: {
character_images: ({
url: string;
metadata: {
inline_styles?: boolean;
dimensions?: string;
};
content_type: string;
}[];
} & (
| {
metadata: {
inline_styles?: boolean;
dimensions?: string;
};
content_type: 'image/svg+xml';
}
| {
metadata: {
color: string;
dimensions: string;
style_name: string;
};
content_type: 'image/png';
}
))[];
amalgamation_subject_ids: SubjectID[];
}
>;
@ -117,7 +128,8 @@ export type IKanji = ISubjectBase<
readings: {
reading: string;
primary: boolean;
type: 'kunyomi' | 'onyomi';
accepted_answer: boolean;
type: 'kunyomi' | 'onyomi' | 'nanori';
}[];
component_subject_ids: SubjectID[];
visually_similar_subject_ids: SubjectID[];

Loading…
Cancel
Save