1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-09-15 18:57:04 +00:00

feat(integration): add support for Wallabag tags

This commit is contained in:
Kevin Sicong Jiang 2025-09-10 09:47:51 +09:00 committed by GitHub
parent f1143151c5
commit 8129500296
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 471 additions and 105 deletions

View file

@ -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
},
}

View file

@ -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),

View file

@ -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"`
}

View file

@ -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)
}
})
}
}

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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í",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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 網址",

View file

@ -29,6 +29,7 @@ type Integration struct {
WallabagClientSecret string
WallabagUsername string
WallabagPassword string
WallabagTags string
NunuxKeeperEnabled bool
NunuxKeeperURL string
NunuxKeeperAPIKey string

View file

@ -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,

View file

@ -664,6 +664,9 @@
<label for="form-wallabag-password">{{ t "form.integration.wallabag_password" }}</label>
<input type="password" name="wallabag_password" id="form-wallabag-password" value="{{ .form.WallabagPassword }}" autocomplete="new-password">
<label for="form-wallabag-tags">{{ t "form.integration.wallabag_tags" }}</label>
<input type="text" name="wallabag_tags" id="form-wallabag-tags" value="{{ .form.WallabagTags }}" spellcheck="false">
<div class="buttons">
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
</div>

View file

@ -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"),

View file

@ -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,