diff --git a/AnkiConnect.py b/AnkiConnect.py index d31859c..8dececb 100644 --- a/AnkiConnect.py +++ b/AnkiConnect.py @@ -199,6 +199,26 @@ class AjaxServer: self.handler = handler self.clients = [] self.sock = None + self.resetHeaders() + + + def setHeader(self, name, value): + self.extraHeaders[name] = value + + + def resetHeaders(self): + self.headers = [ + ['HTTP/1.1 200 OK', None], + ['Content-Type', 'text/json'] + ] + self.extraHeaders = {} + + + def getHeaders(self): + headers = self.headers[:] + for name in self.extraHeaders: + headers.append([name, self.extraHeaders[name]]) + return headers def advance(self): @@ -243,11 +263,9 @@ class AjaxServer: body = json.dumps(None); resp = bytes() - headers = [ - ['HTTP/1.1 200 OK', None], - ['Content-Type', 'text/json'], - ['Content-Length', str(len(body))] - ] + + self.setHeader('Content-Length', str(len(body))) + headers = self.getHeaders() for key, value in headers: if value is None: @@ -474,6 +492,13 @@ class AnkiBridge: return self.collection().sched + def multi(self, actions): + response = [] + for item in actions: + response.append(AnkiConnect.handler(ac, item)) + return response + + def media(self): collection = self.collection() if collection is not None: @@ -502,11 +527,59 @@ class AnkiBridge: return [field['name'] for field in model['flds']] - def multi(self, actions): - response = [] - for item in actions: - response.append(AnkiConnect.handler(ac, item)) - return response + def getDeckConfig(self, deck): + if not deck in self.deckNames(): + return False + + did = self.collection().decks.id(deck) + return self.collection().decks.confForDid(did) + + + def saveDeckConfig(self, config): + configId = str(config['id']) + if not configId in self.collection().decks.dconf: + return False + + mod = anki.utils.intTime() + usn = self.collection().usn() + + config['mod'] = mod + config['usn'] = usn + + self.collection().decks.dconf[configId] = config + self.collection().decks.changed = True + return True + + + def setDeckConfigId(self, decks, configId): + for deck in decks: + if not deck in self.deckNames(): + return False + + if not str(configId) in self.collection().decks.dconf: + return False + + for deck in decks: + did = str(self.collection().decks.id(deck)) + aqt.mw.col.decks.decks[did]['conf'] = configId + + return True + + + def cloneDeckConfigId(self, name, cloneFrom=1): + if not str(cloneFrom) in self.collection().decks.dconf: + return False + + cloneFrom = self.collection().decks.getConf(cloneFrom) + return self.collection().decks.confId(name, cloneFrom) + + + def removeDeckConfigId(self, configId): + if configId == 1 or not str(configId) in self.collection().decks.dconf: + return False + + self.collection().decks.remConf(configId) + return True def deckNames(self): @@ -520,8 +593,8 @@ class AnkiBridge: deckNames = self.deckNames() for deck in deckNames: - id = self.collection().decks.id(deck) - decks[deck] = id + did = self.collection().decks.id(deck) + decks[deck] = did return decks @@ -582,8 +655,8 @@ class AnkiBridge: def deleteDecks(self, decks, cardsToo=False): self.startEditing() for deck in decks: - id = self.collection().decks.id(deck) - self.collection().decks.rem(id, cardsToo) + did = self.collection().decks.id(deck) + self.collection().decks.rem(did, cardsToo) self.stopEditing() @@ -636,7 +709,7 @@ class AnkiBridge: 'fieldOrder': card.ord, 'question': card._getQA()['q'], 'answer': card._getQA()['a'], - 'buttons': map(lambda b: b[0], reviewer._answerButtonList()), + 'buttons': [b[0] for b in reviewer._answerButtonList()], 'modelName': model['name'], 'deckName': self.deckNameFromId(card.did) } @@ -759,6 +832,11 @@ class AnkiConnect: return handler(**params) + @webApi + def multi(self, actions): + return self.anki.multi(actions) + + @webApi def deckNames(self): return self.anki.deckNames() @@ -780,8 +858,28 @@ class AnkiConnect: @webApi - def multi(self, actions): - return self.anki.multi(actions) + def getDeckConfig(self, deck): + return self.anki.getDeckConfig(deck) + + + @webApi + def saveDeckConfig(self, config): + return self.anki.saveDeckConfig(config) + + + @webApi + def setDeckConfigId(self, decks, configId): + return self.anki.setDeckConfigId(decks, configId) + + + @webApi + def cloneDeckConfigId(self, name, cloneFrom=1): + return self.anki.cloneDeckConfigId(name, cloneFrom) + + + @webApi + def removeDeckConfigId(self, configId): + return self.anki.removeDeckConfigId(configId) @webApi diff --git a/README.md b/README.md index 980f4fe..4c388d4 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,35 @@ Below is a list of currently supported actions. Requests with invalid actions or ``` 4 ``` + +* **multi** + + Performs multiple actions in one request, returning an array with the response of each action (in the given order). + + *Sample request*: + ``` + { + "action": "multi", + "params": { + "actions": [ + {"action": "deckNames"}, + { + "action": "browse", + "params": {"query": "deck:current"} + } + ] + } + } + ``` + + *Sample response*: + ``` + [ + ["Default"], + [1494723142483, 1494703460437, 1494703479525] + ] + ``` + * **deckNames** Gets the complete list of deck names for the current user. @@ -162,32 +191,141 @@ Below is a list of currently supported actions. Requests with invalid actions or ] ``` -* **multi** +* **getDeckConfig** - Performs multiple actions in one request, returning an array with the response of each action (in the given order). + Gets the config group object for the given deck. *Sample request*: ``` { - "action": "multi", + "action": "getDeckConfig", "params": { - "actions": [ - {"action": "deckNames"}, - { - "action": "browse", - "params": {"query": "deck:current"} - } - ] + "deck": "Default" } } ``` *Sample response*: ``` - [ - ["Default"], - [1494723142483, 1494703460437, 1494703479525] - ] + { + "lapse": { + "leechFails": 8, + "delays": [10], + "minInt": 1, + "leechAction": 0, + "mult": 0 + }, + "dyn": false, + "autoplay": true, + "mod": 1502970872, + "id": 1, + "maxTaken": 60, + "new": { + "bury": true, + "order": 1, + "initialFactor": 2500, + "perDay": 20, + "delays": [1, 10], + "separate": true, + "ints": [1, 4, 7] + }, + "name": "Default", + "rev": { + "bury": true, + "ivlFct": 1, + "ease4": 1.3, + "maxIvl": 36500, + "perDay": 100, + "minSpace": 1, + "fuzz": 0.05 + }, + "timer": 0, + "replayq": true, + "usn": -1 + } + ``` + +* **saveDeckConfig** + + Saves the given config group, returning `true` on success or `false` if the ID of the config group is invalid (i.e. + it does not exist). + + *Sample request*: + ``` + { + "action": "saveDeckConfig", + "params": { + "config": (config group object) + } + } + ``` + + *Sample response*: + ``` + true + ``` + +* **setDeckConfigId** + + Changes the configuration group for the given decks to the one with the given ID. Returns `true` on success or + `false` if the given configuration group or any of the given decks do not exist. + + *Sample request*: + ``` + { + "action": "setDeckConfigId", + "params": { + "decks": ["Default"], + "configId": 1 + } + } + ``` + + *Sample response*: + ``` + true + ``` + +* **cloneDeckConfigId** + + Creates a new config group with the given name, cloning from the group with the given ID, or from the default group + if this is unspecified. Returns the ID of the new config group, or `false` if the specified group to clone from does + not exist. + + *Sample request*: + ``` + { + "action": "cloneDeckConfigId", + "params": { + "name": "Copy of Default", + "cloneFrom": 1 + } + } + ``` + + *Sample response*: + ``` + 1502972374573 + ``` + +* **removeDeckConfigId** + + Removes the config group with the given ID, returning `true` if successful, or `false` if attempting to remove + either the default config group (ID = 1) or a config group that does not exist. + + *Sample request*: + ``` + { + "action": "removeDeckConfigId", + "params": { + "configId": 1502972374573 + } + } + ``` + + *Sample response*: + ``` + true ``` * **addNote**