commit
17a07b2871
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import anki
|
import anki
|
||||||
import aqt
|
import aqt
|
||||||
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
@ -26,6 +27,7 @@ import select
|
|||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
from time import time
|
from time import time
|
||||||
|
from unicodedata import normalize
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -335,6 +337,29 @@ class AnkiNoteParams:
|
|||||||
#
|
#
|
||||||
|
|
||||||
class AnkiBridge:
|
class AnkiBridge:
|
||||||
|
def storeMediaFile(self, filename, data):
|
||||||
|
self.deleteMediaFile(filename)
|
||||||
|
self.media().writeData(filename, base64.b64decode(data))
|
||||||
|
|
||||||
|
|
||||||
|
def retrieveMediaFile(self, filename):
|
||||||
|
# based on writeData from anki/media.py
|
||||||
|
filename = os.path.basename(filename)
|
||||||
|
filename = normalize("NFC", filename)
|
||||||
|
filename = self.media().stripIllegal(filename)
|
||||||
|
|
||||||
|
path = os.path.join(self.media().dir(), filename)
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, 'rb') as file:
|
||||||
|
return base64.b64encode(file.read()).decode('ascii')
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def deleteMediaFile(self, filename):
|
||||||
|
self.media().syncDelete(filename)
|
||||||
|
|
||||||
|
|
||||||
def addNote(self, params):
|
def addNote(self, params):
|
||||||
collection = self.collection()
|
collection = self.collection()
|
||||||
if collection is None:
|
if collection is None:
|
||||||
@ -881,6 +906,21 @@ class AnkiConnect:
|
|||||||
return self.anki.multi(actions)
|
return self.anki.multi(actions)
|
||||||
|
|
||||||
|
|
||||||
|
@webApi
|
||||||
|
def storeMediaFile(self, filename, data):
|
||||||
|
return self.anki.storeMediaFile(filename, data)
|
||||||
|
|
||||||
|
|
||||||
|
@webApi
|
||||||
|
def retrieveMediaFile(self, filename):
|
||||||
|
return self.anki.retrieveMediaFile(filename)
|
||||||
|
|
||||||
|
|
||||||
|
@webApi
|
||||||
|
def deleteMediaFile(self, filename):
|
||||||
|
return self.anki.deleteMediaFile(filename)
|
||||||
|
|
||||||
|
|
||||||
@webApi
|
@webApi
|
||||||
def deckNames(self):
|
def deckNames(self):
|
||||||
return self.anki.deckNames()
|
return self.anki.deckNames()
|
||||||
|
416
README.md
416
README.md
@ -84,6 +84,22 @@ curl localhost:8765 -X POST -d '{"action": "version"}'
|
|||||||
|
|
||||||
Below is a list of currently supported actions. Requests with invalid actions or parameters will a return `null` result.
|
Below is a list of currently supported actions. Requests with invalid actions or parameters will a return `null` result.
|
||||||
|
|
||||||
|
Categories:
|
||||||
|
|
||||||
|
* [Miscellaneous](#miscellaneous)
|
||||||
|
* [Decks](#decks)
|
||||||
|
* [Deck Configurations](#deck-configurations)
|
||||||
|
* [Models](#models)
|
||||||
|
* [Note Creation](#note-creation)
|
||||||
|
* [Note Tags](#note-tags)
|
||||||
|
* [Card Suspension](#card-suspension)
|
||||||
|
* [Card Intervals](#card-intervals)
|
||||||
|
* [Finding Notes and Cards](#finding-notes-and-cards)
|
||||||
|
* [Media File Storage](#media-file-storage)
|
||||||
|
* [Graphical](#graphical)
|
||||||
|
|
||||||
|
### Miscellaneous ###
|
||||||
|
|
||||||
* **version**
|
* **version**
|
||||||
|
|
||||||
Gets the version of the API exposed by this plugin. Currently versions `1` through `4` are defined.
|
Gets the version of the API exposed by this plugin. Currently versions `1` through `4` are defined.
|
||||||
@ -104,6 +120,24 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
4
|
4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* **upgrade**
|
||||||
|
|
||||||
|
Displays a confirmation dialog box in Anki asking the user if they wish to upgrade AnkiConnect to the latest version
|
||||||
|
from the project's [master branch](https://raw.githubusercontent.com/FooSoft/anki-connect/master/AnkiConnect.py) on
|
||||||
|
GitHub. Returns a boolean value indicating if the plugin was upgraded or not.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "upgrade"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
* **multi**
|
* **multi**
|
||||||
|
|
||||||
Performs multiple actions in one request, returning an array with the response of each action (in the given order).
|
Performs multiple actions in one request, returning an array with the response of each action (in the given order).
|
||||||
@ -132,6 +166,8 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Decks ###
|
||||||
|
|
||||||
* **deckNames**
|
* **deckNames**
|
||||||
|
|
||||||
Gets the complete list of deck names for the current user.
|
Gets the complete list of deck names for the current user.
|
||||||
@ -168,79 +204,17 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* **modelNames**
|
* **getDecks**
|
||||||
|
|
||||||
Gets the complete list of model names for the current user.
|
Accepts an array of card IDs and returns an object with each deck name as a key, and its value an array of the given
|
||||||
|
cards which belong to it.
|
||||||
|
|
||||||
*Sample request*:
|
*Sample request*:
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"action": "modelNames"
|
"action": "getDecks",
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Sample response*:
|
|
||||||
```
|
|
||||||
[
|
|
||||||
"Basic",
|
|
||||||
"Basic (and reversed card)"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
* **modelNamesAndIds**
|
|
||||||
|
|
||||||
Gets the complete list of model names and their corresponding IDs for the current user.
|
|
||||||
|
|
||||||
*Sample request*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"action": "modelNamesAndIds"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Sample response*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"Basic": 1483883011648
|
|
||||||
"Basic (and reversed card)": 1483883011644
|
|
||||||
"Basic (optional reversed card)": 1483883011631
|
|
||||||
"Cloze": 1483883011630
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* **modelFieldNames**
|
|
||||||
|
|
||||||
Gets the complete list of field names for the provided model name.
|
|
||||||
|
|
||||||
*Sample request*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"action": "modelFieldNames",
|
|
||||||
"params": {
|
"params": {
|
||||||
"modelName": "Basic"
|
"cards": [1502298036657, 1502298033753, 1502032366472]
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Sample response*:
|
|
||||||
```
|
|
||||||
[
|
|
||||||
"Front",
|
|
||||||
"Back"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
* **modelFieldsOnTemplates**
|
|
||||||
|
|
||||||
Returns an object indicating the fields on the question and answer side of each card template for the given model
|
|
||||||
name. The question side is given first in each array.
|
|
||||||
|
|
||||||
*Sample request*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"action": "modelFieldsOnTemplates",
|
|
||||||
"params": {
|
|
||||||
"modelName": "Basic (and reversed card)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -248,17 +222,54 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
*Sample response*:
|
*Sample response*:
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"Card 1": [
|
"Default": [1502032366472],
|
||||||
["Front"],
|
"Japanese::JLPT N3": [1502298036657, 1502298033753]
|
||||||
["Back"]
|
|
||||||
],
|
|
||||||
"Card 2": [
|
|
||||||
["Back"],
|
|
||||||
["Front"]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* **changeDeck**
|
||||||
|
|
||||||
|
Moves cards with the given IDs to a different deck, creating the deck if it doesn't exist yet.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "changeDeck",
|
||||||
|
"params": {
|
||||||
|
"cards": [1502098034045, 1502098034048, 1502298033753],
|
||||||
|
"deck": "Japanese::JLPT N3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
null
|
||||||
|
```
|
||||||
|
|
||||||
|
* **deleteDecks**
|
||||||
|
|
||||||
|
Deletes decks with the given names. If `cardsToo` is `true` (defaults to `false` if unspecified), the cards within
|
||||||
|
the deleted decks will also be deleted; otherwise they will be moved to the default deck.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "deleteDecks",
|
||||||
|
"params": {
|
||||||
|
"decks": ["Japanese::JLPT N5", "Easy Spanish"],
|
||||||
|
"cardsToo": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deck Configurations ###
|
||||||
|
|
||||||
* **getDeckConfig**
|
* **getDeckConfig**
|
||||||
|
|
||||||
Gets the config group object for the given deck.
|
Gets the config group object for the given deck.
|
||||||
@ -396,6 +407,101 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
true
|
true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Models ###
|
||||||
|
|
||||||
|
* **modelNames**
|
||||||
|
|
||||||
|
Gets the complete list of model names for the current user.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "modelNames"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
[
|
||||||
|
"Basic",
|
||||||
|
"Basic (and reversed card)"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
* **modelNamesAndIds**
|
||||||
|
|
||||||
|
Gets the complete list of model names and their corresponding IDs for the current user.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "modelNamesAndIds"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"Basic": 1483883011648
|
||||||
|
"Basic (and reversed card)": 1483883011644
|
||||||
|
"Basic (optional reversed card)": 1483883011631
|
||||||
|
"Cloze": 1483883011630
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **modelFieldNames**
|
||||||
|
|
||||||
|
Gets the complete list of field names for the provided model name.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "modelFieldNames",
|
||||||
|
"params": {
|
||||||
|
"modelName": "Basic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
[
|
||||||
|
"Front",
|
||||||
|
"Back"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
* **modelFieldsOnTemplates**
|
||||||
|
|
||||||
|
Returns an object indicating the fields on the question and answer side of each card template for the given model
|
||||||
|
name. The question side is given first in each array.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "modelFieldsOnTemplates",
|
||||||
|
"params": {
|
||||||
|
"modelName": "Basic (and reversed card)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"Card 1": [
|
||||||
|
["Front"],
|
||||||
|
["Back"]
|
||||||
|
],
|
||||||
|
"Card 2": [
|
||||||
|
["Back"],
|
||||||
|
["Front"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Note Creation ###
|
||||||
|
|
||||||
* **addNote**
|
* **addNote**
|
||||||
|
|
||||||
Creates a note using the given deck and model, with the provided field values and tags. Returns the identifier of
|
Creates a note using the given deck and model, with the provided field values and tags. Returns the identifier of
|
||||||
@ -511,6 +617,8 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Note Tags ###
|
||||||
|
|
||||||
* **addTags**
|
* **addTags**
|
||||||
|
|
||||||
Adds tags to notes by note ID.
|
Adds tags to notes by note ID.
|
||||||
@ -551,6 +659,8 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
null
|
null
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Card Suspension ###
|
||||||
|
|
||||||
* **suspend**
|
* **suspend**
|
||||||
|
|
||||||
Suspend cards by card ID; returns `true` if successful (at least one card wasn't already suspended) or `false`
|
Suspend cards by card ID; returns `true` if successful (at least one card wasn't already suspended) or `false`
|
||||||
@ -610,6 +720,8 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
[false, true]
|
[false, true]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Card Intervals ###
|
||||||
|
|
||||||
* **areDue**
|
* **areDue**
|
||||||
|
|
||||||
Returns an array indicating whether each of the given cards is due (in the same order). Note: cards in the learning
|
Returns an array indicating whether each of the given cards is due (in the same order). Note: cards in the learning
|
||||||
@ -670,6 +782,7 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Finding Notes and Cards ###
|
||||||
|
|
||||||
* **findNotes**
|
* **findNotes**
|
||||||
|
|
||||||
@ -717,71 +830,6 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
* **getDecks**
|
|
||||||
|
|
||||||
Accepts an array of card IDs and returns an object with each deck name as a key, and its value an array of the given
|
|
||||||
cards which belong to it.
|
|
||||||
|
|
||||||
*Sample request*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"action": "getDecks",
|
|
||||||
"params": {
|
|
||||||
"cards": [1502298036657, 1502298033753, 1502032366472]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Sample response*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"Default": [1502032366472],
|
|
||||||
"Japanese::JLPT N3": [1502298036657, 1502298033753]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
* **changeDeck**
|
|
||||||
|
|
||||||
Moves cards with the given IDs to a different deck, creating the deck if it doesn't exist yet.
|
|
||||||
|
|
||||||
*Sample request*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"action": "changeDeck",
|
|
||||||
"params": {
|
|
||||||
"cards": [1502098034045, 1502098034048, 1502298033753],
|
|
||||||
"deck": "Japanese::JLPT N3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Sample response*:
|
|
||||||
```
|
|
||||||
null
|
|
||||||
```
|
|
||||||
|
|
||||||
* **deleteDecks**
|
|
||||||
|
|
||||||
Deletes decks with the given names. If `cardsToo` is `true` (defaults to `false` if unspecified), the cards within
|
|
||||||
the deleted decks will also be deleted; otherwise they will be moved to the default deck.
|
|
||||||
|
|
||||||
*Sample request*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"action": "deleteDecks",
|
|
||||||
"params": {
|
|
||||||
"decks": ["Japanese::JLPT N5", "Easy Spanish"],
|
|
||||||
"cardsToo": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Sample response*:
|
|
||||||
```
|
|
||||||
null
|
|
||||||
```
|
|
||||||
|
|
||||||
* **cardsToNotes**
|
* **cardsToNotes**
|
||||||
|
|
||||||
Returns an (unordered) array of note IDs for the given card IDs. For cards with the same note, the ID is only
|
Returns an (unordered) array of note IDs for the given card IDs. For cards with the same note, the ID is only
|
||||||
@ -805,6 +853,76 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Media File Storage ###
|
||||||
|
|
||||||
|
* **storeMediaFile**
|
||||||
|
|
||||||
|
Stores a file with the specified base64-encoded contents inside the media folder.
|
||||||
|
|
||||||
|
Note: to prevent Anki from removing files not used by any cards (e.g. for configuration files), prefix the filename
|
||||||
|
with an underscore. These files are still synchronized to AnkiWeb.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "storeMediaFile",
|
||||||
|
"params": {
|
||||||
|
"filename": "_hello.txt",
|
||||||
|
"data": "SGVsbG8sIHdvcmxkIQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
null
|
||||||
|
```
|
||||||
|
|
||||||
|
*Content of `_hello.txt`*:
|
||||||
|
```
|
||||||
|
Hello world!
|
||||||
|
```
|
||||||
|
|
||||||
|
* **retrieveMediaFile**
|
||||||
|
|
||||||
|
Retrieves the base64-encoded contents of the specified file, returning `false` if the file does not exist.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "retrieveMediaFile",
|
||||||
|
"params": {
|
||||||
|
"filename": "_hello.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
"SGVsbG8sIHdvcmxkIQ=="
|
||||||
|
```
|
||||||
|
|
||||||
|
* **deleteMediaFile**
|
||||||
|
|
||||||
|
Deletes the specified file inside the media folder.
|
||||||
|
|
||||||
|
*Sample request*:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"action": "deleteMediaFile",
|
||||||
|
"params": {
|
||||||
|
"filename": "_hello.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Sample response*:
|
||||||
|
```
|
||||||
|
null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Graphical ###
|
||||||
|
|
||||||
* **guiBrowse**
|
* **guiBrowse**
|
||||||
|
|
||||||
Invokes the card browser and searches for a given query. Returns an array of identifiers of the cards that were found.
|
Invokes the card browser and searches for a given query. Returns an array of identifiers of the cards that were found.
|
||||||
@ -1000,24 +1118,6 @@ Below is a list of currently supported actions. Requests with invalid actions or
|
|||||||
true
|
true
|
||||||
```
|
```
|
||||||
|
|
||||||
* **upgrade**
|
|
||||||
|
|
||||||
Displays a confirmation dialog box in Anki asking the user if they wish to upgrade AnkiConnect to the latest version
|
|
||||||
from the project's [master branch](https://raw.githubusercontent.com/FooSoft/anki-connect/master/AnkiConnect.py) on
|
|
||||||
GitHub. Returns a boolean value indicating if the plugin was upgraded or not.
|
|
||||||
|
|
||||||
*Sample request*:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"action": "upgrade"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Sample response*:
|
|
||||||
```
|
|
||||||
true
|
|
||||||
```
|
|
||||||
|
|
||||||
## License ##
|
## License ##
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
Loading…
Reference in New Issue
Block a user