update sound populater
This commit is contained in:
1 parent
45232b33fc
commit
e486a76236
5 files changed
+127
-96
No files matched your search
@@ -1,89 +1,9 @@
|
|||||||
import { soundTag } from '@/anki';
|
import { WaniKani } from '@/wanikani';
|
||||||
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) {
|
if (require.main === module) {
|
||||||
new WaniKani().subjects().then((subjects) => {
|
new WaniKani().populateSound(
|
||||||
populateSounds('note:jp.takoboto', 'Japanese', 'JapaneseAudio', subjects);
|
'note:jp.takoboto',
|
||||||
});
|
{ ja: 'Japanese', audio: 'JapaneseAudio' },
|
||||||
|
{ mode: { online: true } },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ export async function addSoundTag(query: string, fieldNames: string[]) {
|
|||||||
const { value } = field;
|
const { value } = field;
|
||||||
if (!soundTag.is(value)) {
|
if (!soundTag.is(value)) {
|
||||||
toUpdate = toUpdate || { id: n.noteId, fields: {} };
|
toUpdate = toUpdate || { id: n.noteId, fields: {} };
|
||||||
toUpdate.fields[fieldName] = soundTag.add(value);
|
toUpdate.fields[fieldName] = soundTag.make(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+17
-5
@@ -53,16 +53,28 @@ export const soundTag = {
|
|||||||
is(src: string) {
|
is(src: string) {
|
||||||
return src.startsWith(this.pre) && src.endsWith(this.post);
|
return src.startsWith(this.pre) && src.endsWith(this.post);
|
||||||
},
|
},
|
||||||
add(src: string) {
|
make(src: string) {
|
||||||
if (soundTag.is(src)) return src;
|
if (this.is(src)) return src;
|
||||||
return this.pre + src + this.post;
|
const out = this.pre + src + this.post;
|
||||||
|
if (this.isBroken(out)) return src;
|
||||||
|
return out;
|
||||||
},
|
},
|
||||||
remove(src: string) {
|
clean(src: string) {
|
||||||
if (!soundTag.is(src)) return src;
|
if (this.isBroken(src)) return src;
|
||||||
if (this.isEmpty(src)) return src;
|
if (this.isEmpty(src)) return src;
|
||||||
return src.substring(this.pre.length, src.length - this.post.length);
|
return src.substring(this.pre.length, src.length - this.post.length);
|
||||||
},
|
},
|
||||||
isEmpty(src: string) {
|
isEmpty(src: string) {
|
||||||
return src === this.pre + this.post;
|
return src === this.pre + this.post;
|
||||||
},
|
},
|
||||||
|
isBroken(src: string) {
|
||||||
|
if (this.is(src)) {
|
||||||
|
return (
|
||||||
|
src.indexOf(this.pre) > 0 ||
|
||||||
|
src.indexOf(this.post) < src.length - this.post.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
+1
-1
@@ -266,7 +266,7 @@ export interface IAnkiConnectActions
|
|||||||
params: {
|
params: {
|
||||||
note: {
|
note: {
|
||||||
id: number;
|
id: number;
|
||||||
fields: {
|
fields?: {
|
||||||
[fieldName: string]: string;
|
[fieldName: string]: string;
|
||||||
};
|
};
|
||||||
audio?: IAnkiConnectMediaInNote[];
|
audio?: IAnkiConnectMediaInNote[];
|
||||||
|
|||||||
+102
-3
@@ -2,14 +2,18 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|||||||
|
|
||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
|
import { soundTag } from './anki';
|
||||||
|
import { AnkiConnect, IAnkiConnectActions } from './ankiconnect';
|
||||||
|
import { logger } from './logger';
|
||||||
|
|
||||||
export class WaniKani {
|
export class WaniKani {
|
||||||
$axios: AxiosInstance;
|
$axios: AxiosInstance;
|
||||||
|
|
||||||
constructor(public apiKey = process.env['WANIKANI_API_KEY']!) {
|
constructor(public apiKey = process.env['WANIKANI_API_KEY']) {
|
||||||
this.$axios = axios.create({
|
this.$axios = axios.create({
|
||||||
baseURL: 'https://api.wanikani.com/v2/',
|
baseURL: 'https://api.wanikani.com/v2/',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${process.env['WANIKANI_API_KEY']}`,
|
Authorization: apiKey ? `Bearer ${apiKey}` : '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -54,13 +58,108 @@ export class WaniKani {
|
|||||||
}>(nextURL)
|
}>(nextURL)
|
||||||
.then((r) => r.data);
|
.then((r) => r.data);
|
||||||
data.push(...r.data);
|
data.push(...r.data);
|
||||||
console.info(r.pages.next_url);
|
logger('WaniKani API:', r.pages.next_url);
|
||||||
nextURL = r.pages.next_url || '';
|
nextURL = r.pages.next_url || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
subjects.dump(data);
|
subjects.dump(data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async populateSound(
|
||||||
|
query: string,
|
||||||
|
fields: {
|
||||||
|
ja: string;
|
||||||
|
audio: string;
|
||||||
|
},
|
||||||
|
opts: {
|
||||||
|
mode?: {
|
||||||
|
online?: boolean;
|
||||||
|
};
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
const subjects = await this.subjects();
|
||||||
|
|
||||||
|
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 += ` -${fields.ja}: ${fields.audio}:`;
|
||||||
|
|
||||||
|
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[fields.ja] || {};
|
||||||
|
if (ja) {
|
||||||
|
const audio = audioMap.get(
|
||||||
|
ja.replace(/\[.+?\]/g, '').replace(/ /g, ''),
|
||||||
|
);
|
||||||
|
if (audio) {
|
||||||
|
notesToUpdate.push(
|
||||||
|
opts.mode?.online
|
||||||
|
? {
|
||||||
|
id: n.noteId,
|
||||||
|
fields: {
|
||||||
|
[fields.audio]: soundTag.make(audio.url),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: n.noteId,
|
||||||
|
fields: {},
|
||||||
|
audio: [
|
||||||
|
{
|
||||||
|
url: audio.url,
|
||||||
|
filename: audio.filename,
|
||||||
|
fields: [fields.audio],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notesToUpdate.length) return;
|
||||||
|
|
||||||
|
while (notesToUpdate.length) {
|
||||||
|
await anki.multi<'updateNoteFields'[]>({
|
||||||
|
actions: notesToUpdate.splice(0, 100).map((note) => ({
|
||||||
|
action: 'updateNoteFields',
|
||||||
|
params: {
|
||||||
|
note,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type WaniKaniDate = string;
|
type WaniKaniDate = string;
|
||||||
|
|||||||
Reference in new issue
Block a user