Browse Source

feat(integration): add cubox integration

Signed-off-by: Shaolong Chen <shaolong.chen@outlook.it>
Shaolong Chen 1 year ago
parent
commit
366928b35d

+ 8 - 0
internal/database/migrations.go

@@ -952,4 +952,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 cubox_enabled bool default 'f';
+			ALTER TABLE integrations ADD COLUMN cubox_api_link text default '';
+		`
+		_, err = tx.Exec(sql)
+		return err
+	},
 }

+ 70 - 0
internal/integration/cubox/cubox.go

@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// Cubox API documentation: https://help.cubox.cc/save/api/
+
+package cubox // import "miniflux.app/v2/internal/integration/cubox"
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"time"
+
+	"miniflux.app/v2/internal/version"
+)
+
+const defaultClientTimeout = 10 * time.Second
+
+type Client struct {
+	apiLink string
+}
+
+func NewClient(apiLink string) *Client {
+	return &Client{apiLink: apiLink}
+}
+
+func (c *Client) SaveLink(entryURL string) error {
+	if c.apiLink == "" {
+		return errors.New("cubox: missing API link")
+	}
+
+	requestBody, err := json.Marshal(&card{
+		Type:    "url",
+		Content: entryURL,
+	})
+	if err != nil {
+		return fmt.Errorf("cubox: unable to encode request body: %w", err)
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), defaultClientTimeout)
+	defer cancel()
+
+	request, err := http.NewRequestWithContext(ctx, http.MethodPost, c.apiLink, bytes.NewReader(requestBody))
+	if err != nil {
+		return fmt.Errorf("cubox: unable to create request: %w", err)
+	}
+
+	request.Header.Set("Content-Type", "application/json")
+	request.Header.Set("User-Agent", "Miniflux/"+version.Version)
+
+	response, err := http.DefaultClient.Do(request)
+	if err != nil {
+		return fmt.Errorf("cubox: unable to send request: %w", err)
+	}
+	defer response.Body.Close()
+
+	if response.StatusCode != 200 {
+		return fmt.Errorf("cubox: unable to save link: status=%d", response.StatusCode)
+	}
+
+	return nil
+}
+
+type card struct {
+	Type    string `json:"type"`
+	Content string `json:"content"`
+}

+ 20 - 0
internal/integration/integration.go

@@ -9,6 +9,7 @@ import (
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/integration/apprise"
 	"miniflux.app/v2/internal/integration/betula"
+	"miniflux.app/v2/internal/integration/cubox"
 	"miniflux.app/v2/internal/integration/espial"
 	"miniflux.app/v2/internal/integration/instapaper"
 	"miniflux.app/v2/internal/integration/linkace"
@@ -322,6 +323,25 @@ func SendEntry(entry *model.Entry, userIntegrations *model.Integration) {
 		}
 	}
 
+	if userIntegrations.CuboxEnabled {
+		slog.Debug("Sending entry to Cubox",
+			slog.Int64("user_id", userIntegrations.UserID),
+			slog.Int64("entry_id", entry.ID),
+			slog.String("entry_url", entry.URL),
+		)
+
+		client := cubox.NewClient(userIntegrations.CuboxAPILink)
+
+		if err := client.SaveLink(entry.URL); err != nil {
+			slog.Error("Unable to send entry to Cubox",
+				slog.Int64("user_id", userIntegrations.UserID),
+				slog.Int64("entry_id", entry.ID),
+				slog.String("entry_url", entry.URL),
+				slog.Any("error", err),
+			)
+		}
+	}
+
 	if userIntegrations.ShioriEnabled {
 		slog.Debug("Sending entry to Shiori",
 			slog.Int64("user_id", userIntegrations.UserID),

+ 2 - 0
internal/locale/translations/de_DE.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "Allgemein",
     "form.feed.fieldset.rules": "Regeln",
     "form.feed.fieldset.network_settings": "Netzwerkeinstellungen",

+ 2 - 0
internal/locale/translations/el_EL.json

@@ -359,6 +359,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.category.label.title": "Τίτλος",
     "form.category.hide_globally": "Απόκρυψη καταχωρήσεων σε γενική λίστα μη αναγνωσμένων",
     "form.user.label.username": "Χρήστης",

+ 2 - 0
internal/locale/translations/en_US.json

@@ -510,6 +510,8 @@
     "form.integration.ntfy_username": "Ntfy Username (optional)",
     "form.integration.ntfy_password": "Ntfy Password (optional)",
     "form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.api_key.label.description": "API Key Label",
     "form.submit.loading": "Loading…",
     "form.submit.saving": "Saving…",

+ 2 - 0
internal/locale/translations/es_ES.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Prioridad predeterminada a Ntfy",
     "form.feed.label.ntfy_low_priority": "Prioridad baja a Ntfy",
     "form.feed.label.ntfy_min_priority": "Prioridad mínima a Ntfy",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Reglas",
     "form.feed.fieldset.network_settings": "Ajustes de red",

+ 2 - 0
internal/locale/translations/fi_FI.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Rules",
     "form.feed.fieldset.network_settings": "Network Settings",

+ 2 - 0
internal/locale/translations/fr_FR.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Priorité par défaut de notification",
     "form.feed.label.ntfy_low_priority": "Priorité basse de notification",
     "form.feed.label.ntfy_min_priority": "Priorité minimale de notification",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "Général",
     "form.feed.fieldset.rules": "Règles",
     "form.feed.fieldset.network_settings": "Paramètres réseau",

+ 2 - 0
internal/locale/translations/hi_IN.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Rules",
     "form.feed.fieldset.network_settings": "Network Settings",

+ 2 - 0
internal/locale/translations/id_ID.json

@@ -345,6 +345,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Rules",
     "form.feed.fieldset.network_settings": "Network Settings",

+ 2 - 0
internal/locale/translations/it_IT.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Rules",
     "form.feed.fieldset.network_settings": "Network Settings",

+ 2 - 0
internal/locale/translations/ja_JP.json

@@ -345,6 +345,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Rules",
     "form.feed.fieldset.network_settings": "Network Settings",

+ 2 - 0
internal/locale/translations/nl_NL.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy standaard prioriteit",
     "form.feed.label.ntfy_low_priority": "Ntfy lage prioriteit",
     "form.feed.label.ntfy_min_priority": "Ntfy minimale prioriteit",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "Algemeen",
     "form.feed.fieldset.rules": "Regels",
     "form.feed.fieldset.network_settings": "Netwerk Instellingen",

+ 2 - 0
internal/locale/translations/pl_PL.json

@@ -365,6 +365,8 @@
     "form.feed.label.ntfy_default_priority": "Domyślny priorytet ntfy",
     "form.feed.label.ntfy_low_priority": "Niski priorytet ntfy",
     "form.feed.label.ntfy_min_priority": "Minimalny priorytet ntfy",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "Ogólne",
     "form.feed.fieldset.rules": "Reguły",
     "form.feed.fieldset.network_settings": "Ustawienia sieci",

+ 2 - 0
internal/locale/translations/pt_BR.json

@@ -355,6 +355,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Rules",
     "form.feed.fieldset.network_settings": "Network Settings",

+ 2 - 0
internal/locale/translations/ru_RU.json

@@ -365,6 +365,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "General",
     "form.feed.fieldset.rules": "Rules",
     "form.feed.fieldset.network_settings": "Network Settings",

+ 2 - 0
internal/locale/translations/tr_TR.json

@@ -286,6 +286,8 @@
   "form.feed.label.ntfy_default_priority": "Ntfy default priority",
   "form.feed.label.ntfy_low_priority": "Ntfy low priority",
   "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+  "form.integration.cubox_activate": "Save entries to Cubox",
+  "form.integration.cubox_api_link": "Cubox API link",
   "form.prefs.fieldset.application_settings": "Uygulama Ayarları",
   "form.prefs.fieldset.authentication_settings": "Kimlik Doğrulama Ayarları",
   "form.prefs.fieldset.reader_settings": "Okuyucu Ayarları",

+ 2 - 0
internal/locale/translations/uk_UA.json

@@ -365,6 +365,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.category.label.title": "Назва",
     "form.category.hide_globally": "Приховати записи в глобальному списку непрочитаного",
     "form.feed.fieldset.general": "General",

+ 2 - 0
internal/locale/translations/zh_CN.json

@@ -345,6 +345,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy默认优先级",
     "form.feed.label.ntfy_low_priority": "Ntfy低优先级",
     "form.feed.label.ntfy_min_priority": "Ntfy最低优先级",
+    "form.integration.cubox_activate": "保存文章到 Cubox",
+    "form.integration.cubox_api_link": "Cubox API 链接",
     "form.feed.fieldset.general": "通用",
     "form.feed.fieldset.rules": "规则",
     "form.feed.fieldset.network_settings": "网络设置",

+ 2 - 0
internal/locale/translations/zh_TW.json

@@ -345,6 +345,8 @@
     "form.feed.label.ntfy_default_priority": "Ntfy default priority",
     "form.feed.label.ntfy_low_priority": "Ntfy low priority",
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
+    "form.integration.cubox_activate": "Save entries to Cubox",
+    "form.integration.cubox_api_link": "Cubox API link",
     "form.feed.fieldset.general": "通用",
     "form.feed.fieldset.rules": "規則",
     "form.feed.fieldset.network_settings": "網路設定",

+ 2 - 0
internal/model/integration.go

@@ -104,4 +104,6 @@ type Integration struct {
 	NtfyUsername                     string
 	NtfyPassword                     string
 	NtfyIconURL                      string
+	CuboxEnabled                     bool
+	CuboxAPILink                     string
 }

+ 13 - 4
internal/storage/integration.go

@@ -207,7 +207,9 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) {
 			ntfy_api_token,
 			ntfy_username,
 			ntfy_password,
-			ntfy_icon_url
+			ntfy_icon_url,
+			cubox_enabled,
+			cubox_api_link
 		FROM
 			integrations
 		WHERE
@@ -314,6 +316,8 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) {
 		&integration.NtfyUsername,
 		&integration.NtfyPassword,
 		&integration.NtfyIconURL,
+		&integration.CuboxEnabled,
+		&integration.CuboxAPILink,
 	)
 	switch {
 	case err == sql.ErrNoRows:
@@ -428,9 +432,11 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error {
 			ntfy_api_token=$95,
 			ntfy_username=$96,
 			ntfy_password=$97,
-			ntfy_icon_url=$98
+			ntfy_icon_url=$98,
+			cubox_enabled=$99,
+			cubox_api_link=$100
 		WHERE
-			user_id=$99
+			user_id=$101
 	`
 	_, err := s.db.Exec(
 		query,
@@ -532,6 +538,8 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error {
 		integration.NtfyUsername,
 		integration.NtfyPassword,
 		integration.NtfyIconURL,
+		integration.CuboxEnabled,
+		integration.CuboxAPILink,
 		integration.UserID,
 	)
 
@@ -571,7 +579,8 @@ func (s *Storage) HasSaveEntry(userID int64) (result bool) {
 				webhook_enabled='t' OR
 				omnivore_enabled='t' OR
 				raindrop_enabled='t' OR
-				betula_enabled='t'
+				betula_enabled='t' OR
+				cubox_enabled='t'
 			)
 	`
 	if err := s.db.QueryRow(query, userID).Scan(&result); err != nil {

+ 16 - 0
internal/template/templates/views/integrations.html

@@ -57,6 +57,22 @@
         </div>
     </details>
 
+    <details {{ if .form.CuboxEnabled }}open{{ end }}>
+        <summary>Cubox</summary>
+        <div class="form-section">
+            <label>
+                <input type="checkbox" name="cubox_enabled" value="1" {{ if .form.CuboxEnabled }}checked{{ end }}> {{ t "form.integration.cubox_activate" }}
+            </label>
+
+            <label for="form-cubox-api-link">{{ t "form.integration.cubox_api_link" }}</label>
+            <input type="url" name="cubox_api_link" id="form-cubox-api-link" value="{{ .form.CuboxAPILink }}" placeholder="https://cubox.pro/c/api/save/xxx" spellcheck="false">
+
+            <div class="buttons">
+                <button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
+            </div>
+        </div>
+    </details>
+
     <details {{ if .form.EspialEnabled }}open{{ end }}>
         <summary>Espial</summary>
         <div class="form-section">

+ 6 - 0
internal/ui/form/integration.go

@@ -110,6 +110,8 @@ type IntegrationForm struct {
 	NtfyUsername                     string
 	NtfyPassword                     string
 	NtfyIconURL                      string
+	CuboxEnabled                     bool
+	CuboxAPILink                     string
 }
 
 // Merge copy form values to the model.
@@ -209,6 +211,8 @@ func (i IntegrationForm) Merge(integration *model.Integration) {
 	integration.NtfyUsername = i.NtfyUsername
 	integration.NtfyPassword = i.NtfyPassword
 	integration.NtfyIconURL = i.NtfyIconURL
+	integration.CuboxEnabled = i.CuboxEnabled
+	integration.CuboxAPILink = i.CuboxAPILink
 }
 
 // NewIntegrationForm returns a new IntegrationForm.
@@ -311,6 +315,8 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm {
 		NtfyUsername:                     r.FormValue("ntfy_username"),
 		NtfyPassword:                     r.FormValue("ntfy_password"),
 		NtfyIconURL:                      r.FormValue("ntfy_icon_url"),
+		CuboxEnabled:                     r.FormValue("cubox_enabled") == "1",
+		CuboxAPILink:                     r.FormValue("cubox_api_link"),
 	}
 }
 

+ 2 - 0
internal/ui/integration_show.go

@@ -124,6 +124,8 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) {
 		NtfyUsername:                     integration.NtfyUsername,
 		NtfyPassword:                     integration.NtfyPassword,
 		NtfyIconURL:                      integration.NtfyIconURL,
+		CuboxEnabled:                     integration.CuboxEnabled,
+		CuboxAPILink:                     integration.CuboxAPILink,
 	}
 
 	sess := session.New(h.store, request.SessionID(r))