anki-reader
Version:
A library for reading Anki apkg and collection files.
86 lines (85 loc) • 3.69 kB
JavaScript
import initSqlJs from 'sql.js';
import fetch from 'node-fetch';
import { AnkiCollection } from './classes/AnkiCollection.js';
import * as zip from '@zip.js/zip.js';
export async function readAnkiCollection(ankiCollection, options) {
const collection = await initSqlJs(options?.sqlConfig)
.then(SQL => {
const db = new SQL.Database(new Uint8Array(ankiCollection));
return new AnkiCollection(db);
})
.catch(error => {
console.error('Error loading SQLite database:', error);
throw new Error('Error loading SQLite database');
});
return collection;
}
export async function readFromUrl(url, options) {
const collection = await fetch(url)
.then(async (response) => await response.arrayBuffer())
.then(async (arrayBuffer) => { return await readAnkiCollection(new Uint8Array(arrayBuffer), options); })
.catch(error => {
console.error('Error loading SQLite database:', error);
throw new Error('Error loading SQLite database');
});
return collection;
}
async function readMedia(entries) {
const media = {};
const mediaMap = entries.find(entry => entry.filename === 'media');
if (mediaMap?.getData == null) {
console.warn('Could not find media map in Anki package');
return media;
}
try {
const mediaMapArrayBuffer = await mediaMap.getData(new zip.Uint8ArrayWriter());
const mediaMapString = new TextDecoder('utf-8').decode(new Uint8Array(mediaMapArrayBuffer));
const mediaMapJson = JSON.parse(mediaMapString);
for (const [key, value] of Object.entries(mediaMapJson)) {
const mediaEntry = entries.find(entry => entry.filename === key);
if (mediaEntry?.getData == null) {
continue;
}
const mediaArrayBuffer = await mediaEntry.getData(new zip.Uint8ArrayWriter());
// default to original key if value is invalid
media[value?.toString() ?? key] = new Blob([new Uint8Array(mediaArrayBuffer)]);
}
}
catch (error) {
console.error('Error parsing media map:', error);
return media;
}
return media;
}
export async function readAnkiPackage(ankiPackage, options) {
const reader = new zip.ZipReader(new zip.BlobReader(ankiPackage));
return await reader.getEntries().then(async (entries) => {
const collectionEntry = entries.find(entry => entry.filename === 'collection.anki2');
if (collectionEntry?.getData == null) {
throw new Error('Could not find collection.anki2 in Anki package');
}
const collectionArrayBuffer = await collectionEntry.getData(new zip.Uint8ArrayWriter());
const collection = await readAnkiCollection(new Uint8Array(collectionArrayBuffer), options);
const media = await readMedia(entries);
return {
collection,
media
};
});
}
export async function readAnkiPackageFromUrl(url, options) {
const reader = new zip.ZipReader(new zip.HttpReader(url));
return await reader.getEntries().then(async (entries) => {
const collectionEntry = entries.find(entry => entry.filename === 'collection.anki2');
if (collectionEntry?.getData == null) {
throw new Error('Could not find collection.anki2 in Anki package');
}
const collectionArrayBuffer = await collectionEntry.getData(new zip.Uint8ArrayWriter());
const collection = await readAnkiCollection(new Uint8Array(collectionArrayBuffer), options);
const media = await readMedia(entries);
return {
collection,
media
};
});
}