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.

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