From 8129500296896e9632f72fd80e029cd48fcac510 Mon Sep 17 00:00:00 2001 From: Kevin Sicong Jiang Date: Wed, 10 Sep 2025 09:47:51 +0900 Subject: [PATCH] feat(integration): add support for Wallabag tags --- internal/database/migrations.go | 7 + internal/integration/integration.go | 3 + internal/integration/wallabag/wallabag.go | 13 +- .../integration/wallabag/wallabag_test.go | 321 ++++++++++++++++++ internal/locale/translations/de_DE.json | 1 + internal/locale/translations/el_EL.json | 1 + internal/locale/translations/en_US.json | 1 + internal/locale/translations/es_ES.json | 1 + internal/locale/translations/fi_FI.json | 1 + internal/locale/translations/fr_FR.json | 1 + internal/locale/translations/hi_IN.json | 1 + internal/locale/translations/id_ID.json | 1 + internal/locale/translations/it_IT.json | 1 + internal/locale/translations/ja_JP.json | 1 + .../locale/translations/nan_Latn_pehoeji.json | 1 + internal/locale/translations/nl_NL.json | 1 + internal/locale/translations/pl_PL.json | 1 + internal/locale/translations/pt_BR.json | 1 + internal/locale/translations/ro_RO.json | 1 + internal/locale/translations/ru_RU.json | 1 + internal/locale/translations/tr_TR.json | 1 + internal/locale/translations/uk_UA.json | 1 + internal/locale/translations/zh_CN.json | 1 + internal/locale/translations/zh_TW.json | 1 + internal/model/integration.go | 1 + internal/storage/integration.go | 204 +++++------ .../templates/views/integrations.html | 3 + internal/ui/form/integration.go | 3 + internal/ui/integration_show.go | 1 + 29 files changed, 471 insertions(+), 105 deletions(-) create mode 100644 internal/integration/wallabag/wallabag_test.go diff --git a/internal/database/migrations.go b/internal/database/migrations.go index a1858011..a9d231a4 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -1151,4 +1151,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 wallabag_tags text default ''; + ` + _, err = tx.Exec(sql) + return err + }, } diff --git a/internal/integration/integration.go b/internal/integration/integration.go index 2cd68ad8..bec8b436 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -108,6 +108,7 @@ func SendEntry(entry *model.Entry, userIntegrations *model.Integration) { if userIntegrations.WallabagEnabled { slog.Debug("Sending entry to Wallabag", slog.Int64("user_id", userIntegrations.UserID), + slog.String("user_tags", userIntegrations.WallabagTags), slog.Int64("entry_id", entry.ID), slog.String("entry_url", entry.URL), ) @@ -118,12 +119,14 @@ func SendEntry(entry *model.Entry, userIntegrations *model.Integration) { userIntegrations.WallabagClientSecret, userIntegrations.WallabagUsername, userIntegrations.WallabagPassword, + userIntegrations.WallabagTags, userIntegrations.WallabagOnlyURL, ) if err := client.CreateEntry(entry.URL, entry.Title, entry.Content); err != nil { slog.Error("Unable to send entry to Wallabag", slog.Int64("user_id", userIntegrations.UserID), + slog.String("user_tags", userIntegrations.WallabagTags), slog.Int64("entry_id", entry.ID), slog.String("entry_url", entry.URL), slog.Any("error", err), diff --git a/internal/integration/wallabag/wallabag.go b/internal/integration/wallabag/wallabag.go index 110e6561..686388d6 100644 --- a/internal/integration/wallabag/wallabag.go +++ b/internal/integration/wallabag/wallabag.go @@ -24,11 +24,12 @@ type Client struct { clientSecret string username string password string + tags string onlyURL bool } -func NewClient(baseURL, clientID, clientSecret, username, password string, onlyURL bool) *Client { - return &Client{baseURL, clientID, clientSecret, username, password, onlyURL} +func NewClient(baseURL, clientID, clientSecret, username, password, tags string, onlyURL bool) *Client { + return &Client{baseURL, clientID, clientSecret, username, password, tags, onlyURL} } func (c *Client) CreateEntry(entryURL, entryTitle, entryContent string) error { @@ -41,10 +42,10 @@ func (c *Client) CreateEntry(entryURL, entryTitle, entryContent string) error { return err } - return c.createEntry(accessToken, entryURL, entryTitle, entryContent) + return c.createEntry(accessToken, entryURL, entryTitle, entryContent, c.tags) } -func (c *Client) createEntry(accessToken, entryURL, entryTitle, entryContent string) error { +func (c *Client) createEntry(accessToken, entryURL, entryTitle, entryContent, tags string) error { apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/entries.json") if err != nil { return fmt.Errorf("wallbag: unable to generate entries endpoint: %v", err) @@ -58,6 +59,7 @@ func (c *Client) createEntry(accessToken, entryURL, entryTitle, entryContent str URL: entryURL, Title: entryTitle, Content: entryContent, + Tags: tags, }) if err != nil { return fmt.Errorf("wallbag: unable to encode request body: %v", err) @@ -81,7 +83,7 @@ func (c *Client) createEntry(accessToken, entryURL, entryTitle, entryContent str defer response.Body.Close() if response.StatusCode >= 400 { - return fmt.Errorf("wallabag: unable to get access token: url=%s status=%d", apiEndpoint, response.StatusCode) + return fmt.Errorf("wallabag: unable to get save entry: url=%s status=%d", apiEndpoint, response.StatusCode) } return nil @@ -140,4 +142,5 @@ type createEntryRequest struct { URL string `json:"url"` Title string `json:"title"` Content string `json:"content,omitempty"` + Tags string `json:"tags,omitempty"` } diff --git a/internal/integration/wallabag/wallabag_test.go b/internal/integration/wallabag/wallabag_test.go new file mode 100644 index 00000000..bdca44dd --- /dev/null +++ b/internal/integration/wallabag/wallabag_test.go @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package wallabag + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestCreateEntry(t *testing.T) { + entryURL := "https://example.com" + entryTitle := "title" + entryContent := "content" + tags := "tag1,tag2,tag3" + + tests := []struct { + name string + username string + password string + clientID string + clientSecret string + tags string + onlyURL bool + entryURL string + entryTitle string + entryContent string + serverResponse func(w http.ResponseWriter, r *http.Request) + wantErr bool + errContains string + }{ + { + name: "successful entry creation with url only", + wantErr: false, + onlyURL: true, + username: "username", + password: "password", + clientID: "clientId", + clientSecret: "clientSecret", + tags: tags, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/oauth/v2/token") { + // Return success response + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]any{ + "access_token": "test-token", + "expires_in": 3600, + "refresh_token": "token", + "scope": "scope", + "token_type": "token_type", + }) + return + } + // Verify authorization header + auth := r.Header.Get("Authorization") + if auth != "Bearer test-token" { + t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth) + } + // Verify content type + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Expected Content-Type 'application/json', got %s", contentType) + } + // Parse and verify request + body, _ := io.ReadAll(r.Body) + var req map[string]any + if err := json.Unmarshal(body, &req); err != nil { + t.Errorf("Failed to parse request body: %v", err) + } + if requstEntryURL := req["url"]; requstEntryURL != entryURL { + t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL) + } + if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle { + t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle) + } + if _, ok := req["content"]; ok { + t.Errorf("Expected entryContent to be empty, got value") + } + if requestTags := req["tags"]; requestTags != tags { + t.Errorf("Expected tags %s, got %s", tags, requestTags) + } // Return success response + w.WriteHeader(http.StatusOK) + }, + errContains: "", + }, + { + name: "successful entry creation with content", + wantErr: false, + onlyURL: false, + username: "username", + password: "password", + clientID: "clientId", + clientSecret: "clientSecret", + tags: tags, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/oauth/v2/token") { + // Return success response + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]any{ + "access_token": "test-token", + "expires_in": 3600, + "refresh_token": "token", + "scope": "scope", + "token_type": "token_type", + }) + return + } + // Verify authorization header + auth := r.Header.Get("Authorization") + if auth != "Bearer test-token" { + t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth) + } + // Verify content type + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Expected Content-Type 'application/json', got %s", contentType) + } + // Parse and verify request + body, _ := io.ReadAll(r.Body) + var req map[string]any + if err := json.Unmarshal(body, &req); err != nil { + t.Errorf("Failed to parse request body: %v", err) + } + if requstEntryURL := req["url"]; requstEntryURL != entryURL { + t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL) + } + if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle { + t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle) + } + if requestEntryContent := req["content"]; requestEntryContent != entryContent { + t.Errorf("Expected entryContent %s, got %s", entryContent, requestEntryContent) + } + if requestTags := req["tags"]; requestTags != tags { + t.Errorf("Expected tags %s, got %s", tags, requestTags) + } // Return success response + w.WriteHeader(http.StatusOK) + }, + errContains: "", + }, + { + name: "failed when unable to decode accessToken response", + wantErr: true, + onlyURL: true, + username: "username", + password: "password", + clientID: "clientId", + clientSecret: "clientSecret", + tags: tags, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/oauth/v2/token") { + // Return success response + w.WriteHeader(http.StatusOK) + w.Write([]byte("invalid json")) + return + } + t.Error("Server should not be called when failed to get accessToken") + }, + errContains: "unable to decode token response", + }, + { + name: "failed when saving entry", + wantErr: true, + onlyURL: true, + username: "username", + password: "password", + clientID: "clientId", + clientSecret: "clientSecret", + tags: tags, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/oauth/v2/token") { + // Return success response + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]any{ + "access_token": "test-token", + "expires_in": 3600, + "refresh_token": "token", + "scope": "scope", + "token_type": "token_type", + }) + return + } + w.WriteHeader(http.StatusUnauthorized) + }, + errContains: "unable to get save entry", + }, + { + name: "failure due to no accessToken", + wantErr: true, + onlyURL: false, + username: "username", + password: "password", + clientID: "clientId", + clientSecret: "clientSecret", + tags: tags, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/oauth/v2/token") { + // Return error response + w.WriteHeader(http.StatusUnauthorized) + return + } + t.Error("Server should not be called when failed to get accessToken") + }, + errContains: "unable to get access token", + }, + { + name: "failure due to missing client parameters", + wantErr: true, + onlyURL: false, + tags: tags, + entryURL: entryURL, + entryTitle: entryTitle, + entryContent: entryContent, + serverResponse: func(w http.ResponseWriter, r *http.Request) { + t.Error("Server should not be called when failed to get accessToken") + }, + errContains: "wallabag: missing base URL, client ID, client secret, username or password", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create test server if we have a server response function + var serverURL string + if tt.serverResponse != nil { + server := httptest.NewServer(http.HandlerFunc(tt.serverResponse)) + defer server.Close() + serverURL = server.URL + } + + // Create client with test server URL + client := NewClient(serverURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL) + + // Call CreateBookmark + err := client.CreateEntry(tt.entryURL, tt.entryTitle, tt.entryContent) + + // Check error expectations + if tt.wantErr { + if err == nil { + t.Errorf("Expected error but got none") + } else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("Expected error containing '%s', got '%s'", tt.errContains, err.Error()) + } + } else { + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + }) + } +} + +func TestNewClient(t *testing.T) { + tests := []struct { + name string + baseURL string + clientID string + clientSecret string + username string + password string + tags string + onlyURL bool + }{ + { + name: "with all parameters", + baseURL: "https://wallabag.example.com", + clientID: "clientID", + clientSecret: "clientSecret", + username: "wallabag", + password: "wallabag", + tags: "", + onlyURL: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := NewClient(tt.baseURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL) + + if client.baseURL != tt.baseURL { + t.Errorf("Expected.baseURL %s, got %s", tt.baseURL, client.baseURL) + } + if client.username != tt.username { + t.Errorf("Expected username %s, got %s", tt.username, client.username) + } + if client.password != tt.password { + t.Errorf("Expected password %s, got %s", tt.password, client.password) + } + if client.clientID != tt.clientID { + t.Errorf("Expected clientID %s, got %s", tt.clientID, client.clientID) + } + if client.clientSecret != tt.clientSecret { + t.Errorf("Expected clientSecret %s, got %s", tt.clientSecret, client.clientSecret) + } + if client.tags != tt.tags { + t.Errorf("Expected tags %s, got %s", tt.tags, client.tags) + } + if client.onlyURL != tt.onlyURL { + t.Errorf("Expected onlyURL %v, got %v", tt.onlyURL, client.onlyURL) + } + }) + } +} diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index 04f3bad8..14d947bc 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Nur URL senden (anstelle des vollständigen Inhalts)", "form.integration.wallabag_password": "Wallabag-Passwort", "form.integration.wallabag_username": "Wallabag-Benutzername", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Webhooks aktivieren", "form.integration.webhook_secret": "Webhook-Geheimnis", "form.integration.webhook_url": "Standard-Webhook-URL", diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json index d047408b..e2bdff56 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Αποστολή μόνο URL (αντί για πλήρες περιεχόμενο)", "form.integration.wallabag_password": "Wallabag Κωδικός Πρόσβασης", "form.integration.wallabag_username": "Όνομα Χρήστη Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Ενεργοποίηση Webhooks", "form.integration.webhook_secret": "Μυστικό Webhooks", "form.integration.webhook_url": "Προεπιλεγμένη διεύθυνση URL Webhook", diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json index 4637537b..93fbddde 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Send only URL (instead of full content)", "form.integration.wallabag_password": "Wallabag Password", "form.integration.wallabag_username": "Wallabag Username", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Enable Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook URL", diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json index 39a60698..7afc7f5f 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Enviar solo URL (en lugar de contenido completo)", "form.integration.wallabag_password": "Contraseña de Wallabag", "form.integration.wallabag_username": "Nombre de usuario de Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Habilitar Webhooks", "form.integration.webhook_secret": "Secreto de Webhooks", "form.integration.webhook_url": "Defecto URL de Webhook", diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json index 27b5380f..ba6a31c7 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Lähetä vain URL-osoite (koko sisällön sijaan)", "form.integration.wallabag_password": "Wallabag-salasana", "form.integration.wallabag_username": "Wallabag-käyttäjätunnus", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Enable Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook URL", diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json index 38bd11a4..e0215822 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Envoyer uniquement l'URL (au lieu du contenu complet)", "form.integration.wallabag_password": "Mot de passe de Wallabag", "form.integration.wallabag_username": "Nom d'utilisateur de Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Activer le webhook", "form.integration.webhook_secret": "Secret du webhook", "form.integration.webhook_url": "URL du webhook", diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json index e68c06d3..28914da5 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "केवल URL भेजें (पूर्ण सामग्री के बजाय)", "form.integration.wallabag_password": "वालाबैग पासवर्ड", "form.integration.wallabag_username": "वालाबैग उपयोगकर्ता नाम", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Enable Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook URL", diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json index 4bd54f1c..0bfa1f89 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -330,6 +330,7 @@ "form.integration.wallabag_only_url": "Kirim hanya URL (alih-alih konten penuh)", "form.integration.wallabag_password": "Kata Sandi Wallabag", "form.integration.wallabag_username": "Nama Pengguna Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Aktifkan Webhook", "form.integration.webhook_secret": "Rahasia Webhook", "form.integration.webhook_url": "URL Webhook baku", diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json index 73bd27cf..fa896696 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Invia solo URL (invece del contenuto completo)", "form.integration.wallabag_password": "Password dell'account Wallabag", "form.integration.wallabag_username": "Nome utente dell'account Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Enable Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook URL", diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json index 4144e134..cad72160 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -330,6 +330,7 @@ "form.integration.wallabag_only_url": "URL のみを送信 (完全なコンテンツではなく)", "form.integration.wallabag_password": "Wallabag のパスワード", "form.integration.wallabag_username": "Wallabag のユーザー名", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Enable Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook URL", diff --git a/internal/locale/translations/nan_Latn_pehoeji.json b/internal/locale/translations/nan_Latn_pehoeji.json index 6700887f..0acca29b 100644 --- a/internal/locale/translations/nan_Latn_pehoeji.json +++ b/internal/locale/translations/nan_Latn_pehoeji.json @@ -330,6 +330,7 @@ "form.integration.wallabag_only_url": "Kan-na thoân bāng-chí (m̄ sī oân-chéng ê lōe-iông)", "form.integration.wallabag_password": "Wallabag bi̍t-bé", "form.integration.wallabag_username": "Wallabag kháu-chō miâ", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Khai-sí iōng Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook bāng-chí", diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json index 2f33e4eb..1c5b48c2 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Alleen URL verzenden (in plaats van volledige inhoud)", "form.integration.wallabag_password": "Wallabag wachtwoord", "form.integration.wallabag_username": "Wallabag gebruikersnaam", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Webhooks activeren", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Standard Webhook URL", diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json index a45e03c9..6b6f3692 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -333,6 +333,7 @@ "form.integration.wallabag_client_id": "Identyfikator klienta Wallabag", "form.integration.wallabag_client_secret": "Tajny klucz klienta Wallabag", "form.integration.wallabag_endpoint": "Podstawowy adres URL Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.wallabag_only_url": "Przesyłaj tylko adres URL (zamiast pełnej treści)", "form.integration.wallabag_password": "Hasło do Wallabag", "form.integration.wallabag_username": "Login do Wallabag", diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json index 49ea06db..8af38a5f 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Enviar apenas URL (em vez de conteúdo completo)", "form.integration.wallabag_password": "Senha do Wallabag", "form.integration.wallabag_username": "Nome de usuário do Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Enable Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook URL", diff --git a/internal/locale/translations/ro_RO.json b/internal/locale/translations/ro_RO.json index 80c0b048..56a97bb9 100644 --- a/internal/locale/translations/ro_RO.json +++ b/internal/locale/translations/ro_RO.json @@ -333,6 +333,7 @@ "form.integration.wallabag_client_id": "ID Client Wallabag", "form.integration.wallabag_client_secret": "Secret Client Wallabag", "form.integration.wallabag_endpoint": "URL Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.wallabag_only_url": "Trimite numai URL-ul (fără conținut complet)", "form.integration.wallabag_password": "Parolă Wallabag", "form.integration.wallabag_username": "Utilizator Wallabag", diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json index ce88db26..dde8a3da 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -333,6 +333,7 @@ "form.integration.wallabag_client_id": "Номер клиента Wallabag", "form.integration.wallabag_client_secret": "Секретный код клиента Wallabag", "form.integration.wallabag_endpoint": "URL-адрес базы Валлабаг", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.wallabag_only_url": "Отправлять только ссылку (без содержимого)", "form.integration.wallabag_password": "Пароль Wallabag", "form.integration.wallabag_username": "Имя пользователя Wallabag", diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json index 9cc5bcf0..89abb7f9 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -333,6 +333,7 @@ "form.integration.wallabag_only_url": "Yalnızca URL gönder (tam makale yerine)", "form.integration.wallabag_password": "Wallabag Parolası", "form.integration.wallabag_username": "Wallabag Kullanıcı Adı", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "Webhook'u etkinleştir", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook URL", diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json index 047a6402..c9ff7ff0 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -333,6 +333,7 @@ "form.integration.wallabag_client_id": "Wallabag Client ID", "form.integration.wallabag_client_secret": "Wallabag Client Secret", "form.integration.wallabag_endpoint": "Базова URL-адреса Wallabag", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.wallabag_only_url": "Надіслати лише URL (замість повного вмісту)", "form.integration.wallabag_password": "Пароль Wallabag", "form.integration.wallabag_username": "Ім’я користувача Wallabag", diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index 0bdb8033..4e7516d1 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -330,6 +330,7 @@ "form.integration.wallabag_only_url": "仅发送 URL(而非完整内容)", "form.integration.wallabag_password": "Wallabag 密码", "form.integration.wallabag_username": "Wallabag 用户名", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "启用 Webhooks", "form.integration.webhook_secret": "Webhooks 密钥", "form.integration.webhook_url": "默认 Webhook URL", diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json index 27373315..59a61b90 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -330,6 +330,7 @@ "form.integration.wallabag_only_url": "僅傳送網址(而不是完整內容)", "form.integration.wallabag_password": "Wallabag 密碼", "form.integration.wallabag_username": "Wallabag 使用者名稱", + "form.integration.wallabag_tags": "Wallabag Tags", "form.integration.webhook_activate": "啟用 Webhooks", "form.integration.webhook_secret": "Webhooks Secret", "form.integration.webhook_url": "Default Webhook 網址", diff --git a/internal/model/integration.go b/internal/model/integration.go index b948e25c..bff86357 100644 --- a/internal/model/integration.go +++ b/internal/model/integration.go @@ -29,6 +29,7 @@ type Integration struct { WallabagClientSecret string WallabagUsername string WallabagPassword string + WallabagTags string NunuxKeeperEnabled bool NunuxKeeperURL string NunuxKeeperAPIKey string diff --git a/internal/storage/integration.go b/internal/storage/integration.go index b50ee5a1..761a8dd8 100644 --- a/internal/storage/integration.go +++ b/internal/storage/integration.go @@ -130,6 +130,7 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { wallabag_client_secret, wallabag_username, wallabag_password, + wallabag_tags, notion_enabled, notion_token, notion_page_id, @@ -254,6 +255,7 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.WallabagClientSecret, &integration.WallabagUsername, &integration.WallabagPassword, + &integration.WallabagTags, &integration.NotionEnabled, &integration.NotionToken, &integration.NotionPageID, @@ -384,107 +386,108 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { wallabag_client_secret=$15, wallabag_username=$16, wallabag_password=$17, - nunux_keeper_enabled=$18, - nunux_keeper_url=$19, - nunux_keeper_api_key=$20, - googlereader_enabled=$21, - googlereader_username=$22, - googlereader_password=$23, - telegram_bot_enabled=$24, - telegram_bot_token=$25, - telegram_bot_chat_id=$26, - telegram_bot_topic_id=$27, - telegram_bot_disable_web_page_preview=$28, - telegram_bot_disable_notification=$29, - telegram_bot_disable_buttons=$30, - espial_enabled=$31, - espial_url=$32, - espial_api_key=$33, - espial_tags=$34, - linkace_enabled=$35, - linkace_url=$36, - linkace_api_key=$37, - linkace_tags=$38, - linkace_is_private=$39, - linkace_check_disabled=$40, - linkding_enabled=$41, - linkding_url=$42, - linkding_api_key=$43, - linkding_tags=$44, - linkding_mark_as_unread=$45, - matrix_bot_enabled=$46, - matrix_bot_user=$47, - matrix_bot_password=$48, - matrix_bot_url=$49, - matrix_bot_chat_id=$50, - notion_enabled=$51, - notion_token=$52, - notion_page_id=$53, - readwise_enabled=$54, - readwise_api_key=$55, - apprise_enabled=$56, - apprise_url=$57, - apprise_services_url=$58, - readeck_enabled=$59, - readeck_url=$60, - readeck_api_key=$61, - readeck_labels=$62, - readeck_only_url=$63, - shiori_enabled=$64, - shiori_url=$65, - shiori_username=$66, - shiori_password=$67, - shaarli_enabled=$68, - shaarli_url=$69, - shaarli_api_secret=$70, - webhook_enabled=$71, - webhook_url=$72, - webhook_secret=$73, - rssbridge_enabled=$74, - rssbridge_url=$75, - omnivore_enabled=$76, - omnivore_api_key=$77, - omnivore_url=$78, - linkwarden_enabled=$79, - linkwarden_url=$80, - linkwarden_api_key=$81, - raindrop_enabled=$82, - raindrop_token=$83, - raindrop_collection_id=$84, - raindrop_tags=$85, - betula_enabled=$86, - betula_url=$87, - betula_token=$88, - ntfy_enabled=$89, - ntfy_topic=$90, - ntfy_url=$91, - ntfy_api_token=$92, - ntfy_username=$93, - ntfy_password=$94, - ntfy_icon_url=$95, - ntfy_internal_links=$96, - cubox_enabled=$97, - cubox_api_link=$98, - discord_enabled=$99, - discord_webhook_link=$100, - slack_enabled=$101, - slack_webhook_link=$102, - pushover_enabled=$103, - pushover_user=$104, - pushover_token=$105, - pushover_device=$106, - pushover_prefix=$107, - rssbridge_token=$108, - karakeep_enabled=$109, - karakeep_api_key=$110, - karakeep_url=$111, - linktaco_enabled=$112, - linktaco_api_token=$113, - linktaco_org_slug=$114, - linktaco_tags=$115, - linktaco_visibility=$116 + wallabag_tags=$18, + nunux_keeper_enabled=$19, + nunux_keeper_url=$20, + nunux_keeper_api_key=$21, + googlereader_enabled=$22, + googlereader_username=$23, + googlereader_password=$24, + telegram_bot_enabled=$25, + telegram_bot_token=$26, + telegram_bot_chat_id=$27, + telegram_bot_topic_id=$28, + telegram_bot_disable_web_page_preview=$29, + telegram_bot_disable_notification=$30, + telegram_bot_disable_buttons=$31, + espial_enabled=$32, + espial_url=$33, + espial_api_key=$34, + espial_tags=$35, + linkace_enabled=$36, + linkace_url=$37, + linkace_api_key=$38, + linkace_tags=$39, + linkace_is_private=$40, + linkace_check_disabled=$41, + linkding_enabled=$42, + linkding_url=$43, + linkding_api_key=$44, + linkding_tags=$45, + linkding_mark_as_unread=$46, + matrix_bot_enabled=$47, + matrix_bot_user=$48, + matrix_bot_password=$49, + matrix_bot_url=$50, + matrix_bot_chat_id=$51, + notion_enabled=$52, + notion_token=$53, + notion_page_id=$54, + readwise_enabled=$55, + readwise_api_key=$56, + apprise_enabled=$57, + apprise_url=$58, + apprise_services_url=$59, + readeck_enabled=$60, + readeck_url=$61, + readeck_api_key=$62, + readeck_labels=$63, + readeck_only_url=$64, + shiori_enabled=$65, + shiori_url=$66, + shiori_username=$67, + shiori_password=$68, + shaarli_enabled=$69, + shaarli_url=$70, + shaarli_api_secret=$71, + webhook_enabled=$72, + webhook_url=$73, + webhook_secret=$74, + rssbridge_enabled=$75, + rssbridge_url=$76, + omnivore_enabled=$77, + omnivore_api_key=$78, + omnivore_url=$79, + linkwarden_enabled=$80, + linkwarden_url=$81, + linkwarden_api_key=$82, + raindrop_enabled=$83, + raindrop_token=$84, + raindrop_collection_id=$85, + raindrop_tags=$86, + betula_enabled=$87, + betula_url=$88, + betula_token=$89, + ntfy_enabled=$90, + ntfy_topic=$91, + ntfy_url=$92, + ntfy_api_token=$93, + ntfy_username=$94, + ntfy_password=$95, + ntfy_icon_url=$96, + ntfy_internal_links=$97, + cubox_enabled=$98, + cubox_api_link=$99, + discord_enabled=$100, + discord_webhook_link=$101, + slack_enabled=$102, + slack_webhook_link=$103, + pushover_enabled=$104, + pushover_user=$105, + pushover_token=$106, + pushover_device=$107, + pushover_prefix=$108, + rssbridge_token=$109, + karakeep_enabled=$110, + karakeep_api_key=$111, + karakeep_url=$112, + linktaco_enabled=$113, + linktaco_api_token=$114, + linktaco_org_slug=$115, + linktaco_tags=$116, + linktaco_visibility=$117 WHERE - user_id=$117 + user_id=$118 ` _, err := s.db.Exec( query, @@ -505,6 +508,7 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.WallabagClientSecret, integration.WallabagUsername, integration.WallabagPassword, + integration.WallabagTags, integration.NunuxKeeperEnabled, integration.NunuxKeeperURL, integration.NunuxKeeperAPIKey, diff --git a/internal/template/templates/views/integrations.html b/internal/template/templates/views/integrations.html index 491ae12a..8ce2b933 100644 --- a/internal/template/templates/views/integrations.html +++ b/internal/template/templates/views/integrations.html @@ -664,6 +664,9 @@ + + +
diff --git a/internal/ui/form/integration.go b/internal/ui/form/integration.go index 4d4e29c3..a5b03c18 100644 --- a/internal/ui/form/integration.go +++ b/internal/ui/form/integration.go @@ -32,6 +32,7 @@ type IntegrationForm struct { WallabagClientSecret string WallabagUsername string WallabagPassword string + WallabagTags string NotionEnabled bool NotionPageID string NotionToken string @@ -150,6 +151,7 @@ func (i IntegrationForm) Merge(integration *model.Integration) { integration.WallabagClientSecret = i.WallabagClientSecret integration.WallabagUsername = i.WallabagUsername integration.WallabagPassword = i.WallabagPassword + integration.WallabagTags = i.WallabagTags integration.NotionEnabled = i.NotionEnabled integration.NotionPageID = i.NotionPageID integration.NotionToken = i.NotionToken @@ -270,6 +272,7 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm { WallabagClientSecret: r.FormValue("wallabag_client_secret"), WallabagUsername: r.FormValue("wallabag_username"), WallabagPassword: r.FormValue("wallabag_password"), + WallabagTags: r.FormValue("wallabag_tags"), NotionEnabled: r.FormValue("notion_enabled") == "1", NotionPageID: r.FormValue("notion_page_id"), NotionToken: r.FormValue("notion_token"), diff --git a/internal/ui/integration_show.go b/internal/ui/integration_show.go index d26c02cc..13cc18d3 100644 --- a/internal/ui/integration_show.go +++ b/internal/ui/integration_show.go @@ -45,6 +45,7 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) { WallabagClientSecret: integration.WallabagClientSecret, WallabagUsername: integration.WallabagUsername, WallabagPassword: integration.WallabagPassword, + WallabagTags: integration.WallabagTags, NotionEnabled: integration.NotionEnabled, NotionPageID: integration.NotionPageID, NotionToken: integration.NotionToken,