diff --git a/database/migrations.go b/database/migrations.go index e7f806cd..7a2eb010 100644 --- a/database/migrations.go +++ b/database/migrations.go @@ -716,4 +716,12 @@ 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 readwise_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN readwise_api_key text default ''; + ` + _, err = tx.Exec(sql) + return err + }, } diff --git a/integration/integration.go b/integration/integration.go index 96f02305..3fb2e583 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -13,6 +13,7 @@ import ( "miniflux.app/integration/nunuxkeeper" "miniflux.app/integration/pinboard" "miniflux.app/integration/pocket" + "miniflux.app/integration/readwise" "miniflux.app/integration/telegrambot" "miniflux.app/integration/wallabag" "miniflux.app/logger" @@ -123,6 +124,18 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { logger.Error("[Integration] UserID #%d: %v", integration.UserID, err) } } + + if integration.ReadwiseEnabled { + logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Readwise Reader", entry.ID, entry.URL, integration.UserID) + + client := readwise.NewClient( + integration.ReadwiseAPIKey, + ) + + if err := client.AddEntry(entry.URL); err != nil { + logger.Error("[Integration] UserID #%d: %v", integration.UserID, err) + } + } } // PushEntries pushes an entry array to third-party providers during feed refreshes. diff --git a/integration/readwise/readwise.go b/integration/readwise/readwise.go new file mode 100644 index 00000000..538db68f --- /dev/null +++ b/integration/readwise/readwise.go @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Readwise Reader API documentation: https://readwise.io/reader_api + +package readwise // import "miniflux.app/integration/readwise" + +import ( + "fmt" + "net/url" + + "miniflux.app/http/client" +) + +// Document structure of a Readwise Reader document +// This initial version accepts only the one required field, the URL +type Document struct { + Url string `json:"url"` +} + +// Client represents a Readwise Reader client. +type Client struct { + apiKey string +} + +// NewClient returns a new Readwise Reader client. +func NewClient(apiKey string) *Client { + return &Client{apiKey: apiKey} +} + +// AddEntry sends an entry to Readwise Reader. +func (c *Client) AddEntry(link string) error { + if c.apiKey == "" { + return fmt.Errorf("readwise: missing credentials") + } + + doc := &Document{ + Url: link, + } + + apiURL, err := getAPIEndpoint("https://readwise.io/api/v3/save/") + 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("readwise: unable to send entry: %v", err) + } + + if response.HasServerFailure() { + return fmt.Errorf("readwise: unable to send entry, status=%d", response.StatusCode) + } + + return nil +} + +func getAPIEndpoint(pathURL string) (string, error) { + u, err := url.Parse(pathURL) + if err != nil { + return "", fmt.Errorf("readwise: invalid API endpoint: %v", err) + } + return u.String(), nil +} diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index 0ef14f91..89b2ae16 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Espial API-Endpunkt", "form.integration.espial_api_key": "Espial API-Schlüssel", "form.integration.espial_tags": "Espial tags", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/el_EL.json b/locale/translations/el_EL.json index 6527fed3..0994d9fb 100644 --- a/locale/translations/el_EL.json +++ b/locale/translations/el_EL.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Τελικό σημείο Espial API", "form.integration.espial_api_key": "Κλειδί API Espial", "form.integration.espial_tags": "Ετικέτες Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "Προωθήστε νέα άρθρα στη συνομιλία Telegram", "form.integration.telegram_bot_token": "Διακριτικό bot", "form.integration.telegram_chat_id": "Αναγνωριστικό συνομιλίας", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index d278bfdb..cda3a281 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Espial API Endpoint", "form.integration.espial_api_key": "Espial API key", "form.integration.espial_tags": "Espial Tags", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "Push new entries to Telegram chat", "form.integration.telegram_bot_token": "Bot token", "form.integration.telegram_chat_id": "Chat ID", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index e69e56d4..5ccf5f21 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Acceso API de Espial", "form.integration.espial_api_key": "Clave de API de Espial", "form.integration.espial_tags": "Etiquetas de Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/fi_FI.json b/locale/translations/fi_FI.json index 16b6bb39..c7050abd 100644 --- a/locale/translations/fi_FI.json +++ b/locale/translations/fi_FI.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Espial API-päätepiste", "form.integration.espial_api_key": "Espial API-avain", "form.integration.espial_tags": "Espial-tagit", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index c1e6975d..b4f35c67 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "URL de l'API de Espial", "form.integration.espial_api_key": "Clé d'API de Espial", "form.integration.espial_tags": "Libellés de Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/hi_IN.json b/locale/translations/hi_IN.json index 7e0b2f54..8ad4cc58 100644 --- a/locale/translations/hi_IN.json +++ b/locale/translations/hi_IN.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "जासूसी एपीआई समापन बिंदु", "form.integration.espial_api_key": "जासूसी एपीआई कुंजी", "form.integration.espial_tags": "जासूसी टैग", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "टेलीग्राम चैट के लिए नई विषय-कविता पुश करें", "form.integration.telegram_bot_token": "बॉट टोकन", "form.integration.telegram_chat_id": "चैट आईडी", diff --git a/locale/translations/id_ID.json b/locale/translations/id_ID.json index 4e4a8a39..6e8623d2 100644 --- a/locale/translations/id_ID.json +++ b/locale/translations/id_ID.json @@ -358,6 +358,9 @@ "form.integration.espial_endpoint": "Titik URL API Espial", "form.integration.espial_api_key": "Kunci API Espial", "form.integration.espial_tags": "Tanda di Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "Kirim artikel baru ke percakapan Telegram", "form.integration.telegram_bot_token": "Token Bot", "form.integration.telegram_chat_id": "ID Obrolan", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index d30b7f66..23f61428 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Endpoint dell'API di Espial", "form.integration.espial_api_key": "API key dell'account Espial", "form.integration.espial_tags": "Tag di Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json index 782d6e2d..0f5b6297 100644 --- a/locale/translations/ja_JP.json +++ b/locale/translations/ja_JP.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Espial の API Endpoint", "form.integration.espial_api_key": "Espial の API key", "form.integration.espial_tags": "Espial の Tag", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "新しい記事を Telegram チャットにプッシュする", "form.integration.telegram_bot_token": "ボットトークン", "form.integration.telegram_chat_id": "チャット ID", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index 3fb32dbe..2618ebcb 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Espial URL", "form.integration.espial_api_key": "Espial API-sleutel", "form.integration.espial_tags": "Espial tags", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index 6f32532e..74e34b4e 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -363,6 +363,9 @@ "form.integration.espial_endpoint": "Espial URL", "form.integration.espial_api_key": "Espial API key", "form.integration.espial_tags": "Espial Tags", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/pt_BR.json b/locale/translations/pt_BR.json index f5ee949d..6a2d1b94 100644 --- a/locale/translations/pt_BR.json +++ b/locale/translations/pt_BR.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Endpoint de API do Espial", "form.integration.espial_api_key": "Chave de API do Espial", "form.integration.espial_tags": "Etiquetas (tags) do Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index 82eb0481..3c41d2a9 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -363,6 +363,9 @@ "form.integration.espial_endpoint": "Конечная точка Espial API", "form.integration.espial_api_key": "API-ключ Espial", "form.integration.espial_tags": "Теги Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "Репостить новые статьи в Telegram-чат", "form.integration.telegram_bot_token": "Токен бота", "form.integration.telegram_chat_id": "ID чата", diff --git a/locale/translations/tr_TR.json b/locale/translations/tr_TR.json index 641efcb4..1e1c7341 100644 --- a/locale/translations/tr_TR.json +++ b/locale/translations/tr_TR.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Espial API Uç Noktası", "form.integration.espial_api_key": "Espial API Anahtarı", "form.integration.espial_tags": "Espial Etiketleri", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "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", diff --git a/locale/translations/uk_UA.json b/locale/translations/uk_UA.json index 42be13f7..84659566 100644 --- a/locale/translations/uk_UA.json +++ b/locale/translations/uk_UA.json @@ -360,6 +360,9 @@ "form.integration.espial_endpoint": "Espial API Endpoint", "form.integration.espial_api_key": "Ключ API Espial", "form.integration.espial_tags": "Теги для Espial", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "Відправляти нові статті до чату Telegram", "form.integration.telegram_bot_token": "Токен боту", "form.integration.telegram_chat_id": "ID чату", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index e96e964a..ed6700f7 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -359,6 +359,9 @@ "form.integration.espial_endpoint": "Espial API 端点", "form.integration.espial_api_key": "Espial API 密钥", "form.integration.espial_tags": "Espial 标签", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "将新文章推送到 Telegram", "form.integration.telegram_bot_token": "机器人令牌", "form.integration.telegram_chat_id": "聊天ID", diff --git a/locale/translations/zh_TW.json b/locale/translations/zh_TW.json index 0330664e..b540a992 100644 --- a/locale/translations/zh_TW.json +++ b/locale/translations/zh_TW.json @@ -361,6 +361,9 @@ "form.integration.espial_endpoint": "Espial API 端點", "form.integration.espial_api_key": "Espial API 金鑰", "form.integration.espial_tags": "Espial 標籤", + "form.integration.readwise_activate": "Save entries to Readwise Reader", + "form.integration.readwise_api_key": "Readwise Reader Access Token", + "form.integration.readwise_api_key_link": "Get your Readwise Access Token", "form.integration.telegram_bot_activate": "將新文章推送到 Telegram", "form.integration.telegram_bot_token": "Bot token", "form.integration.telegram_chat_id": "Chat ID", diff --git a/model/integration.go b/model/integration.go index 50abd7fc..27375ef4 100644 --- a/model/integration.go +++ b/model/integration.go @@ -36,6 +36,8 @@ type Integration struct { EspialURL string EspialAPIKey string EspialTags string + ReadwiseEnabled bool + ReadwiseAPIKey string PocketEnabled bool PocketAccessToken string PocketConsumerKey string diff --git a/storage/integration.go b/storage/integration.go index f6378c8f..2601f951 100644 --- a/storage/integration.go +++ b/storage/integration.go @@ -140,6 +140,8 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { espial_url, espial_api_key, espial_tags, + readwise_enabled, + readwise_api_key, pocket_enabled, pocket_access_token, pocket_consumer_key, @@ -194,6 +196,8 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.EspialURL, &integration.EspialAPIKey, &integration.EspialTags, + &integration.ReadwiseEnabled, + &integration.ReadwiseAPIKey, &integration.PocketEnabled, &integration.PocketAccessToken, &integration.PocketConsumerKey, @@ -272,9 +276,11 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { matrix_bot_chat_id=$43, notion_enabled=$44, notion_token=$45, - notion_page_id=$46 + notion_page_id=$46, + readwise_enabled=$47, + readwise_api_key=$48 WHERE - user_id=$47 + user_id=$49 ` _, err := s.db.Exec( query, @@ -324,6 +330,8 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.NotionEnabled, integration.NotionToken, integration.NotionPageID, + integration.ReadwiseEnabled, + integration.ReadwiseAPIKey, integration.UserID, ) @@ -344,7 +352,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 notion_enabled='t' OR nunux_keeper_enabled='t' OR espial_enabled='t' OR pocket_enabled='t' OR linkding_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') ` 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 afc47bef..afbb6aa7 100644 --- a/template/templates/views/integrations.html +++ b/template/templates/views/integrations.html @@ -195,6 +195,22 @@ +

Readwise Reader

+
+ + + + + +

{{ t "form.integration.readwise_api_key_link" }}

+ +
+ +
+
+

Linkding