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.

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