diff options
author | 2023-08-13 12:48:29 -0700 | |
---|---|---|
committer | 2023-08-13 13:32:05 -0700 | |
commit | 28df0b119e8d3562ca00c6066911aa8538378175 (patch) | |
tree | 32e944c42fa3caba1cc3e4e3ffe4c948f2360eeb /internal/integration | |
parent | 13d9d86acd988da5929da829ccd251916a9517b7 (diff) | |
download | v2-28df0b119e8d3562ca00c6066911aa8538378175.tar.gz v2-28df0b119e8d3562ca00c6066911aa8538378175.tar.zst v2-28df0b119e8d3562ca00c6066911aa8538378175.zip |
Add Shiori integration
Diffstat (limited to 'internal/integration')
-rw-r--r-- | internal/integration/integration.go | 15 | ||||
-rw-r--r-- | internal/integration/shiori/shiori.go | 132 |
2 files changed, 147 insertions, 0 deletions
diff --git a/internal/integration/integration.go b/internal/integration/integration.go index faa263d2..1b47638e 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -15,6 +15,7 @@ import ( "miniflux.app/v2/internal/integration/pinboard" "miniflux.app/v2/internal/integration/pocket" "miniflux.app/v2/internal/integration/readwise" + "miniflux.app/v2/internal/integration/shiori" "miniflux.app/v2/internal/integration/telegrambot" "miniflux.app/v2/internal/integration/wallabag" "miniflux.app/v2/internal/logger" @@ -137,6 +138,20 @@ func SendEntry(entry *model.Entry, integration *model.Integration) { logger.Error("[Integration] UserID #%d: %v", integration.UserID, err) } } + + if integration.ShioriEnabled { + logger.Debug("[Integration] Sending Entry #%d %q for User #%d to Shiori", entry.ID, entry.URL, integration.UserID) + + client := shiori.NewClient( + integration.ShioriURL, + integration.ShioriUsername, + integration.ShioriPassword, + ) + + if err := client.AddBookmark(entry.URL, entry.Title); err != nil { + logger.Error("[Integration] Unable to send entry #%d to Shiori for user #%d: %v", entry.ID, integration.UserID, err) + } + } } // PushEntries pushes an entry array to third-party providers during feed refreshes. diff --git a/internal/integration/shiori/shiori.go b/internal/integration/shiori/shiori.go new file mode 100644 index 00000000..c55a530d --- /dev/null +++ b/internal/integration/shiori/shiori.go @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package shiori // import "miniflux.app/v2/internal/integration/shiori" + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" + + "miniflux.app/v2/internal/url" + "miniflux.app/v2/internal/version" +) + +const defaultClientTimeout = 10 * time.Second + +type Client struct { + baseURL string + username string + password string +} + +func NewClient(baseURL, username, password string) *Client { + return &Client{baseURL: baseURL, username: username, password: password} +} + +func (c *Client) AddBookmark(entryURL, entryTitle string) error { + if c.baseURL == "" || c.username == "" || c.password == "" { + return fmt.Errorf("shiori: missing base URL, username or password") + } + + sessionID, err := c.authenticate() + if err != nil { + return fmt.Errorf("shiori: unable to authenticate: %v", err) + } + + apiEndpoint, err := url.JoinBaseURLAndPath(c.baseURL, "/api/bookmarks") + if err != nil { + return fmt.Errorf("shiori: invalid API endpoint: %v", err) + } + + requestBody, err := json.Marshal(&addBookmarkRequest{ + URL: entryURL, + Title: entryTitle, + CreateArchive: true, + }) + + if err != nil { + return fmt.Errorf("shiori: unable to encode request body: %v", err) + } + + request, err := http.NewRequest("POST", apiEndpoint, bytes.NewReader(requestBody)) + if err != nil { + return fmt.Errorf("shiori: unable to create request: %v", err) + } + + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + request.Header.Set("User-Agent", "Miniflux/"+version.Version) + request.Header.Set("X-Session-Id", sessionID) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + + response, err := httpClient.Do(request) + if err != nil { + return fmt.Errorf("shiori: unable to send request: %v", err) + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("shiori: unable to create bookmark: url=%s status=%d", apiEndpoint, response.StatusCode) + } + + return nil +} + +func (c *Client) authenticate() (sessionID string, err error) { + apiEndpoint, err := url.JoinBaseURLAndPath(c.baseURL, "/api/login") + if err != nil { + return "", fmt.Errorf("shiori: invalid API endpoint: %v", err) + } + + requestBody, err := json.Marshal(&authRequest{Username: c.username, Password: c.password}) + if err != nil { + return "", fmt.Errorf("shiori: unable to encode request body: %v", err) + } + + request, err := http.NewRequest("POST", apiEndpoint, bytes.NewReader(requestBody)) + if err != nil { + return "", fmt.Errorf("shiori: unable to create request: %v", err) + } + + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + request.Header.Set("User-Agent", "Miniflux/"+version.Version) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + + response, err := httpClient.Do(request) + if err != nil { + return "", fmt.Errorf("shiori: unable to send request: %v", err) + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return "", fmt.Errorf("shiori: unable to authenticate: url=%s status=%d", apiEndpoint, response.StatusCode) + } + + var authResponse authResponse + if err := json.NewDecoder(response.Body).Decode(&authResponse); err != nil { + return "", fmt.Errorf("shiori: unable to decode response: %v", err) + } + + return authResponse.SessionID, nil +} + +type authRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type authResponse struct { + SessionID string `json:"session"` +} + +type addBookmarkRequest struct { + URL string `json:"url"` + Title string `json:"title"` + CreateArchive bool `json:"createArchive"` +} |