Files
esl-reader/lib/words.py
Christopher T. Johnson 2fa8877e5d MW integration
2024-03-16 00:49:07 -04:00

145 lines
5.3 KiB
Python

import json
import requests
from PyQt6.QtSql import QSqlQuery
from typing import Type, Self, Dict, Any, Optional
from lib import query_error
API = "https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
MWAPI = "https://www.dictionaryapi.com/api/v3/references/collegiate/json/{word}?key=51d9df34-ee13-489e-8656-478c215e846c"
class Word:
_instance = None
_words: Dict[str,str] = {}
_current: Optional[Dict[str,Any]] = None
def __new__(cls: Type[Self], word:str ) -> Self:
if cls._instance:
return cls._instance
cls._instance = super(Word, cls).__new__(cls)
return cls._instance
def __init__(self, word: str) -> None:
print(f"Word == {word}")
#
# Have we already retrieved this word?
#
try:
self._current = json.loads(self._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():
self._words[word] = query.value("definition")
self._current = json.loads(self._words[word])
return
response = requests.get(MWAPI.format(word=word))
if response.status_code != 200:
self._current = None
return
data = json.loads(response.content.decode("utf-8"))
#
# XXX - The first entry should be the correct entry. There could be more
# if there is a "hom" entry, then that will be appended to meta.id
# word = "lady", hom=1, meta.id = "lady:1";
#
print(response.content.decode('utf-8'))
self._words[word] = json.dumps(data[0])
self._current = data[0]
query.prepare("INSERT INTO words "
"(word, definition) "
"VALUES (:word, :definition)")
query.bindValue(":word", word)
query.bindValue(":definition", self._words[word])
if not query.exec():
query_error(query)
return
def get_html(self) -> str|None:
if not self._current:
return None
if "meta" in self._current.keys():
return self.mw_html()
else:
return self.apidictionary_html()
def mw_html(self) -> str:
def sound_url(prs:Dict[str,Any]) -> str|None:
base = 'https://media.merriam-webster.com/audio/prons/en/us/ogg'
if 'sound' not in prs.keys():
return None
audio = prs['sound']['audio']
if audio.startswith('bix'):
url = base + '/bix/'
elif audio.startswith('gg'):
url = base + '/gg/'
elif audio[0] not in "abcdefghijklmnopqrstuvwxyz":
url = base + '/number/'
else:
url = base + '/' + audio[0] + '/'
url += audio + '.ogg'
return url
def parse_sn(sn:str, old:str) -> str:
return sn
assert self._current is not None
word = self._current['hwi']['hw']
label = self._current['fl']
html = f"<h1 class=\"def-word\">{word} <span class=\"def-label\">{label}</span></h1>\n"
if "vrs" in self._current.keys():
html += "<ol class=\"def-vars\'>\n"
html += "<li>"
html += "</li>\n<li>".join([vrs['va'] for vrs in self._current['vrs']])
html += "</li>\n</ol>\n"
if 'prs' in self._current['hwi'].keys():
tmp = []
for prs in self._current['hwi']['prs']:
url = sound_url(prs)
how = prs['mw']
if url:
tmp.append(f'<a href="{url}">\\{how}\\</a>')
else:
tmp.append(f'\\{how}\\')
html += '<span class="def-phonetic">'
html += '</span><span="def-phonetic">'.join(tmp)
html += "</span>\n"
if 'ins' in self._current.keys():
html += "<h2 class=\"def-word\">"
html += ', '.join([ins['if'] for ins in self._current['ins']])
html += "</h2>\n"
html += "<ul class='def-outer'>\n"
for meaning in self._current['def']:
html += f"<li>{meaning['vd']}\n"
html += "<ul class=\"def-inner\">\n"
label = ''
for sseq in meaning['sseq']:
for sense in sseq:
label = parse_sn(sense[1]['sn'], label)
sls = ''
if 'sls' in sense[1].keys():
sls = ', '.join(sense[1]['sls'])
sls = f"<span class=\"def-sls\">{sls}</span> "
for dt in sense[1]['dt']:
if dt[0] == 'text':
html += f"<li class=\"def-text\"><span class=\"def-sn\">{label}</span>{sls}{dt[1]}</li>\n"
elif dt[0] == 'vis':
for vis in dt[1]:
html += f"<li class=\"def-vis\">{vis['t']}</li>\n"
else:
print(f"Do something with {dt[0]}")
html += "</ul>\n"
html += "</ul>\n"
return html
def apidictionary_html(self) ->str:
html = ""
return html