prototype subdeck from Kanji

This commit is contained in:
polv committed 2022-04-27 14:04:52 +07:00
1 parent f427a8e148
commit 13f2bb421c
4 files changed
+148 -84

No files matched your search

+120 -83
View File
@@ -1,4 +1,4 @@
import { AnkiConnect } from '@/ankiconnect'; import { AnkiConnect, IAnkiConnectActions } from '@/ankiconnect';
import { makeKanjiLevels } from './get-kanji-level'; import { makeKanjiLevels } from './get-kanji-level';
@@ -8,95 +8,132 @@ const KANJI_FIELD = 'Japanese';
async function main() { async function main() {
const anki = new AnkiConnect(); const anki = new AnkiConnect();
console.log( const deckConfig = await anki.api('getDeckConfig', {
await anki deck: DECK,
.api('findCards', { });
query: `deck:${DECK}`,
})
.then((cards) => anki.api('cardsInfo', { cards }))
.then(async (rs) => {
const kanjiLevels = await makeKanjiLevels();
const modelToSubdecks = new Map< await anki
string, .api('findCards', {
{ query: `deck:${DECK}`,
[subdeck: string]: number[]; })
} .then((cards) => anki.api('cardsInfo', { cards }))
>(); .then(async (rs) => {
rs.map((r) => { const kanjiLevels = await makeKanjiLevels();
const vs = modelToSubdecks.get(r.modelName) || {};
const { value } = r.fields[KANJI_FIELD] || {};
if (value) {
// Get the subdeck names from Kanji
let subdeck = '';
let level = 0;
Array.from(value).map((k) => { const modelToSubdecks = new Map<
const m = kanjiLevels.get(k); string,
if (m && m.level > level) { {
level = m.level; [subdeck: string]: number[];
subdeck = m.deckName; }
} >();
}); rs.map((r) => {
const vs = modelToSubdecks.get(r.modelName) || {};
const { value } = r.fields[KANJI_FIELD] || {};
if (value) {
// Get the subdeck names from Kanji
let subdeck = '';
let level = 0;
if (subdeck) { Array.from(value).map((k) => {
const cardIds = vs[subdeck] || []; const m = kanjiLevels.get(k);
cardIds.push(r.cardId); if (m && m.level > level) {
vs[subdeck] = cardIds; level = m.level;
subdeck = m.deckName;
modelToSubdecks.set(r.modelName, vs);
} }
} });
});
return Promise.all( if (subdeck) {
Array.from(modelToSubdecks).map(([modelName, subdecks]) => const cardIds = vs[subdeck] || [];
anki.api('modelTemplates', { modelName }).then((templates) => cardIds.push(r.cardId);
Object.entries(subdecks).map(([subdeck, cardIds]) => ({ vs[subdeck] = cardIds;
modelName,
templateNames: Object.keys(templates), modelToSubdecks.set(r.modelName, vs);
subdeck, }
cardIdsSet: new Set(cardIds), }
})), });
),
return Promise.all(
Array.from(modelToSubdecks).map(([modelName, subdecks]) =>
anki.api('modelTemplates', { modelName }).then((templates) =>
Object.entries(subdecks).map(([subdeck, cardIds]) => ({
modelName,
templateNames: Object.keys(templates),
subdeck,
cardIdsSet: new Set(cardIds),
})),
), ),
); ),
}) );
.then((rs) => { })
const cardsByTemplateName = { .then(async (rs) => {
data: new Map<string, number[]>(), const remaining: {
async get(templateName: string) { [templateName: string]: Set<number>;
let v = this.data.get(templateName); } = {};
if (!v) {
v = await anki.api('findCards', {
query: `deck:${DECK} card:${templateName}`,
});
}
this.data.set(templateName, v);
return v;
},
};
return Promise.all( const cardsByTemplateName = {
rs data: new Map<string, number[]>(),
.flat() async get(templateName: string) {
.flatMap((r) => let v = this.data.get(templateName);
r.templateNames.map((templateName) => ({ if (!v) {
...r, v = await anki.api('findCards', {
templateName, query: `deck:${DECK} card:${templateName}`,
})), });
) remaining[templateName] = new Set(v);
.map(({ templateName, subdeck, cardIdsSet }) => }
cardsByTemplateName.get(templateName).then((cards) => this.data.set(templateName, v);
anki.api('changeDeck', { return v;
cards: cards.filter((c) => cardIdsSet.has(c)), },
deck: `${DECK}::${subdeck}::${templateName}`, };
}),
), const toChangeDeck: IAnkiConnectActions['changeDeck']['params'][] = [];
),
); for (const { templateName, subdeck, cardIdsSet } of rs
}), .flat()
); .flatMap((r) =>
r.templateNames.map((templateName) => ({
...r,
templateName,
})),
)) {
const cards = await cardsByTemplateName.get(templateName);
toChangeDeck.push({
cards: cards.filter((c) => {
const r = remaining[templateName];
if (r) {
r.delete(c);
}
return cardIdsSet.has(c);
}),
deck: `${DECK}::${subdeck}::${templateName}`,
});
}
Object.entries(remaining).map(([templateName, cards]) => {
if (cards.size) {
toChangeDeck.push({
cards: [...cards],
deck: `${DECK}::独習::${templateName}`,
});
}
});
if (toChangeDeck.length) {
const batchSize = 20;
for (let i = 0; i < toChangeDeck.length; i += batchSize) {
await Promise.all(
toChangeDeck
.slice(i, i + batchSize)
.map((params) => anki.api('changeDeck', params)),
);
}
await anki.api('setDeckConfigId', {
decks: toChangeDeck.map((r) => r.deck),
configId: deckConfig.id,
});
}
});
} }
if (require.main === module) { if (require.main === module) {
+4
View File
@@ -42,3 +42,7 @@ export interface IAnkiTemplate {
bqfmt: string; bqfmt: string;
bamt: string; bamt: string;
} }
export interface IAnkiDeckConfig {
id: number;
}
+19 -1
View File
@@ -1,6 +1,7 @@
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { IAnkiCard, IAnkiQuery } from './anki'; import { IAnkiCard, IAnkiDeckConfig, IAnkiQuery } from './anki';
import { logger } from './logger';
export interface IAnkiConnectActions export interface IAnkiConnectActions
extends Record< extends Record<
@@ -67,6 +68,19 @@ export interface IAnkiConnectActions
}; };
result: null; result: null;
}; };
getDeckConfig: {
params: {
deck: string;
};
result: IAnkiDeckConfig;
};
setDeckConfigId: {
params: {
decks: string[];
configId: number;
};
result: boolean;
};
// Media Actions // Media Actions
storeMediaFile: { storeMediaFile: {
@@ -443,6 +457,8 @@ export class AnkiConnect {
params: IAnkiConnectActions[A]['params'], params: IAnkiConnectActions[A]['params'],
version = this.version, version = this.version,
): Promise<IAnkiConnectActions[A]['result']> { ): Promise<IAnkiConnectActions[A]['result']> {
logger(`AnkiConnect: calling ${action}`, params);
const { data: response } = await this.$axios.post<{ const { data: response } = await this.$axios.post<{
result: any; result: any;
error: string | null; error: string | null;
@@ -461,6 +477,8 @@ export class AnkiConnect {
throw response.error; throw response.error;
} }
logger(`AnkiConnect: finished ${action}`, params);
return response.result; return response.result;
} }
+5
View File
@@ -0,0 +1,5 @@
export function logger(...args: any[]) {
if (process.env['DEBUG']) {
console.debug(...args);
}
}