UNPKG

@cutls/megalodon

Version:

Mastodon, Pleroma, Misskey API client for node.js and browser

1,627 lines (1,626 loc) 41.9 kB
import FormData from 'form-data' import FirefishAPI from './firefish/api_client.js' import { DEFAULT_UA } from './default.js' import * as FirefishOAuth from './firefish/oauth.js' import { NotImplementedError, ArgumentError, UnexpectedError } from './megalodon.js' import { UnknownNotificationTypeError } from './notification.js' export default class Firefish { client baseUrl constructor(baseUrl, accessToken = null, userAgent = DEFAULT_UA) { let token = '' if (accessToken) { token = accessToken } let agent = DEFAULT_UA if (userAgent) { agent = userAgent } this.client = new FirefishAPI.Client(baseUrl, token, agent) this.baseUrl = baseUrl } cancel() { return this.client.cancel() } async registerApp( client_name, options = { scopes: FirefishAPI.DEFAULT_SCOPE, redirect_uris: this.baseUrl } ) { return this.createApp(client_name, options).then(async (appData) => { return this.generateAuthUrlAndToken(appData.client_secret).then((session) => { appData.url = session.url appData.session_token = session.token return appData }) }) } async createApp( client_name, options = { scopes: FirefishAPI.DEFAULT_SCOPE, redirect_uris: this.baseUrl } ) { const redirect_uris = options.redirect_uris || this.baseUrl const scopes = options.scopes || FirefishAPI.DEFAULT_SCOPE const params = { name: client_name, description: '', permission: scopes, callbackUrl: redirect_uris } return this.client.post('/api/app/create', params).then((res) => { return FirefishOAuth.toAppData(res.data) }) } async generateAuthUrlAndToken(clientSecret) { return this.client .post('/api/auth/session/generate', { appSecret: clientSecret }) .then((res) => res.data) } async verifyAppCredentials() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async fetchAccessToken(_client_id, client_secret, session_token, _redirect_uri) { return this.client .post('/api/auth/session/userkey', { appSecret: client_secret, token: session_token }) .then((res) => { return FirefishOAuth.toTokenData(res.data) }) } async refreshToken(_client_id, _client_secret, _refresh_token) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async revokeToken(_client_id, _client_secret, _token) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async registerAccount(_username, _email, _password, _agreement, _locale, _reason) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async verifyAccountCredentials() { return this.client.post('/api/i').then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.userDetail(res.data) }) }) } async updateCredentials(options) { let params = {} if (options) { if (options.bot !== undefined) { params = Object.assign(params, { isBot: options.bot }) } if (options.display_name) { params = Object.assign(params, { name: options.display_name }) } if (options.note) { params = Object.assign(params, { description: options.note }) } if (options.locked !== undefined) { params = Object.assign(params, { isLocked: options.locked }) } if (options.source) { if (options.source.language) { params = Object.assign(params, { lang: options.source.language }) } if (options.source.sensitive) { params = Object.assign(params, { alwaysMarkNsfw: options.source.sensitive }) } } } return this.client.post('/api/i', params).then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.userDetail(res.data) }) }) } async getAccount(id) { return this.client .post('/api/users/show', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.userDetail(res.data) }) }) } async getAccountStatuses(id, options) { if (options && options.pinned) { return this.client .post('/api/users/show', { userId: id }) .then((res) => { if (res.data.pinnedNotes) { return { ...res, data: res.data.pinnedNotes.map((n) => FirefishAPI.Converter.note(n)) } } return { ...res, data: [] } }) } let params = { userId: id } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.exclude_replies) { params = Object.assign(params, { includeReplies: false }) } if (options.exclude_reblogs) { params = Object.assign(params, { includeMyRenotes: false }) } if (options.only_media) { params = Object.assign(params, { withFiles: options.only_media }) } } return this.client.post('/api/users/notes', params).then((res) => { const statuses = res.data.map((note) => FirefishAPI.Converter.note(note)) return Object.assign(res, { data: statuses }) }) } async getAccountFavourites(_id, _options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async subscribeAccount(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async unsubscribeAccount(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getAccountFollowers(id, options) { let params = { userId: id } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } } return this.client.post('/api/users/followers', params).then((res) => { return Object.assign(res, { data: res.data.map((f) => FirefishAPI.Converter.follower(f)) }) }) } async getAccountFollowing(id, options) { let params = { userId: id } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } } return this.client.post('/api/users/following', params).then((res) => { return Object.assign(res, { data: res.data.map((f) => FirefishAPI.Converter.following(f)) }) }) } async getAccountLists(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getIdentityProof(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async followAccount(id, _options) { await this.client.post('/api/following/create', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async unfollowAccount(id) { await this.client.post('/api/following/delete', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async blockAccount(id) { await this.client.post('/api/blocking/create', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async unblockAccount(id) { await this.client.post('/api/blocking/delete', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async muteAccount(id, _notifications) { await this.client.post('/api/mute/create', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async unmuteAccount(id) { await this.client.post('/api/mute/delete', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async pinAccount(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async unpinAccount(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async setAccountNote(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getRelationship(id) { return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async getRelationships(ids) { return Promise.all(ids.map((id) => this.getRelationship(id))).then((results) => ({ ...results[0], data: results.map((r) => r.data) })) } async searchAccount(q, options) { let params = { query: q, detail: true } if (options) { params = Object.assign(params, { localOnly: !options.resolve }) if (options.limit) { params = Object.assign(params, { limit: options.limit }) } } return this.client.post('/api/users/search', params).then((res) => { return Object.assign(res, { data: res.data.map((u) => FirefishAPI.Converter.userDetail(u)) }) }) } async lookupAccount(_acct) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getBookmarks(_options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getFavourites(options) { let params = {} if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/i/favorites', params).then((res) => { return Object.assign(res, { data: res.data.map((fav) => FirefishAPI.Converter.note(fav.note)) }) }) } async getMutes(options) { let params = {} if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/mute/list', params).then((res) => { return Object.assign(res, { data: res.data.map((mute) => FirefishAPI.Converter.userDetail(mute.mutee)) }) }) } async getBlocks(options) { let params = {} if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/blocking/list', params).then((res) => { return Object.assign(res, { data: res.data.map((blocking) => FirefishAPI.Converter.userDetail(blocking.blockee)) }) }) } async getDomainBlocks(_options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async blockDomain(_domain) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async unblockDomain(_domain) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getFilters() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getFilter(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async createFilter(_phrase, _context, _options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async updateFilter(_id, _phrase, _context, _options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async deleteFilter(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async report(account_id, options) { const category = 'other' return this.client .post('/api/users/report-abuse', { userId: account_id, comment: options.comment }) .then((res) => { return Object.assign(res, { data: { id: '', action_taken: false, action_taken_at: null, comment: options.comment, category: category, forwarded: false, status_ids: null, rule_ids: null } }) }) } async getFollowRequests(_limit) { return this.client.post('/api/following/requests/list').then((res) => { return Object.assign(res, { data: res.data.map((r) => FirefishAPI.Converter.user(r.follower)) }) }) } async acceptFollowRequest(id) { await this.client.post('/api/following/requests/accept', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async rejectFollowRequest(id) { await this.client.post('/api/following/requests/reject', { userId: id }) return this.client .post('/api/users/relation', { userId: id }) .then((res) => { return Object.assign(res, { data: FirefishAPI.Converter.relation(res.data) }) }) } async getEndorsements(_options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getFeaturedTags() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async createFeaturedTag(_name) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async deleteFeaturedTag(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getSuggestedTags() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getPreferences() { return this.client.post('/api/i').then(async (res) => { return Object.assign(res, { data: FirefishAPI.Converter.userPreferences(res.data, await this.getDefaultPostPrivacy()) }) }) } async getDefaultPostPrivacy() { return this.client .post('/api/i/registry/get-unsecure', { key: 'defaultNoteVisibility', scope: ['client', 'base'] }) .then((res) => { if (!res.data || (res.data !== 'public' && res.data !== 'home' && res.data !== 'followers' && res.data !== 'specified')) return 'public' return FirefishAPI.Converter.visibility(res.data) }) .catch((_) => 'public') } async getFollowedTags() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getSuggestions(limit) { let params = {} if (limit) { params = Object.assign(params, { limit: limit }) } return this.client.post('/api/users/recommendation', params).then((res) => ({ ...res, data: res.data.map((u) => FirefishAPI.Converter.userDetail(u)) })) } async getTag(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async followTag(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async unfollowTag(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async postStatus(status, options) { let params = { text: status } if (options) { if (options.media_ids) { params = Object.assign(params, { fileIds: options.media_ids }) } if (options.poll) { let pollParam = { choices: options.poll.options, expiresAt: null, expiredAfter: options.poll.expires_in } if (options.poll.multiple !== undefined) { pollParam = Object.assign(pollParam, { multiple: options.poll.multiple }) } params = Object.assign(params, { poll: pollParam }) } if (options.in_reply_to_id) { params = Object.assign(params, { replyId: options.in_reply_to_id }) } if (options.spoiler_text) { params = Object.assign(params, { cw: options.spoiler_text }) } if (options.visibility) { params = Object.assign(params, { visibility: FirefishAPI.Converter.encodeVisibility(options.visibility) }) } if (options.quote_id) { params = Object.assign(params, { renoteId: options.quote_id }) } } return this.client.post('/api/notes/create', params).then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data.createdNote) })) } async getStatus(id) { return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } async editStatus(_id, _options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async deleteStatus(id) { return this.client.post('/api/notes/delete', { noteId: id }) } async getStatusContext(id, options) { let paramsDescendants = { noteId: id } if (options) { if (options.limit) { paramsDescendants = Object.assign(paramsDescendants, { limit: options.limit }) } if (options.max_id) { paramsDescendants = Object.assign(paramsDescendants, { untilId: options.max_id }) } if (options.since_id) { paramsDescendants = Object.assign(paramsDescendants, { sinceId: options.since_id }) } } let paramsAncestors = { noteId: id } if (options) { if (options.limit) { paramsAncestors = Object.assign(paramsDescendants, { limit: options.limit }) } } const ancestorsPromise = this.client.post('/api/notes/children', paramsAncestors) const descendantsRes = await this.client.post('/api/notes/conversation', paramsDescendants) const context = { ancestors: (await ancestorsPromise).data.map((n) => FirefishAPI.Converter.note(n)).reverse(), descendants: descendantsRes.data.map((n) => FirefishAPI.Converter.note(n)) } return { ...descendantsRes, data: context } } async getStatusSource(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getStatusRebloggedBy(id) { return this.client .post('/api/notes/renotes', { noteId: id }) .then((res) => ({ ...res, data: res.data.map((n) => FirefishAPI.Converter.user(n.user)) })) } async getStatusFavouritedBy(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async favouriteStatus(id) { await this.client.post('/api/notes/favorites/create', { noteId: id }) return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } async unfavouriteStatus(id) { await this.client.post('/api/notes/favorites/delete', { noteId: id }) return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } async reblogStatus(id) { return this.client .post('/api/notes/create', { renoteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data.createdNote) })) } async unreblogStatus(id) { await this.client.post('/api/notes/unrenote', { noteId: id }) return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } async bookmarkStatus(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async unbookmarkStatus(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async muteStatus(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async unmuteStatus(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async pinStatus(id) { await this.client.post('/api/i/pin', { noteId: id }) return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } async unpinStatus(id) { await this.client.post('/api/i/unpin', { noteId: id }) return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } reactionName(name) { const isUnicodeEmoji = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu.test(name) if (isUnicodeEmoji) { return name } return `:${name}:` } async uploadMedia(file, _options) { const formData = new FormData() formData.append('file', file) let headers = {} if (typeof formData.getHeaders === 'function') { headers = formData.getHeaders() } return this.client.post('/api/drive/files/create', formData, headers).then((res) => ({ ...res, data: FirefishAPI.Converter.file(res.data) })) } async getMedia(id) { const res = await this.client.post('/api/drive/files/show', { fileId: id }) return { ...res, data: FirefishAPI.Converter.file(res.data) } } async updateMedia(id, options) { let params = { fileId: id } if (options) { if (options.is_sensitive !== undefined) { params = Object.assign(params, { isSensitive: options.is_sensitive }) } } return this.client.post('/api/drive/files/update', params).then((res) => ({ ...res, data: FirefishAPI.Converter.file(res.data) })) } async getPoll(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async votePoll(_id, choices, status_id) { if (!status_id) { return new Promise((_, reject) => { const err = new ArgumentError('status_id is required') reject(err) }) } const params = { noteId: status_id, choice: choices[0] } await this.client.post('/api/notes/polls/vote', params) const res = await this.client .post('/api/notes/show', { noteId: status_id }) .then((res) => { const note = FirefishAPI.Converter.note(res.data) return { ...res, data: note.poll } }) if (!res.data) { return new Promise((_, reject) => { const err = new UnexpectedError('poll does not exist') reject(err) }) } return { ...res, data: res.data } } async getScheduledStatuses(_options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getScheduledStatus(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async scheduleStatus(_id, _scheduled_at) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async cancelScheduledStatus(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getPublicTimeline(options) { let params = {} if (options) { if (options.only_media !== undefined) { params = Object.assign(params, { withFiles: options.only_media }) } if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/notes/global-timeline', params).then((res) => ({ ...res, data: res.data.map((n) => FirefishAPI.Converter.note(n)) })) } async getLocalTimeline(options) { let params = {} if (options) { if (options.only_media !== undefined) { params = Object.assign(params, { withFiles: options.only_media }) } if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/notes/local-timeline', params).then((res) => ({ ...res, data: res.data.map((n) => FirefishAPI.Converter.note(n)) })) } async getTagTimeline(hashtag, options) { let params = { tag: hashtag } if (options) { if (options.only_media !== undefined) { params = Object.assign(params, { withFiles: options.only_media }) } if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/notes/search-by-tag', params).then((res) => ({ ...res, data: res.data.map((n) => FirefishAPI.Converter.note(n)) })) } async getHomeTimeline(options) { let params = { withFiles: false } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/notes/timeline', params).then((res) => ({ ...res, data: res.data.map((n) => FirefishAPI.Converter.note(n)) })) } async getListTimeline(list_id, options) { let params = { listId: list_id, withFiles: false } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/notes/user-list-timeline', params).then((res) => ({ ...res, data: res.data.map((n) => FirefishAPI.Converter.note(n)) })) } async getConversationTimeline(options) { let params = { visibility: 'specified' } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } } return this.client.post('/api/notes/mentions', params).then((res) => ({ ...res, data: res.data.map((n) => FirefishAPI.Converter.noteToConversation(n)) })) } async deleteConversation(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async readConversation(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getLists() { return this.client.post('/api/users/lists/list').then((res) => ({ ...res, data: res.data.map((l) => FirefishAPI.Converter.list(l)) })) } async getList(id) { return this.client .post('/api/users/lists/show', { listId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.list(res.data) })) } async createList(title) { return this.client .post('/api/users/lists/create', { name: title }) .then((res) => ({ ...res, data: FirefishAPI.Converter.list(res.data) })) } async updateList(id, title) { return this.client .post('/api/users/lists/update', { listId: id, name: title }) .then((res) => ({ ...res, data: FirefishAPI.Converter.list(res.data) })) } async deleteList(id) { return this.client.post('/api/users/lists/delete', { listId: id }) } async getAccountsInList(id, _options) { const res = await this.client.post('/api/users/lists/show', { listId: id }) const promise = res.data.userIds?.map((userId) => this.getAccount(userId)) if (promise) { const accounts = await Promise.all(promise) return { ...res, data: accounts.map((r) => r.data) } } else { return { ...res, data: [] } } } async addAccountsToList(id, account_ids) { return this.client.post('/api/users/lists/push', { listId: id, userId: account_ids[0] }) } async deleteAccountsFromList(id, account_ids) { return this.client.post('/api/users/lists/pull', { listId: id, userId: account_ids[0] }) } async getMarkers(_timeline) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async saveMarkers(_options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getNotifications(options) { let params = {} if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.since_id) { params = Object.assign(params, { sinceId: options.since_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } if (options.exclude_type) { params = Object.assign(params, { excludeType: options.exclude_type.map((e) => FirefishAPI.Converter.encodeNotificationType(e)) }) } } const res = await this.client.post('/api/i/notifications', params) const notifications = res.data.flatMap((n) => { const notify = FirefishAPI.Converter.notification(n) if (notify instanceof UnknownNotificationTypeError) { return [] } return notify }) return { ...res, data: notifications } } async getNotification(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async dismissNotifications() { return this.client.post('/api/notifications/mark-all-as-read') } async dismissNotification(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async readNotifications(_options) { return new Promise((_, reject) => { const err = new NotImplementedError('mastodon does not support') reject(err) }) } async subscribePushNotification(_subscription, _data) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getPushSubscription() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async updatePushSubscription(_data) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async deletePushSubscription() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async searchAccounts(q, options) { let params = { query: q } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.offset) { params = Object.assign(params, { offset: options.offset }) } params = Object.assign(params, { localOnly: !options.resolve }) } const res = await this.client.post('/api/users/search', params) return res.data } async searchStatuses(q, options) { let params = { query: q } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.offset) { params = Object.assign(params, { offset: options.offset }) } if (options.max_id) { params = Object.assign(params, { untilId: options.max_id }) } if (options.min_id) { params = Object.assign(params, { sinceId: options.min_id }) } if (options.account_id) { params = Object.assign(params, { userId: options.account_id }) } } const res = await this.client.post('/api/notes/search', params) return res.data } async searchHashtags(q, options) { let params = { query: q } if (options) { if (options.limit) { params = Object.assign(params, { limit: options.limit }) } if (options.offset) { params = Object.assign(params, { offset: options.offset }) } } const res = await this.client.post('/api/hashtags/search', params) return res.data } async searchAll(q, options) { let accounts = [] try { accounts = await this.searchAccounts(q, options) } catch (e) { console.warn(e) } let statuses = [] try { statuses = await this.searchStatuses(q, options) } catch (e) { console.warn(e) } let hashtags = [] try { hashtags = await this.searchHashtags(q, options) } catch (e) { console.warn(e) } return { data: { accounts: accounts.map((a) => FirefishAPI.Converter.userDetail(a)), statuses: statuses.map((n) => FirefishAPI.Converter.note(n)), hashtags: hashtags.map((h) => ({ name: h, url: h, history: [], following: false })) }, status: 200, statusText: '200', headers: null } } async search(q, options) { if (options) { switch (options.type) { case 'accounts': { const accounts = await this.searchAccounts(q, options) return { data: { accounts: accounts.map((a) => FirefishAPI.Converter.userDetail(a)), statuses: [], hashtags: [] }, status: 200, statusText: '200', headers: null } } case 'statuses': { const statuses = await this.searchStatuses(q, options) return { data: { accounts: [], statuses: statuses.map((n) => FirefishAPI.Converter.note(n)), hashtags: [] }, status: 200, statusText: '200', headers: null } } case 'hashtags': { const hashtags = await this.searchHashtags(q, options) return { data: { accounts: [], statuses: [], hashtags: hashtags.map((h) => ({ name: h, url: h, history: [], following: false })) }, status: 200, statusText: '200', headers: null } } default: { return this.searchAll(q, options) } } } else { return this.searchAll(q) } } async getInstance() { return this.client.get('/api/v1/instance').then((res) => ({ ...res, data: FirefishAPI.Converter.instance(res.data) })) } async getInstancePeers() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getInstanceActivity() { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getInstanceTrends(_limit) { return this.client.post('/api/hashtags/trend').then((res) => ({ ...res, data: res.data.map((h) => FirefishAPI.Converter.hashtag(h)) })) } async getInstanceDirectory(_options) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async getInstanceCustomEmojis() { return this.client.post('/api/meta').then((res) => ({ ...res, data: res.data.emojis.map((e) => FirefishAPI.Converter.emoji(e)) })) } async getInstanceAnnouncements() { return this.client.post('/api/announcements').then((res) => ({ ...res, data: res.data.map((a) => FirefishAPI.Converter.announcement(a)) })) } async dismissInstanceAnnouncement(_id) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async addReactionToAnnouncement(_id, _name) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async removeReactionFromAnnouncement(_id, _name) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async createEmojiReaction(id, emoji) { await this.client.post('/api/notes/reactions/create', { noteId: id, reaction: this.reactionName(emoji) }) return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } async deleteEmojiReaction(id, emoji) { await this.client.post('/api/notes/reactions/delete', { noteId: id, reaction: this.reactionName(emoji) }) return this.client .post('/api/notes/show', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.note(res.data) })) } async getEmojiReactions(id) { return this.client .post('/api/notes/reactions', { noteId: id }) .then((res) => ({ ...res, data: FirefishAPI.Converter.reactions(res.data) })) } async getEmojiReaction(_id, _emoji) { return new Promise((_, reject) => { const err = new NotImplementedError('Firefish does not support this method') reject(err) }) } async streamingURL() { const instance = await this.getInstance() if (instance.data.urls) { return instance.data.urls.streaming_api } return this.baseUrl } async userStreaming() { const url = await this.streamingURL() return this.client.socket(url, 'user') } async publicStreaming() { const url = await this.streamingURL() return this.client.socket(url, 'globalTimeline') } async localStreaming() { const url = await this.streamingURL() return this.client.socket(url, 'localTimeline') } async tagStreaming(_tag) { throw new NotImplementedError('TODO: implement') } async listStreaming(list_id) { const url = await this.streamingURL() return this.client.socket(url, 'list', list_id) } async directStreaming() { const url = await this.streamingURL() return this.client.socket(url, 'conversation') } async userStreamingSubscription() { return this.userStreaming() } async publicStreamingSubscription(_socket) { return this.publicStreaming() } async localStreamingSubscription(_socket) { return this.localStreaming() } async tagStreamingSubscription(_socket, tag) { return this.tagStreaming(tag) } async listStreamingSubscription(_socket, list_id) { return this.listStreaming(list_id) } async directStreamingSubscription(_socket) { return this.directStreaming() } }