From cfdb890eae2b1090e0b43090e4e4d3007e0b94d8 Mon Sep 17 00:00:00 2001 From: MSTCL <33115327+mstcl@users.noreply.github.com> Date: Thu, 22 Feb 2024 03:57:34 +0000 Subject: [PATCH] Add Readeck integration --- internal/database/migrations.go | 11 ++ internal/integration/integration.go | 24 +++ internal/integration/readeck/readeck.go | 149 ++++++++++++++++++ internal/locale/translations/de_DE.json | 5 + internal/locale/translations/el_EL.json | 5 + internal/locale/translations/en_US.json | 5 + internal/locale/translations/es_ES.json | 5 + internal/locale/translations/fi_FI.json | 5 + internal/locale/translations/fr_FR.json | 5 + internal/locale/translations/hi_IN.json | 7 +- internal/locale/translations/id_ID.json | 5 + internal/locale/translations/it_IT.json | 5 + internal/locale/translations/ja_JP.json | 5 + internal/locale/translations/nl_NL.json | 5 + internal/locale/translations/pl_PL.json | 5 + internal/locale/translations/pt_BR.json | 5 + internal/locale/translations/ru_RU.json | 5 + internal/locale/translations/tr_TR.json | 5 + internal/locale/translations/uk_UA.json | 5 + internal/locale/translations/zh_CN.json | 5 + internal/locale/translations/zh_TW.json | 5 + internal/model/integration.go | 5 + internal/storage/integration.go | 59 ++++--- .../templates/views/integrations.html | 26 +++ internal/ui/form/integration.go | 15 ++ internal/ui/integration_show.go | 5 + 26 files changed, 366 insertions(+), 20 deletions(-) create mode 100644 internal/integration/readeck/readeck.go diff --git a/internal/database/migrations.go b/internal/database/migrations.go index 43125153..9acae40f 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -855,4 +855,15 @@ var migrations = []func(tx *sql.Tx) error{ _, err = tx.Exec(sql) return err }, + func(tx *sql.Tx) (err error) { + sql := ` + ALTER TABLE integrations ADD COLUMN readeck_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN readeck_only_url bool default 'f'; + ALTER TABLE integrations ADD COLUMN readeck_url text default ''; + ALTER TABLE integrations ADD COLUMN readeck_api_key text default ''; + ALTER TABLE integrations ADD COLUMN readeck_labels text default ''; + ` + _, err = tx.Exec(sql) + return err + }, } diff --git a/internal/integration/integration.go b/internal/integration/integration.go index b16961b5..710679ff 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -19,6 +19,7 @@ import ( "miniflux.app/v2/internal/integration/omnivore" "miniflux.app/v2/internal/integration/pinboard" "miniflux.app/v2/internal/integration/pocket" + "miniflux.app/v2/internal/integration/readeck" "miniflux.app/v2/internal/integration/readwise" "miniflux.app/v2/internal/integration/shaarli" "miniflux.app/v2/internal/integration/shiori" @@ -250,6 +251,29 @@ func SendEntry(entry *model.Entry, userIntegrations *model.Integration) { } } + if userIntegrations.ReadeckEnabled { + slog.Debug("Sending entry to Readeck", + slog.Int64("user_id", userIntegrations.UserID), + slog.Int64("entry_id", entry.ID), + slog.String("entry_url", entry.URL), + ) + + client := readeck.NewClient( + userIntegrations.ReadeckURL, + userIntegrations.ReadeckAPIKey, + userIntegrations.ReadeckLabels, + userIntegrations.ReadeckOnlyURL, + ) + if err := client.CreateBookmark(entry.URL, entry.Title, entry.Content); err != nil { + slog.Error("Unable to send entry to Readeck", + slog.Int64("user_id", userIntegrations.UserID), + slog.Int64("entry_id", entry.ID), + slog.String("entry_url", entry.URL), + slog.Any("error", err), + ) + } + } + if userIntegrations.ReadwiseEnabled { slog.Debug("Sending entry to Readwise", slog.Int64("user_id", userIntegrations.UserID), diff --git a/internal/integration/readeck/readeck.go b/internal/integration/readeck/readeck.go new file mode 100644 index 00000000..0c4f4dc1 --- /dev/null +++ b/internal/integration/readeck/readeck.go @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package readeck // import "miniflux.app/v2/internal/integration/readeck" + +import ( + "bytes" + "encoding/json" + "fmt" + "mime/multipart" + "net/http" + "strings" + "time" + + "miniflux.app/v2/internal/urllib" + "miniflux.app/v2/internal/version" +) + +const defaultClientTimeout = 10 * time.Second + +type Client struct { + baseURL string + apiKey string + labels string + onlyURL bool +} + +func NewClient(baseURL, apiKey, labels string, onlyURL bool) *Client { + return &Client{baseURL: baseURL, apiKey: apiKey, labels: labels, onlyURL: onlyURL} +} + +func (c *Client) CreateBookmark(entryURL, entryTitle string, entryContent string) error { + if c.baseURL == "" || c.apiKey == "" { + return fmt.Errorf("readeck: missing base URL or API key") + } + + apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/bookmarks/") + if err != nil { + return fmt.Errorf(`readeck: invalid API endpoint: %v`, err) + } + + labelsSplitFn := func(c rune) bool { + return c == ',' || c == ' ' + } + labelsSplit := strings.FieldsFunc(c.labels, labelsSplitFn) + + var request *http.Request + if c.onlyURL { + requestBodyJson, err := json.Marshal(&readeckBookmark{ + Url: entryURL, + Title: entryTitle, + Labels: labelsSplit, + }) + if err != nil { + return fmt.Errorf("readeck: unable to encode request body: %v", err) + } + request, err = http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBodyJson)) + if err != nil { + return fmt.Errorf("readeck: unable to create request: %v", err) + } + request.Header.Set("Content-Type", "application/json") + } else { + requestBody := new(bytes.Buffer) + multipartWriter := multipart.NewWriter(requestBody) + + urlPart, err := multipartWriter.CreateFormField("url") + if err != nil { + return fmt.Errorf("readeck: unable to encode request body (entry url): %v", err) + } + urlPart.Write([]byte(entryURL)) + + titlePart, err := multipartWriter.CreateFormField("title") + if err != nil { + return fmt.Errorf("readeck: unable to encode request body (entry title): %v", err) + } + titlePart.Write([]byte(entryTitle)) + + featurePart, err := multipartWriter.CreateFormField("feature_find_main") + if err != nil { + return fmt.Errorf("readeck: unable to encode request body (feature_find_main flag): %v", err) + } + featurePart.Write([]byte("false")) // false to disable readability + + for _, label := range labelsSplit { + labelPart, err := multipartWriter.CreateFormField("labels") + if err != nil { + return fmt.Errorf("readeck: unable to encode request body (entry labels): %v", err) + } + labelPart.Write([]byte(label)) + } + + contentBodyHeader, err := json.Marshal(&partContentHeader{ + Url: entryURL, + ContentHeader: contentHeader{ContentType: "text/html"}, + }) + if err != nil { + return fmt.Errorf("readeck: unable to encode request body (entry content header): %v", err) + } + + contentPart, err := multipartWriter.CreateFormFile("resource", "blob") + if err != nil { + return fmt.Errorf("readeck: unable to encode request body (entry content): %v", err) + } + contentPart.Write(contentBodyHeader) + contentPart.Write([]byte("\n")) + contentPart.Write([]byte(entryContent)) + + err = multipartWriter.Close() + if err != nil { + return fmt.Errorf("readeck: unable to encode request body: %v", err) + } + request, err = http.NewRequest(http.MethodPost, apiEndpoint, requestBody) + if err != nil { + return fmt.Errorf("readeck: unable to create request: %v", err) + } + request.Header.Set("Content-Type", multipartWriter.FormDataContentType()) + } + + request.Header.Set("User-Agent", "Miniflux/"+version.Version) + request.Header.Set("Authorization", "Bearer "+c.apiKey) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + response, err := httpClient.Do(request) + if err != nil { + return fmt.Errorf("readeck: unable to send request: %v", err) + } + defer response.Body.Close() + + if response.StatusCode >= 400 { + return fmt.Errorf("readeck: unable to create bookmark: url=%s status=%d", apiEndpoint, response.StatusCode) + } + + return nil +} + +type readeckBookmark struct { + Url string `json:"url"` + Title string `json:"title"` + Labels []string `json:"labels,omitempty"` +} + +type contentHeader struct { + ContentType string `json:"content-type"` +} + +type partContentHeader struct { + Url string `json:"url"` + ContentHeader contentHeader `json:"headers"` +} diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index c1ac69ba..193cb8e8 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Passwort für Matrix-Benutzer", "form.integration.matrix_bot_url": "URL des Matrix-Servers", "form.integration.matrix_bot_chat_id": "ID des Matrix-Raums", + "form.integration.readeck_activate": "Artikel in Readeck speichern", + "form.integration.readeck_endpoint": "Readeck API-Endpunkt", + "form.integration.readeck_api_key": "Readeck API-Schlüssel", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Nur URL senden (anstelle des vollständigen Inhalts)", "form.integration.shiori_activate": "Artikel in Shiori speichern", "form.integration.shiori_endpoint": "Shiori API-Endpunkt", "form.integration.shiori_username": "Shiori Benutzername", diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json index 2fbb26ed..e755a0da 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Κωδικός πρόσβασης για τον χρήστη Matrix", "form.integration.matrix_bot_url": "URL διακομιστή Matrix", "form.integration.matrix_bot_chat_id": "Αναγνωριστικό της αίθουσας Matrix", + "form.integration.readeck_activate": "Αποθήκευση άρθρων στο Readeck", + "form.integration.readeck_endpoint": "Τελικό σημείο Readeck API", + "form.integration.readeck_api_key": "Κλειδί API Readeck", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Αποστολή μόνο URL (αντί για πλήρες περιεχόμενο)", "form.integration.shiori_activate": "Αποθήκευση άρθρων στο Shiori", "form.integration.shiori_endpoint": "Τελικό σημείο Shiori", "form.integration.shiori_username": "Όνομα Χρήστη Shiori", diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json index 5fe6155d..c54d7c55 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Password for Matrix user", "form.integration.matrix_bot_url": "Matrix server URL", "form.integration.matrix_bot_chat_id": "ID of Matrix Room", + "form.integration.readeck_activate": "Save entries to readeck", + "form.integration.readeck_endpoint": "Readeck API Endpoint", + "form.integration.readeck_api_key": "Readeck API key", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Send only URL (instead of full content)", "form.integration.shiori_activate": "Save articles to Shiori", "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json index 2c4218b3..e93fe57b 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Contraseña para el usuario de Matrix", "form.integration.matrix_bot_url": "URL del servidor de Matrix", "form.integration.matrix_bot_chat_id": "ID de la sala de Matrix", + "form.integration.readeck_activate": "Enviar artículos a Readeck", + "form.integration.readeck_endpoint": "Acceso API de Readeck", + "form.integration.readeck_api_key": "Clave de API de Readeck", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Enviar solo URL (en lugar de contenido completo)", "form.integration.shiori_activate": "Guardar artículos a Shiori", "form.integration.shiori_endpoint": "Extremo de API de Shiori", "form.integration.shiori_username": "Nombre de usuario de Shiori", diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json index eaaa6668..eba5b9ac 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Matrix-käyttäjän salasana", "form.integration.matrix_bot_url": "Matrix-palvelimen URL-osoite", "form.integration.matrix_bot_chat_id": "Matrix-huoneen tunnus", + "form.integration.readeck_activate": "Tallenna artikkelit Readeckiin", + "form.integration.readeck_endpoint": "Readeck API-päätepiste", + "form.integration.readeck_api_key": "Readeck API-avain", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Lähetä vain URL-osoite (koko sisällön sijaan)", "form.integration.shiori_activate": "Save articles to Shiori", "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json index 4ac71ce6..6f1c6ade 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Mot de passe de l'utilisateur Matrix", "form.integration.matrix_bot_url": "URL du serveur Matrix", "form.integration.matrix_bot_chat_id": "Identifiant de la salle Matrix", + "form.integration.readeck_activate": "Sauvegarder les articles vers Readeck", + "form.integration.readeck_endpoint": "URL de l'API de Readeck", + "form.integration.readeck_api_key": "Clé d'API de Readeck", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Envoyer uniquement l'URL (au lieu du contenu complet)", "form.integration.shiori_activate": "Sauvegarder les articles vers Shiori", "form.integration.shiori_endpoint": "URL de l'API de Shiori", "form.integration.shiori_username": "Nom d'utilisateur de Shiori", diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json index 037e0f27..f7538cd7 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "मैट्रिक्स उपयोगकर्ता के लिए पासवर्ड", "form.integration.matrix_bot_url": "मैट्रिक्स सर्वर URL", "form.integration.matrix_bot_chat_id": "मैट्रिक्स रूम की आईडी", + "form.integration.readeck_activate": "Readeck में विषयवस्तु सहेजें", + "form.integration.readeck_endpoint": "Readeck·एपीआई·समापन·बिंदु", + "form.integration.readeck_api_key": "Readeck एपीआई कुंजी", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "केवल URL भेजें (पूर्ण सामग्री के बजाय)", "form.integration.shiori_activate": "Save articles to Shiori", "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", @@ -518,4 +523,4 @@ "error.feed_not_found": "This feed does not exist or does not belong to this user.", "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v." -} \ No newline at end of file +} diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json index 9007179d..5d92cdbc 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -444,6 +444,11 @@ "form.integration.matrix_bot_password": "Kata Sandi Matrix", "form.integration.matrix_bot_url": "URL Peladen Matrix", "form.integration.matrix_bot_chat_id": "ID Ruang Matrix", + "form.integration.readeck_activate": "Simpan artikel ke Readeck", + "form.integration.readeck_endpoint": "Titik URL API Readeck", + "form.integration.readeck_api_key": "Kunci API Readeck", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Kirim hanya URL (alih-alih konten penuh)", "form.integration.shiori_activate": "Save articles to Shiori", "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json index e7dded72..c071462f 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Password per l'utente Matrix", "form.integration.matrix_bot_url": "URL del server Matrix", "form.integration.matrix_bot_chat_id": "ID della stanza Matrix", + "form.integration.readeck_activate": "Salva gli articoli su Readeck", + "form.integration.readeck_endpoint": "Endpoint dell'API di Readeck", + "form.integration.readeck_api_key": "API key dell'account Readeck", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Invia solo URL (invece del contenuto completo)", "form.integration.shiori_activate": "Salva gli articoli su Shiori", "form.integration.shiori_endpoint": "Endpoint dell'API di Shiori", "form.integration.shiori_username": "Nome utente dell'account Shiori", diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json index 4f34d6b1..acfa8fc0 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Matrixユーザ用パスワード", "form.integration.matrix_bot_url": "MatrixサーバーのURL", "form.integration.matrix_bot_chat_id": "MatrixルームのID", + "form.integration.readeck_activate": "Readeck に記事を保存する", + "form.integration.readeck_endpoint": "Readeck の API Endpoint", + "form.integration.readeck_api_key": "Readeck の API key", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "URL のみを送信 (完全なコンテンツではなく)", "form.integration.shiori_activate": "Shiori に記事を保存する", "form.integration.shiori_endpoint": "Shiori の API Endpoint", "form.integration.shiori_username": "Shiori の ユーザー名", diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json index dc860ee9..be1b2e51 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Wachtwoord voor Matrix-gebruiker", "form.integration.matrix_bot_url": "URL van de Matrix-server", "form.integration.matrix_bot_chat_id": "ID van Matrix-kamer", + "form.integration.readeck_activate": "Opslaan naar Readeck", + "form.integration.readeck_endpoint": "Readeck URL", + "form.integration.readeck_api_key": "Readeck API-sleutel", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Alleen URL verzenden (in plaats van volledige inhoud)", "form.integration.shiori_activate": "Opslaan naar Shiori", "form.integration.shiori_endpoint": "Shiori URL", "form.integration.shiori_username": "Shiori gebruikersnaam", diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json index a9576423..bf767774 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -450,6 +450,11 @@ "form.integration.matrix_bot_password": "Hasło dla użytkownika Matrix", "form.integration.matrix_bot_url": "URL serwera Matrix", "form.integration.matrix_bot_chat_id": "Identyfikator pokoju Matrix", + "form.integration.readeck_activate": "Zapisz artykuły do Readeck", + "form.integration.readeck_endpoint": "Readeck URL", + "form.integration.readeck_api_key": "Readeck API key", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Wyślij tylko adres URL (zamiast pełnej treści)", "form.integration.shiori_activate": "Zapisz artykuły do Shiori", "form.integration.shiori_endpoint": "Shiori URL", "form.integration.shiori_username": "Login do Shiori", diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json index 66aa3ce5..70455879 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Palavra-passe para utilizador da Matrix", "form.integration.matrix_bot_url": "URL do servidor Matrix", "form.integration.matrix_bot_chat_id": "Identificação da sala Matrix", + "form.integration.readeck_activate": "Salvar itens no Readeck", + "form.integration.readeck_endpoint": "Endpoint de API do Readeck", + "form.integration.readeck_api_key": "Chave de API do Readeck", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Enviar apenas URL (em vez de conteúdo completo)", "form.integration.shiori_activate": "Salvar itens no Shiori", "form.integration.shiori_endpoint": "Endpoint da API do Shiori", "form.integration.shiori_username": "Nome de usuário do Shiori", diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json index 84ac9135..2cce28d0 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -450,6 +450,11 @@ "form.integration.matrix_bot_password": "Пароль пользователя Matrix", "form.integration.matrix_bot_url": "Ссылка на сервер Matrix", "form.integration.matrix_bot_chat_id": "ID комнаты Matrix", + "form.integration.readeck_activate": "Сохранять статьи в Readeck", + "form.integration.readeck_endpoint": "Конечная точка Readeck API", + "form.integration.readeck_api_key": "API-ключ Readeck", + "form.integration.readeck_labels": "Теги Readeck", + "form.integration.readeck_only_url": "Отправлять только ссылку (без содержимого)", "form.integration.shiori_activate": "Сохранять статьи в Shiori", "form.integration.shiori_endpoint": "Конечная точка Shiori API", "form.integration.shiori_username": "Имя пользователя Shiori", diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json index 9b242c9a..cb5a643f 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Matrix kullanıcısı için şifre", "form.integration.matrix_bot_url": "Matris sunucusu URL'si", "form.integration.matrix_bot_chat_id": "Matris odasının kimliği", + "form.integration.readeck_activate": "Makaleleri Readeck'e kaydet", + "form.integration.readeck_endpoint": "Readeck API Uç Noktası", + "form.integration.readeck_api_key": "Readeck API Anahtarı", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Yalnızca URL gönder (tam içerik yerine)", "form.integration.shiori_activate": "Makaleleri Shiori'e kaydet", "form.integration.shiori_endpoint": "Shiori API Uç Noktası", "form.integration.shiori_username": "Shiori Kullanıcı Adı", diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json index 7f774d61..d4a88415 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -451,6 +451,11 @@ "form.integration.matrix_bot_password": "Пароль для користувача Matrix", "form.integration.matrix_bot_url": "URL-адреса сервера Матриці", "form.integration.matrix_bot_chat_id": "Ідентифікатор кімнати Матриці", + "form.integration.readeck_activate": "Зберігати статті до Readeck", + "form.integration.readeck_endpoint": "Readeck API Endpoint", + "form.integration.readeck_api_key": "Ключ API Readeck", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "Надіслати лише URL (замість повного вмісту)", "form.integration.shiori_activate": "Save articles to Shiori", "form.integration.shiori_endpoint": "Shiori API Endpoint", "form.integration.shiori_username": "Shiori Username", diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index 33b20ed1..580ae0ac 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -445,6 +445,11 @@ "form.integration.matrix_bot_password": "Matrix Bot 密码", "form.integration.matrix_bot_url": "Matrix 服务器 URL", "form.integration.matrix_bot_chat_id": "Matrix 聊天 ID", + "form.integration.readeck_activate": "保存文章到 Readeck", + "form.integration.readeck_endpoint": "Readeck API 端点", + "form.integration.readeck_api_key": "Readeck API 密钥", + "form.integration.readeck_labels": "Readeck 默认标签", + "form.integration.readeck_only_url": "仅发送 URL(而不是完整内容)", "form.integration.shiori_activate": "保存文章到 Shiori", "form.integration.shiori_endpoint": "Shiori API 端点", "form.integration.shiori_username": "Shiori 用户名", diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json index 9c7fe3e5..c0326e0d 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -447,6 +447,11 @@ "form.integration.matrix_bot_password": "Matrix 的密碼", "form.integration.matrix_bot_url": "Matrix 伺服器的 URL", "form.integration.matrix_bot_chat_id": "Matrix 房間 ID", + "form.integration.readeck_activate": "儲存文章到 Readeck", + "form.integration.readeck_endpoint": "Readeck API 端點", + "form.integration.readeck_api_key": "Readeck API 金鑰", + "form.integration.readeck_labels": "Readeck Labels", + "form.integration.readeck_only_url": "仅发送 URL(而不是完整内容)", "form.integration.shiori_activate": "儲存文章到 Shiori", "form.integration.shiori_endpoint": "Shiori API 端點", "form.integration.shiori_username": "Shiori 使用者名稱", diff --git a/internal/model/integration.go b/internal/model/integration.go index ea980e7b..4ab70c18 100644 --- a/internal/model/integration.go +++ b/internal/model/integration.go @@ -70,6 +70,11 @@ type Integration struct { AppriseEnabled bool AppriseURL string AppriseServicesURL string + ReadeckEnabled bool + ReadeckURL string + ReadeckAPIKey string + ReadeckLabels string + ReadeckOnlyURL bool ShioriEnabled bool ShioriURL string ShioriUsername string diff --git a/internal/storage/integration.go b/internal/storage/integration.go index fb536b4e..d3f3d0eb 100644 --- a/internal/storage/integration.go +++ b/internal/storage/integration.go @@ -174,6 +174,11 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { apprise_enabled, apprise_url, apprise_services_url, + readeck_enabled, + readeck_url, + readeck_api_key, + readeck_labels, + readeck_only_url, shiori_enabled, shiori_url, shiori_username, @@ -261,6 +266,11 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.AppriseEnabled, &integration.AppriseURL, &integration.AppriseServicesURL, + &integration.ReadeckEnabled, + &integration.ReadeckURL, + &integration.ReadeckAPIKey, + &integration.ReadeckLabels, + &integration.ReadeckOnlyURL, &integration.ShioriEnabled, &integration.ShioriURL, &integration.ShioriUsername, @@ -354,26 +364,31 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { apprise_enabled=$59, apprise_url=$60, apprise_services_url=$61, - shiori_enabled=$62, - shiori_url=$63, - shiori_username=$64, - shiori_password=$65, - shaarli_enabled=$66, - shaarli_url=$67, - shaarli_api_secret=$68, - webhook_enabled=$69, - webhook_url=$70, - webhook_secret=$71, - rssbridge_enabled=$72, - rssbridge_url=$73, - omnivore_enabled=$74, - omnivore_api_key=$75, - omnivore_url=$76, - linkwarden_enabled=$77, - linkwarden_url=$78, - linkwarden_api_key=$79 + readeck_enabled=$62, + readeck_url=$63, + readeck_api_key=$64, + readeck_labels=$65, + readeck_only_url=$66, + shiori_enabled=$67, + shiori_url=$68, + shiori_username=$69, + shiori_password=$70, + shaarli_enabled=$71, + shaarli_url=$72, + shaarli_api_secret=$73, + webhook_enabled=$74, + webhook_url=$75, + webhook_secret=$76, + rssbridge_enabled=$77, + rssbridge_url=$78, + omnivore_enabled=$79, + omnivore_api_key=$80, + omnivore_url=$81, + linkwarden_enabled=$82, + linkwarden_url=$83, + linkwarden_api_key=$84 WHERE - user_id=$80 + user_id=$85 ` _, err := s.db.Exec( query, @@ -438,6 +453,11 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.AppriseEnabled, integration.AppriseURL, integration.AppriseServicesURL, + integration.ReadeckEnabled, + integration.ReadeckURL, + integration.ReadeckAPIKey, + integration.ReadeckLabels, + integration.ReadeckOnlyURL, integration.ShioriEnabled, integration.ShioriURL, integration.ShioriUsername, @@ -490,6 +510,7 @@ func (s *Storage) HasSaveEntry(userID int64) (result bool) { linkwarden_enabled='t' OR apprise_enabled='t' OR shiori_enabled='t' OR + readeck_enabled='t' OR shaarli_enabled='t' OR webhook_enabled='t' OR omnivore_enabled='t' diff --git a/internal/template/templates/views/integrations.html b/internal/template/templates/views/integrations.html index 8616f5fc..e9ad2fec 100644 --- a/internal/template/templates/views/integrations.html +++ b/internal/template/templates/views/integrations.html @@ -363,6 +363,32 @@ +
+ Readeck +
+ + + + + + + + + + + + + +
+ +
+
+
+
Shiori
diff --git a/internal/ui/form/integration.go b/internal/ui/form/integration.go index fffd76fd..7bc5cf91 100644 --- a/internal/ui/form/integration.go +++ b/internal/ui/form/integration.go @@ -76,6 +76,11 @@ type IntegrationForm struct { AppriseEnabled bool AppriseURL string AppriseServicesURL string + ReadeckEnabled bool + ReadeckURL string + ReadeckAPIKey string + ReadeckLabels string + ReadeckOnlyURL bool ShioriEnabled bool ShioriURL string ShioriUsername string @@ -157,6 +162,11 @@ func (i IntegrationForm) Merge(integration *model.Integration) { integration.AppriseEnabled = i.AppriseEnabled integration.AppriseServicesURL = i.AppriseServicesURL integration.AppriseURL = i.AppriseURL + integration.ReadeckEnabled = i.ReadeckEnabled + integration.ReadeckURL = i.ReadeckURL + integration.ReadeckAPIKey = i.ReadeckAPIKey + integration.ReadeckLabels = i.ReadeckLabels + integration.ReadeckOnlyURL = i.ReadeckOnlyURL integration.ShioriEnabled = i.ShioriEnabled integration.ShioriURL = i.ShioriURL integration.ShioriUsername = i.ShioriUsername @@ -240,6 +250,11 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm { AppriseEnabled: r.FormValue("apprise_enabled") == "1", AppriseURL: r.FormValue("apprise_url"), AppriseServicesURL: r.FormValue("apprise_services_url"), + ReadeckEnabled: r.FormValue("readeck_enabled") == "1", + ReadeckURL: r.FormValue("readeck_url"), + ReadeckAPIKey: r.FormValue("readeck_api_key"), + ReadeckLabels: r.FormValue("readeck_labels"), + ReadeckOnlyURL: r.FormValue("readeck_only_url") == "1", ShioriEnabled: r.FormValue("shiori_enabled") == "1", ShioriURL: r.FormValue("shiori_url"), ShioriUsername: r.FormValue("shiori_username"), diff --git a/internal/ui/integration_show.go b/internal/ui/integration_show.go index f01f906d..03bc73b6 100644 --- a/internal/ui/integration_show.go +++ b/internal/ui/integration_show.go @@ -90,6 +90,11 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) { AppriseEnabled: integration.AppriseEnabled, AppriseURL: integration.AppriseURL, AppriseServicesURL: integration.AppriseServicesURL, + ReadeckEnabled: integration.ReadeckEnabled, + ReadeckURL: integration.ReadeckURL, + ReadeckAPIKey: integration.ReadeckAPIKey, + ReadeckLabels: integration.ReadeckLabels, + ReadeckOnlyURL: integration.ReadeckOnlyURL, ShioriEnabled: integration.ShioriEnabled, ShioriURL: integration.ShioriURL, ShioriUsername: integration.ShioriUsername,