diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 05e28c9db50..f65edf15fef 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -23,5 +23,9 @@ services: POSTGRES_HOST_AUTH_METHOD: trust ports: - 5432:5432 + apprise: + image: caronc/apprise:latest + restart: unless-stopped + hostname: apprise volumes: postgres-data: null diff --git a/database/migrations.go b/database/migrations.go index 7a2eb01036d..2bc806c7657 100644 --- a/database/migrations.go +++ b/database/migrations.go @@ -724,4 +724,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 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 + }, } diff --git a/integration/apprise/apprise.go b/integration/apprise/apprise.go new file mode 100644 index 00000000000..62cfc941ee0 --- /dev/null +++ b/integration/apprise/apprise.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package apprise + +import ( + "fmt" + "net" + "strings" + "time" + + "miniflux.app/http/client" + "miniflux.app/model" +) + +// Client represents a Apprise client. +type Client struct { + servicesURL string + baseURL string +} + +// NewClient returns a new Apprise client. +func NewClient(serviceURL, baseURL string) *Client { + return &Client{serviceURL, baseURL} +} + +// PushEntry pushes entry to apprise +func (c *Client) PushEntry(entry *model.Entry) error { + if c.baseURL == "" || c.servicesURL == "" { + return fmt.Errorf("apprise: missing credentials") + } + timeout := time.Duration(1 * time.Second) + _, err := net.DialTimeout("tcp", c.baseURL, timeout) + if err != nil { + clt := client.New(c.baseURL + "/notify") + message := "[" + entry.Title + "]" + "(" + entry.URL + ")" + "\n\n" + data := &Data{ + Urls: c.servicesURL, + Body: message, + } + response, error := clt.PostJSON(data) + if error != nil { + return fmt.Errorf("apprise: ending message failed: %v", error) + } + + if response.HasServerFailure() { + return fmt.Errorf("apprise: request failed, status=%d", response.StatusCode) + } + } else { + return fmt.Errorf("%s %s %s", c.baseURL, "responding on port:", strings.Split(c.baseURL, ":")[1]) + } + + return nil +} diff --git a/integration/apprise/wrapper.go b/integration/apprise/wrapper.go new file mode 100644 index 00000000000..8b8bac7da5f --- /dev/null +++ b/integration/apprise/wrapper.go @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package apprise + +type Data struct { + Urls string `json:"urls"` + Body string `json:"body"` +} diff --git a/integration/integration.go b/integration/integration.go index 3fb2e583a5e..7e53531396a 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -5,6 +5,7 @@ package integration // import "miniflux.app/integration" import ( "miniflux.app/config" + "miniflux.app/integration/apprise" "miniflux.app/integration/espial" "miniflux.app/integration/instapaper" "miniflux.app/integration/linkding" @@ -160,4 +161,16 @@ func PushEntry(entry *model.Entry, integration *model.Integration) { logger.Error("[Integration] push entry to telegram bot failed: %v", err) } } + if integration.AppriseEnabled { + logger.Debug("[Integration] Sending Entry %q for User #%d to apprise", entry.URL, integration.UserID) + + client := apprise.NewClient( + integration.AppriseServicesURL, + integration.AppriseURL, + ) + err := client.PushEntry(entry) + if err != nil { + logger.Error("[Integration] push entry to apprise failed: %v", err) + } + } } diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index 89b2ae16751..dce26e84efe 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Artikel in Nunux Keeper speichern", "form.integration.nunux_keeper_endpoint": "Nunux Keeper API-Endpunkt", "form.integration.nunux_keeper_api_key": "Nunux Keeper API-Schlüssel", diff --git a/locale/translations/el_EL.json b/locale/translations/el_EL.json index 0994d9fb105..d504e409df4 100644 --- a/locale/translations/el_EL.json +++ b/locale/translations/el_EL.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Αποθήκευση άρθρων στο Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Τελικό σημείο Nunux Keeper API", "form.integration.nunux_keeper_api_key": "Κλειδί API Nunux Keeper", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index cda3a281c44..7c14437988b 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise Services URLs (seperated by comma)", "form.integration.nunux_keeper_activate": "Save entries to Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Nunux Keeper API Endpoint", "form.integration.nunux_keeper_api_key": "Nunux Keeper API key", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index 5ccf5f217cc..8653aca0e59 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Enviar artículos a Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Acceso API de Nunux Keeper", "form.integration.nunux_keeper_api_key": "Clave de API de Nunux Keeper", diff --git a/locale/translations/fi_FI.json b/locale/translations/fi_FI.json index c7050abd69d..b934bc4b981 100644 --- a/locale/translations/fi_FI.json +++ b/locale/translations/fi_FI.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Tallenna artikkelit Nunux Keeperiin", "form.integration.nunux_keeper_endpoint": "Nunux Keeper API-päätepiste", "form.integration.nunux_keeper_api_key": "Nunux Keeper API-avain", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index b4f35c67a17..43d181abd8f 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Sauvegarder les articles vers Notion", "form.integration.notion_page_id": "l'identifiant de la page Notion", "form.integration.notion_token": "Jeton d'accès de l'API de Notion", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Sauvegarder les articles vers Nunux Keeper", "form.integration.nunux_keeper_endpoint": "URL de l'API de Nunux Keeper", "form.integration.nunux_keeper_api_key": "Clé d'API de Nunux Keeper", diff --git a/locale/translations/hi_IN.json b/locale/translations/hi_IN.json index 8ad4cc58eb3..27ab00b0942 100644 --- a/locale/translations/hi_IN.json +++ b/locale/translations/hi_IN.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "विषय-वस्तु को ननक्स कीपर में सहेजें", "form.integration.nunux_keeper_endpoint": "ननक्स कीपर एपीआई समापन बिंदु", "form.integration.nunux_keeper_api_key": "ननक्स कीपर एपीआई कुंजी", diff --git a/locale/translations/id_ID.json b/locale/translations/id_ID.json index 6e8623d2e63..7b62a2a799c 100644 --- a/locale/translations/id_ID.json +++ b/locale/translations/id_ID.json @@ -351,6 +351,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Simpan artikel ke Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Titik URL API Nunux Keeper", "form.integration.nunux_keeper_api_key": "Kunci API Nunux Keeper", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index 23f61428432..f4031ce857e 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Salva gli articoli su Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Endpoint dell'API di Nunux Keeper", "form.integration.nunux_keeper_api_key": "API key dell'account Nunux Keeper", diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json index 0f5b6297704..68ae14121b8 100644 --- a/locale/translations/ja_JP.json +++ b/locale/translations/ja_JP.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Nunux Keeper に記事を保存する", "form.integration.nunux_keeper_endpoint": "Nunux Keeper の API Endpoint", "form.integration.nunux_keeper_api_key": "Nunux Keeper の API key", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index 2618ebcbbf3..e74a482e1a7 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Opslaan naar Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Nunux Keeper URL", "form.integration.nunux_keeper_api_key": "Nunux Keeper API-sleutel", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index 74e34b4ea7a..d6147821687 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -356,6 +356,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Zapisz artykuly do Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Nunux Keeper URL", "form.integration.nunux_keeper_api_key": "Nunux Keeper API key", diff --git a/locale/translations/pt_BR.json b/locale/translations/pt_BR.json index 6a2d1b94977..485034e8bd6 100644 --- a/locale/translations/pt_BR.json +++ b/locale/translations/pt_BR.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Salvar itens no Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Endpoint de API do Nunux Keeper", "form.integration.nunux_keeper_api_key": "Chave de API do Nunux Keeper", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index 3c41d2a9da6..3c334afe9ea 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -356,6 +356,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Сохранять статьи в Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Конечная точка Nunux Keeper API", "form.integration.nunux_keeper_api_key": "API-ключ Nunux Keeper", diff --git a/locale/translations/tr_TR.json b/locale/translations/tr_TR.json index 1e1c73419ff..88d60326020 100644 --- a/locale/translations/tr_TR.json +++ b/locale/translations/tr_TR.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Makaleleri Nunux Keeper'a kaydet", "form.integration.nunux_keeper_endpoint": "Nunux Keeper API Uç Noktası", "form.integration.nunux_keeper_api_key": "Nunux Keeper API anahtarı", diff --git a/locale/translations/uk_UA.json b/locale/translations/uk_UA.json index 84659566cff..103c05e17ac 100644 --- a/locale/translations/uk_UA.json +++ b/locale/translations/uk_UA.json @@ -353,6 +353,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "Зберігати статті до Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Nunux Keeper API Endpoint", "form.integration.nunux_keeper_api_key": "Ключ API Nunux Keeper", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index ed6700f7242..13e3d15b92a 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -352,6 +352,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "保存文章到 Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Nunux Keeper API 端点", "form.integration.nunux_keeper_api_key": "Nunux Keeper API 密钥", diff --git a/locale/translations/zh_TW.json b/locale/translations/zh_TW.json index b540a992310..ce246288d88 100644 --- a/locale/translations/zh_TW.json +++ b/locale/translations/zh_TW.json @@ -354,6 +354,9 @@ "form.integration.notion_activate": "Save entries to Notion", "form.integration.notion_page_id": "Notion Page ID", "form.integration.notion_token": "Notion Secret Token", + "form.integration.apprise_activate": "Push entries to Apprise", + "form.integration.apprise_url": "Apprise API URL", + "form.integration.apprise_services_url": "Apprise services urls seperated by comma", "form.integration.nunux_keeper_activate": "儲存文章到 Nunux Keeper", "form.integration.nunux_keeper_endpoint": "Nunux Keeper API 端點", "form.integration.nunux_keeper_api_key": "Nunux Keeper API 金鑰", diff --git a/model/integration.go b/model/integration.go index 27375ef4907..83ab8366959 100644 --- a/model/integration.go +++ b/model/integration.go @@ -54,4 +54,7 @@ type Integration struct { MatrixBotPassword string MatrixBotURL string MatrixBotChatID string + AppriseEnabled bool + AppriseURL string + AppriseServicesURL string } diff --git a/storage/integration.go b/storage/integration.go index 2601f951e3c..21e3a2cb353 100644 --- a/storage/integration.go +++ b/storage/integration.go @@ -157,7 +157,10 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { matrix_bot_user, matrix_bot_password, matrix_bot_url, - matrix_bot_chat_id + matrix_bot_chat_id, + apprise_enabled, + apprise_url, + apprise_services_url FROM integrations WHERE @@ -214,6 +217,9 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.MatrixBotPassword, &integration.MatrixBotURL, &integration.MatrixBotChatID, + &integration.AppriseEnabled, + &integration.AppriseURL, + &integration.AppriseServicesURL, ) switch { case err == sql.ErrNoRows: @@ -278,9 +284,12 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { notion_token=$45, notion_page_id=$46, readwise_enabled=$47, - readwise_api_key=$48 + readwise_api_key=$48, + apprise_enabled=$49, + apprise_url=$50, + apprise_services_url=$51 WHERE - user_id=$49 + user_id=$52 ` _, err := s.db.Exec( query, @@ -332,6 +341,9 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.NotionPageID, integration.ReadwiseEnabled, integration.ReadwiseAPIKey, + integration.AppriseEnabled, + integration.AppriseURL, + integration.AppriseServicesURL, integration.UserID, ) @@ -352,7 +364,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 readwise_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' OR apprise_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 afbb6aa7340..0712eb9a85e 100644 --- a/template/templates/views/integrations.html +++ b/template/templates/views/integrations.html @@ -234,7 +234,26 @@

Linkding

+

Apprise

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

Telegram Bot