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.

175 lines
4.3 KiB

  1. // Package writeas provides the binding for the Write.as API
  2. package writeas
  3. import (
  4. "bytes"
  5. "code.as/core/socks"
  6. "encoding/json"
  7. "fmt"
  8. "github.com/writeas/impart"
  9. "io"
  10. "net/http"
  11. "time"
  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 &Client{
  40. client: &http.Client{Timeout: defaultHTTPTimeout},
  41. baseURL: apiURL,
  42. }
  43. }
  44. // NewTorClient creates a new API client for communicating with the Write.as
  45. // Tor hidden service, using the given port to connect to the local SOCKS
  46. // proxy.
  47. func NewTorClient(port int) *Client {
  48. dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, fmt.Sprintf("127.0.0.1:%d", port))
  49. transport := &http.Transport{Dial: dialSocksProxy}
  50. return &Client{
  51. client: &http.Client{Transport: transport},
  52. baseURL: torAPIURL,
  53. }
  54. }
  55. // NewDevClient creates a new API client for development and testing. It'll
  56. // communicate with our development servers, and SHOULD NOT be used in
  57. // production.
  58. func NewDevClient() *Client {
  59. return &Client{
  60. client: &http.Client{Timeout: defaultHTTPTimeout},
  61. baseURL: devAPIURL,
  62. }
  63. }
  64. // SetToken sets the user token for all future Client requests. Setting this to
  65. // an empty string will change back to unauthenticated requests.
  66. func (c *Client) SetToken(token string) {
  67. c.token = token
  68. }
  69. // Token returns the user token currently set to the Client.
  70. func (c *Client) Token() string {
  71. return c.token
  72. }
  73. func (c *Client) get(path string, r interface{}) (*impart.Envelope, error) {
  74. method := "GET"
  75. if method != "GET" && method != "HEAD" {
  76. return nil, fmt.Errorf("Method %s not currently supported by library (only HEAD and GET).\n", method)
  77. }
  78. return c.request(method, path, nil, r)
  79. }
  80. func (c *Client) post(path string, data, r interface{}) (*impart.Envelope, error) {
  81. b := new(bytes.Buffer)
  82. json.NewEncoder(b).Encode(data)
  83. return c.request("POST", path, b, r)
  84. }
  85. func (c *Client) put(path string, data, r interface{}) (*impart.Envelope, error) {
  86. b := new(bytes.Buffer)
  87. json.NewEncoder(b).Encode(data)
  88. return c.request("PUT", path, b, r)
  89. }
  90. func (c *Client) delete(path string, data map[string]string) (*impart.Envelope, error) {
  91. r, err := c.buildRequest("DELETE", path, nil)
  92. if err != nil {
  93. return nil, err
  94. }
  95. q := r.URL.Query()
  96. for k, v := range data {
  97. q.Add(k, v)
  98. }
  99. r.URL.RawQuery = q.Encode()
  100. return c.doRequest(r, nil)
  101. }
  102. func (c *Client) request(method, path string, data io.Reader, result interface{}) (*impart.Envelope, error) {
  103. r, err := c.buildRequest(method, path, data)
  104. if err != nil {
  105. return nil, err
  106. }
  107. return c.doRequest(r, result)
  108. }
  109. func (c *Client) buildRequest(method, path string, data io.Reader) (*http.Request, error) {
  110. url := fmt.Sprintf("%s%s", c.baseURL, path)
  111. r, err := http.NewRequest(method, url, data)
  112. if err != nil {
  113. return nil, fmt.Errorf("Create request: %v", err)
  114. }
  115. c.prepareRequest(r)
  116. return r, nil
  117. }
  118. func (c *Client) doRequest(r *http.Request, result interface{}) (*impart.Envelope, error) {
  119. resp, err := c.client.Do(r)
  120. if err != nil {
  121. return nil, fmt.Errorf("Request: %v", err)
  122. }
  123. defer resp.Body.Close()
  124. env := &impart.Envelope{
  125. Code: resp.StatusCode,
  126. }
  127. if result != nil {
  128. env.Data = result
  129. err = json.NewDecoder(resp.Body).Decode(&env)
  130. if err != nil {
  131. return nil, err
  132. }
  133. }
  134. return env, nil
  135. }
  136. func (c *Client) prepareRequest(r *http.Request) {
  137. ua := c.UserAgent
  138. if ua == "" {
  139. ua = "go-writeas v" + Version
  140. }
  141. r.Header.Add("User-Agent", ua)
  142. r.Header.Add("Content-Type", "application/json")
  143. if c.token != "" {
  144. r.Header.Add("Authorization", "Token "+c.token)
  145. }
  146. }