diff options
Diffstat (limited to 'packages/megalodon/src/mastodon.ts')
| -rw-r--r-- | packages/megalodon/src/mastodon.ts | 3169 |
1 files changed, 3169 insertions, 0 deletions
diff --git a/packages/megalodon/src/mastodon.ts b/packages/megalodon/src/mastodon.ts new file mode 100644 index 0000000000..4a8b1fc1ea --- /dev/null +++ b/packages/megalodon/src/mastodon.ts @@ -0,0 +1,3169 @@ +import { OAuth2 } from 'oauth' +import FormData from 'form-data' +import parseLinkHeader from 'parse-link-header' + +import MastodonAPI from './mastodon/api_client' +import WebSocket from './mastodon/web_socket' +import { MegalodonInterface, NoImplementedError } from './megalodon' +import Response from './response' +import Entity from './entity' +import { NO_REDIRECT, DEFAULT_SCOPE, DEFAULT_UA } from './default' +import { ProxyConfig } from './proxy_config' +import OAuth from './oauth' +import { UnknownNotificationTypeError } from './notification' + +export default class Mastodon implements MegalodonInterface { + public client: MastodonAPI.Interface + public baseUrl: string + + /** + * @param baseUrl hostname or base URL + * @param accessToken access token from OAuth2 authorization + * @param userAgent UserAgent is specified in header on request. + * @param proxyConfig Proxy setting, or set false if don't use proxy. + */ + constructor( + baseUrl: string, + accessToken: string | null = null, + userAgent: string | null = DEFAULT_UA, + proxyConfig: ProxyConfig | false = false + ) { + let token: string = '' + if (accessToken) { + token = accessToken + } + let agent: string = DEFAULT_UA + if (userAgent) { + agent = userAgent + } + this.client = new MastodonAPI.Client(baseUrl, token, agent, proxyConfig) + this.baseUrl = baseUrl + } + + public cancel(): void { + return this.client.cancel() + } + + /** + * Call /api/v1/apps + * + * Create an application. + * @param client_name your application's name + * @param options Form Data + */ + public async registerApp( + client_name: string, + options: Partial<{ scopes: Array<string>; redirect_uris: string; website: string }> + ): Promise<OAuth.AppData> { + const scopes = options.scopes || DEFAULT_SCOPE + return this.createApp(client_name, options).then(async appData => { + return this.generateAuthUrl(appData.client_id, appData.client_secret, { + scope: scopes, + redirect_uri: appData.redirect_uri + }).then(url => { + appData.url = url + return appData + }) + }) + } + + /** + * Call /api/v1/apps + * + * Create an application. + * @param client_name your application's name + * @param options Form Data + */ + public async createApp( + client_name: string, + options: Partial<{ scopes: Array<string>; redirect_uris: string; website: string }> + ): Promise<OAuth.AppData> { + const scopes = options.scopes || DEFAULT_SCOPE + const redirect_uris = options.redirect_uris || NO_REDIRECT + + const params: { + client_name: string + redirect_uris: string + scopes: string + website?: string + } = { + client_name: client_name, + redirect_uris: redirect_uris, + scopes: scopes.join(' ') + } + if (options.website) params.website = options.website + + return this.client + .post<OAuth.AppDataFromServer>('/api/v1/apps', params) + .then((res: Response<OAuth.AppDataFromServer>) => OAuth.AppData.from(res.data)) + } + + /** + * Generate authorization url using OAuth2. + * + * @param clientId your OAuth app's client ID + * @param clientSecret your OAuth app's client Secret + * @param options as property, redirect_uri and scope are available, and must be the same as when you register your app + */ + public generateAuthUrl( + clientId: string, + clientSecret: string, + options: Partial<{ scope: Array<string>; redirect_uri: string }> + ): Promise<string> { + const scope = options.scope || DEFAULT_SCOPE + const redirect_uri = options.redirect_uri || NO_REDIRECT + return new Promise(resolve => { + const oauth = new OAuth2(clientId, clientSecret, this.baseUrl, undefined, '/oauth/token') + const url = oauth.getAuthorizeUrl({ + redirect_uri: redirect_uri, + response_type: 'code', + client_id: clientId, + scope: scope.join(' ') + }) + resolve(url) + }) + } + + // ====================================== + // apps + // ====================================== + /** + * GET /api/v1/apps/verify_credentials + * + * @return An Application + */ + public verifyAppCredentials(): Promise<Response<Entity.Application>> { + return this.client.get<Entity.Application>('/api/v1/apps/verify_credentials') + } + + // ====================================== + // apps/oauth + // ====================================== + /** + * POST /oauth/token + * + * Fetch OAuth access token. + * Get an access token based client_id and client_secret and authorization code. + * @param client_id will be generated by #createApp or #registerApp + * @param client_secret will be generated by #createApp or #registerApp + * @param code will be generated by the link of #generateAuthUrl or #registerApp + * @param redirect_uri must be the same uri as the time when you register your OAuth application + */ + public async fetchAccessToken( + client_id: string | null, + client_secret: string, + code: string, + redirect_uri: string = NO_REDIRECT + ): Promise<OAuth.TokenData> { + if (!client_id) { + throw new Error('client_id is required') + } + return this.client + .post<OAuth.TokenDataFromServer>('/oauth/token', { + client_id, + client_secret, + code, + redirect_uri, + grant_type: 'authorization_code' + }) + .then((res: Response<OAuth.TokenDataFromServer>) => OAuth.TokenData.from(res.data)) + } + + /** + * POST /oauth/revoke + * + * Revoke an OAuth token. + * @param client_id will be generated by #createApp or #registerApp + * @param client_secret will be generated by #createApp or #registerApp + * @param token will be get #fetchAccessToken + */ + public async refreshToken(client_id: string, client_secret: string, refresh_token: string): Promise<OAuth.TokenData> { + return this.client + .post<OAuth.TokenDataFromServer>('/oauth/token', { + client_id, + client_secret, + refresh_token, + grant_type: 'refresh_token' + }) + .then((res: Response<OAuth.TokenDataFromServer>) => OAuth.TokenData.from(res.data)) + } + + /** + * POST /oauth/revoke + * + * Revoke an OAuth token. + * @param client_id will be generated by #createApp or #registerApp + * @param client_secret will be generated by #createApp or #registerApp + * @param token will be get #fetchAccessToken + */ + public async revokeToken(client_id: string, client_secret: string, token: string): Promise<Response<{}>> { + return this.client.post<{}>('/oauth/revoke', { + client_id, + client_secret, + token + }) + } + + // ====================================== + // accounts + // ====================================== + /** + * POST /api/v1/accounts + * + * @param username Username for the account. + * @param email Email for the account. + * @param password Password for the account. + * @param agreement Whether the user agrees to the local rules, terms, and policies. + * @param locale The language of the confirmation email that will be sent + * @param reason Text that will be reviewed by moderators if registrations require manual approval. + * @return An account token. + */ + public async registerAccount( + username: string, + email: string, + password: string, + agreement: boolean, + locale: string, + reason?: string | null + ): Promise<Response<Entity.Token>> { + let params = { + username: username, + email: email, + password: password, + agreement: agreement, + locale: locale + } + if (reason) { + params = Object.assign(params, { + reason: reason + }) + } + return this.client.post<MastodonAPI.Entity.Token>('/api/v1/accounts', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.token(res.data) + }) + }) + } + + /** + * GET /api/v1/accounts/verify_credentials + * + * @return Account. + */ + public async verifyAccountCredentials(): Promise<Response<Entity.Account>> { + return this.client.get<MastodonAPI.Entity.Account>('/api/v1/accounts/verify_credentials').then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.account(res.data) + }) + }) + } + + /** + * PATCH /api/v1/accounts/update_credentials + * + * @return An account. + */ + public async updateCredentials(options?: { + discoverable?: boolean + bot?: boolean + display_name?: string + note?: string + avatar?: string + header?: string + locked?: boolean + source?: { + privacy?: string + sensitive?: boolean + language?: string + } + fields_attributes?: Array<{ name: string; value: string }> + }): Promise<Response<Entity.Account>> { + let params = {} + if (options) { + if (options.discoverable !== undefined) { + params = Object.assign(params, { + discoverable: options.discoverable + }) + } + if (options.bot !== undefined) { + params = Object.assign(params, { + bot: options.bot + }) + } + if (options.display_name) { + params = Object.assign(params, { + display_name: options.display_name + }) + } + if (options.note) { + params = Object.assign(params, { + note: options.note + }) + } + if (options.avatar) { + params = Object.assign(params, { + avatar: options.avatar + }) + } + if (options.header) { + params = Object.assign(params, { + header: options.header + }) + } + if (options.locked !== undefined) { + params = Object.assign(params, { + locked: options.locked + }) + } + if (options.source) { + params = Object.assign(params, { + source: options.source + }) + } + if (options.fields_attributes) { + params = Object.assign(params, { + fields_attributes: options.fields_attributes + }) + } + } + return this.client.patch<MastodonAPI.Entity.Account>('/api/v1/accounts/update_credentials', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.account(res.data) + }) + }) + } + + /** + * GET /api/v1/accounts/:id + * + * @param id The account ID. + * @return An account. + */ + public async getAccount(id: string): Promise<Response<Entity.Account>> { + return this.client.get<MastodonAPI.Entity.Account>(`/api/v1/accounts/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.account(res.data) + }) + }) + } + + /** + * GET /api/v1/accounts/:id/statuses + * + * @param id The account ID. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID but starting with most recent. + * @param options.min_id Return results newer than ID. + * @param options.pinned Return statuses which include pinned statuses. + * @param options.exclude_replies Return statuses which exclude replies. + * @param options.exclude_reblogs Return statuses which exclude reblogs. + * @param options.only_media Show only statuses with media attached? Defaults to false. + * @return Account's statuses. + */ + public async getAccountStatuses( + id: string, + options?: { + limit?: number + max_id?: string + since_id?: string + min_id?: string + pinned?: boolean + exclude_replies?: boolean + exclude_reblogs?: boolean + only_media: boolean + } + ): Promise<Response<Array<Entity.Status>>> { + let params = {} + if (options) { + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.pinned) { + params = Object.assign(params, { + pinned: options.pinned + }) + } + if (options.exclude_replies) { + params = Object.assign(params, { + exclude_replies: options.exclude_replies + }) + } + if (options.exclude_reblogs) { + params = Object.assign(params, { + exclude_reblogs: options.exclude_reblogs + }) + } + if (options.only_media) { + params = Object.assign(params, { + only_media: options.only_media + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>(`/api/v1/accounts/${id}/statuses`, params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + public getAccountFavourites( + _id: string, + _options?: { + limit?: number + max_id?: string + since_id?: string + } + ): Promise<Response<Array<Entity.Status>>> { + return new Promise((_, reject) => { + const err = new NoImplementedError('mastodon does not support') + reject(err) + }) + } + + /** + * POST /api/v1/accounts/:id/follow + * + * @param id Target account ID. + * @return Relationship. + */ + public async subscribeAccount(id: string): Promise<Response<Entity.Relationship>> { + const params = { + notify: true + } + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/follow`, params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/follow + * + * @param id Target account ID. + * @return Relationship. + */ + public async unsubscribeAccount(id: string): Promise<Response<Entity.Relationship>> { + const params = { + notify: false + } + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/follow`, params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * GET /api/v1/accounts/:id/followers + * + * @param id The account ID. + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @return The array of accounts. + */ + public async getAccountFollowers( + id: string, + options?: { + limit?: number + max_id?: string + since_id?: string + get_all?: boolean + sleep_ms?: number + } + ): Promise<Response<Array<Entity.Account>>> { + let params = {} + if (options) { + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.urlToAccounts(`/api/v1/accounts/${id}/followers`, params, options?.get_all || false, options?.sleep_ms || 0) + } + + /** + * GET /api/v1/accounts/:id/following + * + * @param id The account ID. + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @return The array of accounts. + */ + public async getAccountFollowing( + id: string, + options?: { + limit?: number + max_id?: string + since_id?: string + get_all?: boolean + sleep_ms?: number + } + ): Promise<Response<Array<Entity.Account>>> { + let params = {} + if (options) { + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.urlToAccounts(`/api/v1/accounts/${id}/following`, params, options?.get_all || false, options?.sleep_ms || 0) + } + + /** Helper function to optionally follow Link headers as pagination */ + private async urlToAccounts(url: string, params: Record<string, string>, get_all: boolean, sleep_ms: number) { + const res = await this.client.get<Array<MastodonAPI.Entity.Account>>(url, params) + let converted = Object.assign({}, res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + if (get_all && converted.headers.link) { + let parsed = parseLinkHeader(converted.headers.link) + while (parsed?.next) { + const nextRes = await this.client.get<Array<MastodonEntity.Account>>(parsed?.next.url, undefined, undefined, true) + converted = Object.assign({}, converted, { + data: [...converted.data, ...nextRes.data.map(a => MastodonAPI.Converter.account(a))] + }) + parsed = parseLinkHeader(nextRes.headers.link) + if (sleep_ms) { + await new Promise<void>(converted => setTimeout(converted, sleep_ms)) + } + } + } + return converted + } + + /** + * GET /api/v1/accounts/:id/lists + * + * @param id The account ID. + * @return The array of lists. + */ + public async getAccountLists(id: string): Promise<Response<Array<Entity.List>>> { + return this.client.get<Array<MastodonAPI.Entity.List>>(`/api/v1/accounts/${id}/lists`).then(res => { + return Object.assign(res, { + data: res.data.map(l => MastodonAPI.Converter.list(l)) + }) + }) + } + + /** + * GET /api/v1/accounts/:id/identity_proofs + * + * @param id The account ID. + * @return Array of IdentityProof + */ + public async getIdentityProof(id: string): Promise<Response<Array<Entity.IdentityProof>>> { + return this.client.get<Array<MastodonAPI.Entity.IdentityProof>>(`/api/v1/accounts/${id}/identity_proofs`).then(res => { + return Object.assign(res, { + data: res.data.map(i => MastodonAPI.Converter.identity_proof(i)) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/follow + * + * @param id The account ID. + * @param reblog Receive this account's reblogs in home timeline. + * @return Relationship + */ + public async followAccount(id: string, options?: { reblog?: boolean }): Promise<Response<Entity.Relationship>> { + let params = {} + if (options) { + if (options.reblog !== undefined) { + params = Object.assign(params, { + reblog: options.reblog + }) + } + } + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/follow`, params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/unfollow + * + * @param id The account ID. + * @return Relationship + */ + public async unfollowAccount(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/unfollow`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/block + * + * @param id The account ID. + * @return Relationship + */ + public async blockAccount(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/block`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/unblock + * + * @param id The account ID. + * @return RElationship + */ + public async unblockAccount(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/unblock`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/mute + * + * @param id The account ID. + * @param notifications Mute notifications in addition to statuses. + * @return Relationship + */ + public async muteAccount(id: string, notifications: boolean = true): Promise<Response<Entity.Relationship>> { + return this.client + .post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/mute`, { + notifications: notifications + }) + .then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/unmute + * + * @param id The account ID. + * @return Relationship + */ + public async unmuteAccount(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/unmute`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/pin + * + * @param id The account ID. + * @return Relationship + */ + public async pinAccount(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/pin`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/accounts/:id/unpin + * + * @param id The account ID. + * @return Relationship + */ + public async unpinAccount(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/accounts/${id}/unpin`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * GET /api/v1/accounts/relationships + * + * @param id The account ID. + * @return Relationship + */ + public async getRelationship(id: string): Promise<Response<Entity.Relationship>> { + return this.client + .get<Array<MastodonAPI.Entity.Relationship>>('/api/v1/accounts/relationships', { + id: [id] + }) + .then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data[0]) + }) + }) + } + + /** + * GET /api/v1/accounts/relationships + * + * @param ids Array of account IDs. + * @return Array of Relationship. + */ + public async getRelationships(ids: Array<string>): Promise<Response<Array<Entity.Relationship>>> { + return this.client + .get<Array<MastodonAPI.Entity.Relationship>>('/api/v1/accounts/relationships', { + id: ids + }) + .then(res => { + return Object.assign(res, { + data: res.data.map(r => MastodonAPI.Converter.relationship(r)) + }) + }) + } + + /** + * GET /api/v1/accounts/search + * + * @param q Search query. + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @return The array of accounts. + */ + public async searchAccount( + q: string, + options?: { + following?: boolean + resolve?: boolean + limit?: number + max_id?: string + since_id?: string + } + ): Promise<Response<Array<Entity.Account>>> { + let params = { q: q } + if (options) { + if (options.following !== undefined && options.following !== null) { + params = Object.assign(params, { + following: options.following + }) + } + if (options.resolve !== undefined && options.resolve !== null) { + params = Object.assign(params, { + resolve: options.resolve + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Account>>('/api/v1/accounts/search', params).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + // ====================================== + // accounts/bookmarks + // ====================================== + + /** + * GET /api/v1/bookmarks + * + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getBookmarks(options?: { + limit?: number + max_id?: string + since_id?: string + min_id?: string + }): Promise<Response<Array<Entity.Status>>> { + let params = {} + if (options) { + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>('/api/v1/bookmarks', params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + // ====================================== + // accounts/favourites + // ====================================== + + /** + * GET /api/v1/favourites + * + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getFavourites(options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<Entity.Status>>> { + let params = {} + if (options) { + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>('/api/v1/favourites', params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + // ====================================== + // accounts/mutes + // ====================================== + /** + * GET /api/v1/mutes + * + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of accounts. + */ + public async getMutes(options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<Entity.Account>>> { + let params = {} + if (options) { + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Account>>('/api/v1/mutes', params).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + // ====================================== + // accounts/blocks + // ====================================== + /** + * GET /api/v1/blocks + * + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of accounts. + */ + public async getBlocks(options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<Entity.Account>>> { + let params = {} + if (options) { + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Account>>('/api/v1/blocks', params).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + // ====================================== + // accounts/domain_blocks + // ====================================== + /** + * GET /api/v1/domain_blocks + * + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of domain name. + */ + public async getDomainBlocks(options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<string>>> { + let params = {} + if (options) { + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<string>>('/api/v1/domain_blocks', params) + } + + /** + * POST/api/v1/domain_blocks + * + * @param domain Domain to block. + */ + public blockDomain(domain: string): Promise<Response<{}>> { + return this.client.post<{}>('/api/v1/domain_blocks', { + domain: domain + }) + } + + /** + * DELETE /api/v1/domain_blocks + * + * @param domain Domain to unblock + */ + public unblockDomain(domain: string): Promise<Response<{}>> { + return this.client.del<{}>('/api/v1/domain_blocks', { + domain: domain + }) + } + + // ====================================== + // accounts/filters + // ====================================== + /** + * GET /api/v1/filters + * + * @return Array of filters. + */ + public async getFilters(): Promise<Response<Array<Entity.Filter>>> { + return this.client.get<Array<MastodonAPI.Entity.Filter>>('/api/v1/filters').then(res => { + return Object.assign(res, { + data: res.data.map(f => MastodonAPI.Converter.filter(f)) + }) + }) + } + + /** + * GET /api/v1/filters/:id + * + * @param id The filter ID. + * @return Filter. + */ + public async getFilter(id: string): Promise<Response<Entity.Filter>> { + return this.client.get<MastodonAPI.Entity.Filter>(`/api/v1/filters/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.filter(res.data) + }) + }) + } + + /** + * POST /api/v1/filters + * + * @param phrase Text to be filtered. + * @param context Array of enumerable strings home, notifications, public, thread, account. At least one context must be specified. + * @param options.irreversible Should the server irreversibly drop matching entities from home and notifications? + * @param options.whole_word Consider word boundaries? + * @param options.expires_in ISO 8601 Datetime for when the filter expires. + * @return Filter + */ + public async createFilter( + phrase: string, + context: Array<Entity.FilterContext>, + options?: { + irreversible?: boolean + whole_word?: boolean + expires_in?: string + } + ): Promise<Response<Entity.Filter>> { + let params = { + phrase: phrase, + context: context + } + if (options) { + if (options.irreversible !== undefined) { + params = Object.assign(params, { + irreversible: options.irreversible + }) + } + if (options.whole_word !== undefined) { + params = Object.assign(params, { + whole_word: options.whole_word + }) + } + if (options.expires_in) { + params = Object.assign(params, { + expires_in: options.expires_in + }) + } + } + return this.client.post<MastodonAPI.Entity.Filter>('/api/v1/filters', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.filter(res.data) + }) + }) + } + + /** + * PUT /api/v1/filters/:id + * + * @param id The filter ID. + * @param phrase Text to be filtered. + * @param context Array of enumerable strings home, notifications, public, thread, account. At least one context must be specified. + * @param options.irreversible Should the server irreversibly drop matching entities from home and notifications? + * @param options.whole_word Consider word boundaries? + * @param options.expires_in ISO 8601 Datetime for when the filter expires. + * @return Filter + */ + public async updateFilter( + id: string, + phrase: string, + context: Array<Entity.FilterContext>, + options?: { + irreversible?: boolean + whole_word?: boolean + expires_in?: string + } + ): Promise<Response<Entity.Filter>> { + let params = { + phrase: phrase, + context: context + } + if (options) { + if (options.irreversible !== undefined) { + params = Object.assign(params, { + irreversible: options.irreversible + }) + } + if (options.whole_word !== undefined) { + params = Object.assign(params, { + whole_word: options.whole_word + }) + } + if (options.expires_in) { + params = Object.assign(params, { + expires_in: options.expires_in + }) + } + } + return this.client.put<MastodonAPI.Entity.Filter>(`/api/v1/filters/${id}`, params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.filter(res.data) + }) + }) + } + + /** + * DELETE /api/v1/filters/:id + * + * @param id The filter ID. + * @return Removed filter. + */ + public async deleteFilter(id: string): Promise<Response<Entity.Filter>> { + return this.client.del<MastodonAPI.Entity.Filter>(`/api/v1/filters/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.filter(res.data) + }) + }) + } + + // ====================================== + // accounts/reports + // ====================================== + /** + * POST /api/v1/reports + * + * @param account_id Target account ID. + * @param options.status_ids Array of Statuses ids to attach to the report. + * @param options.comment The reason for the report. Default maximum of 1000 characters. + * @param options.forward If the account is remote, should the report be forwarded to the remote admin? + * @param options.category Specify if the report is due to spam, violation of enumerated instance rules, or some other reason. Defaults to other. Will be set to violation if rule_ids[] is provided (regardless of any category value you provide). + * @param options.rule_ids For violation category reports, specify the ID of the exact rules broken. Rules and their IDs are available via GET /api/v1/instance/rules and GET /api/v1/instance. + * @return Report + */ + public async report( + account_id: string, + options?: { + status_ids?: Array<string> + comment: string + forward?: boolean + category?: Entity.Category + rule_ids?: Array<number> + } + ): Promise<Response<Entity.Report>> { + let params = { + account_id: account_id + } + if (options) { + if (options.status_ids) { + params = Object.assign(params, { + status_ids: options.status_ids + }) + } + if (options.comment) { + params = Object.assign(params, { + comment: options.comment + }) + } + if (options.forward !== undefined) { + params = Object.assign(params, { + forward: options.forward + }) + } + if (options.category) { + params = Object.assign(params, { + category: options.category + }) + } + if (options.rule_ids) { + params = Object.assign(params, { + rule_ids: options.rule_ids + }) + } + } + return this.client.post<MastodonAPI.Entity.Report>('/api/v1/reports', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.report(res.data) + }) + }) + } + + // ====================================== + // accounts/follow_requests + // ====================================== + /** + * GET /api/v1/follow_requests + * + * @param limit Maximum number of results. + * @return Array of account. + */ + public async getFollowRequests(limit?: number): Promise<Response<Array<Entity.Account>>> { + if (limit) { + return this.client + .get<Array<MastodonAPI.Entity.Account>>('/api/v1/follow_requests', { + limit: limit + }) + .then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } else { + return this.client.get<Array<MastodonAPI.Entity.Account>>('/api/v1/follow_requests').then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + } + + /** + * POST /api/v1/follow_requests/:id/authorize + * + * @param id Target account ID. + * @return Relationship. + */ + public async acceptFollowRequest(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/follow_requests/${id}/authorize`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + /** + * POST /api/v1/follow_requests/:id/reject + * + * @param id Target account ID. + * @return Relationship. + */ + public async rejectFollowRequest(id: string): Promise<Response<Entity.Relationship>> { + return this.client.post<MastodonAPI.Entity.Relationship>(`/api/v1/follow_requests/${id}/reject`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.relationship(res.data) + }) + }) + } + + // ====================================== + // accounts/endorsements + // ====================================== + /** + * GET /api/v1/endorsements + * + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @return Array of accounts. + */ + public async getEndorsements(options?: { limit?: number; max_id?: string; since_id?: string }): Promise<Response<Array<Entity.Account>>> { + let params = {} + if (options) { + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Account>>('/api/v1/endorsements', params).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + // ====================================== + // accounts/featured_tags + // ====================================== + /** + * GET /api/v1/featured_tags + * + * @return Array of featured tag. + */ + public async getFeaturedTags(): Promise<Response<Array<Entity.FeaturedTag>>> { + return this.client.get<Array<MastodonAPI.Entity.FeaturedTag>>('/api/v1/featured_tags').then(res => { + return Object.assign(res, { + data: res.data.map(f => MastodonAPI.Converter.featured_tag(f)) + }) + }) + } + + /** + * POST /api/v1/featured_tags + * + * @param name Target hashtag name. + * @return FeaturedTag. + */ + public async createFeaturedTag(name: string): Promise<Response<Entity.FeaturedTag>> { + return this.client + .post<MastodonAPI.Entity.FeaturedTag>('/api/v1/featured_tags', { + name: name + }) + .then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.featured_tag(res.data) + }) + }) + } + + /** + * DELETE /api/v1/featured_tags/:id + * + * @param id Target featured tag id. + * @return Empty + */ + public deleteFeaturedTag(id: string): Promise<Response<{}>> { + return this.client.del<{}>(`/api/v1/featured_tags/${id}`) + } + + /** + * GET /api/v1/featured_tags/suggestions + * + * @return Array of tag. + */ + public async getSuggestedTags(): Promise<Response<Array<Entity.Tag>>> { + return this.client.get<Array<MastodonAPI.Entity.Tag>>('/api/v1/featured_tags/suggestions').then(res => { + return Object.assign(res, { + data: res.data.map(t => MastodonAPI.Converter.tag(t)) + }) + }) + } + + // ====================================== + // accounts/preferences + // ====================================== + /** + * GET /api/v1/preferences + * + * @return Preferences. + */ + public async getPreferences(): Promise<Response<Entity.Preferences>> { + return this.client.get<MastodonAPI.Entity.Preferences>('/api/v1/preferences').then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.preferences(res.data) + }) + }) + } + + // ====================================== + // accounts/followed_tags + // ====================================== + /** + * GET /api/v1/followed_tags + * + * @return Array of Tag. + */ + public async getFollowedTags(): Promise<Response<Array<Entity.Tag>>> { + return this.client.get<Array<MastodonAPI.Entity.Tag>>('/api/v1/followed_tags').then(res => { + return Object.assign(res, { + data: res.data.map(tag => MastodonAPI.Converter.tag(tag)) + }) + }) + } + + // ====================================== + // accounts/suggestions + // ====================================== + /** + * GET /api/v1/suggestions + * + * @param limit Maximum number of results. + * @return Array of accounts. + */ + public async getSuggestions(limit?: number): Promise<Response<Array<Entity.Account>>> { + if (limit) { + return this.client + .get<Array<MastodonAPI.Entity.Account>>('/api/v1/suggestions', { + limit: limit + }) + .then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } else { + return this.client.get<Array<MastodonAPI.Entity.Account>>('/api/v1/suggestions').then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + } + + // ====================================== + // accounts/tags + // ====================================== + /** + * GET /api/v1/tags/:id + * + * @param id Target hashtag id. + * @return Tag + */ + public async getTag(id: string): Promise<Response<Entity.Tag>> { + return this.client.get<MastodonAPI.Entity.Tag>(`/api/v1/tags/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.tag(res.data) + }) + }) + } + + /** + * POST /api/v1/tags/:id/follow + * + * @param id Target hashtag id. + * @return Tag + */ + public async followTag(id: string): Promise<Response<Entity.Tag>> { + return this.client.post<MastodonAPI.Entity.Tag>(`/api/v1/tags/${id}/follow`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.tag(res.data) + }) + }) + } + + /** + * POST /api/v1/tags/:id/unfollow + * + * @param id Target hashtag id. + * @return Tag + */ + public async unfollowTag(id: string): Promise<Response<Entity.Tag>> { + return this.client.post<MastodonAPI.Entity.Tag>(`/api/v1/tags/${id}/unfollow`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.tag(res.data) + }) + }) + } + + // ====================================== + // statuses + // ====================================== + /** + * POST /api/v1/statuses + * + * @param status Text content of status. + * @param options.media_ids Array of Attachment ids. + * @param options.poll Poll object. + * @param options.in_reply_to_id ID of the status being replied to, if status is a reply. + * @param options.sensitive Mark status and attached media as sensitive? + * @param options.spoiler_text Text to be shown as a warning or subject before the actual content. + * @param options.visibility Visibility of the posted status. + * @param options.scheduled_at ISO 8601 Datetime at which to schedule a status. + * @param options.language ISO 639 language code for this status. + * @param options.quote_id ID of the status being quoted to, if status is a quote. + * @return Status. When options.scheduled_at is present, ScheduledStatus is returned instead. + */ + public async postStatus( + status: string, + options: { + media_ids?: Array<string> + poll?: { options: Array<string>; expires_in: number; multiple?: boolean; hide_totals?: boolean } + in_reply_to_id?: string + sensitive?: boolean + spoiler_text?: string + visibility?: 'public' | 'unlisted' | 'private' | 'direct' + scheduled_at?: string + language?: string + quote_id?: string + } + ): Promise<Response<Entity.Status | Entity.ScheduledStatus>> { + let params = { + status: status + } + if (options) { + if (options.media_ids) { + params = Object.assign(params, { + media_ids: options.media_ids + }) + } + if (options.poll) { + let pollParam = { + options: options.poll.options, + expires_in: options.poll.expires_in + } + if (options.poll.multiple !== undefined) { + pollParam = Object.assign(pollParam, { + multiple: options.poll.multiple + }) + } + if (options.poll.hide_totals !== undefined) { + pollParam = Object.assign(pollParam, { + hide_totals: options.poll.hide_totals + }) + } + params = Object.assign(params, { + poll: pollParam + }) + } + if (options.in_reply_to_id) { + params = Object.assign(params, { + in_reply_to_id: options.in_reply_to_id + }) + } + if (options.sensitive !== undefined) { + params = Object.assign(params, { + sensitive: options.sensitive + }) + } + if (options.spoiler_text) { + params = Object.assign(params, { + spoiler_text: options.spoiler_text + }) + } + if (options.visibility) { + params = Object.assign(params, { + visibility: options.visibility + }) + } + if (options.scheduled_at) { + params = Object.assign(params, { + scheduled_at: options.scheduled_at + }) + } + if (options.language) { + params = Object.assign(params, { + language: options.language + }) + } + if (options.quote_id) { + params = Object.assign(params, { + quote_id: options.quote_id + }) + } + } + if (options && options.scheduled_at) { + return this.client.post<MastodonAPI.Entity.ScheduledStatus>('/api/v1/statuses', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.scheduled_status(res.data) + }) + }) + } + return this.client.post<MastodonAPI.Entity.Status>('/api/v1/statuses', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * GET /api/v1/statuses/:id + * + * @param id The target status id. + * @return Status + */ + public async getStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.get<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + PUT /api/v1/statuses/:id + * + * @param id The target status id. + * @return Status + */ + public async editStatus( + id: string, + options: { + status?: string + spoiler_text?: string + sensitive?: boolean + media_ids?: Array<string> + poll?: { options?: Array<string>; expires_in?: number; multiple?: boolean; hide_totals?: boolean } + } + ): Promise<Response<Entity.Status>> { + let params = {} + if (options.status) { + params = Object.assign(params, { + status: options.status + }) + } + if (options.spoiler_text) { + params = Object.assign(params, { + spoiler_text: options.spoiler_text + }) + } + if (options.sensitive) { + params = Object.assign(params, { + sensitive: options.sensitive + }) + } + if (options.media_ids) { + params = Object.assign(params, { + media_ids: options.media_ids + }) + } + if (options.poll) { + let pollParam = {} + if (options.poll.options !== undefined) { + pollParam = Object.assign(pollParam, { + options: options.poll.options + }) + } + if (options.poll.expires_in !== undefined) { + pollParam = Object.assign(pollParam, { + expires_in: options.poll.expires_in + }) + } + if (options.poll.multiple !== undefined) { + pollParam = Object.assign(pollParam, { + multiple: options.poll.multiple + }) + } + if (options.poll.hide_totals !== undefined) { + pollParam = Object.assign(pollParam, { + hide_totals: options.poll.hide_totals + }) + } + params = Object.assign(params, { + poll: pollParam + }) + } + return this.client.put<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}`, params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * DELETE /api/v1/statuses/:id + * + * @param id The target status id. + * @return Status + */ + public async deleteStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.del<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * GET /api/v1/statuses/:id/context + * + * Get parent and child statuses. + * @param id The target status id. + * @return Context + */ + public async getStatusContext( + id: string, + options?: { limit?: number; max_id?: string; since_id?: string } + ): Promise<Response<Entity.Context>> { + let params = {} + if (options) { + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + } + return this.client.get<MastodonAPI.Entity.Context>(`/api/v1/statuses/${id}/context`, params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.context(res.data) + }) + }) + } + + /** + * GET /api/v1/statuses/:id/source + * + * Obtain the source properties for a status so that it can be edited. + * @param id The target status id. + * @return StatusSource + */ + public async getStatusSource(id: string): Promise<Response<Entity.StatusSource>> { + return this.client.get<MastodonAPI.Entity.StatusSource>(`/api/v1/statuses/${id}/source`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status_source(res.data) + }) + }) + } + + /** + * GET /api/v1/statuses/:id/reblogged_by + * + * @param id The target status id. + * @return Array of accounts. + */ + public async getStatusRebloggedBy(id: string): Promise<Response<Array<Entity.Account>>> { + return this.client.get<Array<MastodonAPI.Entity.Account>>(`/api/v1/statuses/${id}/reblogged_by`).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + /** + * GET /api/v1/statuses/:id/favourited_by + * + * @param id The target status id. + * @return Array of accounts. + */ + public async getStatusFavouritedBy(id: string): Promise<Response<Array<Entity.Account>>> { + return this.client.get<Array<MastodonAPI.Entity.Account>>(`/api/v1/statuses/${id}/favourited_by`).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/favourite + * + * @param id The target status id. + * @return Status. + */ + public async favouriteStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/favourite`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/unfavourite + * + * @param id The target status id. + * @return Status. + */ + public async unfavouriteStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/unfavourite`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/reblog + * + * @param id The target status id. + * @return Status. + */ + public async reblogStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/reblog`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/unreblog + * + * @param id The target status id. + * @return Status. + */ + public async unreblogStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/unreblog`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/bookmark + * + * @param id The target status id. + * @return Status. + */ + public async bookmarkStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/bookmark`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/unbookmark + * + * @param id The target status id. + * @return Status. + */ + public async unbookmarkStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/unbookmark`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/mute + * + * @param id The target status id. + * @return Status + */ + public async muteStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/mute`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/unmute + * + * @param id The target status id. + * @return Status + */ + public async unmuteStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/unmute`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/pin + * @param id The target status id. + * @return Status + */ + public async pinStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/pin`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + /** + * POST /api/v1/statuses/:id/unpin + * + * @param id The target status id. + * @return Status + */ + public async unpinStatus(id: string): Promise<Response<Entity.Status>> { + return this.client.post<MastodonAPI.Entity.Status>(`/api/v1/statuses/${id}/unpin`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.status(res.data) + }) + }) + } + + // ====================================== + // statuses/media + // ====================================== + /** + * POST /api/v2/media + * + * @param file The file to be attached, using multipart form data. + * @param options.description A plain-text description of the media. + * @param options.focus Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. + * @return Attachment + */ + public async uploadMedia( + file: any, + options?: { description?: string; focus?: string } + ): Promise<Response<Entity.Attachment | Entity.AsyncAttachment>> { + const formData = new FormData() + formData.append('file', file) + if (options) { + if (options.description) { + formData.append('description', options.description) + } + if (options.focus) { + formData.append('focus', options.focus) + } + } + return this.client.postForm<MastodonAPI.Entity.AsyncAttachment>('/api/v2/media', formData).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.async_attachment(res.data) + }) + }) + } + + /** + * GET /api/v1/media/:id + * + * @param id Target media ID. + * @return Attachment + */ + public async getMedia(id: string): Promise<Response<Entity.Attachment>> { + const res = await this.client.get<MastodonAPI.Entity.Attachment>(`/api/v1/media/${id}`) + + return Object.assign(res, { + data: MastodonAPI.Converter.attachment(res.data) + }) + } + + /** + * PUT /api/v1/media/:id + * + * @param id Target media ID. + * @param options.file The file to be attached, using multipart form data. + * @param options.description A plain-text description of the media. + * @param options.focus Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. + * @param options.is_sensitive Whether the media is sensitive. + * @return Attachment + */ + public async updateMedia( + id: string, + options?: { + file?: any + description?: string + focus?: string + } + ): Promise<Response<Entity.Attachment>> { + const formData = new FormData() + if (options) { + if (options.file) { + formData.append('file', options.file) + } + if (options.description) { + formData.append('description', options.description) + } + if (options.focus) { + formData.append('focus', options.focus) + } + } + return this.client.putForm<MastodonAPI.Entity.Attachment>(`/api/v1/media/${id}`, formData).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.attachment(res.data) + }) + }) + } + + // ====================================== + // statuses/polls + // ====================================== + /** + * GET /api/v1/polls/:id + * + * @param id Target poll ID. + * @return Poll + */ + public async getPoll(id: string): Promise<Response<Entity.Poll>> { + return this.client.get<MastodonAPI.Entity.Poll>(`/api/v1/polls/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.poll(res.data) + }) + }) + } + + /** + * POST /api/v1/polls/:id/votes + * + * @param id Target poll ID. + * @param choices Array of own votes containing index for each option (starting from 0). + * @return Poll + */ + public async votePoll(id: string, choices: Array<number>): Promise<Response<Entity.Poll>> { + return this.client + .post<MastodonAPI.Entity.Poll>(`/api/v1/polls/${id}/votes`, { + choices: choices + }) + .then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.poll(res.data) + }) + }) + } + + // ====================================== + // statuses/scheduled_statuses + // ====================================== + /** + * GET /api/v1/scheduled_statuses + * + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of scheduled statuses. + */ + public async getScheduledStatuses(options?: { + limit?: number | null + max_id?: string | null + since_id?: string | null + min_id?: string | null + }): Promise<Response<Array<Entity.ScheduledStatus>>> { + let params = {} + if (options) { + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.ScheduledStatus>>('/api/v1/scheduled_statuses', params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.scheduled_status(s)) + }) + }) + } + + /** + * GET /api/v1/scheduled_statuses/:id + * + * @param id Target status ID. + * @return ScheduledStatus. + */ + public async getScheduledStatus(id: string): Promise<Response<Entity.ScheduledStatus>> { + return this.client.get<MastodonAPI.Entity.ScheduledStatus>(`/api/v1/scheduled_statuses/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.scheduled_status(res.data) + }) + }) + } + + /** + * PUT /api/v1/scheduled_statuses/:id + * + * @param id Target scheduled status ID. + * @param scheduled_at ISO 8601 Datetime at which the status will be published. + * @return ScheduledStatus. + */ + public async scheduleStatus(id: string, scheduled_at?: string | null): Promise<Response<Entity.ScheduledStatus>> { + let params = {} + if (scheduled_at) { + params = Object.assign(params, { + scheduled_at: scheduled_at + }) + } + return this.client.put<MastodonAPI.Entity.ScheduledStatus>(`/api/v1/scheduled_statuses/${id}`, params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.scheduled_status(res.data) + }) + }) + } + + /** + * DELETE /api/v1/scheduled_statuses/:id + * + * @param id Target scheduled status ID. + */ + public cancelScheduledStatus(id: string): Promise<Response<{}>> { + return this.client.del<{}>(`/api/v1/scheduled_statuses/${id}`) + } + + // ====================================== + // timelines + // ====================================== + /** + * GET /api/v1/timelines/public + * + * @param options.only_media Show only statuses with media attached? Defaults to false. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getPublicTimeline(options?: { + only_media?: boolean + limit?: number + max_id?: string + since_id?: string + min_id?: string + }): Promise<Response<Array<Entity.Status>>> { + let params = { + local: false + } + if (options) { + if (options.only_media !== undefined) { + params = Object.assign(params, { + only_media: options.only_media + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>('/api/v1/timelines/public', params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + /** + * GET /api/v1/timelines/public + * + * @param options.only_media Show only statuses with media attached? Defaults to false. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getLocalTimeline(options?: { + only_media?: boolean + limit?: number + max_id?: string + since_id?: string + min_id?: string + }): Promise<Response<Array<Entity.Status>>> { + let params = { + local: true + } + if (options) { + if (options.only_media !== undefined) { + params = Object.assign(params, { + only_media: options.only_media + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>('/api/v1/timelines/public', params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + /** + * GET /api/v1/timelines/tag/:hashtag + * + * @param hashtag Content of a #hashtag, not including # symbol. + * @param options.local Show only local statuses? Defaults to false. + * @param options.only_media Show only statuses with media attached? Defaults to false. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getTagTimeline( + hashtag: string, + options?: { + local?: boolean + only_media?: boolean + limit?: number + max_id?: string + since_id?: string + min_id?: string + } + ): Promise<Response<Array<Entity.Status>>> { + let params = {} + if (options) { + if (options.local !== undefined) { + params = Object.assign(params, { + local: options.local + }) + } + if (options.only_media !== undefined) { + params = Object.assign(params, { + only_media: options.only_media + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>(`/api/v1/timelines/tag/${hashtag}`, params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + /** + * GET /api/v1/timelines/home + * + * @param options.local Show only local statuses? Defaults to false. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getHomeTimeline(options?: { + local?: boolean + limit?: number + max_id?: string + since_id?: string + min_id?: string + }): Promise<Response<Array<Entity.Status>>> { + let params = {} + if (options) { + if (options.local !== undefined) { + params = Object.assign(params, { + local: options.local + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>('/api/v1/timelines/home', params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + /** + * GET /api/v1/timelines/list/:list_id + * + * @param list_id Local ID of the list in the database. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getListTimeline( + list_id: string, + options?: { + limit?: number + max_id?: string + since_id?: string + min_id?: string + } + ): Promise<Response<Array<Entity.Status>>> { + let params = {} + if (options) { + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Status>>(`/api/v1/timelines/list/${list_id}`, params).then(res => { + return Object.assign(res, { + data: res.data.map(s => MastodonAPI.Converter.status(s)) + }) + }) + } + + // ====================================== + // timelines/conversations + // ====================================== + /** + * GET /api/v1/conversations + * + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public async getConversationTimeline(options?: { + limit?: number + max_id?: string + since_id?: string + min_id?: string + }): Promise<Response<Array<Entity.Conversation>>> { + let params = {} + if (options) { + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Conversation>>('/api/v1/conversations', params).then(res => { + return Object.assign(res, { + data: res.data.map(c => MastodonAPI.Converter.conversation(c)) + }) + }) + } + + /** + * DELETE /api/v1/conversations/:id + * + * @param id Target conversation ID. + */ + public deleteConversation(id: string): Promise<Response<{}>> { + return this.client.del<{}>(`/api/v1/conversations/${id}`) + } + + /** + * POST /api/v1/conversations/:id/read + * + * @param id Target conversation ID. + * @return Conversation. + */ + public async readConversation(id: string): Promise<Response<Entity.Conversation>> { + return this.client.post<MastodonAPI.Entity.Conversation>(`/api/v1/conversations/${id}/read`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.conversation(res.data) + }) + }) + } + + // ====================================== + // timelines/lists + // ====================================== + /** + * GET /api/v1/lists + * + * @return Array of lists. + */ + public async getLists(): Promise<Response<Array<Entity.List>>> { + return this.client.get<Array<MastodonAPI.Entity.List>>('/api/v1/lists').then(res => { + return Object.assign(res, { + data: res.data.map(l => MastodonAPI.Converter.list(l)) + }) + }) + } + + /** + * GET /api/v1/lists/:id + * + * @param id Target list ID. + * @return List. + */ + public async getList(id: string): Promise<Response<Entity.List>> { + return this.client.get<MastodonAPI.Entity.List>(`/api/v1/lists/${id}`).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.list(res.data) + }) + }) + } + + /** + * POST /api/v1/lists + * + * @param title List name. + * @return List. + */ + public async createList(title: string): Promise<Response<Entity.List>> { + return this.client + .post<MastodonAPI.Entity.List>('/api/v1/lists', { + title: title + }) + .then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.list(res.data) + }) + }) + } + + /** + * PUT /api/v1/lists/:id + * + * @param id Target list ID. + * @param title New list name. + * @return List. + */ + public async updateList(id: string, title: string): Promise<Response<Entity.List>> { + return this.client + .put<MastodonAPI.Entity.List>(`/api/v1/lists/${id}`, { + title: title + }) + .then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.list(res.data) + }) + }) + } + + /** + * DELETE /api/v1/lists/:id + * + * @param id Target list ID. + */ + public deleteList(id: string): Promise<Response<{}>> { + return this.client.del<{}>(`/api/v1/lists/${id}`) + } + + /** + * GET /api/v1/lists/:id/accounts + * + * @param id Target list ID. + * @param options.limit Max number of results to return. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of accounts. + */ + public async getAccountsInList( + id: string, + options?: { + limit?: number + max_id?: string + since_id?: string + } + ): Promise<Response<Array<Entity.Account>>> { + let params = {} + if (options) { + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Account>>(`/api/v1/lists/${id}/accounts`, params).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + /** + * POST /api/v1/lists/:id/accounts + * + * @param id Target list ID. + * @param account_ids Array of account IDs to add to the list. + */ + public addAccountsToList(id: string, account_ids: Array<string>): Promise<Response<{}>> { + return this.client.post<{}>(`/api/v1/lists/${id}/accounts`, { + account_ids: account_ids + }) + } + + /** + * DELETE /api/v1/lists/:id/accounts + * + * @param id Target list ID. + * @param account_ids Array of account IDs to add to the list. + */ + public deleteAccountsFromList(id: string, account_ids: Array<string>): Promise<Response<{}>> { + return this.client.del<{}>(`/api/v1/lists/${id}/accounts`, { + account_ids: account_ids + }) + } + + // ====================================== + // timelines/markers + // ====================================== + /** + * GET /api/v1/markers + * + * @param timelines Array of timeline names, String enum anyOf home, notifications. + * @return Marker or empty object. + */ + public async getMarkers(timeline: Array<string>): Promise<Response<Entity.Marker | Record<never, never>>> { + return this.client + .get<MastodonAPI.Entity.Marker | Record<never, never>>('/api/v1/markers', { + timeline: timeline + }) + .then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.marker(res.data) + }) + }) + } + + /** + * POST /api/v1/markers + * + * @param options.home Marker position of the last read status ID in home timeline. + * @param options.notifications Marker position of the last read notification ID in notifications. + * @return Marker. + */ + public async saveMarkers(options?: { + home?: { last_read_id: string } + notifications?: { last_read_id: string } + }): Promise<Response<Entity.Marker>> { + let params = {} + if (options) { + if (options.home) { + params = Object.assign(params, { + home: options.home + }) + } + if (options.notifications) { + params = Object.assign(params, { + notifications: options.notifications + }) + } + } + return this.client.post<MastodonAPI.Entity.Marker>('/api/v1/markers', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.marker(res.data) + }) + }) + } + + // ====================================== + // notifications + // ====================================== + /** + * GET /api/v1/notifications + * + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @param options.exclude_types Array of types to exclude. + * @param options.account_id Return only notifications received from this account. + * @return Array of notifications. + */ + public async getNotifications(options?: { + limit?: number + max_id?: string + since_id?: string + min_id?: string + exclude_types?: Array<Entity.NotificationType> + account_id?: string + }): Promise<Response<Array<Entity.Notification>>> { + let params = {} + if (options) { + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.since_id) { + params = Object.assign(params, { + since_id: options.since_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.exclude_types) { + params = Object.assign(params, { + exclude_types: options.exclude_types.map(e => MastodonAPI.Converter.encodeNotificationType(e)) + }) + } + if (options.account_id) { + params = Object.assign(params, { + account_id: options.account_id + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Notification>>('/api/v1/notifications', params).then(res => { + return Object.assign(res, { + data: res.data.flatMap(n => { + const notify = MastodonAPI.Converter.notification(n) + if (notify instanceof UnknownNotificationTypeError) return [] + return notify + }) + }) + }) + } + + /** + * GET /api/v1/notifications/:id + * + * @param id Target notification ID. + * @return Notification. + */ + public async getNotification(id: string): Promise<Response<Entity.Notification>> { + const res = await this.client.get<MastodonAPI.Entity.Notification>(`/api/v1/notifications/${id}`) + const notify = MastodonAPI.Converter.notification(res.data) + if (notify instanceof UnknownNotificationTypeError) { + throw new UnknownNotificationTypeError() + } + return { ...res, data: notify } + } + + /** + * POST /api/v1/notifications/clear + */ + public dismissNotifications(): Promise<Response<{}>> { + return this.client.post<{}>('/api/v1/notifications/clear') + } + + /** + * POST /api/v1/notifications/:id/dismiss + * + * @param id Target notification ID. + */ + public dismissNotification(id: string): Promise<Response<{}>> { + return this.client.post<{}>(`/api/v1/notifications/${id}/dismiss`) + } + + public readNotifications(_options: { + id?: string + max_id?: string + }): Promise<Response<Entity.Notification | Array<Entity.Notification>>> { + return new Promise((_, reject) => { + const err = new NoImplementedError('mastodon does not support') + reject(err) + }) + } + + // ====================================== + // notifications/push + // ====================================== + /** + * POST /api/v1/push/subscription + * + * @param subscription[endpoint] Endpoint URL that is called when a notification event occurs. + * @param subscription[keys][p256dh] User agent public key. Base64 encoded string of public key of ECDH key using prime256v1 curve. + * @param subscription[keys] Auth secret. Base64 encoded string of 16 bytes of random data. + * @param data[alerts][follow] Receive follow notifications? + * @param data[alerts][favourite] Receive favourite notifications? + * @param data[alerts][reblog] Receive reblog notifictaions? + * @param data[alerts][mention] Receive mention notifications? + * @param data[alerts][poll] Receive poll notifications? + * @return PushSubscription. + */ + public async subscribePushNotification( + subscription: { endpoint: string; keys: { p256dh: string; auth: string } }, + data?: { alerts: { follow?: boolean; favourite?: boolean; reblog?: boolean; mention?: boolean; poll?: boolean } } | null + ): Promise<Response<Entity.PushSubscription>> { + let params = { + subscription + } + if (data) { + params = Object.assign(params, { + data + }) + } + return this.client.post<MastodonAPI.Entity.PushSubscription>('/api/v1/push/subscription', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.push_subscription(res.data) + }) + }) + } + + /** + * GET /api/v1/push/subscription + * + * @return PushSubscription. + */ + public async getPushSubscription(): Promise<Response<Entity.PushSubscription>> { + return this.client.get<MastodonAPI.Entity.PushSubscription>('/api/v1/push/subscription').then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.push_subscription(res.data) + }) + }) + } + + /** + * PUT /api/v1/push/subscription + * + * @param data[alerts][follow] Receive follow notifications? + * @param data[alerts][favourite] Receive favourite notifications? + * @param data[alerts][reblog] Receive reblog notifictaions? + * @param data[alerts][mention] Receive mention notifications? + * @param data[alerts][poll] Receive poll notifications? + * @return PushSubscription. + */ + public async updatePushSubscription( + data?: { alerts: { follow?: boolean; favourite?: boolean; reblog?: boolean; mention?: boolean; poll?: boolean } } | null + ): Promise<Response<Entity.PushSubscription>> { + let params = {} + if (data) { + params = Object.assign(params, { + data + }) + } + return this.client.put<MastodonAPI.Entity.PushSubscription>('/api/v1/push/subscription', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.push_subscription(res.data) + }) + }) + } + + /** + * DELETE /api/v1/push/subscription + */ + public deletePushSubscription(): Promise<Response<{}>> { + return this.client.del<{}>('/api/v1/push/subscription') + } + + // ====================================== + // search + // ====================================== + /** + * GET /api/v2/search + * + * @param q The search query. + * @param type Enum of search target. + * @param options.limit Maximum number of results to load, per type. Defaults to 20. Max 40. + * @param options.max_id Return results older than this id. + * @param options.min_id Return results immediately newer than this id. + * @param options.resolve Attempt WebFinger lookup. Defaults to false. + * @param options.following Only include accounts that the user is following. Defaults to false. + * @param options.account_id If provided, statuses returned will be authored only by this account. + * @param options.exclude_unreviewed Filter out unreviewed tags? Defaults to false. + * @return Results. + */ + public async search( + q: string, + options?: { + type?: 'accounts' | 'hashtags' | 'statuses' + limit?: number + max_id?: string + min_id?: string + resolve?: boolean + offset?: number + following?: boolean + account_id?: string + exclude_unreviewed?: boolean + } + ): Promise<Response<Entity.Results>> { + let params = { + q + } + if (options) { + if (options.type) { + params = Object.assign(params, { + type: options.type + }) + } + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.max_id) { + params = Object.assign(params, { + max_id: options.max_id + }) + } + if (options.min_id) { + params = Object.assign(params, { + min_id: options.min_id + }) + } + if (options.resolve !== undefined) { + params = Object.assign(params, { + resolve: options.resolve + }) + } + if (options.offset) { + params = Object.assign(params, { + offset: options.offset + }) + } + if (options.following !== undefined) { + params = Object.assign(params, { + following: options.following + }) + } + if (options.account_id) { + params = Object.assign(params, { + account_id: options.account_id + }) + } + if (options.exclude_unreviewed) { + params = Object.assign(params, { + exclude_unreviewed: options.exclude_unreviewed + }) + } + } + return this.client.get<MastodonAPI.Entity.Results>('/api/v2/search', params).then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.results(res.data) + }) + }) + } + + // ====================================== + // instance + // ====================================== + /** + * GET /api/v1/instance + */ + public async getInstance(): Promise<Response<Entity.Instance>> { + return this.client.get<MastodonAPI.Entity.Instance>('/api/v1/instance').then(res => { + return Object.assign(res, { + data: MastodonAPI.Converter.instance(res.data) + }) + }) + } + + /** + * GET /api/v1/instance/peers + */ + public getInstancePeers(): Promise<Response<Array<string>>> { + return this.client.get<Array<string>>('/api/v1/instance/peers') + } + + /** + * GET /api/v1/instance/activity + */ + public async getInstanceActivity(): Promise<Response<Array<Entity.Activity>>> { + return this.client.get<Array<MastodonAPI.Entity.Activity>>('/api/v1/instance/activity').then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.activity(a)) + }) + }) + } + + // ====================================== + // instance/trends + // ====================================== + /** + * GET /api/v1/trends + * + * @param limit Maximum number of results to return. Defaults to 10. + */ + public async getInstanceTrends(limit?: number | null): Promise<Response<Array<Entity.Tag>>> { + let params = {} + if (limit) { + params = Object.assign(params, { + limit + }) + } + return this.client.get<Array<MastodonAPI.Entity.Tag>>('/api/v1/trends', params).then(res => { + return Object.assign(res, { + data: res.data.map(t => MastodonAPI.Converter.tag(t)) + }) + }) + } + + // ====================================== + // instance/directory + // ====================================== + /** + * GET /api/v1/directory + * + * @param options.limit How many accounts to load. Default 40. + * @param options.offset How many accounts to skip before returning results. Default 0. + * @param options.order Order of results. + * @param options.local Only return local accounts. + * @return Array of accounts. + */ + public async getInstanceDirectory(options?: { + limit?: number + offset?: number + order?: 'active' | 'new' + local?: boolean + }): Promise<Response<Array<Entity.Account>>> { + let params = {} + if (options) { + if (options.limit) { + params = Object.assign(params, { + limit: options.limit + }) + } + if (options.offset) { + params = Object.assign(params, { + offset: options.offset + }) + } + if (options.order) { + params = Object.assign(params, { + order: options.order + }) + } + if (options.local !== undefined) { + params = Object.assign(params, { + local: options.local + }) + } + } + return this.client.get<Array<MastodonAPI.Entity.Account>>('/api/v1/directory', params).then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.account(a)) + }) + }) + } + + // ====================================== + // instance/custom_emojis + // ====================================== + /** + * GET /api/v1/custom_emojis + * + * @return Array of emojis. + */ + public async getInstanceCustomEmojis(): Promise<Response<Array<Entity.Emoji>>> { + return this.client.get<Array<MastodonAPI.Entity.Emoji>>('/api/v1/custom_emojis').then(res => { + return Object.assign(res, { + data: res.data.map(e => MastodonAPI.Converter.emoji(e)) + }) + }) + } + + // ====================================== + // instance/announcements + // ====================================== + /** + * GET /api/v1/announcements + * + * @return Array of announcements. + */ + public async getInstanceAnnouncements(): Promise<Response<Array<Entity.Announcement>>> { + return this.client.get<Array<MastodonAPI.Entity.Announcement>>('/api/v1/announcements').then(res => { + return Object.assign(res, { + data: res.data.map(a => MastodonAPI.Converter.announcement(a)) + }) + }) + } + + /** + * POST /api/v1/announcements/:id/dismiss + * + * @param id The ID of the Announcement in the database. + */ + public async dismissInstanceAnnouncement(id: string): Promise<Response<Record<never, never>>> { + return this.client.post<Record<never, never>>(`/api/v1/announcements/${id}/dismiss`) + } + + /** + * PUT /api/v1/announcements/:id/reactions/:name + * + * @param id The ID of the Announcement in the database. + * @param name Unicode emoji, or the shortcode of a custom emoji. + */ + public async addReactionToAnnouncement(id: string, name: string): Promise<Response<Record<never, never>>> { + return this.client.put<Record<never, never>>(`/api/v1/announcements/${id}/reactions/${name}`) + } + + /** + * DELETE /api/v1/announcements/:id/reactions/:name + * + * @param id The ID of the Announcement in the database. + * @param name Unicode emoji, or the shortcode of a custom emoji. + */ + public async removeReactionFromAnnouncement(id: string, name: string): Promise<Response<Record<never, never>>> { + return this.client.del<Record<never, never>>(`/api/v1/announcements/${id}/reactions/${name}`) + } + + // ====================================== + // Emoji reactions + // ====================================== + public async createEmojiReaction(_id: string, _emoji: string): Promise<Response<Entity.Status>> { + return new Promise((_, reject) => { + const err = new NoImplementedError('mastodon does not support') + reject(err) + }) + } + + public async deleteEmojiReaction(_id: string, _emoji: string): Promise<Response<Entity.Status>> { + return new Promise((_, reject) => { + const err = new NoImplementedError('mastodon does not support') + reject(err) + }) + } + + public async getEmojiReactions(_id: string): Promise<Response<Array<Entity.Reaction>>> { + return new Promise((_, reject) => { + const err = new NoImplementedError('mastodon does not support') + reject(err) + }) + } + + public async getEmojiReaction(_id: string, _emoji: string): Promise<Response<Entity.Reaction>> { + return new Promise((_, reject) => { + const err = new NoImplementedError('mastodon does not support') + reject(err) + }) + } + + // ====================================== + // WebSocket + // ====================================== + public userSocket(): WebSocket { + return this.client.socket('/api/v1/streaming', 'user') + } + + public publicSocket(): WebSocket { + return this.client.socket('/api/v1/streaming', 'public') + } + + public localSocket(): WebSocket { + return this.client.socket('/api/v1/streaming', 'public:local') + } + + public tagSocket(tag: string): WebSocket { + return this.client.socket('/api/v1/streaming', 'hashtag', `tag=${tag}`) + } + + public listSocket(list_id: string): WebSocket { + return this.client.socket('/api/v1/streaming', 'list', `list=${list_id}`) + } + + public directSocket(): WebSocket { + return this.client.socket('/api/v1/streaming', 'direct') + } +} |