This commit is contained in:
Alex Yatskov 2017-02-19 12:07:10 -08:00
parent d47736c746
commit 7bf761a1c0

View File

@ -29,7 +29,11 @@ import socket
#
API_VERSION = 2
TICK_INTERVAL = 25
URL_TIMEOUT = 10
NET_ADDRESS = '127.0.0.1'
NET_BACKLOG = 5
NET_PORT = 8765
#
@ -48,53 +52,15 @@ try:
except ImportError:
import PyQt5 as PyQt
try:
unicode
except:
unicode = str
makeBytes = lambda data: data.encode('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
#
@ -208,14 +174,14 @@ class AjaxServer:
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.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.setblocking(False)
self.sock.bind((address, port))
self.sock.listen(backlog)
self.sock.bind((NET_ADDRESS, NET_PORT))
self.sock.listen(NET_BACKLOG)
def handlerWrapper(self, req):
@ -251,42 +217,107 @@ class AjaxServer:
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
#
class AnkiBridge:
def addNote(self, deckName, modelName, fields, tags, audio):
def addNote(self, params):
collection = self.collection()
if collection is None:
return
note = self.createNote(deckName, modelName, fields, tags)
note = self.createNote(params)
if note is None:
return
if audio is not None and len(audio['fields']) > 0:
url = audio.get('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 params.audio is not None and len(params.audio.fields) > 0:
data = audioDownload(params.audio.url)
if data is not None:
if skipHash is None:
if params.audio.skipHash is None:
skip = False
else:
m = hashlib.md5()
m.update(data)
skip = skipHash == m.hexdigest()
skip = params.audio.skipHash == m.hexdigest()
if not skip:
audioInject(note, audio['fields'], filename)
self.media().writeData(filename, data)
audioInject(note, params.audio.fields, params.audio.filename)
self.media().writeData(params.audio.filename, data)
self.startEditing()
collection.addNote(note)
@ -296,28 +327,28 @@ class AnkiBridge:
return note.id
def canAddNote(self, deckName, modelName, fields):
return bool(self.createNote(deckName, modelName, fields))
def canAddNote(self, note):
return bool(self.createNote(note))
def createNote(self, deckName, modelName, fields, tags=[]):
def createNote(self, params):
collection = self.collection()
if collection is None:
return
model = collection.models.byName(modelName)
model = collection.models.byName(params.modelName)
if model is None:
return
deck = collection.decks.byName(deckName)
deck = collection.decks.byName(params.deckName)
if deck is None:
return
note = anki.notes.Note(collection, model)
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:
note[name] = value
@ -375,14 +406,14 @@ class AnkiBridge:
#
class AnkiConnect:
def __init__(self, interval=25):
def __init__(self):
self.anki = AnkiBridge()
self.server = AjaxServer(self.handler)
self.server.listen()
self.timer = PyQt.QtCore.QTimer()
self.timer.timeout.connect(self.advance)
self.timer.start(interval)
self.timer.start(TICK_INTERVAL)
def advance(self):
@ -390,9 +421,12 @@ class AnkiConnect:
def handler(self, request):
action = 'api_' + (request.get('action') or '')
action = 'api_' + request.get('action', '')
if hasattr(self, action):
return getattr(self, action)(**(request.get('params') or {}))
try:
return getattr(self, action)(**(request.get('params') or {}))
except TypeError:
return None
def api_deckNames(self):
@ -408,25 +442,19 @@ class AnkiConnect:
def api_addNote(self, note):
return self.anki.addNote(
note['deckName'],
note['modelName'],
note['fields'],
note['tags'],
note.get('audio')
)
params = AnkiNoteParams(note)
if params.validate():
return self.anki.addNote(params)
def api_addNotes(self, notes):
results = []
for note in notes:
results.append(self.anki.addNote(
note['deckName'],
note['modelName'],
note['fields'],
note['tags'],
note.get('audio')
))
params = AnkiNoteParams(note)
if note.validate():
results.append(self.anki.addNote(params))
else:
results.append(None)
return results
@ -434,11 +462,8 @@ class AnkiConnect:
def api_canAddNotes(self, notes):
results = []
for note in notes:
results.append(self.anki.canAddNote(
note['deckName'],
note['modelName'],
note['fields']
))
params = AnkiNoteParams(note)
results.append(params.validate() and self.anki.canAddNote(params))
return results