logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/

api.service.js (57748B)


  1. import { each, map, concat, last, get } from 'lodash'
  2. import { parseStatus, parseSource, parseUser, parseNotification, parseAttachment, parseChat, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
  3. import { RegistrationError, StatusCodeError } from '../errors/errors'
  4. /* eslint-env browser */
  5. const MUTES_IMPORT_URL = '/api/pleroma/mutes_import'
  6. const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
  7. const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
  8. const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
  9. const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
  10. const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
  11. const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
  12. const ALIASES_URL = '/api/pleroma/aliases'
  13. const TAG_USER_URL = '/api/pleroma/admin/users/tag'
  14. const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
  15. const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
  16. const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
  17. const ADMIN_USERS_URL = '/api/pleroma/admin/users'
  18. const SUGGESTIONS_URL = '/api/v1/suggestions'
  19. const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
  20. const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
  21. const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
  22. const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
  23. const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
  24. const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
  25. const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
  26. const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
  27. const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
  28. const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
  29. const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
  30. const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss`
  31. const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
  32. const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
  33. const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
  34. const MASTODON_UNRETWEET_URL = id => `/api/v1/statuses/${id}/unreblog`
  35. const MASTODON_DELETE_URL = id => `/api/v1/statuses/${id}`
  36. const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow`
  37. const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow`
  38. const MASTODON_FOLLOWING_URL = id => `/api/v1/accounts/${id}/following`
  39. const MASTODON_FOLLOWERS_URL = id => `/api/v1/accounts/${id}/followers`
  40. const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
  41. const MASTODON_APPROVE_USER_URL = id => `/api/v1/follow_requests/${id}/authorize`
  42. const MASTODON_DENY_USER_URL = id => `/api/v1/follow_requests/${id}/reject`
  43. const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct'
  44. const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public'
  45. const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
  46. const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}`
  47. const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context`
  48. const MASTODON_STATUS_SOURCE_URL = id => `/api/v1/statuses/${id}/source`
  49. const MASTODON_STATUS_HISTORY_URL = id => `/api/v1/statuses/${id}/history`
  50. const MASTODON_USER_URL = '/api/v1/accounts'
  51. const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup'
  52. const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
  53. const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
  54. const MASTODON_USER_IN_LISTS = id => `/api/v1/accounts/${id}/lists`
  55. const MASTODON_LIST_URL = id => `/api/v1/lists/${id}`
  56. const MASTODON_LIST_TIMELINE_URL = id => `/api/v1/timelines/list/${id}`
  57. const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts`
  58. const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}`
  59. const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks'
  60. const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
  61. const MASTODON_USER_MUTES_URL = '/api/v1/mutes/'
  62. const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
  63. const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
  64. const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
  65. const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
  66. const MASTODON_REMOVE_USER_FROM_FOLLOWERS = id => `/api/v1/accounts/${id}/remove_from_followers`
  67. const MASTODON_USER_NOTE_URL = id => `/api/v1/accounts/${id}/note`
  68. const MASTODON_BOOKMARK_STATUS_URL = id => `/api/v1/statuses/${id}/bookmark`
  69. const MASTODON_UNBOOKMARK_STATUS_URL = id => `/api/v1/statuses/${id}/unbookmark`
  70. const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
  71. const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
  72. const MASTODON_VOTE_URL = id => `/api/v1/polls/${id}/votes`
  73. const MASTODON_POLL_URL = id => `/api/v1/polls/${id}`
  74. const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by`
  75. const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by`
  76. const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
  77. const MASTODON_REPORT_USER_URL = '/api/v1/reports'
  78. const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
  79. const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
  80. const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
  81. const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
  82. const MASTODON_SEARCH_2 = '/api/v2/search'
  83. const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
  84. const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
  85. const MASTODON_LISTS_URL = '/api/v1/lists'
  86. const MASTODON_STREAMING = '/api/v1/streaming'
  87. const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
  88. const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements'
  89. const MASTODON_ANNOUNCEMENTS_DISMISS_URL = id => `/api/v1/announcements/${id}/dismiss`
  90. const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
  91. const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
  92. const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
  93. const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats'
  94. const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
  95. const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
  96. const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
  97. const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
  98. const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
  99. const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
  100. const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
  101. const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
  102. const PLEROMA_EDIT_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
  103. const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
  104. const PLEROMA_SCROBBLES_URL = id => `/api/v1/pleroma/accounts/${id}/scrobbles`
  105. const PLEROMA_STATUS_QUOTES_URL = id => `/api/v1/pleroma/statuses/${id}/quotes`
  106. const PLEROMA_USER_FAVORITES_TIMELINE_URL = id => `/api/v1/pleroma/accounts/${id}/favourites`
  107. const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
  108. const PLEROMA_BOOKMARK_FOLDER_URL = id => `/api/v1/pleroma/bookmark_folders/${id}`
  109. const PLEROMA_ADMIN_CONFIG_URL = '/api/pleroma/admin/config'
  110. const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/pleroma/admin/config/descriptions'
  111. const PLEROMA_ADMIN_FRONTENDS_URL = '/api/pleroma/admin/frontends'
  112. const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/install'
  113. const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
  114. const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
  115. const PLEROMA_EMOJI_PACKS_URL = (page, pageSize) => `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}`
  116. const PLEROMA_EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}`
  117. const PLEROMA_EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download'
  118. const PLEROMA_EMOJI_PACKS_LS_REMOTE_URL =
  119. (url, page, pageSize) => `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}`
  120. const PLEROMA_EMOJI_UPDATE_FILE_URL = (name) => `/api/v1/pleroma/emoji/packs/files?name=${name}`
  121. const oldfetch = window.fetch
  122. const fetch = (url, options) => {
  123. options = options || {}
  124. const baseUrl = ''
  125. const fullUrl = baseUrl + url
  126. options.credentials = 'same-origin'
  127. return oldfetch(fullUrl, options)
  128. }
  129. const promisedRequest = ({ method, url, params, payload, credentials, headers = {} }) => {
  130. const options = {
  131. method,
  132. headers: {
  133. Accept: 'application/json',
  134. 'Content-Type': 'application/json',
  135. ...headers
  136. }
  137. }
  138. if (params) {
  139. url += '?' + Object.entries(params)
  140. .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
  141. .join('&')
  142. }
  143. if (payload) {
  144. options.body = JSON.stringify(payload)
  145. }
  146. if (credentials) {
  147. options.headers = {
  148. ...options.headers,
  149. ...authHeaders(credentials)
  150. }
  151. }
  152. return fetch(url, options)
  153. .then((response) => {
  154. return new Promise((resolve, reject) => response.json()
  155. .then((json) => {
  156. if (!response.ok) {
  157. return reject(new StatusCodeError(response.status, json, { url, options }, response))
  158. }
  159. return resolve(json)
  160. })
  161. .catch((error) => {
  162. return reject(new StatusCodeError(response.status, error, { url, options }, response))
  163. })
  164. )
  165. })
  166. }
  167. const updateNotificationSettings = ({ credentials, settings }) => {
  168. const form = new FormData()
  169. each(settings, (value, key) => {
  170. form.append(key, value)
  171. })
  172. return fetch(`${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, {
  173. headers: authHeaders(credentials),
  174. method: 'PUT',
  175. body: form
  176. }).then((data) => data.json())
  177. }
  178. const updateProfileImages = ({ credentials, avatar = null, avatarName = null, banner = null, background = null }) => {
  179. const form = new FormData()
  180. if (avatar !== null) {
  181. if (avatarName !== null) {
  182. form.append('avatar', avatar, avatarName)
  183. } else {
  184. form.append('avatar', avatar)
  185. }
  186. }
  187. if (banner !== null) form.append('header', banner)
  188. if (background !== null) form.append('pleroma_background_image', background)
  189. return fetch(MASTODON_PROFILE_UPDATE_URL, {
  190. headers: authHeaders(credentials),
  191. method: 'PATCH',
  192. body: form
  193. })
  194. .then((data) => data.json())
  195. .then((data) => {
  196. if (data.error) {
  197. throw new Error(data.error)
  198. }
  199. return parseUser(data)
  200. })
  201. }
  202. const updateProfile = ({ credentials, params }) => {
  203. return promisedRequest({
  204. url: MASTODON_PROFILE_UPDATE_URL,
  205. method: 'PATCH',
  206. payload: params,
  207. credentials
  208. }).then((data) => parseUser(data))
  209. }
  210. // Params needed:
  211. // nickname
  212. // email
  213. // fullname
  214. // password
  215. // password_confirm
  216. //
  217. // Optional
  218. // bio
  219. // homepage
  220. // location
  221. // token
  222. // language
  223. const register = ({ params, credentials }) => {
  224. const { nickname, ...rest } = params
  225. return fetch(MASTODON_REGISTRATION_URL, {
  226. method: 'POST',
  227. headers: {
  228. ...authHeaders(credentials),
  229. 'Content-Type': 'application/json'
  230. },
  231. body: JSON.stringify({
  232. nickname,
  233. locale: 'en_US',
  234. agreement: true,
  235. ...rest
  236. })
  237. })
  238. .then((response) => {
  239. if (response.ok) {
  240. return response.json()
  241. } else {
  242. return response.json().then((error) => { throw new RegistrationError(error) })
  243. }
  244. })
  245. }
  246. const getCaptcha = () => fetch('/api/pleroma/captcha').then(resp => resp.json())
  247. const authHeaders = (accessToken) => {
  248. if (accessToken) {
  249. return { Authorization: `Bearer ${accessToken}` }
  250. } else {
  251. return { }
  252. }
  253. }
  254. const followUser = ({ id, credentials, ...options }) => {
  255. const url = MASTODON_FOLLOW_URL(id)
  256. const form = {}
  257. if (options.reblogs !== undefined) { form.reblogs = options.reblogs }
  258. if (options.notify !== undefined) { form.notify = options.notify }
  259. return fetch(url, {
  260. body: JSON.stringify(form),
  261. headers: {
  262. ...authHeaders(credentials),
  263. 'Content-Type': 'application/json'
  264. },
  265. method: 'POST'
  266. }).then((data) => data.json())
  267. }
  268. const unfollowUser = ({ id, credentials }) => {
  269. const url = MASTODON_UNFOLLOW_URL(id)
  270. return fetch(url, {
  271. headers: authHeaders(credentials),
  272. method: 'POST'
  273. }).then((data) => data.json())
  274. }
  275. const fetchUserInLists = ({ id, credentials }) => {
  276. const url = MASTODON_USER_IN_LISTS(id)
  277. return fetch(url, {
  278. headers: authHeaders(credentials)
  279. }).then((data) => data.json())
  280. }
  281. const pinOwnStatus = ({ id, credentials }) => {
  282. return promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST' })
  283. .then((data) => parseStatus(data))
  284. }
  285. const unpinOwnStatus = ({ id, credentials }) => {
  286. return promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST' })
  287. .then((data) => parseStatus(data))
  288. }
  289. const muteConversation = ({ id, credentials }) => {
  290. return promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST' })
  291. .then((data) => parseStatus(data))
  292. }
  293. const unmuteConversation = ({ id, credentials }) => {
  294. return promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST' })
  295. .then((data) => parseStatus(data))
  296. }
  297. const blockUser = ({ id, credentials }) => {
  298. return fetch(MASTODON_BLOCK_USER_URL(id), {
  299. headers: authHeaders(credentials),
  300. method: 'POST'
  301. }).then((data) => data.json())
  302. }
  303. const unblockUser = ({ id, credentials }) => {
  304. return fetch(MASTODON_UNBLOCK_USER_URL(id), {
  305. headers: authHeaders(credentials),
  306. method: 'POST'
  307. }).then((data) => data.json())
  308. }
  309. const removeUserFromFollowers = ({ id, credentials }) => {
  310. return fetch(MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), {
  311. headers: authHeaders(credentials),
  312. method: 'POST'
  313. }).then((data) => data.json())
  314. }
  315. const editUserNote = ({ id, credentials, comment }) => {
  316. return promisedRequest({
  317. url: MASTODON_USER_NOTE_URL(id),
  318. credentials,
  319. payload: {
  320. comment
  321. },
  322. method: 'POST'
  323. })
  324. }
  325. const approveUser = ({ id, credentials }) => {
  326. const url = MASTODON_APPROVE_USER_URL(id)
  327. return fetch(url, {
  328. headers: authHeaders(credentials),
  329. method: 'POST'
  330. }).then((data) => data.json())
  331. }
  332. const denyUser = ({ id, credentials }) => {
  333. const url = MASTODON_DENY_USER_URL(id)
  334. return fetch(url, {
  335. headers: authHeaders(credentials),
  336. method: 'POST'
  337. }).then((data) => data.json())
  338. }
  339. const fetchUser = ({ id, credentials }) => {
  340. const url = `${MASTODON_USER_URL}/${id}`
  341. return promisedRequest({ url, credentials })
  342. .then((data) => parseUser(data))
  343. }
  344. const fetchUserByName = ({ name, credentials }) => {
  345. return promisedRequest({
  346. url: MASTODON_USER_LOOKUP_URL,
  347. credentials,
  348. params: { acct: name }
  349. })
  350. .then(data => data.id)
  351. .catch(error => {
  352. if (error && error.statusCode === 404) {
  353. // Either the backend does not support lookup endpoint,
  354. // or there is no user with such name. Fallback and treat name as id.
  355. return name
  356. } else {
  357. throw error
  358. }
  359. })
  360. .then(id => fetchUser({ id, credentials }))
  361. }
  362. const fetchUserRelationship = ({ id, credentials }) => {
  363. const url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`
  364. return fetch(url, { headers: authHeaders(credentials) })
  365. .then((response) => {
  366. return new Promise((resolve, reject) => response.json()
  367. .then((json) => {
  368. if (!response.ok) {
  369. return reject(new StatusCodeError(response.status, json, { url }, response))
  370. }
  371. return resolve(json)
  372. }))
  373. })
  374. }
  375. const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => {
  376. let url = MASTODON_FOLLOWING_URL(id)
  377. const args = [
  378. maxId && `max_id=${maxId}`,
  379. sinceId && `since_id=${sinceId}`,
  380. limit && `limit=${limit}`,
  381. 'with_relationships=true'
  382. ].filter(_ => _).join('&')
  383. url = url + (args ? '?' + args : '')
  384. return fetch(url, { headers: authHeaders(credentials) })
  385. .then((data) => data.json())
  386. .then((data) => data.map(parseUser))
  387. }
  388. const exportFriends = ({ id, credentials }) => {
  389. // eslint-disable-next-line no-async-promise-executor
  390. return new Promise(async (resolve, reject) => {
  391. try {
  392. let friends = []
  393. let more = true
  394. while (more) {
  395. const maxId = friends.length > 0 ? last(friends).id : undefined
  396. const users = await fetchFriends({ id, maxId, credentials })
  397. friends = concat(friends, users)
  398. if (users.length === 0) {
  399. more = false
  400. }
  401. }
  402. resolve(friends)
  403. } catch (err) {
  404. reject(err)
  405. }
  406. })
  407. }
  408. const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
  409. let url = MASTODON_FOLLOWERS_URL(id)
  410. const args = [
  411. maxId && `max_id=${maxId}`,
  412. sinceId && `since_id=${sinceId}`,
  413. limit && `limit=${limit}`,
  414. 'with_relationships=true'
  415. ].filter(_ => _).join('&')
  416. url += args ? '?' + args : ''
  417. return fetch(url, { headers: authHeaders(credentials) })
  418. .then((data) => data.json())
  419. .then((data) => data.map(parseUser))
  420. }
  421. const fetchFollowRequests = ({ credentials }) => {
  422. const url = MASTODON_FOLLOW_REQUESTS_URL
  423. return fetch(url, { headers: authHeaders(credentials) })
  424. .then((data) => data.json())
  425. .then((data) => data.map(parseUser))
  426. }
  427. const fetchLists = ({ credentials }) => {
  428. const url = MASTODON_LISTS_URL
  429. return fetch(url, { headers: authHeaders(credentials) })
  430. .then((data) => data.json())
  431. }
  432. const createList = ({ title, credentials }) => {
  433. const url = MASTODON_LISTS_URL
  434. const headers = authHeaders(credentials)
  435. headers['Content-Type'] = 'application/json'
  436. return fetch(url, {
  437. headers,
  438. method: 'POST',
  439. body: JSON.stringify({ title })
  440. }).then((data) => data.json())
  441. }
  442. const getList = ({ listId, credentials }) => {
  443. const url = MASTODON_LIST_URL(listId)
  444. return fetch(url, { headers: authHeaders(credentials) })
  445. .then((data) => data.json())
  446. }
  447. const updateList = ({ listId, title, credentials }) => {
  448. const url = MASTODON_LIST_URL(listId)
  449. const headers = authHeaders(credentials)
  450. headers['Content-Type'] = 'application/json'
  451. return fetch(url, {
  452. headers,
  453. method: 'PUT',
  454. body: JSON.stringify({ title })
  455. })
  456. }
  457. const getListAccounts = ({ listId, credentials }) => {
  458. const url = MASTODON_LIST_ACCOUNTS_URL(listId)
  459. return fetch(url, { headers: authHeaders(credentials) })
  460. .then((data) => data.json())
  461. .then((data) => data.map(({ id }) => id))
  462. }
  463. const addAccountsToList = ({ listId, accountIds, credentials }) => {
  464. const url = MASTODON_LIST_ACCOUNTS_URL(listId)
  465. const headers = authHeaders(credentials)
  466. headers['Content-Type'] = 'application/json'
  467. return fetch(url, {
  468. headers,
  469. method: 'POST',
  470. body: JSON.stringify({ account_ids: accountIds })
  471. })
  472. }
  473. const removeAccountsFromList = ({ listId, accountIds, credentials }) => {
  474. const url = MASTODON_LIST_ACCOUNTS_URL(listId)
  475. const headers = authHeaders(credentials)
  476. headers['Content-Type'] = 'application/json'
  477. return fetch(url, {
  478. headers,
  479. method: 'DELETE',
  480. body: JSON.stringify({ account_ids: accountIds })
  481. })
  482. }
  483. const deleteList = ({ listId, credentials }) => {
  484. const url = MASTODON_LIST_URL(listId)
  485. return fetch(url, {
  486. method: 'DELETE',
  487. headers: authHeaders(credentials)
  488. })
  489. }
  490. const fetchConversation = ({ id, credentials }) => {
  491. const urlContext = MASTODON_STATUS_CONTEXT_URL(id)
  492. return fetch(urlContext, { headers: authHeaders(credentials) })
  493. .then((data) => {
  494. if (data.ok) {
  495. return data
  496. }
  497. throw new Error('Error fetching timeline', data)
  498. })
  499. .then((data) => data.json())
  500. .then(({ ancestors, descendants }) => ({
  501. ancestors: ancestors.map(parseStatus),
  502. descendants: descendants.map(parseStatus)
  503. }))
  504. }
  505. const fetchStatus = ({ id, credentials }) => {
  506. const url = MASTODON_STATUS_URL(id)
  507. return fetch(url, { headers: authHeaders(credentials) })
  508. .then((data) => {
  509. if (data.ok) {
  510. return data
  511. }
  512. throw new Error('Error fetching timeline', data)
  513. })
  514. .then((data) => data.json())
  515. .then((data) => parseStatus(data))
  516. }
  517. const fetchStatusSource = ({ id, credentials }) => {
  518. const url = MASTODON_STATUS_SOURCE_URL(id)
  519. return fetch(url, { headers: authHeaders(credentials) })
  520. .then((data) => {
  521. if (data.ok) {
  522. return data
  523. }
  524. throw new Error('Error fetching source', data)
  525. })
  526. .then((data) => data.json())
  527. .then((data) => parseSource(data))
  528. }
  529. const fetchStatusHistory = ({ status, credentials }) => {
  530. const url = MASTODON_STATUS_HISTORY_URL(status.id)
  531. return promisedRequest({ url, credentials })
  532. .then((data) => {
  533. data.reverse()
  534. return data.map((item) => {
  535. item.originalStatus = status
  536. return parseStatus(item)
  537. })
  538. })
  539. }
  540. const tagUser = ({ tag, credentials, user }) => {
  541. const screenName = user.screen_name
  542. const form = {
  543. nicknames: [screenName],
  544. tags: [tag]
  545. }
  546. const headers = authHeaders(credentials)
  547. headers['Content-Type'] = 'application/json'
  548. return fetch(TAG_USER_URL, {
  549. method: 'PUT',
  550. headers,
  551. body: JSON.stringify(form)
  552. })
  553. }
  554. const untagUser = ({ tag, credentials, user }) => {
  555. const screenName = user.screen_name
  556. const body = {
  557. nicknames: [screenName],
  558. tags: [tag]
  559. }
  560. const headers = authHeaders(credentials)
  561. headers['Content-Type'] = 'application/json'
  562. return fetch(TAG_USER_URL, {
  563. method: 'DELETE',
  564. headers,
  565. body: JSON.stringify(body)
  566. })
  567. }
  568. const addRight = ({ right, credentials, user }) => {
  569. const screenName = user.screen_name
  570. return fetch(PERMISSION_GROUP_URL(screenName, right), {
  571. method: 'POST',
  572. headers: authHeaders(credentials),
  573. body: {}
  574. })
  575. }
  576. const deleteRight = ({ right, credentials, user }) => {
  577. const screenName = user.screen_name
  578. return fetch(PERMISSION_GROUP_URL(screenName, right), {
  579. method: 'DELETE',
  580. headers: authHeaders(credentials),
  581. body: {}
  582. })
  583. }
  584. const activateUser = ({ credentials, user: { screen_name: nickname } }) => {
  585. return promisedRequest({
  586. url: ACTIVATE_USER_URL,
  587. method: 'PATCH',
  588. credentials,
  589. payload: {
  590. nicknames: [nickname]
  591. }
  592. }).then(response => get(response, 'users.0'))
  593. }
  594. const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => {
  595. return promisedRequest({
  596. url: DEACTIVATE_USER_URL,
  597. method: 'PATCH',
  598. credentials,
  599. payload: {
  600. nicknames: [nickname]
  601. }
  602. }).then(response => get(response, 'users.0'))
  603. }
  604. const deleteUser = ({ credentials, user }) => {
  605. const screenName = user.screen_name
  606. const headers = authHeaders(credentials)
  607. return fetch(`${ADMIN_USERS_URL}?nickname=${screenName}`, {
  608. method: 'DELETE',
  609. headers
  610. })
  611. }
  612. const fetchTimeline = ({
  613. timeline,
  614. credentials,
  615. since = false,
  616. minId = false,
  617. until = false,
  618. userId = false,
  619. listId = false,
  620. statusId = false,
  621. tag = false,
  622. withMuted = false,
  623. replyVisibility = 'all',
  624. includeTypes = [],
  625. bookmarkFolderId = false
  626. }) => {
  627. const timelineUrls = {
  628. public: MASTODON_PUBLIC_TIMELINE,
  629. friends: MASTODON_USER_HOME_TIMELINE_URL,
  630. dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
  631. notifications: MASTODON_USER_NOTIFICATIONS_URL,
  632. publicAndExternal: MASTODON_PUBLIC_TIMELINE,
  633. user: MASTODON_USER_TIMELINE_URL,
  634. media: MASTODON_USER_TIMELINE_URL,
  635. list: MASTODON_LIST_TIMELINE_URL,
  636. favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
  637. publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
  638. tag: MASTODON_TAG_TIMELINE_URL,
  639. bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
  640. quotes: PLEROMA_STATUS_QUOTES_URL
  641. }
  642. const isNotifications = timeline === 'notifications'
  643. const params = []
  644. let url = timelineUrls[timeline]
  645. if (timeline === 'favorites' && userId) {
  646. url = timelineUrls.publicFavorites(userId)
  647. }
  648. if (timeline === 'user' || timeline === 'media') {
  649. url = url(userId)
  650. }
  651. if (timeline === 'list') {
  652. url = url(listId)
  653. }
  654. if (timeline === 'quotes') {
  655. url = url(statusId)
  656. }
  657. if (minId) {
  658. params.push(['min_id', minId])
  659. }
  660. if (since) {
  661. params.push(['since_id', since])
  662. }
  663. if (until) {
  664. params.push(['max_id', until])
  665. }
  666. if (tag) {
  667. url = url(tag)
  668. }
  669. if (timeline === 'media') {
  670. params.push(['only_media', 1])
  671. }
  672. if (timeline === 'public') {
  673. params.push(['local', true])
  674. }
  675. if (timeline === 'public' || timeline === 'publicAndExternal') {
  676. params.push(['only_media', false])
  677. }
  678. if (timeline !== 'favorites' && timeline !== 'bookmarks') {
  679. params.push(['with_muted', withMuted])
  680. }
  681. if (replyVisibility !== 'all') {
  682. params.push(['reply_visibility', replyVisibility])
  683. }
  684. if (includeTypes.length > 0) {
  685. includeTypes.forEach(type => {
  686. params.push(['include_types[]', type])
  687. })
  688. }
  689. if (timeline === 'bookmarks' && bookmarkFolderId) {
  690. params.push(['folder_id', bookmarkFolderId])
  691. }
  692. params.push(['limit', 20])
  693. const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
  694. url += `?${queryString}`
  695. return fetch(url, { headers: authHeaders(credentials) })
  696. .then(async (response) => {
  697. const success = response.ok
  698. const data = await response.json()
  699. if (success && !data.errors) {
  700. const pagination = parseLinkHeaderPagination(response.headers.get('Link'), {
  701. flakeId: timeline !== 'bookmarks' && timeline !== 'notifications'
  702. })
  703. return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination }
  704. } else {
  705. data.errors ||= []
  706. data.status = response.status
  707. data.statusText = response.statusText
  708. return data
  709. }
  710. })
  711. }
  712. const fetchPinnedStatuses = ({ id, credentials }) => {
  713. const url = MASTODON_USER_TIMELINE_URL(id) + '?pinned=true'
  714. return promisedRequest({ url, credentials })
  715. .then((data) => data.map(parseStatus))
  716. }
  717. const verifyCredentials = (user) => {
  718. return fetch(MASTODON_LOGIN_URL, {
  719. headers: authHeaders(user)
  720. })
  721. .then((response) => {
  722. if (response.ok) {
  723. return response.json()
  724. } else {
  725. return {
  726. error: response
  727. }
  728. }
  729. })
  730. .then((data) => data.error ? data : parseUser(data))
  731. }
  732. const favorite = ({ id, credentials }) => {
  733. return promisedRequest({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials })
  734. .then((data) => parseStatus(data))
  735. }
  736. const unfavorite = ({ id, credentials }) => {
  737. return promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials })
  738. .then((data) => parseStatus(data))
  739. }
  740. const retweet = ({ id, credentials }) => {
  741. return promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials })
  742. .then((data) => parseStatus(data))
  743. }
  744. const unretweet = ({ id, credentials }) => {
  745. return promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials })
  746. .then((data) => parseStatus(data))
  747. }
  748. const bookmarkStatus = ({ id, credentials, ...options }) => {
  749. return promisedRequest({
  750. url: MASTODON_BOOKMARK_STATUS_URL(id),
  751. headers: authHeaders(credentials),
  752. method: 'POST',
  753. payload: {
  754. folder_id: options.folder_id
  755. }
  756. })
  757. }
  758. const unbookmarkStatus = ({ id, credentials }) => {
  759. return promisedRequest({
  760. url: MASTODON_UNBOOKMARK_STATUS_URL(id),
  761. headers: authHeaders(credentials),
  762. method: 'POST'
  763. })
  764. }
  765. const postStatus = ({
  766. credentials,
  767. status,
  768. spoilerText,
  769. visibility,
  770. sensitive,
  771. poll,
  772. mediaIds = [],
  773. inReplyToStatusId,
  774. quoteId,
  775. contentType,
  776. preview,
  777. idempotencyKey
  778. }) => {
  779. const form = new FormData()
  780. const pollOptions = poll.options || []
  781. form.append('status', status)
  782. form.append('source', 'Pleroma FE')
  783. if (spoilerText) form.append('spoiler_text', spoilerText)
  784. if (visibility) form.append('visibility', visibility)
  785. if (sensitive) form.append('sensitive', sensitive)
  786. if (contentType) form.append('content_type', contentType)
  787. mediaIds.forEach(val => {
  788. form.append('media_ids[]', val)
  789. })
  790. if (pollOptions.some(option => option !== '')) {
  791. const normalizedPoll = {
  792. expires_in: parseInt(poll.expiresIn, 10),
  793. multiple: poll.multiple
  794. }
  795. Object.keys(normalizedPoll).forEach(key => {
  796. form.append(`poll[${key}]`, normalizedPoll[key])
  797. })
  798. pollOptions.forEach(option => {
  799. form.append('poll[options][]', option)
  800. })
  801. }
  802. if (inReplyToStatusId) {
  803. form.append('in_reply_to_id', inReplyToStatusId)
  804. }
  805. if (quoteId) {
  806. form.append('quote_id', quoteId)
  807. }
  808. if (preview) {
  809. form.append('preview', 'true')
  810. }
  811. const postHeaders = authHeaders(credentials)
  812. if (idempotencyKey) {
  813. postHeaders['idempotency-key'] = idempotencyKey
  814. }
  815. return fetch(MASTODON_POST_STATUS_URL, {
  816. body: form,
  817. method: 'POST',
  818. headers: postHeaders
  819. })
  820. .then((response) => {
  821. return response.json()
  822. })
  823. .then((data) => data.error ? data : parseStatus(data))
  824. }
  825. const editStatus = ({
  826. id,
  827. credentials,
  828. status,
  829. spoilerText,
  830. sensitive,
  831. poll,
  832. mediaIds = [],
  833. contentType
  834. }) => {
  835. const form = new FormData()
  836. const pollOptions = poll.options || []
  837. form.append('status', status)
  838. if (spoilerText) form.append('spoiler_text', spoilerText)
  839. if (sensitive) form.append('sensitive', sensitive)
  840. if (contentType) form.append('content_type', contentType)
  841. mediaIds.forEach(val => {
  842. form.append('media_ids[]', val)
  843. })
  844. if (pollOptions.some(option => option !== '')) {
  845. const normalizedPoll = {
  846. expires_in: parseInt(poll.expiresIn, 10),
  847. multiple: poll.multiple
  848. }
  849. Object.keys(normalizedPoll).forEach(key => {
  850. form.append(`poll[${key}]`, normalizedPoll[key])
  851. })
  852. pollOptions.forEach(option => {
  853. form.append('poll[options][]', option)
  854. })
  855. }
  856. const putHeaders = authHeaders(credentials)
  857. return fetch(MASTODON_STATUS_URL(id), {
  858. body: form,
  859. method: 'PUT',
  860. headers: putHeaders
  861. })
  862. .then((response) => {
  863. return response.json()
  864. })
  865. .then((data) => data.error ? data : parseStatus(data))
  866. }
  867. const deleteStatus = ({ id, credentials }) => {
  868. return promisedRequest({
  869. url: MASTODON_DELETE_URL(id),
  870. credentials,
  871. method: 'DELETE'
  872. })
  873. }
  874. const uploadMedia = ({ formData, credentials }) => {
  875. return fetch(MASTODON_MEDIA_UPLOAD_URL, {
  876. body: formData,
  877. method: 'POST',
  878. headers: authHeaders(credentials)
  879. })
  880. .then((data) => data.json())
  881. .then((data) => parseAttachment(data))
  882. }
  883. const setMediaDescription = ({ id, description, credentials }) => {
  884. return promisedRequest({
  885. url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`,
  886. method: 'PUT',
  887. headers: authHeaders(credentials),
  888. payload: {
  889. description
  890. }
  891. }).then((data) => parseAttachment(data))
  892. }
  893. const importMutes = ({ file, credentials }) => {
  894. const formData = new FormData()
  895. formData.append('list', file)
  896. return fetch(MUTES_IMPORT_URL, {
  897. body: formData,
  898. method: 'POST',
  899. headers: authHeaders(credentials)
  900. })
  901. .then((response) => response.ok)
  902. }
  903. const importBlocks = ({ file, credentials }) => {
  904. const formData = new FormData()
  905. formData.append('list', file)
  906. return fetch(BLOCKS_IMPORT_URL, {
  907. body: formData,
  908. method: 'POST',
  909. headers: authHeaders(credentials)
  910. })
  911. .then((response) => response.ok)
  912. }
  913. const importFollows = ({ file, credentials }) => {
  914. const formData = new FormData()
  915. formData.append('list', file)
  916. return fetch(FOLLOW_IMPORT_URL, {
  917. body: formData,
  918. method: 'POST',
  919. headers: authHeaders(credentials)
  920. })
  921. .then((response) => response.ok)
  922. }
  923. const deleteAccount = ({ credentials, password }) => {
  924. const form = new FormData()
  925. form.append('password', password)
  926. return fetch(DELETE_ACCOUNT_URL, {
  927. body: form,
  928. method: 'POST',
  929. headers: authHeaders(credentials)
  930. })
  931. .then((response) => response.json())
  932. }
  933. const changeEmail = ({ credentials, email, password }) => {
  934. const form = new FormData()
  935. form.append('email', email)
  936. form.append('password', password)
  937. return fetch(CHANGE_EMAIL_URL, {
  938. body: form,
  939. method: 'POST',
  940. headers: authHeaders(credentials)
  941. })
  942. .then((response) => response.json())
  943. }
  944. const moveAccount = ({ credentials, password, targetAccount }) => {
  945. const form = new FormData()
  946. form.append('password', password)
  947. form.append('target_account', targetAccount)
  948. return fetch(MOVE_ACCOUNT_URL, {
  949. body: form,
  950. method: 'POST',
  951. headers: authHeaders(credentials)
  952. })
  953. .then((response) => response.json())
  954. }
  955. const addAlias = ({ credentials, alias }) => {
  956. return promisedRequest({
  957. url: ALIASES_URL,
  958. method: 'PUT',
  959. credentials,
  960. payload: { alias }
  961. })
  962. }
  963. const deleteAlias = ({ credentials, alias }) => {
  964. return promisedRequest({
  965. url: ALIASES_URL,
  966. method: 'DELETE',
  967. credentials,
  968. payload: { alias }
  969. })
  970. }
  971. const listAliases = ({ credentials }) => {
  972. return promisedRequest({
  973. url: ALIASES_URL,
  974. method: 'GET',
  975. credentials,
  976. params: {
  977. _cacheBooster: (new Date()).getTime()
  978. }
  979. })
  980. }
  981. const changePassword = ({ credentials, password, newPassword, newPasswordConfirmation }) => {
  982. const form = new FormData()
  983. form.append('password', password)
  984. form.append('new_password', newPassword)
  985. form.append('new_password_confirmation', newPasswordConfirmation)
  986. return fetch(CHANGE_PASSWORD_URL, {
  987. body: form,
  988. method: 'POST',
  989. headers: authHeaders(credentials)
  990. })
  991. .then((response) => response.json())
  992. }
  993. const settingsMFA = ({ credentials }) => {
  994. return fetch(MFA_SETTINGS_URL, {
  995. headers: authHeaders(credentials),
  996. method: 'GET'
  997. }).then((data) => data.json())
  998. }
  999. const mfaDisableOTP = ({ credentials, password }) => {
  1000. const form = new FormData()
  1001. form.append('password', password)
  1002. return fetch(MFA_DISABLE_OTP_URL, {
  1003. body: form,
  1004. method: 'DELETE',
  1005. headers: authHeaders(credentials)
  1006. })
  1007. .then((response) => response.json())
  1008. }
  1009. const mfaConfirmOTP = ({ credentials, password, token }) => {
  1010. const form = new FormData()
  1011. form.append('password', password)
  1012. form.append('code', token)
  1013. return fetch(MFA_CONFIRM_OTP_URL, {
  1014. body: form,
  1015. headers: authHeaders(credentials),
  1016. method: 'POST'
  1017. }).then((data) => data.json())
  1018. }
  1019. const mfaSetupOTP = ({ credentials }) => {
  1020. return fetch(MFA_SETUP_OTP_URL, {
  1021. headers: authHeaders(credentials),
  1022. method: 'GET'
  1023. }).then((data) => data.json())
  1024. }
  1025. const generateMfaBackupCodes = ({ credentials }) => {
  1026. return fetch(MFA_BACKUP_CODES_URL, {
  1027. headers: authHeaders(credentials),
  1028. method: 'GET'
  1029. }).then((data) => data.json())
  1030. }
  1031. const fetchMutes = ({ maxId, credentials }) => {
  1032. const query = new URLSearchParams({ with_relationships: true })
  1033. if (maxId) {
  1034. query.append('max_id', maxId)
  1035. }
  1036. return promisedRequest({ url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`, credentials })
  1037. .then((users) => users.map(parseUser))
  1038. }
  1039. const muteUser = ({ id, expiresIn, credentials }) => {
  1040. const payload = {}
  1041. if (expiresIn) {
  1042. payload.expires_in = expiresIn
  1043. }
  1044. return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST', payload })
  1045. }
  1046. const unmuteUser = ({ id, credentials }) => {
  1047. return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' })
  1048. }
  1049. const fetchBlocks = ({ maxId, credentials }) => {
  1050. const query = new URLSearchParams({ with_relationships: true })
  1051. if (maxId) {
  1052. query.append('max_id', maxId)
  1053. }
  1054. return promisedRequest({ url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`, credentials })
  1055. .then((users) => users.map(parseUser))
  1056. }
  1057. const addBackup = ({ credentials }) => {
  1058. return promisedRequest({
  1059. url: PLEROMA_BACKUP_URL,
  1060. method: 'POST',
  1061. credentials
  1062. })
  1063. }
  1064. const listBackups = ({ credentials }) => {
  1065. return promisedRequest({
  1066. url: PLEROMA_BACKUP_URL,
  1067. method: 'GET',
  1068. credentials,
  1069. params: {
  1070. _cacheBooster: (new Date()).getTime()
  1071. }
  1072. })
  1073. }
  1074. const fetchOAuthTokens = ({ credentials }) => {
  1075. const url = '/api/oauth_tokens.json'
  1076. return fetch(url, {
  1077. headers: authHeaders(credentials)
  1078. }).then((data) => {
  1079. if (data.ok) {
  1080. return data.json()
  1081. }
  1082. throw new Error('Error fetching auth tokens', data)
  1083. })
  1084. }
  1085. const revokeOAuthToken = ({ id, credentials }) => {
  1086. const url = `/api/oauth_tokens/${id}`
  1087. return fetch(url, {
  1088. headers: authHeaders(credentials),
  1089. method: 'DELETE'
  1090. })
  1091. }
  1092. const suggestions = ({ credentials }) => {
  1093. return fetch(SUGGESTIONS_URL, {
  1094. headers: authHeaders(credentials)
  1095. }).then((data) => data.json())
  1096. }
  1097. const markNotificationsAsSeen = ({ id, credentials, single = false }) => {
  1098. const body = new FormData()
  1099. if (single) {
  1100. body.append('id', id)
  1101. } else {
  1102. body.append('max_id', id)
  1103. }
  1104. return fetch(NOTIFICATION_READ_URL, {
  1105. body,
  1106. headers: authHeaders(credentials),
  1107. method: 'POST'
  1108. }).then((data) => data.json())
  1109. }
  1110. const vote = ({ pollId, choices, credentials }) => {
  1111. const form = new FormData()
  1112. form.append('choices', choices)
  1113. return promisedRequest({
  1114. url: MASTODON_VOTE_URL(encodeURIComponent(pollId)),
  1115. method: 'POST',
  1116. credentials,
  1117. payload: {
  1118. choices
  1119. }
  1120. })
  1121. }
  1122. const fetchPoll = ({ pollId, credentials }) => {
  1123. return promisedRequest(
  1124. {
  1125. url: MASTODON_POLL_URL(encodeURIComponent(pollId)),
  1126. method: 'GET',
  1127. credentials
  1128. }
  1129. )
  1130. }
  1131. const fetchFavoritedByUsers = ({ id, credentials }) => {
  1132. return promisedRequest({
  1133. url: MASTODON_STATUS_FAVORITEDBY_URL(id),
  1134. method: 'GET',
  1135. credentials
  1136. }).then((users) => users.map(parseUser))
  1137. }
  1138. const fetchRebloggedByUsers = ({ id, credentials }) => {
  1139. return promisedRequest({
  1140. url: MASTODON_STATUS_REBLOGGEDBY_URL(id),
  1141. method: 'GET',
  1142. credentials
  1143. }).then((users) => users.map(parseUser))
  1144. }
  1145. const fetchEmojiReactions = ({ id, credentials }) => {
  1146. return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials })
  1147. .then((reactions) => reactions.map(r => {
  1148. r.accounts = r.accounts.map(parseUser)
  1149. return r
  1150. }))
  1151. }
  1152. const reactWithEmoji = ({ id, emoji, credentials }) => {
  1153. return promisedRequest({
  1154. url: PLEROMA_EMOJI_REACT_URL(id, emoji),
  1155. method: 'PUT',
  1156. credentials
  1157. }).then(parseStatus)
  1158. }
  1159. const unreactWithEmoji = ({ id, emoji, credentials }) => {
  1160. return promisedRequest({
  1161. url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
  1162. method: 'DELETE',
  1163. credentials
  1164. }).then(parseStatus)
  1165. }
  1166. const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
  1167. return promisedRequest({
  1168. url: MASTODON_REPORT_USER_URL,
  1169. method: 'POST',
  1170. payload: {
  1171. account_id: userId,
  1172. status_ids: statusIds,
  1173. comment,
  1174. forward
  1175. },
  1176. credentials
  1177. })
  1178. }
  1179. const searchUsers = ({ credentials, query }) => {
  1180. return promisedRequest({
  1181. url: MASTODON_USER_SEARCH_URL,
  1182. params: {
  1183. q: query,
  1184. resolve: true
  1185. },
  1186. credentials
  1187. })
  1188. .then((data) => data.map(parseUser))
  1189. }
  1190. const search2 = ({ credentials, q, resolve, limit, offset, following, type }) => {
  1191. let url = MASTODON_SEARCH_2
  1192. const params = []
  1193. if (q) {
  1194. params.push(['q', encodeURIComponent(q)])
  1195. }
  1196. if (resolve) {
  1197. params.push(['resolve', resolve])
  1198. }
  1199. if (limit) {
  1200. params.push(['limit', limit])
  1201. }
  1202. if (offset) {
  1203. params.push(['offset', offset])
  1204. }
  1205. if (following) {
  1206. params.push(['following', true])
  1207. }
  1208. if (type) {
  1209. params.push(['following', type])
  1210. }
  1211. params.push(['with_relationships', true])
  1212. const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
  1213. url += `?${queryString}`
  1214. return fetch(url, { headers: authHeaders(credentials) })
  1215. .then((data) => {
  1216. if (data.ok) {
  1217. return data
  1218. }
  1219. throw new Error('Error fetching search result', data)
  1220. })
  1221. .then((data) => { return data.json() })
  1222. .then((data) => {
  1223. data.accounts = data.accounts.slice(0, limit).map(u => parseUser(u))
  1224. data.statuses = data.statuses.slice(0, limit).map(s => parseStatus(s))
  1225. return data
  1226. })
  1227. }
  1228. const fetchKnownDomains = ({ credentials }) => {
  1229. return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
  1230. }
  1231. const fetchDomainMutes = ({ credentials }) => {
  1232. return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
  1233. }
  1234. const muteDomain = ({ domain, credentials }) => {
  1235. return promisedRequest({
  1236. url: MASTODON_DOMAIN_BLOCKS_URL,
  1237. method: 'POST',
  1238. payload: { domain },
  1239. credentials
  1240. })
  1241. }
  1242. const unmuteDomain = ({ domain, credentials }) => {
  1243. return promisedRequest({
  1244. url: MASTODON_DOMAIN_BLOCKS_URL,
  1245. method: 'DELETE',
  1246. payload: { domain },
  1247. credentials
  1248. })
  1249. }
  1250. const dismissNotification = ({ credentials, id }) => {
  1251. return promisedRequest({
  1252. url: MASTODON_DISMISS_NOTIFICATION_URL(id),
  1253. method: 'POST',
  1254. payload: { id },
  1255. credentials
  1256. })
  1257. }
  1258. const adminFetchAnnouncements = ({ credentials }) => {
  1259. return promisedRequest({ url: PLEROMA_ANNOUNCEMENTS_URL, credentials })
  1260. }
  1261. const fetchAnnouncements = ({ credentials }) => {
  1262. return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials })
  1263. }
  1264. const dismissAnnouncement = ({ id, credentials }) => {
  1265. return promisedRequest({
  1266. url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id),
  1267. credentials,
  1268. method: 'POST'
  1269. })
  1270. }
  1271. const announcementToPayload = ({ content, startsAt, endsAt, allDay }) => {
  1272. const payload = { content }
  1273. if (typeof startsAt !== 'undefined') {
  1274. payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null
  1275. }
  1276. if (typeof endsAt !== 'undefined') {
  1277. payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null
  1278. }
  1279. if (typeof allDay !== 'undefined') {
  1280. payload.all_day = allDay
  1281. }
  1282. return payload
  1283. }
  1284. const postAnnouncement = ({ credentials, content, startsAt, endsAt, allDay }) => {
  1285. return promisedRequest({
  1286. url: PLEROMA_POST_ANNOUNCEMENT_URL,
  1287. credentials,
  1288. method: 'POST',
  1289. payload: announcementToPayload({ content, startsAt, endsAt, allDay })
  1290. })
  1291. }
  1292. const editAnnouncement = ({ id, credentials, content, startsAt, endsAt, allDay }) => {
  1293. return promisedRequest({
  1294. url: PLEROMA_EDIT_ANNOUNCEMENT_URL(id),
  1295. credentials,
  1296. method: 'PATCH',
  1297. payload: announcementToPayload({ content, startsAt, endsAt, allDay })
  1298. })
  1299. }
  1300. const deleteAnnouncement = ({ id, credentials }) => {
  1301. return promisedRequest({
  1302. url: PLEROMA_DELETE_ANNOUNCEMENT_URL(id),
  1303. credentials,
  1304. method: 'DELETE'
  1305. })
  1306. }
  1307. export const getMastodonSocketURI = ({ credentials, stream, args = {} }, base) => {
  1308. const url = new URL(MASTODON_STREAMING, base)
  1309. if (credentials) {
  1310. url.searchParams.append('access_token', credentials)
  1311. }
  1312. if (stream) {
  1313. url.searchParams.append('stream', stream)
  1314. }
  1315. Object.entries(args).forEach(([key, val]) => {
  1316. url.searchParams.append(key, val)
  1317. })
  1318. return url
  1319. }
  1320. const MASTODON_STREAMING_EVENTS = new Set([
  1321. 'update',
  1322. 'notification',
  1323. 'delete',
  1324. 'filters_changed',
  1325. 'status.update'
  1326. ])
  1327. const PLEROMA_STREAMING_EVENTS = new Set([
  1328. 'pleroma:chat_update',
  1329. 'pleroma:respond'
  1330. ])
  1331. // A thin wrapper around WebSocket API that allows adding a pre-processor to it
  1332. // Uses EventTarget and a CustomEvent to proxy events
  1333. export const ProcessedWS = ({
  1334. url,
  1335. preprocessor = handleMastoWS,
  1336. id = 'Unknown',
  1337. credentials
  1338. }) => {
  1339. const eventTarget = new EventTarget()
  1340. const socket = new WebSocket(url)
  1341. if (!socket) throw new Error(`Failed to create socket ${id}`)
  1342. const proxy = (original, eventName, processor = a => a) => {
  1343. original.addEventListener(eventName, (eventData) => {
  1344. eventTarget.dispatchEvent(new CustomEvent(
  1345. eventName,
  1346. { detail: processor(eventData) }
  1347. ))
  1348. })
  1349. }
  1350. socket.addEventListener('open', (wsEvent) => {
  1351. console.debug(`[WS][${id}] Socket connected`, wsEvent)
  1352. if (credentials) {
  1353. socket.send(JSON.stringify({
  1354. type: 'pleroma:authenticate',
  1355. token: credentials
  1356. }))
  1357. }
  1358. })
  1359. socket.addEventListener('error', (wsEvent) => {
  1360. console.debug(`[WS][${id}] Socket errored`, wsEvent)
  1361. })
  1362. socket.addEventListener('close', (wsEvent) => {
  1363. console.debug(
  1364. `[WS][${id}] Socket disconnected with code ${wsEvent.code}`,
  1365. wsEvent
  1366. )
  1367. })
  1368. // Commented code reason: very spammy, uncomment to enable message debug logging
  1369. /*
  1370. socket.addEventListener('message', (wsEvent) => {
  1371. console.debug(
  1372. `[WS][${id}] Message received`,
  1373. wsEvent
  1374. )
  1375. })
  1376. /**/
  1377. const onAuthenticated = () => {
  1378. eventTarget.dispatchEvent(new CustomEvent('pleroma:authenticated'))
  1379. }
  1380. proxy(socket, 'open')
  1381. proxy(socket, 'close')
  1382. proxy(socket, 'message', (event) => preprocessor(event, { onAuthenticated }))
  1383. proxy(socket, 'error')
  1384. // 1000 = Normal Closure
  1385. eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
  1386. eventTarget.getState = () => socket.readyState
  1387. eventTarget.subscribe = (stream, args = {}) => {
  1388. console.debug(
  1389. `[WS][${id}] Subscribing to stream ${stream} with args`,
  1390. args
  1391. )
  1392. socket.send(JSON.stringify({
  1393. type: 'subscribe',
  1394. stream,
  1395. ...args
  1396. }))
  1397. }
  1398. eventTarget.unsubscribe = (stream, args = {}) => {
  1399. console.debug(
  1400. `[WS][${id}] Unsubscribing from stream ${stream} with args`,
  1401. args
  1402. )
  1403. socket.send(JSON.stringify({
  1404. type: 'unsubscribe',
  1405. stream,
  1406. ...args
  1407. }))
  1408. }
  1409. return eventTarget
  1410. }
  1411. export const handleMastoWS = (wsEvent, {
  1412. onAuthenticated = () => {}
  1413. } = {}) => {
  1414. const { data } = wsEvent
  1415. if (!data) return
  1416. const parsedEvent = JSON.parse(data)
  1417. const { event, payload } = parsedEvent
  1418. if (MASTODON_STREAMING_EVENTS.has(event) || PLEROMA_STREAMING_EVENTS.has(event)) {
  1419. // MastoBE and PleromaBE both send payload for delete as a PLAIN string
  1420. if (event === 'delete') {
  1421. return { event, id: payload }
  1422. }
  1423. const data = payload ? JSON.parse(payload) : null
  1424. if (event === 'pleroma:respond') {
  1425. if (data.type === 'pleroma:authenticate') {
  1426. if (data.result === 'success') {
  1427. console.debug('[WS] Successfully authenticated')
  1428. onAuthenticated()
  1429. } else {
  1430. console.error('[WS] Unable to authenticate:', data.error)
  1431. wsEvent.target.close()
  1432. }
  1433. }
  1434. return null
  1435. } else if (event === 'update') {
  1436. return { event, status: parseStatus(data) }
  1437. } else if (event === 'status.update') {
  1438. return { event, status: parseStatus(data) }
  1439. } else if (event === 'notification') {
  1440. return { event, notification: parseNotification(data) }
  1441. } else if (event === 'pleroma:chat_update') {
  1442. return { event, chatUpdate: parseChat(data) }
  1443. }
  1444. } else {
  1445. console.warn('Unknown event', wsEvent)
  1446. return null
  1447. }
  1448. }
  1449. export const WSConnectionStatus = Object.freeze({
  1450. JOINED: 1,
  1451. CLOSED: 2,
  1452. ERROR: 3,
  1453. DISABLED: 4,
  1454. STARTING: 5,
  1455. STARTING_INITIAL: 6
  1456. })
  1457. const chats = ({ credentials }) => {
  1458. return fetch(PLEROMA_CHATS_URL, { headers: authHeaders(credentials) })
  1459. .then((data) => data.json())
  1460. .then((data) => {
  1461. return { chats: data.map(parseChat).filter(c => c) }
  1462. })
  1463. }
  1464. const getOrCreateChat = ({ accountId, credentials }) => {
  1465. return promisedRequest({
  1466. url: PLEROMA_CHAT_URL(accountId),
  1467. method: 'POST',
  1468. credentials
  1469. })
  1470. }
  1471. const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => {
  1472. let url = PLEROMA_CHAT_MESSAGES_URL(id)
  1473. const args = [
  1474. maxId && `max_id=${maxId}`,
  1475. sinceId && `since_id=${sinceId}`,
  1476. limit && `limit=${limit}`
  1477. ].filter(_ => _).join('&')
  1478. url = url + (args ? '?' + args : '')
  1479. return promisedRequest({
  1480. url,
  1481. method: 'GET',
  1482. credentials
  1483. })
  1484. }
  1485. const sendChatMessage = ({ id, content, mediaId = null, idempotencyKey, credentials }) => {
  1486. const payload = {
  1487. content
  1488. }
  1489. if (mediaId) {
  1490. payload.media_id = mediaId
  1491. }
  1492. const headers = {}
  1493. if (idempotencyKey) {
  1494. headers['idempotency-key'] = idempotencyKey
  1495. }
  1496. return promisedRequest({
  1497. url: PLEROMA_CHAT_MESSAGES_URL(id),
  1498. method: 'POST',
  1499. payload,
  1500. credentials,
  1501. headers
  1502. })
  1503. }
  1504. const readChat = ({ id, lastReadId, credentials }) => {
  1505. return promisedRequest({
  1506. url: PLEROMA_CHAT_READ_URL(id),
  1507. method: 'POST',
  1508. payload: {
  1509. last_read_id: lastReadId
  1510. },
  1511. credentials
  1512. })
  1513. }
  1514. const deleteChatMessage = ({ chatId, messageId, credentials }) => {
  1515. return promisedRequest({
  1516. url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId),
  1517. method: 'DELETE',
  1518. credentials
  1519. })
  1520. }
  1521. const setReportState = ({ id, state, credentials }) => {
  1522. // TODO: Can't use promisedRequest because on OK this does not return json
  1523. // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322
  1524. return fetch(PLEROMA_ADMIN_REPORTS, {
  1525. headers: {
  1526. ...authHeaders(credentials),
  1527. Accept: 'application/json',
  1528. 'Content-Type': 'application/json'
  1529. },
  1530. method: 'PATCH',
  1531. body: JSON.stringify({
  1532. reports: [{
  1533. id,
  1534. state
  1535. }]
  1536. })
  1537. })
  1538. .then(data => {
  1539. if (data.status >= 500) {
  1540. throw Error(data.statusText)
  1541. } else if (data.status >= 400) {
  1542. return data.json()
  1543. }
  1544. return data
  1545. })
  1546. .then(data => {
  1547. if (data.errors) {
  1548. throw Error(data.errors[0].message)
  1549. }
  1550. })
  1551. }
  1552. // ADMIN STUFF // EXPERIMENTAL
  1553. const fetchInstanceDBConfig = ({ credentials }) => {
  1554. return fetch(PLEROMA_ADMIN_CONFIG_URL, {
  1555. headers: authHeaders(credentials)
  1556. })
  1557. .then((response) => {
  1558. if (response.ok) {
  1559. return response.json()
  1560. } else {
  1561. return {
  1562. error: response
  1563. }
  1564. }
  1565. })
  1566. }
  1567. const fetchInstanceConfigDescriptions = ({ credentials }) => {
  1568. return fetch(PLEROMA_ADMIN_DESCRIPTIONS_URL, {
  1569. headers: authHeaders(credentials)
  1570. })
  1571. .then((response) => {
  1572. if (response.ok) {
  1573. return response.json()
  1574. } else {
  1575. return {
  1576. error: response
  1577. }
  1578. }
  1579. })
  1580. }
  1581. const fetchAvailableFrontends = ({ credentials }) => {
  1582. return fetch(PLEROMA_ADMIN_FRONTENDS_URL, {
  1583. headers: authHeaders(credentials)
  1584. })
  1585. .then((response) => {
  1586. if (response.ok) {
  1587. return response.json()
  1588. } else {
  1589. return {
  1590. error: response
  1591. }
  1592. }
  1593. })
  1594. }
  1595. const pushInstanceDBConfig = ({ credentials, payload }) => {
  1596. return fetch(PLEROMA_ADMIN_CONFIG_URL, {
  1597. headers: {
  1598. Accept: 'application/json',
  1599. 'Content-Type': 'application/json',
  1600. ...authHeaders(credentials)
  1601. },
  1602. method: 'POST',
  1603. body: JSON.stringify(payload)
  1604. })
  1605. .then((response) => {
  1606. if (response.ok) {
  1607. return response.json()
  1608. } else {
  1609. return {
  1610. error: response
  1611. }
  1612. }
  1613. })
  1614. }
  1615. const installFrontend = ({ credentials, payload }) => {
  1616. return fetch(PLEROMA_ADMIN_FRONTENDS_INSTALL_URL, {
  1617. headers: {
  1618. Accept: 'application/json',
  1619. 'Content-Type': 'application/json',
  1620. ...authHeaders(credentials)
  1621. },
  1622. method: 'POST',
  1623. body: JSON.stringify(payload)
  1624. })
  1625. .then((response) => {
  1626. if (response.ok) {
  1627. return response.json()
  1628. } else {
  1629. return {
  1630. error: response
  1631. }
  1632. }
  1633. })
  1634. }
  1635. const fetchScrobbles = ({ accountId, limit = 1 }) => {
  1636. let url = PLEROMA_SCROBBLES_URL(accountId)
  1637. const params = [['limit', limit]]
  1638. const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
  1639. url += `?${queryString}`
  1640. return fetch(url, {})
  1641. .then((response) => {
  1642. if (response.ok) {
  1643. return response.json()
  1644. } else {
  1645. return {
  1646. error: response
  1647. }
  1648. }
  1649. })
  1650. }
  1651. const deleteEmojiPack = ({ name }) => {
  1652. return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'DELETE' })
  1653. }
  1654. const reloadEmoji = () => {
  1655. return fetch(PLEROMA_EMOJI_RELOAD_URL, { method: 'POST' })
  1656. }
  1657. const importEmojiFromFS = () => {
  1658. return fetch(PLEROMA_EMOJI_IMPORT_FS_URL)
  1659. }
  1660. const createEmojiPack = ({ name }) => {
  1661. return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'POST' })
  1662. }
  1663. const listEmojiPacks = ({ page, pageSize }) => {
  1664. return fetch(PLEROMA_EMOJI_PACKS_URL(page, pageSize))
  1665. }
  1666. const listRemoteEmojiPacks = ({ instance, page, pageSize }) => {
  1667. if (!instance.startsWith('http')) {
  1668. instance = 'https://' + instance
  1669. }
  1670. return fetch(
  1671. PLEROMA_EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize),
  1672. {
  1673. headers: { 'Content-Type': 'application/json' }
  1674. }
  1675. )
  1676. }
  1677. const downloadRemoteEmojiPack = ({ instance, packName, as }) => {
  1678. return fetch(
  1679. PLEROMA_EMOJI_PACKS_DL_REMOTE_URL,
  1680. {
  1681. method: 'POST',
  1682. headers: { 'Content-Type': 'application/json' },
  1683. body: JSON.stringify({
  1684. url: instance, name: packName, as
  1685. })
  1686. }
  1687. )
  1688. }
  1689. const saveEmojiPackMetadata = ({ name, newData }) => {
  1690. return fetch(
  1691. PLEROMA_EMOJI_PACK_URL(name),
  1692. {
  1693. method: 'PATCH',
  1694. headers: { 'Content-Type': 'application/json' },
  1695. body: JSON.stringify({ metadata: newData })
  1696. }
  1697. )
  1698. }
  1699. const addNewEmojiFile = ({ packName, file, shortcode, filename }) => {
  1700. const data = new FormData()
  1701. if (filename.trim() !== '') { data.set('filename', filename) }
  1702. if (shortcode.trim() !== '') { data.set('shortcode', shortcode) }
  1703. data.set('file', file)
  1704. return fetch(
  1705. PLEROMA_EMOJI_UPDATE_FILE_URL(packName),
  1706. { method: 'POST', body: data }
  1707. )
  1708. }
  1709. const updateEmojiFile = ({ packName, shortcode, newShortcode, newFilename, force }) => {
  1710. return fetch(
  1711. PLEROMA_EMOJI_UPDATE_FILE_URL(packName),
  1712. {
  1713. method: 'PATCH',
  1714. headers: { 'Content-Type': 'application/json' },
  1715. body: JSON.stringify({ shortcode, new_shortcode: newShortcode, new_filename: newFilename, force })
  1716. }
  1717. )
  1718. }
  1719. const deleteEmojiFile = ({ packName, shortcode }) => {
  1720. return fetch(`${PLEROMA_EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`, { method: 'DELETE' })
  1721. }
  1722. const fetchBookmarkFolders = ({ credentials }) => {
  1723. const url = PLEROMA_BOOKMARK_FOLDERS_URL
  1724. return fetch(url, { headers: authHeaders(credentials) })
  1725. .then((data) => data.json())
  1726. }
  1727. const createBookmarkFolder = ({ name, emoji, credentials }) => {
  1728. const url = PLEROMA_BOOKMARK_FOLDERS_URL
  1729. const headers = authHeaders(credentials)
  1730. headers['Content-Type'] = 'application/json'
  1731. return fetch(url, {
  1732. headers,
  1733. method: 'POST',
  1734. body: JSON.stringify({ name, emoji })
  1735. }).then((data) => data.json())
  1736. }
  1737. const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => {
  1738. const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId)
  1739. const headers = authHeaders(credentials)
  1740. headers['Content-Type'] = 'application/json'
  1741. return fetch(url, {
  1742. headers,
  1743. method: 'PATCH',
  1744. body: JSON.stringify({ name, emoji })
  1745. }).then((data) => data.json())
  1746. }
  1747. const deleteBookmarkFolder = ({ folderId, credentials }) => {
  1748. const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId)
  1749. return fetch(url, {
  1750. method: 'DELETE',
  1751. headers: authHeaders(credentials)
  1752. })
  1753. }
  1754. const apiService = {
  1755. verifyCredentials,
  1756. fetchTimeline,
  1757. fetchPinnedStatuses,
  1758. fetchConversation,
  1759. fetchStatus,
  1760. fetchStatusSource,
  1761. fetchStatusHistory,
  1762. fetchFriends,
  1763. exportFriends,
  1764. fetchFollowers,
  1765. followUser,
  1766. unfollowUser,
  1767. pinOwnStatus,
  1768. unpinOwnStatus,
  1769. muteConversation,
  1770. unmuteConversation,
  1771. blockUser,
  1772. unblockUser,
  1773. removeUserFromFollowers,
  1774. editUserNote,
  1775. fetchUser,
  1776. fetchUserByName,
  1777. fetchUserRelationship,
  1778. favorite,
  1779. unfavorite,
  1780. retweet,
  1781. unretweet,
  1782. bookmarkStatus,
  1783. unbookmarkStatus,
  1784. postStatus,
  1785. editStatus,
  1786. deleteStatus,
  1787. uploadMedia,
  1788. setMediaDescription,
  1789. fetchMutes,
  1790. muteUser,
  1791. unmuteUser,
  1792. fetchBlocks,
  1793. fetchOAuthTokens,
  1794. revokeOAuthToken,
  1795. tagUser,
  1796. untagUser,
  1797. deleteUser,
  1798. addRight,
  1799. deleteRight,
  1800. activateUser,
  1801. deactivateUser,
  1802. register,
  1803. getCaptcha,
  1804. updateProfileImages,
  1805. updateProfile,
  1806. importMutes,
  1807. importBlocks,
  1808. importFollows,
  1809. deleteAccount,
  1810. changeEmail,
  1811. moveAccount,
  1812. addAlias,
  1813. deleteAlias,
  1814. listAliases,
  1815. changePassword,
  1816. settingsMFA,
  1817. mfaDisableOTP,
  1818. generateMfaBackupCodes,
  1819. mfaSetupOTP,
  1820. mfaConfirmOTP,
  1821. addBackup,
  1822. listBackups,
  1823. fetchFollowRequests,
  1824. fetchLists,
  1825. createList,
  1826. getList,
  1827. updateList,
  1828. getListAccounts,
  1829. addAccountsToList,
  1830. removeAccountsFromList,
  1831. deleteList,
  1832. approveUser,
  1833. denyUser,
  1834. suggestions,
  1835. markNotificationsAsSeen,
  1836. dismissNotification,
  1837. vote,
  1838. fetchPoll,
  1839. fetchFavoritedByUsers,
  1840. fetchRebloggedByUsers,
  1841. fetchEmojiReactions,
  1842. reactWithEmoji,
  1843. unreactWithEmoji,
  1844. reportUser,
  1845. updateNotificationSettings,
  1846. search2,
  1847. searchUsers,
  1848. fetchKnownDomains,
  1849. fetchDomainMutes,
  1850. muteDomain,
  1851. unmuteDomain,
  1852. chats,
  1853. getOrCreateChat,
  1854. chatMessages,
  1855. sendChatMessage,
  1856. readChat,
  1857. deleteChatMessage,
  1858. setReportState,
  1859. fetchUserInLists,
  1860. fetchAnnouncements,
  1861. dismissAnnouncement,
  1862. postAnnouncement,
  1863. editAnnouncement,
  1864. deleteAnnouncement,
  1865. fetchScrobbles,
  1866. adminFetchAnnouncements,
  1867. fetchInstanceDBConfig,
  1868. fetchInstanceConfigDescriptions,
  1869. fetchAvailableFrontends,
  1870. pushInstanceDBConfig,
  1871. installFrontend,
  1872. importEmojiFromFS,
  1873. reloadEmoji,
  1874. listEmojiPacks,
  1875. createEmojiPack,
  1876. deleteEmojiPack,
  1877. saveEmojiPackMetadata,
  1878. addNewEmojiFile,
  1879. updateEmojiFile,
  1880. deleteEmojiFile,
  1881. listRemoteEmojiPacks,
  1882. downloadRemoteEmojiPack,
  1883. fetchBookmarkFolders,
  1884. createBookmarkFolder,
  1885. updateBookmarkFolder,
  1886. deleteBookmarkFolder
  1887. }
  1888. export default apiService