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.

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