diff options
Diffstat (limited to 'src/services/api/api.service.js')
| -rw-r--r-- | src/services/api/api.service.js | 857 |
1 files changed, 610 insertions, 247 deletions
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026..887d7d7a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,53 +1,75 @@ +import { each, map, concat, last } from 'lodash' +import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' +import 'whatwg-fetch' +import { RegistrationError, StatusCodeError } from '../errors/errors' + /* eslint-env browser */ -const LOGIN_URL = '/api/account/verify_credentials.json' -const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' -const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing' -const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json' -const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json' -const TAG_TIMELINE_URL = '/api/statusnet/tags/timeline' -const FAVORITE_URL = '/api/favorites/create' -const UNFAVORITE_URL = '/api/favorites/destroy' -const RETWEET_URL = '/api/statuses/retweet' -const UNRETWEET_URL = '/api/statuses/unretweet' -const STATUS_UPDATE_URL = '/api/statuses/update.json' -const STATUS_DELETE_URL = '/api/statuses/destroy' -const STATUS_URL = '/api/statuses/show' -const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' -const CONVERSATION_URL = '/api/statusnet/conversation' -const MENTIONS_URL = '/api/statuses/mentions.json' -const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' -const FOLLOWERS_URL = '/api/statuses/followers.json' -const FRIENDS_URL = '/api/statuses/friends.json' -const BLOCKS_URL = '/api/statuses/blocks.json' -const FOLLOWING_URL = '/api/friendships/create.json' -const UNFOLLOWING_URL = '/api/friendships/destroy.json' -const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json' -const REGISTRATION_URL = '/api/account/register.json' -const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json' -const BG_UPDATE_URL = '/api/qvitter/update_background_image.json' -const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json' -const PROFILE_UPDATE_URL = '/api/account/update_profile.json' -const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' -const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json' -const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' -const BLOCKING_URL = '/api/blocks/create.json' -const UNBLOCKING_URL = '/api/blocks/destroy.json' -const USER_URL = '/api/users/show.json' +const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' -const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests' -const APPROVE_USER_URL = '/api/pleroma/friendships/approve' -const DENY_USER_URL = '/api/pleroma/friendships/deny' +const TAG_USER_URL = '/api/pleroma/admin/users/tag' +const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}` +const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status` +const ADMIN_USERS_URL = '/api/pleroma/admin/users' const SUGGESTIONS_URL = '/api/v1/suggestions' +const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' -const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MFA_SETTINGS_URL = '/api/pleroma/profile/mfa' +const MFA_BACKUP_CODES_URL = '/api/pleroma/profile/mfa/backup_codes' -import { each, map } from 'lodash' -import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' -import 'whatwg-fetch' -import { StatusCodeError } from '../errors/errors' +const MFA_SETUP_OTP_URL = '/api/pleroma/profile/mfa/setup/totp' +const MFA_CONFIRM_OTP_URL = '/api/pleroma/profile/mfa/confirm/totp' +const MFA_DISABLE_OTP_URL = '/api/pleroma/profile/mfa/totp' + +const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' +const MASTODON_REGISTRATION_URL = '/api/v1/accounts' +const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' +const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite` +const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite` +const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog` +const MASTODON_UNRETWEET_URL = id => `/api/v1/statuses/${id}/unreblog` +const MASTODON_DELETE_URL = id => `/api/v1/statuses/${id}` +const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow` +const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow` +const MASTODON_FOLLOWING_URL = id => `/api/v1/accounts/${id}/following` +const MASTODON_FOLLOWERS_URL = id => `/api/v1/accounts/${id}/followers` +const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' +const MASTODON_APPROVE_USER_URL = id => `/api/v1/follow_requests/${id}/authorize` +const MASTODON_DENY_USER_URL = id => `/api/v1/follow_requests/${id}/reject` +const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct' +const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public' +const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' +const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}` +const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` +const MASTODON_USER_URL = '/api/v1/accounts' +const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' +const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` +const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}` +const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/' +const MASTODON_USER_MUTES_URL = '/api/v1/mutes/' +const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block` +const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock` +const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute` +const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute` +const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe` +const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe` +const MASTODON_POST_STATUS_URL = '/api/v1/statuses' +const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media' +const MASTODON_VOTE_URL = id => `/api/v1/polls/${id}/votes` +const MASTODON_POLL_URL = id => `/api/v1/polls/${id}` +const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by` +const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by` +const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials' +const MASTODON_REPORT_USER_URL = '/api/v1/reports' +const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin` +const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin` +const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` +const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` +const MASTODON_SEARCH_2 = `/api/v2/search` +const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const oldfetch = window.fetch @@ -59,94 +81,96 @@ let fetch = (url, options) => { return oldfetch(fullUrl, options) } -// Params -// cropH -// cropW -// cropX -// cropY -// img (base 64 encodend data url) -const updateAvatar = ({credentials, params}) => { - let url = AVATAR_UPDATE_URL +const promisedRequest = ({ method, url, params, payload, credentials, headers = {} }) => { + const options = { + method, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + ...headers + } + } + if (params) { + url += '?' + Object.entries(params) + .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value)) + .join('&') + } + if (payload) { + options.body = JSON.stringify(payload) + } + if (credentials) { + options.headers = { + ...options.headers, + ...authHeaders(credentials) + } + } + return fetch(url, options) + .then((response) => { + return new Promise((resolve, reject) => response.json() + .then((json) => { + if (!response.ok) { + return reject(new StatusCodeError(response.status, json, { url, options }, response)) + } + return resolve(json) + })) + }) +} +const updateNotificationSettings = ({ credentials, settings }) => { const form = new FormData() - each(params, (value, key) => { - if (value) { - form.append(key, value) - } + each(settings, (value, key) => { + form.append(key, value) }) - return fetch(url, { + return fetch(NOTIFICATION_SETTINGS_URL, { headers: authHeaders(credentials), - method: 'POST', + method: 'PUT', body: form }).then((data) => data.json()) } -const updateBg = ({credentials, params}) => { - let url = BG_UPDATE_URL - +const updateAvatar = ({ credentials, avatar }) => { const form = new FormData() - - each(params, (value, key) => { - if (value) { - form.append(key, value) - } - }) - - return fetch(url, { + form.append('avatar', avatar) + return fetch(MASTODON_PROFILE_UPDATE_URL, { headers: authHeaders(credentials), - method: 'POST', + method: 'PATCH', body: form }).then((data) => data.json()) + .then((data) => parseUser(data)) } -// Params -// height -// width -// offset_left -// offset_top -// banner (base 64 encodend data url) -const updateBanner = ({credentials, params}) => { - let url = BANNER_UPDATE_URL - +const updateBg = ({ credentials, background }) => { const form = new FormData() - - each(params, (value, key) => { - if (value) { - form.append(key, value) - } - }) - - return fetch(url, { + form.append('pleroma_background_image', background) + return fetch(MASTODON_PROFILE_UPDATE_URL, { headers: authHeaders(credentials), - method: 'POST', + method: 'PATCH', body: form - }).then((data) => data.json()) + }) + .then((data) => data.json()) + .then((data) => parseUser(data)) } -// Params -// name -// url -// location -// description -const updateProfile = ({credentials, params}) => { - // Always include these fields, because they might be empty or false - const fields = ['description', 'locked', 'no_rich_text', 'hide_follows', 'hide_followers', 'show_role'] - let url = PROFILE_UPDATE_URL - +const updateBanner = ({ credentials, banner }) => { const form = new FormData() - - each(params, (value, key) => { - if (fields.includes(key) || value) { - form.append(key, value) - } - }) - return fetch(url, { + form.append('header', banner) + return fetch(MASTODON_PROFILE_UPDATE_URL, { headers: authHeaders(credentials), - method: 'POST', + method: 'PATCH', body: form }).then((data) => data.json()) + .then((data) => parseUser(data)) +} + +const updateProfile = ({ credentials, params }) => { + return promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + method: 'PATCH', + payload: params, + credentials + }).then((data) => parseUser(data)) } // Params needed: @@ -161,19 +185,28 @@ const updateProfile = ({credentials, params}) => { // homepage // location // token -const register = (params) => { - const form = new FormData() - - each(params, (value, key) => { - if (value) { - form.append(key, value) - } - }) - - return fetch(REGISTRATION_URL, { +const register = ({ params, credentials }) => { + const { nickname, ...rest } = params + return fetch(MASTODON_REGISTRATION_URL, { method: 'POST', - body: form + headers: { + ...authHeaders(credentials), + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + nickname, + locale: 'en_US', + agreement: true, + ...rest + }) }) + .then((response) => { + if (response.ok) { + return response.json() + } else { + return response.json().then((error) => { throw new RegistrationError(error) }) + } + }) } const getCaptcha = () => fetch('/api/pleroma/captcha').then(resp => resp.json()) @@ -186,64 +219,80 @@ const authHeaders = (accessToken) => { } } -const externalProfile = ({profileUrl, credentials}) => { - let url = `${EXTERNAL_PROFILE_URL}?profileurl=${profileUrl}` +const followUser = ({ id, credentials }) => { + let url = MASTODON_FOLLOW_URL(id) return fetch(url, { headers: authHeaders(credentials), - method: 'GET' + method: 'POST' }).then((data) => data.json()) } -const followUser = ({id, credentials}) => { - let url = `${FOLLOWING_URL}?user_id=${id}` +const unfollowUser = ({ id, credentials }) => { + let url = MASTODON_UNFOLLOW_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' }).then((data) => data.json()) } -const unfollowUser = ({id, credentials}) => { - let url = `${UNFOLLOWING_URL}?user_id=${id}` - return fetch(url, { - headers: authHeaders(credentials), - method: 'POST' - }).then((data) => data.json()) +const pinOwnStatus = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) } -const blockUser = ({id, credentials}) => { - let url = `${BLOCKING_URL}?user_id=${id}` - return fetch(url, { +const unpinOwnStatus = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) +} + +const muteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) +} + +const unmuteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) +} + +const blockUser = ({ id, credentials }) => { + return fetch(MASTODON_BLOCK_USER_URL(id), { headers: authHeaders(credentials), method: 'POST' }).then((data) => data.json()) } -const unblockUser = ({id, credentials}) => { - let url = `${UNBLOCKING_URL}?user_id=${id}` - return fetch(url, { +const unblockUser = ({ id, credentials }) => { + return fetch(MASTODON_UNBLOCK_USER_URL(id), { headers: authHeaders(credentials), method: 'POST' }).then((data) => data.json()) } -const approveUser = ({id, credentials}) => { - let url = `${APPROVE_USER_URL}?user_id=${id}` +const approveUser = ({ id, credentials }) => { + let url = MASTODON_APPROVE_USER_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' }).then((data) => data.json()) } -const denyUser = ({id, credentials}) => { - let url = `${DENY_USER_URL}?user_id=${id}` +const denyUser = ({ id, credentials }) => { + let url = MASTODON_DENY_USER_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' }).then((data) => data.json()) } -const fetchUser = ({id, credentials}) => { - let url = `${USER_URL}?user_id=${id}` +const fetchUser = ({ id, credentials }) => { + let url = `${MASTODON_USER_URL}/${id}` + return promisedRequest({ url, credentials }) + .then((data) => parseUser(data)) +} + +const fetchUserRelationship = ({ id, credentials }) => { + let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` return fetch(url, { headers: authHeaders(credentials) }) .then((response) => { return new Promise((resolve, reject) => response.json() @@ -254,52 +303,66 @@ const fetchUser = ({id, credentials}) => { return resolve(json) })) }) - .then((data) => parseUser(data)) } -const fetchFriends = ({id, page, credentials}) => { - let url = `${FRIENDS_URL}?user_id=${id}` - if (page) { - url = url + `&page=${page}` - } - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(parseUser)) -} +const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { + let url = MASTODON_FOLLOWING_URL(id) + const args = [ + maxId && `max_id=${maxId}`, + sinceId && `since_id=${sinceId}`, + limit && `limit=${limit}` + ].filter(_ => _).join('&') -const exportFriends = ({id, credentials}) => { - let url = `${FRIENDS_URL}?user_id=${id}&all=true` + url = url + (args ? '?' + args : '') return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) .then((data) => data.map(parseUser)) } -const fetchFollowers = ({id, page, credentials}) => { - let url = `${FOLLOWERS_URL}?user_id=${id}` - if (page) { - url = url + `&page=${page}` - } - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(parseUser)) +const exportFriends = ({ id, credentials }) => { + return new Promise(async (resolve, reject) => { + try { + let friends = [] + let more = true + while (more) { + const maxId = friends.length > 0 ? last(friends).id : undefined + const users = await fetchFriends({ id, maxId, credentials }) + friends = concat(friends, users) + if (users.length === 0) { + more = false + } + } + resolve(friends) + } catch (err) { + reject(err) + } + }) } -const fetchAllFollowing = ({username, credentials}) => { - const url = `${ALL_FOLLOWING_URL}/${username}.json` +const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { + let url = MASTODON_FOLLOWERS_URL(id) + const args = [ + maxId && `max_id=${maxId}`, + sinceId && `since_id=${sinceId}`, + limit && `limit=${limit}` + ].filter(_ => _).join('&') + + url += args ? '?' + args : '' return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) .then((data) => data.map(parseUser)) } -const fetchFollowRequests = ({credentials}) => { - const url = FOLLOW_REQUESTS_URL +const fetchFollowRequests = ({ credentials }) => { + const url = MASTODON_FOLLOW_REQUESTS_URL return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) + .then((data) => data.map(parseUser)) } -const fetchConversation = ({id, credentials}) => { - let url = `${CONVERSATION_URL}/${id}.json?count=100` - return fetch(url, { headers: authHeaders(credentials) }) +const fetchConversation = ({ id, credentials }) => { + let urlContext = MASTODON_STATUS_CONTEXT_URL(id) + return fetch(urlContext, { headers: authHeaders(credentials) }) .then((data) => { if (data.ok) { return data @@ -307,11 +370,14 @@ const fetchConversation = ({id, credentials}) => { throw new Error('Error fetching timeline', data) }) .then((data) => data.json()) - .then((data) => data.map(parseStatus)) + .then(({ ancestors, descendants }) => ({ + ancestors: ancestors.map(parseStatus), + descendants: descendants.map(parseStatus) + })) } -const fetchStatus = ({id, credentials}) => { - let url = `${STATUS_URL}/${id}.json` +const fetchStatus = ({ id, credentials }) => { + let url = MASTODON_STATUS_URL(id) return fetch(url, { headers: authHeaders(credentials) }) .then((data) => { if (data.ok) { @@ -323,57 +389,136 @@ const fetchStatus = ({id, credentials}) => { .then((data) => parseStatus(data)) } -const setUserMute = ({id, credentials, muted = true}) => { - const form = new FormData() +const tagUser = ({ tag, credentials, ...options }) => { + const screenName = options.screen_name + const form = { + nicknames: [screenName], + tags: [tag] + } - const muteInteger = muted ? 1 : 0 + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' - form.append('namespace', 'qvitter') - form.append('data', muteInteger) - form.append('topic', `mute:${id}`) + return fetch(TAG_USER_URL, { + method: 'PUT', + headers: headers, + body: JSON.stringify(form) + }) +} + +const untagUser = ({ tag, credentials, ...options }) => { + const screenName = options.screen_name + const body = { + nicknames: [screenName], + tags: [tag] + } - return fetch(QVITTER_USER_PREF_URL, { + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(TAG_USER_URL, { + method: 'DELETE', + headers: headers, + body: JSON.stringify(body) + }) +} + +const addRight = ({ right, credentials, ...user }) => { + const screenName = user.screen_name + + return fetch(PERMISSION_GROUP_URL(screenName, right), { method: 'POST', headers: authHeaders(credentials), - body: form + body: {} }) } -const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false}) => { +const deleteRight = ({ right, credentials, ...user }) => { + const screenName = user.screen_name + + return fetch(PERMISSION_GROUP_URL(screenName, right), { + method: 'DELETE', + headers: authHeaders(credentials), + body: {} + }) +} + +const setActivationStatus = ({ status, credentials, ...user }) => { + const screenName = user.screen_name + const body = { + status: status + } + + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(ACTIVATION_STATUS_URL(screenName), { + method: 'PUT', + headers: headers, + body: JSON.stringify(body) + }) +} + +const deleteUser = ({ credentials, ...user }) => { + const screenName = user.screen_name + const headers = authHeaders(credentials) + + return fetch(`${ADMIN_USERS_URL}?nickname=${screenName}`, { + method: 'DELETE', + headers: headers + }) +} + +const fetchTimeline = ({ + timeline, + credentials, + since = false, + until = false, + userId = false, + tag = false, + withMuted = false +}) => { const timelineUrls = { - public: PUBLIC_TIMELINE_URL, - friends: FRIENDS_TIMELINE_URL, - mentions: MENTIONS_URL, - dms: DM_TIMELINE_URL, - notifications: QVITTER_USER_NOTIFICATIONS_URL, - 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL, - user: QVITTER_USER_TIMELINE_URL, - media: QVITTER_USER_TIMELINE_URL, + public: MASTODON_PUBLIC_TIMELINE, + friends: MASTODON_USER_HOME_TIMELINE_URL, + dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, + notifications: MASTODON_USER_NOTIFICATIONS_URL, + 'publicAndExternal': MASTODON_PUBLIC_TIMELINE, + user: MASTODON_USER_TIMELINE_URL, + media: MASTODON_USER_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, - tag: TAG_TIMELINE_URL + tag: MASTODON_TAG_TIMELINE_URL } const isNotifications = timeline === 'notifications' const params = [] let url = timelineUrls[timeline] + if (timeline === 'user' || timeline === 'media') { + url = url(userId) + } + if (since) { params.push(['since_id', since]) } if (until) { params.push(['max_id', until]) } - if (userId) { - params.push(['user_id', userId]) - } if (tag) { - url += `/${tag}.json` + url = url(tag) } if (timeline === 'media') { params.push(['only_media', 1]) } + if (timeline === 'public') { + params.push(['local', true]) + } + if (timeline === 'public' || timeline === 'publicAndExternal') { + params.push(['only_media', false]) + } params.push(['count', 20]) + params.push(['with_muted', withMuted]) const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') url += `?${queryString}` @@ -389,9 +534,14 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use .then((data) => data.map(isNotifications ? parseNotification : parseStatus)) } +const fetchPinnedStatuses = ({ id, credentials }) => { + const url = MASTODON_USER_TIMELINE_URL(id) + '?pinned=true' + return promisedRequest({ url, credentials }) + .then((data) => data.map(parseStatus)) +} + const verifyCredentials = (user) => { - return fetch(LOGIN_URL, { - method: 'POST', + return fetch(MASTODON_LOGIN_URL, { headers: authHeaders(user) }) .then((response) => { @@ -407,50 +557,66 @@ const verifyCredentials = (user) => { } const favorite = ({ id, credentials }) => { - return fetch(`${FAVORITE_URL}/${id}.json`, { - headers: authHeaders(credentials), - method: 'POST' - }) + return promisedRequest({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials }) + .then((data) => parseStatus(data)) } const unfavorite = ({ id, credentials }) => { - return fetch(`${UNFAVORITE_URL}/${id}.json`, { - headers: authHeaders(credentials), - method: 'POST' - }) + return promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials }) + .then((data) => parseStatus(data)) } const retweet = ({ id, credentials }) => { - return fetch(`${RETWEET_URL}/${id}.json`, { - headers: authHeaders(credentials), - method: 'POST' - }) + return promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials }) + .then((data) => parseStatus(data)) } const unretweet = ({ id, credentials }) => { - return fetch(`${UNRETWEET_URL}/${id}.json`, { - headers: authHeaders(credentials), - method: 'POST' - }) + return promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials }) + .then((data) => parseStatus(data)) } -const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks}) => { - const idsText = mediaIds.join(',') +const postStatus = ({ + credentials, + status, + spoilerText, + visibility, + sensitive, + poll, + mediaIds = [], + inReplyToStatusId, + contentType +}) => { const form = new FormData() + const pollOptions = poll.options || [] form.append('status', status) form.append('source', 'Pleroma FE') - if (noAttachmentLinks) form.append('no_attachment_links', noAttachmentLinks) if (spoilerText) form.append('spoiler_text', spoilerText) if (visibility) form.append('visibility', visibility) if (sensitive) form.append('sensitive', sensitive) if (contentType) form.append('content_type', contentType) - form.append('media_ids', idsText) + mediaIds.forEach(val => { + form.append('media_ids[]', val) + }) + if (pollOptions.some(option => option !== '')) { + const normalizedPoll = { + expires_in: poll.expiresIn, + multiple: poll.multiple + } + Object.keys(normalizedPoll).forEach(key => { + form.append(`poll[${key}]`, normalizedPoll[key]) + }) + + pollOptions.forEach(option => { + form.append('poll[options][]', option) + }) + } if (inReplyToStatusId) { - form.append('in_reply_to_status_id', inReplyToStatusId) + form.append('in_reply_to_id', inReplyToStatusId) } - return fetch(STATUS_UPDATE_URL, { + return fetch(MASTODON_POST_STATUS_URL, { body: form, method: 'POST', headers: authHeaders(credentials) @@ -468,32 +634,45 @@ const postStatus = ({credentials, status, spoilerText, visibility, sensitive, me } const deleteStatus = ({ id, credentials }) => { - return fetch(`${STATUS_DELETE_URL}/${id}.json`, { + return fetch(MASTODON_DELETE_URL(id), { headers: authHeaders(credentials), - method: 'POST' + method: 'DELETE' + }) +} + +const uploadMedia = ({ formData, credentials }) => { + return fetch(MASTODON_MEDIA_UPLOAD_URL, { + body: formData, + method: 'POST', + headers: authHeaders(credentials) }) + .then((data) => data.json()) + .then((data) => parseAttachment(data)) } -const uploadMedia = ({formData, credentials}) => { - return fetch(MEDIA_UPLOAD_URL, { +const importBlocks = ({ file, credentials }) => { + const formData = new FormData() + formData.append('list', file) + return fetch(BLOCKS_IMPORT_URL, { body: formData, method: 'POST', headers: authHeaders(credentials) }) - .then((response) => response.text()) - .then((text) => (new DOMParser()).parseFromString(text, 'application/xml')) + .then((response) => response.ok) } -const followImport = ({params, credentials}) => { +const importFollows = ({ file, credentials }) => { + const formData = new FormData() + formData.append('list', file) return fetch(FOLLOW_IMPORT_URL, { - body: params, + body: formData, method: 'POST', headers: authHeaders(credentials) }) .then((response) => response.ok) } -const deleteAccount = ({credentials, password}) => { +const deleteAccount = ({ credentials, password }) => { const form = new FormData() form.append('password', password) @@ -506,7 +685,7 @@ const deleteAccount = ({credentials, password}) => { .then((response) => response.json()) } -const changePassword = ({credentials, password, newPassword, newPasswordConfirmation}) => { +const changePassword = ({ credentials, password, newPassword, newPasswordConfirmation }) => { const form = new FormData() form.append('password', password) @@ -521,34 +700,91 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma .then((response) => response.json()) } -const fetchMutes = ({credentials}) => { - const url = '/api/qvitter/mutes.json' - - return fetch(url, { - headers: authHeaders(credentials) +const settingsMFA = ({ credentials }) => { + return fetch(MFA_SETTINGS_URL, { + headers: authHeaders(credentials), + method: 'GET' }).then((data) => data.json()) } -const fetchBlocks = ({page, credentials}) => { - return fetch(BLOCKS_URL, { +const mfaDisableOTP = ({ credentials, password }) => { + const form = new FormData() + + form.append('password', password) + + return fetch(MFA_DISABLE_OTP_URL, { + body: form, + method: 'DELETE', headers: authHeaders(credentials) - }).then((data) => { - if (data.ok) { - return data.json() - } - throw new Error('Error fetching blocks', data) }) + .then((response) => response.json()) } -const fetchOAuthTokens = ({credentials}) => { +const mfaConfirmOTP = ({ credentials, password, token }) => { + const form = new FormData() + + form.append('password', password) + form.append('code', token) + + return fetch(MFA_CONFIRM_OTP_URL, { + body: form, + headers: authHeaders(credentials), + method: 'POST' + }).then((data) => data.json()) +} +const mfaSetupOTP = ({ credentials }) => { + return fetch(MFA_SETUP_OTP_URL, { + headers: authHeaders(credentials), + method: 'GET' + }).then((data) => data.json()) +} +const generateMfaBackupCodes = ({ credentials }) => { + return fetch(MFA_BACKUP_CODES_URL, { + headers: authHeaders(credentials), + method: 'GET' + }).then((data) => data.json()) +} + +const fetchMutes = ({ credentials }) => { + return promisedRequest({ url: MASTODON_USER_MUTES_URL, credentials }) + .then((users) => users.map(parseUser)) +} + +const muteUser = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST' }) +} + +const unmuteUser = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' }) +} + +const subscribeUser = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_SUBSCRIBE_USER(id), credentials, method: 'POST' }) +} + +const unsubscribeUser = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' }) +} + +const fetchBlocks = ({ credentials }) => { + return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials }) + .then((users) => users.map(parseUser)) +} + +const fetchOAuthTokens = ({ credentials }) => { const url = '/api/oauth_tokens.json' return fetch(url, { headers: authHeaders(credentials) - }).then((data) => data.json()) + }).then((data) => { + if (data.ok) { + return data.json() + } + throw new Error('Error fetching auth tokens', data) + }) } -const revokeOAuthToken = ({id, credentials}) => { +const revokeOAuthToken = ({ id, credentials }) => { const url = `/api/oauth_tokens/${id}` return fetch(url, { @@ -557,13 +793,13 @@ const revokeOAuthToken = ({id, credentials}) => { }) } -const suggestions = ({credentials}) => { +const suggestions = ({ credentials }) => { return fetch(SUGGESTIONS_URL, { headers: authHeaders(credentials) }).then((data) => data.json()) } -const markNotificationsAsSeen = ({id, credentials}) => { +const markNotificationsAsSeen = ({ id, credentials }) => { const body = new FormData() body.append('latest_id', id) @@ -575,9 +811,110 @@ const markNotificationsAsSeen = ({id, credentials}) => { }).then((data) => data.json()) } +const vote = ({ pollId, choices, credentials }) => { + const form = new FormData() + form.append('choices', choices) + + return promisedRequest({ + url: MASTODON_VOTE_URL(encodeURIComponent(pollId)), + method: 'POST', + credentials, + payload: { + choices: choices + } + }) +} + +const fetchPoll = ({ pollId, credentials }) => { + return promisedRequest( + { + url: MASTODON_POLL_URL(encodeURIComponent(pollId)), + method: 'GET', + credentials + } + ) +} + +const fetchFavoritedByUsers = ({ id }) => { + return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser)) +} + +const fetchRebloggedByUsers = ({ id }) => { + return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser)) +} + +const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { + return promisedRequest({ + url: MASTODON_REPORT_USER_URL, + method: 'POST', + payload: { + 'account_id': userId, + 'status_ids': statusIds, + comment, + forward + }, + credentials + }) +} + +const searchUsers = ({ credentials, query }) => { + return promisedRequest({ + url: MASTODON_USER_SEARCH_URL, + params: { + q: query, + resolve: true + }, + credentials + }) + .then((data) => data.map(parseUser)) +} + +const search2 = ({ credentials, q, resolve, limit, offset, following }) => { + let url = MASTODON_SEARCH_2 + let params = [] + + if (q) { + params.push(['q', encodeURIComponent(q)]) + } + + if (resolve) { + params.push(['resolve', resolve]) + } + + if (limit) { + params.push(['limit', limit]) + } + + if (offset) { + params.push(['offset', offset]) + } + + if (following) { + params.push(['following', true]) + } + + let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') + url += `?${queryString}` + + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching search result', data) + }) + .then((data) => { return data.json() }) + .then((data) => { + data.accounts = data.accounts.slice(0, limit).map(u => parseUser(u)) + data.statuses = data.statuses.slice(0, limit).map(s => parseStatus(s)) + return data + }) +} + const apiService = { verifyCredentials, fetchTimeline, + fetchPinnedStatuses, fetchConversation, fetchStatus, fetchFriends, @@ -585,9 +922,14 @@ const apiService = { fetchFollowers, followUser, unfollowUser, + pinOwnStatus, + unpinOwnStatus, + muteConversation, + unmuteConversation, blockUser, unblockUser, fetchUser, + fetchUserRelationship, favorite, unfavorite, retweet, @@ -595,27 +937,48 @@ const apiService = { postStatus, deleteStatus, uploadMedia, - fetchAllFollowing, - setUserMute, fetchMutes, + muteUser, + unmuteUser, + subscribeUser, + unsubscribeUser, fetchBlocks, fetchOAuthTokens, revokeOAuthToken, + tagUser, + untagUser, + deleteUser, + addRight, + deleteRight, + setActivationStatus, register, getCaptcha, updateAvatar, updateBg, updateProfile, updateBanner, - externalProfile, - followImport, + importBlocks, + importFollows, deleteAccount, changePassword, + settingsMFA, + mfaDisableOTP, + generateMfaBackupCodes, + mfaSetupOTP, + mfaConfirmOTP, fetchFollowRequests, approveUser, denyUser, suggestions, - markNotificationsAsSeen + markNotificationsAsSeen, + vote, + fetchPoll, + fetchFavoritedByUsers, + fetchRebloggedByUsers, + reportUser, + updateNotificationSettings, + search2, + searchUsers } export default apiService |
