Adding notes with ankiconnect works!
This commit is contained in:
138
main.py
138
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': '<div style="text-align:center;font-size: 1.4rem">{{Question}}</div>',
|
||||
'afmt': '<div style="text-align:center">{{FrontSide}}<hr id="answer">{{Answer}}</div>',
|
||||
},
|
||||
])
|
||||
### Ankiconnect
|
||||
|
||||
def generate_card_from_word(word: Word):
|
||||
return generate_card(word.traditional, f"{convert_pinyin(word.pinyin)}<br>{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()
|
||||
app.MainLoop()
|
||||
|
||||
Reference in New Issue
Block a user