From 1658db7f10e2bc7b7d04e5cef0f9f5d172749f99 Mon Sep 17 00:00:00 2001 From: kencx Date: Mon, 23 May 2022 23:53:06 +0800 Subject: [PATCH] Add Linkding integration --- database/migrations.go | 9 +++ integration/integration.go | 13 ++++ integration/linkding/linkding.go | 74 ++++++++++++++++++++++ locale/translations/de_DE.json | 3 + locale/translations/el_EL.json | 3 + locale/translations/en_US.json | 3 + locale/translations/es_ES.json | 3 + locale/translations/fi_FI.json | 3 + locale/translations/fr_FR.json | 3 + locale/translations/it_IT.json | 3 + locale/translations/ja_JP.json | 3 + locale/translations/nl_NL.json | 3 + locale/translations/pl_PL.json | 3 + locale/translations/pt_BR.json | 3 + locale/translations/ru_RU.json | 3 + locale/translations/tr_TR.json | 3 + locale/translations/zh_CN.json | 3 + locale/translations/zh_TW.json | 3 + model/integration.go | 3 + storage/integration.go | 28 ++++++-- template/templates/views/integrations.html | 21 +++++- ui/form/integration.go | 9 +++ ui/integration_show.go | 3 + 23 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 integration/linkding/linkding.go diff --git a/database/migrations.go b/database/migrations.go index 10bf76c6..47e9c7dc 100644 --- a/database/migrations.go +++ b/database/migrations.go @@ -582,4 +582,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 linkding_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN linkding_url text default ''; + ALTER TABLE integrations ADD COLUMN linkding_api_key text default ''; + ` + _, err = tx.Exec(sql) + return err + }, } diff --git a/integration/integration.go b/integration/integration.go index 19f3ebcf..e45f2162 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -8,6 +8,7 @@ import ( "miniflux.app/config" "miniflux.app/integration/espial" "miniflux.app/integration/instapaper" + "miniflux.app/integration/linkding" "miniflux.app/integration/nunuxkeeper" "miniflux.app/integration/pinboard" "miniflux.app/integration/pocket" @@ -94,6 +95,18 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { logger.Error("[Integration] UserID #%d: %v", integration.UserID, err) } } + + if integration.LinkdingEnabled { + logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Linkding", entry.ID, entry.URL, integration.UserID) + + client := linkding.NewClient( + integration.LinkdingURL, + integration.LinkdingAPIKey, + ) + if err := client.AddEntry(entry.Title, entry.URL); err != nil { + logger.Error("[Integration] UserID #%d: %v", integration.UserID, err) + } + } } // PushEntry pushes an entry to third-party providers during feed refreshes. diff --git a/integration/linkding/linkding.go b/integration/linkding/linkding.go new file mode 100644 index 00000000..4674c6a1 --- /dev/null +++ b/integration/linkding/linkding.go @@ -0,0 +1,74 @@ +// Copyright 2017 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package linkding // import "miniflux.app/integration/linkding" + +import ( + "fmt" + "net/url" + + "miniflux.app/http/client" +) + +// Document structure of a Linkding document +type Document struct { + Url string `json:"url,omitempty"` + Title string `json:"title,omitempty"` +} + +// Client represents an Linkding client. +type Client struct { + baseURL string + apiKey string +} + +// NewClient returns a new Linkding client. +func NewClient(baseURL, apiKey string) *Client { + return &Client{baseURL: baseURL, apiKey: apiKey} +} + +// AddEntry sends an entry to Linkding. +func (c *Client) AddEntry(title, url string) error { + if c.baseURL == "" || c.apiKey == "" { + return fmt.Errorf("linkding: missing credentials") + } + + doc := &Document{ + Url: url, + Title: title, + } + + apiURL, err := getAPIEndpoint(c.baseURL, "/api/bookmarks/") + if err != nil { + return err + } + + clt := client.New(apiURL) + clt.WithAuthorization("Token " + c.apiKey) + response, err := clt.PostJSON(doc) + if err != nil { + return fmt.Errorf("linkding: unable to send entry: %v", err) + } + + if response.HasServerFailure() { + return fmt.Errorf("linkding: unable to send entry, status=%d", response.StatusCode) + } + + return nil +} + +func getAPIEndpoint(baseURL, pathURL string) (string, error) { + u, err := url.Parse(baseURL) + if err != nil { + return "", fmt.Errorf("linkding: invalid API endpoint: %v", err) + } + + relative, err := url.Parse(pathURL) + if err != nil { + return "", fmt.Errorf("linkding: invalid API endpoint: %v", err) + } + + u = u.ResolveReference(relative) + return u.String(), nil +} diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index 692c0c91..4b4ab61a 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Pushen Sie neue Artikel in den Telegram-Chat", "form.integration.telegram_bot_token": "Bot token", "form.integration.telegram_chat_id": "Chat ID", + "form.integration.linkding_activate": "Artikel in Linkding speichern", + "form.integration.linkding_endpoint": "Linkding API-Endpunkt", + "form.integration.linkding_api_key": "Linkding API-Schlüssel", "form.api_key.label.description": "API-Schlüsselbezeichnung", "form.submit.loading": "Lade...", "form.submit.saving": "Speichern...", diff --git a/locale/translations/el_EL.json b/locale/translations/el_EL.json index 6cb4dec7..d1b0acec 100644 --- a/locale/translations/el_EL.json +++ b/locale/translations/el_EL.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Προωθήστε νέα άρθρα στη συνομιλία Telegram", "form.integration.telegram_bot_token": "Διακριτικό bot", "form.integration.telegram_chat_id": "Αναγνωριστικό συνομιλίας", + "form.integration.linkding_activate": "Αποθήκευση άρθρων στο Linkding", + "form.integration.linkding_endpoint": "Τελικό σημείο Linkding API", + "form.integration.linkding_api_key": "Κλειδί API Linkding", "form.api_key.label.description": "Ετικέτα κλειδιού API", "form.submit.loading": "Φόρτωση...", "form.submit.saving": "Αποθήκευση...", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index c799240a..4b2c8cb2 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Push new articles to Telegram chat", "form.integration.telegram_bot_token": "Bot token", "form.integration.telegram_chat_id": "Chat ID", + "form.integration.linkding_activate": "Save articles to Linkding", + "form.integration.linkding_endpoint": "Linkding API Endpoint", + "form.integration.linkding_api_key": "Linkding API key", "form.api_key.label.description": "API Key Label", "form.submit.loading": "Loading...", "form.submit.saving": "Saving...", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index d90525d4..cb57c149 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Envíe nuevos artículos al chat de Telegram", "form.integration.telegram_bot_token": "Token de bot", "form.integration.telegram_chat_id": "ID de chat", + "form.integration.linkding_activate": "Guardar artículos a Linkding", + "form.integration.linkding_endpoint": "Extremo de API de Linkding", + "form.integration.linkding_api_key": "Clave de API de Linkding", "form.api_key.label.description": "Etiqueta de clave API", "form.submit.loading": "Cargando...", "form.submit.saving": "Guardando...", diff --git a/locale/translations/fi_FI.json b/locale/translations/fi_FI.json index 21d641dc..f869adfa 100644 --- a/locale/translations/fi_FI.json +++ b/locale/translations/fi_FI.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Lähetä uusia artikkeleita Telegram-chatiin", "form.integration.telegram_bot_token": "Bot-tunnus", "form.integration.telegram_chat_id": "Chat ID", + "form.integration.linkding_activate": "Tallenna artikkelit Linkkiin", + "form.integration.linkding_endpoint": "Linkding API-päätepiste", + "form.integration.linkding_api_key": "Linkding API-avain", "form.api_key.label.description": "API Key Label", "form.submit.loading": "Ladataan...", "form.submit.saving": "Tallennetaan...", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index 7fb00661..fd9c0a3b 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Envoyer les nouveaux articles vers Telegram", "form.integration.telegram_bot_token": "Jeton de sécurité de l'API du Bot Telegram", "form.integration.telegram_chat_id": "Identifiant de discussion", + "form.integration.linkding_activate": "Sauvegarder les articles vers Linkding", + "form.integration.linkding_endpoint": "URL de l'API de Linkding", + "form.integration.linkding_api_key": "Clé d'API de Linkding", "form.api_key.label.description": "Libellé de la clé d'API", "form.submit.loading": "Chargement...", "form.submit.saving": "Sauvegarde en cours...", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index 87e41481..10c36ed8 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Invia nuovi articoli alla chat di Telegram", "form.integration.telegram_bot_token": "Token bot", "form.integration.telegram_chat_id": "ID chat", + "form.integration.linkding_activate": "Salva gli articoli su Linkding", + "form.integration.linkding_endpoint": "Endpoint dell'API di Linkding", + "form.integration.linkding_api_key": "API key dell'account Linkding", "form.api_key.label.description": "Etichetta chiave API", "form.submit.loading": "Caricamento in corso...", "form.submit.saving": "Salvataggio in corso...", diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json index 16336198..29bcef53 100644 --- a/locale/translations/ja_JP.json +++ b/locale/translations/ja_JP.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "新しい記事をTelegramチャットにプッシュする", "form.integration.telegram_bot_token": "ボットトークン", "form.integration.telegram_chat_id": "チャットID", + "form.integration.linkding_activate": "Linkding に記事を保存する", + "form.integration.linkding_endpoint": "Linkding の API Endpoint", + "form.integration.linkding_api_key": "Linkding の API key", "form.api_key.label.description": "APIキーラベル", "form.submit.loading": "読み込み中…", "form.submit.saving": "保存中…", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index 0f185d8b..638370b3 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Push nieuwe artikelen naar Telegram-chat", "form.integration.telegram_bot_token": "Bot token", "form.integration.telegram_chat_id": "Chat ID", + "form.integration.linkding_activate": "Opslaan naar Linkding", + "form.integration.linkding_endpoint": "Linkding URL", + "form.integration.linkding_api_key": "Linkding API-sleutel", "form.api_key.label.description": "API-sleutellabel", "form.submit.loading": "Laden...", "form.submit.saving": "Opslaag...", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index 859e2fee..d3f08e3f 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -344,6 +344,9 @@ "form.integration.telegram_bot_activate": "Przesyłaj nowe artykuły do czatu Telegram", "form.integration.telegram_bot_token": "Token bota", "form.integration.telegram_chat_id": "Identyfikator czatu", + "form.integration.linkding_activate": "Zapisz artykuły do Linkding", + "form.integration.linkding_endpoint": "Linkding URL", + "form.integration.linkding_api_key": "Linkding API key", "form.api_key.label.description": "Etykieta klucza API", "form.submit.loading": "Ładowanie...", "form.submit.saving": "Zapisywanie...", diff --git a/locale/translations/pt_BR.json b/locale/translations/pt_BR.json index 2ddaafba..6a93ea62 100644 --- a/locale/translations/pt_BR.json +++ b/locale/translations/pt_BR.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Envie novos artigos para o chat do Telegram", "form.integration.telegram_bot_token": "Token de bot", "form.integration.telegram_chat_id": "ID de bate-papo", + "form.integration.linkding_activate": "Salvar itens no Linkding", + "form.integration.linkding_endpoint": "Endpoint de API do Linkding", + "form.integration.linkding_api_key": "Chave de API do Linkding", "form.api_key.label.description": "Etiqueta da chave de API", "form.submit.loading": "Carregando...", "form.submit.saving": "Salvando...", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index f6ba8e8b..ec54657c 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -344,6 +344,9 @@ "form.integration.telegram_bot_activate": "Публикуйте новые статьи в Telegram-чате", "form.integration.telegram_bot_token": "Токен бота", "form.integration.telegram_chat_id": "ID чата", + "form.integration.linkding_activate": "Сохранять статьи в Linkding", + "form.integration.linkding_endpoint": "Конечная точка Linkding API", + "form.integration.linkding_api_key": "Linkding API key", "form.api_key.label.description": "Описание API-ключа", "form.submit.loading": "Загрузка…", "form.submit.saving": "Сохранение…", diff --git a/locale/translations/tr_TR.json b/locale/translations/tr_TR.json index a422e83d..d7e6c8f6 100644 --- a/locale/translations/tr_TR.json +++ b/locale/translations/tr_TR.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "Yeni makaleleri Telegram sohbetine gönderin", "form.integration.telegram_bot_token": "Bot jetonu", "form.integration.telegram_chat_id": "Sohbet kimliği", + "form.integration.linkding_activate": "Makaleleri Linkding'e kaydet", + "form.integration.linkding_endpoint": "Linkding API Uç Noktası", + "form.integration.linkding_api_key": "Linkding API Anahtarı", "form.api_key.label.description": "API Anahtar Etiketi", "form.submit.loading": "Yükleniyor...", "form.submit.saving": "Kaydediliyor...", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index a523ae13..877b298d 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -340,6 +340,9 @@ "form.integration.telegram_bot_activate": "将新文章推送到 Telegram", "form.integration.telegram_bot_token": "机器人令牌", "form.integration.telegram_chat_id": "聊天ID", + "form.integration.linkding_activate": "保存文章到 Linkding", + "form.integration.linkding_endpoint": "Linkding API 端点", + "form.integration.linkding_api_key": "Linkding API 密钥", "form.api_key.label.description": "API密钥标签", "form.submit.loading": "载入中…", "form.submit.saving": "保存中…", diff --git a/locale/translations/zh_TW.json b/locale/translations/zh_TW.json index df7cea00..59e19847 100644 --- a/locale/translations/zh_TW.json +++ b/locale/translations/zh_TW.json @@ -342,6 +342,9 @@ "form.integration.telegram_bot_activate": "將新文章推送到 Telegram", "form.integration.telegram_bot_token": "Bot token", "form.integration.telegram_chat_id": "Chat ID", + "form.integration.linkding_activate": "儲存文章到 Linkding", + "form.integration.linkding_endpoint": "Linkding API 端點", + "form.integration.linkding_api_key": "Linkding API 金鑰", "form.api_key.label.description": "API金鑰標籤", "form.submit.loading": "載入中…", "form.submit.saving": "儲存中…", diff --git a/model/integration.go b/model/integration.go index 3efed7cf..b876c3c6 100644 --- a/model/integration.go +++ b/model/integration.go @@ -39,4 +39,7 @@ type Integration struct { TelegramBotEnabled bool TelegramBotToken string TelegramBotChatID string + LinkdingEnabled bool + LinkdingURL string + LinkdingAPIKey string } diff --git a/storage/integration.go b/storage/integration.go index 05fb074b..85a6cff0 100644 --- a/storage/integration.go +++ b/storage/integration.go @@ -142,7 +142,10 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { pocket_consumer_key, telegram_bot_enabled, telegram_bot_token, - telegram_bot_chat_id + telegram_bot_chat_id, + linkding_enabled, + linkding_url, + linkding_api_key FROM integrations WHERE @@ -183,6 +186,9 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.TelegramBotEnabled, &integration.TelegramBotToken, &integration.TelegramBotChatID, + &integration.LinkdingEnabled, + &integration.LinkdingURL, + &integration.LinkdingAPIKey, ) switch { case err == sql.ErrNoRows: @@ -238,8 +244,11 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { espial_url=$30, espial_api_key=$31, espial_tags=$32, + linkding_enabled=$33, + linkding_url=$34, + linkding_api_key=$35 WHERE - user_id=$33 + user_id=$36 ` _, err = s.db.Exec( query, @@ -275,6 +284,9 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.EspialURL, integration.EspialAPIKey, integration.EspialTags, + integration.LinkdingEnabled, + integration.LinkdingURL, + integration.LinkdingAPIKey, integration.UserID, ) } else { @@ -313,9 +325,12 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { espial_enabled=$29, espial_url=$30, espial_api_key=$31, - espial_tags=$32 + espial_tags=$32, + linkding_enabled=$33, + linkding_url=$34, + linkding_api_key=$35 WHERE - user_id=$33 + user_id=$36 ` _, err = s.db.Exec( query, @@ -351,6 +366,9 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.EspialURL, integration.EspialAPIKey, integration.EspialTags, + integration.LinkdingEnabled, + integration.LinkdingURL, + integration.LinkdingAPIKey, integration.UserID, ) } @@ -372,7 +390,7 @@ 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 nunux_keeper_enabled='t' OR espial_enabled='t' OR pocket_enabled='t') + (pinboard_enabled='t' OR instapaper_enabled='t' OR wallabag_enabled='t' OR nunux_keeper_enabled='t' OR espial_enabled='t' OR pocket_enabled='t' OR linkding_enabled='t') ` if err := s.db.QueryRow(query, userID).Scan(&result); err != nil { result = false diff --git a/template/templates/views/integrations.html b/template/templates/views/integrations.html index 9f7e0c0e..86356f83 100644 --- a/template/templates/views/integrations.html +++ b/template/templates/views/integrations.html @@ -31,7 +31,7 @@ - +

Google Reader

+

Linkding

+
+ + + + + + + + +
+ +
+
+

Telegram Bot