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.

205 lines
5.1 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"
  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. // BaseURL returns the base API URL the Client will make calls against.
  92. func (c *Client) BaseURL() string {
  93. return c.baseURL
  94. }
  95. func (c *Client) get(path string, r interface{}) (*impart.Envelope, error) {
  96. method := "GET"
  97. if method != "GET" && method != "HEAD" {
  98. return nil, fmt.Errorf("Method %s not currently supported by library (only HEAD and GET).\n", method)
  99. }
  100. return c.request(method, path, nil, r)
  101. }
  102. func (c *Client) post(path string, data, r interface{}) (*impart.Envelope, error) {
  103. b := new(bytes.Buffer)
  104. json.NewEncoder(b).Encode(data)
  105. return c.request("POST", path, b, r)
  106. }
  107. func (c *Client) put(path string, data, r interface{}) (*impart.Envelope, error) {
  108. b := new(bytes.Buffer)
  109. json.NewEncoder(b).Encode(data)
  110. return c.request("PUT", path, b, r)
  111. }
  112. func (c *Client) delete(path string, data map[string]string) (*impart.Envelope, error) {
  113. r, err := c.buildRequest("DELETE", path, nil)
  114. if err != nil {
  115. return nil, err
  116. }
  117. q := r.URL.Query()
  118. for k, v := range data {
  119. q.Add(k, v)
  120. }
  121. r.URL.RawQuery = q.Encode()
  122. return c.doRequest(r, nil)
  123. }
  124. func (c *Client) request(method, path string, data io.Reader, result interface{}) (*impart.Envelope, error) {
  125. r, err := c.buildRequest(method, path, data)
  126. if err != nil {
  127. return nil, err
  128. }
  129. return c.doRequest(r, result)
  130. }
  131. func (c *Client) buildRequest(method, path string, data io.Reader) (*http.Request, error) {
  132. url := fmt.Sprintf("%s%s", c.baseURL, path)
  133. r, err := http.NewRequest(method, url, data)
  134. if err != nil {
  135. return nil, fmt.Errorf("Create request: %v", err)
  136. }
  137. c.prepareRequest(r)
  138. return r, nil
  139. }
  140. func (c *Client) doRequest(r *http.Request, result interface{}) (*impart.Envelope, error) {
  141. resp, err := c.client.Do(r)
  142. if err != nil {
  143. return nil, fmt.Errorf("Request: %v", err)
  144. }
  145. defer resp.Body.Close()
  146. env := &impart.Envelope{
  147. Code: resp.StatusCode,
  148. }
  149. if result != nil {
  150. env.Data = result
  151. err = json.NewDecoder(resp.Body).Decode(&env)
  152. if err != nil {
  153. return nil, err
  154. }
  155. }
  156. return env, nil
  157. }
  158. func (c *Client) prepareRequest(r *http.Request) {
  159. ua := c.UserAgent
  160. if ua == "" {
  161. ua = "go-writeas v" + Version
  162. }
  163. r.Header.Set("User-Agent", ua)
  164. r.Header.Add("Content-Type", "application/json")
  165. if c.token != "" {
  166. r.Header.Add("Authorization", "Token "+c.token)
  167. }
  168. }