status.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. package mastodon
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "mime/multipart"
  7. "net/http"
  8. "net/url"
  9. "time"
  10. )
  11. type StatusPleroma struct {
  12. InReplyToAccountAcct string `json:"in_reply_to_account_acct"`
  13. }
  14. type ReplyInfo struct {
  15. ID string `json:"id"`
  16. Number int `json:"number"`
  17. }
  18. // Status is struct to hold status.
  19. type Status struct {
  20. ID string `json:"id"`
  21. URI string `json:"uri"`
  22. URL string `json:"url"`
  23. Account Account `json:"account"`
  24. InReplyToID interface{} `json:"in_reply_to_id"`
  25. InReplyToAccountID interface{} `json:"in_reply_to_account_id"`
  26. Reblog *Status `json:"reblog"`
  27. Content string `json:"content"`
  28. CreatedAt time.Time `json:"created_at"`
  29. Emojis []Emoji `json:"emojis"`
  30. RepliesCount int64 `json:"replies_count"`
  31. ReblogsCount int64 `json:"reblogs_count"`
  32. FavouritesCount int64 `json:"favourites_count"`
  33. Reblogged interface{} `json:"reblogged"`
  34. Favourited interface{} `json:"favourited"`
  35. Muted interface{} `json:"muted"`
  36. Sensitive bool `json:"sensitive"`
  37. SpoilerText string `json:"spoiler_text"`
  38. Visibility string `json:"visibility"`
  39. MediaAttachments []Attachment `json:"media_attachments"`
  40. Mentions []Mention `json:"mentions"`
  41. Tags []Tag `json:"tags"`
  42. Card *Card `json:"card"`
  43. Application Application `json:"application"`
  44. Language string `json:"language"`
  45. Pinned interface{} `json:"pinned"`
  46. Bookmarked bool `json:"bookmarked"`
  47. Poll *Poll `json:"poll"`
  48. // Custom fields
  49. Pleroma StatusPleroma `json:"pleroma"`
  50. ShowReplies bool `json:"show_replies"`
  51. IDReplies map[string][]ReplyInfo `json:"id_replies"`
  52. IDNumbers map[string]int `json:"id_numbers"`
  53. RetweetedByID string `json:"retweeted_by_id"`
  54. }
  55. // Context hold information for mastodon context.
  56. type Context struct {
  57. Ancestors []*Status `json:"ancestors"`
  58. Descendants []*Status `json:"descendants"`
  59. }
  60. // Card hold information for mastodon card.
  61. type Card struct {
  62. URL string `json:"url"`
  63. Title string `json:"title"`
  64. Description string `json:"description"`
  65. Image string `json:"image"`
  66. Type string `json:"type"`
  67. AuthorName string `json:"author_name"`
  68. AuthorURL string `json:"author_url"`
  69. ProviderName string `json:"provider_name"`
  70. ProviderURL string `json:"provider_url"`
  71. HTML string `json:"html"`
  72. Width int64 `json:"width"`
  73. Height int64 `json:"height"`
  74. }
  75. // GetFavourites return the favorite list of the current user.
  76. func (c *Client) GetFavourites(ctx context.Context, pg *Pagination) ([]*Status, error) {
  77. var statuses []*Status
  78. err := c.doAPI(ctx, http.MethodGet, "/api/v1/favourites", nil, &statuses, pg)
  79. if err != nil {
  80. return nil, err
  81. }
  82. return statuses, nil
  83. }
  84. // GetStatus return status specified by id.
  85. func (c *Client) GetStatus(ctx context.Context, id string) (*Status, error) {
  86. var status Status
  87. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%s", id), nil, &status, nil)
  88. if err != nil {
  89. return nil, err
  90. }
  91. return &status, nil
  92. }
  93. // GetStatusContext return status specified by id.
  94. func (c *Client) GetStatusContext(ctx context.Context, id string) (*Context, error) {
  95. var context Context
  96. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%s/context", id), nil, &context, nil)
  97. if err != nil {
  98. return nil, err
  99. }
  100. return &context, nil
  101. }
  102. // GetStatusCard return status specified by id.
  103. func (c *Client) GetStatusCard(ctx context.Context, id string) (*Card, error) {
  104. var card Card
  105. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%s/card", id), nil, &card, nil)
  106. if err != nil {
  107. return nil, err
  108. }
  109. return &card, nil
  110. }
  111. // GetRebloggedBy returns the account list of the user who reblogged the toot of id.
  112. func (c *Client) GetRebloggedBy(ctx context.Context, id string, pg *Pagination) ([]*Account, error) {
  113. var accounts []*Account
  114. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%s/reblogged_by", id), nil, &accounts, pg)
  115. if err != nil {
  116. return nil, err
  117. }
  118. return accounts, nil
  119. }
  120. // GetFavouritedBy returns the account list of the user who liked the toot of id.
  121. func (c *Client) GetFavouritedBy(ctx context.Context, id string, pg *Pagination) ([]*Account, error) {
  122. var accounts []*Account
  123. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%s/favourited_by", id), nil, &accounts, pg)
  124. if err != nil {
  125. return nil, err
  126. }
  127. return accounts, nil
  128. }
  129. // Reblog is reblog the toot of id and return status of reblog.
  130. func (c *Client) Reblog(ctx context.Context, id string) (*Status, error) {
  131. var status Status
  132. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/reblog", id), nil, &status, nil)
  133. if err != nil {
  134. return nil, err
  135. }
  136. return &status, nil
  137. }
  138. // Unreblog is unreblog the toot of id and return status of the original toot.
  139. func (c *Client) Unreblog(ctx context.Context, id string) (*Status, error) {
  140. var status Status
  141. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/unreblog", id), nil, &status, nil)
  142. if err != nil {
  143. return nil, err
  144. }
  145. return &status, nil
  146. }
  147. // Favourite is favourite the toot of id and return status of the favourite toot.
  148. func (c *Client) Favourite(ctx context.Context, id string) (*Status, error) {
  149. var status Status
  150. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/favourite", id), nil, &status, nil)
  151. if err != nil {
  152. return nil, err
  153. }
  154. return &status, nil
  155. }
  156. // Unfavourite is unfavourite the toot of id and return status of the unfavourite toot.
  157. func (c *Client) Unfavourite(ctx context.Context, id string) (*Status, error) {
  158. var status Status
  159. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/unfavourite", id), nil, &status, nil)
  160. if err != nil {
  161. return nil, err
  162. }
  163. return &status, nil
  164. }
  165. // GetTimelineHome return statuses from home timeline.
  166. func (c *Client) GetTimelineHome(ctx context.Context, pg *Pagination) ([]*Status, error) {
  167. var statuses []*Status
  168. err := c.doAPI(ctx, http.MethodGet, "/api/v1/timelines/home", nil, &statuses, pg)
  169. if err != nil {
  170. return nil, err
  171. }
  172. return statuses, nil
  173. }
  174. // GetTimelinePublic return statuses from public timeline.
  175. func (c *Client) GetTimelinePublic(ctx context.Context, isLocal bool, pg *Pagination) ([]*Status, error) {
  176. params := url.Values{}
  177. if isLocal {
  178. params.Set("local", "true")
  179. }
  180. var statuses []*Status
  181. err := c.doAPI(ctx, http.MethodGet, "/api/v1/timelines/public", params, &statuses, pg)
  182. if err != nil {
  183. return nil, err
  184. }
  185. return statuses, nil
  186. }
  187. // GetTimelineHashtag return statuses from tagged timeline.
  188. func (c *Client) GetTimelineHashtag(ctx context.Context, tag string, isLocal bool, pg *Pagination) ([]*Status, error) {
  189. params := url.Values{}
  190. if isLocal {
  191. params.Set("local", "t")
  192. }
  193. var statuses []*Status
  194. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/timelines/tag/%s", url.PathEscape(tag)), params, &statuses, pg)
  195. if err != nil {
  196. return nil, err
  197. }
  198. return statuses, nil
  199. }
  200. // GetTimelineList return statuses from a list timeline.
  201. func (c *Client) GetTimelineList(ctx context.Context, id string, pg *Pagination) ([]*Status, error) {
  202. var statuses []*Status
  203. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/timelines/list/%s", url.PathEscape(string(id))), nil, &statuses, pg)
  204. if err != nil {
  205. return nil, err
  206. }
  207. return statuses, nil
  208. }
  209. // GetTimelineMedia return statuses from media timeline.
  210. // NOTE: This is an experimental feature of pawoo.net.
  211. func (c *Client) GetTimelineMedia(ctx context.Context, isLocal bool, pg *Pagination) ([]*Status, error) {
  212. params := url.Values{}
  213. params.Set("media", "t")
  214. if isLocal {
  215. params.Set("local", "t")
  216. }
  217. var statuses []*Status
  218. err := c.doAPI(ctx, http.MethodGet, "/api/v1/timelines/public", params, &statuses, pg)
  219. if err != nil {
  220. return nil, err
  221. }
  222. return statuses, nil
  223. }
  224. // PostStatus post the toot.
  225. func (c *Client) PostStatus(ctx context.Context, toot *Toot) (*Status, error) {
  226. params := url.Values{}
  227. params.Set("status", toot.Status)
  228. if toot.InReplyToID != "" {
  229. params.Set("in_reply_to_id", string(toot.InReplyToID))
  230. }
  231. if toot.MediaIDs != nil {
  232. for _, media := range toot.MediaIDs {
  233. params.Add("media_ids[]", string(media))
  234. }
  235. }
  236. if toot.Visibility != "" {
  237. params.Set("visibility", fmt.Sprint(toot.Visibility))
  238. }
  239. if toot.Sensitive {
  240. params.Set("sensitive", "true")
  241. }
  242. if toot.SpoilerText != "" {
  243. params.Set("spoiler_text", toot.SpoilerText)
  244. }
  245. if toot.ContentType != "" {
  246. params.Set("content_type", toot.ContentType)
  247. }
  248. var status Status
  249. err := c.doAPI(ctx, http.MethodPost, "/api/v1/statuses", params, &status, nil)
  250. if err != nil {
  251. return nil, err
  252. }
  253. return &status, nil
  254. }
  255. // DeleteStatus delete the toot.
  256. func (c *Client) DeleteStatus(ctx context.Context, id string) error {
  257. return c.doAPI(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/statuses/%s", id), nil, nil, nil)
  258. }
  259. // Search search content with query.
  260. func (c *Client) Search(ctx context.Context, q string, qType string, limit int, resolve bool, offset int, accountID string) (*Results, error) {
  261. var results Results
  262. params := url.Values{}
  263. params.Set("q", q)
  264. params.Set("type", qType)
  265. params.Set("limit", fmt.Sprint(limit))
  266. params.Set("resolve", fmt.Sprint(resolve))
  267. params.Set("offset", fmt.Sprint(offset))
  268. if len(accountID) > 0 {
  269. params.Set("account_id", accountID)
  270. }
  271. err := c.doAPI(ctx, http.MethodGet, "/api/v2/search", params, &results, nil)
  272. if err != nil {
  273. return nil, err
  274. }
  275. return &results, nil
  276. }
  277. // UploadMedia upload a media attachment from a file.
  278. func (c *Client) UploadMedia(ctx context.Context, file string) (*Attachment, error) {
  279. var attachment Attachment
  280. err := c.doAPI(ctx, http.MethodPost, "/api/v1/media", file, &attachment, nil)
  281. if err != nil {
  282. return nil, err
  283. }
  284. return &attachment, nil
  285. }
  286. // UploadMediaFromReader uploads a media attachment from a io.Reader.
  287. func (c *Client) UploadMediaFromReader(ctx context.Context, reader io.Reader) (*Attachment, error) {
  288. var attachment Attachment
  289. err := c.doAPI(ctx, http.MethodPost, "/api/v1/media", reader, &attachment, nil)
  290. if err != nil {
  291. return nil, err
  292. }
  293. return &attachment, nil
  294. }
  295. // UploadMediaFromReader uploads a media attachment from a io.Reader.
  296. func (c *Client) UploadMediaFromMultipartFileHeader(ctx context.Context, fh *multipart.FileHeader) (*Attachment, error) {
  297. var attachment Attachment
  298. err := c.doAPI(ctx, http.MethodPost, "/api/v1/media", fh, &attachment, nil)
  299. if err != nil {
  300. return nil, err
  301. }
  302. return &attachment, nil
  303. }
  304. // GetTimelineDirect return statuses from direct timeline.
  305. func (c *Client) GetTimelineDirect(ctx context.Context, pg *Pagination) ([]*Status, error) {
  306. params := url.Values{}
  307. var statuses []*Status
  308. err := c.doAPI(ctx, http.MethodGet, "/api/v1/timelines/direct", params, &statuses, pg)
  309. if err != nil {
  310. return nil, err
  311. }
  312. return statuses, nil
  313. }
  314. // MuteConversation mutes status specified by id.
  315. func (c *Client) MuteConversation(ctx context.Context, id string) (*Status, error) {
  316. var status Status
  317. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/mute", id), nil, &status, nil)
  318. if err != nil {
  319. return nil, err
  320. }
  321. return &status, nil
  322. }
  323. // UnmuteConversation unmutes status specified by id.
  324. func (c *Client) UnmuteConversation(ctx context.Context, id string) (*Status, error) {
  325. var status Status
  326. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/unmute", id), nil, &status, nil)
  327. if err != nil {
  328. return nil, err
  329. }
  330. return &status, nil
  331. }
  332. // Bookmark bookmarks status specified by id.
  333. func (c *Client) Bookmark(ctx context.Context, id string) (*Status, error) {
  334. var status Status
  335. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/bookmark", id), nil, &status, nil)
  336. if err != nil {
  337. return nil, err
  338. }
  339. return &status, nil
  340. }
  341. // Unbookmark bookmarks status specified by id.
  342. func (c *Client) Unbookmark(ctx context.Context, id string) (*Status, error) {
  343. var status Status
  344. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/unbookmark", id), nil, &status, nil)
  345. if err != nil {
  346. return nil, err
  347. }
  348. return &status, nil
  349. }