french-wiktionary-flashcards

Easily create Anki flashcards from Wiktionary data.
Log | Files | Refs | README

commit b36723cf9fc1d1832758bbbd9eba3847978535f1
parent a299b44fd8d9b7e045414cb087f0fa4e2527aa8c
Author: David Schlachter <t480-debian-git@schlachter.ca>
Date:   Sun,  4 Oct 2026 01:46:31 -0400

Add option to auto-sync after adding card

Diffstat:
MREADME.md | 4++++
Mmain.go | 7++++++-
Async.go | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mui.go | 15+++++++++++++++
4 files changed, 72 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md @@ -65,6 +65,10 @@ Usage of french-wiktionary-flashcards: -apiURL string Base URL to access the anki-connect plugin API. (default "http://localhost:8765") + -autosync bool + If set to "true", automatically sync the collection to + Ankiweb after adding a new card. Useful for running on a + a headless server or a machine where Anki is always open. -deck string Name of the deck where new Anki cards will be created. -dictionary string diff --git a/main.go b/main.go @@ -45,6 +45,11 @@ func main() { "", "Optional: first word to look up on program launch.", ) + autoSync := flag.Bool( + "autosync", + false, + "Whether to sync automatically with Ankiweb", + ) flag.Parse() @@ -82,7 +87,7 @@ func main() { c := http.DefaultClient c.Timeout = 5 * time.Second - p := tea.NewProgram(initialModel(c, db, *apiURL, *deckName, *modelName, *rawDict, *initialWord)) + p := tea.NewProgram(initialModel(c, db, *apiURL, *deckName, *modelName, *rawDict, *initialWord, *autoSync)) if _, err := p.Run(); err != nil { log.Fatalf("Unexpected error encountered while running program: %s", err) } diff --git a/sync.go b/sync.go @@ -0,0 +1,47 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + tea "github.com/charmbracelet/bubbletea" +) + +func syncWithAnkiWeb(c *http.Client, apiURL string) tea.Cmd { + return func() tea.Msg { + jsonBytes, err := json.Marshal(struct { + Action string `json:"action"` + Version int `json:"version"` + }{ + "sync", apiVersion, + }) + if err != nil { + return errMsg(fmt.Errorf("marshaling JSON: %s", err)) + } + + req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonBytes)) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.Do(req) + if err != nil { + return errMsg(fmt.Errorf("making request: %s", err)) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + var jsonResp struct { + Error string `json:"error"` + } + + json.Unmarshal(body, &jsonResp) + if jsonResp.Error != "" { + return errMsg(fmt.Errorf("creating card: %s", jsonResp.Error)) + } + + return syncedMsg(true) + } +} diff --git a/ui.go b/ui.go @@ -30,6 +30,7 @@ type model struct { ankiModel string apiURL string rawDictionaryPath string + autoSync bool dictionaryReady bool @@ -44,6 +45,7 @@ type ( errMsg error definitionMsg string wordAddedMsg string + syncedMsg bool isDictionaryEmptyMsg bool populatingDictionaryMsg *dictionaryPopulator ) @@ -52,6 +54,7 @@ func initialModel( c *http.Client, db *sql.DB, apiURL, ankiDeck, ankiModel, rawDictionary, firstWord string, + autoSync bool, ) model { input := textinput.New() input.Placeholder = "" @@ -94,6 +97,7 @@ func initialModel( ankiModel: ankiModel, apiURL: apiURL, rawDictionaryPath: rawDictionary, + autoSync: autoSync, currentWord: firstWord, } @@ -146,6 +150,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.wordInput.SetValue("") m.definitionViewport.SetContent("") m.err = nil + // If we're running on a headless server, or a machine where we always + // have Anki open, we can sync the collection automatically after adding + // a new card. + if m.autoSync { + return m, syncWithAnkiWeb( + m.c, + m.apiURL, + ) + } + case syncedMsg: + m.statusString = "✅ Synced collection to AnkiWeb" case tea.WindowSizeMsg: m.importProgress.Width = msg.Width m.wordInput.Width = msg.Width