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.

328 lines
9.3 KiB

  1. package writeas
  2. import (
  3. "fmt"
  4. "net/http"
  5. "time"
  6. )
  7. type (
  8. // Post represents a published Write.as post, whether anonymous, owned by a
  9. // user, or part of a collection.
  10. Post struct {
  11. ID string `json:"id"`
  12. Slug string `json:"slug"`
  13. Token string `json:"token"`
  14. Font string `json:"appearance"`
  15. Language *string `json:"language"`
  16. RTL *bool `json:"rtl"`
  17. Listed bool `json:"listed"`
  18. Created time.Time `json:"created"`
  19. Updated time.Time `json:"updated"`
  20. Title string `json:"title"`
  21. Content string `json:"body"`
  22. Views int64 `json:"views"`
  23. Tags []string `json:"tags"`
  24. Images []string `json:"images"`
  25. OwnerName string `json:"owner,omitempty"`
  26. Collection *Collection `json:"collection,omitempty"`
  27. }
  28. // OwnedPostParams are, together, fields only the original post author knows.
  29. OwnedPostParams struct {
  30. ID string `json:"-"`
  31. Token string `json:"token,omitempty"`
  32. }
  33. // PostParams holds values for creating or updating a post.
  34. PostParams struct {
  35. // Parameters only for updating
  36. ID string `json:"-"`
  37. Token string `json:"token,omitempty"`
  38. // Parameters for creating or updating
  39. Title string `json:"title,omitempty"`
  40. Content string `json:"body,omitempty"`
  41. Font string `json:"font,omitempty"`
  42. IsRTL *bool `json:"rtl,omitempty"`
  43. Language *string `json:"lang,omitempty"`
  44. // Parameters only for creating
  45. Crosspost []map[string]string `json:"crosspost,omitempty"`
  46. // Parameters for collection posts
  47. Collection string `json:"-"`
  48. }
  49. // PinnedPostParams holds values for pinning a post
  50. PinnedPostParams struct {
  51. ID string `json:"id"`
  52. Position int `json:"position"`
  53. }
  54. // BatchPostResult contains the post-specific result as part of a larger
  55. // batch operation.
  56. BatchPostResult struct {
  57. ID string `json:"id,omitempty"`
  58. Code int `json:"code,omitempty"`
  59. ErrorMessage string `json:"error_msg,omitempty"`
  60. }
  61. // ClaimPostResult contains the post-specific result for a request to
  62. // associate a post to an account.
  63. ClaimPostResult struct {
  64. ID string `json:"id,omitempty"`
  65. Code int `json:"code,omitempty"`
  66. ErrorMessage string `json:"error_msg,omitempty"`
  67. Post *Post `json:"post,omitempty"`
  68. }
  69. )
  70. // GetPost retrieves a published post, returning the Post and any error (in
  71. // user-friendly form) that occurs. See
  72. // https://developer.write.as/docs/api/#retrieve-a-post.
  73. func (c *Client) GetPost(id string) (*Post, error) {
  74. p := &Post{}
  75. env, err := c.get(fmt.Sprintf("/posts/%s", id), p)
  76. if err != nil {
  77. return nil, err
  78. }
  79. var ok bool
  80. if p, ok = env.Data.(*Post); !ok {
  81. return nil, fmt.Errorf("Wrong data returned from API.")
  82. }
  83. status := env.Code
  84. if status == http.StatusOK {
  85. return p, nil
  86. } else if status == http.StatusNotFound {
  87. return nil, fmt.Errorf("Post not found.")
  88. } else if status == http.StatusGone {
  89. return nil, fmt.Errorf("Post unpublished.")
  90. }
  91. return nil, fmt.Errorf("Problem getting post: %d. %s\n", status, env.ErrorMessage)
  92. }
  93. // CreatePost publishes a new post, returning a user-friendly error if one comes
  94. // up. See https://developer.write.as/docs/api/#publish-a-post.
  95. func (c *Client) CreatePost(sp *PostParams) (*Post, error) {
  96. p := &Post{}
  97. endPre := ""
  98. if sp.Collection != "" {
  99. endPre = "/collections/" + sp.Collection
  100. }
  101. env, err := c.post(endPre+"/posts", sp, p)
  102. if err != nil {
  103. return nil, err
  104. }
  105. var ok bool
  106. if p, ok = env.Data.(*Post); !ok {
  107. return nil, fmt.Errorf("Wrong data returned from API.")
  108. }
  109. status := env.Code
  110. if status != http.StatusCreated {
  111. if status == http.StatusBadRequest {
  112. return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
  113. }
  114. return nil, fmt.Errorf("Problem creating post: %d. %s\n", status, env.ErrorMessage)
  115. }
  116. return p, nil
  117. }
  118. // UpdatePost updates a published post with the given PostParams. See
  119. // https://developer.write.as/docs/api/#update-a-post.
  120. func (c *Client) UpdatePost(id, token string, sp *PostParams) (*Post, error) {
  121. return c.updatePost("", id, token, sp)
  122. }
  123. func (c *Client) updatePost(collection, identifier, token string, sp *PostParams) (*Post, error) {
  124. p := &Post{}
  125. endpoint := "/posts/" + identifier
  126. /*
  127. if collection != "" {
  128. endpoint = "/collections/" + collection + endpoint
  129. } else {
  130. sp.Token = token
  131. }
  132. */
  133. sp.Token = token
  134. env, err := c.put(endpoint, sp, p)
  135. if err != nil {
  136. return nil, err
  137. }
  138. var ok bool
  139. if p, ok = env.Data.(*Post); !ok {
  140. return nil, fmt.Errorf("Wrong data returned from API.")
  141. }
  142. status := env.Code
  143. if status != http.StatusOK {
  144. if c.isNotLoggedIn(status) {
  145. return nil, fmt.Errorf("Not authenticated.")
  146. } else if status == http.StatusBadRequest {
  147. return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
  148. }
  149. return nil, fmt.Errorf("Problem updating post: %d. %s\n", status, env.ErrorMessage)
  150. }
  151. return p, nil
  152. }
  153. // DeletePost permanently deletes a published post. See
  154. // https://developer.write.as/docs/api/#delete-a-post.
  155. func (c *Client) DeletePost(id, token string) error {
  156. return c.deletePost("", id, token)
  157. }
  158. func (c *Client) deletePost(collection, identifier, token string) error {
  159. p := map[string]string{}
  160. endpoint := "/posts/" + identifier
  161. /*
  162. if collection != "" {
  163. endpoint = "/collections/" + collection + endpoint
  164. } else {
  165. p["token"] = token
  166. }
  167. */
  168. p["token"] = token
  169. env, err := c.delete(endpoint, p)
  170. if err != nil {
  171. return err
  172. }
  173. status := env.Code
  174. if status == http.StatusNoContent {
  175. return nil
  176. } else if c.isNotLoggedIn(status) {
  177. return fmt.Errorf("Not authenticated.")
  178. } else if status == http.StatusBadRequest {
  179. return fmt.Errorf("Bad request: %s", env.ErrorMessage)
  180. }
  181. return fmt.Errorf("Problem deleting post: %d. %s\n", status, env.ErrorMessage)
  182. }
  183. // ClaimPosts associates anonymous posts with a user / account.
  184. // https://developer.write.as/docs/api/#claim-posts.
  185. func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) {
  186. p := &[]ClaimPostResult{}
  187. env, err := c.put("/posts/claim", sp, p)
  188. if err != nil {
  189. return nil, err
  190. }
  191. var ok bool
  192. if p, ok = env.Data.(*[]ClaimPostResult); !ok {
  193. return nil, fmt.Errorf("Wrong data returned from API.")
  194. }
  195. status := env.Code
  196. if status == http.StatusOK {
  197. return p, nil
  198. } else if c.isNotLoggedIn(status) {
  199. return nil, fmt.Errorf("Not authenticated.")
  200. } else if status == http.StatusBadRequest {
  201. return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
  202. } else {
  203. return nil, fmt.Errorf("Problem claiming post: %d. %s\n", status, env.ErrorMessage)
  204. }
  205. // TODO: does this also happen with moving posts?
  206. }
  207. // GetUserPosts retrieves the authenticated user's posts.
  208. // See https://developers.write.as/docs/api/#retrieve-user-39-s-posts
  209. func (c *Client) GetUserPosts() (*[]Post, error) {
  210. p := &[]Post{}
  211. env, err := c.get("/me/posts", p)
  212. if err != nil {
  213. return nil, err
  214. }
  215. var ok bool
  216. if p, ok = env.Data.(*[]Post); !ok {
  217. return nil, fmt.Errorf("Wrong data returned from API.")
  218. }
  219. status := env.Code
  220. if status != http.StatusOK {
  221. if c.isNotLoggedIn(status) {
  222. return nil, fmt.Errorf("Not authenticated.")
  223. }
  224. return nil, fmt.Errorf("Problem getting user posts: %d. %s\n", status, env.ErrorMessage)
  225. }
  226. return p, nil
  227. }
  228. // PinPost pins a post in the given collection.
  229. // See https://developers.write.as/docs/api/#pin-a-post-to-a-collection
  230. func (c *Client) PinPost(alias string, pp *PinnedPostParams) error {
  231. res := &[]BatchPostResult{}
  232. env, err := c.post(fmt.Sprintf("/collections/%s/pin", alias), []*PinnedPostParams{pp}, res)
  233. if err != nil {
  234. return err
  235. }
  236. var ok bool
  237. if res, ok = env.Data.(*[]BatchPostResult); !ok {
  238. return fmt.Errorf("Wrong data returned from API.")
  239. }
  240. // Check for basic request errors on top level response
  241. status := env.Code
  242. if status != http.StatusOK {
  243. if c.isNotLoggedIn(status) {
  244. return fmt.Errorf("Not authenticated.")
  245. }
  246. return fmt.Errorf("Problem pinning post: %d. %s\n", status, env.ErrorMessage)
  247. }
  248. // Check the individual post result
  249. if len(*res) == 0 || len(*res) > 1 {
  250. return fmt.Errorf("Wrong data returned from API.")
  251. }
  252. if (*res)[0].Code != http.StatusOK {
  253. return fmt.Errorf("Problem pinning post: %d", (*res)[0].Code)
  254. // TODO: return ErrorMessage (right now it'll be empty)
  255. // return fmt.Errorf("Problem pinning post: %s", res[0].ErrorMessage)
  256. }
  257. return nil
  258. }
  259. // UnpinPost unpins a post from the given collection.
  260. // See https://developers.write.as/docs/api/#unpin-a-post-from-a-collection
  261. func (c *Client) UnpinPost(alias string, pp *PinnedPostParams) error {
  262. res := &[]BatchPostResult{}
  263. env, err := c.post(fmt.Sprintf("/collections/%s/unpin", alias), []*PinnedPostParams{pp}, res)
  264. if err != nil {
  265. return err
  266. }
  267. var ok bool
  268. if res, ok = env.Data.(*[]BatchPostResult); !ok {
  269. return fmt.Errorf("Wrong data returned from API.")
  270. }
  271. // Check for basic request errors on top level response
  272. status := env.Code
  273. if status != http.StatusOK {
  274. if c.isNotLoggedIn(status) {
  275. return fmt.Errorf("Not authenticated.")
  276. }
  277. return fmt.Errorf("Problem unpinning post: %d. %s\n", status, env.ErrorMessage)
  278. }
  279. // Check the individual post result
  280. if len(*res) == 0 || len(*res) > 1 {
  281. return fmt.Errorf("Wrong data returned from API.")
  282. }
  283. if (*res)[0].Code != http.StatusOK {
  284. return fmt.Errorf("Problem unpinning post: %d", (*res)[0].Code)
  285. // TODO: return ErrorMessage (right now it'll be empty)
  286. // return fmt.Errorf("Problem unpinning post: %s", res[0].ErrorMessage)
  287. }
  288. return nil
  289. }