diff --git a/internal/api/subscription.go b/internal/api/subscription.go index 315db6f2..eb744419 100644 --- a/internal/api/subscription.go +++ b/internal/api/subscription.go @@ -7,6 +7,7 @@ import ( json_parser "encoding/json" "net/http" + "miniflux.app/v2/internal/http/request" "miniflux.app/v2/internal/http/response/json" "miniflux.app/v2/internal/model" "miniflux.app/v2/internal/reader/subscription" @@ -25,6 +26,12 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request) return } + var rssbridgeURL string + intg, err := h.store.Integration(request.UserID(r)) + if err == nil && intg != nil && intg.RSSBridgeEnabled { + rssbridgeURL = intg.RSSBridgeURL + } + subscriptions, finderErr := subscription.FindSubscriptions( subscriptionDiscoveryRequest.URL, subscriptionDiscoveryRequest.UserAgent, @@ -33,6 +40,7 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request) subscriptionDiscoveryRequest.Password, subscriptionDiscoveryRequest.FetchViaProxy, subscriptionDiscoveryRequest.AllowSelfSignedCertificates, + rssbridgeURL, ) if finderErr != nil { json.ServerError(w, r, finderErr) diff --git a/internal/database/migrations.go b/internal/database/migrations.go index 1b4f44dd..ba356217 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -799,4 +799,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 rssbridge_enabled bool default 'f'; + ALTER TABLE integrations ADD COLUMN rssbridge_url text default ''; + ` + _, err = tx.Exec(sql) + return + }, } diff --git a/internal/googlereader/handler.go b/internal/googlereader/handler.go index 2312a20e..681bed4c 100644 --- a/internal/googlereader/handler.go +++ b/internal/googlereader/handler.go @@ -673,7 +673,7 @@ func (h *handler) quickAddHandler(w http.ResponseWriter, r *http.Request) { return } - subscriptions, s_err := mfs.FindSubscriptions(url, "", "", "", "", false, false) + subscriptions, s_err := mfs.FindSubscriptions(url, "", "", "", "", false, false, "") if s_err != nil { json.ServerError(w, r, s_err) return diff --git a/internal/integration/rssbridge/rssbridge.go b/internal/integration/rssbridge/rssbridge.go new file mode 100644 index 00000000..117b9c84 --- /dev/null +++ b/internal/integration/rssbridge/rssbridge.go @@ -0,0 +1,45 @@ +package rssbridge // import "miniflux.app/integration/rssbridge" + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +type Bridge struct { + URL string `json:"url"` + BridgeMeta BridgeMeta `json:"bridgeMeta"` +} + +type BridgeMeta struct { + Name string `json:"name"` +} + +func DetectBridges(rssbridgeURL, websiteURL string) (bridgeResponse []Bridge, err error) { + u, err := url.Parse(rssbridgeURL) + if err != nil { + return nil, err + } + values := u.Query() + values.Add("action", "findfeed") + values.Add("format", "atom") + values.Add("url", websiteURL) + u.RawQuery = values.Encode() + + response, err := http.Get(u.String()) + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode == http.StatusNotFound { + return + } + if response.StatusCode > 400 { + return nil, fmt.Errorf("RSS-Bridge: server failure (%d)", response.StatusCode) + } + if err := json.NewDecoder(response.Body).Decode(&bridgeResponse); err != nil { + return nil, fmt.Errorf("RSS-Bridge: unable to decode bridge response: %w", err) + } + return +} diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index 15851aa4..abcddc1b 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 cbacf37b..6a4d87e4 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 89f16821..e553b5b1 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 3e9198b8..5db848b4 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 2a950314..a83a43c9 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 0b3b27d2..a27de069 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Activer le webhook", "form.integration.webhook_url": "URL du webhook", "form.integration.webhook_secret": "Secret du webhook", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 50cf7753..278d78c6 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 8d3c7f9a..62ea5c76 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -401,6 +401,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 13ad90f2..dd4a33e8 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 3bae562d..3eadf836 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 ddc280d6..2b887f95 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 e27614d6..08ff54c0 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -406,6 +406,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 ce019f4e..bb9d40a0 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 fd542255..2ad084c3 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -406,6 +406,8 @@ "form.integration.webhook_activate": "Включить вебхуки", "form.integration.webhook_url": "Адрес вебхуков", "form.integration.webhook_secret": "Секретный ключ для вебхуков", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 4515c569..d896b8f7 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 c7a7b2b0..22d69d77 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -407,6 +407,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 63697de0..da90de6b 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -402,6 +402,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 d7da6d62..52b75745 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -404,6 +404,8 @@ "form.integration.webhook_activate": "Enable Webhook", "form.integration.webhook_url": "Webhook URL", "form.integration.webhook_secret": "Webhook Secret", + "form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions", + "form.integration.rssbridge_url": "RSS-Bridge server URL", "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 49b6be07..e31ae8fe 100644 --- a/internal/model/integration.go +++ b/internal/model/integration.go @@ -71,4 +71,6 @@ type Integration struct { WebhookEnabled bool WebhookURL string WebhookSecret string + RSSBridgeEnabled bool + RSSBridgeURL string } diff --git a/internal/reader/subscription/finder.go b/internal/reader/subscription/finder.go index 1683f393..f20d272a 100644 --- a/internal/reader/subscription/finder.go +++ b/internal/reader/subscription/finder.go @@ -12,6 +12,7 @@ import ( "miniflux.app/v2/internal/config" "miniflux.app/v2/internal/errors" "miniflux.app/v2/internal/http/client" + "miniflux.app/v2/internal/integration/rssbridge" "miniflux.app/v2/internal/reader/browser" "miniflux.app/v2/internal/reader/parser" "miniflux.app/v2/internal/urllib" @@ -26,7 +27,25 @@ var ( ) // FindSubscriptions downloads and try to find one or more subscriptions from an URL. -func FindSubscriptions(websiteURL, userAgent, cookie, username, password string, fetchViaProxy, allowSelfSignedCertificates bool) (Subscriptions, *errors.LocalizedError) { +func FindSubscriptions(websiteURL, userAgent, cookie, username, password string, fetchViaProxy, allowSelfSignedCertificates bool, rssbridgeURL string) (Subscriptions, *errors.LocalizedError) { + if rssbridgeURL != "" { + bridges, err := rssbridge.DetectBridges(rssbridgeURL, websiteURL) + if err != nil { + return nil, errors.NewLocalizedError("RSS-Bridge: %v", err) + } + if len(bridges) > 0 { + var subscriptions Subscriptions + for _, bridge := range bridges { + subscriptions = append(subscriptions, &Subscription{ + Title: bridge.BridgeMeta.Name, + URL: bridge.URL, + Type: "atom", + }) + } + return subscriptions, nil + } + } + websiteURL = findYoutubeChannelFeed(websiteURL) websiteURL = parseYoutubeVideoPage(websiteURL) diff --git a/internal/storage/integration.go b/internal/storage/integration.go index 56bcea4b..54e4f7a4 100644 --- a/internal/storage/integration.go +++ b/internal/storage/integration.go @@ -174,7 +174,9 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { shaarli_api_secret, webhook_enabled, webhook_url, - webhook_secret + webhook_secret, + rssbridge_enabled, + rssbridge_url FROM integrations WHERE @@ -248,6 +250,8 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.WebhookEnabled, &integration.WebhookURL, &integration.WebhookSecret, + &integration.RSSBridgeEnabled, + &integration.RSSBridgeURL, ) switch { case err == sql.ErrNoRows: @@ -329,9 +333,11 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { shaarli_api_secret=$62, webhook_enabled=$63, webhook_url=$64, - webhook_secret=$65 + webhook_secret=$65, + rssbridge_enabled=$66, + rssbridge_url=$67 WHERE - user_id=$66 + user_id=$68 ` _, err := s.db.Exec( query, @@ -400,6 +406,8 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.WebhookEnabled, integration.WebhookURL, integration.WebhookSecret, + integration.RSSBridgeEnabled, + integration.RSSBridgeURL, integration.UserID, ) diff --git a/internal/template/templates/views/integrations.html b/internal/template/templates/views/integrations.html index 761cec17..faffb3b7 100644 --- a/internal/template/templates/views/integrations.html +++ b/internal/template/templates/views/integrations.html @@ -401,6 +401,22 @@ + +
+ RSS-Bridge +
+ + + + + +
+ +
+
+

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

diff --git a/internal/ui/form/integration.go b/internal/ui/form/integration.go index 0d6b4e19..2976ed6d 100644 --- a/internal/ui/form/integration.go +++ b/internal/ui/form/integration.go @@ -77,6 +77,8 @@ type IntegrationForm struct { WebhookEnabled bool WebhookURL string WebhookSecret string + RSSBridgeEnabled bool + RSSBridgeURL string } // Merge copy form values to the model. @@ -143,6 +145,8 @@ func (i IntegrationForm) Merge(integration *model.Integration) { integration.ShaarliAPISecret = i.ShaarliAPISecret integration.WebhookEnabled = i.WebhookEnabled integration.WebhookURL = i.WebhookURL + integration.RSSBridgeEnabled = i.RSSBridgeEnabled + integration.RSSBridgeURL = i.RSSBridgeURL } // NewIntegrationForm returns a new IntegrationForm. @@ -212,6 +216,8 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm { ShaarliAPISecret: r.FormValue("shaarli_api_secret"), WebhookEnabled: r.FormValue("webhook_enabled") == "1", WebhookURL: r.FormValue("webhook_url"), + RSSBridgeEnabled: r.FormValue("rssbridge_enabled") == "1", + RSSBridgeURL: r.FormValue("rssbridge_url"), } } diff --git a/internal/ui/integration_show.go b/internal/ui/integration_show.go index e3dbc08d..d27ce412 100644 --- a/internal/ui/integration_show.go +++ b/internal/ui/integration_show.go @@ -91,6 +91,8 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) { WebhookEnabled: integration.WebhookEnabled, WebhookURL: integration.WebhookURL, WebhookSecret: integration.WebhookSecret, + RSSBridgeEnabled: integration.RSSBridgeEnabled, + RSSBridgeURL: integration.RSSBridgeURL, } sess := session.New(h.store, request.SessionID(r)) diff --git a/internal/ui/subscription_submit.go b/internal/ui/subscription_submit.go index 75774c2e..41e75a4a 100644 --- a/internal/ui/subscription_submit.go +++ b/internal/ui/subscription_submit.go @@ -50,6 +50,11 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) { return } + var rssbridgeURL string + if intg, err := h.store.Integration(user.ID); err == nil && intg != nil && intg.RSSBridgeEnabled { + rssbridgeURL = intg.RSSBridgeURL + } + subscriptions, findErr := subscription.FindSubscriptions( subscriptionForm.URL, subscriptionForm.UserAgent, @@ -58,6 +63,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) { subscriptionForm.Password, subscriptionForm.FetchViaProxy, subscriptionForm.AllowSelfSignedCertificates, + rssbridgeURL, ) if findErr != nil { v.Set("form", subscriptionForm)