128 lines
3.7 KiB
Python
128 lines
3.7 KiB
Python
import importlib
|
|
import json
|
|
import pkgutil
|
|
from types import ModuleType
|
|
from typing import Any, Iterable, TypedDict, cast
|
|
|
|
from PyQt6.QtCore import Qt, pyqtSlot
|
|
from PyQt6.QtSql import QSqlQuery
|
|
from PyQt6.QtWidgets import QScrollArea
|
|
from trycast import trycast
|
|
|
|
import plugins
|
|
from lib.definition import Definition, Line
|
|
from lib.sounds import SoundOff
|
|
from lib.utils import query_error
|
|
|
|
|
|
def find_plugins(ns_pkg: ModuleType) -> Iterable[pkgutil.ModuleInfo]:
|
|
return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".")
|
|
|
|
|
|
discovered_plugins = {
|
|
# finder, name, ispkg
|
|
importlib.import_module(name).registration[
|
|
"source"
|
|
]: importlib.import_module(name)
|
|
for _, name, _ in find_plugins(plugins)
|
|
}
|
|
|
|
API = "https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
|
|
|
|
|
|
class WordType(TypedDict):
|
|
word: str
|
|
source: str
|
|
definition: str
|
|
|
|
|
|
class Word:
|
|
"""All processing of a dictionary word."""
|
|
|
|
_words: dict[str, WordType] = {}
|
|
|
|
def __init__(self, word: str) -> None:
|
|
#
|
|
# Have we already retrieved this word?
|
|
#
|
|
try:
|
|
self.current = Word._words[word]
|
|
return
|
|
except KeyError:
|
|
pass
|
|
query = QSqlQuery()
|
|
query.prepare("SELECT * FROM words " "WHERE word = :word")
|
|
query.bindValue(":word", word)
|
|
if not query.exec():
|
|
query_error(query)
|
|
if query.next():
|
|
Word._words[word] = {
|
|
"word": word,
|
|
"source": query.value("source"),
|
|
"definition": json.loads(query.value("definition")),
|
|
}
|
|
self.current = Word._words[word]
|
|
return
|
|
#
|
|
# The code should look at our settings to see if we have an API
|
|
# key for MW to decide on the source to use.
|
|
#
|
|
source = "mw"
|
|
|
|
self._words[word] = discovered_plugins[source].fetch(word)
|
|
self.current = Word._words[word]
|
|
query.prepare(
|
|
"INSERT INTO words "
|
|
"(word, source, definition) "
|
|
"VALUES (:word, :source, :definition)"
|
|
)
|
|
query.bindValue(":word", self.current["word"])
|
|
query.bindValue(":source", self.current["source"])
|
|
query.bindValue(":definition", json.dumps(self.current["definition"]))
|
|
if not query.exec():
|
|
query_error(query)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def playSound(self) -> None:
|
|
url = discovered_plugins[self.current["source"]].getFirstSound(
|
|
self.current["definition"]
|
|
)
|
|
if url.isValid():
|
|
snd = SoundOff()
|
|
snd.playSound(url)
|
|
return
|
|
|
|
def playPRS(self) -> None:
|
|
return
|
|
|
|
def getWord(self) -> str:
|
|
return self.current["word"]
|
|
|
|
def get_html(self) -> str | None:
|
|
src = self.current["source"]
|
|
try:
|
|
return cast(str, discovered_plugins[src].getHtml(self.current))
|
|
except KeyError:
|
|
raise Exception(f"Unknown source: {src}")
|
|
|
|
def get_def(self) -> list[Line]:
|
|
src = self.current["source"]
|
|
try:
|
|
lines = discovered_plugins[src].getDef(self.current["definition"])
|
|
lines = trycast(list[Line], lines)
|
|
assert lines is not None
|
|
return lines
|
|
except KeyError:
|
|
raise Exception(f"Unknown source: {self.current['source']}")
|
|
|
|
|
|
class DefinitionArea(QScrollArea):
|
|
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
|
|
super(DefinitionArea, self).__init__(*args, *kwargs)
|
|
d = Definition(w)
|
|
self.setWidget(d)
|
|
self.setWidgetResizable(True)
|
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
|
return
|