From 9f465fd70db9368e87e2dac77f4ea2d2a27c7c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Sun, 13 Aug 2023 14:30:57 -0700 Subject: [PATCH] Add Shaarli integration --- internal/database/migrations.go | 9 ++ internal/integration/integration.go | 34 +++++-- internal/integration/shaarli/shaarli.go | 91 +++++++++++++++++++ internal/locale/translations/de_DE.json | 3 + internal/locale/translations/el_EL.json | 3 + internal/locale/translations/en_US.json | 3 + internal/locale/translations/es_ES.json | 3 + internal/locale/translations/fi_FI.json | 3 + internal/locale/translations/fr_FR.json | 3 + internal/locale/translations/hi_IN.json | 3 + internal/locale/translations/id_ID.json | 3 + internal/locale/translations/it_IT.json | 3 + internal/locale/translations/ja_JP.json | 3 + internal/locale/translations/nl_NL.json | 3 + internal/locale/translations/pl_PL.json | 3 + internal/locale/translations/pt_BR.json | 3 + internal/locale/translations/ru_RU.json | 3 + internal/locale/translations/tr_TR.json | 3 + internal/locale/translations/uk_UA.json | 35 +++++-- internal/locale/translations/zh_CN.json | 3 + internal/locale/translations/zh_TW.json | 3 + internal/model/integration.go | 3 + internal/storage/integration.go | 21 ++++- .../templates/views/integrations.html | 21 ++++- internal/ui/form/integration.go | 9 ++ internal/ui/integration_show.go | 3 + 26 files changed, 256 insertions(+), 21 deletions(-) create mode 100644 internal/integration/shaarli/shaarli.go diff --git a/internal/database/migrations.go b/internal/database/migrations.go index 59d0801296c..f238993c216 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -743,4 +743,13 @@ 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 shaarli_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN shaarli_url text default ''; + ALTER TABLE integrations ADD COLUMN shaarli_api_secret text default ''; + ` + _, err = tx.Exec(sql) + return err + }, } diff --git a/internal/integration/integration.go b/internal/integration/integration.go index 1b47638ee4e..98bda7028ec 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -15,6 +15,7 @@ import ( "miniflux.app/v2/internal/integration/pinboard" "miniflux.app/v2/internal/integration/pocket" "miniflux.app/v2/internal/integration/readwise" + "miniflux.app/v2/internal/integration/shaarli" "miniflux.app/v2/internal/integration/shiori" "miniflux.app/v2/internal/integration/telegrambot" "miniflux.app/v2/internal/integration/wallabag" @@ -25,7 +26,7 @@ import ( // SendEntry sends the entry to third-party providers when the user click on "Save". func SendEntry(entry *model.Entry, integration *model.Integration) { if integration.PinboardEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Pinboard", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Pinboard", entry.ID, entry.URL, integration.UserID) client := pinboard.NewClient(integration.PinboardToken) err := client.AddBookmark( @@ -41,7 +42,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.InstapaperEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Instapaper", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Instapaper", entry.ID, entry.URL, integration.UserID) client := instapaper.NewClient(integration.InstapaperUsername, integration.InstapaperPassword) if err := client.AddURL(entry.URL, entry.Title); err != nil { @@ -50,7 +51,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.WallabagEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Wallabag", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Wallabag", entry.ID, entry.URL, integration.UserID) client := wallabag.NewClient( integration.WallabagURL, @@ -67,7 +68,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.NotionEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Notion", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Notion", entry.ID, entry.URL, integration.UserID) client := notion.NewClient( integration.NotionToken, @@ -79,7 +80,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.NunuxKeeperEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to NunuxKeeper", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to NunuxKeeper", entry.ID, entry.URL, integration.UserID) client := nunuxkeeper.NewClient( integration.NunuxKeeperURL, @@ -92,7 +93,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.EspialEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Espial", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Espial", entry.ID, entry.URL, integration.UserID) client := espial.NewClient( integration.EspialURL, @@ -105,7 +106,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.PocketEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Pocket", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Pocket", entry.ID, entry.URL, integration.UserID) client := pocket.NewClient(config.Opts.PocketConsumerKey(integration.PocketConsumerKey), integration.PocketAccessToken) if err := client.AddURL(entry.URL, entry.Title); err != nil { @@ -114,7 +115,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.LinkdingEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Linkding", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Linkding", entry.ID, entry.URL, integration.UserID) client := linkding.NewClient( integration.LinkdingURL, @@ -128,7 +129,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.ReadwiseEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Readwise Reader", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Readwise Reader", entry.ID, entry.URL, integration.UserID) client := readwise.NewClient( integration.ReadwiseAPIKey, @@ -140,7 +141,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { } if integration.ShioriEnabled { - logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Shiori", entry.ID, entry.URL, integration.UserID) + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Shiori", entry.ID, entry.URL, integration.UserID) client := shiori.NewClient( integration.ShioriURL, @@ -152,6 +153,19 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { logger.Error("[Integration] Unable to send entry #%d to Shiori for user #%d: %v", entry.ID, integration.UserID, err) } } + + if integration.ShaarliEnabled { + logger.Debug("[Integration] Sending entry #%d %q for user #%d to Shaarli", entry.ID, entry.URL, integration.UserID) + + client := shaarli.NewClient( + integration.ShaarliURL, + integration.ShaarliAPISecret, + ) + + if err := client.AddLink(entry.URL, entry.Title); err != nil { + logger.Error("[Integration] Unable to send entry #%d to Shaarli for user #%d: %v", entry.ID, integration.UserID, err) + } + } } // PushEntries pushes an entry array to third-party providers during feed refreshes. diff --git a/internal/integration/shaarli/shaarli.go b/internal/integration/shaarli/shaarli.go new file mode 100644 index 00000000000..d88e3cf4b0c --- /dev/null +++ b/internal/integration/shaarli/shaarli.go @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package shaarli // import "miniflux.app/v2/internal/integration/shaarli" + +import ( + "bytes" + "crypto/hmac" + "crypto/sha512" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "miniflux.app/v2/internal/url" + "miniflux.app/v2/internal/version" +) + +const defaultClientTimeout = 10 * time.Second + +type Client struct { + baseURL string + apiSecret string +} + +func NewClient(baseURL, apiSecret string) *Client { + return &Client{baseURL: baseURL, apiSecret: apiSecret} +} + +func (c *Client) AddLink(entryURL, entryTitle string) error { + if c.baseURL == "" || c.apiSecret == "" { + return fmt.Errorf("shaarli: missing base URL or API secret") + } + + apiEndpoint, err := url.JoinBaseURLAndPath(c.baseURL, "/api/v1/links") + if err != nil { + return fmt.Errorf("shaarli: invalid API endpoint: %v", err) + } + + requestBody, err := json.Marshal(&addLinkRequest{ + URL: entryURL, + Title: entryTitle, + Private: true, + }) + + if err != nil { + return fmt.Errorf("shaarli: unable to encode request body: %v", err) + } + + request, err := http.NewRequest("POST", apiEndpoint, bytes.NewReader(requestBody)) + if err != nil { + return fmt.Errorf("shaarli: unable to create request: %v", err) + } + + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + request.Header.Set("User-Agent", "Miniflux/"+version.Version) + request.Header.Set("Authorization", "Bearer "+c.generateBearerToken()) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + response, err := httpClient.Do(request) + if err != nil { + return fmt.Errorf("shaarli: unable to send request: %v", err) + } + defer response.Body.Close() + + if response.StatusCode != http.StatusCreated { + return fmt.Errorf("shaarli: unable to add link: url=%s status=%d", apiEndpoint, response.StatusCode) + } + + return nil +} + +func (c *Client) generateBearerToken() string { + header := strings.TrimRight(base64.URLEncoding.EncodeToString([]byte(`{"typ":"JWT", "alg":"HS256"}`)), "=") + payload := strings.TrimRight(base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(`{"iat": %d}`, time.Now().Unix()))), "=") + + mac := hmac.New(sha512.New, []byte(c.apiSecret)) + mac.Write([]byte(header + "." + payload)) + signature := strings.TrimRight(base64.URLEncoding.EncodeToString(mac.Sum(nil)), "=") + + return header + "." + payload + "." + signature +} + +type addLinkRequest struct { + URL string `json:"url"` + Title string `json:"title"` + Private bool `json:"private"` +} diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index 4455d53e7b5..46d8b72774c 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori API-Endpunkt", "form.integration.shiori_username": "Shiori Benutzername", "form.integration.shiori_password": "Shiori Passwort", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API-Schlüsselbezeichnung", "form.submit.loading": "Lade...", "form.submit.saving": "Speichern...", diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json index e3928be5634..1e8509fbeef 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Τελικό σημείο Shiori", "form.integration.shiori_username": "Όνομα Χρήστη Shiori", "form.integration.shiori_password": "Κωδικός Πρόσβασης Shiori", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Ετικέτα κλειδιού API", "form.submit.loading": "Φόρτωση...", "form.submit.saving": "Αποθήκευση...", diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json index 66a4762d0a8..85ac803d6a5 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", "form.integration.shiori_password": "Shiori Password", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API Key Label", "form.submit.loading": "Loading…", "form.submit.saving": "Saving…", diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json index 09a28fd4fe6..22017cf46e3 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Extremo de API de Shiori", "form.integration.shiori_username": "Nombre de usuario de Shiori", "form.integration.shiori_password": "Contraseña de Shiori", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Etiqueta de clave API", "form.submit.loading": "Cargando...", "form.submit.saving": "Guardando...", diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json index 00c731ef7b9..37fa74f5a94 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", "form.integration.shiori_password": "Shiori Password", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API Key Label", "form.submit.loading": "Ladataan...", "form.submit.saving": "Tallennetaan...", diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json index 23044dd9aca..13668ae3a98 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "URL de l'API de Shiori", "form.integration.shiori_username": "Nom d'utilisateur de Shiori", "form.integration.shiori_password": "Mot de passe de Shiori", + "form.integration.shaarli_activate": "Sauvegarder les articles vers Shaarli", + "form.integration.shaarli_endpoint": "URL de l'API de Shaarli", + "form.integration.shaarli_api_secret": "Clé d'API de Shaarli API", "form.api_key.label.description": "Libellé de la clé d'API", "form.submit.loading": "Chargement...", "form.submit.saving": "Sauvegarde en cours...", diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json index 888e6a3a391..f2c124c36ea 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", "form.integration.shiori_password": "Shiori Password", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "एपीआई कुंजी लेबल", "form.submit.loading": "लोड हो रहा है...", "form.submit.saving": "सहेजा जा रहा है...", diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json index 4934ed57de8..05108450526 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -382,6 +382,9 @@ "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", "form.integration.shiori_password": "Shiori Password", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Label Kunci API", "form.submit.loading": "Memuat...", "form.submit.saving": "Menyimpan...", diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json index f292f8c21b6..68903209a9c 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Endpoint dell'API di Shiori", "form.integration.shiori_username": "Nome utente dell'account Shiori", "form.integration.shiori_password": "Password dell'account Shiori", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Etichetta chiave API", "form.submit.loading": "Caricamento in corso...", "form.submit.saving": "Salvataggio in corso...", diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json index 18044a712b3..5cc3a36239b 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori の API Endpoint", "form.integration.shiori_username": "Shiori の ユーザー名", "form.integration.shiori_password": "Shiori の パスワード", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API キーラベル", "form.submit.loading": "読み込み中…", "form.submit.saving": "保存中…", diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json index 033c58edb6c..d8bb7d21fad 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori URL", "form.integration.shiori_username": "Shiori gebruikersnaam", "form.integration.shiori_password": "Shiori wachtwoord", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API-sleutellabel", "form.submit.loading": "Laden...", "form.submit.saving": "Opslaag...", diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json index c8e221310bc..fe40c6c0e1c 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -387,6 +387,9 @@ "form.integration.shiori_endpoint": "Shiori URL", "form.integration.shiori_username": "Login do Shiori", "form.integration.shiori_password": "Hasło do Shiori", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Etykieta klucza API", "form.submit.loading": "Ładowanie...", "form.submit.saving": "Zapisywanie...", diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json index 0b796060ac2..db9fea56c95 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Endpoint da API do Shiori", "form.integration.shiori_username": "Nome de usuário do Shiori", "form.integration.shiori_password": "Senha do Shiori", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Etiqueta da chave de API", "form.submit.loading": "Carregando...", "form.submit.saving": "Salvando...", diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json index 8ad3af8e6ca..1580322c80c 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -387,6 +387,9 @@ "form.integration.shiori_endpoint": "Конечная точка Shiori API", "form.integration.shiori_username": "Имя пользователя Shiori", "form.integration.shiori_password": "Пароль Shiori", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Описание API-ключа", "form.submit.loading": "Загрузка…", "form.submit.saving": "Сохранение…", diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json index d4774b91ed0..6d70f8423c4 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori API Uç Noktası", "form.integration.shiori_username": "Shiori Kullanıcı Adı", "form.integration.shiori_password": "Shiori Parolası", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API Anahtar Etiketi", "form.submit.loading": "Yükleniyor...", "form.submit.saving": "Kaydediliyor...", diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json index b0d3c569bb6..6008884fba7 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -105,7 +105,11 @@ "page.feeds.last_check": "Остання перевірка:", "page.feeds.unread_counter": "Кількість непрочитаних записів", "page.feeds.read_counter": "Кількість прочитаних записів", - "page.feeds.error_count": ["%d помилка", "%d помилки", "%d помилок"], + "page.feeds.error_count": [ + "%d помилка", + "%d помилки", + "%d помилок" + ], "page.history.title": "Історія", "page.import.title": "Імпорт", "page.search.title": "Результати пошуку", @@ -384,6 +388,9 @@ "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", "form.integration.shiori_password": "Shiori Password", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "Назва ключа API", "form.submit.loading": "Завантаження...", "form.submit.saving": "Зберігаю...", @@ -395,13 +402,29 @@ "%d хвилини тому", "%d хвилин тому" ], - "time_elapsed.hours": ["%d годину тому", "%d години тому", "%d годин тому"], - "time_elapsed.days": ["%d день тому", "%d дні тому", "%d днів тому"], - "time_elapsed.weeks": ["%d тиждень тому", "%d тижня тому", "%d тижнів тому"], + "time_elapsed.hours": [ + "%d годину тому", + "%d години тому", + "%d годин тому" + ], + "time_elapsed.days": [ + "%d день тому", + "%d дні тому", + "%d днів тому" + ], + "time_elapsed.weeks": [ + "%d тиждень тому", + "%d тижня тому", + "%d тижнів тому" + ], "time_elapsed.months": [ "%d місяць тому", "%d місяця тому", "%d місяців тому" ], - "time_elapsed.years": ["%d рік тому", "%d роки тому", "%d років тому"] -} + "time_elapsed.years": [ + "%d рік тому", + "%d роки тому", + "%d років тому" + ] +} \ No newline at end of file diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index 6034e23bd9e..e985b2c37e8 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -383,6 +383,9 @@ "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori 用户名", "form.integration.shiori_password": "Shiori 密码", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API密钥标签", "form.submit.loading": "载入中…", "form.submit.saving": "保存中…", diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json index b32f8758a0a..a762bb983d2 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -385,6 +385,9 @@ "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", "form.integration.shiori_password": "Shiori Password", + "form.integration.shaarli_activate": "Save articles to Shaarli", + "form.integration.shaarli_endpoint": "Shaarli URL", + "form.integration.shaarli_api_secret": "Shaarli API Secret", "form.api_key.label.description": "API金鑰標籤", "form.submit.loading": "載入中…", "form.submit.saving": "儲存中…", diff --git a/internal/model/integration.go b/internal/model/integration.go index 28a0da9a3f1..c00be86f8cf 100644 --- a/internal/model/integration.go +++ b/internal/model/integration.go @@ -61,4 +61,7 @@ type Integration struct { ShioriURL string ShioriUsername string ShioriPassword string + ShaarliEnabled bool + ShaarliURL string + ShaarliAPISecret string } diff --git a/internal/storage/integration.go b/internal/storage/integration.go index 61ecc6798d3..c68168d01ba 100644 --- a/internal/storage/integration.go +++ b/internal/storage/integration.go @@ -164,7 +164,10 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { shiori_enabled, shiori_url, shiori_username, - shiori_password + shiori_password, + shaarli_enabled, + shaarli_url, + shaarli_api_secret FROM integrations WHERE @@ -228,6 +231,9 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.ShioriURL, &integration.ShioriUsername, &integration.ShioriPassword, + &integration.ShaarliEnabled, + &integration.ShaarliURL, + &integration.ShaarliAPISecret, ) switch { case err == sql.ErrNoRows: @@ -299,9 +305,12 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { shiori_enabled=$52, shiori_url=$53, shiori_username=$54, - shiori_password=$55 + shiori_password=$55, + shaarli_enabled=$56, + shaarli_url=$57, + shaarli_api_secret=$58 WHERE - user_id=$56 + user_id=$59 ` _, err := s.db.Exec( query, @@ -360,6 +369,9 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.ShioriURL, integration.ShioriUsername, integration.ShioriPassword, + integration.ShaarliEnabled, + integration.ShaarliURL, + integration.ShaarliAPISecret, integration.UserID, ) @@ -391,7 +403,8 @@ func (s *Storage) HasSaveEntry(userID int64) (result bool) { pocket_enabled='t' OR linkding_enabled='t' OR apprise_enabled='t' OR - shiori_enabled='t' + shiori_enabled='t' OR + shaarli_enabled='t' ) ` if err := s.db.QueryRow(query, userID).Scan(&result); err != nil { diff --git a/internal/template/templates/views/integrations.html b/internal/template/templates/views/integrations.html index f26d399f362..255bc825755 100644 --- a/internal/template/templates/views/integrations.html +++ b/internal/template/templates/views/integrations.html @@ -325,6 +325,25 @@

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

+
+ Shaarli +
+ + + + + + + + +
+ +
+
+
+
Shiori
@@ -339,7 +358,7 @@

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

- +
diff --git a/internal/ui/form/integration.go b/internal/ui/form/integration.go index 5a332f28b3f..53ff92022b9 100644 --- a/internal/ui/form/integration.go +++ b/internal/ui/form/integration.go @@ -66,6 +66,9 @@ type IntegrationForm struct { ShioriURL string ShioriUsername string ShioriPassword string + ShaarliEnabled bool + ShaarliURL string + ShaarliAPISecret string } // Merge copy form values to the model. @@ -123,6 +126,9 @@ func (i IntegrationForm) Merge(integration *model.Integration) { integration.ShioriURL = i.ShioriURL integration.ShioriUsername = i.ShioriUsername integration.ShioriPassword = i.ShioriPassword + integration.ShaarliEnabled = i.ShaarliEnabled + integration.ShaarliURL = i.ShaarliURL + integration.ShaarliAPISecret = i.ShaarliAPISecret } // NewIntegrationForm returns a new IntegrationForm. @@ -183,5 +189,8 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm { ShioriURL: r.FormValue("shiori_url"), ShioriUsername: r.FormValue("shiori_username"), ShioriPassword: r.FormValue("shiori_password"), + ShaarliEnabled: r.FormValue("shaarli_enabled") == "1", + ShaarliURL: r.FormValue("shaarli_url"), + ShaarliAPISecret: r.FormValue("shaarli_api_secret"), } } diff --git a/internal/ui/integration_show.go b/internal/ui/integration_show.go index b1985d51e5d..7a30a0436b4 100644 --- a/internal/ui/integration_show.go +++ b/internal/ui/integration_show.go @@ -81,6 +81,9 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) { ShioriURL: integration.ShioriURL, ShioriUsername: integration.ShioriUsername, ShioriPassword: integration.ShioriPassword, + ShaarliEnabled: integration.ShaarliEnabled, + ShaarliURL: integration.ShaarliURL, + ShaarliAPISecret: integration.ShaarliAPISecret, } sess := session.New(h.store, request.SessionID(r))