Go client for the Write.as API https://developers.write.as
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
5.0 KiB

  1. // Package writeas provides the binding for the Write.as API
  2. package writeas
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "time"
  10. "code.as/core/socks"
  11. "github.com/writeas/impart"
  12. )
  13. const (
  14. apiURL = "https://write.as/api"
  15. devAPIURL = "https://development.write.as/api"
  16. torAPIURL = "http://writeas7pm7rcdqg.onion/api"
  17. // Current go-writeas version
  18. Version = "2-dev"
  19. )
  20. // Client is used to interact with the Write.as API. It can be used to make
  21. // authenticated or unauthenticated calls.
  22. type Client struct {
  23. baseURL string
  24. // Access token for the user making requests.
  25. token string
  26. // Client making requests to the API
  27. client *http.Client
  28. // UserAgent overrides the default User-Agent header
  29. UserAgent string
  30. }
  31. // defaultHTTPTimeout is the default http.Client timeout.
  32. const defaultHTTPTimeout = 10 * time.Second
  33. // NewClient creates a new API client. By default, all requests are made
  34. // unauthenticated. To optionally make authenticated requests, call `SetToken`.
  35. //
  36. // c := writeas.NewClient()
  37. // c.SetToken("00000000-0000-0000-0000-000000000000")
  38. func NewClient() *Client {
  39. return NewClientWith(Config{URL: apiURL})
  40. }
  41. // NewTorClient creates a new API client for communicating with the Write.as
  42. // Tor hidden service, using the given port to connect to the local SOCKS
  43. // proxy.
  44. func NewTorClient(port int) *Client {
  45. return NewClientWith(Config{URL: torAPIURL, TorPort: port})
  46. }
  47. // NewDevClient creates a new API client for development and testing. It'll
  48. // communicate with our development servers, and SHOULD NOT be used in
  49. // production.
  50. func NewDevClient() *Client {
  51. return NewClientWith(Config{URL: devAPIURL})
  52. }
  53. // Config configures a Write.as client.
  54. type Config struct {
  55. // URL of the Write.as API service. Defaults to https://write.as/api.
  56. URL string
  57. // If specified, the API client will communicate with the Write.as Tor
  58. // hidden service using the provided port to connect to the local SOCKS
  59. // proxy.
  60. TorPort int
  61. // If specified, requests will be authenticated using this user token.
  62. // This may be provided after making a few anonymous requests with
  63. // SetToken.
  64. Token string
  65. }
  66. // NewClientWith builds a new API client with the provided configuration.
  67. func NewClientWith(c Config) *Client {
  68. if c.URL == "" {
  69. c.URL = apiURL
  70. }
  71. httpClient := &http.Client{Timeout: defaultHTTPTimeout}
  72. if c.TorPort > 0 {
  73. dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, fmt.Sprintf("127.0.0.1:%d", c.TorPort))
  74. httpClient.Transport = &http.Transport{Dial: dialSocksProxy}
  75. }
  76. return &Client{
  77. client: httpClient,
  78. baseURL: c.URL,
  79. token: c.Token,
  80. }
  81. }
  82. // SetToken sets the user token for all future Client requests. Setting this to
  83. // an empty string will change back to unauthenticated requests.
  84. func (c *Client) SetToken(token string) {
  85. c.token = token
  86. }
  87. // Token returns the user token currently set to the Client.
  88. func (c *Client) Token() string {
  89. return c.token
  90. }
  91. func (c *Client) get(path string, r interface{}) (*impart.Envelope, error) {
  92. method := "GET"
  93. if method != "GET" && method != "HEAD" {
  94. return nil, fmt.Errorf("Method %s not currently supported by library (only HEAD and GET).\n", method)
  95. }
  96. return c.request(method, path, nil, r)
  97. }
  98. func (c *Client) post(path string, data, r interface{}) (*impart.Envelope, error) {
  99. b := new(bytes.Buffer)
  100. json.NewEncoder(b).Encode(data)
  101. return c.request("POST", path, b, r)
  102. }
  103. func (c *Client) put(path string, data, r interface{}) (*impart.Envelope, error) {
  104. b := new(bytes.Buffer)
  105. json.NewEncoder(b).Encode(data)
  106. return c.request("PUT", path, b, r)
  107. }
  108. func (c *Client) delete(path string, data map[string]string) (*impart.Envelope, error) {
  109. r, err := c.buildRequest("DELETE", path, nil)
  110. if err != nil {
  111. return nil, err
  112. }
  113. q := r.URL.Query()
  114. for k, v := range data {
  115. q.Add(k, v)
  116. }
  117. r.URL.RawQuery = q.Encode()
  118. return c.doRequest(r, nil)
  119. }
  120. func (c *Client) request(method, path string, data io.Reader, result interface{}) (*impart.Envelope, error) {
  121. r, err := c.buildRequest(method, path, data)
  122. if err != nil {
  123. return nil, err
  124. }
  125. return c.doRequest(r, result)
  126. }
  127. func (c *Client) buildRequest(method, path string, data io.Reader) (*http.Request, error) {
  128. url := fmt.Sprintf("%s%s", c.baseURL, path)
  129. r, err := http.NewRequest(method, url, data)
  130. if err != nil {
  131. return nil, fmt.Errorf("Create request: %v", err)
  132. }
  133. c.prepareRequest(r)
  134. return r, nil
  135. }
  136. func (c *Client) doRequest(r *http.Request, result interface{}) (*impart.Envelope, error) {
  137. resp, err := c.client.Do(r)
  138. if err != nil {
  139. return nil, fmt.Errorf("Request: %v", err)
  140. }
  141. defer resp.Body.Close()
  142. env := &impart.Envelope{
  143. Code: resp.StatusCode,
  144. }
  145. if result != nil {
  146. env.Data = result
  147. err = json.NewDecoder(resp.Body).Decode(&env)
  148. if err != nil {
  149. return nil, err
  150. }
  151. }
  152. return env, nil
  153. }
  154. func (c *Client) prepareRequest(r *http.Request) {
  155. ua := c.UserAgent
  156. if ua == "" {
  157. ua = "go-writeas v" + Version
  158. }
  159. r.Header.Add("User-Agent", ua)
  160. r.Header.Add("Content-Type", "application/json")
  161. if c.token != "" {
  162. r.Header.Add("Authorization", "Token "+c.token)
  163. }
  164. }