wallabag.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package wallabag // import "miniflux.app/v2/integration/wallabag"
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/url"
  9. "miniflux.app/v2/http/client"
  10. )
  11. // Client represents a Wallabag client.
  12. type Client struct {
  13. baseURL string
  14. clientID string
  15. clientSecret string
  16. username string
  17. password string
  18. onlyURL bool
  19. }
  20. // NewClient returns a new Wallabag client.
  21. func NewClient(baseURL, clientID, clientSecret, username, password string, onlyURL bool) *Client {
  22. return &Client{baseURL, clientID, clientSecret, username, password, onlyURL}
  23. }
  24. // AddEntry sends a link to Wallabag.
  25. // Pass an empty string in `content` to let Wallabag fetch the article content.
  26. func (c *Client) AddEntry(link, title, content string) error {
  27. if c.baseURL == "" || c.clientID == "" || c.clientSecret == "" || c.username == "" || c.password == "" {
  28. return fmt.Errorf("wallabag: missing credentials")
  29. }
  30. accessToken, err := c.getAccessToken()
  31. if err != nil {
  32. return err
  33. }
  34. return c.createEntry(accessToken, link, title, content)
  35. }
  36. func (c *Client) createEntry(accessToken, link, title, content string) error {
  37. endpoint, err := url.JoinPath(c.baseURL, "/api/entries.json")
  38. if err != nil {
  39. return fmt.Errorf("wallbag: unable to generate entries endpoint using %q: %v", c.baseURL, err)
  40. }
  41. data := map[string]string{"url": link, "title": title}
  42. if !c.onlyURL {
  43. data["content"] = content
  44. }
  45. clt := client.New(endpoint)
  46. clt.WithAuthorization("Bearer " + accessToken)
  47. response, err := clt.PostJSON(data)
  48. if err != nil {
  49. return fmt.Errorf("wallabag: unable to post entry using %q endpoint: %v", endpoint, err)
  50. }
  51. if response.HasServerFailure() {
  52. return fmt.Errorf("wallabag: request failed using %q, status=%d", endpoint, response.StatusCode)
  53. }
  54. return nil
  55. }
  56. func (c *Client) getAccessToken() (string, error) {
  57. values := url.Values{}
  58. values.Add("grant_type", "password")
  59. values.Add("client_id", c.clientID)
  60. values.Add("client_secret", c.clientSecret)
  61. values.Add("username", c.username)
  62. values.Add("password", c.password)
  63. endpoint, err := url.JoinPath(c.baseURL, "/oauth/v2/token")
  64. if err != nil {
  65. return "", fmt.Errorf("wallbag: unable to generate token endpoint using %q: %v", c.baseURL, err)
  66. }
  67. clt := client.New(endpoint)
  68. response, err := clt.PostForm(values)
  69. if err != nil {
  70. return "", fmt.Errorf("wallabag: unable to get access token using %q endpoint: %v", endpoint, err)
  71. }
  72. if response.HasServerFailure() {
  73. return "", fmt.Errorf("wallabag: request failed using %q, status=%d", endpoint, response.StatusCode)
  74. }
  75. token, err := decodeTokenResponse(response.Body)
  76. if err != nil {
  77. return "", err
  78. }
  79. return token.AccessToken, nil
  80. }
  81. type tokenResponse struct {
  82. AccessToken string `json:"access_token"`
  83. Expires int `json:"expires_in"`
  84. RefreshToken string `json:"refresh_token"`
  85. Scope string `json:"scope"`
  86. TokenType string `json:"token_type"`
  87. }
  88. func decodeTokenResponse(body io.Reader) (*tokenResponse, error) {
  89. var token tokenResponse
  90. decoder := json.NewDecoder(body)
  91. if err := decoder.Decode(&token); err != nil {
  92. return nil, fmt.Errorf("wallabag: unable to decode token response: %v", err)
  93. }
  94. return &token, nil
  95. }