|
|
- import { AnkiConnect, IAnkiConnectActions } from '@/ankiconnect';
-
- import { makeKanjiLevels } from './get-kanji-level';
-
- const DECK = 'Takoboto';
- const KANJI_FIELD = 'Japanese';
- /**
- * Whether to use Level > 60, i.e. outside WaniKani
- *
- * @link https://community.wanikani.com/t/fake-levels-61-70-or-%E7%84%A1%E9%99%90-infinity/16399
- */
- const USE_BEYOND = true;
-
- async function main() {
- const anki = new AnkiConnect();
-
- const deckConfig = await anki.api('getDeckConfig', {
- deck: DECK,
- });
-
- const deckQuery = `deck:${DECK} -deck:${DECK}::*`;
- const kanjiLevels = await makeKanjiLevels({ useBeyond: USE_BEYOND });
-
- await anki
- .api('findCards', {
- query: deckQuery,
- })
- .then((cards) => anki.api('cardsInfo', { cards }))
- .then(async (rs) => {
- const modelToSubdecks = new Map<
- string,
- {
- [subdeck: string]: number[];
- }
- >();
- 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;
-
- Array.from(value).map((k) => {
- const m = kanjiLevels.get(k);
- if (m && m.level > level) {
- level = m.level;
- subdeck = m.deckName;
- }
- });
-
- if (subdeck) {
- const cardIds = vs[subdeck] || [];
- cardIds.push(r.cardId);
- vs[subdeck] = cardIds;
-
- modelToSubdecks.set(r.modelName, vs);
- }
- }
- });
-
- 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(async (rs) => {
- const remaining: {
- [templateName: string]: Set<number>;
- } = {};
-
- const cardsByTemplateName = {
- data: new Map<string, number[]>(),
- async get(templateName: string) {
- let v = this.data.get(templateName);
- if (!v) {
- v = await anki.api('findCards', {
- query: `${deckQuery} card:${templateName}`,
- });
- remaining[templateName] = new Set(v);
- }
- this.data.set(templateName, v);
- return v;
- },
- };
-
- 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) {
- main();
- }
|