work in progress on cleaning up api
This commit is contained in:
parent
4173f4e5fe
commit
e80bf2e8aa
536
AnkiConnect.py
536
AnkiConnect.py
@ -47,7 +47,7 @@ URL_UPGRADE = 'https://raw.githubusercontent.com/FooSoft/anki-connect/master/A
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# General helpers
|
# Helpers
|
||||||
#
|
#
|
||||||
|
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
@ -66,19 +66,6 @@ else:
|
|||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Helpers
|
|
||||||
#
|
|
||||||
|
|
||||||
def webApi(*versions):
|
|
||||||
def decorator(func):
|
|
||||||
method = lambda *args, **kwargs: func(*args, **kwargs)
|
|
||||||
setattr(method, 'versions', versions)
|
|
||||||
setattr(method, 'api', True)
|
|
||||||
return method
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def makeBytes(data):
|
def makeBytes(data):
|
||||||
return data.encode('utf-8')
|
return data.encode('utf-8')
|
||||||
|
|
||||||
@ -87,35 +74,14 @@ def makeStr(data):
|
|||||||
return data.decode('utf-8')
|
return data.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def download(url):
|
def api(*versions):
|
||||||
try:
|
def decorator(func):
|
||||||
resp = web.urlopen(url, timeout=URL_TIMEOUT)
|
method = lambda *args, **kwargs: func(*args, **kwargs)
|
||||||
except web.URLError as e:
|
setattr(method, 'versions', versions)
|
||||||
raise Exception('A urlError has occurred for url ' + url + '. Error messages was: ' + e.message)
|
setattr(method, 'api', True)
|
||||||
|
return method
|
||||||
|
|
||||||
if resp.code != 200:
|
return decorator
|
||||||
raise Exception('Return code for url request' + url + 'was not 200. Error code: ' + resp.code)
|
|
||||||
|
|
||||||
return resp.read()
|
|
||||||
|
|
||||||
|
|
||||||
def audioInject(note, fields, filename):
|
|
||||||
for field in fields:
|
|
||||||
if field in note:
|
|
||||||
note[field] += u'[sound:{}]'.format(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def verifyString(string):
|
|
||||||
t = type(string)
|
|
||||||
return t == str or t == unicode
|
|
||||||
|
|
||||||
|
|
||||||
def verifyStringList(strings):
|
|
||||||
for s in strings:
|
|
||||||
if not verifyString(s):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -212,7 +178,7 @@ class WebServer:
|
|||||||
|
|
||||||
|
|
||||||
def setHeader(self, name, value):
|
def setHeader(self, name, value):
|
||||||
self.extraHeaders[name] = value
|
self.headersOpt[name] = value
|
||||||
|
|
||||||
|
|
||||||
def resetHeaders(self):
|
def resetHeaders(self):
|
||||||
@ -221,13 +187,14 @@ class WebServer:
|
|||||||
['Content-Type', 'text/json'],
|
['Content-Type', 'text/json'],
|
||||||
['Access-Control-Allow-Origin', '*']
|
['Access-Control-Allow-Origin', '*']
|
||||||
]
|
]
|
||||||
self.extraHeaders = {}
|
self.headersOpt = {}
|
||||||
|
|
||||||
|
|
||||||
def getHeaders(self):
|
def getHeaders(self):
|
||||||
headers = self.headers[:]
|
headers = self.headers[:]
|
||||||
for name in self.extraHeaders:
|
for name in self.headersOpt:
|
||||||
headers.append([name, self.extraHeaders[name]])
|
headers.append([name, self.headersOpt[name]])
|
||||||
|
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
@ -301,49 +268,7 @@ class WebServer:
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# AnkiNoteParams
|
# AnkiConnect
|
||||||
#
|
|
||||||
|
|
||||||
class AnkiNoteParams:
|
|
||||||
def __init__(self, params):
|
|
||||||
self.deckName = params.get('deckName')
|
|
||||||
self.modelName = params.get('modelName')
|
|
||||||
self.fields = params.get('fields', {})
|
|
||||||
self.tags = params.get('tags', [])
|
|
||||||
|
|
||||||
class Audio:
|
|
||||||
def __init__(self, params):
|
|
||||||
self.url = params.get('url')
|
|
||||||
self.filename = params.get('filename')
|
|
||||||
self.skipHash = params.get('skipHash')
|
|
||||||
self.fields = params.get('fields', [])
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
return (
|
|
||||||
verifyString(self.url) and
|
|
||||||
verifyString(self.filename) and os.path.dirname(self.filename) == '' and
|
|
||||||
verifyStringList(self.fields) and
|
|
||||||
(verifyString(self.skipHash) or self.skipHash is None)
|
|
||||||
)
|
|
||||||
|
|
||||||
audio = Audio(params.get('audio', {}))
|
|
||||||
self.audio = audio if audio.validate() else None
|
|
||||||
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
return (
|
|
||||||
verifyString(self.deckName) and
|
|
||||||
verifyString(self.modelName) and
|
|
||||||
type(self.fields) == dict and verifyStringList(list(self.fields.keys())) and verifyStringList(list(self.fields.values())) and
|
|
||||||
type(self.tags) == list and verifyStringList(self.tags)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return 'DeckName: ' + self.deckName + '. ModelName: ' + self.modelName + '. Fields: ' + str(self.fields) + '. Tags: ' + str(self.tags) + '.'
|
|
||||||
|
|
||||||
#
|
|
||||||
# AnkiBridge
|
|
||||||
#
|
#
|
||||||
|
|
||||||
class AnkiConnect:
|
class AnkiConnect:
|
||||||
@ -407,6 +332,50 @@ class AnkiConnect:
|
|||||||
return reply['result']
|
return reply['result']
|
||||||
|
|
||||||
|
|
||||||
|
def download(self, url):
|
||||||
|
resp = web.urlopen(url, timeout=URL_TIMEOUT)
|
||||||
|
if resp.code == 200:
|
||||||
|
return resp.read()
|
||||||
|
else:
|
||||||
|
raise Exception('return code for download of {} was {}'.format(url, resp.code))
|
||||||
|
|
||||||
|
|
||||||
|
def window(self):
|
||||||
|
return aqt.mw
|
||||||
|
|
||||||
|
|
||||||
|
def reviewer(self):
|
||||||
|
reviewer = self.window().reviewer
|
||||||
|
if reviewer is None:
|
||||||
|
raise Exception('reviewer is not available')
|
||||||
|
else:
|
||||||
|
return reviewer
|
||||||
|
|
||||||
|
|
||||||
|
def collection(self):
|
||||||
|
collection = self.window().col
|
||||||
|
if collection is None:
|
||||||
|
raise Exception('collection is not available')
|
||||||
|
else:
|
||||||
|
return collection
|
||||||
|
|
||||||
|
|
||||||
|
def scheduler(self):
|
||||||
|
scheduler = self.collection().sched
|
||||||
|
if scheduler is None:
|
||||||
|
raise Exception('scheduler is not available')
|
||||||
|
else:
|
||||||
|
return scheduler
|
||||||
|
|
||||||
|
|
||||||
|
def media(self):
|
||||||
|
media = self.collection().media
|
||||||
|
if media is None:
|
||||||
|
raise Exception('media is not available')
|
||||||
|
else:
|
||||||
|
return media
|
||||||
|
|
||||||
|
|
||||||
def startEditing(self):
|
def startEditing(self):
|
||||||
self.window().requireReset()
|
self.window().requireReset()
|
||||||
|
|
||||||
@ -416,68 +385,44 @@ class AnkiConnect:
|
|||||||
self.window().maybeReset()
|
self.window().maybeReset()
|
||||||
|
|
||||||
|
|
||||||
def window(self):
|
def createNote(self, note):
|
||||||
return aqt.mw
|
|
||||||
|
|
||||||
|
|
||||||
def reviewer(self):
|
|
||||||
return self.window().reviewer
|
|
||||||
|
|
||||||
|
|
||||||
def collection(self):
|
|
||||||
return self.window().col
|
|
||||||
|
|
||||||
|
|
||||||
def scheduler(self):
|
|
||||||
return self.collection().sched
|
|
||||||
|
|
||||||
|
|
||||||
def media(self):
|
|
||||||
collection = self.collection()
|
collection = self.collection()
|
||||||
if collection is not None:
|
|
||||||
return collection.media
|
|
||||||
|
|
||||||
|
model = collection.models.byName(note['modelName'])
|
||||||
def createNote(self, params):
|
|
||||||
collection = self.collection()
|
|
||||||
if collection is None:
|
|
||||||
raise Exception('Collection was not found.')
|
|
||||||
|
|
||||||
model = collection.models.byName(params.modelName)
|
|
||||||
if model is None:
|
if model is None:
|
||||||
raise Exception('Model was not found for model: ' + params.modelName)
|
raise Exception('model was not found: {}'.format(note['modelName']))
|
||||||
|
|
||||||
deck = collection.decks.byName(params.deckName)
|
deck = collection.decks.byName(note['deckName'])
|
||||||
if deck is None:
|
if deck is None:
|
||||||
raise Exception('Deck was not found for deck: ' + params.deckName)
|
raise Exception('deck was not found: {}'.format(note['deckName']))
|
||||||
|
|
||||||
note = anki.notes.Note(collection, model)
|
ankiNote = anki.notes.Note(collection, model)
|
||||||
note.model()['did'] = deck['id']
|
ankiNote.model()['did'] = deck['id']
|
||||||
note.tags = params.tags
|
ankiNote.tags = note['tags']
|
||||||
|
|
||||||
for name, value in params.fields.items():
|
for name, value in note['fields'].items():
|
||||||
if name in note:
|
if name in ankiNote:
|
||||||
note[name] = value
|
ankiNote[name] = value
|
||||||
|
|
||||||
# Returns 1 if empty. 2 if duplicate. Otherwise returns False
|
duplicateOrEmpty = ankiNote.dupeOrEmpty()
|
||||||
duplicateOrEmpty = note.dupeOrEmpty()
|
|
||||||
if duplicateOrEmpty == 1:
|
if duplicateOrEmpty == 1:
|
||||||
raise Exception('Note was empty. Param were: ' + str(params))
|
raise Exception('cannot create note because it is empty')
|
||||||
elif duplicateOrEmpty == 2:
|
elif duplicateOrEmpty == 2:
|
||||||
raise Exception('Note is duplicate of existing note. Params were: ' + str(params))
|
raise Exception('cannot create note because it is a duplicte')
|
||||||
elif duplicateOrEmpty == False:
|
elif duplicateOrEmpty == False:
|
||||||
return note
|
return ankiNote
|
||||||
|
else:
|
||||||
|
raise Exception('cannot create note for unknown reason')
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def storeMediaFile(self, filename, data):
|
def storeMediaFile(self, filename, data):
|
||||||
self.deleteMediaFile(filename)
|
self.deleteMediaFile(filename)
|
||||||
self.media().writeData(filename, base64.b64decode(data))
|
self.media().writeData(filename, base64.b64decode(data))
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def retrieveMediaFile(self, filename):
|
def retrieveMediaFile(self, filename):
|
||||||
# based on writeData from anki/media.py
|
|
||||||
filename = os.path.basename(filename)
|
filename = os.path.basename(filename)
|
||||||
filename = normalize('NFC', filename)
|
filename = normalize('NFC', filename)
|
||||||
filename = self.media().stripIllegal(filename)
|
filename = self.media().stripIllegal(filename)
|
||||||
@ -490,224 +435,201 @@ class AnkiConnect:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def deleteMediaFile(self, filename):
|
def deleteMediaFile(self, filename):
|
||||||
self.media().syncDelete(filename)
|
self.media().syncDelete(filename)
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def addNote(self, note):
|
def addNote(self, note):
|
||||||
params = AnkiNoteParams(note)
|
ankiNote = self.createNote(note)
|
||||||
if not params.validate():
|
|
||||||
raise Exception('Invalid note parameters')
|
|
||||||
|
|
||||||
collection = self.collection()
|
if note['audio'] is not None and len(note['audio']['fields']) > 0:
|
||||||
if collection is None:
|
audio = note['audio']
|
||||||
raise Exception('Collection was not found.')
|
data = download(audio['url'])
|
||||||
|
|
||||||
note = self.createNote(params)
|
|
||||||
if note is None:
|
|
||||||
raise Exception('Failed to create note from params: ' + str(params))
|
|
||||||
|
|
||||||
if params.audio is not None and len(params.audio.fields) > 0:
|
|
||||||
data = download(params.audio.url)
|
|
||||||
if data is not None:
|
if data is not None:
|
||||||
if params.audio.skipHash is None:
|
if audio['skipHash'] is None:
|
||||||
skip = False
|
skip = False
|
||||||
else:
|
else:
|
||||||
m = hashlib.md5()
|
m = hashlib.md5()
|
||||||
m.update(data)
|
m.update(data)
|
||||||
skip = params.audio.skipHash == m.hexdigest()
|
skip = audio['skipHash'] == m.hexdigest()
|
||||||
|
|
||||||
if not skip:
|
if not skip:
|
||||||
audioInject(note, params.audio.fields, params.audio.filename)
|
for field in audio['fields']:
|
||||||
self.media().writeData(params.audio.filename, data)
|
if field in ankiNote:
|
||||||
|
ankiNote[field] += u'[sound:{}]'.format(audio['filename'])
|
||||||
|
|
||||||
|
self.media().writeData(audio['filename'], data)
|
||||||
|
|
||||||
|
collection = self.collection()
|
||||||
self.startEditing()
|
self.startEditing()
|
||||||
collection.addNote(note)
|
collection.addNote(ankiNote)
|
||||||
collection.autosave()
|
collection.autosave()
|
||||||
self.stopEditing()
|
self.stopEditing()
|
||||||
|
|
||||||
return note.id
|
return ankiNote.id
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def canAddNote(self, note):
|
def canAddNote(self, note):
|
||||||
params = AnkiNoteParams(note)
|
|
||||||
if not params.validate():
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return bool(self.createNote(params))
|
return bool(self.createNote(note))
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def updateNoteFields(self, params):
|
def updateNoteFields(self, params):
|
||||||
collection = self.collection()
|
note = self.collection().getNote(params['id'])
|
||||||
if collection is None:
|
|
||||||
raise Exception('Collection was not found.')
|
|
||||||
|
|
||||||
note = collection.getNote(params['id'])
|
|
||||||
if note is None:
|
if note is None:
|
||||||
raise Exception('Failed to get note:{}'.format(params['id']))
|
raise Exception('note was not found: {}'.format(params['id']))
|
||||||
|
|
||||||
for name, value in params['fields'].items():
|
for name, value in params['fields'].items():
|
||||||
if name in note:
|
if name in note:
|
||||||
note[name] = value
|
note[name] = value
|
||||||
|
|
||||||
note.flush()
|
note.flush()
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def addTags(self, notes, tags, add=True):
|
def addTags(self, notes, tags, add=True):
|
||||||
self.startEditing()
|
self.startEditing()
|
||||||
self.collection().tags.bulkAdd(notes, tags, add)
|
self.collection().tags.bulkAdd(notes, tags, add)
|
||||||
self.stopEditing()
|
self.stopEditing()
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def removeTags(self, notes, tags):
|
def removeTags(self, notes, tags):
|
||||||
return self.addTags(notes, tags, False)
|
return self.addTags(notes, tags, False)
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def getTags(self):
|
def getTags(self):
|
||||||
return self.collection().tags.all()
|
return self.collection().tags.all()
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def suspend(self, cards, suspend=True):
|
def suspend(self, cards, suspend=True):
|
||||||
for card in cards:
|
for card in cards:
|
||||||
isSuspended = self.isSuspended(card)
|
if self.suspended(card) == suspend:
|
||||||
if suspend and isSuspended:
|
|
||||||
cards.remove(card)
|
|
||||||
elif not suspend and not isSuspended:
|
|
||||||
cards.remove(card)
|
cards.remove(card)
|
||||||
|
|
||||||
if cards:
|
if len(cards) == 0:
|
||||||
self.startEditing()
|
|
||||||
if suspend:
|
|
||||||
self.collection().sched.suspendCards(cards)
|
|
||||||
else:
|
|
||||||
self.collection().sched.unsuspendCards(cards)
|
|
||||||
self.stopEditing()
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
scheduler = self.scheduler()
|
||||||
|
self.startEditing()
|
||||||
|
if suspend:
|
||||||
|
scheduler.suspendCards(cards)
|
||||||
|
else:
|
||||||
|
scheduler.unsuspendCards(cards)
|
||||||
|
self.stopEditing()
|
||||||
|
|
||||||
@webApi()
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@api()
|
||||||
def unsuspend(self, cards):
|
def unsuspend(self, cards):
|
||||||
self.suspend(cards, False)
|
self.suspend(cards, False)
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def isSuspended(self, card):
|
def suspended(self, card):
|
||||||
card = self.collection().getCard(card)
|
card = self.collection().getCard(card)
|
||||||
return card.queue == -1
|
return card.queue == -1
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def areSuspended(self, cards):
|
def areSuspended(self, cards):
|
||||||
suspended = []
|
suspended = []
|
||||||
for card in cards:
|
for card in cards:
|
||||||
suspended.append(self.isSuspended(card))
|
suspended.append(self.suspended(card))
|
||||||
|
|
||||||
return suspended
|
return suspended
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def areDue(self, cards):
|
def areDue(self, cards):
|
||||||
due = []
|
due = []
|
||||||
for card in cards:
|
for card in cards:
|
||||||
if self.findCards('cid:%s is:new' % card):
|
if self.findCards('cid:{} is:new'.format(card)):
|
||||||
due.append(True)
|
due.append(True)
|
||||||
continue
|
else:
|
||||||
|
|
||||||
date, ivl = self.collection().db.all('select id/1000.0, ivl from revlog where cid = ?', card)[-1]
|
date, ivl = self.collection().db.all('select id/1000.0, ivl from revlog where cid = ?', card)[-1]
|
||||||
if (ivl >= -1200):
|
if ivl >= -1200:
|
||||||
if self.findCards('cid:%s is:due' % card):
|
duo.append(bool(self.findCards('cid:{} is:due'.format(card))))
|
||||||
due.append(True)
|
|
||||||
else:
|
else:
|
||||||
due.append(False)
|
due.append(date - ivl <= time())
|
||||||
else:
|
|
||||||
if date - ivl <= time():
|
|
||||||
due.append(True)
|
|
||||||
else:
|
|
||||||
due.append(False)
|
|
||||||
|
|
||||||
return due
|
return due
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def getIntervals(self, cards, complete=False):
|
def getIntervals(self, cards, complete=False):
|
||||||
intervals = []
|
intervals = []
|
||||||
for card in cards:
|
for card in cards:
|
||||||
if self.findCards('cid:%s is:new' % card):
|
if self.findCards('cid:{} is:new'.format(card)):
|
||||||
intervals.append(0)
|
intervals.append(0)
|
||||||
continue
|
else:
|
||||||
|
|
||||||
interval = self.collection().db.list('select ivl from revlog where cid = ?', card)
|
interval = self.collection().db.list('select ivl from revlog where cid = ?', card)
|
||||||
if not complete:
|
if not complete:
|
||||||
interval = interval[-1]
|
interval = interval[-1]
|
||||||
intervals.append(interval)
|
intervals.append(interval)
|
||||||
|
|
||||||
return intervals
|
return intervals
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def multi(self, actions):
|
def multi(self, actions):
|
||||||
response = []
|
response = []
|
||||||
for item in actions:
|
for item in actions:
|
||||||
response.append(self.handler(item))
|
response.append(self.handler(item))
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def modelNames(self):
|
def modelNames(self):
|
||||||
collection = self.collection()
|
return self.collection().models.allNames()
|
||||||
if collection is not None:
|
|
||||||
return collection.models.allNames()
|
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def modelNamesAndIds(self):
|
def modelNamesAndIds(self):
|
||||||
models = {}
|
models = {}
|
||||||
|
for model in self.modelNames():
|
||||||
modelNames = self.modelNames()
|
models[model] = int(self.collection().models.byName(model)['id'])
|
||||||
for model in modelNames:
|
|
||||||
mid = self.collection().models.byName(model)['id']
|
|
||||||
mid = int(mid) # sometimes Anki stores the ID as a string
|
|
||||||
models[model] = mid
|
|
||||||
|
|
||||||
return models
|
return models
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def modelNameFromId(self, modelId):
|
def modelNameFromId(self, modelId):
|
||||||
collection = self.collection()
|
model = self.collection().models.get(modelId)
|
||||||
if collection is not None:
|
if model is None:
|
||||||
model = collection.models.get(modelId)
|
raise Exception('model was not found: {}'.format(modelId))
|
||||||
if model is not None:
|
else:
|
||||||
return model['name']
|
return model['name']
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def modelFieldNames(self, modelName):
|
def modelFieldNames(self, modelName):
|
||||||
collection = self.collection()
|
model = self.collection().models.byName(modelName)
|
||||||
if collection is not None:
|
if model is None:
|
||||||
model = collection.models.byName(modelName)
|
raise Exception('model was not found: {}'.format(modelName))
|
||||||
if model is not None:
|
else:
|
||||||
return [field['name'] for field in model['flds']]
|
return [field['name'] for field in model['flds']]
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def modelFieldsOnTemplates(self, modelName):
|
def modelFieldsOnTemplates(self, modelName):
|
||||||
model = self.collection().models.byName(modelName)
|
model = self.collection().models.byName(modelName)
|
||||||
|
if model is None:
|
||||||
|
raise Exception('model was not found: {}'.format(modelName))
|
||||||
|
|
||||||
if model is not None:
|
|
||||||
templates = {}
|
templates = {}
|
||||||
for template in model['tmpls']:
|
for template in model['tmpls']:
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
for side in ['qfmt', 'afmt']:
|
for side in ['qfmt', 'afmt']:
|
||||||
fieldsForSide = []
|
fieldsForSide = []
|
||||||
|
|
||||||
@ -723,7 +645,6 @@ class AnkiConnect:
|
|||||||
continue
|
continue
|
||||||
fieldsForSide.append(match)
|
fieldsForSide.append(match)
|
||||||
|
|
||||||
|
|
||||||
fields.append(fieldsForSide)
|
fields.append(fieldsForSide)
|
||||||
|
|
||||||
templates[template['name']] = fields
|
templates[template['name']] = fields
|
||||||
@ -731,111 +652,111 @@ class AnkiConnect:
|
|||||||
return templates
|
return templates
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def getDeckConfig(self, deck):
|
def getDeckConfig(self, deck):
|
||||||
if not deck in self.deckNames():
|
if not deck in self.deckNames():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
did = self.collection().decks.id(deck)
|
collection = self.collection()
|
||||||
return self.collection().decks.confForDid(did)
|
did = collection.decks.id(deck)
|
||||||
|
return collection.decks.confForDid(did)
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def saveDeckConfig(self, config):
|
def saveDeckConfig(self, config):
|
||||||
configId = str(config['id'])
|
collection = self.collection()
|
||||||
if not configId in self.collection().decks.dconf:
|
|
||||||
|
config['id'] = str(config['id'])
|
||||||
|
config['mod'] = anki.utils.intTime()
|
||||||
|
config['usn'] = collection.usn()
|
||||||
|
|
||||||
|
if not config['id'] in collection.decks.dconf:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
mod = anki.utils.intTime()
|
collection.decks.dconf[config['id']] = config
|
||||||
usn = self.collection().usn()
|
collection.decks.changed = True
|
||||||
|
|
||||||
config['mod'] = mod
|
|
||||||
config['usn'] = usn
|
|
||||||
|
|
||||||
self.collection().decks.dconf[configId] = config
|
|
||||||
self.collection().decks.changed = True
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def setDeckConfigId(self, decks, configId):
|
def setDeckConfigId(self, decks, configId):
|
||||||
|
configId = str(configId)
|
||||||
for deck in decks:
|
for deck in decks:
|
||||||
if not deck in self.deckNames():
|
if not deck in self.deckNames():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not str(configId) in self.collection().decks.dconf:
|
collection = self.collection()
|
||||||
|
if not configId in collection.decks.dconf:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for deck in decks:
|
for deck in decks:
|
||||||
did = str(self.collection().decks.id(deck))
|
did = str(collection.decks.id(deck))
|
||||||
aqt.mw.col.decks.decks[did]['conf'] = configId
|
aqt.mw.col.decks.decks[did]['conf'] = configId
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def cloneDeckConfigId(self, name, cloneFrom=1):
|
def cloneDeckConfigId(self, name, cloneFrom='1'):
|
||||||
if not str(cloneFrom) in self.collection().decks.dconf:
|
configId = str(cloneFrom)
|
||||||
|
if not configId in self.collection().decks.dconf:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
cloneFrom = self.collection().decks.getConf(cloneFrom)
|
config = self.collection().decks.getConf(configId)
|
||||||
return self.collection().decks.confId(name, cloneFrom)
|
return self.collection().decks.confId(name, config)
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def removeDeckConfigId(self, configId):
|
def removeDeckConfigId(self, configId):
|
||||||
if configId == 1 or not str(configId) in self.collection().decks.dconf:
|
configId = str(configId)
|
||||||
|
collection = self.collection()
|
||||||
|
if configId == 1 or not configId in collection.decks.dconf:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.collection().decks.remConf(configId)
|
collection.decks.remConf(configId)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def deckNames(self):
|
def deckNames(self):
|
||||||
collection = self.collection()
|
return self.collection().decks.allNames()
|
||||||
if collection is not None:
|
|
||||||
return collection.decks.allNames()
|
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def deckNamesAndIds(self):
|
def deckNamesAndIds(self):
|
||||||
decks = {}
|
decks = {}
|
||||||
|
for deck in self.deckNames():
|
||||||
deckNames = self.deckNames()
|
decks[deck] = self.collection().decks.id(deck)
|
||||||
for deck in deckNames:
|
|
||||||
did = self.collection().decks.id(deck)
|
|
||||||
decks[deck] = did
|
|
||||||
|
|
||||||
return decks
|
return decks
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def deckNameFromId(self, deckId):
|
def deckNameFromId(self, deckId):
|
||||||
collection = self.collection()
|
deck = self.collection().decks.get(deckId)
|
||||||
if collection is not None:
|
if deck is None:
|
||||||
deck = collection.decks.get(deckId)
|
raise Exception('deck was not found: {}'.format(deckId))
|
||||||
if deck is not None:
|
else:
|
||||||
return deck['name']
|
return deck['name']
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def findNotes(self, query=None):
|
def findNotes(self, query=None):
|
||||||
if query is not None:
|
if query is None:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
return self.collection().findNotes(query)
|
return self.collection().findNotes(query)
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def findCards(self, query=None):
|
def findCards(self, query=None):
|
||||||
if query is not None:
|
if query not None:
|
||||||
return self.collection().findCards(query)
|
|
||||||
else:
|
|
||||||
return []
|
return []
|
||||||
|
else:
|
||||||
|
return self.collection().findCards(query)
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def cardsInfo(self, cards):
|
def cardsInfo(self, cards):
|
||||||
result = []
|
result = []
|
||||||
for cid in cards:
|
for cid in cards:
|
||||||
@ -874,7 +795,7 @@ class AnkiConnect:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def notesInfo(self, notes):
|
def notesInfo(self, notes):
|
||||||
result = []
|
result = []
|
||||||
for nid in notes:
|
for nid in notes:
|
||||||
@ -893,8 +814,7 @@ class AnkiConnect:
|
|||||||
'tags' : note.tags,
|
'tags' : note.tags,
|
||||||
'fields': fields,
|
'fields': fields,
|
||||||
'modelName': model['name'],
|
'modelName': model['name'],
|
||||||
'cards': self.collection().db.list(
|
'cards': self.collection().db.list('select id from cards where nid = ? order by ord', note.id)
|
||||||
'select id from cards where nid = ? order by ord', note.id)
|
|
||||||
})
|
})
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
# Anki will give a TypeError if the note ID does not exist.
|
# Anki will give a TypeError if the note ID does not exist.
|
||||||
@ -902,15 +822,17 @@ class AnkiConnect:
|
|||||||
# returned result, so that the items of the input and return
|
# returned result, so that the items of the input and return
|
||||||
# lists correspond.
|
# lists correspond.
|
||||||
result.append({})
|
result.append({})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def getDecks(self, cards):
|
def getDecks(self, cards):
|
||||||
decks = {}
|
decks = {}
|
||||||
|
collection = self.collection()
|
||||||
for card in cards:
|
for card in cards:
|
||||||
did = self.collection().db.scalar('select did from cards where id = ?', card)
|
did = collection.db.scalar('select did from cards where id = ?', card)
|
||||||
deck = self.collection().decks.get(did)['name']
|
deck = collection.decks.get(did)['name']
|
||||||
|
|
||||||
if deck in decks:
|
if deck in decks:
|
||||||
decks[deck].append(card)
|
decks[deck].append(card)
|
||||||
@ -920,7 +842,7 @@ class AnkiConnect:
|
|||||||
return decks
|
return decks
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def createDeck(self, deck):
|
def createDeck(self, deck):
|
||||||
self.startEditing()
|
self.startEditing()
|
||||||
deckId = self.collection().decks.id(deck)
|
deckId = self.collection().decks.id(deck)
|
||||||
@ -929,7 +851,7 @@ class AnkiConnect:
|
|||||||
return deckId
|
return deckId
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def changeDeck(self, cards, deck):
|
def changeDeck(self, cards, deck):
|
||||||
self.startEditing()
|
self.startEditing()
|
||||||
|
|
||||||
@ -947,7 +869,7 @@ class AnkiConnect:
|
|||||||
self.stopEditing()
|
self.stopEditing()
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def deleteDecks(self, decks, cardsToo=False):
|
def deleteDecks(self, decks, cardsToo=False):
|
||||||
self.startEditing()
|
self.startEditing()
|
||||||
for deck in decks:
|
for deck in decks:
|
||||||
@ -956,12 +878,12 @@ class AnkiConnect:
|
|||||||
self.stopEditing()
|
self.stopEditing()
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def cardsToNotes(self, cards):
|
def cardsToNotes(self, cards):
|
||||||
return self.collection().db.list('select distinct nid from cards where id in ' + anki.utils.ids2str(cards))
|
return self.collection().db.list('select distinct nid from cards where id in ' + anki.utils.ids2str(cards))
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiBrowse(self, query=None):
|
def guiBrowse(self, query=None):
|
||||||
browser = aqt.dialogs.open('Browser', self.window())
|
browser = aqt.dialogs.open('Browser', self.window())
|
||||||
browser.activateWindow()
|
browser.activateWindow()
|
||||||
@ -976,18 +898,18 @@ class AnkiConnect:
|
|||||||
return browser.model.cards
|
return browser.model.cards
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiAddCards(self):
|
def guiAddCards(self):
|
||||||
addCards = aqt.dialogs.open('AddCards', self.window())
|
addCards = aqt.dialogs.open('AddCards', self.window())
|
||||||
addCards.activateWindow()
|
addCards.activateWindow()
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiReviewActive(self):
|
def guiReviewActive(self):
|
||||||
return self.reviewer().card is not None and self.window().state == 'review'
|
return self.reviewer().card is not None and self.window().state == 'review'
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiCurrentCard(self):
|
def guiCurrentCard(self):
|
||||||
if not self.guiReviewActive():
|
if not self.guiReviewActive():
|
||||||
raise Exception('Gui review is not currently active.')
|
raise Exception('Gui review is not currently active.')
|
||||||
@ -1017,7 +939,7 @@ class AnkiConnect:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiStartCardTimer(self):
|
def guiStartCardTimer(self):
|
||||||
if not self.guiReviewActive():
|
if not self.guiReviewActive():
|
||||||
return False
|
return False
|
||||||
@ -1031,7 +953,7 @@ class AnkiConnect:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiShowQuestion(self):
|
def guiShowQuestion(self):
|
||||||
if self.guiReviewActive():
|
if self.guiReviewActive():
|
||||||
self.reviewer()._showQuestion()
|
self.reviewer()._showQuestion()
|
||||||
@ -1040,7 +962,7 @@ class AnkiConnect:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiShowAnswer(self):
|
def guiShowAnswer(self):
|
||||||
if self.guiReviewActive():
|
if self.guiReviewActive():
|
||||||
self.window().reviewer._showAnswer()
|
self.window().reviewer._showAnswer()
|
||||||
@ -1049,7 +971,7 @@ class AnkiConnect:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiAnswerCard(self, ease):
|
def guiAnswerCard(self, ease):
|
||||||
if not self.guiReviewActive():
|
if not self.guiReviewActive():
|
||||||
return False
|
return False
|
||||||
@ -1064,7 +986,7 @@ class AnkiConnect:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiDeckOverview(self, name):
|
def guiDeckOverview(self, name):
|
||||||
collection = self.collection()
|
collection = self.collection()
|
||||||
if collection is not None:
|
if collection is not None:
|
||||||
@ -1077,12 +999,12 @@ class AnkiConnect:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiDeckBrowser(self):
|
def guiDeckBrowser(self):
|
||||||
self.window().moveToState('deckBrowser')
|
self.window().moveToState('deckBrowser')
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiDeckReview(self, name):
|
def guiDeckReview(self, name):
|
||||||
if self.guiDeckOverview(name):
|
if self.guiDeckOverview(name):
|
||||||
self.window().moveToState('review')
|
self.window().moveToState('review')
|
||||||
@ -1091,7 +1013,7 @@ class AnkiConnect:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def guiExitAnki(self):
|
def guiExitAnki(self):
|
||||||
timer = QTimer()
|
timer = QTimer()
|
||||||
def exitAnki():
|
def exitAnki():
|
||||||
@ -1101,12 +1023,12 @@ class AnkiConnect:
|
|||||||
timer.start(1000) # 1s should be enough to allow the response to be sent.
|
timer.start(1000) # 1s should be enough to allow the response to be sent.
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def sync(self):
|
def sync(self):
|
||||||
self.window().onSync()
|
self.window().onSync()
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def upgrade(self):
|
def upgrade(self):
|
||||||
response = QMessageBox.question(
|
response = QMessageBox.question(
|
||||||
self.window(),
|
self.window(),
|
||||||
@ -1129,12 +1051,12 @@ class AnkiConnect:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def version(self):
|
def version(self):
|
||||||
return API_VERSION
|
return API_VERSION
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def addNotes(self, notes):
|
def addNotes(self, notes):
|
||||||
results = []
|
results = []
|
||||||
for note in notes:
|
for note in notes:
|
||||||
@ -1146,7 +1068,7 @@ class AnkiConnect:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
@webApi()
|
@api()
|
||||||
def canAddNotes(self, notes):
|
def canAddNotes(self, notes):
|
||||||
results = []
|
results = []
|
||||||
for note in notes:
|
for note in notes:
|
||||||
|
Loading…
Reference in New Issue
Block a user