accounts.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. package mastodon
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "encoding/json"
  7. "io"
  8. "mime/multipart"
  9. "net/http"
  10. "net/url"
  11. "path/filepath"
  12. "strconv"
  13. "time"
  14. "strings"
  15. "path"
  16. )
  17. type AccountPleroma struct {
  18. Relationship Relationship `json:"relationship"`
  19. IsAdmin bool `json:"is_admin"`
  20. IsModerator bool `json:"is_moderator"`
  21. IsConfirmed bool `json:"is_confirmed"`
  22. AcceptsChatMessages bool `json:"accepts_chat_messages"`
  23. HideFavourites *bool `json:"hide_favorites"`
  24. }
  25. // Account hold information for mastodon account.
  26. type Account struct {
  27. ID string `json:"id"`
  28. Username string `json:"username"`
  29. Acct string `json:"acct"`
  30. DisplayName string `json:"display_name"`
  31. Locked bool `json:"locked"`
  32. CreatedAt time.Time `json:"created_at"`
  33. FollowersCount int64 `json:"followers_count"`
  34. FollowingCount int64 `json:"following_count"`
  35. StatusesCount int64 `json:"statuses_count"`
  36. Note string `json:"note"`
  37. URL string `json:"url"`
  38. Avatar string `json:"avatar"`
  39. AvatarStatic string `json:"avatar_static"`
  40. Header string `json:"header"`
  41. HeaderStatic string `json:"header_static"`
  42. Emojis []Emoji `json:"emojis"`
  43. Moved *Account `json:"moved"`
  44. Fields []Field `json:"fields"`
  45. Bot bool `json:"bot"`
  46. Source *AccountSource `json:"source"`
  47. Pleroma *AccountPleroma `json:"pleroma"`
  48. MastodonAccount bool
  49. }
  50. // Field is a Mastodon account profile field.
  51. type Field struct {
  52. Name string `json:"name"`
  53. Value string `json:"value"`
  54. VerifiedAt time.Time `json:"verified_at"`
  55. }
  56. // AccountSource is a Mastodon account profile field.
  57. type AccountSource struct {
  58. Privacy *string `json:"privacy"`
  59. Sensitive *bool `json:"sensitive"`
  60. Language *string `json:"language"`
  61. Note *string `json:"note"`
  62. Fields *[]Field `json:"fields"`
  63. }
  64. type RegisterCredintals struct {
  65. http.Client
  66. Server string
  67. App *Application
  68. Reason string
  69. Username string
  70. Email string
  71. Password string
  72. Agreement bool
  73. Locale string
  74. }
  75. // Use for register account on GoToSocial
  76. type Registred struct {
  77. AccessToken string `json:"access_token"`
  78. CreatedAt int64 `json:"created_at"`
  79. Scope string `json:"scope"`
  80. TokenType string `json:"token_type"`
  81. }
  82. // RegisterAccount create new account
  83. func RegisterAccount(ctx context.Context, instance string, reason string, username string, email string, password string, agreement bool, locale string, registerCrendintals RegisterCredintals, bearer string) (*string, error) {
  84. var registred Registred
  85. params := url.Values{}
  86. params.Set("reason", reason)
  87. params.Set("username", username)
  88. params.Set("email", email)
  89. params.Set("password", password)
  90. params.Set("agreement", strconv.FormatBool(agreement))
  91. params.Set("locale", locale)
  92. u, err := url.Parse("https://" + instance)
  93. if err != nil {
  94. return nil, err
  95. }
  96. u.Path = path.Join(u.Path, "/api/v1/accounts")
  97. req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode()))
  98. if err != nil {
  99. return nil, err
  100. }
  101. req = req.WithContext(ctx)
  102. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  103. req.Header.Set("Authorization", "Bearer "+bearer)
  104. resp, err := registerCrendintals.Do(req)
  105. fmt.Println(req)
  106. fmt.Println(resp)
  107. if err != nil {
  108. return nil, err
  109. }
  110. defer resp.Body.Close()
  111. if resp.StatusCode != http.StatusOK {
  112. return nil, parseAPIError("bad request", resp)
  113. }
  114. err = json.NewDecoder(resp.Body).Decode(&registred)
  115. fmt.Println(resp.Body)
  116. if err != nil {
  117. return nil, err
  118. }
  119. return &registred.AccessToken, nil
  120. }
  121. // GetAccount return Account.
  122. func (c *Client) GetAccount(ctx context.Context, id string) (*Account, error) {
  123. var account Account
  124. params := url.Values{}
  125. params.Set("with_relationships", strconv.FormatBool(true))
  126. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%s", url.PathEscape(string(id))), params, &account, nil)
  127. if err != nil {
  128. return nil, err
  129. }
  130. if account.Pleroma == nil || len(account.Pleroma.Relationship.ID) < 1 {
  131. rs, err := c.GetAccountRelationships(ctx, []string{id})
  132. if err != nil {
  133. return nil, err
  134. }
  135. if len(rs) > 0 {
  136. if account.Pleroma != nil {
  137. account.Pleroma = &AccountPleroma{*rs[0],
  138. account.Pleroma.IsAdmin,
  139. account.Pleroma.IsModerator,
  140. account.Pleroma.IsConfirmed,
  141. account.Pleroma.AcceptsChatMessages,
  142. account.Pleroma.HideFavourites,
  143. }
  144. } else {
  145. account.MastodonAccount = true
  146. account.Pleroma = &AccountPleroma{*rs[0], false, false, false, false, nil}
  147. }
  148. }
  149. }
  150. return &account, nil
  151. }
  152. // GetAccountCurrentUser return Account of current user.
  153. func (c *Client) GetAccountCurrentUser(ctx context.Context) (*Account, error) {
  154. var account Account
  155. err := c.doAPI(ctx, http.MethodGet, "/api/v1/accounts/verify_credentials", nil, &account, nil)
  156. if err != nil {
  157. return nil, err
  158. }
  159. return &account, nil
  160. }
  161. // Profile is a struct for updating profiles.
  162. type Profile struct {
  163. // If it is nil it will not be updated.
  164. // If it is empty, update it with empty.
  165. DisplayName *string
  166. Note *string
  167. Locked *bool
  168. Fields *[]Field
  169. Source *AccountSource
  170. // Set the base64 encoded character string of the image.
  171. Avatar *multipart.FileHeader
  172. Header *multipart.FileHeader
  173. //Other settings
  174. Bot *bool
  175. Pleroma *ProfilePleroma
  176. }
  177. type ProfilePleroma struct {
  178. AcceptsChatMessages *bool
  179. ActorType *string
  180. AllowFollowingMove *bool
  181. Discoverable *bool
  182. HideFavourites *bool
  183. HideFollowers *bool
  184. HideFollows *bool
  185. HideFollowersCount *bool
  186. HideFollowsCount *bool
  187. Avatar *multipart.FileHeader
  188. Header *multipart.FileHeader
  189. }
  190. // AccountUpdate updates the information of the current user.
  191. func (c *Client) AccountUpdate(ctx context.Context, profile *Profile) (*Account, error) {
  192. var buf bytes.Buffer
  193. mw := multipart.NewWriter(&buf)
  194. if profile.DisplayName != nil {
  195. err := mw.WriteField("display_name", *profile.DisplayName)
  196. if err != nil {
  197. return nil, err
  198. }
  199. }
  200. if profile.Note != nil {
  201. err := mw.WriteField("note", *profile.Note)
  202. if err != nil {
  203. return nil, err
  204. }
  205. }
  206. if profile.Locked != nil {
  207. err := mw.WriteField("locked", strconv.FormatBool(*profile.Locked))
  208. if err != nil {
  209. return nil, err
  210. }
  211. }
  212. if profile.Fields != nil {
  213. for idx, field := range *profile.Fields {
  214. err := mw.WriteField(fmt.Sprintf("fields_attributes[%d][name]", idx), field.Name)
  215. if err != nil {
  216. return nil, err
  217. }
  218. err = mw.WriteField(fmt.Sprintf("fields_attributes[%d][value]", idx), field.Value)
  219. if err != nil {
  220. return nil, err
  221. }
  222. }
  223. }
  224. if profile.Avatar != nil {
  225. f, err := profile.Avatar.Open()
  226. if err != nil {
  227. return nil, err
  228. }
  229. fname := filepath.Base(profile.Avatar.Filename)
  230. part, err := mw.CreateFormFile("avatar", fname)
  231. if err != nil {
  232. return nil, err
  233. }
  234. _, err = io.Copy(part, f)
  235. if err != nil {
  236. return nil, err
  237. }
  238. }
  239. if profile.Header != nil {
  240. f, err := profile.Header.Open()
  241. if err != nil {
  242. return nil, err
  243. }
  244. fname := filepath.Base(profile.Header.Filename)
  245. part, err := mw.CreateFormFile("header", fname)
  246. if err != nil {
  247. return nil, err
  248. }
  249. _, err = io.Copy(part, f)
  250. if err != nil {
  251. return nil, err
  252. }
  253. }
  254. err := mw.Close()
  255. if err != nil {
  256. return nil, err
  257. }
  258. params := &multipartRequest{Data: &buf, ContentType: mw.FormDataContentType()}
  259. var account Account
  260. err = c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
  261. if err != nil {
  262. return nil, err
  263. }
  264. return &account, nil
  265. }
  266. func (c *Client) accountDeleteField(ctx context.Context, field string) (*Account, error) {
  267. var buf bytes.Buffer
  268. mw := multipart.NewWriter(&buf)
  269. _, err := mw.CreateFormField(field)
  270. if err != nil {
  271. return nil, err
  272. }
  273. err = mw.Close()
  274. if err != nil {
  275. return nil, err
  276. }
  277. params := &multipartRequest{Data: &buf, ContentType: mw.FormDataContentType()}
  278. var account Account
  279. err = c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
  280. if err != nil {
  281. return nil, err
  282. }
  283. return &account, nil
  284. }
  285. func (c *Client) AccountDeleteAvatar(ctx context.Context) (*Account, error) {
  286. return c.accountDeleteField(ctx, "avatar")
  287. }
  288. func (c *Client) AccountDeleteHeader(ctx context.Context) (*Account, error) {
  289. return c.accountDeleteField(ctx, "header")
  290. }
  291. // GetAccountStatuses return statuses by specified accuont.
  292. func (c *Client) GetAccountStatuses(ctx context.Context, id string, onlyMedia bool, onlyPinned bool, pg *Pagination) ([]*Status, error) {
  293. var statuses []*Status
  294. params := url.Values{}
  295. params.Set("only_media", strconv.FormatBool(onlyMedia))
  296. params.Set("pinned", strconv.FormatBool(onlyPinned))
  297. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%s/statuses", url.PathEscape(string(id))), params, &statuses, pg)
  298. if err != nil {
  299. return nil, err
  300. }
  301. return statuses, nil
  302. }
  303. // GetAccountFollowers return followers list.
  304. func (c *Client) GetAccountFollowers(ctx context.Context, id string, pg *Pagination) ([]*Account, error) {
  305. var accounts []*Account
  306. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%s/followers", url.PathEscape(string(id))), nil, &accounts, pg)
  307. if err != nil {
  308. return nil, err
  309. }
  310. return accounts, nil
  311. }
  312. // GetAccountFollowing return following list.
  313. func (c *Client) GetAccountFollowing(ctx context.Context, id string, pg *Pagination) ([]*Account, error) {
  314. var accounts []*Account
  315. err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%s/following", url.PathEscape(string(id))), nil, &accounts, pg)
  316. if err != nil {
  317. return nil, err
  318. }
  319. return accounts, nil
  320. }
  321. // GetBlocks return block list.
  322. func (c *Client) GetBlocks(ctx context.Context, pg *Pagination) ([]*Account, error) {
  323. var accounts []*Account
  324. err := c.doAPI(ctx, http.MethodGet, "/api/v1/blocks", nil, &accounts, pg)
  325. if err != nil {
  326. return nil, err
  327. }
  328. return accounts, nil
  329. }
  330. // Relationship hold information for relation-ship to the account.
  331. type Relationship struct {
  332. ID string `json:"id"`
  333. Following bool `json:"following"`
  334. FollowedBy bool `json:"followed_by"`
  335. Blocking bool `json:"blocking"`
  336. BlockedBy bool `json:"blocked_by"`
  337. Muting bool `json:"muting"`
  338. MutingNotifications bool `json:"muting_notifications"`
  339. Subscribing bool `json:"subscribing"`
  340. Requested bool `json:"requested"`
  341. DomainBlocking bool `json:"domain_blocking"`
  342. ShowingReblogs bool `json:"showing_reblogs"`
  343. Endorsed bool `json:"endorsed"`
  344. }
  345. // AccountFollow follow the account.
  346. func (c *Client) AccountFollow(ctx context.Context, id string, reblogs *bool) (*Relationship, error) {
  347. var relationship Relationship
  348. params := url.Values{}
  349. if reblogs != nil {
  350. params.Set("reblogs", strconv.FormatBool(*reblogs))
  351. }
  352. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/follow", url.PathEscape(id)), params, &relationship, nil)
  353. if err != nil {
  354. return nil, err
  355. }
  356. return &relationship, nil
  357. }
  358. // AccountUnfollow unfollow the account.
  359. func (c *Client) AccountUnfollow(ctx context.Context, id string) (*Relationship, error) {
  360. var relationship Relationship
  361. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/unfollow", url.PathEscape(string(id))), nil, &relationship, nil)
  362. if err != nil {
  363. return nil, err
  364. }
  365. return &relationship, nil
  366. }
  367. // AccountBlock block the account.
  368. func (c *Client) AccountBlock(ctx context.Context, id string) (*Relationship, error) {
  369. var relationship Relationship
  370. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/block", url.PathEscape(string(id))), nil, &relationship, nil)
  371. if err != nil {
  372. return nil, err
  373. }
  374. return &relationship, nil
  375. }
  376. // AccountUnblock unblock the account.
  377. func (c *Client) AccountUnblock(ctx context.Context, id string) (*Relationship, error) {
  378. var relationship Relationship
  379. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/unblock", url.PathEscape(string(id))), nil, &relationship, nil)
  380. if err != nil {
  381. return nil, err
  382. }
  383. return &relationship, nil
  384. }
  385. // AccountMute mute the account.
  386. func (c *Client) AccountMute(ctx context.Context, id string, notifications bool, duration int) (*Relationship, error) {
  387. params := url.Values{}
  388. params.Set("notifications", strconv.FormatBool(notifications))
  389. params.Set("duration", strconv.Itoa(duration))
  390. var relationship Relationship
  391. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/mute", url.PathEscape(string(id))), params, &relationship, nil)
  392. if err != nil {
  393. return nil, err
  394. }
  395. return &relationship, nil
  396. }
  397. // AccountUnmute unmute the account.
  398. func (c *Client) AccountUnmute(ctx context.Context, id string) (*Relationship, error) {
  399. var relationship Relationship
  400. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/unmute", url.PathEscape(string(id))), nil, &relationship, nil)
  401. if err != nil {
  402. return nil, err
  403. }
  404. return &relationship, nil
  405. }
  406. // GetAccountRelationships return relationship for the account.
  407. func (c *Client) GetAccountRelationships(ctx context.Context, ids []string) ([]*Relationship, error) {
  408. params := url.Values{}
  409. for _, id := range ids {
  410. params.Add("id[]", id)
  411. }
  412. var relationships []*Relationship
  413. err := c.doAPI(ctx, http.MethodGet, "/api/v1/accounts/relationships", params, &relationships, nil)
  414. if err != nil {
  415. return nil, err
  416. }
  417. return relationships, nil
  418. }
  419. // AccountsSearch search accounts by query.
  420. func (c *Client) AccountsSearch(ctx context.Context, q string, limit int64) ([]*Account, error) {
  421. params := url.Values{}
  422. params.Set("q", q)
  423. params.Set("limit", fmt.Sprint(limit))
  424. var accounts []*Account
  425. err := c.doAPI(ctx, http.MethodGet, "/api/v1/accounts/search", params, &accounts, nil)
  426. if err != nil {
  427. return nil, err
  428. }
  429. return accounts, nil
  430. }
  431. // FollowRemoteUser send follow-request.
  432. func (c *Client) FollowRemoteUser(ctx context.Context, uri string) (*Account, error) {
  433. params := url.Values{}
  434. params.Set("uri", uri)
  435. var account Account
  436. err := c.doAPI(ctx, http.MethodPost, "/api/v1/follows", params, &account, nil)
  437. if err != nil {
  438. return nil, err
  439. }
  440. return &account, nil
  441. }
  442. // GetFollowRequests return follow-requests.
  443. func (c *Client) GetFollowRequests(ctx context.Context, pg *Pagination) ([]*Account, error) {
  444. var accounts []*Account
  445. err := c.doAPI(ctx, http.MethodGet, "/api/v1/follow_requests", nil, &accounts, pg)
  446. if err != nil {
  447. return nil, err
  448. }
  449. return accounts, nil
  450. }
  451. // FollowRequestAuthorize is authorize the follow request of user with id.
  452. func (c *Client) FollowRequestAuthorize(ctx context.Context, id string) error {
  453. return c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%s/authorize", url.PathEscape(string(id))), nil, nil, nil)
  454. }
  455. // FollowRequestReject is rejects the follow request of user with id.
  456. func (c *Client) FollowRequestReject(ctx context.Context, id string) error {
  457. return c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%s/reject", url.PathEscape(string(id))), nil, nil, nil)
  458. }
  459. // GetMutes returns the list of users muted by the current user.
  460. func (c *Client) GetMutes(ctx context.Context, pg *Pagination) ([]*Account, error) {
  461. var accounts []*Account
  462. err := c.doAPI(ctx, http.MethodGet, "/api/v1/mutes", nil, &accounts, pg)
  463. if err != nil {
  464. return nil, err
  465. }
  466. return accounts, nil
  467. }
  468. // Subscribe to receive notifications for all statuses posted by a user
  469. func (c *Client) Subscribe(ctx context.Context, id string) (*Relationship, error) {
  470. var relationship *Relationship
  471. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/pleroma/accounts/%s/subscribe", url.PathEscape(id)), nil, &relationship, nil)
  472. if err != nil {
  473. return nil, err
  474. }
  475. return relationship, nil
  476. }
  477. // UnSubscribe to stop receiving notifications from user statuses
  478. func (c *Client) UnSubscribe(ctx context.Context, id string) (*Relationship, error) {
  479. var relationship *Relationship
  480. err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/pleroma/accounts/%s/unsubscribe", url.PathEscape(id)), nil, &relationship, nil)
  481. if err != nil {
  482. return nil, err
  483. }
  484. return relationship, nil
  485. }
  486. // GetBookmarks returns the list of bookmarked statuses
  487. func (c *Client) GetBookmarks(ctx context.Context, pg *Pagination) ([]*Status, error) {
  488. var statuses []*Status
  489. err := c.doAPI(ctx, http.MethodGet, "/api/v1/bookmarks", nil, &statuses, pg)
  490. if err != nil {
  491. return nil, err
  492. }
  493. return statuses, nil
  494. }