diff --git a/src/ankiconnect.ts b/src/ankiconnect.ts index d17ffe8..0c6c6f6 100644 --- a/src/ankiconnect.ts +++ b/src/ankiconnect.ts @@ -1,11 +1,120 @@ import axios, { AxiosInstance } from 'axios'; +export interface IAnkiConnectActions + extends Record< + string, + { + params?: Record; + result: unknown; + } + > { + // Deck Actions + deckNames: { + result: string[]; + }; + getDecks: { + params: { + cards: number[]; + }; + result: { + [deckName: string]: number[]; + }; + }; + changeDeck: { + params: { + cards: number[]; + deck: string; + }; + result: null; + }; + + // Media Actions + storeMediaFile: { + params: ( + | { + /** + * Specified base64-encoded contents + */ + data: string; + } + | { + path: string; + } + | { + url: string; + } + ) & { + /** + * To prevent Anki from removing files not used by any cards (e.g. for configuration files), + * prefix the filename with an underscore. + */ + filename: string; + /** + * Any existing file with the same name is deleted by default. + * Set `deleteExisting` to `false` to prevent that by letting Anki give the new file a non-conflicting name. + */ + deleteExisting?: boolean; + }; + result: string; + }; + retrieveMediaFile: { + /** + * Retrieves the base64-encoded contents of the specified file, returning `false` if the file does not exist. + */ + params: { + filename: string; + }; + result: string | false; + }; + getMediaFilesNames: { + /** + * Gets the names of media files matched the pattern. Returning all names by default. + */ + params: { + pattern: string; + }; + result: string[]; + }; + deleteMediaFile: { + params: { + filename: string; + }; + result: null; + }; +} + export class AnkiConnect { $axios: AxiosInstance; - constructor(public url = 'http://localhost:8765') { + constructor(public url = 'http://localhost:8765', public version = 6) { this.$axios = axios.create({ baseURL: url, }); } + + async api( + action: A, + params: IAnkiConnectActions[A]['params'], + version = this.version, + ): Promise { + const { data: response } = await this.$axios.post<{ + result: any; + error: string | null; + }>('/', { action, params, version }); + + if (Object.getOwnPropertyNames(response).length != 2) { + throw 'response has an unexpected number of fields'; + } + if (!response.hasOwnProperty('error')) { + throw 'response is missing required error field'; + } + if (!response.hasOwnProperty('result')) { + throw 'response is missing required result field'; + } + if (response.error) { + throw response.error; + } + + return response.result; + } }