Merge pull request #39 from techdavid/storage-api

Add storage actions
This commit is contained in:
Alex Yatskov 2017-08-22 13:13:03 -07:00 committed by GitHub
commit 17a07b2871
2 changed files with 298 additions and 158 deletions

View File

@ -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()

388
README.md
View File

@ -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,97 +204,72 @@ 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*: *Sample response*:
``` ```
[ {
"Front", "Default": [1502032366472],
"Back" "Japanese::JLPT N3": [1502298036657, 1502298033753]
] }
``` ```
* **modelFieldsOnTemplates** * **changeDeck**
Returns an object indicating the fields on the question and answer side of each card template for the given model Moves cards with the given IDs to a different deck, creating the deck if it doesn't exist yet.
name. The question side is given first in each array.
*Sample request*: *Sample request*:
``` ```
{ {
"action": "modelFieldsOnTemplates", "action": "changeDeck",
"params": { "params": {
"modelName": "Basic (and reversed card)" "cards": [1502098034045, 1502098034048, 1502298033753],
"deck": "Japanese::JLPT N3"
} }
} }
``` ```
*Sample response*: *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*:
```
{ {
"Card 1": [ "action": "deleteDecks",
["Front"], "params": {
["Back"] "decks": ["Japanese::JLPT N5", "Easy Spanish"],
], "cardsToo": true
"Card 2": [ }
["Back"],
["Front"]
]
} }
``` ```
*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