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.

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