wip
This commit is contained in:
parent
d47736c746
commit
7bf761a1c0
209
AnkiConnect.py
209
AnkiConnect.py
@ -29,7 +29,11 @@ import socket
|
|||||||
#
|
#
|
||||||
|
|
||||||
API_VERSION = 2
|
API_VERSION = 2
|
||||||
|
TICK_INTERVAL = 25
|
||||||
URL_TIMEOUT = 10
|
URL_TIMEOUT = 10
|
||||||
|
NET_ADDRESS = '127.0.0.1'
|
||||||
|
NET_BACKLOG = 5
|
||||||
|
NET_PORT = 8765
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -48,53 +52,15 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import PyQt5 as PyQt
|
import PyQt5 as PyQt
|
||||||
|
|
||||||
|
try:
|
||||||
|
unicode
|
||||||
|
except:
|
||||||
|
unicode = str
|
||||||
|
|
||||||
makeBytes = lambda data: data.encode('utf-8')
|
makeBytes = lambda data: data.encode('utf-8')
|
||||||
makeStr = lambda data: data.decode('utf-8')
|
makeStr = lambda data: data.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Audio helpers
|
|
||||||
#
|
|
||||||
|
|
||||||
def audioDownload(url):
|
|
||||||
try:
|
|
||||||
resp = web.urlopen(url, timeout=URL_TIMEOUT)
|
|
||||||
except web.URLError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if resp.code != 200:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return resp.read()
|
|
||||||
|
|
||||||
|
|
||||||
def audioInject(note, fields, filename):
|
|
||||||
for field in fields:
|
|
||||||
if field in note:
|
|
||||||
note[field] += u'[sound:{}]'.format(filename)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Legacy JPOD101 handlers
|
|
||||||
#
|
|
||||||
|
|
||||||
def jpodBuildFilename(kana, kanji):
|
|
||||||
filename = u'yomichan_{}'.format(kana)
|
|
||||||
if kanji:
|
|
||||||
filename += u'_{}'.format(kanji)
|
|
||||||
|
|
||||||
filename += u'.mp3'
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
|
||||||
def jpodDownload(kana, kanji):
|
|
||||||
url = 'https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji={}'.format(web.quote(kanji.encode('utf-8')))
|
|
||||||
if kana:
|
|
||||||
url += '&kana={}'.format(web.quote(kana.encode('utf-8')))
|
|
||||||
|
|
||||||
return audioDownload(url)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# AjaxRequest
|
# AjaxRequest
|
||||||
#
|
#
|
||||||
@ -208,14 +174,14 @@ class AjaxServer:
|
|||||||
self.clients = list(filter(lambda c: c.advance(), self.clients))
|
self.clients = list(filter(lambda c: c.advance(), self.clients))
|
||||||
|
|
||||||
|
|
||||||
def listen(self, address='127.0.0.1', port=8765, backlog=5):
|
def listen(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
self.sock.setblocking(False)
|
self.sock.setblocking(False)
|
||||||
self.sock.bind((address, port))
|
self.sock.bind((NET_ADDRESS, NET_PORT))
|
||||||
self.sock.listen(backlog)
|
self.sock.listen(NET_BACKLOG)
|
||||||
|
|
||||||
|
|
||||||
def handlerWrapper(self, req):
|
def handlerWrapper(self, req):
|
||||||
@ -251,42 +217,107 @@ class AjaxServer:
|
|||||||
self.clients = []
|
self.clients = []
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Helpers
|
||||||
|
#
|
||||||
|
|
||||||
|
def audioDownload(url):
|
||||||
|
try:
|
||||||
|
resp = web.urlopen(url, timeout=URL_TIMEOUT)
|
||||||
|
except web.URLError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if resp.code != 200:
|
||||||
|
return None
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# AnkiNoteParams
|
||||||
|
#
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# AnkiBridge
|
# AnkiBridge
|
||||||
#
|
#
|
||||||
|
|
||||||
class AnkiBridge:
|
class AnkiBridge:
|
||||||
def addNote(self, deckName, modelName, fields, tags, audio):
|
def addNote(self, params):
|
||||||
collection = self.collection()
|
collection = self.collection()
|
||||||
if collection is None:
|
if collection is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
note = self.createNote(deckName, modelName, fields, tags)
|
note = self.createNote(params)
|
||||||
if note is None:
|
if note is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if audio is not None and len(audio['fields']) > 0:
|
if params.audio is not None and len(params.audio.fields) > 0:
|
||||||
url = audio.get('url')
|
data = audioDownload(params.audio.url)
|
||||||
if url is None:
|
|
||||||
data = jpodDownload(audio['kana'], audio['kanji'])
|
|
||||||
filename = jpodBuildFilename(audio['kana'], audio['kanji'])
|
|
||||||
skipHash = '7e2c2f954ef6051373ba916f000168dc'
|
|
||||||
else:
|
|
||||||
data = audioDownload(url)
|
|
||||||
filename = os.path.basename(audio['filename'])
|
|
||||||
skipHash = audio.get('skipHash')
|
|
||||||
|
|
||||||
if data is not None:
|
if data is not None:
|
||||||
if skipHash is None:
|
if params.audio.skipHash is None:
|
||||||
skip = False
|
skip = False
|
||||||
else:
|
else:
|
||||||
m = hashlib.md5()
|
m = hashlib.md5()
|
||||||
m.update(data)
|
m.update(data)
|
||||||
skip = skipHash == m.hexdigest()
|
skip = params.audio.skipHash == m.hexdigest()
|
||||||
|
|
||||||
if not skip:
|
if not skip:
|
||||||
audioInject(note, audio['fields'], filename)
|
audioInject(note, params.audio.fields, params.audio.filename)
|
||||||
self.media().writeData(filename, data)
|
self.media().writeData(params.audio.filename, data)
|
||||||
|
|
||||||
self.startEditing()
|
self.startEditing()
|
||||||
collection.addNote(note)
|
collection.addNote(note)
|
||||||
@ -296,28 +327,28 @@ class AnkiBridge:
|
|||||||
return note.id
|
return note.id
|
||||||
|
|
||||||
|
|
||||||
def canAddNote(self, deckName, modelName, fields):
|
def canAddNote(self, note):
|
||||||
return bool(self.createNote(deckName, modelName, fields))
|
return bool(self.createNote(note))
|
||||||
|
|
||||||
|
|
||||||
def createNote(self, deckName, modelName, fields, tags=[]):
|
def createNote(self, params):
|
||||||
collection = self.collection()
|
collection = self.collection()
|
||||||
if collection is None:
|
if collection is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
model = collection.models.byName(modelName)
|
model = collection.models.byName(params.modelName)
|
||||||
if model is None:
|
if model is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
deck = collection.decks.byName(deckName)
|
deck = collection.decks.byName(params.deckName)
|
||||||
if deck is None:
|
if deck is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
note = anki.notes.Note(collection, model)
|
note = anki.notes.Note(collection, model)
|
||||||
note.model()['did'] = deck['id']
|
note.model()['did'] = deck['id']
|
||||||
note.tags = tags
|
note.tags = params.tags
|
||||||
|
|
||||||
for name, value in fields.items():
|
for name, value in params.fields.items():
|
||||||
if name in note:
|
if name in note:
|
||||||
note[name] = value
|
note[name] = value
|
||||||
|
|
||||||
@ -375,14 +406,14 @@ class AnkiBridge:
|
|||||||
#
|
#
|
||||||
|
|
||||||
class AnkiConnect:
|
class AnkiConnect:
|
||||||
def __init__(self, interval=25):
|
def __init__(self):
|
||||||
self.anki = AnkiBridge()
|
self.anki = AnkiBridge()
|
||||||
self.server = AjaxServer(self.handler)
|
self.server = AjaxServer(self.handler)
|
||||||
self.server.listen()
|
self.server.listen()
|
||||||
|
|
||||||
self.timer = PyQt.QtCore.QTimer()
|
self.timer = PyQt.QtCore.QTimer()
|
||||||
self.timer.timeout.connect(self.advance)
|
self.timer.timeout.connect(self.advance)
|
||||||
self.timer.start(interval)
|
self.timer.start(TICK_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
def advance(self):
|
def advance(self):
|
||||||
@ -390,9 +421,12 @@ class AnkiConnect:
|
|||||||
|
|
||||||
|
|
||||||
def handler(self, request):
|
def handler(self, request):
|
||||||
action = 'api_' + (request.get('action') or '')
|
action = 'api_' + request.get('action', '')
|
||||||
if hasattr(self, action):
|
if hasattr(self, action):
|
||||||
|
try:
|
||||||
return getattr(self, action)(**(request.get('params') or {}))
|
return getattr(self, action)(**(request.get('params') or {}))
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def api_deckNames(self):
|
def api_deckNames(self):
|
||||||
@ -408,25 +442,19 @@ class AnkiConnect:
|
|||||||
|
|
||||||
|
|
||||||
def api_addNote(self, note):
|
def api_addNote(self, note):
|
||||||
return self.anki.addNote(
|
params = AnkiNoteParams(note)
|
||||||
note['deckName'],
|
if params.validate():
|
||||||
note['modelName'],
|
return self.anki.addNote(params)
|
||||||
note['fields'],
|
|
||||||
note['tags'],
|
|
||||||
note.get('audio')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def api_addNotes(self, notes):
|
def api_addNotes(self, notes):
|
||||||
results = []
|
results = []
|
||||||
for note in notes:
|
for note in notes:
|
||||||
results.append(self.anki.addNote(
|
params = AnkiNoteParams(note)
|
||||||
note['deckName'],
|
if note.validate():
|
||||||
note['modelName'],
|
results.append(self.anki.addNote(params))
|
||||||
note['fields'],
|
else:
|
||||||
note['tags'],
|
results.append(None)
|
||||||
note.get('audio')
|
|
||||||
))
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@ -434,11 +462,8 @@ class AnkiConnect:
|
|||||||
def api_canAddNotes(self, notes):
|
def api_canAddNotes(self, notes):
|
||||||
results = []
|
results = []
|
||||||
for note in notes:
|
for note in notes:
|
||||||
results.append(self.anki.canAddNote(
|
params = AnkiNoteParams(note)
|
||||||
note['deckName'],
|
results.append(params.validate() and self.anki.canAddNote(params))
|
||||||
note['modelName'],
|
|
||||||
note['fields']
|
|
||||||
))
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user