aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/scripts/import-anki.ts
blob: 739b485d2a53776e4d3f0fb66f776f9220daf9cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import * as readline from "node:readline/promises";
import { mapAnkiToKioku, parseAnkiPackage } from "../anki/index.js";
import { db } from "../db/index.js";
import { cards, decks } from "../db/schema.js";
import { userRepository } from "../repositories/index.js";

async function main() {
	const rl = readline.createInterface({
		input: process.stdin,
		output: process.stdout,
	});

	// Get file path from command line argument or prompt
	let filePath = process.argv[2];
	if (!filePath) {
		filePath = await rl.question("Anki package path (.apkg): ");
	}

	if (!filePath) {
		console.error("Error: File path is required");
		process.exit(1);
	}

	// Get username
	const username = await rl.question("Username: ");
	rl.close();

	if (!username) {
		console.error("Error: Username is required");
		process.exit(1);
	}

	// Find user
	const user = await userRepository.findByUsername(username);
	if (!user) {
		console.error(`Error: User "${username}" not found`);
		process.exit(1);
	}

	console.log(`\nParsing Anki package: ${filePath}`);

	// Parse the Anki package
	const ankiPackage = await parseAnkiPackage(filePath);

	console.log(`Found ${ankiPackage.decks.length} deck(s)`);
	console.log(`Found ${ankiPackage.notes.length} note(s)`);
	console.log(`Found ${ankiPackage.cards.length} card(s)`);

	// Convert to Kioku format
	const importData = mapAnkiToKioku(ankiPackage);

	if (importData.length === 0) {
		console.log("\nNo decks to import (all decks may be empty or skipped)");
		process.exit(0);
	}

	console.log(`\nImporting ${importData.length} deck(s):`);
	for (const { deck, cards: deckCards } of importData) {
		console.log(`  - ${deck.name}: ${deckCards.length} card(s)`);
	}

	// Import decks and cards
	let totalCards = 0;
	for (const { deck, cards: kiokuCards } of importData) {
		// Create deck
		const [newDeck] = await db
			.insert(decks)
			.values({
				userId: user.id,
				name: deck.name,
				description: deck.description,
				newCardsPerDay: 20,
			})
			.returning();

		if (!newDeck) {
			console.error(`Error: Failed to create deck "${deck.name}"`);
			continue;
		}

		// Create cards in batches
		if (kiokuCards.length > 0) {
			const cardValues = kiokuCards.map((card) => ({
				deckId: newDeck.id,
				front: card.front,
				back: card.back,
				state: card.state,
				due: card.due,
				stability: card.stability,
				difficulty: card.difficulty,
				elapsedDays: card.elapsedDays,
				scheduledDays: card.scheduledDays,
				reps: card.reps,
				lapses: card.lapses,
				lastReview: card.lastReview,
			}));

			await db.insert(cards).values(cardValues);
			totalCards += kiokuCards.length;
		}

		console.log(
			`  Created deck "${deck.name}" with ${kiokuCards.length} cards`,
		);
	}

	console.log(`\nImport complete!`);
	console.log(`  Decks: ${importData.length}`);
	console.log(`  Cards: ${totalCards}`);

	process.exit(0);
}

main().catch((error) => {
	console.error("Error:", error.message);
	process.exit(1);
});