├── .gitattributes ├── .eslintignore ├── .prettierrc ├── .vscode └── settings.json ├── scripts ├── open-folder.ts ├── wsl.ts └── read.ts ├── src ├── tsconfig.json ├── util.ts ├── dir.ts ├── ankiconnect.ts ├── anki20 │ └── index.ts ├── index.ts └── anki21 │ └── index.ts ├── lib ├── util.d.ts ├── dir.d.ts.map ├── util.d.ts.map ├── dir.d.ts ├── util.js ├── util.js.map ├── ankiconnect.js.map ├── ankiconnect.js ├── index.d.ts.map ├── index.d.ts ├── dir.js.map ├── anki20 │ ├── index.d.ts.map │ ├── index.js.map │ ├── index.d.ts │ └── index.js ├── ankiconnect.d.ts.map ├── dir.js ├── ankiconnect.d.ts ├── anki21 │ ├── index.d.ts.map │ ├── index.js.map │ ├── index.d.ts │ └── index.js ├── index.js.map └── index.js ├── .eslintrc.js ├── README.md ├── LICENSE ├── package.json ├── .gitignore └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | lib/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.* 2 | !*.js 3 | !*.ts 4 | /lib/ 5 | /node_modules/ 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "arrowParens": "always", 4 | "singleQuote": true, 5 | "trailingComma": "none" 6 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "rvest.vs-code-prettier-eslint", 3 | "editor.formatOnSave": true 4 | } -------------------------------------------------------------------------------- /scripts/open-folder.ts: -------------------------------------------------------------------------------- 1 | import open from 'open' 2 | 3 | import { getAnkiPath } from '../lib/dir' 4 | 5 | open(getAnkiPath('User 1')) 6 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../lib", 5 | "rootDir": "." 6 | } 7 | } -------------------------------------------------------------------------------- /lib/util.d.ts: -------------------------------------------------------------------------------- 1 | export declare function mapAsync(arr: T[], cb: (el: T, i: number, a0: T[]) => Promise): Promise; 2 | //# sourceMappingURL=util.d.ts.map -------------------------------------------------------------------------------- /lib/dir.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"dir.d.ts","sourceRoot":"","sources":["../src/dir.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,UAwBvC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,UAE7C"} -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export async function mapAsync( 2 | arr: T[], 3 | cb: (el: T, i: number, a0: T[]) => Promise 4 | ): Promise { 5 | return Promise.all( 6 | arr.map(async (el, i, a0) => { 7 | return await cb(el, i, a0) 8 | }) 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /lib/util.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EACrC,GAAG,EAAE,CAAC,EAAE,EACR,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5C,OAAO,CAAC,CAAC,EAAE,CAAC,CAMd"} -------------------------------------------------------------------------------- /lib/dir.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Most reliable way is to go to 3 | * 4 | * Tools >> Add-ons >> View Files 5 | */ 6 | export declare function getAnkiPath(user: string): string; 7 | /** 8 | * Get User's `collection.anki2` 9 | */ 10 | export declare function getAnkiCollection(user: string): string; 11 | //# sourceMappingURL=dir.d.ts.map -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.mapAsync = void 0; 4 | async function mapAsync(arr, cb) { 5 | return Promise.all(arr.map(async (el, i, a0) => { 6 | return await cb(el, i, a0); 7 | })); 8 | } 9 | exports.mapAsync = mapAsync; 10 | //# sourceMappingURL=util.js.map -------------------------------------------------------------------------------- /lib/util.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAO,KAAK,UAAU,QAAQ,CAC5B,GAAQ,EACR,EAA6C;IAE7C,OAAO,OAAO,CAAC,GAAG,CAChB,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE;QAC1B,OAAO,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAC,CACH,CAAA;AACH,CAAC;AATD,4BASC"} -------------------------------------------------------------------------------- /scripts/wsl.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from 'child_process' 2 | import path from 'path' 3 | 4 | console.log( 5 | path.join( 6 | '/mnt', 7 | spawnSync('cmd.exe', ['/c', 'echo', '%APPDATA%']) 8 | .stdout.toString() 9 | .trim() 10 | .replace(/^(\S+):/, (_, p: string) => p.toLocaleLowerCase()) 11 | .replace(/\\/g, '/') 12 | ) 13 | ) 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2020: true, 4 | node: true 5 | }, 6 | extends: ['standard'], 7 | globals: { 8 | NodeJS: 'readonly' 9 | }, 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | ecmaVersion: 12, 13 | sourceType: 'module' 14 | }, 15 | plugins: ['@typescript-eslint'], 16 | rules: { 17 | 'space-before-function-paren': 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ankisync.js 2 | 3 | Do in Anki what Anki cannot do in JavaScript/TypeScript (Node.js) 4 | 5 | Ankisync.js is powered by [liteorm](https://github.com/patarapolw/liteorm). 6 | 7 | ## Example 8 | 9 | See [/scripts](/scripts) 10 | 11 | ```ts 12 | import { Apkg, ankiCards, ankiNotes, ankiModels, ankiTemplates, ankiDecks } from 'ankisync' 13 | 14 | ;(async () => { 15 | const { anki2 } = await Apkg.connect('foo.apkg') 16 | const r = await await apkg.anki2.find({}) 17 | console.log(r) 18 | })().catch(console.error) 19 | ``` 20 | -------------------------------------------------------------------------------- /lib/ankiconnect.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ankiconnect.js","sourceRoot":"","sources":["../src/ankiconnect.ts"],"names":[],"mappings":";;;;;;AAAA,kDAAyB;AAkFZ,QAAA,WAAW,GAAG;IACzB,GAAG,EAAE,eAAK,CAAC,MAAM,CAAC;QAChB,OAAO,EAAE,uBAAuB;KACjC,CAAC;IACF,KAAK,CAAC,MAAM,CACV,MAAS,EACT,SAAqC,EAAE,EACvC,OAAO,GAAG,CAAC;QAEX,MAAM,EACJ,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EACxB,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;QAEzD,IAAI,KAAK,EAAE;YACT,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,CAAA;SACvB;QAED,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA"} -------------------------------------------------------------------------------- /lib/ankiconnect.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.ankiconnect = void 0; 7 | const axios_1 = __importDefault(require("axios")); 8 | exports.ankiconnect = { 9 | api: axios_1.default.create({ 10 | baseURL: 'http://127.0.0.1:8765' 11 | }), 12 | async invoke(action, params = {}, version = 6) { 13 | const { data: { result, error } } = await this.api.post('/', { action, version, params }); 14 | if (error) { 15 | throw new Error(error); 16 | } 17 | return result; 18 | } 19 | }; 20 | //# sourceMappingURL=ankiconnect.js.map -------------------------------------------------------------------------------- /lib/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAgB,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAK1C,qBAAa,KAAK;WACI,OAAO,CAAC,OAAO,EAAE,MAAM;IA8E3C,EAAE,EAAE,QAAQ,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IAEf,OAAO;IAKD,IAAI,CACR,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CACR;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,MAAM,EAAE,CAAA;QAChB,IAAI,EAAE,MAAM,EAAE,CAAA;QACd,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;QAClB,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;QACnB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAE,MAAM,CAAA;KACd,EAAE,CACJ;IA4BK,QAAQ;IAiGR,OAAO;CAGd;AAED,qBAAa,IAAI;WACF,OAAO,CAAC,QAAQ,EAAE,MAAM;IAwCrC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,KAAK,CAAA;IAEZ,OAAO;IAMD,QAAQ,CAAC,SAAS,UAAO;IAiC/B;;;;OAIG;IACG,OAAO;CAId"} -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Database } from 'sqlite'; 2 | export declare class Anki2 { 3 | static connect(colPath: string): Promise; 4 | db: Database; 5 | colPath: string; 6 | private constructor(); 7 | find(where?: string, postfix?: string): Promise<{ 8 | deck: string; 9 | values: string[]; 10 | keys: string[]; 11 | css: string | null; 12 | qfmt: string; 13 | afmt: string | null; 14 | template: string; 15 | model: string; 16 | }[]>; 17 | finalize(): Promise; 18 | cleanup(): Promise; 19 | } 20 | export declare class Apkg { 21 | static connect(filePath: string): Promise; 22 | filePath: string; 23 | dir: string; 24 | anki2: Anki2; 25 | private constructor(); 26 | finalize(overwrite?: boolean): Promise; 27 | /** 28 | * You will lose any unsaved data. 29 | * 30 | * Use #finalize to save data. 31 | */ 32 | cleanup(): Promise; 33 | } 34 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /lib/dir.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"dir.js","sourceRoot":"","sources":["../src/dir.ts"],"names":[],"mappings":";;;;;;AAAA,iDAAyC;AACzC,4CAAmB;AACnB,4CAAmB;AACnB,gDAAuB;AAEvB;;;;GAIG;AACH,SAAgB,WAAW,CAAC,IAAY;IACtC,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,QAAQ,YAAE,CAAC,QAAQ,EAAE,EAAE;YACrB,KAAK,OAAO;gBACV,IAAI,YAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE;oBAClE,OAAO,cAAI,CAAC,IAAI,CACd,MAAM,EACN,yBAAS,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;yBAC9C,MAAM,CAAC,QAAQ,EAAE;yBACjB,IAAI,EAAE;yBACN,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;yBAC3D,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CACvB,CAAA;iBACF;gBACD,MAAK;YACP,KAAK,OAAO;gBACV,OAAO,OAAO,CAAC,GAAG,CAAC,OAAQ,CAAA;YAC7B,KAAK,QAAQ;gBACX,OAAO,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAK,EAAE,6BAA6B,CAAC,CAAA;SACrE;QAED,OAAO,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAK,EAAE,cAAc,CAAC,CAAA;IACrD,CAAC,CAAA;IACD,OAAO,cAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;AACzC,CAAC;AAxBD,kCAwBC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAAC,IAAY;IAC5C,OAAO,cAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC,CAAA;AACzD,CAAC;AAFD,8CAEC"} -------------------------------------------------------------------------------- /lib/anki20/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anki20/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAiB,MAAM,SAAS,CAAA;AAItD,qBACa,KAAK;IACkB,EAAE,EAAG,MAAM,CAAA;IACrB,IAAI,EAAG,MAAM,CAAA;CACtC;AAED,eAAO,MAAM,OAAO,yBAAmB,CAAA;AAEvC,qBACa,MAAM;IACiB,EAAE,EAAG,MAAM,CAAA;IACrB,IAAI,EAAG,MAAM,CAAA;IAQrC,IAAI,EAAG,MAAM,EAAE,CAAA;IAEP,GAAG,EAAG,MAAM,CAAA;CACrB;AAED,eAAO,MAAM,QAAQ,2BAAoB,CAAA;AAEzC,qBACa,SAAS;IACc,EAAE,EAAG,MAAM,CAAA;IACA,GAAG,EAAG,MAAM,CAAA;IAClC,IAAI,EAAG,MAAM,CAAA;IAC5B,IAAI,EAAG,MAAM,CAAA;IACb,IAAI,EAAG,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,WAAW,iCAAuB,CAAA;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBACa,GAAG;IACW,EAAE,EAAG,MAAM,CAAA;IACqB,GAAG,EAAG,MAAM,CAAA;IACnB,GAAG,EAAG,MAAM,CAAA;IACxC,GAAG,EAAG,MAAM,CAAA;IACX,GAAG,EAAG,MAAM,CAAA;IACb,GAAG,EAAG,MAAM,CAAA;IACZ,GAAG,EAAG,MAAM,CAAA;IACZ,EAAE,EAAG,MAAM,CAAA;IACzB,IAAI,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,KAAK,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,KAAK,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,IAAI,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACvC;AAED,eAAO,MAAM,KAAK,qBAAiB,CAAA"} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pacharapol Withayasakpunt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/dir.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from 'child_process' 2 | import fs from 'fs' 3 | import os from 'os' 4 | import path from 'path' 5 | 6 | /** 7 | * Most reliable way is to go to 8 | * 9 | * Tools >> Add-ons >> View Files 10 | */ 11 | export function getAnkiPath(user: string) { 12 | const root = () => { 13 | switch (os.platform()) { 14 | case 'linux': 15 | if (fs.readFileSync('/proc/version', 'utf8').includes('microsoft')) { 16 | return path.join( 17 | '/mnt', 18 | spawnSync('cmd.exe', ['/c', 'echo', '%APPDATA%']) 19 | .stdout.toString() 20 | .trim() 21 | .replace(/^(\S+):/, (_, p: string) => p.toLocaleLowerCase()) 22 | .replace(/\\/g, '/') 23 | ) 24 | } 25 | break 26 | case 'win32': 27 | return process.env.APPDATA! 28 | case 'darwin': 29 | return path.join(process.env.HOME!, 'Library/Application Support') 30 | } 31 | 32 | return path.join(process.env.HOME!, '.local/share') 33 | } 34 | return path.join(root(), 'Anki2', user) 35 | } 36 | 37 | /** 38 | * Get User's `collection.anki2` 39 | */ 40 | export function getAnkiCollection(user: string) { 41 | return path.join(getAnkiPath(user), 'collection.anki2') 42 | } 43 | -------------------------------------------------------------------------------- /scripts/read.ts: -------------------------------------------------------------------------------- 1 | // import fs from 'fs-extra' 2 | 3 | import { dbCards, dbDecks, dbNotes, initDatabase } from '../lib/anki21' 4 | import { ankiconnect } from '../lib/ankiconnect' 5 | // import { getAnkiCollection } from '../lib/dir' 6 | 7 | async function main() { 8 | // fs.copyFileSync(getAnkiCollection('User 1'), 'collection.anki2') 9 | const db = initDatabase('collection.anki2') 10 | // db.on('find-sql', console.log) 11 | 12 | const dMap = new Map() 13 | 14 | await db 15 | .all( 16 | dbNotes, 17 | { 18 | from: dbNotes.c.id, 19 | to: dbCards.c.nid 20 | }, 21 | { 22 | from: dbCards.c.did, 23 | to: dbDecks.c.id 24 | } 25 | )( 26 | { 27 | deck: { $like: 'ZhLevel%', $collate: 'binary' } 28 | }, 29 | { 30 | deck: dbDecks.c.name, 31 | tags: dbNotes.c.tags, 32 | nid: dbNotes.c.id 33 | } 34 | ) 35 | .then((rs) => 36 | rs.map((r) => { 37 | const [type, level, ...tags] = r.deck.split('\x1f').reverse() 38 | tags.push(`${type}_${level.replace(/ /g, '_')}`) 39 | tags.pop() 40 | 41 | tags.map((t) => { 42 | dMap.set(t, [...(dMap.get(t) || []), r.nid]) 43 | }) 44 | }) 45 | ) 46 | 47 | for (const [t, notes] of dMap) { 48 | ankiconnect.invoke('addTags', { 49 | notes, 50 | tags: t 51 | }) 52 | } 53 | 54 | await db.close() 55 | } 56 | 57 | if (require.main === module) { 58 | main().catch(console.error) 59 | } 60 | -------------------------------------------------------------------------------- /lib/ankiconnect.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ankiconnect.d.ts","sourceRoot":"","sources":["../src/ankiconnect.ts"],"names":[],"mappings":"AAEA,UAAU,QAAQ;IAChB,CAAC,CAAC,EAAE,MAAM,GAAG;QACX,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,MAAM,EAAE,OAAO,CAAA;KAChB,CAAA;CACF;AAED,UAAU,MAAO,SAAQ,QAAQ;IAC/B,gBAAgB,EAAE;QAChB,MAAM,EAAE;YACN,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;SAC5B,CAAA;KACF,CAAA;CACF;AAED,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,qBAAqB,CAAC,EAAE;YACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;YACjB,aAAa,CAAC,EAAE,OAAO,CAAA;SACxB,CAAA;KACF,CAAA;IACD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAA;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,EAAE,CAAA;KACjB,EAAE,CAAA;CACJ;AAED,UAAU,KAAM,SAAQ,QAAQ;IAC9B,SAAS,EAAE;QACT,OAAO,EAAE;YACP,KAAK,EAAE,MAAM,CAAA;SACd,CAAA;QACD,MAAM,EAAE,MAAM,EAAE,CAAA;KACjB,CAAA;IACD,SAAS,EAAE;QACT,OAAO,EAAE;YACP,KAAK,EAAE,MAAM,EAAE,CAAA;SAChB,CAAA;QACD,MAAM,EAAE;YACN,MAAM,EAAE,MAAM,CACZ,MAAM,EACN;gBACE,GAAG,EAAE,MAAM,CAAA;gBACX,KAAK,EAAE,MAAM,CAAA;aACd,CACF,CAAA;SACF,EAAE,CAAA;KACJ,CAAA;IACD,OAAO,EAAE;QACP,OAAO,EAAE;YACP,IAAI,EAAE,QAAQ,CAAA;SACf,CAAA;QACD,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;IACD,QAAQ,EAAE;QACR,OAAO,EAAE;YACP,IAAI,EAAE,QAAQ,EAAE,CAAA;SACjB,CAAA;QACD,MAAM,EAAE,MAAM,EAAE,CAAA;KACjB,CAAA;IACD,OAAO,EAAE;QACP,OAAO,EAAE;YACP,KAAK,EAAE,MAAM,EAAE,CAAA;YACf,IAAI,EAAE,MAAM,CAAA;SACb,CAAA;QACD,MAAM,EAAE,IAAI,CAAA;KACb,CAAA;CACF;AAED,aAAK,YAAY,GAAG,MAAM,GAAG,KAAK,CAAA;AAElC,eAAO,MAAM,WAAW;;;CAmBvB,CAAA"} -------------------------------------------------------------------------------- /lib/dir.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.getAnkiCollection = exports.getAnkiPath = void 0; 7 | const child_process_1 = require("child_process"); 8 | const fs_1 = __importDefault(require("fs")); 9 | const os_1 = __importDefault(require("os")); 10 | const path_1 = __importDefault(require("path")); 11 | /** 12 | * Most reliable way is to go to 13 | * 14 | * Tools >> Add-ons >> View Files 15 | */ 16 | function getAnkiPath(user) { 17 | const root = () => { 18 | switch (os_1.default.platform()) { 19 | case 'linux': 20 | if (fs_1.default.readFileSync('/proc/version', 'utf8').includes('microsoft')) { 21 | return path_1.default.join('/mnt', child_process_1.spawnSync('cmd.exe', ['/c', 'echo', '%APPDATA%']) 22 | .stdout.toString() 23 | .trim() 24 | .replace(/^(\S+):/, (_, p) => p.toLocaleLowerCase()) 25 | .replace(/\\/g, '/')); 26 | } 27 | break; 28 | case 'win32': 29 | return process.env.APPDATA; 30 | case 'darwin': 31 | return path_1.default.join(process.env.HOME, 'Library/Application Support'); 32 | } 33 | return path_1.default.join(process.env.HOME, '.local/share'); 34 | }; 35 | return path_1.default.join(root(), 'Anki2', user); 36 | } 37 | exports.getAnkiPath = getAnkiPath; 38 | /** 39 | * Get User's `collection.anki2` 40 | */ 41 | function getAnkiCollection(user) { 42 | return path_1.default.join(getAnkiPath(user), 'collection.anki2'); 43 | } 44 | exports.getAnkiCollection = getAnkiCollection; 45 | //# sourceMappingURL=dir.js.map -------------------------------------------------------------------------------- /lib/ankiconnect.d.ts: -------------------------------------------------------------------------------- 1 | interface IDefault { 2 | [k: string]: { 3 | payload?: unknown; 4 | result: unknown; 5 | }; 6 | } 7 | interface IModel extends IDefault { 8 | modelNamesAndIds: { 9 | result: { 10 | [modelName: string]: number; 11 | }; 12 | }; 13 | } 14 | interface IAddNote { 15 | deckName: string; 16 | modelName: string; 17 | fields: Record; 18 | options?: { 19 | allowDuplicate?: boolean; 20 | duplicateScope?: string; 21 | duplicateScopeOptions?: { 22 | deckName?: string; 23 | checkChildren?: boolean; 24 | }; 25 | }; 26 | tags?: string[]; 27 | audio?: { 28 | url: string; 29 | filename: string; 30 | skipHash?: string; 31 | fields: string[]; 32 | }[]; 33 | } 34 | interface INote extends IDefault { 35 | findNotes: { 36 | payload: { 37 | query: string; 38 | }; 39 | result: number[]; 40 | }; 41 | notesInfo: { 42 | payload: { 43 | notes: number[]; 44 | }; 45 | result: { 46 | fields: Record; 50 | }[]; 51 | }; 52 | addNote: { 53 | payload: { 54 | note: IAddNote; 55 | }; 56 | result: number; 57 | }; 58 | addNotes: { 59 | payload: { 60 | note: IAddNote[]; 61 | }; 62 | result: number[]; 63 | }; 64 | addTags: { 65 | payload: { 66 | notes: number[]; 67 | tags: string; 68 | }; 69 | result: null; 70 | }; 71 | } 72 | declare type IAnkiconnect = IModel & INote; 73 | export declare const ankiconnect: { 74 | api: import("axios").AxiosInstance; 75 | invoke(action: K, params?: IAnkiconnect[K]["payload"], version?: number): Promise; 76 | }; 77 | export {}; 78 | //# sourceMappingURL=ankiconnect.d.ts.map -------------------------------------------------------------------------------- /lib/anki20/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/anki20/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/ankiconnect.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | interface IDefault { 4 | [k: string]: { 5 | payload?: unknown 6 | result: unknown 7 | } 8 | } 9 | 10 | interface IModel extends IDefault { 11 | modelNamesAndIds: { 12 | result: { 13 | [modelName: string]: number 14 | } 15 | } 16 | } 17 | 18 | interface IAddNote { 19 | deckName: string 20 | modelName: string 21 | fields: Record 22 | options?: { 23 | allowDuplicate?: boolean 24 | duplicateScope?: string 25 | duplicateScopeOptions?: { 26 | deckName?: string 27 | checkChildren?: boolean 28 | } 29 | } 30 | tags?: string[] 31 | audio?: { 32 | url: string 33 | filename: string 34 | skipHash?: string 35 | fields: string[] 36 | }[] 37 | } 38 | 39 | interface INote extends IDefault { 40 | findNotes: { 41 | payload: { 42 | query: string 43 | } 44 | result: number[] 45 | } 46 | notesInfo: { 47 | payload: { 48 | notes: number[] 49 | } 50 | result: { 51 | fields: Record< 52 | string, 53 | { 54 | ord: number 55 | value: string 56 | } 57 | > 58 | }[] 59 | } 60 | addNote: { 61 | payload: { 62 | note: IAddNote 63 | } 64 | result: number 65 | } 66 | addNotes: { 67 | payload: { 68 | note: IAddNote[] 69 | } 70 | result: number[] 71 | } 72 | addTags: { 73 | payload: { 74 | notes: number[] 75 | tags: string 76 | } 77 | result: null 78 | } 79 | } 80 | 81 | type IAnkiconnect = IModel & INote 82 | 83 | export const ankiconnect = { 84 | api: axios.create({ 85 | baseURL: 'http://127.0.0.1:8765' 86 | }), 87 | async invoke( 88 | action: K, 89 | params: IAnkiconnect[K]['payload'] = {}, 90 | version = 6 91 | ): Promise { 92 | const { 93 | data: { result, error } 94 | } = await this.api.post('/', { action, version, params }) 95 | 96 | if (error) { 97 | throw new Error(error) 98 | } 99 | 100 | return result 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ankisync", 3 | "version": "0.6.0", 4 | "description": "Do in Anki what Anki cannot do", 5 | "keywords": [ 6 | "anki", 7 | "ankisync", 8 | "anki-flashcards", 9 | "ankiconnect" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/patarapolw/ankisync.js.git" 14 | }, 15 | "license": "MIT", 16 | "author": { 17 | "name": "Pacharapol Withayasakpunt", 18 | "email": "polv@polv.cc", 19 | "url": "https://www.polv.cc" 20 | }, 21 | "main": "lib/index.js", 22 | "types": "lib/index.d.ts", 23 | "files": [ 24 | "lib", 25 | "src", 26 | "tsconfig.json" 27 | ], 28 | "scripts": { 29 | "build": "tsc -p src/tsconfig.json", 30 | "prepack": "npm run build", 31 | "test": "ts-mocha tests/**/*.spec.ts" 32 | }, 33 | "dependencies": { 34 | "adm-zip": "^0.4.16", 35 | "axios": "^0.21.0", 36 | "bluebird": "^3.7.2", 37 | "bluebird-global": "^1.0.1", 38 | "fs-extra": "^8.1.0", 39 | "liteorm": "github:patarapolw/liteorm", 40 | "nanoid": "^3.1.16", 41 | "reflect-metadata": "^0.1.13", 42 | "rimraf": "^3.0.2", 43 | "sqlite3": "^5.0.0", 44 | "string-strip-html": "^6.3.0" 45 | }, 46 | "devDependencies": { 47 | "@types/adm-zip": "^0.4.33", 48 | "@types/fs-extra": "^8.1.1", 49 | "@types/nanoid": "^2.1.0", 50 | "@types/rimraf": "^2.0.4", 51 | "@typescript-eslint/eslint-plugin": "^4.8.1", 52 | "@typescript-eslint/parser": "^4.8.1", 53 | "eslint": "^7.13.0", 54 | "eslint-config-standard": "^15.0.1", 55 | "eslint-plugin-import": "^2.22.1", 56 | "eslint-plugin-node": "^11.1.0", 57 | "eslint-plugin-promise": "^4.2.1", 58 | "eslint-plugin-standard": "^4.1.0", 59 | "husky": "^4.3.0", 60 | "import-sort-parser-typescript": "^6.0.0", 61 | "open": "^7.3.0", 62 | "prettier": "^2.1.2", 63 | "sqlite": "^4.0.17", 64 | "ts-node": "^9.0.0", 65 | "typescript": "^4.0.5" 66 | }, 67 | "engines": { 68 | "node": "12.x", 69 | "npm": "please-use-pnpm", 70 | "pnpm": "5.x", 71 | "yarn": "please-use-pnpm" 72 | }, 73 | "engineStrict": true, 74 | "importSort": { 75 | ".js, .jsx, .ts, .tsx": { 76 | "parser": "typescript", 77 | "style": "module" 78 | } 79 | }, 80 | "husky": { 81 | "hooks": { 82 | "pre-commit": "pnpm build" 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,macos 3 | # Edit at https://www.gitignore.io/?templates=node,macos 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### Node ### 34 | # Logs 35 | logs 36 | *.log 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | lerna-debug.log* 41 | 42 | # Diagnostic reports (https://nodejs.org/api/report.html) 43 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 44 | 45 | # Runtime data 46 | pids 47 | *.pid 48 | *.seed 49 | *.pid.lock 50 | 51 | # Directory for instrumented libs generated by jscoverage/JSCover 52 | lib-cov 53 | 54 | # Coverage directory used by tools like istanbul 55 | coverage 56 | *.lcov 57 | 58 | # nyc test coverage 59 | .nyc_output 60 | 61 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 62 | .grunt 63 | 64 | # Bower dependency directory (https://bower.io/) 65 | bower_components 66 | 67 | # node-waf configuration 68 | .lock-wscript 69 | 70 | # Compiled binary addons (https://nodejs.org/api/addons.html) 71 | build/Release 72 | 73 | # Dependency directories 74 | node_modules/ 75 | jspm_packages/ 76 | 77 | # TypeScript v1 declaration files 78 | typings/ 79 | 80 | # TypeScript cache 81 | *.tsbuildinfo 82 | 83 | # Optional npm cache directory 84 | .npm 85 | 86 | # Optional eslint cache 87 | .eslintcache 88 | 89 | # Optional REPL history 90 | .node_repl_history 91 | 92 | # Output of 'npm pack' 93 | *.tgz 94 | 95 | # Yarn Integrity file 96 | .yarn-integrity 97 | 98 | # dotenv environment variables file 99 | .env 100 | .env.test 101 | 102 | # parcel-bundler cache (https://parceljs.org/) 103 | .cache 104 | 105 | # next.js build output 106 | .next 107 | 108 | # nuxt.js build output 109 | .nuxt 110 | 111 | # vuepress build output 112 | .vuepress/dist 113 | 114 | # Serverless directories 115 | .serverless/ 116 | 117 | # FuseBox cache 118 | .fusebox/ 119 | 120 | # DynamoDB Local files 121 | .dynamodb/ 122 | 123 | # End of https://www.gitignore.io/api/node,macos 124 | 125 | *.anki2 126 | *.anki2-* 127 | *.apkg 128 | -------------------------------------------------------------------------------- /lib/anki20/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Table } from 'liteorm'; 2 | export declare class Decks { 3 | id: number; 4 | name: string; 5 | } 6 | export declare const dbDecks: Table; 7 | export declare class Models { 8 | id: number; 9 | name: string; 10 | flds: string[]; 11 | css: string; 12 | } 13 | export declare const dbModels: Table; 14 | export declare class Templates { 15 | id: number; 16 | mid: string; 17 | name: string; 18 | qfmt: string; 19 | afmt: string; 20 | } 21 | export declare const dbTemplates: Table; 22 | /** 23 | * col contains a single row that holds various information about the collection 24 | * 25 | * ```sql 26 | * CREATE TABLE col ( 27 | id integer primary key, 28 | -- arbitrary number since there is only one row 29 | crt integer not null, 30 | -- created timestamp 31 | mod integer not null, 32 | -- last modified in milliseconds 33 | scm integer not null, 34 | -- schema mod time: time when "schema" was modified. 35 | -- If server scm is different from the client scm a full-sync is required 36 | ver integer not null, 37 | -- version 38 | dty integer not null, 39 | -- dirty: unused, set to 0 40 | usn integer not null, 41 | -- update sequence number: used for finding diffs when syncing. 42 | -- See usn in cards table for more details. 43 | ls integer not null, 44 | -- "last sync time" 45 | conf text not null, 46 | -- json object containing configuration options that are synced 47 | models text not null, 48 | -- json array of json objects containing the models (aka Note types) 49 | decks text not null, 50 | -- json array of json objects containing the deck 51 | dconf text not null, 52 | -- json array of json objects containing the deck options 53 | tags text not null 54 | -- a cache of tags used in the collection (This list is displayed in the browser. Potentially at other place) 55 | ); 56 | ``` 57 | */ 58 | export declare class Col { 59 | id: number; 60 | crt: number; 61 | mod: number; 62 | scm: number; 63 | ver: number; 64 | dty: number; 65 | usn: number; 66 | ls: number; 67 | conf: Record; 68 | models: Record; 69 | decks: Record; 70 | dconf: Record; 71 | tags: Record; 72 | } 73 | export declare const dbCol: Table; 74 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /src/anki20/index.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Table, primary, prop } from 'liteorm' 2 | 3 | // Missing tables in Anki 4 | 5 | @Entity() 6 | export class Decks { 7 | @primary({ autoincrement: true }) id!: number 8 | @prop({ unique: true }) name!: string 9 | } 10 | 11 | export const dbDecks = new Table(Decks) 12 | 13 | @Entity() 14 | export class Models { 15 | @primary({ autoincrement: true }) id!: number 16 | @prop({ unique: true }) name!: string 17 | @prop({ 18 | type: 'string', 19 | transform: { 20 | get: (r: string) => r.split('\x1f'), 21 | set: (d: string[]) => d.join('\x1f') 22 | } 23 | }) 24 | flds!: string[] 25 | 26 | @prop() css!: string 27 | } 28 | 29 | export const dbModels = new Table(Models) 30 | 31 | @Entity() 32 | export class Templates { 33 | @primary({ autoincrement: true }) id!: number 34 | @prop({ references: dbModels, index: true }) mid!: string 35 | @prop({ index: true }) name!: string 36 | @prop() qfmt!: string 37 | @prop() afmt!: string 38 | } 39 | 40 | export const dbTemplates = new Table(Templates) 41 | 42 | // Default tables in Anki 43 | 44 | /** 45 | * col contains a single row that holds various information about the collection 46 | * 47 | * ```sql 48 | * CREATE TABLE col ( 49 | id integer primary key, 50 | -- arbitrary number since there is only one row 51 | crt integer not null, 52 | -- created timestamp 53 | mod integer not null, 54 | -- last modified in milliseconds 55 | scm integer not null, 56 | -- schema mod time: time when "schema" was modified. 57 | -- If server scm is different from the client scm a full-sync is required 58 | ver integer not null, 59 | -- version 60 | dty integer not null, 61 | -- dirty: unused, set to 0 62 | usn integer not null, 63 | -- update sequence number: used for finding diffs when syncing. 64 | -- See usn in cards table for more details. 65 | ls integer not null, 66 | -- "last sync time" 67 | conf text not null, 68 | -- json object containing configuration options that are synced 69 | models text not null, 70 | -- json array of json objects containing the models (aka Note types) 71 | decks text not null, 72 | -- json array of json objects containing the deck 73 | dconf text not null, 74 | -- json array of json objects containing the deck options 75 | tags text not null 76 | -- a cache of tags used in the collection (This list is displayed in the browser. Potentially at other place) 77 | ); 78 | ``` 79 | */ 80 | @Entity() 81 | export class Col { 82 | @primary({ default: 1 }) id!: number 83 | @prop({ default: () => Math.floor(+new Date() / 1000) }) crt!: number 84 | @prop({ default: () => Math.floor(+new Date()) }) mod!: number 85 | @prop({ default: 0 }) scm!: number 86 | @prop({ default: 11 }) ver!: number 87 | @prop({ default: 0 }) dty!: number 88 | @prop({ default: 0 }) usn!: number 89 | @prop({ default: 0 }) ls!: number 90 | @prop() conf!: Record 91 | @prop() models!: Record 92 | @prop() decks!: Record 93 | @prop() dconf!: Record 94 | @prop() tags!: Record 95 | } 96 | 97 | export const dbCol = new Table(Col) 98 | -------------------------------------------------------------------------------- /lib/anki21/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anki21/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAU,KAAK,EAAiB,MAAM,SAAS,CAAA;AAI1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBACa,GAAG;IACW,EAAE,CAAC,EAAE,MAAM,CAAA;IAEpC,GAAG,CAAC,EAAE,MAAM,CAAA;IAEmD,GAAG,CAAC,EAAE,MAAM,CAAA;IACxC,GAAG,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACb,EAAE,CAAC,EAAE,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACtD;AAED,eAAO,MAAM,KAAK,qBAAiB,CAAA;AAEnC;;GAEG;AACH,qBACa,MAAM;IACM,GAAG,EAAG,MAAM,CAAA;IACA,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC5C,GAAG,EAAG,WAAW,CAAA;CAC1B;AAED,eAAO,MAAM,QAAQ,2BAAoB,CAAA;AAEzC;;GAEG;AACH,qBACa,UAAU;IACa,EAAE,EAAG,MAAM,CAAA;IACf,IAAI,EAAG,MAAM,CAAA;IAG3C,SAAS,CAAC,EAAE,MAAM,CAAA;IAEiB,GAAG,CAAC,EAAE,MAAM,CAAA;IACvC,MAAM,EAAG,WAAW,CAAA;CAC7B;AAED,eAAO,MAAM,YAAY,mCAAwB,CAAA;AAEjD;;GAEG;AACH,qBACa,KAAK;IACkB,EAAE,EAAG,MAAM,CAAA;IACU,IAAI,EAAG,MAAM,CAAA;IAGpE,SAAS,CAAC,EAAE,MAAM,CAAA;IAEiB,GAAG,CAAC,EAAE,MAAM,CAAA;IACvC,MAAM,EAAG,WAAW,CAAA;IACpB,IAAI,EAAG,WAAW,CAAA;CAC3B;AAED,eAAO,MAAM,OAAO,yBAAmB,CAAA;AAEvC;;;;;;;;;;;;GAYG;AACH,qBACa,MAAM;IACmB,GAAG,CAAC,EAAE,MAAM,CAAA;IACzB,GAAG,EAAG,MAAM,CAAA;IACZ,IAAI,EAAG,MAAM,CAAA;CACrC;AAED,eAAO,MAAM,QAAQ,2BAAoB,CAAA;AAEzC,qBACa,SAAS;IACc,EAAE,EAAG,MAAM,CAAA;IACc,IAAI,EAAG,MAAM,CAAA;IAGxE,SAAS,CAAC,EAAE,MAAM,CAAA;IAE6C,GAAG,CAAC,EAAE,MAAM,CAAA;IACnE,MAAM,EAAG,WAAW,CAAA;CAC7B;AAED,eAAO,MAAM,WAAW,iCAAuB,CAAA;AAG/C,qBAKa,MAAM;IAC+B,IAAI,EAAG,MAAM,CAAA;IACtC,GAAG,EAAG,MAAM,CAAA;IACL,IAAI,EAAG,MAAM,CAAA;IACnC,MAAM,EAAG,WAAW,CAAA;CAC7B;AAED,eAAO,MAAM,QAAQ,2BAAoB,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBACa,KAAK;IACkB,EAAE,EAAG,MAAM,CAAA;IACI,IAAI,CAAC,EAAE,MAAM,CAAA;IAG9D,GAAG,EAAG,MAAM,CAAA;IAEoD,GAAG,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IAUvE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IASf,IAAI,EAAG,MAAM,EAAE,CAAA;IAEP,IAAI,CAAC,EAAE,MAAM,CAAA;IAC0B,IAAI,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;CACrC;AAED,eAAO,MAAM,OAAO,yBAAmB,CAAA;AAMvC;;GAEG;AACH,qBACa,IAAI;IAC4B,GAAG,EAAG,MAAM,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;CAChD;AAED,eAAO,MAAM,MAAM,uBAAkB,CAAA;AAGrC,qBAUa,SAAS;IAC4B,IAAI,EAAG,MAAM,CAAA;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAG,MAAM,CAAA;IACR,SAAS,CAAC,EAAE,MAAM,CAAA;IACU,GAAG,CAAC,EAAE,MAAM,CAAA;IACnE,MAAM,EAAG,WAAW,CAAA;CAC7B;AAED,eAAO,MAAM,WAAW,iCAAuB,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,qBAGa,KAAK;IACkB,EAAE,CAAC,EAAE,MAAM,CAAA;IAE7C,GAAG,EAAG,MAAM,CAAA;IAEgC,GAAG,EAAG,MAAM,CAAA;IACjC,GAAG,EAAG,MAAM,CAAA;IAC6B,GAAG,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAA;IACA,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACY,IAAI,CAAC,EAAE,MAAM,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;CACrC;AAED,eAAO,MAAM,OAAO,yBAAmB,CAAA;AAMvC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBACa,MAAM;IACiB,EAAE,CAAC,EAAE,MAAM,CAAA;IAE7C,GAAG,EAAG,MAAM,CAAA;IAEgD,GAAG,CAAC,EAAE,MAAM,CAAA;IACjD,IAAI,EAAG,MAAM,CAAA;IACb,GAAG,EAAG,MAAM,CAAA;IACZ,OAAO,EAAG,MAAM,CAAA;IAChB,MAAM,EAAG,MAAM,CAAA;IACf,IAAI,EAAG,MAAM,CAAA;IACb,IAAI,EAAG,MAAM,CAAA;CACrC;AAED,eAAO,MAAM,QAAQ,2BAAoB,CAAA;AAEzC,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,MAG5C"} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/anki20/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.dbCol = exports.Col = exports.dbTemplates = exports.Templates = exports.dbModels = exports.Models = exports.dbDecks = exports.Decks = void 0; 13 | const liteorm_1 = require("liteorm"); 14 | // Missing tables in Anki 15 | let Decks = class Decks { 16 | }; 17 | __decorate([ 18 | liteorm_1.primary({ autoincrement: true }), 19 | __metadata("design:type", Number) 20 | ], Decks.prototype, "id", void 0); 21 | __decorate([ 22 | liteorm_1.prop({ unique: true }), 23 | __metadata("design:type", String) 24 | ], Decks.prototype, "name", void 0); 25 | Decks = __decorate([ 26 | liteorm_1.Entity() 27 | ], Decks); 28 | exports.Decks = Decks; 29 | exports.dbDecks = new liteorm_1.Table(Decks); 30 | let Models = class Models { 31 | }; 32 | __decorate([ 33 | liteorm_1.primary({ autoincrement: true }), 34 | __metadata("design:type", Number) 35 | ], Models.prototype, "id", void 0); 36 | __decorate([ 37 | liteorm_1.prop({ unique: true }), 38 | __metadata("design:type", String) 39 | ], Models.prototype, "name", void 0); 40 | __decorate([ 41 | liteorm_1.prop({ 42 | type: 'string', 43 | transform: { 44 | get: (r) => r.split('\x1f'), 45 | set: (d) => d.join('\x1f') 46 | } 47 | }), 48 | __metadata("design:type", Array) 49 | ], Models.prototype, "flds", void 0); 50 | __decorate([ 51 | liteorm_1.prop(), 52 | __metadata("design:type", String) 53 | ], Models.prototype, "css", void 0); 54 | Models = __decorate([ 55 | liteorm_1.Entity() 56 | ], Models); 57 | exports.Models = Models; 58 | exports.dbModels = new liteorm_1.Table(Models); 59 | let Templates = class Templates { 60 | }; 61 | __decorate([ 62 | liteorm_1.primary({ autoincrement: true }), 63 | __metadata("design:type", Number) 64 | ], Templates.prototype, "id", void 0); 65 | __decorate([ 66 | liteorm_1.prop({ references: exports.dbModels, index: true }), 67 | __metadata("design:type", String) 68 | ], Templates.prototype, "mid", void 0); 69 | __decorate([ 70 | liteorm_1.prop({ index: true }), 71 | __metadata("design:type", String) 72 | ], Templates.prototype, "name", void 0); 73 | __decorate([ 74 | liteorm_1.prop(), 75 | __metadata("design:type", String) 76 | ], Templates.prototype, "qfmt", void 0); 77 | __decorate([ 78 | liteorm_1.prop(), 79 | __metadata("design:type", String) 80 | ], Templates.prototype, "afmt", void 0); 81 | Templates = __decorate([ 82 | liteorm_1.Entity() 83 | ], Templates); 84 | exports.Templates = Templates; 85 | exports.dbTemplates = new liteorm_1.Table(Templates); 86 | // Default tables in Anki 87 | /** 88 | * col contains a single row that holds various information about the collection 89 | * 90 | * ```sql 91 | * CREATE TABLE col ( 92 | id integer primary key, 93 | -- arbitrary number since there is only one row 94 | crt integer not null, 95 | -- created timestamp 96 | mod integer not null, 97 | -- last modified in milliseconds 98 | scm integer not null, 99 | -- schema mod time: time when "schema" was modified. 100 | -- If server scm is different from the client scm a full-sync is required 101 | ver integer not null, 102 | -- version 103 | dty integer not null, 104 | -- dirty: unused, set to 0 105 | usn integer not null, 106 | -- update sequence number: used for finding diffs when syncing. 107 | -- See usn in cards table for more details. 108 | ls integer not null, 109 | -- "last sync time" 110 | conf text not null, 111 | -- json object containing configuration options that are synced 112 | models text not null, 113 | -- json array of json objects containing the models (aka Note types) 114 | decks text not null, 115 | -- json array of json objects containing the deck 116 | dconf text not null, 117 | -- json array of json objects containing the deck options 118 | tags text not null 119 | -- a cache of tags used in the collection (This list is displayed in the browser. Potentially at other place) 120 | ); 121 | ``` 122 | */ 123 | let Col = class Col { 124 | }; 125 | __decorate([ 126 | liteorm_1.primary({ default: 1 }), 127 | __metadata("design:type", Number) 128 | ], Col.prototype, "id", void 0); 129 | __decorate([ 130 | liteorm_1.prop({ default: () => Math.floor(+new Date() / 1000) }), 131 | __metadata("design:type", Number) 132 | ], Col.prototype, "crt", void 0); 133 | __decorate([ 134 | liteorm_1.prop({ default: () => Math.floor(+new Date()) }), 135 | __metadata("design:type", Number) 136 | ], Col.prototype, "mod", void 0); 137 | __decorate([ 138 | liteorm_1.prop({ default: 0 }), 139 | __metadata("design:type", Number) 140 | ], Col.prototype, "scm", void 0); 141 | __decorate([ 142 | liteorm_1.prop({ default: 11 }), 143 | __metadata("design:type", Number) 144 | ], Col.prototype, "ver", void 0); 145 | __decorate([ 146 | liteorm_1.prop({ default: 0 }), 147 | __metadata("design:type", Number) 148 | ], Col.prototype, "dty", void 0); 149 | __decorate([ 150 | liteorm_1.prop({ default: 0 }), 151 | __metadata("design:type", Number) 152 | ], Col.prototype, "usn", void 0); 153 | __decorate([ 154 | liteorm_1.prop({ default: 0 }), 155 | __metadata("design:type", Number) 156 | ], Col.prototype, "ls", void 0); 157 | __decorate([ 158 | liteorm_1.prop(), 159 | __metadata("design:type", Object) 160 | ], Col.prototype, "conf", void 0); 161 | __decorate([ 162 | liteorm_1.prop(), 163 | __metadata("design:type", Object) 164 | ], Col.prototype, "models", void 0); 165 | __decorate([ 166 | liteorm_1.prop(), 167 | __metadata("design:type", Object) 168 | ], Col.prototype, "decks", void 0); 169 | __decorate([ 170 | liteorm_1.prop(), 171 | __metadata("design:type", Object) 172 | ], Col.prototype, "dconf", void 0); 173 | __decorate([ 174 | liteorm_1.prop(), 175 | __metadata("design:type", Object) 176 | ], Col.prototype, "tags", void 0); 177 | Col = __decorate([ 178 | liteorm_1.Entity() 179 | ], Col); 180 | exports.Col = Col; 181 | exports.dbCol = new liteorm_1.Table(Col); 182 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/anki21/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/anki21/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,qCAA0D;AAC1D,mCAA+B;AAC/B,0EAAyC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,IAAa,GAAG,GAAhB,MAAa,GAAG;CAgBf,CAAA;AAf0B;IAAxB,iBAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;+BAAY;AAEpC;IADC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;;gCACzD;AAEmD;IAA9D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;;gCAAa;AACxC;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;gCAAa;AACX;IAAnC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;gCAAa;AACb;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;gCAAa;AACX;IAAnC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;gCAAa;AACb;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;+BAAY;AACvB;IAAtB,cAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;iCAA+B;AAC9B;IAAtB,cAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;mCAAiC;AAChC;IAAtB,cAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;kCAAgC;AAC/B;IAAtB,cAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;kCAAgC;AAC/B;IAAtB,cAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;iCAA+B;AAf1C,GAAG;IADf,gBAAM,EAAE;GACI,GAAG,CAgBf;AAhBY,kBAAG;AAkBH,QAAA,KAAK,GAAG,IAAI,eAAK,CAAC,GAAG,CAAC,CAAA;AAEnC;;GAEG;AAEH,IAAa,MAAM,GAAnB,MAAa,MAAM;CAKlB,CAAA;AAJwB;IAAtB,cAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;;mCAAa;AACA;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;mCAAa;AACZ;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;wCAAkB;AAC5C;IAAP,cAAI,EAAE;8BAAO,WAAW;mCAAA;AAJd,MAAM;IADlB,gBAAM,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;GAClB,MAAM,CAKlB;AALY,wBAAM;AAON,QAAA,QAAQ,GAAG,IAAI,eAAK,CAAC,MAAM,CAAC,CAAA;AAEzC;;GAEG;AAEH,IAAa,UAAU,GAAvB,MAAa,UAAU;CAStB,CAAA;AARmC;IAAjC,iBAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;;sCAAY;AACf;IAA7B,cAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;wCAAc;AAG3C;IADC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;;6CACnD;AAEiB;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;uCAAa;AACvC;IAAP,cAAI,EAAE;8BAAU,WAAW;0CAAA;AARjB,UAAU;IADtB,gBAAM,EAAE;GACI,UAAU,CAStB;AATY,gCAAU;AAWV,QAAA,YAAY,GAAG,IAAI,eAAK,CAAC,UAAU,CAAC,CAAA;AAEjD;;GAEG;AAEH,IAAa,KAAK,GAAlB,MAAa,KAAK;CAUjB,CAAA;AATmC;IAAjC,iBAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;;iCAAY;AACU;IAAtD,cAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;;mCAAc;AAGpE;IADC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;;wCACnD;AAEiB;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;kCAAa;AACvC;IAAP,cAAI,EAAE;8BAAU,WAAW;qCAAA;AACpB;IAAP,cAAI,EAAE;8BAAQ,WAAW;mCAAA;AATf,KAAK;IADjB,gBAAM,EAAE;GACI,KAAK,CAUjB;AAVY,sBAAK;AAYL,QAAA,OAAO,GAAG,IAAI,eAAK,CAAC,KAAK,CAAC,CAAA;AAEvC;;;;;;;;;;;;GAYG;AAEH,IAAa,MAAM,GAAnB,MAAa,MAAM;CAIlB,CAAA;AAHqC;IAAnC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;;mCAAa;AACzB;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;mCAAa;AACZ;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;oCAAc;AAHzB,MAAM;IADlB,gBAAM,EAAE;GACI,MAAM,CAIlB;AAJY,wBAAM;AAMN,QAAA,QAAQ,GAAG,IAAI,eAAK,CAAC,MAAM,CAAC,CAAA;AAGzC,IAAa,SAAS,GAAtB,MAAa,SAAS;CASrB,CAAA;AARmC;IAAjC,iBAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;;qCAAY;AACc;IAA1D,cAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;;uCAAc;AAGxE;IADC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;;4CACnD;AAE6C;IAA9D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;;sCAAa;AACnE;IAAP,cAAI,EAAE;8BAAU,WAAW;yCAAA;AARjB,SAAS;IADrB,gBAAM,EAAE;GACI,SAAS,CASrB;AATY,8BAAS;AAWT,QAAA,WAAW,GAAG,IAAI,eAAK,CAAC,SAAS,CAAC,CAAA;AAE/C,gDAAgD;AAMhD,IAAa,MAAM,GAAnB,MAAa,MAAM;CAKlB,CAAA;AAJiD;IAA/C,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAW,EAAE,CAAC;;oCAAc;AACtC;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;mCAAa;AACL;IAA7B,cAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;oCAAc;AACnC;IAAP,cAAI,EAAE;8BAAU,WAAW;sCAAA;AAJjB,MAAM;IALlB,gBAAM,CAAS;QACd,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QACxB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QACjE,YAAY,EAAE,IAAI;KACnB,CAAC;GACW,MAAM,CAKlB;AALY,wBAAM;AAON,QAAA,QAAQ,GAAG,IAAI,eAAK,CAAC,MAAM,CAAC,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,IAAa,KAAK,GAAlB,MAAa,KAAK;CAiCjB,CAAA;AAhCmC;IAAjC,iBAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;;iCAAY;AACI;IAAhD,cAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,eAAM,EAAE,EAAE,CAAC;;mCAAc;AAG9D;IADC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAW,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;;kCAC3D;AAEoD;IAA/D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;;kCAAa;AACjB;IAA1D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;;kCAAa;AAUvE;IARC,cAAI,CAAW;QACd,IAAI,EAAE,QAAQ;QACd,SAAS,EAAE;YACT,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;YACnD,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;SACxB;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE;KAClB,CAAC;;mCACa;AASf;IAPC,cAAI,CAAW;QACd,IAAI,EAAE,QAAQ;QACd,SAAS,EAAE;YACT,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;YACtD,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;SAC3B;KACF,CAAC;;mCACa;AAEP;IAAP,cAAI,EAAE;;mCAAc;AAC0B;IAA9C,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;;mCAAc;AACzB;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;oCAAe;AAC1B;IAAtB,cAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;mCAAc;AAhCzB,KAAK;IADjB,gBAAM,EAAE;GACI,KAAK,CAiCjB;AAjCY,sBAAK;AAmCL,QAAA,OAAO,GAAG,IAAI,eAAK,CAAC,KAAK,CAAC,CAAA;AAEvC,eAAO,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE;IAC7B,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,2BAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AAClD,CAAC,CAAC,CAAA;AAEF;;GAEG;AAEH,IAAa,IAAI,GAAjB,MAAa,IAAI;CAGhB,CAAA;AAF4C;IAA1C,cAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;iCAAa;AACpB;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;iCAAa;AAFpC,IAAI;IADhB,gBAAM,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;GAClB,IAAI,CAGhB;AAHY,oBAAI;AAKJ,QAAA,MAAM,GAAG,IAAI,eAAK,CAAC,IAAI,CAAC,CAAA;AAErC,gDAAgD;AAWhD,IAAa,SAAS,GAAtB,MAAa,SAAS;CAOrB,CAAA;AANiD;IAA/C,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAW,EAAE,CAAC;;uCAAc;AAC1B;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;sCAAa;AACjB;IAA7B,cAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;uCAAc;AACR;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;4CAAmB;AACU;IAA9D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;;sCAAa;AACnE;IAAP,cAAI,EAAE;8BAAU,WAAW;yCAAA;AANjB,SAAS;IAVrB,gBAAM,CAAY;QACjB,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QACxB,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,yBAAyB;gBAC/B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;aACvB;SACF;QACD,YAAY,EAAE,IAAI;KACnB,CAAC;GACW,SAAS,CAOrB;AAPY,8BAAS;AAST,QAAA,WAAW,GAAG,IAAI,eAAK,CAAC,SAAS,CAAC,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,gDAAgD;AAIhD,IAAa,KAAK,GAAlB,MAAa,KAAK;CAoBjB,CAAA;AAnBmC;IAAjC,iBAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;;iCAAY;AAE7C;IADC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;;kCACtD;AAEgC;IAA3C,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAO,EAAE,CAAC;;kCAAa;AACjC;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;kCAAa;AAC6B;IAA/D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;;kCAAa;AACjB;IAA1D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;;kCAAa;AACpC;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;oCAAe;AAC1B;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;kCAAa;AACA;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;kCAAa;AACZ;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;qCAAgB;AACf;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;mCAAc;AACb;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;qCAAgB;AACf;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;mCAAc;AACb;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;mCAAc;AACY;IAA3D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;;mCAAc;AACtC;IAAlC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;oCAAe;AAC1B;IAAtB,cAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;mCAAc;AAnBzB,KAAK;IAHjB,gBAAM,CAAQ;QACb,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;KACnE,CAAC;GACW,KAAK,CAoBjB;AApBY,sBAAK;AAsBL,QAAA,OAAO,GAAG,IAAI,eAAK,CAAC,KAAK,CAAC,CAAA;AAEvC,eAAO,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE;IAC7B,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAA;AAC3B,CAAC,CAAC,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,IAAa,MAAM,GAAnB,MAAa,MAAM;CAYlB,CAAA;AAXmC;IAAjC,iBAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;;kCAAY;AAE7C;IADC,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAO,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;;mCACvD;AAEgD;IAA3D,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;;mCAAa;AACjD;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;oCAAc;AACb;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;mCAAa;AACZ;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;uCAAiB;AAChB;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;sCAAgB;AACf;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;oCAAc;AACb;IAAtB,cAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;oCAAc;AAXzB,MAAM;IADlB,gBAAM,EAAE;GACI,MAAM,CAYlB;AAZY,wBAAM;AAcN,QAAA,QAAQ,GAAG,IAAI,eAAK,CAAC,MAAM,CAAC,CAAA;AAEzC,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,EAAE,GAAG,IAAI,YAAE,CAAC,QAAQ,CAAC,CAAA;IAC3B,OAAO,EAAE,CAAA;AACX,CAAC;AAHD,oCAGC"} -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | import path from 'path' 3 | 4 | import AdmZip from 'adm-zip' 5 | import fs from 'fs-extra' 6 | import rimraf from 'rimraf' 7 | import sqlite3, { Database } from 'sqlite' 8 | import { Database as Driver } from 'sqlite3' 9 | 10 | import { mapAsync } from './util' 11 | 12 | export class Anki2 { 13 | public static async connect(colPath: string) { 14 | const db = await sqlite3.open({ filename: colPath, driver: Driver }) 15 | const tables = ( 16 | await db.all(/* sql */ ` 17 | SELECT name FROM sqlite_master WHERE type='table' 18 | `) 19 | ).map((t) => t.name as string) 20 | 21 | if (!tables.includes('deck')) { 22 | await db.exec(/* sql */ ` 23 | CREATE TABLE IF NOT EXISTS decks ( 24 | id INTEGER PRIMARY KEY, 25 | [name] TEXT NOT NULL 26 | ); 27 | 28 | CREATE TABLE IF NOT EXISTS media ( 29 | id INTEGER PRIMARY KEY, 30 | [name] TEXT NOT NULL 31 | ); 32 | 33 | CREATE TABLE IF NOT EXISTS models ( 34 | id INTEGER PRIMARY KEY, 35 | [name] TEXT NOT NULL, 36 | flds TEXT NOT NULL, -- \x1f field 37 | css TEXT 38 | ); 39 | 40 | CREATE TABLE IF NOT EXISTS templates ( 41 | mid INTEGER NOT NULL REFERENCES models(id), 42 | ord INTEGER NOT NULL, 43 | [name] TEXT NOT NULL, 44 | qfmt TEXT NOT NULL, 45 | afmt TEXT 46 | ) 47 | `) 48 | 49 | const { decks, models } = await db.get(/* sql */ ` 50 | SELECT decks, models FROM col 51 | `) 52 | 53 | await mapAsync(Object.values(JSON.parse(decks)), async (d: any) => { 54 | await db.run( 55 | /* sql */ ` 56 | INSERT INTO decks (id, [name]) VALUES (?, ?) 57 | `, 58 | [parseInt(d.id), d.name] 59 | ) 60 | }) 61 | 62 | await mapAsync(Object.values(JSON.parse(models)), async (m: any) => { 63 | await db.run( 64 | /* sql */ ` 65 | INSERT INTO models (id, [name], flds, css) 66 | VALUES (?, ?, ?, ?) 67 | `, 68 | [ 69 | parseInt(m.id), 70 | m.name, 71 | m.flds.map((f: any) => f.name).join('\x1f'), 72 | m.css 73 | ] 74 | ) 75 | 76 | await mapAsync(m.tmpls, async (t: any, i: number) => { 77 | await db.run( 78 | /* sql */ ` 79 | INSERT INTO templates (mid, ord, [name], qfmt, afmt) 80 | VALUES (?, ?, ?, ?, ?) 81 | `, 82 | [parseInt(m.id), i, t.name, t.qfmt, t.afmt] 83 | ) 84 | }) 85 | }) 86 | } 87 | 88 | return new Anki2({ colPath, db }) 89 | } 90 | 91 | db: Database 92 | colPath: string 93 | 94 | private constructor(params: { colPath: string; db: Database }) { 95 | this.db = params.db 96 | this.colPath = params.colPath 97 | } 98 | 99 | async find( 100 | where?: string, 101 | postfix?: string 102 | ): Promise< 103 | { 104 | deck: string 105 | values: string[] 106 | keys: string[] 107 | css: string | null 108 | qfmt: string 109 | afmt: string | null 110 | template: string 111 | model: string 112 | }[] 113 | > { 114 | return ( 115 | await this.db.all(/* sql */ ` 116 | SELECT 117 | d.name AS deck, 118 | n.flds AS [values], 119 | m.flds AS keys, 120 | m.css AS css, 121 | t.qfmt AS qfmt, 122 | t.afmt AS afmt, 123 | t.name AS template, 124 | m.name AS model 125 | FROM cards AS c 126 | JOIN notes AS n ON c.nid = n.id 127 | JOIN decks AS d ON c.did = d.id 128 | JOIN models AS m ON n.mid = m.id 129 | JOIN templates AS t ON t.ord = c.ord AND t.mid = n.mid 130 | ${where ? `WHERE ${where}` : ''} 131 | ${postfix || ''} 132 | `) 133 | ).map((el) => { 134 | el.keys = el.keys.split('\x1f') 135 | el.values = el.values.split('\x1f') 136 | 137 | return el 138 | }) 139 | } 140 | 141 | async finalize() { 142 | const { models, decks } = await this.db.get(/* sql */ ` 143 | SELECT models, decks FROM col 144 | `) 145 | const ms = JSON.parse(models) 146 | const ds = JSON.parse(decks) 147 | 148 | await mapAsync( 149 | await this.db.all(/* sql */ ` 150 | SELECT id, [name], flds, css FROM models 151 | `), 152 | async (m: any) => { 153 | const ts = await this.db.all( 154 | /* sql */ ` 155 | SELECT [name], ord, qfmt, afmt FROM templates 156 | WHERE mid = ? ORDER BY ord 157 | `, 158 | [m.id] 159 | ) 160 | 161 | ms[m.id.toString()] = { 162 | id: m.id, 163 | css: m.css, 164 | flds: m.flds.split('\x1f').map((f: string) => ({ 165 | size: 20, 166 | name: f, 167 | media: [], 168 | rtl: false, 169 | ord: 0, 170 | font: 'Arial', 171 | sticky: false 172 | })), 173 | tmpls: ts.map((t) => ({ 174 | afmt: t.afmt || '', 175 | name: t.name, 176 | qfmt: t.qfmt, 177 | did: null, 178 | ord: t.ord, 179 | bafmt: '', 180 | bqfmt: '' 181 | })), 182 | vers: [], 183 | name: m.name, 184 | tags: [], 185 | did: 1, 186 | usn: -1, 187 | req: [ 188 | [0, 'all', [0]], 189 | [1, 'all', [1, 2]] 190 | ], 191 | sortf: 0, 192 | latexPre: 193 | '\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n\\begin{document}\n', 194 | latexPost: '\\end{document}', 195 | type: 0, 196 | mod: 1540966298 197 | } 198 | } 199 | ) 200 | ;( 201 | await this.db.all(/* sql */ ` 202 | SELECT id, [name] FROM decks 203 | `) 204 | ).map((d) => { 205 | ds[d.id.toString()] = { 206 | id: d.id, 207 | name: d.name, 208 | extendRev: 50, 209 | usn: 0, 210 | collapsed: false, 211 | newToday: [0, 0], 212 | timeToday: [0, 0], 213 | dyn: 0, 214 | extendNew: 10, 215 | conf: 1, 216 | revToday: [0, 0], 217 | lrnToday: [0, 0], 218 | mod: 1540966298, 219 | desc: '' 220 | } 221 | }) 222 | 223 | await this.db.run( 224 | /* sql */ ` 225 | UPDATE col 226 | SET models = ?, decks = ? 227 | `, 228 | [JSON.stringify(ms), JSON.stringify(ds)] 229 | ) 230 | 231 | await this.db.exec(/* sql */ ` 232 | DROP TABLE templates; 233 | DROP TABLE models; 234 | DROP TABLE decks; 235 | `) 236 | } 237 | 238 | async cleanup() { 239 | await this.db.close() 240 | } 241 | } 242 | 243 | export class Apkg { 244 | static async connect(filePath: string) { 245 | const p = path.parse(filePath) 246 | const dir = path.join( 247 | p.dir, 248 | p.name + '_' + Math.random().toString(36).substr(2) 249 | ) 250 | fs.ensureDirSync(dir) 251 | 252 | if (fs.existsSync(filePath)) { 253 | const zip = new AdmZip(filePath) 254 | zip.extractAllTo(dir) 255 | } 256 | 257 | const anki2 = await Anki2.connect(path.join(dir, 'collection.anki2')) 258 | 259 | if (fs.existsSync(filePath)) { 260 | const mediaJson = JSON.parse( 261 | fs.readFileSync(path.join(dir, 'media'), 'utf8') 262 | ) 263 | await Promise.all( 264 | Object.keys(mediaJson).map((k) => { 265 | const data = fs.readFileSync(path.join(dir, k)) 266 | 267 | anki2.db.run( 268 | /* sql */ ` 269 | INSERT INTO media (h, [name]) 270 | VALUES (?, ?) 271 | `, 272 | [ 273 | crypto.createHash('sha256').update(data).digest('base64'), 274 | mediaJson[k] 275 | ] 276 | ) 277 | }) 278 | ) 279 | } 280 | 281 | return new Apkg({ filePath, anki2, dir }) 282 | } 283 | 284 | filePath: string 285 | dir: string 286 | anki2: Anki2 287 | 288 | private constructor(params: { filePath: string; anki2: Anki2; dir: string }) { 289 | this.filePath = params.filePath 290 | this.anki2 = params.anki2 291 | this.dir = params.dir 292 | } 293 | 294 | async finalize(overwrite = true) { 295 | await this.anki2.finalize() 296 | const mediaJson = {} as any 297 | 298 | const zip = new AdmZip() 299 | zip.addLocalFile(path.join(this.dir, 'collection.anki2')) 300 | ;( 301 | await this.anki2.db.all(/* sql */ ` 302 | SELECT id, [name] FROM media 303 | `) 304 | ).map((m) => { 305 | mediaJson[m.id.toString()] = m.name 306 | zip.addFile(m.name, fs.readFileSync(path.join(this.dir, m.id.toString()))) 307 | }) 308 | 309 | zip.addFile('media', Buffer.from(JSON.stringify(mediaJson))) 310 | 311 | await this.anki2.db.close() 312 | 313 | if (!overwrite) { 314 | const originalFilePath = this.filePath 315 | 316 | while (fs.existsSync(this.filePath)) { 317 | const p = path.parse(originalFilePath) 318 | this.filePath = 319 | p.dir + p.base + '_' + Math.random().toString(36).substr(2) + p.ext 320 | } 321 | } 322 | 323 | zip.writeZip(this.filePath) 324 | rimraf.sync(this.dir) 325 | } 326 | 327 | /** 328 | * You will lose any unsaved data. 329 | * 330 | * Use #finalize to save data. 331 | */ 332 | async cleanup() { 333 | await this.anki2.cleanup() 334 | rimraf.sync(this.dir) 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.Apkg = exports.Anki2 = void 0; 7 | const crypto_1 = __importDefault(require("crypto")); 8 | const path_1 = __importDefault(require("path")); 9 | const adm_zip_1 = __importDefault(require("adm-zip")); 10 | const fs_extra_1 = __importDefault(require("fs-extra")); 11 | const rimraf_1 = __importDefault(require("rimraf")); 12 | const sqlite_1 = __importDefault(require("sqlite")); 13 | const sqlite3_1 = require("sqlite3"); 14 | const util_1 = require("./util"); 15 | class Anki2 { 16 | constructor(params) { 17 | this.db = params.db; 18 | this.colPath = params.colPath; 19 | } 20 | static async connect(colPath) { 21 | const db = await sqlite_1.default.open({ filename: colPath, driver: sqlite3_1.Database }); 22 | const tables = (await db.all(/* sql */ ` 23 | SELECT name FROM sqlite_master WHERE type='table' 24 | `)).map((t) => t.name); 25 | if (!tables.includes('deck')) { 26 | await db.exec(/* sql */ ` 27 | CREATE TABLE IF NOT EXISTS decks ( 28 | id INTEGER PRIMARY KEY, 29 | [name] TEXT NOT NULL 30 | ); 31 | 32 | CREATE TABLE IF NOT EXISTS media ( 33 | id INTEGER PRIMARY KEY, 34 | [name] TEXT NOT NULL 35 | ); 36 | 37 | CREATE TABLE IF NOT EXISTS models ( 38 | id INTEGER PRIMARY KEY, 39 | [name] TEXT NOT NULL, 40 | flds TEXT NOT NULL, -- \x1f field 41 | css TEXT 42 | ); 43 | 44 | CREATE TABLE IF NOT EXISTS templates ( 45 | mid INTEGER NOT NULL REFERENCES models(id), 46 | ord INTEGER NOT NULL, 47 | [name] TEXT NOT NULL, 48 | qfmt TEXT NOT NULL, 49 | afmt TEXT 50 | ) 51 | `); 52 | const { decks, models } = await db.get(/* sql */ ` 53 | SELECT decks, models FROM col 54 | `); 55 | await util_1.mapAsync(Object.values(JSON.parse(decks)), async (d) => { 56 | await db.run( 57 | /* sql */ ` 58 | INSERT INTO decks (id, [name]) VALUES (?, ?) 59 | `, [parseInt(d.id), d.name]); 60 | }); 61 | await util_1.mapAsync(Object.values(JSON.parse(models)), async (m) => { 62 | await db.run( 63 | /* sql */ ` 64 | INSERT INTO models (id, [name], flds, css) 65 | VALUES (?, ?, ?, ?) 66 | `, [ 67 | parseInt(m.id), 68 | m.name, 69 | m.flds.map((f) => f.name).join('\x1f'), 70 | m.css 71 | ]); 72 | await util_1.mapAsync(m.tmpls, async (t, i) => { 73 | await db.run( 74 | /* sql */ ` 75 | INSERT INTO templates (mid, ord, [name], qfmt, afmt) 76 | VALUES (?, ?, ?, ?, ?) 77 | `, [parseInt(m.id), i, t.name, t.qfmt, t.afmt]); 78 | }); 79 | }); 80 | } 81 | return new Anki2({ colPath, db }); 82 | } 83 | async find(where, postfix) { 84 | return (await this.db.all(/* sql */ ` 85 | SELECT 86 | d.name AS deck, 87 | n.flds AS [values], 88 | m.flds AS keys, 89 | m.css AS css, 90 | t.qfmt AS qfmt, 91 | t.afmt AS afmt, 92 | t.name AS template, 93 | m.name AS model 94 | FROM cards AS c 95 | JOIN notes AS n ON c.nid = n.id 96 | JOIN decks AS d ON c.did = d.id 97 | JOIN models AS m ON n.mid = m.id 98 | JOIN templates AS t ON t.ord = c.ord AND t.mid = n.mid 99 | ${where ? `WHERE ${where}` : ''} 100 | ${postfix || ''} 101 | `)).map((el) => { 102 | el.keys = el.keys.split('\x1f'); 103 | el.values = el.values.split('\x1f'); 104 | return el; 105 | }); 106 | } 107 | async finalize() { 108 | const { models, decks } = await this.db.get(/* sql */ ` 109 | SELECT models, decks FROM col 110 | `); 111 | const ms = JSON.parse(models); 112 | const ds = JSON.parse(decks); 113 | await util_1.mapAsync(await this.db.all(/* sql */ ` 114 | SELECT id, [name], flds, css FROM models 115 | `), async (m) => { 116 | const ts = await this.db.all( 117 | /* sql */ ` 118 | SELECT [name], ord, qfmt, afmt FROM templates 119 | WHERE mid = ? ORDER BY ord 120 | `, [m.id]); 121 | ms[m.id.toString()] = { 122 | id: m.id, 123 | css: m.css, 124 | flds: m.flds.split('\x1f').map((f) => ({ 125 | size: 20, 126 | name: f, 127 | media: [], 128 | rtl: false, 129 | ord: 0, 130 | font: 'Arial', 131 | sticky: false 132 | })), 133 | tmpls: ts.map((t) => ({ 134 | afmt: t.afmt || '', 135 | name: t.name, 136 | qfmt: t.qfmt, 137 | did: null, 138 | ord: t.ord, 139 | bafmt: '', 140 | bqfmt: '' 141 | })), 142 | vers: [], 143 | name: m.name, 144 | tags: [], 145 | did: 1, 146 | usn: -1, 147 | req: [ 148 | [0, 'all', [0]], 149 | [1, 'all', [1, 2]] 150 | ], 151 | sortf: 0, 152 | latexPre: '\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n\\begin{document}\n', 153 | latexPost: '\\end{document}', 154 | type: 0, 155 | mod: 1540966298 156 | }; 157 | }); 158 | (await this.db.all(/* sql */ ` 159 | SELECT id, [name] FROM decks 160 | `)).map((d) => { 161 | ds[d.id.toString()] = { 162 | id: d.id, 163 | name: d.name, 164 | extendRev: 50, 165 | usn: 0, 166 | collapsed: false, 167 | newToday: [0, 0], 168 | timeToday: [0, 0], 169 | dyn: 0, 170 | extendNew: 10, 171 | conf: 1, 172 | revToday: [0, 0], 173 | lrnToday: [0, 0], 174 | mod: 1540966298, 175 | desc: '' 176 | }; 177 | }); 178 | await this.db.run( 179 | /* sql */ ` 180 | UPDATE col 181 | SET models = ?, decks = ? 182 | `, [JSON.stringify(ms), JSON.stringify(ds)]); 183 | await this.db.exec(/* sql */ ` 184 | DROP TABLE templates; 185 | DROP TABLE models; 186 | DROP TABLE decks; 187 | `); 188 | } 189 | async cleanup() { 190 | await this.db.close(); 191 | } 192 | } 193 | exports.Anki2 = Anki2; 194 | class Apkg { 195 | constructor(params) { 196 | this.filePath = params.filePath; 197 | this.anki2 = params.anki2; 198 | this.dir = params.dir; 199 | } 200 | static async connect(filePath) { 201 | const p = path_1.default.parse(filePath); 202 | const dir = path_1.default.join(p.dir, p.name + '_' + Math.random().toString(36).substr(2)); 203 | fs_extra_1.default.ensureDirSync(dir); 204 | if (fs_extra_1.default.existsSync(filePath)) { 205 | const zip = new adm_zip_1.default(filePath); 206 | zip.extractAllTo(dir); 207 | } 208 | const anki2 = await Anki2.connect(path_1.default.join(dir, 'collection.anki2')); 209 | if (fs_extra_1.default.existsSync(filePath)) { 210 | const mediaJson = JSON.parse(fs_extra_1.default.readFileSync(path_1.default.join(dir, 'media'), 'utf8')); 211 | await Promise.all(Object.keys(mediaJson).map((k) => { 212 | const data = fs_extra_1.default.readFileSync(path_1.default.join(dir, k)); 213 | anki2.db.run( 214 | /* sql */ ` 215 | INSERT INTO media (h, [name]) 216 | VALUES (?, ?) 217 | `, [ 218 | crypto_1.default.createHash('sha256').update(data).digest('base64'), 219 | mediaJson[k] 220 | ]); 221 | })); 222 | } 223 | return new Apkg({ filePath, anki2, dir }); 224 | } 225 | async finalize(overwrite = true) { 226 | await this.anki2.finalize(); 227 | const mediaJson = {}; 228 | const zip = new adm_zip_1.default(); 229 | zip.addLocalFile(path_1.default.join(this.dir, 'collection.anki2')); 230 | (await this.anki2.db.all(/* sql */ ` 231 | SELECT id, [name] FROM media 232 | `)).map((m) => { 233 | mediaJson[m.id.toString()] = m.name; 234 | zip.addFile(m.name, fs_extra_1.default.readFileSync(path_1.default.join(this.dir, m.id.toString()))); 235 | }); 236 | zip.addFile('media', Buffer.from(JSON.stringify(mediaJson))); 237 | await this.anki2.db.close(); 238 | if (!overwrite) { 239 | const originalFilePath = this.filePath; 240 | while (fs_extra_1.default.existsSync(this.filePath)) { 241 | const p = path_1.default.parse(originalFilePath); 242 | this.filePath = 243 | p.dir + p.base + '_' + Math.random().toString(36).substr(2) + p.ext; 244 | } 245 | } 246 | zip.writeZip(this.filePath); 247 | rimraf_1.default.sync(this.dir); 248 | } 249 | /** 250 | * You will lose any unsaved data. 251 | * 252 | * Use #finalize to save data. 253 | */ 254 | async cleanup() { 255 | await this.anki2.cleanup(); 256 | rimraf_1.default.sync(this.dir); 257 | } 258 | } 259 | exports.Apkg = Apkg; 260 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/anki21/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Db, Table } from 'liteorm'; 2 | /** 3 | * col contains a single row that holds various information about the collection 4 | * 5 | * ```sql 6 | * CREATE TABLE col ( 7 | id integer primary key, 8 | -- arbitrary number since there is only one row 9 | crt integer not null, 10 | -- created timestamp 11 | mod integer not null, 12 | -- last modified in milliseconds 13 | scm integer not null, 14 | -- schema mod time: time when "schema" was modified. 15 | -- If server scm is different from the client scm a full-sync is required 16 | ver integer not null, 17 | -- version 18 | dty integer not null, 19 | -- dirty: unused, set to 0 20 | usn integer not null, 21 | -- update sequence number: used for finding diffs when syncing. 22 | -- See usn in cards table for more details. 23 | ls integer not null, 24 | -- "last sync time" 25 | conf text not null, 26 | -- json object containing configuration options that are synced 27 | models text not null, 28 | -- json array of json objects containing the models (aka Note types) 29 | decks text not null, 30 | -- json array of json objects containing the deck 31 | dconf text not null, 32 | -- json array of json objects containing the deck options 33 | tags text not null 34 | -- a cache of tags used in the collection (This list is displayed in the browser. Potentially at other place) 35 | ); 36 | ``` 37 | */ 38 | export declare class Col { 39 | id?: number; 40 | crt?: number; 41 | mod?: number; 42 | scm?: number; 43 | ver?: number; 44 | dty?: number; 45 | usn?: number; 46 | ls?: number; 47 | conf?: Record; 48 | models?: Record; 49 | decks?: Record; 50 | dconf?: Record; 51 | tags?: Record; 52 | } 53 | export declare const dbCol: Table; 54 | /** 55 | * json object containing configuration options that are synced 56 | */ 57 | export declare class Config { 58 | key: string; 59 | usn?: number; 60 | mtimeSec?: number; 61 | val: ArrayBuffer; 62 | } 63 | export declare const dbConfig: Table; 64 | /** 65 | * json array of json objects containing the deck options 66 | */ 67 | export declare class DeckConfig { 68 | id: number; 69 | name: string; 70 | mtimeSecs?: number; 71 | usn?: number; 72 | config: ArrayBuffer; 73 | } 74 | export declare const dbDeckConfig: Table; 75 | /** 76 | * json array of json objects containing the deck options 77 | */ 78 | export declare class Decks { 79 | id: number; 80 | name: string; 81 | mtimeSecs?: number; 82 | usn?: number; 83 | common: ArrayBuffer; 84 | kind: ArrayBuffer; 85 | } 86 | export declare const dbDecks: Table; 87 | /** 88 | * ```sql 89 | * -- Contains deleted cards, notes, and decks that need to be synced. 90 | -- usn should be set to -1, 91 | -- oid is the original id. 92 | -- type: 0 for a card, 1 for a note and 2 for a deck 93 | CREATE TABLE graves ( 94 | usn integer not null, 95 | oid integer not null, 96 | type integer not null 97 | ); 98 | * ``` 99 | */ 100 | export declare class Graves { 101 | usn?: number; 102 | oid: number; 103 | type: number; 104 | } 105 | export declare const dbGraves: Table; 106 | export declare class Notetypes { 107 | id: number; 108 | name: string; 109 | mtimeSecs?: number; 110 | usn?: number; 111 | config: ArrayBuffer; 112 | } 113 | export declare const dbNotetypes: Table; 114 | export declare class Fields { 115 | ntid: number; 116 | ord: number; 117 | name: string; 118 | config: ArrayBuffer; 119 | } 120 | export declare const dbFields: Table; 121 | /** 122 | * ```sql 123 | * -- Notes contain the raw information that is formatted into a number of cards 124 | -- according to the models 125 | CREATE TABLE notes ( 126 | id integer primary key, 127 | -- epoch seconds of when the note was created 128 | guid text not null, 129 | -- globally unique id, almost certainly used for syncing 130 | mid integer not null, 131 | -- model id 132 | mod integer not null, 133 | -- modification timestamp, epoch seconds 134 | usn integer not null, 135 | -- update sequence number: for finding diffs when syncing. 136 | -- See the description in the cards table for more info 137 | tags text not null, 138 | -- space-separated string of tags. 139 | -- includes space at the beginning and end, for LIKE "% tag %" queries 140 | flds text not null, 141 | -- the values of the fields in this note. separated by 0x1f (31) character. 142 | sfld text not null, 143 | -- sort field: used for quick sorting and duplicate check 144 | csum integer not null, 145 | -- field checksum used for duplicate check. 146 | -- integer representation of first 8 digits of sha1 hash of the first field 147 | flags integer not null, 148 | -- unused 149 | data text not null 150 | -- unused 151 | ); 152 | * ``` 153 | */ 154 | export declare class Notes { 155 | id: number; 156 | guid?: string; 157 | mid: number; 158 | mod?: number; 159 | usn?: number; 160 | tags?: string[]; 161 | flds: string[]; 162 | sfld?: string; 163 | csum?: number; 164 | flags?: number; 165 | data?: string; 166 | } 167 | export declare const dbNotes: Table; 168 | /** 169 | * Empty table, despite already created some tags 170 | */ 171 | export declare class Tags { 172 | tag: string; 173 | usn?: number; 174 | } 175 | export declare const dbTags: Table; 176 | export declare class Templates { 177 | ntid: number; 178 | ord?: number; 179 | name: string; 180 | mtimeSecs?: number; 181 | usn?: number; 182 | config: ArrayBuffer; 183 | } 184 | export declare const dbTemplates: Table; 185 | /** 186 | * ```sql 187 | * -- Cards are what you review. 188 | -- There can be multiple cards for each note, as determined by the Template. 189 | CREATE TABLE cards ( 190 | id integer primary key, 191 | -- the epoch milliseconds of when the card was created 192 | nid integer not null,-- 193 | -- notes.id 194 | did integer not null, 195 | -- deck id (available in col table) 196 | ord integer not null, 197 | -- ordinal : identifies which of the card templates it corresponds to 198 | -- valid values are from 0 to num templates - 1 199 | mod integer not null, 200 | -- modificaton time as epoch seconds 201 | usn integer not null, 202 | -- update sequence number : used to figure out diffs when syncing. 203 | -- value of -1 indicates changes that need to be pushed to server. 204 | -- usn < server usn indicates changes that need to be pulled from server. 205 | type integer not null, 206 | -- 0=new, 1=learning, 2=due, 3=filtered 207 | queue integer not null, 208 | -- -3=sched buried, -2=user buried, -1=suspended, 209 | -- 0=new, 1=learning, 2=due (as for type) 210 | -- 3=in learning, next rev in at least a day after the previous review 211 | due integer not null, 212 | -- Due is used differently for different card types: 213 | -- new: note id or random int 214 | -- due: integer day, relative to the collection's creation time 215 | -- learning: integer timestamp 216 | ivl integer not null, 217 | -- interval (used in SRS algorithm). Negative = seconds, positive = days 218 | factor integer not null, 219 | -- factor (used in SRS algorithm) 220 | reps integer not null, 221 | -- number of reviews 222 | lapses integer not null, 223 | -- the number of times the card went from a "was answered correctly" 224 | -- to "was answered incorrectly" state 225 | left integer not null, 226 | -- of the form a*1000+b, with: 227 | -- b the number of reps left till graduation 228 | -- a the number of reps left today 229 | odue integer not null, 230 | -- original due: only used when the card is currently in filtered deck 231 | odid integer not null, 232 | -- original did: only used when the card is currently in filtered deck 233 | flags integer not null, 234 | -- currently unused 235 | data text not null 236 | -- currently unused 237 | ); 238 | ``` 239 | */ 240 | export declare class Cards { 241 | id?: number; 242 | nid: number; 243 | did: number; 244 | ord: number; 245 | mod?: number; 246 | usn?: number; 247 | queue?: number; 248 | due?: number; 249 | ivl?: number; 250 | factor?: number; 251 | reps?: number; 252 | lapses?: number; 253 | left?: number; 254 | odue?: number; 255 | odid?: number; 256 | flags?: number; 257 | data?: string; 258 | } 259 | export declare const dbCards: Table; 260 | /** 261 | * ```sql 262 | * -- revlog is a review history; it has a row for every review you've ever done! 263 | CREATE TABLE revlog ( 264 | id integer primary key, 265 | -- epoch-milliseconds timestamp of when you did the review 266 | cid integer not null, 267 | -- cards.id 268 | usn integer not null, 269 | -- update sequence number: for finding diffs when syncing. 270 | -- See the description in the cards table for more info 271 | ease integer not null, 272 | -- which button you pushed to score your recall. 273 | -- review: 1(wrong), 2(hard), 3(ok), 4(easy) 274 | -- learn/relearn: 1(wrong), 2(ok), 3(easy) 275 | ivl integer not null, 276 | -- interval 277 | lastIvl integer not null, 278 | -- last interval 279 | factor integer not null, 280 | -- factor 281 | time integer not null, 282 | -- how many milliseconds your review took, up to 60000 (60s) 283 | type integer not null 284 | -- 0=learn, 1=review, 2=relearn, 3=cram 285 | ); 286 | * ``` 287 | */ 288 | export declare class Revlog { 289 | id?: number; 290 | cid: number; 291 | usn?: number; 292 | ease: number; 293 | ivl: number; 294 | lastIvl: number; 295 | factor: number; 296 | time: number; 297 | type: number; 298 | } 299 | export declare const dbRevlog: Table; 300 | export declare function initDatabase(filename: string): Db; 301 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /src/anki21/index.ts: -------------------------------------------------------------------------------- 1 | import { Db, Entity, Table, primary, prop } from 'liteorm' 2 | import { nanoid } from 'nanoid' 3 | import stripHtml from 'string-strip-html' 4 | 5 | /** 6 | * col contains a single row that holds various information about the collection 7 | * 8 | * ```sql 9 | * CREATE TABLE col ( 10 | id integer primary key, 11 | -- arbitrary number since there is only one row 12 | crt integer not null, 13 | -- created timestamp 14 | mod integer not null, 15 | -- last modified in milliseconds 16 | scm integer not null, 17 | -- schema mod time: time when "schema" was modified. 18 | -- If server scm is different from the client scm a full-sync is required 19 | ver integer not null, 20 | -- version 21 | dty integer not null, 22 | -- dirty: unused, set to 0 23 | usn integer not null, 24 | -- update sequence number: used for finding diffs when syncing. 25 | -- See usn in cards table for more details. 26 | ls integer not null, 27 | -- "last sync time" 28 | conf text not null, 29 | -- json object containing configuration options that are synced 30 | models text not null, 31 | -- json array of json objects containing the models (aka Note types) 32 | decks text not null, 33 | -- json array of json objects containing the deck 34 | dconf text not null, 35 | -- json array of json objects containing the deck options 36 | tags text not null 37 | -- a cache of tags used in the collection (This list is displayed in the browser. Potentially at other place) 38 | ); 39 | ``` 40 | */ 41 | @Entity() 42 | export class Col { 43 | @primary({ default: 1 }) id?: number 44 | @prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }) 45 | crt?: number 46 | 47 | @prop({ type: 'int', default: () => Math.floor(+new Date()) }) mod?: number 48 | @prop({ type: 'int', default: 0 }) scm?: number 49 | @prop({ type: 'int', default: 16 }) ver?: number 50 | @prop({ type: 'int', default: 0 }) dty?: number 51 | @prop({ type: 'int', default: 19 }) usn?: number 52 | @prop({ type: 'int', default: 0 }) ls?: number 53 | @prop({ default: '' }) conf?: Record 54 | @prop({ default: '' }) models?: Record 55 | @prop({ default: '' }) decks?: Record 56 | @prop({ default: '' }) dconf?: Record 57 | @prop({ default: '' }) tags?: Record 58 | } 59 | 60 | export const dbCol = new Table(Col) 61 | 62 | /** 63 | * json object containing configuration options that are synced 64 | */ 65 | @Entity({ withoutRowID: true }) 66 | export class Config { 67 | @prop({ index: true }) key!: string 68 | @prop({ type: 'int', default: 0 }) usn?: number 69 | @prop({ type: 'int', default: 0 }) mtimeSec?: number 70 | @prop() val!: ArrayBuffer 71 | } 72 | 73 | export const dbConfig = new Table(Config) 74 | 75 | /** 76 | * json array of json objects containing the deck options 77 | */ 78 | @Entity() 79 | export class DeckConfig { 80 | @primary({ autoincrement: true }) id!: number 81 | @prop({ collate: 'unicase' }) name!: string 82 | 83 | @prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }) 84 | mtimeSecs?: number 85 | 86 | @prop({ type: 'int', default: 0 }) usn?: number 87 | @prop() config!: ArrayBuffer 88 | } 89 | 90 | export const dbDeckConfig = new Table(DeckConfig) 91 | 92 | /** 93 | * json array of json objects containing the deck options 94 | */ 95 | @Entity() 96 | export class Decks { 97 | @primary({ autoincrement: true }) id!: number 98 | @prop({ collate: 'unicase', index: 'idx_decks_name' }) name!: string 99 | 100 | @prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }) 101 | mtimeSecs?: number 102 | 103 | @prop({ type: 'int', default: 0 }) usn?: number 104 | @prop() common!: ArrayBuffer 105 | @prop() kind!: ArrayBuffer 106 | } 107 | 108 | export const dbDecks = new Table(Decks) 109 | 110 | /** 111 | * ```sql 112 | * -- Contains deleted cards, notes, and decks that need to be synced. 113 | -- usn should be set to -1, 114 | -- oid is the original id. 115 | -- type: 0 for a card, 1 for a note and 2 for a deck 116 | CREATE TABLE graves ( 117 | usn integer not null, 118 | oid integer not null, 119 | type integer not null 120 | ); 121 | * ``` 122 | */ 123 | @Entity() 124 | export class Graves { 125 | @prop({ type: 'int', default: -1 }) usn?: number 126 | @prop({ type: 'int' }) oid!: number 127 | @prop({ type: 'int' }) type!: number 128 | } 129 | 130 | export const dbGraves = new Table(Graves) 131 | 132 | @Entity() 133 | export class Notetypes { 134 | @primary({ autoincrement: true }) id!: number 135 | @prop({ collate: 'unicase', index: 'idx_notetypes_name' }) name!: string 136 | 137 | @prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }) 138 | mtimeSecs?: number 139 | 140 | @prop({ type: 'int', default: 0, index: 'idx_notetypes_usn' }) usn?: number 141 | @prop() config!: ArrayBuffer 142 | } 143 | 144 | export const dbNotetypes = new Table(Notetypes) 145 | 146 | // eslint-disable-next-line no-use-before-define 147 | @Entity({ 148 | primary: ['ntid', 'ord'], 149 | index: [{ name: 'idx_fields_name_ntid', keys: ['name', 'ntid'] }], 150 | withoutRowID: true 151 | }) 152 | export class Fields { 153 | @prop({ type: 'int', references: dbNotetypes }) ntid!: number 154 | @prop({ type: 'int' }) ord!: number 155 | @prop({ collate: 'unicase' }) name!: string 156 | @prop() config!: ArrayBuffer 157 | } 158 | 159 | export const dbFields = new Table(Fields) 160 | 161 | /** 162 | * ```sql 163 | * -- Notes contain the raw information that is formatted into a number of cards 164 | -- according to the models 165 | CREATE TABLE notes ( 166 | id integer primary key, 167 | -- epoch seconds of when the note was created 168 | guid text not null, 169 | -- globally unique id, almost certainly used for syncing 170 | mid integer not null, 171 | -- model id 172 | mod integer not null, 173 | -- modification timestamp, epoch seconds 174 | usn integer not null, 175 | -- update sequence number: for finding diffs when syncing. 176 | -- See the description in the cards table for more info 177 | tags text not null, 178 | -- space-separated string of tags. 179 | -- includes space at the beginning and end, for LIKE "% tag %" queries 180 | flds text not null, 181 | -- the values of the fields in this note. separated by 0x1f (31) character. 182 | sfld text not null, 183 | -- sort field: used for quick sorting and duplicate check 184 | csum integer not null, 185 | -- field checksum used for duplicate check. 186 | -- integer representation of first 8 digits of sha1 hash of the first field 187 | flags integer not null, 188 | -- unused 189 | data text not null 190 | -- unused 191 | ); 192 | * ``` 193 | */ 194 | @Entity() 195 | export class Notes { 196 | @primary({ autoincrement: true }) id!: number 197 | @prop({ unique: true, default: () => nanoid() }) guid?: string 198 | 199 | @prop({ type: 'int', references: dbNotetypes, index: 'idx_notes_mid' }) 200 | mid!: number 201 | 202 | @prop({ type: 'int', onChange: () => Math.floor(+new Date()) }) mod?: number 203 | @prop({ type: 'int', default: -1, index: 'ix_notes_usn' }) usn?: number 204 | 205 | @prop({ 206 | type: 'string', 207 | transform: { 208 | get: (r: string) => r.split(' ').filter((el) => el), 209 | set: (d) => d.join(' ') 210 | }, 211 | default: () => [] 212 | }) 213 | tags?: string[] 214 | 215 | @prop({ 216 | type: 'string', 217 | transform: { 218 | get: (r: string) => r.split('\x1f').filter((el) => el), 219 | set: (d) => d.join('\x1f') 220 | } 221 | }) 222 | flds!: string[] 223 | 224 | @prop() sfld?: string 225 | @prop({ type: 'int', index: 'ix_notes_csum' }) csum?: number 226 | @prop({ type: 'int', default: 0 }) flags?: number 227 | @prop({ default: '' }) data?: string 228 | } 229 | 230 | export const dbNotes = new Table(Notes) 231 | 232 | dbNotes.on('pre-create', (d) => { 233 | d.entry.sfld = stripHtml(d.entry.flds[0]).result 234 | }) 235 | 236 | /** 237 | * Empty table, despite already created some tags 238 | */ 239 | @Entity({ withoutRowID: true }) 240 | export class Tags { 241 | @prop({ index: true, collate: 'unicase' }) tag!: string 242 | @prop({ type: 'int', default: 0 }) usn?: number 243 | } 244 | 245 | export const dbTags = new Table(Tags) 246 | 247 | // eslint-disable-next-line no-use-before-define 248 | @Entity({ 249 | primary: ['ntid', 'ord'], 250 | index: [ 251 | { 252 | name: 'idx_templates_name_ntid', 253 | keys: ['name', 'ntid'] 254 | } 255 | ], 256 | withoutRowID: true 257 | }) 258 | export class Templates { 259 | @prop({ type: 'int', references: dbNotetypes }) ntid!: number 260 | @prop({ type: 'int', default: 0 }) ord?: number 261 | @prop({ collate: 'unicase' }) name!: string 262 | @prop({ type: 'int', default: 0 }) mtimeSecs?: number 263 | @prop({ type: 'int', default: 0, index: 'idx_templates_usn' }) usn?: number 264 | @prop() config!: ArrayBuffer 265 | } 266 | 267 | export const dbTemplates = new Table(Templates) 268 | 269 | /** 270 | * ```sql 271 | * -- Cards are what you review. 272 | -- There can be multiple cards for each note, as determined by the Template. 273 | CREATE TABLE cards ( 274 | id integer primary key, 275 | -- the epoch milliseconds of when the card was created 276 | nid integer not null,-- 277 | -- notes.id 278 | did integer not null, 279 | -- deck id (available in col table) 280 | ord integer not null, 281 | -- ordinal : identifies which of the card templates it corresponds to 282 | -- valid values are from 0 to num templates - 1 283 | mod integer not null, 284 | -- modificaton time as epoch seconds 285 | usn integer not null, 286 | -- update sequence number : used to figure out diffs when syncing. 287 | -- value of -1 indicates changes that need to be pushed to server. 288 | -- usn < server usn indicates changes that need to be pulled from server. 289 | type integer not null, 290 | -- 0=new, 1=learning, 2=due, 3=filtered 291 | queue integer not null, 292 | -- -3=sched buried, -2=user buried, -1=suspended, 293 | -- 0=new, 1=learning, 2=due (as for type) 294 | -- 3=in learning, next rev in at least a day after the previous review 295 | due integer not null, 296 | -- Due is used differently for different card types: 297 | -- new: note id or random int 298 | -- due: integer day, relative to the collection's creation time 299 | -- learning: integer timestamp 300 | ivl integer not null, 301 | -- interval (used in SRS algorithm). Negative = seconds, positive = days 302 | factor integer not null, 303 | -- factor (used in SRS algorithm) 304 | reps integer not null, 305 | -- number of reviews 306 | lapses integer not null, 307 | -- the number of times the card went from a "was answered correctly" 308 | -- to "was answered incorrectly" state 309 | left integer not null, 310 | -- of the form a*1000+b, with: 311 | -- b the number of reps left till graduation 312 | -- a the number of reps left today 313 | odue integer not null, 314 | -- original due: only used when the card is currently in filtered deck 315 | odid integer not null, 316 | -- original did: only used when the card is currently in filtered deck 317 | flags integer not null, 318 | -- currently unused 319 | data text not null 320 | -- currently unused 321 | ); 322 | ``` 323 | */ 324 | // eslint-disable-next-line no-use-before-define 325 | @Entity({ 326 | index: [{ name: 'ix_cards_sched', keys: ['did', 'queue', 'due'] }] 327 | }) 328 | export class Cards { 329 | @primary({ autoincrement: true }) id?: number 330 | @prop({ type: 'int', references: dbNotes, index: 'ix_cards_nid' }) 331 | nid!: number 332 | 333 | @prop({ type: 'int', references: dbDecks }) did!: number 334 | @prop({ type: 'int' }) ord!: number 335 | @prop({ type: 'int', onChange: () => Math.floor(+new Date()) }) mod?: number 336 | @prop({ type: 'int', default: -1, index: 'ix_cards_usn' }) usn?: number 337 | @prop({ type: 'int', default: 0 }) queue?: number 338 | @prop({ type: 'int' }) due?: number 339 | @prop({ type: 'int', default: 0 }) ivl?: number 340 | @prop({ type: 'int', default: 0 }) factor?: number 341 | @prop({ type: 'int', default: 0 }) reps?: number 342 | @prop({ type: 'int', default: 0 }) lapses?: number 343 | @prop({ type: 'int', default: 0 }) left?: number 344 | @prop({ type: 'int', default: 0 }) odue?: number 345 | @prop({ type: 'int', default: 0, index: 'idx_cards_odid' }) odid?: number 346 | @prop({ type: 'int', default: 0 }) flags?: number 347 | @prop({ default: '' }) data?: string 348 | } 349 | 350 | export const dbCards = new Table(Cards) 351 | 352 | dbCards.on('pre-create', (d) => { 353 | d.entry.due = d.entry.nid 354 | }) 355 | 356 | /** 357 | * ```sql 358 | * -- revlog is a review history; it has a row for every review you've ever done! 359 | CREATE TABLE revlog ( 360 | id integer primary key, 361 | -- epoch-milliseconds timestamp of when you did the review 362 | cid integer not null, 363 | -- cards.id 364 | usn integer not null, 365 | -- update sequence number: for finding diffs when syncing. 366 | -- See the description in the cards table for more info 367 | ease integer not null, 368 | -- which button you pushed to score your recall. 369 | -- review: 1(wrong), 2(hard), 3(ok), 4(easy) 370 | -- learn/relearn: 1(wrong), 2(ok), 3(easy) 371 | ivl integer not null, 372 | -- interval 373 | lastIvl integer not null, 374 | -- last interval 375 | factor integer not null, 376 | -- factor 377 | time integer not null, 378 | -- how many milliseconds your review took, up to 60000 (60s) 379 | type integer not null 380 | -- 0=learn, 1=review, 2=relearn, 3=cram 381 | ); 382 | * ``` 383 | */ 384 | @Entity() 385 | export class Revlog { 386 | @primary({ autoincrement: true }) id?: number 387 | @prop({ type: 'int', references: dbCards, index: 'ix_revlog_cid' }) 388 | cid!: number 389 | 390 | @prop({ type: 'int', default: -1, index: 'ix_revlog_usn' }) usn?: number 391 | @prop({ type: 'int' }) ease!: number 392 | @prop({ type: 'int' }) ivl!: number 393 | @prop({ type: 'int' }) lastIvl!: number 394 | @prop({ type: 'int' }) factor!: number 395 | @prop({ type: 'int' }) time!: number 396 | @prop({ type: 'int' }) type!: number 397 | } 398 | 399 | export const dbRevlog = new Table(Revlog) 400 | 401 | export function initDatabase(filename: string) { 402 | const db = new Db(filename) 403 | return db 404 | } 405 | -------------------------------------------------------------------------------- /lib/anki21/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.initDatabase = exports.dbRevlog = exports.Revlog = exports.dbCards = exports.Cards = exports.dbTemplates = exports.Templates = exports.dbTags = exports.Tags = exports.dbNotes = exports.Notes = exports.dbFields = exports.Fields = exports.dbNotetypes = exports.Notetypes = exports.dbGraves = exports.Graves = exports.dbDecks = exports.Decks = exports.dbDeckConfig = exports.DeckConfig = exports.dbConfig = exports.Config = exports.dbCol = exports.Col = void 0; 16 | const liteorm_1 = require("liteorm"); 17 | const nanoid_1 = require("nanoid"); 18 | const string_strip_html_1 = __importDefault(require("string-strip-html")); 19 | /** 20 | * col contains a single row that holds various information about the collection 21 | * 22 | * ```sql 23 | * CREATE TABLE col ( 24 | id integer primary key, 25 | -- arbitrary number since there is only one row 26 | crt integer not null, 27 | -- created timestamp 28 | mod integer not null, 29 | -- last modified in milliseconds 30 | scm integer not null, 31 | -- schema mod time: time when "schema" was modified. 32 | -- If server scm is different from the client scm a full-sync is required 33 | ver integer not null, 34 | -- version 35 | dty integer not null, 36 | -- dirty: unused, set to 0 37 | usn integer not null, 38 | -- update sequence number: used for finding diffs when syncing. 39 | -- See usn in cards table for more details. 40 | ls integer not null, 41 | -- "last sync time" 42 | conf text not null, 43 | -- json object containing configuration options that are synced 44 | models text not null, 45 | -- json array of json objects containing the models (aka Note types) 46 | decks text not null, 47 | -- json array of json objects containing the deck 48 | dconf text not null, 49 | -- json array of json objects containing the deck options 50 | tags text not null 51 | -- a cache of tags used in the collection (This list is displayed in the browser. Potentially at other place) 52 | ); 53 | ``` 54 | */ 55 | let Col = class Col { 56 | }; 57 | __decorate([ 58 | liteorm_1.primary({ default: 1 }), 59 | __metadata("design:type", Number) 60 | ], Col.prototype, "id", void 0); 61 | __decorate([ 62 | liteorm_1.prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }), 63 | __metadata("design:type", Number) 64 | ], Col.prototype, "crt", void 0); 65 | __decorate([ 66 | liteorm_1.prop({ type: 'int', default: () => Math.floor(+new Date()) }), 67 | __metadata("design:type", Number) 68 | ], Col.prototype, "mod", void 0); 69 | __decorate([ 70 | liteorm_1.prop({ type: 'int', default: 0 }), 71 | __metadata("design:type", Number) 72 | ], Col.prototype, "scm", void 0); 73 | __decorate([ 74 | liteorm_1.prop({ type: 'int', default: 16 }), 75 | __metadata("design:type", Number) 76 | ], Col.prototype, "ver", void 0); 77 | __decorate([ 78 | liteorm_1.prop({ type: 'int', default: 0 }), 79 | __metadata("design:type", Number) 80 | ], Col.prototype, "dty", void 0); 81 | __decorate([ 82 | liteorm_1.prop({ type: 'int', default: 19 }), 83 | __metadata("design:type", Number) 84 | ], Col.prototype, "usn", void 0); 85 | __decorate([ 86 | liteorm_1.prop({ type: 'int', default: 0 }), 87 | __metadata("design:type", Number) 88 | ], Col.prototype, "ls", void 0); 89 | __decorate([ 90 | liteorm_1.prop({ default: '' }), 91 | __metadata("design:type", Object) 92 | ], Col.prototype, "conf", void 0); 93 | __decorate([ 94 | liteorm_1.prop({ default: '' }), 95 | __metadata("design:type", Object) 96 | ], Col.prototype, "models", void 0); 97 | __decorate([ 98 | liteorm_1.prop({ default: '' }), 99 | __metadata("design:type", Object) 100 | ], Col.prototype, "decks", void 0); 101 | __decorate([ 102 | liteorm_1.prop({ default: '' }), 103 | __metadata("design:type", Object) 104 | ], Col.prototype, "dconf", void 0); 105 | __decorate([ 106 | liteorm_1.prop({ default: '' }), 107 | __metadata("design:type", Object) 108 | ], Col.prototype, "tags", void 0); 109 | Col = __decorate([ 110 | liteorm_1.Entity() 111 | ], Col); 112 | exports.Col = Col; 113 | exports.dbCol = new liteorm_1.Table(Col); 114 | /** 115 | * json object containing configuration options that are synced 116 | */ 117 | let Config = class Config { 118 | }; 119 | __decorate([ 120 | liteorm_1.prop({ index: true }), 121 | __metadata("design:type", String) 122 | ], Config.prototype, "key", void 0); 123 | __decorate([ 124 | liteorm_1.prop({ type: 'int', default: 0 }), 125 | __metadata("design:type", Number) 126 | ], Config.prototype, "usn", void 0); 127 | __decorate([ 128 | liteorm_1.prop({ type: 'int', default: 0 }), 129 | __metadata("design:type", Number) 130 | ], Config.prototype, "mtimeSec", void 0); 131 | __decorate([ 132 | liteorm_1.prop(), 133 | __metadata("design:type", ArrayBuffer) 134 | ], Config.prototype, "val", void 0); 135 | Config = __decorate([ 136 | liteorm_1.Entity({ withoutRowID: true }) 137 | ], Config); 138 | exports.Config = Config; 139 | exports.dbConfig = new liteorm_1.Table(Config); 140 | /** 141 | * json array of json objects containing the deck options 142 | */ 143 | let DeckConfig = class DeckConfig { 144 | }; 145 | __decorate([ 146 | liteorm_1.primary({ autoincrement: true }), 147 | __metadata("design:type", Number) 148 | ], DeckConfig.prototype, "id", void 0); 149 | __decorate([ 150 | liteorm_1.prop({ collate: 'unicase' }), 151 | __metadata("design:type", String) 152 | ], DeckConfig.prototype, "name", void 0); 153 | __decorate([ 154 | liteorm_1.prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }), 155 | __metadata("design:type", Number) 156 | ], DeckConfig.prototype, "mtimeSecs", void 0); 157 | __decorate([ 158 | liteorm_1.prop({ type: 'int', default: 0 }), 159 | __metadata("design:type", Number) 160 | ], DeckConfig.prototype, "usn", void 0); 161 | __decorate([ 162 | liteorm_1.prop(), 163 | __metadata("design:type", ArrayBuffer) 164 | ], DeckConfig.prototype, "config", void 0); 165 | DeckConfig = __decorate([ 166 | liteorm_1.Entity() 167 | ], DeckConfig); 168 | exports.DeckConfig = DeckConfig; 169 | exports.dbDeckConfig = new liteorm_1.Table(DeckConfig); 170 | /** 171 | * json array of json objects containing the deck options 172 | */ 173 | let Decks = class Decks { 174 | }; 175 | __decorate([ 176 | liteorm_1.primary({ autoincrement: true }), 177 | __metadata("design:type", Number) 178 | ], Decks.prototype, "id", void 0); 179 | __decorate([ 180 | liteorm_1.prop({ collate: 'unicase', index: 'idx_decks_name' }), 181 | __metadata("design:type", String) 182 | ], Decks.prototype, "name", void 0); 183 | __decorate([ 184 | liteorm_1.prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }), 185 | __metadata("design:type", Number) 186 | ], Decks.prototype, "mtimeSecs", void 0); 187 | __decorate([ 188 | liteorm_1.prop({ type: 'int', default: 0 }), 189 | __metadata("design:type", Number) 190 | ], Decks.prototype, "usn", void 0); 191 | __decorate([ 192 | liteorm_1.prop(), 193 | __metadata("design:type", ArrayBuffer) 194 | ], Decks.prototype, "common", void 0); 195 | __decorate([ 196 | liteorm_1.prop(), 197 | __metadata("design:type", ArrayBuffer) 198 | ], Decks.prototype, "kind", void 0); 199 | Decks = __decorate([ 200 | liteorm_1.Entity() 201 | ], Decks); 202 | exports.Decks = Decks; 203 | exports.dbDecks = new liteorm_1.Table(Decks); 204 | /** 205 | * ```sql 206 | * -- Contains deleted cards, notes, and decks that need to be synced. 207 | -- usn should be set to -1, 208 | -- oid is the original id. 209 | -- type: 0 for a card, 1 for a note and 2 for a deck 210 | CREATE TABLE graves ( 211 | usn integer not null, 212 | oid integer not null, 213 | type integer not null 214 | ); 215 | * ``` 216 | */ 217 | let Graves = class Graves { 218 | }; 219 | __decorate([ 220 | liteorm_1.prop({ type: 'int', default: -1 }), 221 | __metadata("design:type", Number) 222 | ], Graves.prototype, "usn", void 0); 223 | __decorate([ 224 | liteorm_1.prop({ type: 'int' }), 225 | __metadata("design:type", Number) 226 | ], Graves.prototype, "oid", void 0); 227 | __decorate([ 228 | liteorm_1.prop({ type: 'int' }), 229 | __metadata("design:type", Number) 230 | ], Graves.prototype, "type", void 0); 231 | Graves = __decorate([ 232 | liteorm_1.Entity() 233 | ], Graves); 234 | exports.Graves = Graves; 235 | exports.dbGraves = new liteorm_1.Table(Graves); 236 | let Notetypes = class Notetypes { 237 | }; 238 | __decorate([ 239 | liteorm_1.primary({ autoincrement: true }), 240 | __metadata("design:type", Number) 241 | ], Notetypes.prototype, "id", void 0); 242 | __decorate([ 243 | liteorm_1.prop({ collate: 'unicase', index: 'idx_notetypes_name' }), 244 | __metadata("design:type", String) 245 | ], Notetypes.prototype, "name", void 0); 246 | __decorate([ 247 | liteorm_1.prop({ type: 'int', default: () => Math.floor(+new Date() / 1000) }), 248 | __metadata("design:type", Number) 249 | ], Notetypes.prototype, "mtimeSecs", void 0); 250 | __decorate([ 251 | liteorm_1.prop({ type: 'int', default: 0, index: 'idx_notetypes_usn' }), 252 | __metadata("design:type", Number) 253 | ], Notetypes.prototype, "usn", void 0); 254 | __decorate([ 255 | liteorm_1.prop(), 256 | __metadata("design:type", ArrayBuffer) 257 | ], Notetypes.prototype, "config", void 0); 258 | Notetypes = __decorate([ 259 | liteorm_1.Entity() 260 | ], Notetypes); 261 | exports.Notetypes = Notetypes; 262 | exports.dbNotetypes = new liteorm_1.Table(Notetypes); 263 | // eslint-disable-next-line no-use-before-define 264 | let Fields = class Fields { 265 | }; 266 | __decorate([ 267 | liteorm_1.prop({ type: 'int', references: exports.dbNotetypes }), 268 | __metadata("design:type", Number) 269 | ], Fields.prototype, "ntid", void 0); 270 | __decorate([ 271 | liteorm_1.prop({ type: 'int' }), 272 | __metadata("design:type", Number) 273 | ], Fields.prototype, "ord", void 0); 274 | __decorate([ 275 | liteorm_1.prop({ collate: 'unicase' }), 276 | __metadata("design:type", String) 277 | ], Fields.prototype, "name", void 0); 278 | __decorate([ 279 | liteorm_1.prop(), 280 | __metadata("design:type", ArrayBuffer) 281 | ], Fields.prototype, "config", void 0); 282 | Fields = __decorate([ 283 | liteorm_1.Entity({ 284 | primary: ['ntid', 'ord'], 285 | index: [{ name: 'idx_fields_name_ntid', keys: ['name', 'ntid'] }], 286 | withoutRowID: true 287 | }) 288 | ], Fields); 289 | exports.Fields = Fields; 290 | exports.dbFields = new liteorm_1.Table(Fields); 291 | /** 292 | * ```sql 293 | * -- Notes contain the raw information that is formatted into a number of cards 294 | -- according to the models 295 | CREATE TABLE notes ( 296 | id integer primary key, 297 | -- epoch seconds of when the note was created 298 | guid text not null, 299 | -- globally unique id, almost certainly used for syncing 300 | mid integer not null, 301 | -- model id 302 | mod integer not null, 303 | -- modification timestamp, epoch seconds 304 | usn integer not null, 305 | -- update sequence number: for finding diffs when syncing. 306 | -- See the description in the cards table for more info 307 | tags text not null, 308 | -- space-separated string of tags. 309 | -- includes space at the beginning and end, for LIKE "% tag %" queries 310 | flds text not null, 311 | -- the values of the fields in this note. separated by 0x1f (31) character. 312 | sfld text not null, 313 | -- sort field: used for quick sorting and duplicate check 314 | csum integer not null, 315 | -- field checksum used for duplicate check. 316 | -- integer representation of first 8 digits of sha1 hash of the first field 317 | flags integer not null, 318 | -- unused 319 | data text not null 320 | -- unused 321 | ); 322 | * ``` 323 | */ 324 | let Notes = class Notes { 325 | }; 326 | __decorate([ 327 | liteorm_1.primary({ autoincrement: true }), 328 | __metadata("design:type", Number) 329 | ], Notes.prototype, "id", void 0); 330 | __decorate([ 331 | liteorm_1.prop({ unique: true, default: () => nanoid_1.nanoid() }), 332 | __metadata("design:type", String) 333 | ], Notes.prototype, "guid", void 0); 334 | __decorate([ 335 | liteorm_1.prop({ type: 'int', references: exports.dbNotetypes, index: 'idx_notes_mid' }), 336 | __metadata("design:type", Number) 337 | ], Notes.prototype, "mid", void 0); 338 | __decorate([ 339 | liteorm_1.prop({ type: 'int', onChange: () => Math.floor(+new Date()) }), 340 | __metadata("design:type", Number) 341 | ], Notes.prototype, "mod", void 0); 342 | __decorate([ 343 | liteorm_1.prop({ type: 'int', default: -1, index: 'ix_notes_usn' }), 344 | __metadata("design:type", Number) 345 | ], Notes.prototype, "usn", void 0); 346 | __decorate([ 347 | liteorm_1.prop({ 348 | type: 'string', 349 | transform: { 350 | get: (r) => r.split(' ').filter((el) => el), 351 | set: (d) => d.join(' ') 352 | }, 353 | default: () => [] 354 | }), 355 | __metadata("design:type", Array) 356 | ], Notes.prototype, "tags", void 0); 357 | __decorate([ 358 | liteorm_1.prop({ 359 | type: 'string', 360 | transform: { 361 | get: (r) => r.split('\x1f').filter((el) => el), 362 | set: (d) => d.join('\x1f') 363 | } 364 | }), 365 | __metadata("design:type", Array) 366 | ], Notes.prototype, "flds", void 0); 367 | __decorate([ 368 | liteorm_1.prop(), 369 | __metadata("design:type", String) 370 | ], Notes.prototype, "sfld", void 0); 371 | __decorate([ 372 | liteorm_1.prop({ type: 'int', index: 'ix_notes_csum' }), 373 | __metadata("design:type", Number) 374 | ], Notes.prototype, "csum", void 0); 375 | __decorate([ 376 | liteorm_1.prop({ type: 'int', default: 0 }), 377 | __metadata("design:type", Number) 378 | ], Notes.prototype, "flags", void 0); 379 | __decorate([ 380 | liteorm_1.prop({ default: '' }), 381 | __metadata("design:type", String) 382 | ], Notes.prototype, "data", void 0); 383 | Notes = __decorate([ 384 | liteorm_1.Entity() 385 | ], Notes); 386 | exports.Notes = Notes; 387 | exports.dbNotes = new liteorm_1.Table(Notes); 388 | exports.dbNotes.on('pre-create', (d) => { 389 | d.entry.sfld = string_strip_html_1.default(d.entry.flds[0]).result; 390 | }); 391 | /** 392 | * Empty table, despite already created some tags 393 | */ 394 | let Tags = class Tags { 395 | }; 396 | __decorate([ 397 | liteorm_1.prop({ index: true, collate: 'unicase' }), 398 | __metadata("design:type", String) 399 | ], Tags.prototype, "tag", void 0); 400 | __decorate([ 401 | liteorm_1.prop({ type: 'int', default: 0 }), 402 | __metadata("design:type", Number) 403 | ], Tags.prototype, "usn", void 0); 404 | Tags = __decorate([ 405 | liteorm_1.Entity({ withoutRowID: true }) 406 | ], Tags); 407 | exports.Tags = Tags; 408 | exports.dbTags = new liteorm_1.Table(Tags); 409 | // eslint-disable-next-line no-use-before-define 410 | let Templates = class Templates { 411 | }; 412 | __decorate([ 413 | liteorm_1.prop({ type: 'int', references: exports.dbNotetypes }), 414 | __metadata("design:type", Number) 415 | ], Templates.prototype, "ntid", void 0); 416 | __decorate([ 417 | liteorm_1.prop({ type: 'int', default: 0 }), 418 | __metadata("design:type", Number) 419 | ], Templates.prototype, "ord", void 0); 420 | __decorate([ 421 | liteorm_1.prop({ collate: 'unicase' }), 422 | __metadata("design:type", String) 423 | ], Templates.prototype, "name", void 0); 424 | __decorate([ 425 | liteorm_1.prop({ type: 'int', default: 0 }), 426 | __metadata("design:type", Number) 427 | ], Templates.prototype, "mtimeSecs", void 0); 428 | __decorate([ 429 | liteorm_1.prop({ type: 'int', default: 0, index: 'idx_templates_usn' }), 430 | __metadata("design:type", Number) 431 | ], Templates.prototype, "usn", void 0); 432 | __decorate([ 433 | liteorm_1.prop(), 434 | __metadata("design:type", ArrayBuffer) 435 | ], Templates.prototype, "config", void 0); 436 | Templates = __decorate([ 437 | liteorm_1.Entity({ 438 | primary: ['ntid', 'ord'], 439 | index: [ 440 | { 441 | name: 'idx_templates_name_ntid', 442 | keys: ['name', 'ntid'] 443 | } 444 | ], 445 | withoutRowID: true 446 | }) 447 | ], Templates); 448 | exports.Templates = Templates; 449 | exports.dbTemplates = new liteorm_1.Table(Templates); 450 | /** 451 | * ```sql 452 | * -- Cards are what you review. 453 | -- There can be multiple cards for each note, as determined by the Template. 454 | CREATE TABLE cards ( 455 | id integer primary key, 456 | -- the epoch milliseconds of when the card was created 457 | nid integer not null,-- 458 | -- notes.id 459 | did integer not null, 460 | -- deck id (available in col table) 461 | ord integer not null, 462 | -- ordinal : identifies which of the card templates it corresponds to 463 | -- valid values are from 0 to num templates - 1 464 | mod integer not null, 465 | -- modificaton time as epoch seconds 466 | usn integer not null, 467 | -- update sequence number : used to figure out diffs when syncing. 468 | -- value of -1 indicates changes that need to be pushed to server. 469 | -- usn < server usn indicates changes that need to be pulled from server. 470 | type integer not null, 471 | -- 0=new, 1=learning, 2=due, 3=filtered 472 | queue integer not null, 473 | -- -3=sched buried, -2=user buried, -1=suspended, 474 | -- 0=new, 1=learning, 2=due (as for type) 475 | -- 3=in learning, next rev in at least a day after the previous review 476 | due integer not null, 477 | -- Due is used differently for different card types: 478 | -- new: note id or random int 479 | -- due: integer day, relative to the collection's creation time 480 | -- learning: integer timestamp 481 | ivl integer not null, 482 | -- interval (used in SRS algorithm). Negative = seconds, positive = days 483 | factor integer not null, 484 | -- factor (used in SRS algorithm) 485 | reps integer not null, 486 | -- number of reviews 487 | lapses integer not null, 488 | -- the number of times the card went from a "was answered correctly" 489 | -- to "was answered incorrectly" state 490 | left integer not null, 491 | -- of the form a*1000+b, with: 492 | -- b the number of reps left till graduation 493 | -- a the number of reps left today 494 | odue integer not null, 495 | -- original due: only used when the card is currently in filtered deck 496 | odid integer not null, 497 | -- original did: only used when the card is currently in filtered deck 498 | flags integer not null, 499 | -- currently unused 500 | data text not null 501 | -- currently unused 502 | ); 503 | ``` 504 | */ 505 | // eslint-disable-next-line no-use-before-define 506 | let Cards = class Cards { 507 | }; 508 | __decorate([ 509 | liteorm_1.primary({ autoincrement: true }), 510 | __metadata("design:type", Number) 511 | ], Cards.prototype, "id", void 0); 512 | __decorate([ 513 | liteorm_1.prop({ type: 'int', references: exports.dbNotes, index: 'ix_cards_nid' }), 514 | __metadata("design:type", Number) 515 | ], Cards.prototype, "nid", void 0); 516 | __decorate([ 517 | liteorm_1.prop({ type: 'int', references: exports.dbDecks }), 518 | __metadata("design:type", Number) 519 | ], Cards.prototype, "did", void 0); 520 | __decorate([ 521 | liteorm_1.prop({ type: 'int' }), 522 | __metadata("design:type", Number) 523 | ], Cards.prototype, "ord", void 0); 524 | __decorate([ 525 | liteorm_1.prop({ type: 'int', onChange: () => Math.floor(+new Date()) }), 526 | __metadata("design:type", Number) 527 | ], Cards.prototype, "mod", void 0); 528 | __decorate([ 529 | liteorm_1.prop({ type: 'int', default: -1, index: 'ix_cards_usn' }), 530 | __metadata("design:type", Number) 531 | ], Cards.prototype, "usn", void 0); 532 | __decorate([ 533 | liteorm_1.prop({ type: 'int', default: 0 }), 534 | __metadata("design:type", Number) 535 | ], Cards.prototype, "queue", void 0); 536 | __decorate([ 537 | liteorm_1.prop({ type: 'int' }), 538 | __metadata("design:type", Number) 539 | ], Cards.prototype, "due", void 0); 540 | __decorate([ 541 | liteorm_1.prop({ type: 'int', default: 0 }), 542 | __metadata("design:type", Number) 543 | ], Cards.prototype, "ivl", void 0); 544 | __decorate([ 545 | liteorm_1.prop({ type: 'int', default: 0 }), 546 | __metadata("design:type", Number) 547 | ], Cards.prototype, "factor", void 0); 548 | __decorate([ 549 | liteorm_1.prop({ type: 'int', default: 0 }), 550 | __metadata("design:type", Number) 551 | ], Cards.prototype, "reps", void 0); 552 | __decorate([ 553 | liteorm_1.prop({ type: 'int', default: 0 }), 554 | __metadata("design:type", Number) 555 | ], Cards.prototype, "lapses", void 0); 556 | __decorate([ 557 | liteorm_1.prop({ type: 'int', default: 0 }), 558 | __metadata("design:type", Number) 559 | ], Cards.prototype, "left", void 0); 560 | __decorate([ 561 | liteorm_1.prop({ type: 'int', default: 0 }), 562 | __metadata("design:type", Number) 563 | ], Cards.prototype, "odue", void 0); 564 | __decorate([ 565 | liteorm_1.prop({ type: 'int', default: 0, index: 'idx_cards_odid' }), 566 | __metadata("design:type", Number) 567 | ], Cards.prototype, "odid", void 0); 568 | __decorate([ 569 | liteorm_1.prop({ type: 'int', default: 0 }), 570 | __metadata("design:type", Number) 571 | ], Cards.prototype, "flags", void 0); 572 | __decorate([ 573 | liteorm_1.prop({ default: '' }), 574 | __metadata("design:type", String) 575 | ], Cards.prototype, "data", void 0); 576 | Cards = __decorate([ 577 | liteorm_1.Entity({ 578 | index: [{ name: 'ix_cards_sched', keys: ['did', 'queue', 'due'] }] 579 | }) 580 | ], Cards); 581 | exports.Cards = Cards; 582 | exports.dbCards = new liteorm_1.Table(Cards); 583 | exports.dbCards.on('pre-create', (d) => { 584 | d.entry.due = d.entry.nid; 585 | }); 586 | /** 587 | * ```sql 588 | * -- revlog is a review history; it has a row for every review you've ever done! 589 | CREATE TABLE revlog ( 590 | id integer primary key, 591 | -- epoch-milliseconds timestamp of when you did the review 592 | cid integer not null, 593 | -- cards.id 594 | usn integer not null, 595 | -- update sequence number: for finding diffs when syncing. 596 | -- See the description in the cards table for more info 597 | ease integer not null, 598 | -- which button you pushed to score your recall. 599 | -- review: 1(wrong), 2(hard), 3(ok), 4(easy) 600 | -- learn/relearn: 1(wrong), 2(ok), 3(easy) 601 | ivl integer not null, 602 | -- interval 603 | lastIvl integer not null, 604 | -- last interval 605 | factor integer not null, 606 | -- factor 607 | time integer not null, 608 | -- how many milliseconds your review took, up to 60000 (60s) 609 | type integer not null 610 | -- 0=learn, 1=review, 2=relearn, 3=cram 611 | ); 612 | * ``` 613 | */ 614 | let Revlog = class Revlog { 615 | }; 616 | __decorate([ 617 | liteorm_1.primary({ autoincrement: true }), 618 | __metadata("design:type", Number) 619 | ], Revlog.prototype, "id", void 0); 620 | __decorate([ 621 | liteorm_1.prop({ type: 'int', references: exports.dbCards, index: 'ix_revlog_cid' }), 622 | __metadata("design:type", Number) 623 | ], Revlog.prototype, "cid", void 0); 624 | __decorate([ 625 | liteorm_1.prop({ type: 'int', default: -1, index: 'ix_revlog_usn' }), 626 | __metadata("design:type", Number) 627 | ], Revlog.prototype, "usn", void 0); 628 | __decorate([ 629 | liteorm_1.prop({ type: 'int' }), 630 | __metadata("design:type", Number) 631 | ], Revlog.prototype, "ease", void 0); 632 | __decorate([ 633 | liteorm_1.prop({ type: 'int' }), 634 | __metadata("design:type", Number) 635 | ], Revlog.prototype, "ivl", void 0); 636 | __decorate([ 637 | liteorm_1.prop({ type: 'int' }), 638 | __metadata("design:type", Number) 639 | ], Revlog.prototype, "lastIvl", void 0); 640 | __decorate([ 641 | liteorm_1.prop({ type: 'int' }), 642 | __metadata("design:type", Number) 643 | ], Revlog.prototype, "factor", void 0); 644 | __decorate([ 645 | liteorm_1.prop({ type: 'int' }), 646 | __metadata("design:type", Number) 647 | ], Revlog.prototype, "time", void 0); 648 | __decorate([ 649 | liteorm_1.prop({ type: 'int' }), 650 | __metadata("design:type", Number) 651 | ], Revlog.prototype, "type", void 0); 652 | Revlog = __decorate([ 653 | liteorm_1.Entity() 654 | ], Revlog); 655 | exports.Revlog = Revlog; 656 | exports.dbRevlog = new liteorm_1.Table(Revlog); 657 | function initDatabase(filename) { 658 | const db = new liteorm_1.Db(filename); 659 | return db; 660 | } 661 | exports.initDatabase = initDatabase; 662 | //# sourceMappingURL=index.js.map --------------------------------------------------------------------------------