diff --git a/internal/database/migrations.go b/internal/database/migrations.go index 6906977664a..def44c2dd5e 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -1380,4 +1380,11 @@ var migrations = [...]func(tx *sql.Tx) error{ _, err = tx.Exec(sql) return err }, + func(tx *sql.Tx) (err error) { + sql := ` + ALTER TABLE integrations ADD COLUMN readeck_push_enabled bool default 'f'; + ` + _, err = tx.Exec(sql) + return err + }, } diff --git a/internal/integration/integration.go b/internal/integration/integration.go index fc5fc4d1798..d1284342bda 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -685,4 +685,30 @@ func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *mode } } } + + // Push each new entry to Readeck when push is enabled + if userIntegrations.ReadeckPushEnabled { + client := readeck.NewClient( + userIntegrations.ReadeckURL, + userIntegrations.ReadeckAPIKey, + userIntegrations.ReadeckLabels, + userIntegrations.ReadeckOnlyURL, + ) + for _, entry := range entries { + slog.Debug("Sending a new entry to Readeck", + slog.Int64("user_id", userIntegrations.UserID), + slog.Int64("entry_id", entry.ID), + slog.String("entry_url", entry.URL), + ) + + if err := client.CreateBookmark(entry.URL, entry.Title, entry.Content); err != nil { + slog.Error("Unable to send entry to Readeck", + slog.Int64("user_id", userIntegrations.UserID), + slog.Int64("entry_id", entry.ID), + slog.String("entry_url", entry.URL), + slog.Any("error", err), + ) + } + } + } } diff --git a/internal/integration/readeck/readeck_test.go b/internal/integration/readeck/readeck_test.go new file mode 100644 index 00000000000..cb2ec203a12 --- /dev/null +++ b/internal/integration/readeck/readeck_test.go @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package readeck + +import ( + "encoding/json" + "io" + "mime/multipart" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestCreateBookmark(t *testing.T) { + entryURL := "https://example.com/article" + entryTitle := "Example Title" + entryContent := "

Some HTML content

" + labels := "tag1,tag2" + + tests := []struct { + name string + onlyURL bool + baseURL string + apiKey string + labels string + entryURL string + entryTitle string + entryContent string + serverResponse func(w http.ResponseWriter, r *http.Request) + wantErr bool + errContains string + }{ + { + name: "successful bookmark creation with only URL", + onlyURL: true, + labels: labels, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("expected POST, got %s", r.Method) + } + if r.URL.Path != "/api/bookmarks/" { + t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path) + } + if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") { + t.Errorf("expected Authorization Bearer header, got %q", got) + } + if ct := r.Header.Get("Content-Type"); ct != "application/json" { + t.Errorf("expected Content-Type application/json, got %s", ct) + } + + body, _ := io.ReadAll(r.Body) + var payload map[string]any + if err := json.Unmarshal(body, &payload); err != nil { + t.Fatalf("failed to parse JSON body: %v", err) + } + if u := payload["url"]; u != entryURL { + t.Errorf("expected url %s, got %v", entryURL, u) + } + if title := payload["title"]; title != entryTitle { + t.Errorf("expected title %s, got %v", entryTitle, title) + } + // Labels should be split into an array + if raw := payload["labels"]; raw == nil { + t.Errorf("expected labels to be set") + } else if arr, ok := raw.([]any); ok { + if len(arr) != 2 || arr[0] != "tag1" || arr[1] != "tag2" { + t.Errorf("unexpected labels: %#v", arr) + } + } else { + t.Errorf("labels should be an array, got %T", raw) + } + w.WriteHeader(http.StatusOK) + }, + }, + { + name: "successful bookmark creation with content (multipart)", + onlyURL: false, + labels: labels, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("expected POST, got %s", r.Method) + } + if r.URL.Path != "/api/bookmarks/" { + t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path) + } + if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") { + t.Errorf("expected Authorization Bearer header, got %q", got) + } + ct := r.Header.Get("Content-Type") + if !strings.HasPrefix(ct, "multipart/form-data;") { + t.Errorf("expected multipart/form-data, got %s", ct) + } + boundaryIdx := strings.Index(ct, "boundary=") + if boundaryIdx == -1 { + t.Fatalf("missing multipart boundary in Content-Type: %s", ct) + } + boundary := ct[boundaryIdx+len("boundary="):] + mr := multipart.NewReader(r.Body, boundary) + + seenLabels := []string{} + var seenURL, seenTitle, seenFeature string + var resourceHeader map[string]any + var resourceBody string + + for { + part, err := mr.NextPart() + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("reading multipart: %v", err) + } + name := part.FormName() + data, _ := io.ReadAll(part) + switch name { + case "url": + seenURL = string(data) + case "title": + seenTitle = string(data) + case "feature_find_main": + seenFeature = string(data) + case "labels": + seenLabels = append(seenLabels, string(data)) + case "resource": + // First line is JSON header, then newline, then content + all := string(data) + idx := strings.IndexByte(all, '\n') + if idx == -1 { + t.Fatalf("resource content missing header separator") + } + headerJSON := all[:idx] + resourceBody = all[idx+1:] + if err := json.Unmarshal([]byte(headerJSON), &resourceHeader); err != nil { + t.Fatalf("invalid resource header JSON: %v", err) + } + } + } + + if seenURL != entryURL { + t.Errorf("expected url %s, got %s", entryURL, seenURL) + } + if seenTitle != entryTitle { + t.Errorf("expected title %s, got %s", entryTitle, seenTitle) + } + if seenFeature != "false" { + t.Errorf("expected feature_find_main to be 'false', got %s", seenFeature) + } + if len(seenLabels) != 2 || seenLabels[0] != "tag1" || seenLabels[1] != "tag2" { + t.Errorf("unexpected labels: %#v", seenLabels) + } + if resourceHeader == nil { + t.Fatalf("missing resource header") + } + if hURL, _ := resourceHeader["url"].(string); hURL != entryURL { + t.Errorf("expected resource header url %s, got %v", entryURL, hURL) + } + if headers, ok := resourceHeader["headers"].(map[string]any); ok { + if ct, _ := headers["content-type"].(string); ct != "text/html; charset=utf-8" { + t.Errorf("expected resource header content-type text/html; charset=utf-8, got %v", ct) + } + } else { + t.Errorf("missing resource header 'headers' field") + } + if resourceBody != entryContent { + t.Errorf("expected resource body %q, got %q", entryContent, resourceBody) + } + + w.WriteHeader(http.StatusOK) + }, + }, + { + name: "error when server returns 400", + onlyURL: true, + labels: labels, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + }, + wantErr: true, + errContains: "unable to create bookmark", + }, + { + name: "error when missing baseURL or apiKey", + onlyURL: true, + baseURL: "", + apiKey: "", + labels: labels, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: nil, + wantErr: true, + errContains: "missing base URL or API key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var serverURL string + if tt.serverResponse != nil { + srv := httptest.NewServer(http.HandlerFunc(tt.serverResponse)) + defer srv.Close() + serverURL = srv.URL + } + baseURL := tt.baseURL + if baseURL == "" { + baseURL = serverURL + } + apiKey := tt.apiKey + if apiKey == "" { + apiKey = "test-api-key" + } + + client := NewClient(baseURL, apiKey, tt.labels, tt.onlyURL) + err := client.CreateBookmark(tt.entryURL, tt.entryTitle, tt.entryContent) + + if tt.wantErr { + if err == nil { + t.Fatalf("expected error, got none") + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Fatalf("expected error containing %q, got %q", tt.errContains, err.Error()) + } + } else if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + +func TestNewClient(t *testing.T) { + baseURL := "https://readeck.example.com" + apiKey := "key" + labels := "tag1,tag2" + onlyURL := true + + c := NewClient(baseURL, apiKey, labels, onlyURL) + if c.baseURL != baseURL { + t.Errorf("expected baseURL %s, got %s", baseURL, c.baseURL) + } + if c.apiKey != apiKey { + t.Errorf("expected apiKey %s, got %s", apiKey, c.apiKey) + } + if c.labels != labels { + t.Errorf("expected labels %s, got %s", labels, c.labels) + } + if c.onlyURL != onlyURL { + t.Errorf("expected onlyURL %v, got %v", onlyURL, c.onlyURL) + } +} diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index a3d1b04bdad..8eba68f6a5d 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Readeck-URL", "form.integration.readeck_labels": "Readeck-Labels", "form.integration.readeck_only_url": "Nur URL senden (anstelle des vollständigen Inhalts)", + "form.integration.readeck_push_activate": "Neue Artikel automatisch in Readeck speichern", "form.integration.readwise_activate": "Artikel in Readwise Reader speichern", "form.integration.readwise_api_key": "Readwise-Reader-Zugangstoken", "form.integration.readwise_api_key_link": "Erhalten Sie Ihren Readwise-Zugangstoken", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "gestern", "tooltip.keyboard_shortcuts": "Tastenkürzel: %s", "tooltip.logged_user": "Angemeldet als %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json index 4739c786b25..1fb4b8040e8 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Τελικό σημείο Readeck API", "form.integration.readeck_labels": "Ετικέτες Readeck", "form.integration.readeck_only_url": "Αποστολή μόνο URL (αντί για πλήρες περιεχόμενο)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Αποθήκευση καταχωρήσεων στο Readwise Reader", "form.integration.readwise_api_key": "Διακριτικό πρόσβασης Readwise Reader", "form.integration.readwise_api_key_link": "Λήψη του διακριτικού πρόσβασης Readwise", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "χθες", "tooltip.keyboard_shortcuts": "Συντόμευση πληκτρολογίου: % s", "tooltip.logged_user": "Συνδεδεμένος/η ως %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json index eacaf03f00e..fdd567d2a96 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Readeck URL", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "Send only URL (instead of full content)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Save entries to Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Get your Readwise Access Token", diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json index eb561eb13f7..eb7c67daba7 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Acceso API de Readeck", "form.integration.readeck_labels": "Etiquetas de Readeck", "form.integration.readeck_only_url": "Enviar solo URL (en lugar de contenido completo)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Guardar artículos en Readwise Reader", "form.integration.readwise_api_key": "Token de acceso a Readwise Reader", "form.integration.readwise_api_key_link": "Obtener tu token de acceso a Readwise", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "ayer", "tooltip.keyboard_shortcuts": "Atajo de teclado: %s", "tooltip.logged_user": "Registrado como %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json index d3c4fe3bfd0..475a79352c2 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Readeck API-päätepiste", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "Lähetä vain URL-osoite (koko sisällön sijaan)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Save entries to Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Get your Readwise Access Token", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "eilen", "tooltip.keyboard_shortcuts": "Pikanäppäin: %s", "tooltip.logged_user": "Kirjautunut %s-käyttäjänä" -} \ No newline at end of file +} diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json index f0fa2bb3772..f7a3c0fcb56 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "URL de l'API de Readeck", "form.integration.readeck_labels": "Libellés Readeck", "form.integration.readeck_only_url": "Envoyer uniquement l'URL (au lieu du contenu complet)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Enregistrer les entrées vers Readwise Reader", "form.integration.readwise_api_key": "Jeton d'accès au lecteur Readwise", "form.integration.readwise_api_key_link": "Obtenez votre jeton d'accès Readwise", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "hier", "tooltip.keyboard_shortcuts": "Raccourci clavier : %s", "tooltip.logged_user": "Connecté en tant que %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json index 7e46405c703..464ef753944 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Readeck यूआरएल", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "केवल URL भेजें (पूर्ण सामग्री के बजाय)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Save entries to Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Get your Readwise Access Token", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "कल", "tooltip.keyboard_shortcuts": "कुंजीपटल शॉर्टकट: %s", "tooltip.logged_user": "%s के रूप में लॉग इन किया" -} \ No newline at end of file +} diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json index 0895dec55da..12ef8c4a70c 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -304,6 +304,7 @@ "form.integration.readeck_endpoint": "Titik URL API Readeck", "form.integration.readeck_labels": "Tagar Readeck", "form.integration.readeck_only_url": "Kirim hanya URL (alih-alih konten penuh)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Simpan artikel ke Readwise", "form.integration.readwise_api_key": "Token Akses Readwise", "form.integration.readwise_api_key_link": "Dapatkan Token Akses Readwise Anda", @@ -612,4 +613,4 @@ "time_elapsed.yesterday": "kemarin", "tooltip.keyboard_shortcuts": "Pintasan Papan Tik: %s", "tooltip.logged_user": "Masuk sebagai %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json index 2aafd95e036..4b00e6e931e 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Endpoint dell'API di Readeck", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "Invia solo URL (invece del contenuto completo)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Save entries to Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Get your Readwise Access Token", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "ieri", "tooltip.keyboard_shortcuts": "Scorciatoia da tastiera: %s", "tooltip.logged_user": "Autenticato come %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json index e11ad665f6a..bc487baa32c 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -304,6 +304,7 @@ "form.integration.readeck_endpoint": "Readeck の API Endpoint", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "URL のみを送信 (完全なコンテンツではなく)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Save entries to Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Get your Readwise Access Token", @@ -612,4 +613,4 @@ "time_elapsed.yesterday": "昨日", "tooltip.keyboard_shortcuts": "キーボードショートカット: %s", "tooltip.logged_user": "%s としてログイン中" -} \ No newline at end of file +} diff --git a/internal/locale/translations/nan_Latn_pehoeji.json b/internal/locale/translations/nan_Latn_pehoeji.json index a72f7e898c4..eb881aa863e 100644 --- a/internal/locale/translations/nan_Latn_pehoeji.json +++ b/internal/locale/translations/nan_Latn_pehoeji.json @@ -304,6 +304,7 @@ "form.integration.readeck_endpoint": "Readeck API thâu", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "Kan-na thoân bāng-chí (m̄ sī oân-chéng ê lōe-iông)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Pó-chûn siau-sit kàu Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Acess Token", "form.integration.readwise_api_key_link": "Chhú-tek lí ê Readwise Acess Token", @@ -612,4 +613,4 @@ "time_elapsed.yesterday": "cha-hng", "tooltip.keyboard_shortcuts": "Khoài-sok khí:%s", "tooltip.logged_user": "Chit-má teng-lo̍k--ê: %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json index ff5c6ab2d83..f2252588e71 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Readeck URL", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "Alleen URL verzenden (in plaats van volledige inhoud)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Artikelen opslaan in Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Readwise Access Token ophalen", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "gisteren", "tooltip.keyboard_shortcuts": "Sneltoets: %s", "tooltip.logged_user": "Ingelogd als %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json index 694dfe2320c..80609a97342 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -310,6 +310,7 @@ "form.integration.readeck_endpoint": "Adres URL Readeck", "form.integration.readeck_labels": "Etykiety Readeck", "form.integration.readeck_only_url": "Wysyłaj tylko adres URL (zamiast pełnej treści)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Zapisuj wpisy w czytniku Readwise", "form.integration.readwise_api_key": "Token dostępu do czytnika Readwise", "form.integration.readwise_api_key_link": "Zdobądź token dostępu Readwise", @@ -648,4 +649,4 @@ "time_elapsed.yesterday": "wczoraj", "tooltip.keyboard_shortcuts": "Skróty klawiszowe: %s", "tooltip.logged_user": "Zalogowany jako %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json index 03bcd7462af..7c824188a13 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Endpoint de API do Readeck", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "Enviar apenas URL (em vez de conteúdo completo)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Salvar itens no Readwise Reader", "form.integration.readwise_api_key": "Token de acesso do Readwise Reader", "form.integration.readwise_api_key_link": "Obtenha seu token de acesso do Readwise", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "ontem", "tooltip.keyboard_shortcuts": "Atalho do teclado: %s", "tooltip.logged_user": "Autenticado como %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/ro_RO.json b/internal/locale/translations/ro_RO.json index 76dce4eca8c..5b160cca44d 100644 --- a/internal/locale/translations/ro_RO.json +++ b/internal/locale/translations/ro_RO.json @@ -310,6 +310,7 @@ "form.integration.readeck_endpoint": "URL Readeck", "form.integration.readeck_labels": "Etichete Readeck", "form.integration.readeck_only_url": "Trimite numai URL (în loc de tot conținutul)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Salvare înregistrări în Readwise Reader", "form.integration.readwise_api_key": "Token Acces Readwise Reader", "form.integration.readwise_api_key_link": "Obțineți Token-ul de Acess pe Readwise", @@ -648,4 +649,4 @@ "time_elapsed.yesterday": "ieri", "tooltip.keyboard_shortcuts": "Scurtături Tastatură: %s", "tooltip.logged_user": "Atentificat ca %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json index 46faf202c61..1a5b3d2895d 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -310,6 +310,7 @@ "form.integration.readeck_endpoint": "Конечная точка Readeck API", "form.integration.readeck_labels": "Теги Readeck", "form.integration.readeck_only_url": "Отправлять только ссылку (без содержимого)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Сохранить статьи в Readwise", "form.integration.readwise_api_key": "Токен доступа в Readwise", "form.integration.readwise_api_key_link": "Получить токен доступа Readwise", @@ -648,4 +649,4 @@ "time_elapsed.yesterday": "вчера", "tooltip.keyboard_shortcuts": "Сочетания клавиш: %s", "tooltip.logged_user": "Авторизован как %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json index 06a9e1d1137..07d21afd4f5 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -307,6 +307,7 @@ "form.integration.readeck_endpoint": "Readeck API Uç Noktası", "form.integration.readeck_labels": "Readeck Etiketleri", "form.integration.readeck_only_url": "Yalnızca URL gönder (tam makale yerine)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Makaleleri Readwise Reader'a kaydet", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Readwise Access Token'ınızı alın", @@ -630,4 +631,4 @@ "time_elapsed.yesterday": "dün", "tooltip.keyboard_shortcuts": "Klavye Kısayolu: %s", "tooltip.logged_user": "%s olarak giriş yapıldı" -} \ No newline at end of file +} diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json index 854b7e7251e..c33170b80cc 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -310,6 +310,7 @@ "form.integration.readeck_endpoint": "Readeck URL", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "Надіслати лише URL (замість повного вмісту)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "Save entries to Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader Access Token", "form.integration.readwise_api_key_link": "Get your Readwise Access Token", @@ -648,4 +649,4 @@ "time_elapsed.yesterday": "вчора", "tooltip.keyboard_shortcuts": "Комбінація клавіш: %s", "tooltip.logged_user": "Здійснено вхід як %s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index c16cf72a04a..b719b806faa 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -304,6 +304,7 @@ "form.integration.readeck_endpoint": "Readeck API 端点", "form.integration.readeck_labels": "Readeck 标签", "form.integration.readeck_only_url": "仅发送 URL(而非完整内容)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "保存条目到 Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader 访问令牌", "form.integration.readwise_api_key_link": "获取你的 Readwise 访问令牌", @@ -612,4 +613,4 @@ "time_elapsed.yesterday": "昨天", "tooltip.keyboard_shortcuts": "键盘快捷键:%s", "tooltip.logged_user": "登录用户:%s" -} \ No newline at end of file +} diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json index 3661ecdd794..d45de3d3cac 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -304,6 +304,7 @@ "form.integration.readeck_endpoint": "Readeck API 端點", "form.integration.readeck_labels": "Readeck Labels", "form.integration.readeck_only_url": "僅傳送網址(而不是完整內容)", + "form.integration.readeck_push_activate": "Automatically push new entries to Readeck", "form.integration.readwise_activate": "儲存文章到 Readwise Reader", "form.integration.readwise_api_key": "Readwise Reader 存取金鑰", "form.integration.readwise_api_key_link": "取得您的 Readwise 存取金鑰", @@ -612,4 +613,4 @@ "time_elapsed.yesterday": "昨天", "tooltip.keyboard_shortcuts": "快捷鍵:%s", "tooltip.logged_user": "目前登入 %s" -} \ No newline at end of file +} diff --git a/internal/model/integration.go b/internal/model/integration.go index edc95b9dcb1..b523bc936e3 100644 --- a/internal/model/integration.go +++ b/internal/model/integration.go @@ -78,6 +78,7 @@ type Integration struct { AppriseURL string AppriseServicesURL string ReadeckEnabled bool + ReadeckPushEnabled bool ReadeckURL string ReadeckAPIKey string ReadeckLabels string diff --git a/internal/storage/integration.go b/internal/storage/integration.go index 6f104aa4316..5c4bdc5f7fb 100644 --- a/internal/storage/integration.go +++ b/internal/storage/integration.go @@ -178,6 +178,7 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { readeck_api_key, readeck_labels, readeck_only_url, + readeck_push_enabled, shiori_enabled, shiori_url, shiori_username, @@ -306,6 +307,7 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.ReadeckAPIKey, &integration.ReadeckLabels, &integration.ReadeckOnlyURL, + &integration.ReadeckPushEnabled, &integration.ShioriEnabled, &integration.ShioriURL, &integration.ShioriUsername, @@ -494,9 +496,10 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { linktaco_tags=$117, linktaco_visibility=$118, archiveorg_enabled=$119, - linkwarden_collection_id=$120 + linkwarden_collection_id=$120, + readeck_push_enabled=$121 WHERE - user_id=$121 + user_id=$122 ` _, err := s.db.Exec( query, @@ -620,6 +623,7 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.LinktacoVisibility, integration.ArchiveorgEnabled, integration.LinkwardenCollectionID, + integration.ReadeckPushEnabled, integration.UserID, ) diff --git a/internal/template/templates/views/integrations.html b/internal/template/templates/views/integrations.html index 813dd763468..e3052e88f25 100644 --- a/internal/template/templates/views/integrations.html +++ b/internal/template/templates/views/integrations.html @@ -509,6 +509,10 @@

{{ t "page.integrations.title" }}

{{ t "form.integration.readeck_activate" }} + + diff --git a/internal/ui/form/integration.go b/internal/ui/form/integration.go index ef95aad0973..166ce5de918 100644 --- a/internal/ui/form/integration.go +++ b/internal/ui/form/integration.go @@ -81,6 +81,7 @@ type IntegrationForm struct { AppriseURL string AppriseServicesURL string ReadeckEnabled bool + ReadeckPushEnabled bool ReadeckURL string ReadeckAPIKey string ReadeckLabels string @@ -203,6 +204,7 @@ func (i IntegrationForm) Merge(integration *model.Integration) { integration.AppriseServicesURL = i.AppriseServicesURL integration.AppriseURL = i.AppriseURL integration.ReadeckEnabled = i.ReadeckEnabled + integration.ReadeckPushEnabled = i.ReadeckPushEnabled integration.ReadeckURL = i.ReadeckURL integration.ReadeckAPIKey = i.ReadeckAPIKey integration.ReadeckLabels = i.ReadeckLabels @@ -327,6 +329,7 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm { AppriseURL: r.FormValue("apprise_url"), AppriseServicesURL: r.FormValue("apprise_services_url"), ReadeckEnabled: r.FormValue("readeck_enabled") == "1", + ReadeckPushEnabled: r.FormValue("readeck_push_enabled") == "1", ReadeckURL: r.FormValue("readeck_url"), ReadeckAPIKey: r.FormValue("readeck_api_key"), ReadeckLabels: r.FormValue("readeck_labels"), diff --git a/internal/ui/integration_show.go b/internal/ui/integration_show.go index 77b05bff666..260e6195ce7 100644 --- a/internal/ui/integration_show.go +++ b/internal/ui/integration_show.go @@ -94,6 +94,7 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) { AppriseURL: integration.AppriseURL, AppriseServicesURL: integration.AppriseServicesURL, ReadeckEnabled: integration.ReadeckEnabled, + ReadeckPushEnabled: integration.ReadeckPushEnabled, ReadeckURL: integration.ReadeckURL, ReadeckAPIKey: integration.ReadeckAPIKey, ReadeckLabels: integration.ReadeckLabels,