got kanji with ja and en translation working (not both) and all options at random

This commit is contained in:
Rene Kievits
2025-10-26 02:35:30 +01:00
parent fc341e57af
commit 882328c28e
11 changed files with 799 additions and 221 deletions

View File

@@ -1,8 +1,6 @@
import fetch from 'node-fetch'
import { ApiKeyModel } from '../models/apikey.model.ts'
import { AssignmentModel } from '../models/assignments.model.ts'
import { KanjiModel } from '../models/kanji.model.ts'
import { VocabularyModel } from '../models/vocabulary.model.ts'
import { ReviewItemModel } from '../models/reviewitem.model.ts'
import type { KanjiItem, VocabularyItem } from '../types/wanikani.ts'
@@ -24,6 +22,7 @@ export interface WaniKaniSubject {
level: number
slug: string
unlocked_at?: string
subject_type: 'kanji' | 'vocabulary' | 'kana_vocabulary'
}
}
@@ -63,28 +62,56 @@ const fetchSubjects = async (
return results
}
const mapKanji = (item: WaniKaniSubject, userId: string): KanjiItem => ({
userId: userId,
characters: item.data.characters,
meanings: item.data.meanings,
readings: item.data.readings,
auxiliary_meanings: item.data.auxiliary_meanings,
level: item.data.level,
slug: item.data.slug,
srs_score: 0,
})
const mapToReviewItem = (item: WaniKaniSubject, userId: string) => {
let type: 'kanji' | 'vocab'
const subjectType = item.data.subject_type || item.object
if (subjectType === 'kanji') type = 'kanji'
else if (['vocabulary', 'kana_vocabulary'].includes(subjectType)) type = 'vocab'
else throw new Error(`Unknown WaniKani subject type: ${subjectType}`)
const mapVocab = (item: WaniKaniSubject, userId: string): VocabularyItem => ({
userId: userId,
characters: item.data.characters,
meanings: item.data.meanings,
readings: item.data.readings ?? [],
auxiliary_meanings: item.data.auxiliary_meanings,
pronunciation_audios: item.data.pronunciation_audios ?? [],
level: item.data.level,
slug: item.data.slug,
srs_score: 0,
})
return {
id: item.id,
userId,
type,
characters: item.data.characters,
meanings: item.data.meanings,
readings: item.data.readings ?? [],
auxiliary_meanings: item.data.auxiliary_meanings ?? [],
pronunciation_audios: item.data.pronunciation_audios ?? [],
level: item.data.level,
slug: item.data.slug,
srs: {
jp_en_meaning: {
streak: 0,
ease_factor: 2.5,
interval: 1,
next_review: new Date(),
last_review: null,
},
jp_en_reading: {
streak: 0,
ease_factor: 2.5,
interval: 1,
next_review: new Date(),
last_review: null,
},
en_jp_writing: {
streak: 0,
ease_factor: 2.5,
interval: 1,
next_review: new Date(),
last_review: null,
},
writing_practice: {
streak: 0,
ease_factor: 2.5,
interval: 1,
next_review: new Date(),
last_review: null,
},
},
}
}
export const syncWanikaniData = async (apiKey: string, userId: string): Promise<void> => {
const headers = { Authorization: `Bearer ${apiKey}` }
@@ -92,50 +119,48 @@ export const syncWanikaniData = async (apiKey: string, userId: string): Promise<
try {
await ApiKeyModel.updateOne(
{},
{ $set: { user: userId, apiKey: apiKey, lastUsed: new Date() } },
{ $set: { userId: userId, apiKey: apiKey, lastUsed: new Date() } },
{ upsert: true },
)
const assignments = await fetchAllPages(`${WANIKANI_API_BASE}/assignments`, headers)
const unlockedKanjiSubjectIds: number[] = []
const unlockedVocabSubjectIds: number[] = []
const unlockedKanjiIds: number[] = []
const unlockedVocabIds: number[] = []
for (const a of assignments) {
const assignmentData = a.data as any
if (!assignmentData.unlocked_at) continue
if (assignmentData.subject_type === 'kanji') {
unlockedKanjiSubjectIds.push(assignmentData.subject_id)
} else if (['vocabulary', 'kana_vocabulary'].includes(assignmentData.subject_type)) {
unlockedVocabSubjectIds.push(assignmentData.subject_id)
}
if (assignmentData.subject_type === 'kanji') unlockedKanjiIds.push(assignmentData.subject_id)
else if (['vocabulary', 'kana_vocabulary'].includes(assignmentData.subject_type)) unlockedVocabIds.push(assignmentData.subject_id)
}
await AssignmentModel.updateOne(
{ userId: userId, subject_type: 'kanji' },
{ $set: { subject_ids: unlockedKanjiSubjectIds } },
{ userId, subject_type: 'kanji' },
{ $set: { subject_ids: unlockedKanjiIds } },
{ upsert: true },
)
await AssignmentModel.updateOne(
{ userId: userId, subject_type: 'vocabulary' },
{ $set: { subject_ids: unlockedVocabSubjectIds } },
{ userId, subject_type: 'vocab' },
{ $set: { subject_ids: unlockedVocabIds } },
{ upsert: true },
)
const existingKanjiSlugs = new Set((await KanjiModel.find({}, { slug: 1 })).map(k => k.slug))
const kanjiSubjects = await fetchSubjects(unlockedKanjiSubjectIds, headers)
const newKanji = kanjiSubjects.filter(s => !existingKanjiSlugs.has(s.data.slug))
if (newKanji.length > 0) await KanjiModel.insertMany(newKanji.map(k => mapKanji(k, userId)))
// Fetch and insert new review items
const kanjiSubjects = await fetchSubjects(unlockedKanjiIds, headers)
const vocabSubjects = await fetchSubjects(unlockedVocabIds, headers)
const existingVocabSlugs = new Set((await VocabularyModel.find({}, { slug: 1 })).map(v => v.slug))
const vocabSubjects = await fetchSubjects(unlockedVocabSubjectIds, headers)
const newVocab = vocabSubjects.filter(s => !existingVocabSlugs.has(s.data.slug))
if (newVocab.length > 0) await VocabularyModel.insertMany(newVocab.map(v => mapVocab(v, userId)))
const allSlugs = new Set((await ReviewItemModel.find({ userId }, { slug: 1 })).map(r => r.slug))
console.log('✅ Sync complete')
const newItems = [...kanjiSubjects, ...vocabSubjects]
.filter(s => !allSlugs.has(s.data.slug))
.map(s => mapToReviewItem(s, userId))
if (newItems.length > 0) await ReviewItemModel.insertMany(newItems)
console.log('✅ WaniKani sync complete')
} catch (err) {
console.error('❌ Error syncing WaniKani data:', err)
throw err