|
|
@ -1,4 +1,4 @@ |
|
|
|
import { AnkiConnect } from '@/ankiconnect'; |
|
|
|
import { AnkiConnect, IAnkiConnectActions } from '@/ankiconnect'; |
|
|
|
|
|
|
|
import { makeKanjiLevels } from './get-kanji-level'; |
|
|
|
|
|
|
@ -8,95 +8,132 @@ const KANJI_FIELD = 'Japanese'; |
|
|
|
async function main() { |
|
|
|
const anki = new AnkiConnect(); |
|
|
|
|
|
|
|
console.log( |
|
|
|
await anki |
|
|
|
.api('findCards', { |
|
|
|
query: `deck:${DECK}`, |
|
|
|
}) |
|
|
|
.then((cards) => anki.api('cardsInfo', { cards })) |
|
|
|
.then(async (rs) => { |
|
|
|
const kanjiLevels = await makeKanjiLevels(); |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
}); |
|
|
|
const deckConfig = await anki.api('getDeckConfig', { |
|
|
|
deck: DECK, |
|
|
|
}); |
|
|
|
|
|
|
|
await anki |
|
|
|
.api('findCards', { |
|
|
|
query: `deck:${DECK}`, |
|
|
|
}) |
|
|
|
.then((cards) => anki.api('cardsInfo', { cards })) |
|
|
|
.then(async (rs) => { |
|
|
|
const kanjiLevels = await makeKanjiLevels(); |
|
|
|
|
|
|
|
if (subdeck) { |
|
|
|
const cardIds = vs[subdeck] || []; |
|
|
|
cardIds.push(r.cardId); |
|
|
|
vs[subdeck] = cardIds; |
|
|
|
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; |
|
|
|
|
|
|
|
modelToSubdecks.set(r.modelName, vs); |
|
|
|
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), |
|
|
|
})), |
|
|
|
), |
|
|
|
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 = { |
|
|
|
data: new Map<string, number[]>(), |
|
|
|
async get(templateName: string) { |
|
|
|
let v = this.data.get(templateName); |
|
|
|
if (!v) { |
|
|
|
v = await anki.api('findCards', { |
|
|
|
query: `deck:${DECK} card:${templateName}`, |
|
|
|
}); |
|
|
|
), |
|
|
|
); |
|
|
|
}) |
|
|
|
.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: `deck:${DECK} 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); |
|
|
|
} |
|
|
|
this.data.set(templateName, v); |
|
|
|
return v; |
|
|
|
}, |
|
|
|
}; |
|
|
|
|
|
|
|
return Promise.all( |
|
|
|
rs |
|
|
|
.flat() |
|
|
|
.flatMap((r) => |
|
|
|
r.templateNames.map((templateName) => ({ |
|
|
|
...r, |
|
|
|
templateName, |
|
|
|
})), |
|
|
|
) |
|
|
|
.map(({ templateName, subdeck, cardIdsSet }) => |
|
|
|
cardsByTemplateName.get(templateName).then((cards) => |
|
|
|
anki.api('changeDeck', { |
|
|
|
cards: cards.filter((c) => cardIdsSet.has(c)), |
|
|
|
deck: `${DECK}::${subdeck}::${templateName}`, |
|
|
|
}), |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
}), |
|
|
|
); |
|
|
|
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) { |
|
|
|