From 28df0b119e8d3562ca00c6066911aa8538378175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Sun, 13 Aug 2023 12:48:29 -0700 Subject: [PATCH] Add Shiori integration --- internal/database/migrations.go | 26 ++-- internal/integration/integration.go | 15 ++ internal/integration/shiori/shiori.go | 132 ++++++++++++++++++ internal/locale/translations/de_DE.json | 4 + internal/locale/translations/el_EL.json | 4 + internal/locale/translations/en_US.json | 4 + internal/locale/translations/es_ES.json | 4 + internal/locale/translations/fi_FI.json | 4 + internal/locale/translations/fr_FR.json | 4 + internal/locale/translations/hi_IN.json | 4 + internal/locale/translations/id_ID.json | 4 + internal/locale/translations/it_IT.json | 4 + internal/locale/translations/ja_JP.json | 4 + internal/locale/translations/nl_NL.json | 4 + internal/locale/translations/pl_PL.json | 4 + internal/locale/translations/pt_BR.json | 4 + internal/locale/translations/ru_RU.json | 4 + internal/locale/translations/tr_TR.json | 4 + internal/locale/translations/uk_UA.json | 4 + internal/locale/translations/zh_CN.json | 4 + internal/locale/translations/zh_TW.json | 4 + internal/model/integration.go | 4 + internal/storage/integration.go | 38 ++++- .../templates/views/integrations.html | 22 +++ internal/ui/form/integration.go | 12 ++ internal/ui/integration_show.go | 4 + 26 files changed, 312 insertions(+), 13 deletions(-) create mode 100644 internal/integration/shiori/shiori.go diff --git a/internal/database/migrations.go b/internal/database/migrations.go index 10975de0..59d08012 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -709,26 +709,36 @@ var migrations = []func(tx *sql.Tx) error{ }, func(tx *sql.Tx) (err error) { sql := ` - ALTER TABLE integrations ADD COLUMN notion_enabled bool default 'f'; - ALTER TABLE integrations ADD COLUMN notion_token text default ''; - ALTER TABLE integrations ADD COLUMN notion_page_id text default ''; + ALTER TABLE integrations ADD COLUMN notion_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN notion_token text default ''; + ALTER TABLE integrations ADD COLUMN notion_page_id text default ''; ` _, err = tx.Exec(sql) return err }, func(tx *sql.Tx) (err error) { sql := ` - ALTER TABLE integrations ADD COLUMN readwise_enabled bool default 'f'; - ALTER TABLE integrations ADD COLUMN readwise_api_key text default ''; + ALTER TABLE integrations ADD COLUMN readwise_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN readwise_api_key text default ''; ` _, err = tx.Exec(sql) return err }, func(tx *sql.Tx) (err error) { sql := ` - ALTER TABLE integrations ADD COLUMN apprise_enabled bool default 'f'; - ALTER TABLE integrations ADD COLUMN apprise_url text default ''; - ALTER TABLE integrations ADD COLUMN apprise_services_url text default ''; + ALTER TABLE integrations ADD COLUMN apprise_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN apprise_url text default ''; + ALTER TABLE integrations ADD COLUMN apprise_services_url text default ''; + ` + _, err = tx.Exec(sql) + return err + }, + func(tx *sql.Tx) (err error) { + sql := ` + ALTER TABLE integrations ADD COLUMN shiori_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN shiori_url text default ''; + ALTER TABLE integrations ADD COLUMN shiori_username text default ''; + ALTER TABLE integrations ADD COLUMN shiori_password text default ''; ` _, err = tx.Exec(sql) return err diff --git a/internal/integration/integration.go b/internal/integration/integration.go index faa263d2..1b47638e 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/shiori" "miniflux.app/v2/internal/integration/telegrambot" "miniflux.app/v2/internal/integration/wallabag" "miniflux.app/v2/internal/logger" @@ -137,6 +138,20 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { logger.Error("[Integration] UserID #%d: %v", integration.UserID, err) } } + + if integration.ShioriEnabled { + logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Shiori", entry.ID, entry.URL, integration.UserID) + + client := shiori.NewClient( + integration.ShioriURL, + integration.ShioriUsername, + integration.ShioriPassword, + ) + + if err := client.AddBookmark(entry.URL, entry.Title); err != nil { + logger.Error("[Integration] Unable to send entry #%d to Shiori 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/shiori/shiori.go b/internal/integration/shiori/shiori.go new file mode 100644 index 00000000..c55a530d --- /dev/null +++ b/internal/integration/shiori/shiori.go @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package shiori // import "miniflux.app/v2/internal/integration/shiori" + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" + + "miniflux.app/v2/internal/url" + "miniflux.app/v2/internal/version" +) + +const defaultClientTimeout = 10 * time.Second + +type Client struct { + baseURL string + username string + password string +} + +func NewClient(baseURL, username, password string) *Client { + return &Client{baseURL: baseURL, username: username, password: password} +} + +func (c *Client) AddBookmark(entryURL, entryTitle string) error { + if c.baseURL == "" || c.username == "" || c.password == "" { + return fmt.Errorf("shiori: missing base URL, username or password") + } + + sessionID, err := c.authenticate() + if err != nil { + return fmt.Errorf("shiori: unable to authenticate: %v", err) + } + + apiEndpoint, err := url.JoinBaseURLAndPath(c.baseURL, "/api/bookmarks") + if err != nil { + return fmt.Errorf("shiori: invalid API endpoint: %v", err) + } + + requestBody, err := json.Marshal(&addBookmarkRequest{ + URL: entryURL, + Title: entryTitle, + CreateArchive: true, + }) + + if err != nil { + return fmt.Errorf("shiori: unable to encode request body: %v", err) + } + + request, err := http.NewRequest("POST", apiEndpoint, bytes.NewReader(requestBody)) + if err != nil { + return fmt.Errorf("shiori: 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("X-Session-Id", sessionID) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + + response, err := httpClient.Do(request) + if err != nil { + return fmt.Errorf("shiori: unable to send request: %v", err) + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("shiori: unable to create bookmark: url=%s status=%d", apiEndpoint, response.StatusCode) + } + + return nil +} + +func (c *Client) authenticate() (sessionID string, err error) { + apiEndpoint, err := url.JoinBaseURLAndPath(c.baseURL, "/api/login") + if err != nil { + return "", fmt.Errorf("shiori: invalid API endpoint: %v", err) + } + + requestBody, err := json.Marshal(&authRequest{Username: c.username, Password: c.password}) + if err != nil { + return "", fmt.Errorf("shiori: unable to encode request body: %v", err) + } + + request, err := http.NewRequest("POST", apiEndpoint, bytes.NewReader(requestBody)) + if err != nil { + return "", fmt.Errorf("shiori: 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) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + + response, err := httpClient.Do(request) + if err != nil { + return "", fmt.Errorf("shiori: unable to send request: %v", err) + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return "", fmt.Errorf("shiori: unable to authenticate: url=%s status=%d", apiEndpoint, response.StatusCode) + } + + var authResponse authResponse + if err := json.NewDecoder(response.Body).Decode(&authResponse); err != nil { + return "", fmt.Errorf("shiori: unable to decode response: %v", err) + } + + return authResponse.SessionID, nil +} + +type authRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type authResponse struct { + SessionID string `json:"session"` +} + +type addBookmarkRequest struct { + URL string `json:"url"` + Title string `json:"title"` + CreateArchive bool `json:"createArchive"` +} diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index d06830b8..4455d53e 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -381,6 +381,10 @@ "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.shiori_activate": "Artikel in Shiori", + "form.integration.shiori_endpoint": "Shiori API-Endpunkt", + "form.integration.shiori_username": "Shiori Benutzername", + "form.integration.shiori_password": "Shiori Passwort", "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 e7774136..e3928be5 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -381,6 +381,10 @@ "form.integration.matrix_bot_password": "Κωδικός πρόσβασης για τον χρήστη Matrix", "form.integration.matrix_bot_url": "URL διακομιστή Matrix", "form.integration.matrix_bot_chat_id": "Αναγνωριστικό της αίθουσας Matrix", + "form.integration.shiori_activate": "Αποθήκευση άρθρων στο Shiori", + "form.integration.shiori_endpoint": "Τελικό σημείο Shiori", + "form.integration.shiori_username": "Όνομα Χρήστη Shiori", + "form.integration.shiori_password": "Κωδικός Πρόσβασης Shiori", "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 7b3e2ccf..66a4762d 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -381,6 +381,10 @@ "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.shiori_activate": "Save articles to Shiori", + "form.integration.shiori_endpoint": "Shiori API Endpoint", + "form.integration.shiori_username": "Shiori Username", + "form.integration.shiori_password": "Shiori Password", "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 d09be909..09a28fd4 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -381,6 +381,10 @@ "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.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", + "form.integration.shiori_password": "Contraseña de Shiori", "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 81fdb5e6..00c731ef 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -381,6 +381,10 @@ "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.shiori_activate": "Save articles to Shiori", + "form.integration.shiori_endpoint": "Shiori API Endpoint", + "form.integration.shiori_username": "Shiori Username", + "form.integration.shiori_password": "Shiori Password", "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 cc024e18..23044dd9 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -381,6 +381,10 @@ "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.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", + "form.integration.shiori_password": "Mot de passe de Shiori", "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 c56b275d..888e6a3a 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -381,6 +381,10 @@ "form.integration.matrix_bot_password": "मैट्रिक्स उपयोगकर्ता के लिए पासवर्ड", "form.integration.matrix_bot_url": "मैट्रिक्स सर्वर URL", "form.integration.matrix_bot_chat_id": "मैट्रिक्स रूम की आईडी", + "form.integration.shiori_activate": "Save articles to Shiori", + "form.integration.shiori_endpoint": "Shiori API Endpoint", + "form.integration.shiori_username": "Shiori Username", + "form.integration.shiori_password": "Shiori Password", "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 f0c62fd5..4934ed57 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -378,6 +378,10 @@ "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.shiori_activate": "Save articles to Shiori", + "form.integration.shiori_endpoint": "Shiori API Endpoint", + "form.integration.shiori_username": "Shiori Username", + "form.integration.shiori_password": "Shiori Password", "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 e45754a7..f292f8c2 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -381,6 +381,10 @@ "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.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", + "form.integration.shiori_password": "Password dell'account Shiori", "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 a7e88a40..18044a71 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -381,6 +381,10 @@ "form.integration.matrix_bot_password": "Matrixユーザ用パスワード", "form.integration.matrix_bot_url": "MatrixサーバーのURL", "form.integration.matrix_bot_chat_id": "MatrixルームのID", + "form.integration.shiori_activate": "Shiori に記事を保存する", + "form.integration.shiori_endpoint": "Shiori の API Endpoint", + "form.integration.shiori_username": "Shiori の ユーザー名", + "form.integration.shiori_password": "Shiori の パスワード", "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 d401f98d..033c58ed 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -381,6 +381,10 @@ "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.shiori_activate": "Opslaan naar Shiori", + "form.integration.shiori_endpoint": "Shiori URL", + "form.integration.shiori_username": "Shiori gebruikersnaam", + "form.integration.shiori_password": "Shiori wachtwoord", "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 21f0c339..c8e22131 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -383,6 +383,10 @@ "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.shiori_activate": "Zapisz artykuły do Shiori", + "form.integration.shiori_endpoint": "Shiori URL", + "form.integration.shiori_username": "Login do Shiori", + "form.integration.shiori_password": "Hasło do Shiori", "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 e97089df..0b796060 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -381,6 +381,10 @@ "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.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", + "form.integration.shiori_password": "Senha do Shiori", "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 a679cf81..8ad3af8e 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -383,6 +383,10 @@ "form.integration.matrix_bot_password": "Пароль пользователя Matrix", "form.integration.matrix_bot_url": "Ссылка на сервер Matrix", "form.integration.matrix_bot_chat_id": "ID комнаты Matrix", + "form.integration.shiori_activate": "Сохранять статьи в Shiori", + "form.integration.shiori_endpoint": "Конечная точка Shiori API", + "form.integration.shiori_username": "Имя пользователя Shiori", + "form.integration.shiori_password": "Пароль Shiori", "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 3fd475a4..d4774b91 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -381,6 +381,10 @@ "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.shiori_activate": "Makaleleri Shiori'e kaydet", + "form.integration.shiori_endpoint": "Shiori API Uç Noktası", + "form.integration.shiori_username": "Shiori Kullanıcı Adı", + "form.integration.shiori_password": "Shiori Parolası", "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 5ba751d4..b0d3c569 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -380,6 +380,10 @@ "form.integration.matrix_bot_password": "Пароль для користувача Matrix", "form.integration.matrix_bot_url": "URL-адреса сервера Матриці", "form.integration.matrix_bot_chat_id": "Ідентифікатор кімнати Матриці", + "form.integration.shiori_activate": "Save articles to Shiori", + "form.integration.shiori_endpoint": "Shiori API Endpoint", + "form.integration.shiori_username": "Shiori Username", + "form.integration.shiori_password": "Shiori Password", "form.api_key.label.description": "Назва ключа API", "form.submit.loading": "Завантаження...", "form.submit.saving": "Зберігаю...", diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index a107a523..6034e23b 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -379,6 +379,10 @@ "form.integration.matrix_bot_password": "矩阵用户密码", "form.integration.matrix_bot_url": "矩阵服务器 URL", "form.integration.matrix_bot_chat_id": "Matrix房间ID", + "form.integration.shiori_activate": "保存文章到 Shiori", + "form.integration.shiori_endpoint": "Shiori API Endpoint", + "form.integration.shiori_username": "Shiori 用户名", + "form.integration.shiori_password": "Shiori 密码", "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 e9c5f285..b32f8758 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -381,6 +381,10 @@ "form.integration.matrix_bot_password": "矩陣用戶密碼", "form.integration.matrix_bot_url": "矩陣服務器 URL", "form.integration.matrix_bot_chat_id": "Matrix房間ID", + "form.integration.shiori_activate": "Save articles to Shiori", + "form.integration.shiori_endpoint": "Shiori API Endpoint", + "form.integration.shiori_username": "Shiori Username", + "form.integration.shiori_password": "Shiori Password", "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 edfad601..28a0da9a 100644 --- a/internal/model/integration.go +++ b/internal/model/integration.go @@ -57,4 +57,8 @@ type Integration struct { AppriseEnabled bool AppriseURL string AppriseServicesURL string + ShioriEnabled bool + ShioriURL string + ShioriUsername string + ShioriPassword string } diff --git a/internal/storage/integration.go b/internal/storage/integration.go index b0a10128..61ecc679 100644 --- a/internal/storage/integration.go +++ b/internal/storage/integration.go @@ -160,7 +160,11 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { matrix_bot_chat_id, apprise_enabled, apprise_url, - apprise_services_url + apprise_services_url, + shiori_enabled, + shiori_url, + shiori_username, + shiori_password FROM integrations WHERE @@ -220,6 +224,10 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.AppriseEnabled, &integration.AppriseURL, &integration.AppriseServicesURL, + &integration.ShioriEnabled, + &integration.ShioriURL, + &integration.ShioriUsername, + &integration.ShioriPassword, ) switch { case err == sql.ErrNoRows: @@ -287,9 +295,13 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { readwise_api_key=$48, apprise_enabled=$49, apprise_url=$50, - apprise_services_url=$51 + apprise_services_url=$51, + shiori_enabled=$52, + shiori_url=$53, + shiori_username=$54, + shiori_password=$55 WHERE - user_id=$52 + user_id=$56 ` _, err := s.db.Exec( query, @@ -344,11 +356,15 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.AppriseEnabled, integration.AppriseURL, integration.AppriseServicesURL, + integration.ShioriEnabled, + integration.ShioriURL, + integration.ShioriUsername, + integration.ShioriPassword, integration.UserID, ) if err != nil { - return fmt.Errorf(`store: unable to update integration row: %v`, err) + return fmt.Errorf(`store: unable to update integration record: %v`, err) } return nil @@ -364,7 +380,19 @@ func (s *Storage) HasSaveEntry(userID int64) (result bool) { WHERE user_id=$1 AND - (pinboard_enabled='t' OR instapaper_enabled='t' OR wallabag_enabled='t' OR notion_enabled='t' OR nunux_keeper_enabled='t' OR espial_enabled='t' OR readwise_enabled='t' OR pocket_enabled='t' OR linkding_enabled='t' OR apprise_enabled='t') + ( + pinboard_enabled='t' OR + instapaper_enabled='t' OR + wallabag_enabled='t' OR + notion_enabled='t' OR + nunux_keeper_enabled='t' OR + espial_enabled='t' OR + readwise_enabled='t' OR + pocket_enabled='t' OR + linkding_enabled='t' OR + apprise_enabled='t' OR + shiori_enabled='t' + ) ` if err := s.db.QueryRow(query, userID).Scan(&result); err != nil { result = false diff --git a/internal/template/templates/views/integrations.html b/internal/template/templates/views/integrations.html index 01f6e31e..f26d399f 100644 --- a/internal/template/templates/views/integrations.html +++ b/internal/template/templates/views/integrations.html @@ -324,6 +324,28 @@ + +
+ Shiori +
+ + + + + + + + + + + +
+ +
+
+

{{ t "page.integration.bookmarklet" }}

diff --git a/internal/ui/form/integration.go b/internal/ui/form/integration.go index 9e60d6fd..5a332f28 100644 --- a/internal/ui/form/integration.go +++ b/internal/ui/form/integration.go @@ -62,6 +62,10 @@ type IntegrationForm struct { AppriseEnabled bool AppriseURL string AppriseServicesURL string + ShioriEnabled bool + ShioriURL string + ShioriUsername string + ShioriPassword string } // Merge copy form values to the model. @@ -115,6 +119,10 @@ func (i IntegrationForm) Merge(integration *model.Integration) { integration.AppriseEnabled = i.AppriseEnabled integration.AppriseServicesURL = i.AppriseServicesURL integration.AppriseURL = i.AppriseURL + integration.ShioriEnabled = i.ShioriEnabled + integration.ShioriURL = i.ShioriURL + integration.ShioriUsername = i.ShioriUsername + integration.ShioriPassword = i.ShioriPassword } // NewIntegrationForm returns a new IntegrationForm. @@ -171,5 +179,9 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm { AppriseEnabled: r.FormValue("apprise_enabled") == "1", AppriseURL: r.FormValue("apprise_url"), AppriseServicesURL: r.FormValue("apprise_services_url"), + ShioriEnabled: r.FormValue("shiori_enabled") == "1", + ShioriURL: r.FormValue("shiori_url"), + ShioriUsername: r.FormValue("shiori_username"), + ShioriPassword: r.FormValue("shiori_password"), } } diff --git a/internal/ui/integration_show.go b/internal/ui/integration_show.go index 405628dc..b1985d51 100644 --- a/internal/ui/integration_show.go +++ b/internal/ui/integration_show.go @@ -77,6 +77,10 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) { AppriseEnabled: integration.AppriseEnabled, AppriseURL: integration.AppriseURL, AppriseServicesURL: integration.AppriseServicesURL, + ShioriEnabled: integration.ShioriEnabled, + ShioriURL: integration.ShioriURL, + ShioriUsername: integration.ShioriUsername, + ShioriPassword: integration.ShioriPassword, } sess := session.New(h.store, request.SessionID(r))