From b153fa8b3cd2e48bbe13326695f11d2013427ebc Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Mon, 18 Dec 2017 20:52:46 -0800 Subject: Add Wallabag integration --- integration/instapaper/instapaper.go | 2 +- integration/integration.go | 30 +++++++-- integration/pinboard/pinboard.go | 2 +- integration/wallabag/wallabag.go | 116 +++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 integration/wallabag/wallabag.go (limited to 'integration') diff --git a/integration/instapaper/instapaper.go b/integration/instapaper/instapaper.go index 51c5e05..33a2535 100644 --- a/integration/instapaper/instapaper.go +++ b/integration/instapaper/instapaper.go @@ -27,7 +27,7 @@ func (c *Client) AddURL(link, title string) error { client := http.NewClientWithCredentials(apiURL, c.username, c.password) response, err := client.Get() if response.HasServerFailure() { - return fmt.Errorf("unable to send bookmark to instapaper, status=%d", response.StatusCode) + return fmt.Errorf("instapaper: unable to send url, status=%d", response.StatusCode) } return err diff --git a/integration/integration.go b/integration/integration.go index 18975e9..1468a2b 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -7,6 +7,7 @@ package integration import ( "github.com/miniflux/miniflux/integration/instapaper" "github.com/miniflux/miniflux/integration/pinboard" + "github.com/miniflux/miniflux/integration/wallabag" "github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/model" ) @@ -15,17 +16,36 @@ import ( func SendEntry(entry *model.Entry, integration *model.Integration) { if integration.PinboardEnabled { client := pinboard.NewClient(integration.PinboardToken) - err := client.AddBookmark(entry.URL, entry.Title, integration.PinboardTags, integration.PinboardMarkAsUnread) + err := client.AddBookmark( + entry.URL, + entry.Title, + integration.PinboardTags, + integration.PinboardMarkAsUnread, + ) + if err != nil { - logger.Error("[Pinboard] %v", err) + logger.Error("[Integration] %v", err) } } if integration.InstapaperEnabled { client := instapaper.NewClient(integration.InstapaperUsername, integration.InstapaperPassword) - err := client.AddURL(entry.URL, entry.Title) - if err != nil { - logger.Error("[Instapaper] %v", err) + if err := client.AddURL(entry.URL, entry.Title); err != nil { + logger.Error("[Integration] %v", err) + } + } + + if integration.WallabagEnabled { + client := wallabag.NewClient( + integration.WallabagURL, + integration.WallabagClientID, + integration.WallabagClientSecret, + integration.WallabagUsername, + integration.WallabagPassword, + ) + + if err := client.AddEntry(entry.URL, entry.Title); err != nil { + logger.Error("[Integration] %v", err) } } } diff --git a/integration/pinboard/pinboard.go b/integration/pinboard/pinboard.go index 2e1bbd7..bad65b1 100644 --- a/integration/pinboard/pinboard.go +++ b/integration/pinboard/pinboard.go @@ -33,7 +33,7 @@ func (c *Client) AddBookmark(link, title, tags string, markAsUnread bool) error client := http.NewClient("https://api.pinboard.in/v1/posts/add?" + values.Encode()) response, err := client.Get() if response.HasServerFailure() { - return fmt.Errorf("unable to send bookmark to pinboard, status=%d", response.StatusCode) + return fmt.Errorf("pinboard: unable to send bookmark, status=%d", response.StatusCode) } return err diff --git a/integration/wallabag/wallabag.go b/integration/wallabag/wallabag.go new file mode 100644 index 0000000..fbb100a --- /dev/null +++ b/integration/wallabag/wallabag.go @@ -0,0 +1,116 @@ +// Copyright 2017 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package wallabag + +import ( + "encoding/json" + "fmt" + "io" + "net/url" + + "github.com/miniflux/miniflux/http" +) + +// Client represents a Wallabag client. +type Client struct { + baseURL string + clientID string + clientSecret string + username string + password string +} + +// AddEntry sends a link to Wallabag. +func (c *Client) AddEntry(link, title string) error { + accessToken, err := c.getAccessToken() + if err != nil { + return err + } + + return c.createEntry(accessToken, link, title) +} + +func (c *Client) createEntry(accessToken, link, title string) error { + endpoint, err := getAPIEndpoint(c.baseURL, "/api/entries.json") + if err != nil { + return fmt.Errorf("wallbag: unable to get entries endpoint: %v", err) + } + + client := http.NewClientWithAuthorization(endpoint, "Bearer "+accessToken) + response, err := client.PostJSON(map[string]string{"url": link, "title": title}) + if err != nil { + return fmt.Errorf("wallabag: unable to post entry: %v", err) + } + + if response.HasServerFailure() { + return fmt.Errorf("wallabag: request failed, status=%d", response.StatusCode) + } + + return nil +} + +func (c *Client) getAccessToken() (string, error) { + values := url.Values{} + values.Add("grant_type", "password") + values.Add("client_id", c.clientID) + values.Add("client_secret", c.clientSecret) + values.Add("username", c.username) + values.Add("password", c.password) + + endpoint, err := getAPIEndpoint(c.baseURL, "/oauth/v2/token") + if err != nil { + return "", fmt.Errorf("wallbag: unable to get token endpoint: %v", err) + } + + client := http.NewClient(endpoint) + response, err := client.PostForm(values) + if err != nil { + return "", fmt.Errorf("wallabag: unable to get access token: %v", err) + } + + if response.HasServerFailure() { + return "", fmt.Errorf("wallabag: request failed, status=%d", response.StatusCode) + } + + token, err := decodeTokenResponse(response.Body) + if err != nil { + return "", err + } + + return token.AccessToken, nil +} + +// NewClient returns a new Wallabag client. +func NewClient(baseURL, clientID, clientSecret, username, password string) *Client { + return &Client{baseURL, clientID, clientSecret, username, password} +} + +func getAPIEndpoint(baseURL, path string) (string, error) { + u, err := url.Parse(baseURL) + if err != nil { + return "", fmt.Errorf("wallabag: invalid API endpoint: %v", err) + } + u.Path = path + return u.String(), nil +} + +type tokenResponse struct { + AccessToken string `json:"access_token"` + Expires int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + TokenType string `json:"token_type"` +} + +func decodeTokenResponse(body io.Reader) (*tokenResponse, error) { + var token tokenResponse + + decoder := json.NewDecoder(body) + if err := decoder.Decode(&token); err != nil { + return nil, fmt.Errorf("wallabag: unable to decode token response: %v", err) + } + + return &token, nil +} -- cgit v1.2.3