diff --git a/main.py b/main.py index 7bd5734..299b71a 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,8 @@ import re from dataclasses import dataclass +import json +import requests import wx -import genanki import uuid ### Dictionary stuff @@ -37,10 +38,11 @@ def parse_line(line: str): def build_pinyin_index(words: list[Word]): index = {} for word in words: - if word.pinyin in index: - index[word.pinyin].append(word) + normalized_word = normalize_pinyin(word.pinyin) + if normalized_word in index: + index[normalized_word].append(word) else: - index[word.pinyin] = [word] + index[normalized_word] = [word] return index def build_traditional_index(words: list[Word]): @@ -64,6 +66,10 @@ pinyinToneMarks = { def convert_pinyin_callback(match): tone= int(match.group(3)) vowel = match.group(1) + # the 5th tone is neutral + if tone == 5: + return match + # for multiple vowels, use first one if it is a/e/o, otherwise use second one pos=0 if len(vowel) > 1 and not vowel[0] in 'aeoAEO': @@ -71,37 +77,36 @@ def convert_pinyin_callback(match): return vowel[0:pos]+pinyinToneMarks[vowel[pos]][tone-1]+vowel[pos+1:] + match.group(2) -def convert_pinyin(s): +def convert_pinyin(s: str): return re.sub(r'([aeiou]{1,3})(n?g?r?)([12345])', convert_pinyin_callback, s, flags=re.IGNORECASE) -### Anki stuff +def normalize_pinyin(s: str): + return re.sub(r'[12345]', '', s).replace(' ', '') -simple_anki_model = genanki.Model( - 1607392319, - 'Simple Model', - fields=[ - {'name': 'Question'}, - {'name': 'Answer'}, - ], - templates=[ - { - 'name': 'Card 1', - 'qfmt': '
{{Question}}
', - 'afmt': '
{{FrontSide}}
{{Answer}}
', - }, - ]) +### Ankiconnect -def generate_card_from_word(word: Word): - return generate_card(word.traditional, f"{convert_pinyin(word.pinyin)}
{word.definitions[0]}") - -def generate_card(front: str, back: str): - return genanki.Note(model=simple_anki_model, fields=[front, back]) - -def generate_deck(cards: list, name: str): - d = genanki.Deck(2059400110, name) - for card in cards: - d.add_note(card) - return d +# TODO make deck and model configurable +def add_note(hanzi: str, pinyin: str, definition: str, tags: list[str]): + request_data = { + "version": 5, + "action": "addNote", + "params": { + "note": { + "deckName": "寫漢字", + "modelName": "Mandarin Custom", + "fields": { + "Pinyin": pinyin, + "Character": hanzi, + "Definition": definition + }, + "tags": tags + } + } + } + print(json.dumps(request_data)) + r = requests.post("http://localhost:8765/", data=json.dumps(request_data)) + print(r.json()) + return r.json() ### UI stuff @@ -116,71 +121,80 @@ class MainFrame(wx.Frame): # IDs of words in results self.result_words = [] - super().__init__(parent=None, title='Mandarin Anki', size = (420, 340)) + super().__init__(parent=None, title='Mandarin Anki', size = (460, 400)) panel = wx.Panel(self) vertical_layout = wx.BoxSizer(wx.VERTICAL) + results_vertical_layout = wx.BoxSizer(wx.VERTICAL) columns_layout = wx.BoxSizer(wx.HORIZONTAL) # search and results + search_label = wx.StaticText(panel, label="Search:") self.search = wx.TextCtrl(panel, size = (200, -1)) self.search.Bind(wx.EVT_TEXT, self.OnKeyTyped) self.results = wx.ListBox(panel, size = (200, 200)) self.results.Bind(wx.EVT_LISTBOX, self.OnWordSelected) - self.selected = wx.ListBox(panel, size = (200, 200)) - self.selected.Bind(wx.EVT_LISTBOX, self.OnWordRemoved) + self.selected_hanzi = wx.StaticText(panel, label="") + self.selected_pinyin = wx.StaticText(panel, label="") + self.selected_definitions = wx.CheckListBox(panel, size= (200,200)) + tags_label = wx.StaticText(panel, label="Tags (comma separated):") + self.tags = wx.TextCtrl(panel, size = (200, -1)) + results_vertical_layout.Add(self.selected_hanzi, 0) + results_vertical_layout.Add(self.selected_pinyin, 0) + results_vertical_layout.Add(self.selected_definitions, 0) + results_vertical_layout.Add(tags_label) + results_vertical_layout.Add(self.tags, 0) - self.deck_name = wx.TextCtrl(panel, size = (200, -1)) + self.add_card = wx.Button(panel, label = "Add card") + self.add_card.Bind(wx.EVT_BUTTON, self.OnAddCard) + results_vertical_layout.Add(self.add_card, 0) - self.create_deck = wx.Button(panel, label = "ank!") - self.create_deck.Bind(wx.EVT_BUTTON, self.OnAnk) + vertical_layout.Add(search_label, 0) vertical_layout.Add(self.search, 0) vertical_layout.Add(columns_layout) - vertical_layout.Add(self.deck_name, 0) - vertical_layout.Add(self.create_deck, 0) columns_layout.Add(self.results, 0, wx.EXPAND) - columns_layout.Add(self.selected, 0, wx.EXPAND) + columns_layout.Add(results_vertical_layout, 0, wx.EXPAND) panel.SetSizer(vertical_layout) self.Show() def OnKeyTyped(self, event): if event.GetString(): - if event.GetString() in self.pinyin_idx: - matches = self.pinyin_idx[event.GetString()] + if normalize_pinyin(event.GetString()) in self.pinyin_idx: + matches = self.pinyin_idx[normalize_pinyin(event.GetString())] self.results.Set([f"{w.traditional} {convert_pinyin(w.pinyin)} {w.definitions[0]}" for w in matches]) self.result_words = matches elif event.GetString() in self.traditional_idx: - match = self.traditional_idx[event.GetString()] - self.result_words = [match] - self.results.Set([f"{match.traditional} {convert_pinyin(match.pinyin)} {match.definitions[0]}"]) + matches = self.traditional_idx[event.GetString()] + self.results.Set([f"{w.traditional} {convert_pinyin(w.pinyin)} {w.definitions[0]}" for w in matches]) + self.result_words = matches else: self.results.Set([]) self.result_words = [] def OnWordSelected(self, event): word = self.result_words[event.GetEventObject().GetSelection()] - formatted = f"{word.traditional} {convert_pinyin(word.pinyin)} {word.definitions[0]}" + self.selected_word = word + self.selected_hanzi.SetLabel(word.traditional) + self.selected_pinyin.SetLabel(convert_pinyin(word.pinyin)) + self.selected_definitions.Clear() + self.selected_definitions.InsertItems(word.definitions, 0) - if not word in self.selected_words: - self.selected.InsertItems([formatted], 0) - self.selected_words.insert(0, word) - - - def OnWordRemoved(self, event): - idx = event.GetEventObject().GetSelection() - self.selected.SetSelection(-1) - self.selected.Delete(idx) - del self.selected_words[idx] - - def OnAnk(self, event): - cards = [generate_card_from_word(w) for w in self.selected_words] - deck = generate_deck(cards, self.deck_name.GetValue()) - deck.write_to_file(f"{self.deck_name.GetValue().replace(" ", "-")}.apkg") + def OnAddCard(self, event): + if not self.selected_word or not self.selected_definitions.GetCheckedItems(): + return + print("adding note " + self.selected_word.traditional) + tags = [tag.strip() for tag in self.tags.GetValue().split(",")] + add_note( + self.selected_word.traditional, + convert_pinyin(self.selected_word.pinyin), + ", ".join(self.selected_definitions.GetCheckedStrings()), + tags + ) if __name__ == '__main__': app = wx.App() frame = MainFrame() - app.MainLoop() \ No newline at end of file + app.MainLoop()