diff --git a/packages/pl-fe/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
similarity index 100%
rename from packages/pl-fe/CODE_OF_CONDUCT.md
rename to CODE_OF_CONDUCT.md
diff --git a/package.json b/package.json
index 7abf63df9..b1b0b5b5f 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,6 @@
"husky": "^9.0.0",
"lint-staged": ">=10"
},
- "workspaces": ["pl-fe"],
+ "workspaces": ["pl-api", "pl-fe"],
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
diff --git a/packages/pl-api/.eslintignore b/packages/pl-api/.eslintignore
new file mode 100644
index 000000000..256b5ff45
--- /dev/null
+++ b/packages/pl-api/.eslintignore
@@ -0,0 +1,7 @@
+/node_modules/**
+/dist/**
+/static/**
+/public/**
+/tmp/**
+/coverage/**
+/custom/**
diff --git a/packages/pl-api/.eslintrc.json b/packages/pl-api/.eslintrc.json
new file mode 100644
index 000000000..15ab81b31
--- /dev/null
+++ b/packages/pl-api/.eslintrc.json
@@ -0,0 +1,214 @@
+{
+ "root": true,
+ "extends": [
+ "eslint:recommended",
+ "plugin:import/typescript",
+ "plugin:compat/recommended"
+ ],
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true,
+ "jest": true
+ },
+ "globals": {
+ "ATTACHMENT_HOST": false
+ },
+ "plugins": [
+ "import",
+ "promise",
+ "@typescript-eslint"
+ ],
+ "parserOptions": {
+ "sourceType": "module",
+ "ecmaFeatures": {
+ "experimentalObjectRestSpread": true
+ },
+ "ecmaVersion": 2018
+ },
+ "settings": {
+ "import/extensions": [
+ ".js",
+ ".cjs",
+ ".mjs",
+ ".ts"
+ ],
+ "import/ignore": [
+ "node_modules",
+ "\\.(css|scss|json)$"
+ ],
+ "import/resolver": {
+ "typescript": true,
+ "node": true
+ },
+ "polyfills": [
+ "es:all",
+ "fetch",
+ "IntersectionObserver",
+ "Promise",
+ "ResizeObserver",
+ "URL",
+ "URLSearchParams"
+ ],
+ "tailwindcss": {
+ "config": "tailwind.config.ts"
+ }
+ },
+ "rules": {
+ "brace-style": "error",
+ "comma-dangle": [
+ "error",
+ "always-multiline"
+ ],
+ "comma-spacing": [
+ "warn",
+ {
+ "before": false,
+ "after": true
+ }
+ ],
+ "comma-style": [
+ "warn",
+ "last"
+ ],
+ "import/no-duplicates": "error",
+ "space-before-function-paren": [
+ "error",
+ "never"
+ ],
+ "space-infix-ops": "error",
+ "space-in-parens": [
+ "error",
+ "never"
+ ],
+ "keyword-spacing": "error",
+ "dot-notation": "error",
+ "eqeqeq": "error",
+ "indent": [
+ "error",
+ 2,
+ {
+ "SwitchCase": 1,
+ "ignoredNodes": [
+ "TemplateLiteral"
+ ]
+ }
+ ],
+ "key-spacing": [
+ "error",
+ {
+ "mode": "minimum"
+ }
+ ],
+ "no-catch-shadow": "error",
+ "no-cond-assign": "error",
+ "no-console": [
+ "warn",
+ {
+ "allow": [
+ "error",
+ "warn"
+ ]
+ }
+ ],
+ "no-extra-semi": "error",
+ "no-const-assign": "error",
+ "no-fallthrough": "error",
+ "no-irregular-whitespace": "error",
+ "no-loop-func": "error",
+ "no-mixed-spaces-and-tabs": "error",
+ "no-nested-ternary": "warn",
+ "no-trailing-spaces": "warn",
+ "no-undef": "error",
+ "no-unreachable": "error",
+ "no-unused-expressions": "error",
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ "vars": "all",
+ "args": "none",
+ "ignoreRestSiblings": true
+ }
+ ],
+ "no-useless-escape": "warn",
+ "no-var": "error",
+ "object-curly-spacing": [
+ "error",
+ "always"
+ ],
+ "padded-blocks": [
+ "error",
+ {
+ "classes": "always"
+ }
+ ],
+ "prefer-const": "error",
+ "quotes": [
+ "error",
+ "single"
+ ],
+ "semi": "error",
+ "space-unary-ops": [
+ "error",
+ {
+ "words": true,
+ "nonwords": false
+ }
+ ],
+ "strict": "off",
+ "valid-typeof": "error",
+ "import/extensions": [
+ "error",
+ "always",
+ {
+ "js": "never",
+ "mjs": "ignorePackages",
+ "ts": "never"
+ }
+ ],
+ "import/newline-after-import": "error",
+ "import/no-extraneous-dependencies": "error",
+ "import/no-unresolved": "error",
+ "import/no-webpack-loader-syntax": "error",
+ "import/order": [
+ "error",
+ {
+ "groups": [
+ "builtin",
+ "external",
+ "internal",
+ "parent",
+ "sibling",
+ "index",
+ "object",
+ "type"
+ ],
+ "newlines-between": "always",
+ "alphabetize": {
+ "order": "asc"
+ }
+ }
+ ],
+ "@typescript-eslint/member-delimiter-style": "error",
+ "promise/catch-or-return": "error",
+ "sort-imports": [
+ "error",
+ {
+ "ignoreCase": true,
+ "ignoreDeclarationSort": true
+ }
+ ],
+ "eol-last": "error"
+ },
+ "overrides": [
+ {
+ "files": ["**/*.ts"],
+ "rules": {
+ "no-undef": "off",
+ "space-before-function-paren": "off"
+ },
+ "parser": "@typescript-eslint/parser"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/pl-api/.gitignore b/packages/pl-api/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/packages/pl-api/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/packages/pl-api/README.md b/packages/pl-api/README.md
new file mode 100644
index 000000000..73fa562aa
--- /dev/null
+++ b/packages/pl-api/README.md
@@ -0,0 +1,24 @@
+# `pl-api`
+
+This project should be considered unstable before the 1.0.0 release. I will not provide any changelog or information on breaking changes until then.
+
+## Projects using `pl-api`
+
+[`pl-fe`](https://github.com/mkljczk/pl-fe) is a web client for Mastodon-compatible servers forked from Soapbox. It uses `pl-api` for API interactions.
+
+## License
+
+`pl-api` utilizes code from [Soapbox](https://gitlab.com/soapbox-pub/soapbox) and bases off official [Mastodon documentation](https://docs.joinmastodon.org).
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
\ No newline at end of file
diff --git a/packages/pl-api/index.html b/packages/pl-api/index.html
new file mode 100644
index 000000000..99b1f02fa
--- /dev/null
+++ b/packages/pl-api/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ pl-api
+
+
+
+
+
+
diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts
new file mode 100644
index 000000000..5231fd217
--- /dev/null
+++ b/packages/pl-api/lib/client.ts
@@ -0,0 +1,3990 @@
+import z from 'zod';
+
+import {
+ accountSchema,
+ adminAccountSchema,
+ adminCanonicalEmailBlockSchema,
+ adminCohortSchema,
+ adminDimensionSchema,
+ adminDomainAllowSchema,
+ adminDomainBlockSchema,
+ adminEmailDomainBlockSchema,
+ adminIpBlockSchema,
+ adminMeasureSchema,
+ adminReportSchema,
+ adminTagSchema,
+ announcementSchema,
+ applicationSchema,
+ backupSchema,
+ bookmarkFolderSchema,
+ chatMessageSchema,
+ chatSchema,
+ contextSchema,
+ conversationSchema,
+ credentialAccountSchema,
+ customEmojiSchema,
+ domainBlockSchema,
+ emojiReactionSchema,
+ extendedDescriptionSchema,
+ familiarFollowersSchema,
+ featuredTagSchema,
+ filterKeywordSchema,
+ filterSchema,
+ filterStatusSchema,
+ groupMemberSchema,
+ groupRelationshipSchema,
+ groupSchema,
+ instanceSchema,
+ interactionPoliciesSchema,
+ interactionRequestSchema,
+ listSchema,
+ locationSchema,
+ markersSchema,
+ mediaAttachmentSchema,
+ mutedAccountSchema,
+ notificationPolicySchema,
+ notificationRequestSchema,
+ notificationSchema,
+ oauthTokenSchema,
+ pollSchema,
+ relationshipSchema,
+ reportSchema,
+ ruleSchema,
+ scheduledStatusSchema,
+ searchSchema,
+ statusEditSchema,
+ statusSchema,
+ statusSourceSchema,
+ streamingEventSchema,
+ suggestionSchema,
+ tagSchema,
+ tokenSchema,
+ translationSchema,
+ trendsLinkSchema,
+ webPushSubscriptionSchema,
+} from './entities';
+import { filteredArray } from './entities/utils';
+import { AKKOMA, type Features, getFeatures, GOTOSOCIAL, MITRA } from './features';
+import request, { getNextLink, getPrevLink, type RequestBody, RequestMeta } from './request';
+import { buildFullPath } from './utils/url';
+
+import type {
+ Account,
+ AdminAccount,
+ AdminCanonicalEmailBlock,
+ AdminDomainAllow,
+ AdminDomainBlock,
+ AdminEmailDomainBlock,
+ AdminIpBlock,
+ AdminReport,
+ Chat,
+ ChatMessage,
+ Conversation,
+ GroupRole,
+ Instance,
+ Notification,
+ ScheduledStatus,
+ Status,
+ StreamingEvent,
+ Tag,
+} from './entities';
+import type {
+ AdminAccountAction,
+ AdminCreateDomainBlockParams,
+ AdminCreateIpBlockParams,
+ AdminDimensionKey,
+ AdminGetAccountsParams,
+ AdminGetCanonicalEmailBlocks,
+ AdminGetDimensionsParams,
+ AdminGetDomainAllowsParams,
+ AdminGetDomainBlocksParams,
+ AdminGetEmailDomainBlocksParams,
+ AdminGetGroupsParams,
+ AdminGetIpBlocksParams,
+ AdminGetMeasuresParams,
+ AdminGetReportsParams,
+ AdminGetStatusesParams,
+ AdminMeasureKey,
+ AdminPerformAccountActionParams,
+ AdminUpdateDomainBlockParams,
+ AdminUpdateReportParams,
+ AdminUpdateStatusParams,
+ BubbleTimelineParams,
+ CreateAccountParams,
+ CreateApplicationParams,
+ CreateBookmarkFolderParams,
+ CreateChatMessageParams,
+ CreateEventParams,
+ CreateFilterParams,
+ CreateGroupParams,
+ CreateListParams,
+ CreatePushNotificationsSubscriptionParams,
+ CreateStatusParams,
+ EditEventParams,
+ EditStatusParams,
+ FollowAccountParams,
+ GetAccountEndorsementsParams,
+ GetAccountFavouritesParams,
+ GetAccountFollowersParams,
+ GetAccountFollowingParams,
+ GetAccountParams,
+ GetAccountStatusesParams,
+ GetBlocksParams,
+ GetBookmarksParams,
+ GetChatMessagesParams,
+ GetChatsParams,
+ GetConversationsParams,
+ GetDomainBlocksParams,
+ GetEndorsementsParams,
+ GetEventParticipationRequestsParams,
+ GetEventParticipationsParams,
+ GetFavouritedByParams,
+ GetFavouritesParams,
+ GetFollowedTagsParams,
+ GetFollowRequestsParams,
+ GetGroupBlocksParams,
+ GetGroupMembershipRequestsParams,
+ GetGroupMembershipsParams,
+ GetInteractionRequestsParams,
+ GetJoinedEventsParams,
+ GetListAccountsParams,
+ GetMutesParams,
+ GetNotificationParams,
+ GetNotificationRequestsParams,
+ GetRebloggedByParams,
+ GetRelationshipsParams,
+ GetScheduledStatusesParams,
+ GetStatusContextParams,
+ GetStatusesParams,
+ GetStatusParams,
+ GetStatusQuotesParams,
+ GetTokenParams,
+ GetTrendingLinks,
+ GetTrendingStatuses,
+ GetTrendingTags,
+ GroupTimelineParams,
+ HashtagTimelineParams,
+ HomeTimelineParams,
+ ListTimelineParams,
+ MfaChallengeParams,
+ MuteAccountParams,
+ OauthAuthorizeParams,
+ ProfileDirectoryParams,
+ PublicTimelineParams,
+ ReportAccountParams,
+ RevokeTokenParams,
+ SaveMarkersParams,
+ SearchAccountParams,
+ SearchParams,
+ UpdateBookmarkFolderParams,
+ UpdateCredentialsParams,
+ UpdateFilterParams,
+ UpdateGroupParams,
+ UpdateInteractionPoliciesParams,
+ UpdateListParams,
+ UpdateMediaParams,
+ UpdateNotificationPolicyRequest,
+ UpdateNotificationSettingsParams,
+ UpdatePushNotificationsSubscriptionParams,
+ UploadMediaParams,
+} from './params';
+import type { PaginatedResponse } from './responses';
+import type { ZodTypeAny } from 'zod';
+
+class PlApiClient {
+
+ baseURL: string;
+ #accessToken?: string;
+ #instance: Instance = instanceSchema.parse({});
+ public request = request.bind(this) as typeof request;
+ public features: Features = getFeatures();
+ #socket?: {
+ listen: (listener: any, stream?: string) => number;
+ unlisten: (listener: any) => void;
+ subscribe: (stream: string, params?: { list?: string; tag?: string }) => void;
+ unsubscribe: (stream: string, params?: { list?: string; tag?: string }) => void;
+ close: () => void;
+ };
+
+ constructor(baseURL: string, accessToken?: string, { instance, fetchInstance }: {
+ instance?: Instance;
+ fetchInstance?: boolean;
+ } = {}) {
+ this.baseURL = baseURL;
+ this.#accessToken = accessToken;
+
+ if (instance) {
+ this.#setInstance(instance);
+ }
+ if (fetchInstance) {
+ this.instance.getInstance();
+ }
+ }
+
+ #paginatedGet = async (input: URL | RequestInfo, body: RequestBody, schema: ZodTypeAny): Promise> => {
+ const getMore = (input: string | null) => input ? async () => {
+ const response = await this.request(input);
+
+ return {
+ previous: getMore(getPrevLink(response)),
+ next: getMore(getNextLink(response)),
+ items: filteredArray(schema).parse(response.json) as Array,
+ partial: response.status === 206,
+ };
+ } : null;
+
+ const response = await this.request(input, body);
+
+ return {
+ previous: getMore(getPrevLink(response)),
+ next: getMore(getNextLink(response)),
+ items: filteredArray(schema).parse(response.json) as Array,
+ partial: response.status === 206,
+ };
+ };
+
+ #paginatedPleromaAccounts = async (params: {
+ query?: string;
+ filters?: string;
+ page?: number;
+ page_size: number;
+ tags?: Array;
+ actor_types?: Array;
+ name?: string;
+ email?: string;
+ }): Promise> => {
+ const response = await this.request('/api/v1/pleroma/admin/users', { params });
+
+ return {
+ previous: !params.page ? null : () => this.#paginatedPleromaAccounts({ ...params, page: params.page! - 1 }),
+ next: response.json?.count > (params.page_size * ((params.page || 1) - 1) + response.json?.users?.length)
+ ? () => this.#paginatedPleromaAccounts({ ...params, page: (params.page || 0) + 1 })
+ : null,
+ items: filteredArray(adminAccountSchema).parse(response.json?.users),
+ partial: response.status === 206,
+ total: response.json?.total,
+ };
+ };
+
+ #paginatedPleromaReports = async (params: {
+ state?: 'open' | 'closed' | 'resolved';
+ limit?: number;
+ page?: number;
+ page_size: number;
+ }): Promise> => {
+ const response = await this.request('/api/v1/pleroma/admin/reports', { params });
+
+ return {
+ previous: !params.page ? null : () => this.#paginatedPleromaReports({ ...params, page: params.page! - 1 }),
+ next: response.json?.total > (params.page_size * ((params.page || 1) - 1) + response.json?.reports?.length)
+ ? () => this.#paginatedPleromaReports({ ...params, page: (params.page || 0) + 1 })
+ : null,
+ items: filteredArray(adminReportSchema).parse(response.json?.reports),
+ partial: response.status === 206,
+ total: response.json?.total,
+ };
+ };
+
+ #paginatedPleromaStatuses = async (params: {
+ page_size?: number;
+ local_only?: boolean;
+ godmode?: boolean;
+ with_reblogs?: boolean;
+ page?: number;
+ }): Promise> => {
+ const response = await this.request('/api/v1/pleroma/admin/statuses', { params });
+
+ return {
+ previous: !params.page ? null : () => this.#paginatedPleromaStatuses({ ...params, page: params.page! - 1 }),
+ next: response.json?.length
+ ? () => this.#paginatedPleromaStatuses({ ...params, page: (params.page || 0) + 1 })
+ : null,
+ items: filteredArray(statusSchema).parse(response.json),
+ partial: response.status === 206,
+ };
+ };
+
+ /** Register client applications that can be used to obtain OAuth tokens. */
+ public readonly apps = {
+ /**
+ * Create an application
+ * Create a new application to obtain OAuth2 credentials.
+ * @see {@link https://docs.joinmastodon.org/methods/apps/#create}
+ */
+ createApplication: async (params: CreateApplicationParams) => {
+ const response = await this.request('/api/v1/apps', { method: 'POST', body: params });
+
+ return applicationSchema.parse(response.json);
+ },
+
+ /**
+ * Verify your app works
+ * Confirm that the app’s OAuth2 credentials work.
+ * @see {@link https://docs.joinmastodon.org/methods/apps/#verify_credentials}
+ */
+ verifyApplication: async () => {
+ const response = await this.request('/api/v1/apps/verify_credentials');
+
+ return applicationSchema.parse(response.json);
+ },
+ };
+
+ public readonly oauth = {
+ /**
+ * Authorize a user
+ * Displays an authorization form to the user. If approved, it will create and return an authorization code, then redirect to the desired `redirect_uri`, or show the authorization code if `urn:ietf:wg:oauth:2.0:oob` was requested. The authorization code can be used while requesting a token to obtain access to user-level methods.
+ * @see {@link https://docs.joinmastodon.org/methods/oauth/#authorize}
+ */
+ authorize: async (params: OauthAuthorizeParams) => {
+ const response = await this.request('/oauth/authorize', { params });
+
+ return z.string().parse(response.json);
+ },
+
+ /**
+ * Obtain a token
+ * Obtain an access token, to be used during API calls that are not public.
+ * @see {@link https://docs.joinmastodon.org/methods/oauth/#token}
+ */
+ getToken: async (params: GetTokenParams) => {
+ const response = await this.request('/oauth/token', { method: 'POST', body: params });
+
+ return tokenSchema.parse(response.json);
+ },
+
+ /**
+ * Revoke a token
+ * Revoke an access token to make it no longer valid for use.
+ * @see {@link https://docs.joinmastodon.org/methods/oauth/#revoke}
+ */
+ revokeToken: async (params: RevokeTokenParams) => {
+ const response = await this.request('/oauth/revoke', { method: 'POST', body: params });
+
+ this.#socket?.close();
+
+ return response.json as {};
+ },
+
+ /**
+ * Get a new captcha
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromacaptcha}
+ */
+ getCaptcha: async () => {
+ const response = await this.request('/api/pleroma/captcha');
+
+ return z.intersection(z.object({
+ type: z.string(),
+ }), z.record(z.any())).parse(response.json);
+ },
+
+ mfaChallenge: async (params: MfaChallengeParams) => {
+ const response = await this.request('/oauth/mfa/challenge', { method: 'POST', body: params });
+
+ return tokenSchema.parse(response.json);
+ },
+ };
+
+ public readonly emails = {
+ resendConfirmationEmail: async (email: string) => {
+ const response = await this.request('/api/v1/emails/confirmations', { method: 'POST', body: { email } });
+
+ return response.json as {};
+ },
+ };
+
+ public readonly accounts = {
+ /**
+ * Get account
+ * View information about a profile.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#get}
+ */
+ getAccount: async (accountId: string, params?: GetAccountParams) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}`, { params });
+
+ return accountSchema.parse(response.json);
+ },
+
+ /**
+ * Get multiple accounts
+ * View information about multiple profiles.
+ *
+ * Requires features{@link Features['getAccounts']}.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#index}
+ */
+ getAccounts: async (accountId: string[]) => {
+ const response = await this.request('/api/v1/accounts', { params: { id: accountId } });
+
+ return filteredArray(accountSchema).parse(response.json);
+ },
+
+ /**
+ * Get account’s statuses
+ * Statuses posted to the given account.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#statuses}
+ */
+ getAccountStatuses: async (accountId: string, params?: GetAccountStatusesParams) =>
+ this.#paginatedGet(`/api/v1/accounts/${accountId}/statuses`, { params }, statusSchema),
+
+ /**
+ * Get account’s followers
+ * Accounts which follow the given account, if network is not hidden by the account owner.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#followers}
+ */
+ getAccountFollowers: async (accountId: string, params?: GetAccountFollowersParams) =>
+ this.#paginatedGet(`/api/v1/accounts/${accountId}/followers`, { params }, accountSchema),
+
+ /**
+ * Get account’s following
+ * Accounts which the given account is following, if network is not hidden by the account owner.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#following}
+ */
+ getAccountFollowing: async (accountId: string, params?: GetAccountFollowingParams) =>
+ this.#paginatedGet(`/api/v1/accounts/${accountId}/following`, { params }, accountSchema),
+
+ /**
+ * Get account’s featured tags
+ * Tags featured by this account.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#featured_tags}
+ */
+ getAccountFeaturedTags: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/featured_tags`);
+
+ return filteredArray(featuredTagSchema).parse(response.json);
+ },
+
+ /**
+ * Get lists containing this account
+ * User lists that you have added this account to.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#lists}
+ */
+ getAccountLists: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/lists`);
+
+ return filteredArray(listSchema).parse(response.json);
+ },
+
+ /**
+ * Follow account
+ * Follow the given account. Can also be used to update whether to show reblogs or enable notifications.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#follow}
+ */
+ followAccount: async (accountId: string, params?: FollowAccountParams) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/follow`, { method: 'POST', body: params });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Unfollow account
+ * Unfollow the given account.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#unfollow}
+ */
+ unfollowAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/unfollow`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Remove account from followers
+ * Remove the given account from your followers.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#remove_from_followers}
+ */
+ removeAccountFromFollowers: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/remove_from_followers`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Feature account on your profile
+ * Add the given account to the user’s featured profiles.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#pin}
+ */
+ pinAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/pin`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Unfeature account from profile
+ * Remove the given account from the user’s featured profiles.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#unpin}
+ */
+ unpinAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/unpin`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Set private note on profile
+ * Sets a private note on a user.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#note}
+ */
+ updateAccountNote: async (accountId: string, comment: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/note`, { method: 'POST', body: { comment } });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Check relationships to other accounts
+ * Find out whether a given account is followed, blocked, muted, etc.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#relationships}
+ */
+ getRelationships: async (accountIds: string[], params?: GetRelationshipsParams) => {
+ const response = await this.request('/api/v1/accounts/relationships', { params: { ...params, id: accountIds } });
+
+ return filteredArray(relationshipSchema).parse(response.json);
+ },
+
+ /**
+ * Find familiar followers
+ * Obtain a list of all accounts that follow a given account, filtered for accounts you follow.
+ *
+ * Requires features{@link Features['familiarFollowers']}.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#familiar_followers}
+ */
+ getFamiliarFollowers: async (accountIds: string[]) => {
+ const response = await this.request('/api/v1/accounts/familiar_followers', { params: { id: accountIds } });
+
+ return filteredArray(familiarFollowersSchema).parse(response.json);
+ },
+
+ /**
+ * Search for matching accounts
+ * Search for matching accounts by username or display name.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#search}
+ */
+ searchAccounts: async (q: string, params?: SearchAccountParams, meta?: RequestMeta) => {
+ const response = await this.request('/api/v1/accounts/search', { ...meta, params: { ...params, q } });
+
+ return filteredArray(accountSchema).parse(response.json);
+ },
+
+ /**
+ * Lookup account ID from Webfinger address
+ * Quickly lookup a username to see if it is available, skipping WebFinger resolution.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#lookup}
+ */
+ lookupAccount: async (acct: string, meta?: RequestMeta) => {
+ const response = await this.request('/api/v1/accounts/lookup', { ...meta, params: { acct } });
+
+ return accountSchema.parse(response.json);
+ },
+
+ /**
+ * File a report
+ * @see {@link https://docs.joinmastodon.org/methods/reports/#post}
+ */
+ reportAccount: async (accountId: string, params: ReportAccountParams) => {
+ const response = await this.request('/api/v1/reports', {
+ method: 'POST',
+ body: { ...params, account_id: accountId },
+ });
+
+ return reportSchema.parse(response.json);
+ },
+
+ /**
+ * Endorsements
+ * Returns endorsed accounts
+ *
+ * Requires features{@link Features['accountEndorsements']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaaccountsidendorsements}
+ */
+ getAccountEndorsements: async (accountId: string, params?: GetAccountEndorsementsParams) => {
+ const response = await this.request(`/api/v1/pleroma/accounts/${accountId}/endorsements`, { params });
+
+ return filteredArray(accountSchema).parse(response.json);
+ },
+
+ /**
+ * Birthday reminders
+ * Birthday reminders about users you follow.
+ *
+ * Requires features{@link Features['birthdays']}.
+ */
+ getBirthdays: async (day: number, month: number) => {
+ const response = await this.request('/api/v1/pleroma/birthdays', { params: { day, month } });
+
+ return filteredArray(accountSchema).parse(response.json);
+ },
+
+ /**
+ * Returns favorites timeline of any user
+ *
+ * Requires features{@link Features['publicFavourites']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaaccountsidfavourites}
+ */
+ getAccountFavourites: async (accountId: string, params?: GetAccountFavouritesParams) =>
+ this.#paginatedGet(`/api/v1/pleroma/accounts/${accountId}/favourites`, { params }, statusSchema),
+
+ /**
+ * Interact with profile or status from remote account
+ *
+ * Requires features{@link Features['remoteInteractions']}.
+ * @param ap_id - Profile or status ActivityPub ID
+ * @param profile - Remote profile webfinger
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaremote_interaction}
+ */
+ remoteInteraction: async (ap_id: string, profile: string) => {
+ const response = await this.request('/api/v1/pleroma/remote_interaction', { method: 'POST', body: { ap_id, profile } });
+
+ if (response.json?.error) throw response.json.error;
+
+ return z.object({
+ url: z.string(),
+ }).parse(response.json);
+ },
+
+ /**
+ * Bite the given user.
+ *
+ * Requires features{@link Features['bites']}.
+ * @see {@link https://github.com/purifetchi/Toki/blob/master/Toki/Controllers/MastodonApi/Bite/BiteController.cs}
+ */
+ biteAccount: async (id: string) => {
+ const response = await this.request('/api/v1/bite', { method: 'POST', params: { id } });
+
+ return response.json as {};
+ },
+ };
+
+ public readonly myAccount = {
+ /**
+ * View bookmarked statuses
+ * Statuses the user has bookmarked.
+ * @see {@link https://docs.joinmastodon.org/methods/bookmarks/#get}
+ */
+ getBookmarks: async (params?: GetBookmarksParams) =>
+ this.#paginatedGet('/api/v1/bookmarks', { params }, statusSchema),
+
+ /**
+ * View favourited statuses
+ * Statuses the user has favourited.
+ * @see {@link https://docs.joinmastodon.org/methods/favourites/#get}
+ */
+ getFavourites: async (params?: GetFavouritesParams) =>
+ this.#paginatedGet('/api/v1/favourites', { params }, statusSchema),
+
+ /**
+ * View pending follow requests
+ * @see {@link https://docs.joinmastodon.org/methods/follow_requests/#get}
+ */
+ getFollowRequests: async (params?: GetFollowRequestsParams) =>
+ this.#paginatedGet('/api/v1/follow_requests', { params }, accountSchema),
+
+ /**
+ * Accept follow request
+ * @see {@link https://docs.joinmastodon.org/methods/follow_requests/#accept}
+ */
+ acceptFollowRequest: async (accountId: string) => {
+ const response = await this.request(`/api/v1/follow_requests/${accountId}/authorize`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Reject follow request
+ * @see {@link https://docs.joinmastodon.org/methods/follow_requests/#reject}
+ */
+ rejectFollowRequest: async (accountId: string) => {
+ const response = await this.request(`/api/v1/follow_requests/${accountId}/reject`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * View currently featured profiles
+ * Accounts that the user is currently featuring on their profile.
+ * @see {@link https://docs.joinmastodon.org/methods/endorsements/#get}
+ */
+ getEndorsements: async (params?: GetEndorsementsParams) =>
+ this.#paginatedGet('/api/v1/endorsements', { params }, accountSchema),
+
+ /**
+ * View your featured tags
+ * List all hashtags featured on your profile.
+ * @see {@link https://docs.joinmastodon.org/methods/featured_tags/#get}
+ */
+ getFeaturedTags: async () => {
+ const response = await this.request('/api/v1/featured_tags');
+
+ return filteredArray(featuredTagSchema).parse(response.json);
+ },
+
+ /**
+ * Feature a tag
+ * Promote a hashtag on your profile.
+ * @see {@link https://docs.joinmastodon.org/methods/featured_tags/#feature}
+ */
+ featureTag: async (name: string) => {
+ const response = await this.request('/api/v1/featured_tags', {
+ method: 'POST',
+ body: { name },
+ });
+
+ return filteredArray(featuredTagSchema).parse(response.json);
+ },
+
+ /**
+ * Unfeature a tag
+ * Stop promoting a hashtag on your profile.
+ * @see {@link https://docs.joinmastodon.org/methods/featured_tags/#unfeature}
+ */
+ unfeatureTag: async (name: string) => {
+ const response = await this.request('/api/v1/featured_tags', {
+ method: 'DELETE',
+ body: { name },
+ });
+
+ return response.json as {};
+ },
+
+ /**
+ * View suggested tags to feature
+ * Shows up to 10 recently-used tags.
+ * @see {@link https://docs.joinmastodon.org/methods/featured_tags/#suggestions}
+ */
+ getFeaturedTagsSuggestions: async () => {
+ const response = await this.request('/api/v1/featured_tags/suggestions');
+
+ return filteredArray(tagSchema).parse(response.json);
+ },
+
+ /**
+ * View all followed tags
+ * List your followed hashtags.
+ *
+ * Requires features{@link Features['followHashtags']}.
+ * @see {@link https://docs.joinmastodon.org/methods/followed_tags/#get}
+ */
+ getFollowedTags: async (params?: GetFollowedTagsParams) =>
+ this.#paginatedGet('/api/v1/followed_tags', { params }, tagSchema),
+
+ /**
+ * View information about a single tag
+ * Show a hashtag and its associated information
+ * @see {@link https://docs.joinmastodon.org/methods/tags/#get}
+ */
+ getTag: async (tagId: string) => {
+ const response = await this.request(`/api/v1/tags/${tagId}`);
+
+ return tagSchema.parse(response.json);
+ },
+
+ /**
+ * Follow a hashtag
+ * Follow a hashtag. Posts containing a followed hashtag will be inserted into your home timeline.
+ * @see {@link https://docs.joinmastodon.org/methods/tags/#follow}
+ */
+ followTag: async (tagId: string) => {
+ const response = await this.request(`/api/v1/tags/${tagId}/follow`, { method: 'POST' });
+
+ return tagSchema.parse(response.json);
+ },
+
+ /**
+ * Unfollow a hashtag
+ * Unfollow a hashtag. Posts containing this hashtag will no longer be inserted into your home timeline.
+ * @see {@link https://docs.joinmastodon.org/methods/tags/#unfollow}
+ */
+ unfollowTag: async (tagId: string) => {
+ const response = await this.request(`/api/v1/tags/${tagId}/unfollow`, { method: 'POST' });
+
+ return tagSchema.parse(response.json);
+ },
+
+ /**
+ * View follow suggestions
+ * Accounts that are promoted by staff, or that the user has had past positive interactions with, but is not yet following.
+ *
+ * Requires features{@link Features['suggestions']}.
+ * @see {@link https://docs.joinmastodon.org/methods/suggestions/#v2}
+ */
+ getSuggestions: async (limit?: number) => {
+ const response = await this.request(
+ this.features.suggestionsV2 ? '/api/v2/suggestions' : '/api/v1/suggestions',
+ { params: { limit } },
+ );
+
+ return filteredArray(suggestionSchema).parse(response.json);
+ },
+
+ /**
+ * Remove a suggestion
+ * Remove an account from follow suggestions.
+ *
+ * Requires features{@link Features['suggestions']}.
+ * @see {@link https://docs.joinmastodon.org/methods/suggestions/#remove}
+ */
+ dismissSuggestions: async (accountId: string) => {
+ const response = await this.request(`/api/v1/suggestions/${accountId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Gets user bookmark folders
+ *
+ * Requires features{@link Features['bookmarkFolders']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromabookmark_folders}
+ */
+ getBookmarkFolders: async () => {
+ const response = await this.request('/api/v1/pleroma/bookmark_folders');
+
+ return filteredArray(bookmarkFolderSchema).parse(response.json);
+ },
+
+ /**
+ * Creates a bookmark folder
+ *
+ * Requires features{@link Features['bookmarkFolders']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabookmark_folders}
+ */
+ createBookmarkFolder: async (params: CreateBookmarkFolderParams) => {
+ const response = await this.request('/api/v1/pleroma/bookmark_folders', { method: 'POST', body: params });
+
+ return bookmarkFolderSchema.parse(response.json);
+ },
+
+ /**
+ * Updates a bookmark folder
+ *
+ * Requires features{@link Features['bookmarkFolders']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#patch-apiv1pleromabookmark_foldersid}
+ */
+ updateBookmarkFolder: async (bookmarkFolderId: string, params: UpdateBookmarkFolderParams) => {
+ const response = await this.request(`/api/v1/pleroma/bookmark_folders/${bookmarkFolderId}`, { method: 'PATCH', body: params });
+
+ return bookmarkFolderSchema.parse(response.json);
+ },
+
+ /**
+ * Deletes a bookmark folder
+ *
+ * Requires features{@link Features['bookmarkFolders']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#delete-apiv1pleromabookmark_foldersid}
+ */
+ deleteBookmarkFolder: async (bookmarkFolderId: string) => {
+ const response = await this.request(`/api/v1/pleroma/bookmark_folders/${bookmarkFolderId}`, { method: 'DELETE' });
+
+ return bookmarkFolderSchema.parse(response.json);
+ },
+ };
+
+ public readonly settings = {
+ /**
+ * Register an account
+ * Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.
+ *
+ * Requires features{@link Features['accountCreation`
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#create}
+ */
+ createAccount: async (params: CreateAccountParams) => {
+ const response = await this.request('/api/v1/accounts', {
+ method: 'POST',
+ body: { language: params.locale, ...params },
+ });
+
+ return tokenSchema.parse(response.json);
+ },
+
+ /**
+ * Verify account credentials
+ * Test to make sure that the user token works.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#verify_credentials}
+ */
+ verifyCredentials: async () => {
+ const response = await this.request('/api/v1/accounts/verify_credentials');
+
+ return credentialAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Update account credentials
+ * Update the user’s display and preferences.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#update_credentials}
+ */
+ updateCredentials: async (params: UpdateCredentialsParams) => {
+ if (params.background_image) {
+ (params as any).pleroma_background_image = params.background_image;
+ delete params.background_image;
+ }
+
+ if (params.settings_store) {
+ (params as any).pleroma_settings_store = params.settings_store;
+ delete params.settings_store;
+ }
+
+ const response = await this.request('/api/v1/accounts/update_credentials', {
+ method: 'PATCH',
+ contentType: (this.features.version.software === GOTOSOCIAL || params.avatar || params.header) ? '' : undefined,
+ body: params,
+ });
+
+ return credentialAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Delete profile avatar
+ * Deletes the avatar associated with the user’s profile.
+ * @see {@link https://docs.joinmastodon.org/methods/profile/#delete-profile-avatar}
+ */
+ deleteAvatar: async () => {
+ const response = await this.request('/api/v1/profile/avatar', { method: 'DELETE' });
+
+ return credentialAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Delete profile header
+ * Deletes the header image associated with the user’s profile.
+ * @see {@link https://docs.joinmastodon.org/methods/profile/#delete-profile-header}
+ */
+ deleteHeader: async () => {
+ const response = await this.request('/api/v1/profile/header', { method: 'DELETE' });
+
+ return credentialAccountSchema.parse(response.json);
+ },
+
+ /**
+ * View user preferences
+ * Preferences defined by the user in their account settings.
+ * @see {@link https://docs.joinmastodon.org/methods/preferences/#get}
+ */
+ getPreferences: async () => {
+ const response = await this.request('/api/v1/preferences');
+
+ return response.json as Record;
+ },
+
+ /**
+ * Create a user backup archive
+ *
+ * Requires features{@link Features['accountBackups']}.
+ */
+ createBackup: async () => {
+ const response = await this.request('/api/v1/pleroma/backups', { method: 'POST' });
+
+ return backupSchema.parse(response.json);
+ },
+
+ /**
+ * List user backups
+ *
+ * Requires features{@link Features['accountBackups']}.
+ */
+ getBackups: async () => {
+ const response = await this.request('/api/v1/pleroma/backups');
+
+ return filteredArray(backupSchema).parse(response.json);
+ },
+
+ /**
+ * Get aliases of the current account
+ *
+ * Requires features{@link Features['manageAccountAliases']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-aliases-of-the-current-account}
+ */
+ getAccountAliases: async () => {
+ const response = await this.request('/api/v1/pleroma/aliases');
+
+ return z.object({ aliases: filteredArray(z.string()) }).parse(response.json);
+ },
+
+ /**
+ * Add alias to the current account
+ *
+ * Requires features{@link Features['manageAccountAliases']}.
+ * @param alias - the nickname of the alias to add, e.g. foo@example.org.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#add-alias-to-the-current-account}
+ */
+ addAccountAlias: async (alias: string) => {
+ const response = await this.request('/api/v1/pleroma/aliases', { method: 'PUT', body: { alias } });
+
+ return z.object({ status: z.literal('success') }).parse(response.json);
+ },
+
+ /**
+ * Delete alias from the current account
+ *
+ * Requires features{@link Features['manageAccountAliases']}.
+ * @param alias - the nickname of the alias to add, e.g. foo@example.org.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#delete-alias-from-the-current-account}
+ */
+ deleteAccountAlias: async (alias: string) => {
+ const response = await this.request('/api/v1/pleroma/aliases', { method: 'DELETE', body: { alias } });
+
+ return z.object({ status: z.literal('success') }).parse(response.json);
+ },
+
+ /**
+ * Retrieve a list of active sessions for the user
+ *
+ * Requires features{@link Features['sessions']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apioauth_tokens}
+ */
+ getOauthTokens: async () => {
+ const response = await this.request('/api/oauth_tokens');
+
+ return filteredArray(oauthTokenSchema).parse(response.json);
+ },
+
+ /**
+ * Revoke a user session by its ID
+ *
+ * Requires features{@link Features['sessions']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#delete-apioauth_tokensid}
+ */
+ deleteOauthToken: async (oauthTokenId: number) => {
+ const response = await this.request(`/api/oauth_tokens/${oauthTokenId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Change account password
+ * @see {@link https://docs.gotosocial.org/en/latest/api/swagger}
+ * @see {@link https://codeberg.org/silverpill/mitra/src/commit/f15c19527191d82bc3643f984deca43d1527525d/docs/openapi.yaml}
+ * @see {@link https://git.pleroma.social/pleroma/pleroma/-/blob/develop/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex?ref_type=heads#L68}
+ */
+ changePassword: async (current_password: string, new_password: string) => {
+ let response;
+
+ switch (this.features.version.software) {
+ case GOTOSOCIAL:
+ response = await this.request('/api/v1/user/password_change', {
+ method: 'POST',
+ body: {
+ old_password: current_password,
+ new_password,
+ },
+ });
+ break;
+ case MITRA:
+ response = await this.request('/api/v1/settings/change_password', {
+ method: 'POST',
+ body: { new_password },
+ });
+ break;
+ default:
+ response = await this.request('/api/pleroma/change_password', {
+ method: 'POST',
+ body: {
+ password: current_password,
+ new_password,
+ new_password_confirmation: new_password,
+ },
+ });
+ }
+
+ return response.json as {};
+ },
+
+ /**
+ * Request password reset e-mail
+ *
+ * Requires features{@link Features['resetPassword']}.
+ */
+ resetPassword: async (email?: string, nickname?: string) => {
+ const response = await this.request('/auth/password', {
+ method: 'POST',
+ body: { email, nickname },
+ });
+
+ return response.json as {};
+ },
+
+ /**
+ * Requires features{@link Features['changeEmail']}.
+ */
+ changeEmail: async (email: string, password: string) => {
+ let response;
+
+ switch (this.features.version.software) {
+ case GOTOSOCIAL:
+ response = await this.request('/api/v1/user/email_change', {
+ method: 'POST',
+ body: {
+ new_email: email,
+ password,
+ },
+ });
+ break;
+ default:
+ response = await this.request('/api/pleroma/change_email', {
+ method: 'POST',
+ body: {
+ email,
+ password,
+ },
+ });
+ }
+
+ if (response.json?.error) throw response.json.error;
+
+ return response.json as {};
+ },
+
+ /**
+ * Requires features{@link Features['deleteAccount']}.
+ */
+ deleteAccount: async (password: string) => {
+ let response;
+
+ switch (this.features.version.software) {
+ case GOTOSOCIAL:
+ response = await this.request('/api/v1/accounts/delete', {
+ method: 'POST',
+ body: { password },
+ });
+ break;
+ default:
+ response = await this.request('/api/pleroma/delete_account', {
+ method: 'POST',
+ body: { password },
+ });
+ }
+
+ if (response.json?.error) throw response.json.error;
+
+ return response.json as {};
+ },
+
+ /**
+ * Disable an account
+ *
+ * Requires features{@link Features['disableAccount']}.
+ */
+ disableAccount: async (password: string) => {
+ const response = await this.request('/api/pleroma/disable_account', {
+ method: 'POST',
+ body: { password },
+ });
+
+ if (response.json?.error) throw response.json.error;
+
+ return response.json as {};
+ },
+
+ /**
+ * Requires features{@link Features['accountMoving']}.
+ */
+ moveAccount: async (target_account: string, password: string) => {
+ const response = await this.request('/api/pleroma/move_account', {
+ method: 'POST',
+ body: { password, target_account },
+ });
+
+ if (response.json?.error) throw response.json.error;
+
+ return response.json as {};
+ },
+
+ mfa: {
+ /**
+ * Requires features{@link Features['manageMfa`.
+ */
+ getMfaSettings: async () => {
+ const response = await this.request('/api/pleroma/accounts/mfa');
+
+ return z.object({
+ settings: z.object({
+ enabled: z.boolean(),
+ totp: z.boolean(),
+ }),
+ }).parse(response.json);
+ },
+
+ /**
+ * Requires features{@link Features['manageMfa`.
+ */
+ getMfaBackupCodes: async () => {
+ const response = await this.request('/api/pleroma/accounts/mfa/backup_codes');
+
+ return z.object({
+ codes: z.array(z.string()),
+ }).parse(response.json);
+ },
+
+ /**
+ * Requires features{@link Features['manageMfa`.
+ */
+ getMfaSetup: async (method: 'totp') => {
+ const response = await this.request(`/api/pleroma/accounts/mfa/setup/${method}`);
+
+ return z.object({
+ key: z.string(),
+ provisioning_uri: z.string(),
+ }).parse(response.json);
+ },
+
+ /**
+ * Requires features{@link Features['manageMfa`.
+ */
+ confirmMfaSetup: async (method: 'totp', code: string, password: string) => {
+ const response = await this.request(`/api/pleroma/accounts/mfa/confirm/${method}`, {
+ method: 'POST',
+ body: { code, password },
+ });
+
+ if (response.json?.error) throw response.json.error;
+
+ return response.json as {};
+ },
+
+ /**
+ * Requires features{@link Features['manageMfa`.
+ */
+ disableMfa: async (method: 'totp', password: string) => {
+ const response = await this.request(`/api/pleroma/accounts/mfa/${method}`, {
+ method: 'DELETE',
+ body: { password },
+ });
+
+ if (response.json?.error) throw response.json.error;
+
+ return response.json as {};
+ },
+ },
+
+ /**
+ * Imports your follows, for example from a Mastodon CSV file.
+ *
+ * Requires features{@link Features['importFollows']}.
+ * `overwrite` mode requires features{@link Features['importOverwrite']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apipleromafollow_import}
+ */
+ importFollows: async (list: File | string, mode?: 'merge' | 'overwrite') => {
+ let response;
+
+ switch (this.features.version.software) {
+ case GOTOSOCIAL:
+ response = await this.request('/api/v1/import', {
+ method: 'POST',
+ body: { data: list, type: 'following', mode },
+ contentType: '',
+ });
+ break;
+ default:
+ response = await this.request('/api/pleroma/follow_import', {
+ method: 'POST',
+ body: { list },
+ contentType: '',
+ });
+ }
+
+ return response.json;
+ },
+
+ /**
+ * Imports your blocks.
+ *
+ * Requires features{@link Features['importBlocks']}.
+ * `overwrite` mode requires features{@link Features['importOverwrite']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apipleromablocks_import}
+ */
+ importBlocks: async (list: File | string, mode?: 'merge' | 'overwrite') => {
+ let response;
+
+ switch (this.features.version.software) {
+ case GOTOSOCIAL:
+ response = await this.request('/api/v1/import', {
+ method: 'POST',
+ body: { data: list, type: 'blocks', mode },
+ contentType: '',
+ });
+ break;
+ default:
+ response = await this.request('/api/pleroma/blocks_import', {
+ method: 'POST',
+ body: { list },
+ contentType: '',
+ });
+ }
+
+ return response.json;
+ },
+
+ /**
+ * Imports your mutes.
+ *
+ * Requires features{@link Features['importMutes']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apipleromamutes_import}
+ */
+ importMutes: async (list: File | string) => {
+ const response = await this.request('/api/pleroma/mutes_import', {
+ method: 'POST',
+ body: { list },
+ contentType: '',
+ });
+
+ return response.json;
+ },
+
+ /**
+ * Updates user notification settings
+ *
+ * Requires features{@link Features['muteStrangers']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apipleromanotification_settings}
+ */
+ updateNotificationSettings: async (params: UpdateNotificationSettingsParams) => {
+ const response = await this.request('/api/pleroma/notification_settings', { method: 'PUT', body: params });
+
+ if (response.json?.error) throw response.json.error;
+
+ return z.object({ status: z.string() }).parse(response.json);
+ },
+
+ /**
+ * Get default interaction policies for new statuses created by you.
+ *
+ * Requires features{@link Features['interactionRequests']}.
+ * @see {@link https://docs.gotosocial.org/en/latest/api/swagger/}
+ */
+ getInteractionPolicies: async () => {
+ const response = await this.request('/api/v1/interaction_policies/defaults');
+
+ return interactionPoliciesSchema.parse(response.json);
+ },
+
+ /**
+ * Update default interaction policies per visibility level for new statuses created by you.
+ *
+ * Requires features{@link Features['interactionRequests']}.
+ * @see {@link https://docs.gotosocial.org/en/latest/api/swagger/}
+ */
+ updateInteractionPolicies: async (params: UpdateInteractionPoliciesParams) => {
+ const response = await this.request('/api/v1/interaction_policies/defaults', { method: 'PATCH', body: params });
+
+ return interactionPoliciesSchema.parse(response.json);
+ },
+ };
+
+ public readonly filtering = {
+ /**
+ * Block account
+ * Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#block}
+ */
+ blockAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/block`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Unblock account
+ * Unblock the given account.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#unblock}
+ */
+ unblockAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/unblock`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Mute account
+ * Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).
+ *
+ * Requires features{@link Features['mutes']}.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#mute}
+ */
+ muteAccount: async (accountId: string, params?: MuteAccountParams) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/mute`, { method: 'POST', body: params });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * Unmute account
+ * Unmute the given account.
+ *
+ * Requires features{@link Features['mutes']}.
+ * @see {@link https://docs.joinmastodon.org/methods/accounts/#unmute}
+ */
+ unmuteAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/accounts/${accountId}/unmute`, { method: 'POST' });
+
+ return relationshipSchema.parse(response.json);
+ },
+
+ /**
+ * View muted accounts
+ * Accounts the user has muted.
+ *
+ * Requires features{@link Features['mutes']}.
+ * @see {@link https://docs.joinmastodon.org/methods/mutes/#get}
+ */
+ getMutes: async (params?: GetMutesParams) =>
+ this.#paginatedGet('/api/v1/mutes', { params }, mutedAccountSchema),
+
+ /**
+ * View blocked users
+ * @see {@link https://docs.joinmastodon.org/methods/blocks/#get}
+ */
+ getBlocks: async (params?: GetBlocksParams) =>
+ this.#paginatedGet('/api/v1/blocks', { params }, accountSchema),
+
+ /**
+ * Get domain blocks
+ * View domains the user has blocked.
+ * @see {@link https://docs.joinmastodon.org/methods/domain_blocks/#get}
+ */
+ getDomainBlocks: async (params?: GetDomainBlocksParams) =>
+ this.#paginatedGet('/api/v1/domain_blocks', { params }, z.string()),
+
+ /**
+ * Block a domain
+ * Block a domain to:
+ * - hide all public posts from it
+ * - hide all notifications from it
+ * - remove all followers from it
+ * - prevent following new users from it (but does not remove existing follows)
+ * @see {@link https://docs.joinmastodon.org/methods/domain_blocks/#block}
+ */
+ blockDomain: async (domain: string) => {
+ const response = await this.request('/api/v1/domain_blocks', { method: 'POST', body: { domain } });
+
+ return response.json as {};
+ },
+
+ /**
+ * Unblock a domain
+ * Remove a domain block, if it exists in the user’s array of blocked domains.
+ * @see {@link https://docs.joinmastodon.org/methods/domain_blocks/#unblock}
+ */
+ unblockDomain: async (domain: string) => {
+ const response = await this.request('/api/v1/domain_blocks', {
+ method: 'DELETE',
+ body: { domain },
+ });
+
+ return response.json as {};
+ },
+
+ /**
+ * View all filters
+ * Obtain a list of all filter groups for the current user.
+ *
+ * Requires features{@link Features['filters']} or features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#get}
+ */
+ getFilters: async () => {
+ const response = await this.request(this.features.filtersV2 ? '/api/v2/filters' : '/api/v1/filters');
+
+ return filteredArray(filterSchema).parse(response.json);
+ },
+
+ /**
+ * View a specific filter
+ * Obtain a single filter group owned by the current user.
+ *
+ * Requires features{@link Features['filters']} or features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#get-one}
+ */
+ getFilter: async (filterId: string) => {
+ const response = await this.request(
+ this.features.filtersV2
+ ? `/api/v2/filters/${filterId}`
+ : `/api/v1/filters/${filterId}`,
+ );
+
+ return filterSchema.parse(response.json);
+ },
+
+ /**
+ * Create a filter
+ * Create a filter group with the given parameters.
+ *
+ * Requires features{@link Features['filters']} or features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#create}
+ */
+ createFilter: async (params: CreateFilterParams) => {
+ const { filtersV2 } = this.features;
+ const response = await this.request(
+ filtersV2 ? '/api/v2/filters' : '/api/v1/filters',
+ {
+ method: 'POST',
+ body: filtersV2 ? params : {
+ phrase: params.keywords_attributes[0]?.keyword,
+ context: params.context,
+ irreversible: params.filter_action === 'hide',
+ whole_word: params.keywords_attributes[0]?.whole_word,
+ expires_in: params.expires_in,
+ },
+ },
+ );
+
+ return filterSchema.parse(response.json);
+ },
+
+ /**
+ * Update a filter
+ * Update a filter group with the given parameters.
+ *
+ * Requires features{@link Features['filters']} or features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#update}
+ */
+ updateFilter: async (filterId: string, params: UpdateFilterParams) => {
+ const { filtersV2 } = this.features;
+ const response = await this.request(
+ filtersV2 ? `/api/v2/filters/${filterId}` : `/api/v1/filters/${filterId}`,
+ {
+ method: 'PUT',
+ body: filtersV2 ? params : {
+ phrase: params.keywords_attributes?.[0]?.keyword,
+ context: params.context,
+ irreversible: params.filter_action === 'hide',
+ whole_word: params.keywords_attributes?.[0]?.whole_word,
+ expires_in: params.expires_in,
+ },
+ },
+ );
+
+ return filterSchema.parse(response.json);
+ },
+
+ /**
+ * Delete a filter
+ * Delete a filter group with the given id.
+ *
+ * Requires features{@link Features['filters']} or features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#delete}
+ */
+ deleteFilter: async (filterId: string) => {
+ const response = await this.request(
+ this.features.filtersV2
+ ? `/api/v2/filters/${filterId}`
+ : `/api/v1/filters/${filterId}`,
+ { method: 'DELETE' },
+ );
+
+ return response.json as {};
+ },
+
+ /**
+ * View keywords added to a filter
+ * List all keywords attached to the current filter group.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#keywords-get}
+ */
+ getFilterKeywords: async (filterId: string) => {
+ const response = await this.request(`/api/v2/filters/${filterId}/keywords`);
+
+ return filteredArray(filterKeywordSchema).parse(response.json);
+ },
+
+ /**
+ * Add a keyword to a filter
+ * Add the given keyword to the specified filter group
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#keywords-create}
+ */
+ addFilterKeyword: async (filterId: string, keyword: string, whole_word?: boolean) => {
+ const response = await this.request(`/api/v2/filters/${filterId}/keywords`, {
+ method: 'POST',
+ body: { keyword, whole_word },
+ });
+
+ return filterKeywordSchema.parse(response.json);
+ },
+
+ /**
+ * View a single keyword
+ * Get one filter keyword by the given id.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#keywords-get-one}
+ */
+ getFilterKeyword: async (filterId: string) => {
+ const response = await this.request(`/api/v2/filters/keywords/${filterId}`);
+
+ return filterKeywordSchema.parse(response.json);
+ },
+
+ /**
+ * Edit a keyword within a filter
+ * Update the given filter keyword.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#keywords-update}
+ */
+ updateFilterKeyword: async (filterId: string, keyword: string, whole_word?: boolean) => {
+ const response = await this.request(`/api/v2/filters/keywords/${filterId}`, {
+ method: 'PUT',
+ body: { keyword, whole_word },
+ });
+
+ return filterKeywordSchema.parse(response.json);
+ },
+
+ /**
+ * Remove keywords from a filter
+ * Deletes the given filter keyword.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#keywords-delete}
+ */
+ deleteFilterKeyword: async (filterId: string) => {
+ const response = await this.request(`/api/v2/filters/keywords/${filterId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ /**
+ * View all status filters
+ * Obtain a list of all status filters within this filter group.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#statuses-get}
+ */
+ getFilterStatuses: async (filterId: string) => {
+ const response = await this.request(`/api/v2/filters/${filterId}/statuses`);
+
+ return filteredArray(filterStatusSchema).parse(response.json);
+ },
+
+ /**
+ * Add a status to a filter group
+ * Add a status filter to the current filter group.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#statuses-add}
+ */
+ addFilterStatus: async (filterId: string, statusId: string) => {
+ const response = await this.request(`/api/v2/filters/${filterId}/statuses`, {
+ method: 'POST',
+ body: { status_id: statusId },
+ });
+
+ return filterStatusSchema.parse(response.json);
+ },
+
+ /**
+ * View a single status filter
+ * Obtain a single status filter.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#statuses-get-one}
+ */
+ getFilterStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v2/filters/statuses/${statusId}`);
+
+ return filterStatusSchema.parse(response.json);
+ },
+
+ /**
+ * Remove a status from a filter group
+ * Remove a status filter from the current filter group.
+ *
+ * Requires features{@link Features['filtersV2']}.
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#statuses-remove}
+ */
+ deleteFilterStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v2/filters/statuses/${statusId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ };
+
+ public readonly statuses = {
+ /**
+ * Post a new status
+ * Publish a status with the given parameters.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#create}
+ */
+ createStatus: async (params: CreateStatusParams) => {
+ const response = await this.request('/api/v1/statuses', {
+ method: 'POST',
+ body: params,
+ });
+
+ if (response.json?.scheduled_at) return scheduledStatusSchema.parse(response.json);
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * View a single status
+ * Obtain information about a status.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#get}
+ */
+ getStatus: async (statusId: string, params?: GetStatusParams) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}`, { params });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * View multiple statuses
+ * Obtain information about multiple statuses.
+ *
+ * Requires features{@link Features['getStatuses']}.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#index}
+ */
+ getStatuses: async (statusIds: string[], params?: GetStatusesParams) => {
+ const response = await this.request('/api/v1/statuses', { params: { ...params, id: statusIds } });
+
+ return filteredArray(statusSchema).parse(response.json);
+ },
+
+ /**
+ * Delete a status
+ * Delete one of your own statuses.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#delete}
+ */
+ deleteStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}`, { method: 'DELETE' });
+
+ return statusSourceSchema.parse(response.json);
+ },
+
+ /**
+ * Get parent and child statuses in context
+ * View statuses above and below this status in the thread.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#context}
+ */
+ getContext: async (statusId: string, params?: GetStatusContextParams) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/context`, { params });
+
+ return contextSchema.parse(response.json);
+ },
+
+ /**
+ * Translate a status
+ * Translate the status content into some language.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#translate}
+ */
+ translateStatus: async (statusId: string, lang?: string) => {
+ let response;
+ if (this.features.version.build === AKKOMA) {
+ response = await this.request(`/api/v1/statuses/${statusId}/translations/${lang}`);
+
+ } else {
+ response = await this.request(`/api/v1/statuses/${statusId}/translate`, { method: 'POST', body: { lang } });
+ }
+
+ return translationSchema.parse(response.json);
+ },
+
+ /**
+ * See who boosted a status
+ * View who boosted a given status.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#reblogged_by}
+ */
+ getRebloggedBy: async (statusId: string, params?: GetRebloggedByParams) =>
+ this.#paginatedGet(`/api/v1/statuses/${statusId}/reblogged_by`, { params }, accountSchema),
+
+ /**
+ * See who favourited a status
+ * View who favourited a given status.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#favourited_by}
+ */
+ getFavouritedBy: async (statusId: string, params?: GetFavouritedByParams) =>
+ this.#paginatedGet(`/api/v1/statuses/${statusId}/favourited_by`, { params }, accountSchema),
+
+ /**
+ * Favourite a status
+ * Add a status to your favourites list.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#favourite}
+ */
+ favouriteStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/favourite`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Undo favourite of a status
+ * Remove a status from your favourites list.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#unfavourite}
+ */
+ unfavouriteStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/unfavourite`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Boost a status
+ * Reshare a status on your own profile.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#reblog}
+ */
+ reblogStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/reblog`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Undo boost of a status
+ * Undo a reshare of a status.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#unreblog}
+ */
+ unreblogStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/unreblog`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Bookmark a status
+ * Privately bookmark a status.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#bookmark}
+ */
+ bookmarkStatus: async (statusId: string, folderId?: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/bookmark`, { method: 'POST', body: { folder_id: folderId } });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Undo bookmark of a status
+ * Remove a status from your private bookmarks.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#unbookmark}
+ */
+ unbookmarkStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/unbookmark`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Mute a conversation
+ * Do not receive notifications for the thread that this status is part of. Must be a thread in which you are a participant.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#mute}
+ */
+ muteStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/mute`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Unmute a conversation
+ * Start receiving notifications again for the thread that this status is part of.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#unmute}
+ */
+ unmuteStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/unmute`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Pin status to profile
+ * Feature one of your own public statuses at the top of your profile.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#pin}
+ */
+ pinStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/pin`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Unpin status from profile
+ * Unfeature a status from the top of your profile.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#unpin}
+ */
+ unpinStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/unpin`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Edit a status
+ * Edit a given status to change its text, sensitivity, media attachments, or poll. Note that editing a poll’s options will reset the votes.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#unpin}
+ */
+ editStatus: async (statusId: string, params: EditStatusParams) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}`, { method: 'PUT', body: params });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * View edit history of a status
+ * Get all known versions of a status, including the initial and current states.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#history}
+ */
+ getStatusHistory: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/history`);
+
+ return filteredArray(statusEditSchema).parse(response.json);
+ },
+
+ /**
+ * View status source
+ * Obtain the source properties for a status so that it can be edited.
+ * @see {@link https://docs.joinmastodon.org/methods/statuses/#source}
+ */
+ getStatusSource: async (statusId: string) => {
+ const response = await this.request(`/api/v1/statuses/${statusId}/source`);
+
+ return statusSourceSchema.parse(response.json);
+ },
+
+ /**
+ * Get an object of emoji to account mappings with accounts that reacted to the post
+ *
+ * Requires features{@link Features['emojiReacts']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromastatusesidreactions}
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromastatusesidreactionsemoji}
+ */
+ getStatusReactions: async (statusId: string, emoji?: string) => {
+ const response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions${emoji ? `/${emoji}` : ''}`);
+
+ return filteredArray(emojiReactionSchema).parse(response.json);
+ },
+
+ /**
+ * React to a post with a unicode emoji
+ *
+ * Requires features{@link Features['emojiReacts']}.
+ * Using custom emojis requires features{@link Features['customEmojiReacts']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#put-apiv1pleromastatusesidreactionsemoji}
+ */
+ createStatusReaction: async (statusId: string, emoji: string) => {
+ const response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions/${emoji}`, { method: 'PUT' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Remove a reaction to a post with a unicode emoji
+ *
+ * Requires features{@link Features['emojiReacts']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#delete-apiv1pleromastatusesidreactionsemoji}
+ */
+ deleteStatusReaction: async (statusId: string, emoji: string) => {
+ const response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions/${emoji}`, { method: 'DELETE' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * View quotes for a given status
+ *
+ * Requires features{@link Features['quotePosts']}.
+ */
+ getStatusQuotes: async (statusId: string, params?: GetStatusQuotesParams) =>
+ this.#paginatedGet(`/api/v1/pleroma/statuses/${statusId}/quotes`, { params }, statusSchema),
+
+ /**
+ * Returns the list of accounts that have disliked the status as known by the current server
+ *
+ * Requires features{@link Features['statusDislikes']}.
+ * @see {@link https://github.com/friendica/friendica/blob/2024.06-rc/doc/API-Friendica.md#get-apifriendicastatusesiddisliked_by}
+ */
+ getDislikedBy: async (statusId: string) => {
+ const response = await this.request(`/api/friendica/statuses/${statusId}/disliked_by`);
+
+ return filteredArray(accountSchema).parse(response.json);
+ },
+
+ /**
+ * Marks the given status as disliked by this user
+ * @see {@link https://github.com/friendica/friendica/blob/2024.06-rc/doc/API-Friendica.md#post-apifriendicastatusesiddislike}
+ */
+ dislikeStatus: async (statusId: string) => {
+ const response = await this.request(`/api/friendica/statuses/${statusId}/dislike`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Removes the dislike mark (if it exists) on this status for this user
+ * @see {@link https://github.com/friendica/friendica/blob/2024.06-rc/doc/API-Friendica.md#post-apifriendicastatusesidundislike}
+ */
+ undislikeStatus: async (statusId: string) => {
+ const response = await this.request(`/api/friendica/statuses/${statusId}/undislike`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+ };
+
+ public readonly media = {
+ /**
+ * Upload media as an attachment
+ * Creates a media attachment to be used with a new status. The full sized media will be processed asynchronously in the background for large uploads.
+ * @see {@link https://docs.joinmastodon.org/methods/media/#v2}
+ */
+ uploadMedia: async (params: UploadMediaParams, meta?: RequestMeta) => {
+ const response = await this.request(
+ this.features.mediaV2 ? '/api/v2/media' : '/api/v1/media',
+ { ...meta, method: 'POST', body: params, contentType: '' },
+ );
+
+ return mediaAttachmentSchema.parse(response.json);
+ },
+
+ /**
+ * Get media attachment
+ * Get a media attachment, before it is attached to a status and posted, but after it is accepted for processing. Use this method to check that the full-sized media has finished processing.
+ * @see {@link https://docs.joinmastodon.org/methods/media/#get}
+ */
+ getMedia: async (attachmentId: string) => {
+ const response = await this.request(`/api/v1/media/${attachmentId}`);
+
+ return mediaAttachmentSchema.parse(response.json);
+ },
+
+ /**
+ * Update media attachment
+ * Update a MediaAttachment’s parameters, before it is attached to a status and posted.
+ * @see {@link https://docs.joinmastodon.org/methods/media/#update}
+ */
+ updateMedia: async (attachmentId: string, params: UpdateMediaParams) => {
+ const response = await this.request(`/api/v1/media/${attachmentId}`, {
+ method: 'PUT',
+ body: params, contentType: params.thumbnail ? '' : undefined,
+ });
+
+ return mediaAttachmentSchema.parse(response.json);
+ },
+ };
+
+ public readonly polls = {
+ /**
+ * View a poll
+ * View a poll attached to a status.
+ * @see {@link https://docs.joinmastodon.org/methods/polls/#get}
+ */
+ getPoll: async (pollId: string) => {
+ const response = await this.request(`/api/v1/polls/${pollId}`);
+
+ return pollSchema.parse(response.json);
+ },
+
+ /**
+ * Vote on a poll
+ * Vote on a poll attached to a status.
+ * @see {@link https://docs.joinmastodon.org/methods/polls/#vote}
+ */
+ vote: async (pollId: string, choices: number[]) => {
+ const response = await this.request(`/api/v1/polls/${pollId}/votes`, { method: 'POST', body: { choices } });
+
+ return pollSchema.parse(response.json);
+ },
+ };
+
+ public readonly scheduledStatuses = {
+ /**
+ * View scheduled statuses
+ * @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/#get}
+ */
+ getScheduledStatuses: async (params?: GetScheduledStatusesParams) =>
+ this.#paginatedGet('/api/v1/scheduled_statuses', { params }, scheduledStatusSchema),
+
+ /**
+ * View a single scheduled status
+ * @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/#get-one}
+ */
+ getScheduledStatus: async (scheduledStatusId: string) => {
+ const response = await this.request(`/api/v1/scheduled_statuses/${scheduledStatusId}`);
+
+ return scheduledStatusSchema.parse(response.json);
+ },
+
+ /**
+ * Update a scheduled status’s publishing date
+ * @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/#update}
+ */
+ updateScheduledStatus: async (scheduledStatusId: string, scheduled_at: string) => {
+ const response = await this.request(`/api/v1/scheduled_statuses/${scheduledStatusId}`, {
+ method: 'PUT',
+ body: { scheduled_at },
+ });
+
+ return scheduledStatusSchema.parse(response.json);
+ },
+
+ /**
+ * Cancel a scheduled status
+ * @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/#cancel}
+ */
+ cancelScheduledStatus: async (scheduledStatusId: string) => {
+ const response = await this.request(`/api/v1/scheduled_statuses/${scheduledStatusId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ };
+
+ public readonly timelines = {
+ /**
+ * View public timeline
+ * View public statuses.
+ * @see {@link https://docs.joinmastodon.org/methods/timelines/#public}
+ */
+ publicTimeline: (params?: PublicTimelineParams) =>
+ this.#paginatedGet('/api/v1/timelines/public', { params }, statusSchema),
+
+ /**
+ * View hashtag timeline
+ * View public statuses containing the given hashtag.
+ * @see {@link https://docs.joinmastodon.org/methods/timelines/#tag}
+ */
+ hashtagTimeline: (hashtag: string, params?: HashtagTimelineParams) =>
+ this.#paginatedGet(`/api/v1/timelines/tag/${hashtag}`, { params }, statusSchema),
+
+ /**
+ * View home timeline
+ * View statuses from followed users and hashtags.
+ * @see {@link https://docs.joinmastodon.org/methods/timelines/#home}
+ */
+ homeTimeline: (params?: HomeTimelineParams) =>
+ this.#paginatedGet('/api/v1/timelines/home', { params }, statusSchema),
+
+ /**
+ * View link timeline
+ * View public statuses containing a link to the specified currently-trending article. This only lists statuses from people who have opted in to discoverability features.
+ * @see {@link https://docs.joinmastodon.org/methods/timelines/#link}
+ */
+ linkTimeline: (url: string, params?: HashtagTimelineParams) =>
+ this.#paginatedGet('/api/v1/timelines/link', { params: { ...params, url } }, statusSchema),
+
+ /**
+ * View list timeline
+ * View statuses in the given list timeline.
+ * @see {@link https://docs.joinmastodon.org/methods/timelines/#list}
+ */
+ listTimeline: (listId: string, params?: ListTimelineParams) =>
+ this.#paginatedGet(`/api/v1/timelines/list/${listId}`, { params }, statusSchema),
+
+ /**
+ * View all conversations
+ * @see {@link https://docs.joinmastodon.org/methods/conversations/#get}
+ */
+ getConversations: (params?: GetConversationsParams) =>
+ this.#paginatedGet('/api/v1/conversations', { params }, conversationSchema),
+
+ /**
+ * Remove a conversation
+ * Removes a conversation from your list of conversations.
+ * @see {@link https://docs.joinmastodon.org/methods/conversations/#delete}
+ */
+ deleteConversation: async (conversationId: string) => {
+ const response = await this.request(`/api/v1/conversations/${conversationId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Mark a conversation as read
+ * @see {@link https://docs.joinmastodon.org/methods/conversations/#read}
+ */
+ markConversationRead: async (conversationId: string) => {
+ const response = await this.request(`/api/v1/conversations/${conversationId}/read`, { method: 'POST' });
+
+ return conversationSchema.parse(response.json);
+ },
+
+ /**
+ * Get saved timeline positions
+ * Get current positions in timelines.
+ * @see {@link https://docs.joinmastodon.org/methods/markers/#get}
+ */
+ getMarkers: async (timelines?: string[]) => {
+ const response = await this.request('/api/v1/markers', { params: { timeline: timelines } });
+
+ return markersSchema.parse(response.json);
+ },
+
+ /**
+ * Save your position in a timeline
+ * Save current position in timeline.
+ * @see {@link https://docs.joinmastodon.org/methods/markers/#create}
+ */
+ saveMarkers: async (params: SaveMarkersParams) => {
+ const response = await this.request('/api/v1/markers', { method: 'POST', body: params });
+
+ return markersSchema.parse(response.json);
+ },
+
+ /**
+ * Requires features{@link Features['groups']}.
+ */
+ groupTimeline: async (groupId: string, params?: GroupTimelineParams) =>
+ this.#paginatedGet(`/api/v1/timelines/group/${groupId}`, { params }, statusSchema),
+
+ /**
+ * Requires features{@link Features['bubbleTimeline']}.
+ */
+ bubbleTimeline: async (params?: BubbleTimelineParams) =>
+ this.#paginatedGet('/api/v1/timelines/bubble', { params }, statusSchema),
+ };
+
+ public readonly lists = {
+ /**
+ * View your lists
+ * Fetch all lists that the user owns.
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#get}
+ */
+ getLists: async () => {
+ const response = await this.request('/api/v1/lists');
+
+ return filteredArray(listSchema).parse(response.json);
+ },
+
+ /**
+ * Show a single list
+ * Fetch the list with the given ID. Used for verifying the title of a list, and which replies to show within that list.
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#get-one}
+ */
+ getList: async (listId: string) => {
+ const response = await this.request(`/api/v1/lists/${listId}`);
+
+ return listSchema.parse(response.json);
+ },
+
+ /**
+ * Create a list
+ * Create a new list.
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#create}
+ */
+ createList: async (params: CreateListParams) => {
+ const response = await this.request('/api/v1/lists', { method: 'POST', body: params });
+
+ return listSchema.parse(response.json);
+ },
+
+ /**
+ * Update a list
+ * Change the title of a list, or which replies to show.
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#update}
+ */
+ updateList: async (listId: string, params: UpdateListParams) => {
+ const response = await this.request(`/api/v1/lists/${listId}`, { method: 'PUT', body: params });
+
+ return listSchema.parse(response.json);
+ },
+
+ /**
+ * Delete a list
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#delete}
+ */
+ deleteList: async (listId: string) => {
+ const response = await this.request(`/api/v1/lists/${listId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ /**
+ * View accounts in a list
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#accounts}
+ */
+ getListAccounts: async (listId: string, params?: GetListAccountsParams) =>
+ this.#paginatedGet(`/api/v1/lists/${listId}/accounts`, { params }, accountSchema),
+
+ /**
+ * Add accounts to a list
+ * Add accounts to the given list. Note that the user must be following these accounts.
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#accounts-add}
+ */
+ addListAccounts: async (listId: string, accountIds: string[]) => {
+ const response = await this.request(`/api/v1/lists/${listId}/accounts`, {
+ method: 'POST', body: { account_ids: accountIds },
+ });
+
+ return response.json as {};
+ },
+
+ /**
+ * Remove accounts from list
+ * Remove accounts from the given list.
+ * @see {@link https://docs.joinmastodon.org/methods/lists/#accounts-remove}
+ */
+ deleteListAccounts: async (listId: string, accountIds: string[]) => {
+ const response = await this.request(`/api/v1/lists/${listId}/accounts`, {
+ method: 'DELETE', body: { account_ids: accountIds },
+ });
+
+ return response.json as {};
+ },
+ };
+
+ public readonly streaming = {
+ /**
+ * Check if the server is alive
+ * Verify that the streaming service is alive before connecting to it
+ * @see {@link https://docs.joinmastodon.org/methods/streaming/#health}
+ */
+ health: async () => {
+ const response = await this.request('/api/v1/streaming/health');
+
+ return z.literal('OK').parse(response.json);
+ },
+
+ /**
+ * Establishing a WebSocket connection
+ * Open a multiplexed WebSocket connection to receive events.
+ * @see {@link https://docs.joinmastodon.org/methods/streaming/#websocket}
+ */
+ connect: () => {
+ if (this.#socket) return this.#socket;
+
+ const path = buildFullPath('/api/v1/streaming', this.#instance?.configuration.urls.streaming, { access_token: this.accessToken });
+
+ const ws = new WebSocket(path, this.accessToken as any);
+
+ let listeners: Array<{ listener: (event: StreamingEvent) => any; stream?: string }> = [];
+ const queue: Array<() => any> = [];
+
+ const enqueue = (fn: () => any) => ws.readyState === WebSocket.CONNECTING ? queue.push(fn) : fn();
+
+ ws.onmessage = (event) => {
+ const message = streamingEventSchema.parse(JSON.parse(event.data as string));
+
+ listeners.filter(({ listener, stream }) => (!stream || message.stream.includes(stream)) && listener(message));
+ };
+
+ ws.onopen = () => {
+ queue.forEach(fn => fn());
+ };
+
+ this.#socket = {
+ listen: (listener: (event: StreamingEvent) => any, stream?: string) => listeners.push({ listener, stream }),
+ unlisten: (listener: (event: StreamingEvent) => any) => listeners = listeners.filter((value) => value.listener !== listener),
+ subscribe: (stream: string, { list, tag }: { list?: string; tag?: string } = {}) =>
+ enqueue(() => ws.send(JSON.stringify({ type: 'subscribe', stream, list, tag }))),
+ unsubscribe: (stream: string, { list, tag }: { list?: string; tag?: string } = {}) =>
+ enqueue(() => ws.send(JSON.stringify({ type: 'unsubscribe', stream, list, tag }))),
+ close: () => {
+ ws.close();
+ this.#socket = undefined;
+ },
+ };
+
+ return this.#socket;
+ },
+ };
+
+ public readonly notifications = {
+ /**
+ * Get all notifications
+ * Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#get}
+ */
+ getNotifications: async (params?: GetNotificationParams, meta?: RequestMeta) => {
+ const PLEROMA_TYPES = [
+ 'chat_mention', 'emoji_reaction', 'report', 'participation_accepted', 'participation_request', 'event_reminder', 'event_update',
+ ];
+
+ if (params?.types) params.types = [
+ ...params.types,
+ ...params.types.filter(type => PLEROMA_TYPES.includes(type)).map(type => `pleroma:${type}`),
+ ];
+
+ if (params?.exclude_types) params.exclude_types = [
+ ...params.exclude_types,
+ ...params.exclude_types.filter(type => PLEROMA_TYPES.includes(type)).map(type => `pleroma:${type}`),
+ ];
+
+ return this.#paginatedGet('/api/v1/notifications', { ...meta, params }, notificationSchema);
+ },
+
+ /**
+ * Get a single notification
+ * View information about a notification with a given ID.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#get-one}
+ */
+ getNotification: async (notificationId: string) => {
+ const response = await this.request(`/api/v1/notifications/${notificationId}`);
+
+ return notificationSchema.parse(response.json);
+ },
+
+ /**
+ * Dismiss all notifications
+ * Clear all notifications from the server.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#clear}
+ */
+ dismissNotifications: async () => {
+ const response = await this.request('/api/v1/notifications/clear', { method: 'POST' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Dismiss a single notification
+ * Dismiss a single notification from the server.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#dismiss}
+ */
+ dismissNotification: async (notificationId: string) => {
+ const response = await this.request(`/api/v1/notifications/${notificationId}/dismiss`, { method: 'POST' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Get the filtering policy for notifications
+ * Notifications filtering policy for the user.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#get-policy}
+ */
+ getNotificationPolicy: async () => {
+ const response = await this.request('/api/v1/notifications/policy');
+
+ return notificationPolicySchema.parse(response.json);
+ },
+
+ /**
+ * Update the filtering policy for notifications
+ * Update the user’s notifications filtering policy.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#update-the-filtering-policy-for-notifications}
+ */
+ updateNotificationPolicy: async (params: UpdateNotificationPolicyRequest) => {
+ const response = await this.request('/api/v1/notifications/policy', { method: 'POST', body: params });
+
+ return notificationPolicySchema.parse(response.json);
+ },
+
+ /**
+ * Get all notification requests
+ * Notification requests for notifications filtered by the user’s policy. This API returns Link headers containing links to the next/previous page.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#get-requests}
+ */
+ getNotificationRequests: async (params?: GetNotificationRequestsParams) =>
+ this.#paginatedGet('/api/v1/notifications/requests', { params }, notificationRequestSchema),
+
+ /**
+ * Get a single notification request
+ * View information about a notification request with a given ID.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#get-one-request}
+ */
+ getNotificationRequest: async (notificationRequestId: string) => {
+ const response = await this.request(`/api/v1/notifications/requests/${notificationRequestId}`);
+
+ return notificationRequestSchema.parse(response.json);
+ },
+
+ /**
+ * Accept a single notification request
+ * Accept a notification request, which merges the filtered notifications from that user back into the main notification and accepts any future notification from them.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#accept-request}
+ */
+ acceptNotificationRequest: async (notificationRequestId: string) => {
+ const response = await this.request(`/api/v1/notifications/requests/${notificationRequestId}/dismiss`, { method: 'POST' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Dismiss a single notification request
+ * Dismiss a notification request, which hides it and prevent it from contributing to the pending notification requests count.
+ * @see {@link https://docs.joinmastodon.org/methods/notifications/#dismiss-request}
+ */
+ dismissNotificationRequest: async (notificationRequestId: string) => {
+ const response = await this.request(`/api/v1/notifications/requests/${notificationRequestId}/dismiss`, { method: 'POST' });
+
+ return response.json as {};
+ },
+
+ /**
+ * An endpoint to delete multiple statuses by IDs.
+ *
+ * Requires features{@link Features['notificationsDismissMultiple']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/differences_in_mastoapi_responses/#delete-apiv1notificationsdestroy_multiple}
+ */
+ dismissMultipleNotifications: async (notificationIds: string[]) => {
+ const response = await this.request('/api/v1/notifications/destroy_multiple', {
+ params: { ids: notificationIds },
+ method: 'DELETE',
+ });
+
+ return response.json as {};
+ },
+ };
+
+ public readonly pushNotifications = {
+ /**
+ * Subscribe to push notifications
+ * Add a Web Push API subscription to receive notifications. Each access token can have one push subscription. If you create a new subscription, the old subscription is deleted.
+ * @see {@link https://docs.joinmastodon.org/methods/push/#create}
+ */
+ createSubscription: async (params: CreatePushNotificationsSubscriptionParams) => {
+ const response = await this.request('/api/v1/push/subscription', { method: 'POST', body: params });
+
+ return webPushSubscriptionSchema.parse(response.json);
+ },
+
+ /**
+ * Get current subscription
+ * View the PushSubscription currently associated with this access token.
+ * @see {@link https://docs.joinmastodon.org/methods/push/#get}
+ */
+ getSubscription: async () => {
+ const response = await this.request('/api/v1/push/subscription');
+
+ return webPushSubscriptionSchema.parse(response.json);
+ },
+
+ /**
+ * Change types of notifications
+ * Updates the current push subscription. Only the data part can be updated. To change fundamentals, a new subscription must be created instead.
+ * @see {@link https://docs.joinmastodon.org/methods/push/#update}
+ */
+ updateSubscription: async (params: UpdatePushNotificationsSubscriptionParams) => {
+ const response = await this.request('/api/v1/push/subscription', { method: 'PUT', body: params });
+
+ return webPushSubscriptionSchema.parse(response.json);
+ },
+
+ /**
+ * Remove current subscription
+ * Removes the current Web Push API subscription.
+ * @see {@link https://docs.joinmastodon.org/methods/push/#delete}
+ */
+ deleteSubscription: async () => {
+ const response = await this.request('/api/v1/push/subscription', { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ };
+
+ public readonly search = {
+ /**
+ * Perform a search
+ * @see {@link https://docs.joinmastodon.org/methods/search/#v2}
+ */
+ search: async (q: string, params?: SearchParams, meta?: RequestMeta) => {
+ const response = await this.request('/api/v2/search', { ...meta, params: { ...params, q } });
+
+ return searchSchema.parse(response.json);
+ },
+
+ /**
+ * Searches for locations
+ *
+ * Requires features{@link Features['events']}.
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#apiv1pleromasearchlocation}
+ */
+ searchLocation: async (q: string, meta?: RequestMeta) => {
+ const response = await this.request('/api/v1/pleroma/search/location', { ...meta, params: { q } });
+
+ return filteredArray(locationSchema).parse(response.json);
+ },
+ };
+
+ public readonly instance = {
+ /**
+ * View server information
+ * Obtain general information about the server.
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#v2}
+ */
+ getInstance: async () => {
+ let response;
+ try {
+ response = await this.request('/api/v2/instance');
+ } catch (e) {
+ response = await this.request('/api/v1/instance');
+ }
+
+ const instance = instanceSchema.readonly().parse(response.json);
+ this.#setInstance(instance);
+
+ return instance;
+ },
+
+ /**
+ * List of connected domains
+ * Domains that this instance is aware of.
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#peers}
+ */
+ getInstancePeers: async () => {
+ const response = await this.request('/api/v1/instance/peers');
+
+ return z.array(z.string()).parse(response.json);
+ },
+
+ /**
+ * Weekly activity
+ * Instance activity over the last 3 months, binned weekly.
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#activity}
+ */
+ getInstanceActivity: async () => {
+ const response = await this.request('/api/v1/instance/activity');
+
+ return z.array(z.object({
+ week: z.string(),
+ statuses: z.coerce.string(),
+ logins: z.coerce.string(),
+ registrations: z.coerce.string(),
+ })).parse(response.json);
+ },
+
+ /**
+ * List of rules
+ * Rules that the users of this service should follow.
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#rules}
+ */
+ getInstanceRules: async () => {
+ const response = await this.request('/api/v1/instance/rules');
+
+ return filteredArray(ruleSchema).parse(response.json);
+ },
+
+ /**
+ * View moderated servers
+ * Obtain a list of domains that have been blocked.
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#domain_blocks}
+ */
+ getInstanceDomainBlocks: async () => {
+ const response = await this.request('/api/v1/instance/rules');
+
+ return filteredArray(domainBlockSchema).parse(response.json);
+ },
+
+ /**
+ * View extended description
+ * Obtain an extended description of this server
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#extended_description}
+ */
+ getInstanceExtendedDescription: async () => {
+ const response = await this.request('/api/v1/instance/extended_description');
+
+ return extendedDescriptionSchema.parse(response.json);
+ },
+
+ /**
+ * View translation languages
+ * Translation language pairs supported by the translation engine used by the server.
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#translation_languages}
+ */
+ getInstanceTranslationLanguages: async () => {
+ if (this.features.version.build === AKKOMA) {
+ const response = await this.request<{
+ source: Array<{ code: string; name: string }>;
+ target: Array<{ code: string; name: string }>;
+ }>('/api/v1/akkoma/translation/languages');
+
+ return Object.fromEntries(response.json.source.map(source => [
+ source.code.toLocaleLowerCase(),
+ response.json.target.map(lang => lang.code).filter(lang => lang !== source.code).map(lang => lang.toLocaleLowerCase()),
+ ]));
+ }
+
+ const response = await this.request('/api/v1/instance/translation_languages');
+
+ return z.record(z.array(z.string())).parse(response.json);
+ },
+
+ /**
+ * View profile directory
+ * List accounts visible in the directory.
+ * @see {@link https://docs.joinmastodon.org/methods/directory/#get}
+ */
+ profileDirectory: async (params?: ProfileDirectoryParams) => {
+ const response = await this.request('/api/v1/directory', { params });
+
+ return filteredArray(accountSchema).parse(response.json);
+ },
+
+ /**
+ * View all custom emoji
+ * Returns custom emojis that are available on the server.
+ * @see {@link https://docs.joinmastodon.org/methods/custom_emojis/#get}
+ */
+ getCustomEmojis: async () => {
+ const response = await this.request('/api/v1/custom_emojis');
+
+ return filteredArray(customEmojiSchema).parse(response.json);
+ },
+
+ /**
+ * Dump frontend configurations
+ *
+ * Requires features{@link Features['frontendConfigurations']}.
+ */
+ getFrontendConfigurations: async () => {
+ const response = await this.request('/api/pleroma/frontend_configurations');
+
+ return z.record(z.record(z.any())).catch({}).parse(response);
+ },
+ };
+
+ public readonly trends = {
+ /**
+ * View trending tags
+ * Tags that are being used more frequently within the past week.
+ * @see {@link https://docs.joinmastodon.org/methods/trends/#tags}
+ */
+ getTrendingTags: async (params?: GetTrendingTags) => {
+ const response = await this.request('/api/v1/trends/tags', { params });
+
+ return filteredArray(tagSchema).parse(response.json);
+ },
+
+ /**
+ * View trending statuses
+ * Statuses that have been interacted with more than others.
+ * @see {@link https://docs.joinmastodon.org/methods/trends/#statuses}
+ */
+ getTrendingStatuses: async (params?: GetTrendingStatuses) => {
+ const response = await this.request('/api/v1/trends/statuses', { params });
+
+ return filteredArray(statusSchema).parse(response.json);
+ },
+
+ /**
+ * View trending links
+ * Links that have been shared more than others.
+ * @see {@link https://docs.joinmastodon.org/methods/trends/#links}
+ */
+ getTrendingLinks: async (params?: GetTrendingLinks) => {
+ const response = await this.request('/api/v1/trends/links', { params });
+
+ return filteredArray(trendsLinkSchema).parse(response.json);
+ },
+ };
+
+ public readonly announcements = {
+ /**
+ * View all announcements
+ * See all currently active announcements set by admins.
+ * @see {@link https://docs.joinmastodon.org/methods/announcements/#get}
+ */
+ getAnnouncements: async () => {
+ const response = await this.request('/api/v1/announcements');
+
+ return filteredArray(announcementSchema).parse(response.json);
+ },
+
+ /**
+ * Dismiss an announcement
+ * Allows a user to mark the announcement as read.
+ * @see {@link https://docs.joinmastodon.org/methods/announcements/#dismiss}
+ */
+ dismissAnnouncements: async (announcementId: string) => {
+ const response = await this.request(`/api/v1/announcements/${announcementId}`, { method: 'POST' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Add a reaction to an announcement
+ * React to an announcement with an emoji.
+ * @see {@link https://docs.joinmastodon.org/methods/announcements/#put-reactions}
+ */
+ addAnnouncementReaction: async (announcementId: string, emoji: string) => {
+ const response = await this.request(`/api/v1/announcements/${announcementId}/reactions/${emoji}`, { method: 'PUT' });
+
+ return response.json as {};
+ },
+
+ /**
+ * Remove a reaction from an announcement
+ * Undo a react emoji to an announcement.
+ * @see {@link https://docs.joinmastodon.org/methods/announcements/#delete-reactions}
+ */
+ deleteAnnouncementReaction: async (announcementId: string, emoji: string) => {
+ const response = await this.request(`/api/v1/announcements/${announcementId}/reactions/${emoji}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ };
+
+ public readonly admin = {
+ /** Perform moderation actions with accounts. */
+ accounts: {
+ /**
+ * View accounts
+ * View all accounts, optionally matching certain criteria for filtering, up to 100 at a time.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#v2}
+ */
+ getAccounts: async (params?: AdminGetAccountsParams) => {
+ if (this.features.mastodonAdminV2) {
+ return this.#paginatedGet('/api/v2/admin/accounts', { params }, adminAccountSchema);
+ } else {
+ return this.#paginatedPleromaAccounts(params ? {
+ query: params.username,
+ name: params.display_name,
+ email: params.email,
+ filters: [
+ params.origin === 'local' && 'local',
+ params.origin === 'remote' && 'external',
+ params.status === 'active' && 'active',
+ params.status === 'pending' && 'need_approval',
+ params.status === 'disabled' && 'deactivated',
+ params.permissions === 'staff' && 'is_admin',
+ params.permissions === 'staff' && 'is_moderator',
+ ].filter(filter => filter).join(','),
+ page_size: 100,
+ } : { page_size: 100 });
+ }
+ },
+
+ /**
+ * View a specific account
+ * View admin-level information about the given account.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#get-one}
+ */
+ getAccount: async (accountId: string) => {
+ let response;
+
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/accounts/${accountId}`);
+ } else {
+ response = await this.request(`/api/v1/admin/users/${accountId}`);
+ }
+
+ return adminAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Approve a pending account
+ * Approve the given local account if it is currently pending approval.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#approve}
+ */
+ approveAccount: async (accountId: string) => {
+ let response;
+
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/accounts/${accountId}/approve`, { method: 'POST' });
+ } else {
+ const account = await this.admin.accounts.getAccount(accountId)!;
+
+ response = await this.request('/api/v1/pleroma/admin/users/approve', { body: { nicknames: [account.account!.username] } });
+ response.json = response.json?.users?.[0];
+ }
+
+ return adminAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Reject a pending account
+ * Reject the given local account if it is currently pending approval.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#reject}
+ */
+ rejectAccount: async (accountId: string) => {
+ let response;
+
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/accounts/${accountId}/reject`, { method: 'POST' });
+ } else {
+ const account = await this.admin.accounts.getAccount(accountId)!;
+
+ response = await this.request('/api/v1/pleroma/admin/users/approve', { body: {
+ method: 'DELETE',
+ nicknames: [account.account!.username],
+ } });
+ }
+
+ return adminAccountSchema.safeParse(response.json).data || {};
+ },
+
+ /**
+ * Delete an account
+ * Permanently delete data for a suspended account.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#delete}
+ */
+ deleteAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/admin/accounts/${accountId}`, { method: 'DELETE' });
+
+ return adminAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Perform an action against an account
+ * Perform an action against an account and log this action in the moderation history. Also resolves any open reports against this account.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#action}
+ */
+ performAccountAction: async (accountId: string, type: AdminAccountAction, params?: AdminPerformAccountActionParams) => {
+ let response;
+
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/accounts/${accountId}/action`, { body: { ...params, type } });
+ } else {
+ const account = await this.admin.accounts.getAccount(accountId)!;
+
+ switch (type) {
+ case 'disable':
+ case 'suspend':
+ response = await this.request('/api/v1/pleroma/admin/users/deactivate', { body: { nicknames: [account.account!.username] } });
+ break;
+ default:
+ response = { json: {} };
+ break;
+ }
+ if (params?.report_id) await this.admin.reports.resolveReport(params.report_id);
+ }
+
+ return response.json as {};
+ },
+
+ /**
+ * Enable a currently disabled account
+ * Re-enable a local account whose login is currently disabled.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#enable}
+ */
+ enableAccount: async (accountId: string) => {
+ let response;
+
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/accounts/${accountId}/enable`, { method: 'POST' });
+ } else {
+ const account = await this.admin.accounts.getAccount(accountId)!;
+
+ response = await this.request('/api/v1/pleroma/admin/users/activate', { body: { nicknames: [account.account!.username] } });
+ response.json = response.json?.users?.[0];
+ }
+
+ return adminAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Unsilence an account
+ * Unsilence an account if it is currently silenced.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#unsilence}
+ */
+ unsilenceAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/admin/accounts/${accountId}/unsilence`, { method: 'POST' });
+
+ return adminAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Unsuspend an account
+ * Unsuspend a currently suspended account.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#unsuspend}
+ */
+ unsuspendAccount: async (accountId: string) => {
+ let response;
+
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/accounts/${accountId}/unsuspend`, { method: 'POST' });
+ } else {
+ const { account } = await this.admin.accounts.getAccount(accountId)!;
+
+ response = await this.request('/api/v1/pleroma/admin/users/activate', { body: { nicknames: [account!.username] } });
+ response.json = response.json?.users?.[0];
+ }
+
+ return adminAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Unmark an account as sensitive
+ * Stops marking an account’s posts as sensitive, if it was previously flagged as sensitive.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/accounts/#unsensitive}
+ */
+ unsensitiveAccount: async (accountId: string) => {
+ const response = await this.request(`/api/v1/admin/accounts/${accountId}/unsensitive`, { method: 'POST' });
+
+ return adminAccountSchema.parse(response.json);
+ },
+
+ /**
+ * Requires features{@link Features['pleromaAdminAccounts']}.
+ */
+ promoteToAdmin: async (accountId: string) => {
+ const { account } = await this.admin.accounts.getAccount(accountId)!;
+
+ await this.request('/api/v1/pleroma/admin/users/permission_group/moderator', {
+ method: 'DELETE',
+ body: { nicknames: [account!.username] },
+ });
+ const response = await this.request('/api/v1/pleroma/admin/users/permission_group/admin', {
+ method: 'POST',
+ body: { nicknames: [account!.username] },
+ });
+
+ return response.json as {};
+ },
+
+ /**
+ * Requires features{@link Features['pleromaAdminAccounts']}.
+ */
+ promoteToModerator: async (accountId: string) => {
+ const { account } = await this.admin.accounts.getAccount(accountId)!;
+
+ await this.request('/api/v1/pleroma/admin/users/permission_group/admin', {
+ method: 'DELETE', body: { nicknames: [account!.username] } });
+ const response = await this.request('/api/v1/pleroma/admin/users/permission_group/moderator', {
+ method: 'POST', body: { nicknames: [account!.username] } });
+
+ return response.json as {};
+ },
+
+ /**
+ * Requires features{@link Features['pleromaAdminAccounts']}.
+ */
+ demoteToUser: async (accountId: string) => {
+ const { account } = await this.admin.accounts.getAccount(accountId)!;
+
+ await this.request('/api/v1/pleroma/admin/users/permission_group/moderator', {
+ method: 'DELETE',
+ body: { nicknames: [account!.username] },
+ });
+ const response = await this.request('/api/v1/pleroma/admin/users/permission_group/admin', {
+ method: 'DELETE',
+ body: { nicknames: [account!.username] },
+ });
+
+ return response.json as {};
+ },
+ },
+
+ /** Disallow certain domains to federate. */
+ domainBlocks: {
+ /**
+ * List all blocked domains
+ * Show information about all blocked domains.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_blocks/#get}
+ */
+ getDomainBlocks: (params?: AdminGetDomainBlocksParams) =>
+ this.#paginatedGet('/api/v1/admin/domain_blocks', { params }, adminDomainBlockSchema),
+
+ /**
+ * Get a single blocked domain
+ * Show information about a single blocked domain.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_blocks/#get-one}
+ */
+ getDomainBlock: async (domainBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/domain_blocks/${domainBlockId}`);
+
+ return adminDomainBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Block a domain from federating
+ * Add a domain to the list of domains blocked from federating.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_blocks/#create}
+ */
+ createDomainBlock: async (domain: string, params?: AdminCreateDomainBlockParams) => {
+ const response = await this.request('/api/v1/admin/domain_blocks', {
+ method: 'POST',
+ body: { ...params, domain },
+ });
+
+ return adminDomainBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Update a domain block
+ * Change parameters for an existing domain block.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_blocks/#update}
+ */
+ updateDomainBlock: async (domainBlockId: string, params?: AdminUpdateDomainBlockParams) => {
+ const response = await this.request(`/api/v1/admin/domain_blocks/${domainBlockId}`, {
+ method: 'PUT',
+ body: params,
+ });
+
+ return adminDomainBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Remove a domain block
+ * Lift a block against a domain.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_blocks/#delete}
+ */
+ deleteDomainBlock: async (domainBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/domain_blocks/${domainBlockId}`, {
+ method: 'DELETE',
+ });
+
+ return response.json as {};
+ },
+ },
+
+ /** Perform moderation actions with reports. */
+ reports: {
+ /**
+ * View all reports
+ * View information about all reports.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/reports/#get}
+ */
+ getReports: async (params?: AdminGetReportsParams) => {
+ if (this.features.mastodonAdmin) {
+ return this.#paginatedGet('/api/v1/admin/reports', { params }, adminReportSchema);
+ } else {
+ return this.#paginatedPleromaReports({
+ state: params?.resolved === true ? 'resolved' : params?.resolved === false ? 'open' : undefined,
+ page_size: params?.limit || 100,
+ });
+ }
+ },
+
+ /**
+ * View a single report
+ * @see {@link https://docs.joinmastodon.org/methods/admin/reports/#get-one}
+ */
+ getReport: async (reportId: string) => {
+ let response;
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/reports/${reportId}`);
+ } else {
+ response = await this.request(`/api/v1/pleroma/admin/reports/${reportId}`);
+ }
+
+ return adminReportSchema.parse(response.json);
+ },
+
+ /**
+ * Update a report
+ * Change metadata for a report.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/reports/#update}
+ */
+ updateReport: async (reportId: string, params: AdminUpdateReportParams) => {
+ const response = await this.request(`/api/v1/admin/reports/${reportId}`, { method: 'PUT', body: params });
+
+ return adminReportSchema.parse(response.json);
+ },
+
+ /**
+ * Assign report to self
+ * Claim the handling of this report to yourself.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/reports/#assign_to_self}
+ */
+ assignReportToSelf: async (reportId: string) => {
+ const response = await this.request(`/api/v1/admin/reports/${reportId}/assign_to_self`, { method: 'POST' });
+
+ return adminReportSchema.parse(response.json);
+ },
+
+ /**
+ * Unassign report
+ * Unassign a report so that someone else can claim it.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/reports/#unassign}
+ */
+ unassignReport: async (reportId: string) => {
+ const response = await this.request(`/api/v1/admin/reports/${reportId}/unassign`, { method: 'POST' });
+
+ return adminReportSchema.parse(response.json);
+ },
+
+ /**
+ * Mark report as resolved
+ * Mark a report as resolved with no further action taken.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/reports/#resolve}
+ */
+ resolveReport: async (reportId: string) => {
+ let response;
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/reports/${reportId}/resolve`, { method: 'POST' });
+ } else {
+ response = await this.request(`/api/v1/pleroma/admin/reports/${reportId}`, {
+ method: 'PATCH',
+ body: { reports: [{ id: reportId, state: 'resolved' }] },
+ });
+ }
+
+ return adminReportSchema.parse(response.json);
+ },
+
+ /**
+ * Reopen a closed report
+ * Reopen a currently closed report, if it is closed.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/reports/#reopen}
+ */
+ reopenReport: async (reportId: string) => {
+ let response;
+ if (this.features.mastodonAdmin) {
+ response = await this.request(`/api/v1/admin/reports/${reportId}/reopen`, { method: 'POST' });
+ } else {
+ response = await this.request(`/api/v1/pleroma/admin/reports/${reportId}`, {
+ method: 'PATCH',
+ body: { reports: [{ id: reportId, state: 'open' }] },
+ });
+ }
+
+ return adminReportSchema.parse(response.json);
+ },
+ },
+
+ statuses: {
+ /**
+ * @param params Retrieves all latest statuses
+ *
+ * The params are subject to change in case Mastodon implements alike route.
+ *
+ * Requires features{@link Features['pleromaAdminStatuses']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminstatuses}
+ */
+ getStatuses: async (params?: AdminGetStatusesParams) => this.#paginatedPleromaStatuses({
+ page_size: params?.limit || 100,
+ page: 1,
+ local_only: params?.local_only,
+ with_reblogs: params?.with_reblogs,
+ godmode: params?.with_private,
+ }),
+
+ /**
+ * Show status by id
+ *
+ * Requires features{@link Features['pleromaAdminStatuses']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminstatusesid}
+ */
+ getStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/pleroma/admin/statuses/${statusId}`);
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Change the scope of an individual reported status
+ *
+ * Requires features{@link Features['pleromaAdminStatuses']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#put-apiv1pleromaadminstatusesid}
+ */
+ updateStatus: async (statusId: string, params: AdminUpdateStatusParams) => {
+ const response = await this.request(`/api/v1/pleroma/admin/statuses/${statusId}`, { method: 'PUT', params });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Delete an individual reported status
+ *
+ * Requires features{@link Features['pleromaAdminStatuses']}.
+ * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#delete-apiv1pleromaadminstatusesid}
+ */
+ deleteStatus: async (statusId: string) => {
+ const response = await this.request(`/api/v1/pleroma/admin/statuses/${statusId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ },
+
+ trends: {
+ /**
+ * View trending links
+ * Links that have been shared more than others, including unapproved and unreviewed links.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/trends/#links}
+ */
+ getTrendingLinks: async () => {
+ const response = await this.request('/api/v1/admin/trends/links');
+
+ return filteredArray(trendsLinkSchema).parse(response.json);
+ },
+
+ /**
+ * View trending statuses
+ * Statuses that have been interacted with more than others, including unapproved and unreviewed statuses.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/trends/#statuses}
+ */
+ getTrendingStatuses: async () => {
+ const response = await this.request('/api/v1/admin/trends/statuses');
+
+ return filteredArray(statusSchema).parse(response.json);
+ },
+
+ /**
+ * View trending tags
+ * Tags that are being used more frequently within the past week, including unapproved and unreviewed tags.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/trends/#tags}
+ */
+ getTrendingTags: async () => {
+ const response = await this.request('/api/v1/admin/trends/links');
+
+ return filteredArray(adminTagSchema).parse(response.json);
+ },
+ },
+
+ /** Block certain email addresses by their hash. */
+ canonicalEmailBlocks: {
+ /**
+ * List all canonical email blocks
+ * @see {@link https://docs.joinmastodon.org/methods/admin/canonical_email_blocks/#get}
+ */
+ getCanonicalEmailBlocks: async (params?: AdminGetCanonicalEmailBlocks) =>
+ this.#paginatedGet('/api/v1/admin/canonical_email_blocks', { params }, adminCanonicalEmailBlockSchema),
+
+ /**
+ * Show a single canonical email block
+ * @see {@link https://docs.joinmastodon.org/methods/admin/canonical_email_blocks/#get-one}
+ */
+ getCanonicalEmailBlock: async (canonicalEmailBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/canonical_email_blocks/${canonicalEmailBlockId}`);
+
+ return adminCanonicalEmailBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Test
+ * Canoniocalize and hash an email address.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/canonical_email_blocks/#test}
+ */
+ testCanonicalEmailBlock: async (email: string) => {
+ const response = await this.request('/api/v1/admin/canonical_email_blocks/test', { method: 'POST', body: { email } });
+
+ return filteredArray(adminCanonicalEmailBlockSchema).parse(response.json);
+ },
+
+ /**
+ * Block a canonical email
+ * @see {@link https://docs.joinmastodon.org/methods/admin/canonical_email_blocks/#create}
+ */
+ createCanonicalEmailBlock: async (email: string, canonical_email_hash?: string) => {
+ const response = await this.request('/api/v1/admin/canonical_email_blocks', { method: 'POST', body: { email, canonical_email_hash } });
+
+ return filteredArray(adminCanonicalEmailBlockSchema).parse(response.json);
+ },
+
+ /**
+ * Delete a canonical email block
+ * @see {@link https://docs.joinmastodon.org/methods/admin/canonical_email_blocks/#delete}
+ */
+ deleteCanonicalEmailBlock: async (canonicalEmailBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/canonical_email_blocks/${canonicalEmailBlockId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ },
+
+ /** Obtain qualitative metrics about the server. */
+ dimensions: {
+ /**
+ * Get dimensional data
+ * Obtain information about popularity of certain accounts, servers, languages, etc.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/dimensions/#get}
+ */
+ getDimensions: async (keys: AdminDimensionKey[], params?: AdminGetDimensionsParams) => {
+ const response = await this.request('/api/v1/admin/dimensions', { params: { ...params, keys } });
+
+ return filteredArray(adminDimensionSchema).parse(response.json);
+ },
+ },
+
+ /** Allow certain domains to federate. */
+ domainAllows: {
+ /**
+ * List all allowed domains
+ * Show information about all allowed domains.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_allows/#get}
+ */
+ getDomainAllows: (params?: AdminGetDomainAllowsParams) =>
+ this.#paginatedGet('/api/v1/admin/domain_allows', { params }, adminDomainAllowSchema),
+
+ /**
+ * Get a single allowed domain
+ * Show information about a single allowed domain.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_allows/#get-one}
+ */
+ getDomainAllow: async (domainAllowId: string) => {
+ const response = await this.request(`/api/v1/admin/domain_allows/${domainAllowId}`);
+
+ return adminDomainAllowSchema.parse(response.json);
+ },
+
+ /**
+ * Allow a domain to federate
+ * Add a domain to the list of domains allowed to federate, to be used when the instance is in allow-list federation mode.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_allows/#create}
+ */
+ createDomainAllow: async (domain: string) => {
+ const response = await this.request('/api/v1/admin/domain_allows', { method: 'POST', body: { domain } });
+
+ return adminDomainAllowSchema.parse(response.json);
+ },
+
+ /**
+ * Delete an allowed domain
+ * Delete a domain from the allowed domains list.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/domain_allows/#delete}
+ */
+ deleteDomainAllow: async (domainAllowId: string) => {
+ const response = await this.request(`/api/v1/admin/domain_allows/${domainAllowId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ },
+
+ /** Disallow certain email domains from signing up. */
+ emailDomainBlocks: {
+ /**
+ * List all blocked email domains
+ * Show information about all email domains blocked from signing up.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/email_domain_blocks/#get}
+ */
+ getEmailDomainBlocks: (params?: AdminGetEmailDomainBlocksParams) =>
+ this.#paginatedGet('/api/v1/admin/email_domain_blocks', { params }, adminEmailDomainBlockSchema),
+
+ /**
+ * Get a single blocked email domain
+ * Show information about a single email domain that is blocked from signups.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/email_domain_blocks/#get-one}
+ */
+ getEmailDomainBlock: async (emailDomainBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/email_domain_blocks/${emailDomainBlockId}`);
+
+ return adminEmailDomainBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Block an email domain from signups
+ * Add a domain to the list of email domains blocked from signups.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/email_domain_blocks/#create}
+ */
+ createEmailDomainBlock: async (domain: string) => {
+ const response = await this.request('/api/v1/admin/email_domain_blocks', { method: 'POST', body: { domain } });
+
+ return adminEmailDomainBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Delete an email domain block
+ * Lift a block against an email domain.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/email_domain_blocks/#delete}
+ */
+ deleteEmailDomainBlock: async (emailDomainBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/email_domain_blocks/${emailDomainBlockId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ },
+
+ /** Disallow certain IP address ranges from signing up. */
+ ipBlocks: {
+ /**
+ * List all IP blocks
+ * Show information about all blocked IP ranges.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/ip_blocks/#get}
+ */
+ getIpBlocks: (params?: AdminGetIpBlocksParams) =>
+ this.#paginatedGet('/api/v1/admin/ip_blocks', { params }, adminIpBlockSchema),
+
+ /**
+ * Get a single IP block
+ * Show information about a single IP block.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/ip_blocks/#get-one}
+ */
+ getIpBlock: async (ipBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/ip_blocks/${ipBlockId}`);
+
+ return adminIpBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Block an IP address range from signing up
+ * Add an IP address range to the list of IP blocks.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/ip_blocks/#create}
+ */
+ createIpBlock: async (params: AdminCreateIpBlockParams) => {
+ const response = await this.request('/api/v1/admin/ip_blocks', { method: 'POST', body: params });
+
+ return adminIpBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Update a domain block
+ * Change parameters for an existing IP block.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/ip_blocks/#update}
+ */
+ updateIpBlock: async (ipBlockId: string, params: AdminCreateIpBlockParams) => {
+ const response = await this.request(`/api/v1/admin/ip_blocks/${ipBlockId}`, { method: 'POST', body: params });
+
+ return adminIpBlockSchema.parse(response.json);
+ },
+
+ /**
+ * Delete an IP block
+ * Lift a block against an IP range.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/ip_blocks/#delete}
+ */
+ deleteIpBlock: async (ipBlockId: string) => {
+ const response = await this.request(`/api/v1/admin/ip_blocks/${ipBlockId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+ },
+
+ /** Obtain quantitative metrics about the server. */
+ measures: {
+ /**
+ * Get measurable data
+ * Obtain quantitative metrics about the server.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/measures/#get}
+ */
+ getMeasures: async (keys: AdminMeasureKey[], start_at: string, end_at: string, params?: AdminGetMeasuresParams) => {
+ const response = await this.request('/api/v1/admin/measures', { params: { ...params, keys, start_at, end_at } });
+
+ return filteredArray(adminMeasureSchema).parse(response.json);
+ },
+ },
+
+ /** Show retention data over time. */
+ retention: {
+ /**
+ * Calculate retention data
+ * Generate a retention data report for a given time period and bucket.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/retention/#create}
+ */
+ getRetention: async (start_at: string, end_at: string, frequency: 'day' | 'month') => {
+ const response = await this.request('/api/v1/admin/retention', { params: { start_at, end_at, frequency } });
+
+ return adminCohortSchema.parse(response.json);
+ },
+ },
+ };
+
+ public readonly oembed = {
+ /**
+ * Get OEmbed info as JSON
+ * @see {@link https://docs.joinmastodon.org/methods/oembed/#get}
+ */
+ getOembed: async (url: string, maxwidth?: number, maxheight?: number) => {
+ const response = await this.request('/api/oembed', { params: { url, maxwidth, maxheight } });
+
+ return z.object({
+ type: z.string().catch('rich'),
+ version: z.string().catch(''),
+ author_name: z.string().catch(''),
+ author_url: z.string().catch('').catch(''),
+ provider_name: z.string().catch(''),
+ provider_url: z.string().catch(''),
+ cache_age: z.number(),
+ html: z.string(),
+ width: z.number().nullable().catch(null),
+ height: z.number().nullable().catch(null),
+ }).parse(response.json);
+ },
+ };
+
+ /** @see {@link https://docs.pleroma.social/backend/development/API/chats} */
+ public readonly chats = {
+ /**
+ * create or get an existing Chat for a certain recipient
+ * @see {@link https://docs.pleroma.social/backend/development/API/chats/#creating-or-getting-a-chat}
+ */
+ createChat: async (accountId: string) => {
+ const response = await this.request(`/api/v1/pleroma/chats/by-account-id/${accountId}`, { method: 'POST' });
+
+ return chatSchema.parse(response.json);
+ },
+
+ /**
+ * @see {@link https://docs.pleroma.social/backend/development/API/chats/#creating-or-getting-a-chat}
+ */
+ getChat: async (chatId: string) => {
+ const response = await this.request(`/api/v1/pleroma/chats/${chatId}`);
+
+ return chatSchema.parse(response.json);
+ },
+
+ /**
+ * Marking a chat as read
+ * mark a number of messages in a chat up to a certain message as read
+ * @see {@link https://docs.pleroma.social/backend/development/API/chats/#marking-a-chat-as-read}
+ */
+ markChatAsRead: async (chatId: string, last_read_id: string) => {
+ const response = await this.request(`/api/v1/pleroma/chats/${chatId}/read`, { method: 'POST', body: { last_read_id } });
+
+ return chatSchema.parse(response.json);
+ },
+
+ /**
+ * Marking a single chat message as read
+ * To set the `unread` property of a message to `false`
+ * https://docs.pleroma.social/backend/development/API/chats/#marking-a-single-chat-message-as-read
+ */
+ markChatMessageAsRead: async (chatId: string, chatMessageId: string) => {
+ const response = await this.request(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}/read`, { method: 'POST' });
+
+ return chatSchema.parse(response.json);
+ },
+
+ /**
+ * Getting a list of Chats
+ * This will return a list of chats that you have been involved in, sorted by their last update (so new chats will be at the top).
+ * @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-a-list-of-chats}
+ */
+ getChats: async (params?: GetChatsParams) =>
+ this.#paginatedGet('/api/v2/pleroma/chats', { params }, chatSchema),
+
+ /**
+ * Getting the messages for a Chat
+ * For a given Chat id, you can get the associated messages with
+ */
+ getChatMessages: async (chatId: string, params?: GetChatMessagesParams) =>
+ this.#paginatedGet(`/api/v1/pleroma/chats/${chatId}/messages`, { params }, chatMessageSchema),
+
+ /**
+ * Posting a chat message
+ * Posting a chat message for given Chat id works like this:
+ * @see {@link https://docs.pleroma.social/backend/development/API/chats/#posting-a-chat-message}
+ */
+ createChatMessage: async (chatId: string, params: CreateChatMessageParams) => {
+ const response = await this.request(`/api/v1/pleroma/chats/${chatId}/messages`, { method: 'POST', body: params });
+
+ return chatMessageSchema.parse(response.json);
+ },
+
+ /**
+ * Deleting a chat message
+ * Deleting a chat message for given Chat id works like this:
+ * @see {@link https://docs.pleroma.social/backend/development/API/chats/#deleting-a-chat-message}
+ */
+ deleteChatMessage: async (chatId: string, messageId: string) => {
+ const response = await this.request(`/api/v1/pleroma/chats/${chatId}/messages/${messageId}`, { method: 'DELETE' });
+
+ return chatMessageSchema.parse(response.json);
+ },
+
+ /**
+ * Deleting a chat
+ *
+ * Requires features{@link Features['chatsDelete']}.
+ */
+ deleteChat: async (chatId: string) => {
+ const response = await this.request(`/api/v1/pleroma/chats/${chatId}`, { method: 'DELETE' });
+
+ return chatSchema.parse(response.json);
+ },
+ };
+
+ public readonly events = {
+ /**
+ * Creates an event
+ * @see {@link }
+ */
+ createEvent: async (params: CreateEventParams) => {
+ const response = await this.request('/api/v1/pleroma/events', { method: 'POST', body: params });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Edits an event
+ * @see {@link }
+ */
+ editEvent: async (statusId: string, params: EditEventParams) => {
+ const response = await this.request(`/api/v1/pleroma/events/${statusId}`, { method: 'PUT', body: params });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Gets user's joined events
+ * @see {@link }
+ */
+ getJoinedEvents: async (state?: 'pending' | 'reject' | 'accept', params?: GetJoinedEventsParams) =>
+ this.#paginatedGet('/api/v1/pleroma/events/joined_events', { params: { ...params, state } }, statusSchema),
+
+ /**
+ * Gets event participants
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#apiv1pleromaeventsidparticipations}
+ */
+ getEventParticipations: async (statusId: string, params?: GetEventParticipationsParams) =>
+ this.#paginatedGet(`/api/v1/pleroma/events/${statusId}/participations`, { params }, accountSchema),
+
+ /**
+ * Gets event participation requests
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#apiv1pleromaeventsidparticipation_requests}
+ */
+ getEventParticipationRequests: async (statusId: string, params?: GetEventParticipationRequestsParams) =>
+ this.#paginatedGet<{
+ account:Account;
+ participation_message: string;
+ }>(`/api/v1/pleroma/events/${statusId}/participation_requests`, { params }, z.object({
+ account: accountSchema,
+ participation_message: z.string().catch(''),
+ })),
+
+ /**
+ * Accepts user to the event
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#apiv1pleromaeventsidparticipation_requestsparticipant_idauthorize}
+ */
+ acceptEventParticipationRequest: async (statusId: string, accountId: string) => {
+ const response = await this.request(`/api/v1/pleroma/events/${statusId}/participation_requests/${accountId}/authorize`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Rejects user from the event
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#rejects-user-from-the-event}
+ */
+ rejectEventParticipationRequest: async (statusId: string, accountId: string) => {
+ const response = await this.request(`/api/v1/pleroma/events/${statusId}/participation_requests/${accountId}/reject`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Joins the event
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#joins-the-event}
+ */
+ joinEvent: async (statusId: string, participation_message?: string) => {
+ const response = await this.request(`/api/v1/pleroma/events/${statusId}/join`, { method: 'POST', body: { participation_message } });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Leaves the event
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#leaves-the-event}
+ */
+ leaveEvent: async (statusId: string) => {
+ const response = await this.request(`/api/v1/pleroma/events/${statusId}/leave`, { method: 'POST' });
+
+ return statusSchema.parse(response.json);
+ },
+
+ /**
+ * Event ICS file
+ * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#event-ics-file}
+ */
+ getEventIcs: async (statusId: string) => {
+ const response = await this.request(`/api/v1/pleroma/events/${statusId}/ics`, { contentType: '' });
+
+ return response.data;
+ },
+ };
+
+ public readonly interactionRequests = {
+ /**
+ * Get an array of interactions requested on your statuses by other accounts, and pending your approval.
+ *
+ * Requires features{@link Features['interactionRequests']}.
+ */
+ getInteractionRequests: async (params?: GetInteractionRequestsParams) =>
+ this.#paginatedGet('/api/v1/interaction_requests', { params }, interactionRequestSchema),
+
+ /**
+ * Get interaction request with the given ID.
+ *
+ * Requires features{@link Features['interactionRequests']}.
+ */
+ getInteractionRequest: async (interactionRequestId: string) => {
+ const response = await this.request(`/api/v1/interaction_requests/${interactionRequestId}`);
+
+ return interactionRequestSchema.parse(response.json);
+ },
+
+ /**
+ * Accept/authorize/approve an interaction request with the given ID.
+ *
+ * Requires features{@link Features['interactionRequests']}.
+ */
+ authorizeInteractionRequest: async (interactionRequestId: string) => {
+ const response = await this.request(`/api/v1/interaction_requests/${interactionRequestId}/authorize`, { method: 'POST' });
+
+ return interactionRequestSchema.parse(response.json);
+ },
+
+ /**
+ * Reject an interaction request with the given ID.
+ *
+ * Requires features{@link Features['interactionRequests']}.
+ */
+ rejectInteractionRequest: async (interactionRequestId: string) => {
+ const response = await this.request(`/api/v1/interaction_requests/${interactionRequestId}/authorize`, { method: 'POST' });
+
+ return interactionRequestSchema.parse(response.json);
+ },
+ };
+
+ /** Routes that are not part of any stable release */
+ public readonly experimental = {
+ admin: {
+ /** @see {@link https://github.com/mastodon/mastodon/pull/19059} */
+ groups: {
+ /** list groups known to the instance. Mimics the interface of `/api/v1/admin/accounts` */
+ getGroups: async (params?: AdminGetGroupsParams) => {
+ const response = await this.request('/api/v1/admin/groups', { params });
+
+ return filteredArray(groupSchema).parse(response.json);
+ },
+
+ /** return basic group information */
+ getGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/admin/groups/${groupId}`);
+
+ return groupSchema.parse(response.json);
+ },
+
+ /** suspends a group */
+ suspendGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/admin/groups/${groupId}/suspend`, { method: 'POST' });
+
+ return groupSchema.parse(response.json);
+ },
+
+ /** lift a suspension */
+ unsuspendGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/admin/groups/${groupId}/unsuspend`, { method: 'POST' });
+
+ return groupSchema.parse(response.json);
+ },
+
+ /** deletes an already-suspended group */
+ deleteGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/admin/groups/${groupId}`, { method: 'DELETE' });
+
+ return groupSchema.parse(response.json);
+ },
+ },
+ },
+
+ /** @see {@link https://github.com/mastodon/mastodon/pull/19059} */
+ groups: {
+ /** returns an array of `Group` entities the current user is a member of */
+ getGroups: async () => {
+ const response = await this.request('/api/v1/groups');
+
+ return filteredArray(groupSchema).parse(response.json);
+ },
+
+ /** create a group with the given attributes (`display_name`, `note`, `avatar` and `header`). Sets the user who made the request as group administrator */
+ createGroup: async (params: CreateGroupParams) => {
+ const response = await this.request('/api/v1/groups', {
+ method: 'POST',
+ body: params,
+ contentType: params.avatar || params.header ? '' : undefined,
+ });
+
+ return groupSchema.parse(response.json);
+ },
+
+ /** returns the `Group` entity describing a given group */
+ getGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/groups/${groupId}`);
+
+ return groupSchema.parse(response.json);
+ },
+
+ /** update group attributes (`display_name`, `note`, `avatar` and `header`) */
+ updateGroup: async (groupId: string, params: UpdateGroupParams) => {
+ const response = await this.request(`/api/v1/groups/${groupId}`, {
+ method: 'PUT',
+ body: params,
+ contentType: params.avatar || params.header ? '' : undefined,
+ });
+
+ return groupSchema.parse(response.json);
+ },
+
+ /** irreversibly deletes the group */
+ deleteGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/groups/${groupId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ /** Has an optional role attribute that can be used to filter by role (valid roles are `"admin"`, `"moderator"`, `"user"`). */
+ getGroupMemberships: async (groupId: string, role?: GroupRole, params?: GetGroupMembershipsParams) =>
+ this.#paginatedGet(`/api/v1/groups/${groupId}/memberships`, { params: { ...params, role } }, groupMemberSchema),
+
+ /** returns an array of `Account` entities representing pending requests to join a group */
+ getGroupMembershipRequests: async (groupId: string, params?: GetGroupMembershipRequestsParams) =>
+ this.#paginatedGet(`/api/v1/groups/${groupId}/membership_requests`, { params }, accountSchema),
+
+ /** accept a pending request to become a group member */
+ acceptGroupMembershipRequest: async (groupId: string, accountId: string) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`, { method: 'POST' });
+
+ return response.json as {};
+ },
+
+ /** reject a pending request to become a group member */
+ rejectGroupMembershipRequest: async (groupId: string, accountId: string) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`, { method: 'POST' });
+
+ return response.json as {};
+ },
+
+ /** delete a group post (actually marks it as `revoked` if it is a local post) */
+ deleteGroupStatus: async (groupId: string, statusId: string) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/statuses/${statusId}`, { method: 'DELETE' });
+
+ return response.json as {};
+ },
+
+ /** list accounts blocked from interacting with the group */
+ getGroupBlocks: async (groupId: string, params?: GetGroupBlocksParams) =>
+ this.#paginatedGet(`/api/v1/groups/${groupId}/blocks`, { params }, accountSchema),
+
+ /** block one or more users. If they were in the group, they are also kicked of it */
+ blockGroupUsers: async (groupId: string, accountIds: string[]) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/blocks`, { method: 'POST', params: { account_ids: accountIds } });
+
+ return response.json as {};
+ },
+
+ /** block one or more users. If they were in the group, they are also kicked of it */
+ unblockGroupUsers: async (groupId: string, accountIds: string[]) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/blocks`, { method: 'DELETE', params: { account_ids: accountIds } });
+
+ return response.json as {};
+ },
+
+ /** joins (or request to join) a given group */
+ joinGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/join`, { method: 'POST' });
+
+ return groupRelationshipSchema.parse(response.json);
+ },
+
+ /** leaves a given group */
+ leaveGroup: async (groupId: string) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/leave`, { method: 'POST' });
+
+ return groupRelationshipSchema.parse(response.json);
+ },
+
+ /** kick one or more group members */
+ kickGroupUsers: async (groupId: string, accountIds: string[]) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/kick`, { method: 'POST', params: { account_ids: accountIds } });
+
+ return response.json as {};
+ },
+
+ /** promote one or more accounts to role `new_role`. An error is returned if any of those accounts has a higher role than `new_role` already, or if the role is higher than the issuing user's. Valid roles are `admin`, and `moderator` and `user`. */
+ promoteGroupUsers: async (groupId: string, accountIds: string[], role: GroupRole) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/promote`, { method: 'POST', params: { account_ids: accountIds, role } });
+
+ return filteredArray(groupMemberSchema).parse(response.json);
+ },
+
+ /** demote one or more accounts to role `new_role`. Returns an error unless every of the target account has a strictly lower role than the user (you cannot demote someone with the same role as you), or if any target account already has a role lower than `new_role`. Valid roles are `admin`, `moderator` and `user`. */
+ demoteGroupUsers: async (groupId: string, accountIds: string[], role: GroupRole) => {
+ const response = await this.request(`/api/v1/groups/${groupId}/demote`, { method: 'POST', params: { account_ids: accountIds, role } });
+
+ return filteredArray(groupMemberSchema).parse(response.json);
+ },
+
+ getGroupRelationships: async (groupIds: string[]) => {
+ const response = await this.request('/api/v1/groups/relationships', { params: { id: groupIds } });
+
+ return filteredArray(groupRelationshipSchema).parse(response.json);
+ },
+ },
+ };
+
+ #setInstance = (instance: Instance) => {
+ this.#instance = instance;
+ this.features = getFeatures(this.#instance);
+ };
+
+ get accessToken(): string | undefined {
+ return this.#accessToken;
+ }
+
+ set accessToken(accessToken: string | undefined) {
+ if (this.#accessToken === accessToken) return;
+
+ this.#socket?.close();
+ this.#accessToken = accessToken;
+ }
+
+ get instanceInformation() {
+ return this.#instance;
+ }
+
+}
+
+export {
+ PlApiClient,
+ PlApiClient as default,
+};
diff --git a/packages/pl-api/lib/entities/account-warning.ts b/packages/pl-api/lib/entities/account-warning.ts
new file mode 100644
index 000000000..68d7becdd
--- /dev/null
+++ b/packages/pl-api/lib/entities/account-warning.ts
@@ -0,0 +1,27 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { accountSchema } from './account';
+import { dateSchema } from './utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Appeal/} */
+const appealSchema = z.object({
+ text: z.string(),
+ state: z.enum(['approved', 'rejected', 'pending']),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/AccountWarning/} */
+const accountWarningSchema = z.object({
+ id: z.string(),
+ action: z.enum(['none', 'disable', 'mark_statuses_as_sensitive', 'delete_statuses', 'sensitive', 'silence', 'suspend']),
+ text: z.string().catch(''),
+ status_ids: z.array(z.string()).catch([]),
+ target_account: accountSchema,
+ appeal: appealSchema.nullable().catch(null),
+ created_at: dateSchema,
+});
+
+type AccountWarning = Resolve>;
+
+export { accountWarningSchema, type AccountWarning };
diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts
new file mode 100644
index 000000000..5bb56d081
--- /dev/null
+++ b/packages/pl-api/lib/entities/account.ts
@@ -0,0 +1,177 @@
+import pick from 'lodash.pick';
+import z from 'zod';
+
+import { customEmojiSchema } from './custom-emoji';
+import { relationshipSchema } from './relationship';
+import { roleSchema } from './role';
+import { coerceObject, dateSchema, filteredArray } from './utils';
+
+const filterBadges = (tags?: string[]) =>
+ tags?.filter(tag => tag.startsWith('badge:')).map(tag => roleSchema.parse({ id: tag, name: tag.replace(/^badge:/, '') }));
+
+const preprocessAccount = (account: any) => {
+ if (!account?.acct) return null;
+
+ return {
+ username: account.username || account.acct.split('@')[0],
+ display_name: account.display_name.trim() || account.username,
+ roles: account.roles?.length ? account.roles : filterBadges(account.pleroma?.tags),
+ avatar_static: account.avatar_static || account.avatar,
+ header_static: account.header_static || account.header,
+ source: account.source
+ ? { ...(pick(account.pleroma?.source || {}, [
+ 'show_role', 'no_rich_text', 'discoverable', 'actor_type', 'show_birthday',
+ ])), ...account.source }
+ : undefined,
+ local: typeof account.pleroma?.is_local === 'boolean' ? account.pleroma.is_local : account.acct.split('@')[1] === undefined,
+ discoverable: account.discoverable || account.pleroma?.source?.discoverable,
+ verified: account.verified || account.pleroma?.tags?.includes('verified'),
+ ...(pick(account.pleroma || {}, [
+ 'ap_id',
+ 'background_image',
+ 'relationship',
+ 'is_moderator',
+ 'is_admin',
+ 'hide_favorites',
+ 'hide_followers',
+ 'hide_follows',
+ 'hide_followers_count',
+ 'hide_follows_count',
+ 'accepts_chat_messages',
+ 'favicon',
+ 'birthday',
+ 'deactivated',
+
+ 'settings_store',
+ 'chat_token',
+ 'allow_following_move',
+ 'unread_conversation_count',
+ 'unread_notifications_count',
+ 'notification_settings',
+
+ 'location',
+ ])),
+ ...(pick(account.other_settings || {}), ['birthday', 'location']),
+ __meta: pick(account, ['pleroma', 'source']),
+ ...account,
+ };
+};
+
+const fieldSchema = z.object({
+ name: z.string(),
+ value: z.string(),
+ verified_at: z.string().datetime({ offset: true }).nullable().catch(null),
+});
+
+const baseAccountSchema = z.object({
+ id: z.string(),
+ username: z.string().catch(''),
+ acct: z.string().catch(''),
+ url: z.string().url(),
+ display_name: z.string().catch(''),
+ note: z.string().catch(''),
+ avatar: z.string().catch(''),
+ avatar_static: z.string().url().catch(''),
+ header: z.string().url().catch(''),
+ header_static: z.string().url().catch(''),
+ locked: z.boolean().catch(false),
+ fields: filteredArray(fieldSchema),
+ emojis: filteredArray(customEmojiSchema),
+ bot: z.boolean().catch(false),
+ group: z.boolean().catch(false),
+ discoverable: z.boolean().catch(false),
+ noindex: z.boolean().nullable().catch(null),
+ suspended: z.boolean().optional().catch(undefined),
+ limited: z.boolean().optional().catch(undefined),
+ created_at: z.string().datetime().catch(new Date().toUTCString()),
+ last_status_at: z.string().date().nullable().catch(null),
+ statuses_count: z.number().catch(0),
+ followers_count: z.number().catch(0),
+ following_count: z.number().catch(0),
+ roles: filteredArray(roleSchema),
+
+ fqn: z.string().nullable().catch(null),
+ ap_id: z.string().nullable().catch(null),
+ background_image: z.string().nullable().catch(null),
+ relationship: relationshipSchema.optional().catch(undefined),
+ is_moderator: z.boolean().optional().catch(undefined),
+ is_admin: z.boolean().optional().catch(undefined),
+ is_suggested: z.boolean().optional().catch(undefined),
+ hide_favorites: z.boolean().catch(true),
+ hide_followers: z.boolean().optional().catch(undefined),
+ hide_follows: z.boolean().optional().catch(undefined),
+ hide_followers_count: z.boolean().optional().catch(undefined),
+ hide_follows_count: z.boolean().optional().catch(undefined),
+ accepts_chat_messages: z.boolean().nullable().catch(null),
+ favicon: z.string().optional().catch(undefined),
+ birthday: z.string().date().optional().catch(undefined),
+ deactivated: z.boolean().optional().catch(undefined),
+
+ location: z.string().optional().catch(undefined),
+ local: z.boolean().optional().catch(false),
+
+ avatar_description: z.string().catch(''),
+ enable_rss: z.boolean().catch(false),
+ header_description: z.string().catch(''),
+
+ verified: z.boolean().optional().catch(undefined),
+
+ __meta: coerceObject({
+ pleroma: z.any().optional().catch(undefined),
+ source: z.any().optional().catch(undefined),
+ }),
+});
+
+const accountWithMovedAccountSchema = baseAccountSchema.extend({
+ moved: baseAccountSchema.optional().catch(undefined),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Account/} */
+const accountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema);
+
+type Account = z.infer;
+
+const credentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({
+ source: z.object({
+ note: z.string().catch(''),
+ fields: filteredArray(fieldSchema),
+ privacy: z.enum(['public', 'unlisted', 'private', 'direct']),
+ sensitive: z.boolean().catch(false),
+ language: z.string().nullable().catch(null),
+ follow_requests_count: z.number().int().nonnegative().catch(0),
+
+ show_role: z.boolean().optional().nullable().catch(undefined),
+ no_rich_text: z.boolean().optional().nullable().catch(undefined),
+ discoverable: z.boolean().optional().catch(undefined),
+ actor_type: z.string().optional().catch(undefined),
+ show_birthday: z.boolean().optional().catch(undefined),
+ }).nullable().catch(null),
+ role: roleSchema.nullable().catch(null),
+
+ settings_store: z.record(z.any()).optional().catch(undefined),
+ chat_token: z.string().optional().catch(undefined),
+ allow_following_move: z.boolean().optional().catch(undefined),
+ unread_conversation_count: z.number().optional().catch(undefined),
+ unread_notifications_count: z.number().optional().catch(undefined),
+ notification_settings: z.object({
+ block_from_strangers: z.boolean().catch(false),
+ hide_notification_contents: z.boolean().catch(false),
+ }).optional().catch(undefined),
+}));
+
+type CredentialAccount = z.infer;
+
+const mutedAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({
+ mute_expires_at: dateSchema.nullable().catch(null),
+}));
+
+type MutedAccount = z.infer;
+
+export {
+ accountSchema,
+ credentialAccountSchema,
+ mutedAccountSchema,
+ type Account,
+ type CredentialAccount,
+ type MutedAccount,
+};
diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts
new file mode 100644
index 000000000..3e25324aa
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/account.ts
@@ -0,0 +1,69 @@
+import { z } from 'zod';
+
+import { accountSchema } from '../account';
+import { roleSchema } from '../role';
+import { dateSchema, filteredArray } from '../utils';
+
+import { adminIpSchema } from './ip';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_Account/} */
+const adminAccountSchema = z.preprocess((account: any) => {
+ if (!account.account) {
+ /**
+ * Convert Pleroma account schema
+ * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminusers}
+ */
+ return {
+ id: account.id,
+ account: null,
+ username: account.nickname,
+ domain: account.nickname.split('@')[1] || null,
+ created_at: account.created_at,
+ email: account.email,
+ invite_request: account.registration_reason,
+ role: account.roles?.is_admin
+ ? roleSchema.parse({ name: 'Admin' })
+ : account.roles?.moderator
+ ? roleSchema.parse({ name: 'Moderator ' }) :
+ null,
+ confirmed: account.is_confirmed,
+ approved: account.is_approved,
+ disabled: !account.is_active,
+
+ actor_type: account.actor_type,
+ display_name: account.display_name,
+ suggested: account.is_suggested,
+ };
+ }
+ return account;
+}, z.object({
+ id: z.string(),
+ username: z.string(),
+ domain: z.string().nullable().catch(null),
+ created_at: dateSchema,
+ email: z.string().nullable().catch(null),
+ ip: z.string().ip().nullable().catch(null),
+ ips: filteredArray(adminIpSchema),
+ locale: z.string().nullable().catch(null),
+ invite_request: z.string().nullable().catch(null),
+ role: roleSchema.nullable().catch(null),
+ confirmed: z.boolean().catch(false),
+ approved: z.boolean().catch(false),
+ disabled: z.boolean().catch(false),
+ silenced: z.boolean().catch(false),
+ suspended: z.boolean().catch(false),
+ account: accountSchema.nullable().catch(null),
+ created_by_application_id: z.string().optional().catch(undefined),
+ invited_by_account_id: z.string().optional().catch(undefined),
+
+ actor_type: z.string().nullable().catch(null),
+ display_name: z.string().nullable().catch(null),
+ suggested: z.boolean().nullable().catch(null),
+}));
+
+type AdminAccount = z.infer;
+
+export {
+ adminAccountSchema,
+ type AdminAccount,
+};
diff --git a/packages/pl-api/lib/entities/admin/announcement.ts b/packages/pl-api/lib/entities/admin/announcement.ts
new file mode 100644
index 000000000..0bae102e1
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/announcement.ts
@@ -0,0 +1,17 @@
+import pick from 'lodash.pick';
+import { z } from 'zod';
+
+import { Resolve } from '../../utils/types';
+import { announcementSchema } from '../announcement';
+
+/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminannouncements} */
+const adminAnnouncementSchema = z.preprocess((announcement: any) => ({
+ ...announcement,
+ ...pick(announcement.pleroma, 'raw_content'),
+}), announcementSchema.extend({
+ raw_content: z.string().catch(''),
+}));
+
+type AdminAnnouncement = Resolve>;
+
+export { adminAnnouncementSchema, type AdminAnnouncement };
diff --git a/packages/pl-api/lib/entities/admin/canonical-email-block.ts b/packages/pl-api/lib/entities/admin/canonical-email-block.ts
new file mode 100644
index 000000000..817028c20
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/canonical-email-block.ts
@@ -0,0 +1,14 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_CanonicalEmailBlock/} */
+const adminCanonicalEmailBlockSchema = z.object({
+ id: z.string(),
+ canonical_email_hash: z.string(),
+});
+
+type AdminCanonicalEmailBlock = z.infer;
+
+export {
+ adminCanonicalEmailBlockSchema,
+ type AdminCanonicalEmailBlock,
+};
diff --git a/packages/pl-api/lib/entities/admin/cohort.ts b/packages/pl-api/lib/entities/admin/cohort.ts
new file mode 100644
index 000000000..5c3cb3ea8
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/cohort.ts
@@ -0,0 +1,19 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/} */
+const adminCohortSchema = z.object({
+ period: z.string().datetime({ offset: true }),
+ frequency: z.enum(['day', 'month']),
+ data: z.array(z.object({
+ date: z.string().datetime({ offset: true }),
+ rate: z.number(),
+ value: z.number().int(),
+ })),
+});
+
+type AdminCohort = z.infer;
+
+export {
+ adminCohortSchema,
+ type AdminCohort,
+};
diff --git a/packages/pl-api/lib/entities/admin/dimension.ts b/packages/pl-api/lib/entities/admin/dimension.ts
new file mode 100644
index 000000000..45105a2a7
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/dimension.ts
@@ -0,0 +1,20 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_Dimension/} */
+const adminDimensionSchema = z.object({
+ key: z.string(),
+ data: z.object({
+ key: z.string(),
+ human_key: z.string(),
+ value: z.string(),
+ unit: z.string().optional().catch(undefined),
+ human_value: z.string().optional().catch(undefined),
+ }),
+});
+
+type AdminDimension = z.infer;
+
+export {
+ adminDimensionSchema,
+ type AdminDimension,
+};
diff --git a/packages/pl-api/lib/entities/admin/domain-allow.ts b/packages/pl-api/lib/entities/admin/domain-allow.ts
new file mode 100644
index 000000000..a012938f0
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/domain-allow.ts
@@ -0,0 +1,17 @@
+import { z } from 'zod';
+
+import { dateSchema } from '../utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainAllow/} */
+const adminDomainAllowSchema = z.object({
+ id: z.string(),
+ domain: z.string(),
+ created_at: dateSchema,
+});
+
+type AdminDomainAllow = z.infer;
+
+export {
+ adminDomainAllowSchema,
+ type AdminDomainAllow,
+};
diff --git a/packages/pl-api/lib/entities/admin/domain-block.ts b/packages/pl-api/lib/entities/admin/domain-block.ts
new file mode 100644
index 000000000..b1452c3ea
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/domain-block.ts
@@ -0,0 +1,24 @@
+import { z } from 'zod';
+
+import { dateSchema } from '../utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainBlock/} */
+const adminDomainBlockSchema = z.object({
+ id: z.string(),
+ domain: z.string(),
+ digest: z.string(),
+ created_at: dateSchema,
+ severity: z.enum(['silence', 'suspend', 'noop']),
+ reject_media: z.boolean(),
+ reject_reports: z.boolean(),
+ private_comment: z.string().nullable().catch(null),
+ public_comment: z.string().nullable().catch(null),
+ obfuscate: z.boolean(),
+});
+
+type AdminDomainBlock = z.infer;
+
+export {
+ adminDomainBlockSchema,
+ type AdminDomainBlock,
+};
diff --git a/packages/pl-api/lib/entities/admin/domain.ts b/packages/pl-api/lib/entities/admin/domain.ts
new file mode 100644
index 000000000..73c3a8f58
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/domain.ts
@@ -0,0 +1,13 @@
+import z from 'zod';
+
+const adminDomainSchema = z.object({
+ domain: z.string().catch(''),
+ id: z.coerce.string(),
+ public: z.boolean().catch(false),
+ resolves: z.boolean().catch(false),
+ last_checked_at: z.string().datetime().catch(''),
+});
+
+type AdminDomain = z.infer
+
+export { adminDomainSchema, type AdminDomain };
diff --git a/packages/pl-api/lib/entities/admin/email-domain-block.ts b/packages/pl-api/lib/entities/admin/email-domain-block.ts
new file mode 100644
index 000000000..093ee85f5
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/email-domain-block.ts
@@ -0,0 +1,22 @@
+import { z } from 'zod';
+
+import { dateSchema } from '../utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_EmailDomainBlock/} */
+const adminEmailDomainBlockSchema = z.object({
+ id: z.string(),
+ domain: z.string(),
+ created_at: dateSchema,
+ history: z.array(z.object({
+ day: z.coerce.string(),
+ accounts: z.coerce.string(),
+ uses: z.coerce.string(),
+ })),
+});
+
+type AdminEmailDomainBlock = z.infer;
+
+export {
+ adminEmailDomainBlockSchema,
+ type AdminEmailDomainBlock,
+};
diff --git a/packages/pl-api/lib/entities/admin/ip-block.ts b/packages/pl-api/lib/entities/admin/ip-block.ts
new file mode 100644
index 000000000..f3cf6bfe3
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/ip-block.ts
@@ -0,0 +1,20 @@
+import { z } from 'zod';
+
+import { dateSchema } from '../utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_IpBlock/} */
+const adminIpBlockSchema = z.object({
+ id: z.string(),
+ ip: z.string().ip(),
+ severity: z.enum(['sign_up_requires_approval', 'sign_up_block', 'no_access']),
+ comment: z.string().catch(''),
+ created_at: dateSchema,
+ expires_at: z.string().datetime({ offset: true }),
+});
+
+type AdminIpBlock = z.infer;
+
+export {
+ adminIpBlockSchema,
+ type AdminIpBlock,
+};
diff --git a/packages/pl-api/lib/entities/admin/ip.ts b/packages/pl-api/lib/entities/admin/ip.ts
new file mode 100644
index 000000000..e27b30f30
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/ip.ts
@@ -0,0 +1,16 @@
+import { z } from 'zod';
+
+import { dateSchema } from '../utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_Ip/} */
+const adminIpSchema = z.object({
+ ip: z.string().ip(),
+ used_at: dateSchema,
+});
+
+type AdminIp = z.infer;
+
+export {
+ adminIpSchema,
+ type AdminIp,
+};
diff --git a/packages/pl-api/lib/entities/admin/measure.ts b/packages/pl-api/lib/entities/admin/measure.ts
new file mode 100644
index 000000000..5e1cda5da
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/measure.ts
@@ -0,0 +1,21 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_Measure/} */
+const adminMeasureSchema = z.object({
+ key: z.string(),
+ unit: z.string().nullable().catch(null),
+ total: z.coerce.number(),
+ human_value: z.string().optional().catch(undefined),
+ previous_total: z.coerce.string().optional().catch(undefined),
+ data: z.array(z.object({
+ date: z.string().datetime({ offset: true }),
+ value: z.coerce.string(),
+ })),
+});
+
+type AdminMeasure = z.infer;
+
+export {
+ adminMeasureSchema,
+ type AdminMeasure,
+};
diff --git a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts
new file mode 100644
index 000000000..0bd93ef37
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts
@@ -0,0 +1,13 @@
+import z from 'zod';
+
+/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */
+const adminModerationLogEntrySchema = z.object({
+ id: z.coerce.string(),
+ data: z.record(z.string(), z.any()).catch({}),
+ time: z.number().catch(0),
+ message: z.string().catch(''),
+});
+
+type AdminModerationLogEntry = z.infer
+
+export { adminModerationLogEntrySchema, type AdminModerationLogEntry };
diff --git a/packages/pl-api/lib/entities/admin/relay.ts b/packages/pl-api/lib/entities/admin/relay.ts
new file mode 100644
index 000000000..b084b2353
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/relay.ts
@@ -0,0 +1,11 @@
+import z from 'zod';
+
+const adminRelaySchema = z.preprocess((data: any) => ({ id: data.actor, ...data }), z.object({
+ actor: z.string().catch(''),
+ id: z.string(),
+ followed_back: z.boolean().catch(false),
+}));
+
+type AdminRelay = z.infer
+
+export { adminRelaySchema, type AdminRelay };
diff --git a/packages/pl-api/lib/entities/admin/report.ts b/packages/pl-api/lib/entities/admin/report.ts
new file mode 100644
index 000000000..a669517c4
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/report.ts
@@ -0,0 +1,46 @@
+import pick from 'lodash.pick';
+import { z } from 'zod';
+
+import { ruleSchema } from '../rule';
+import { statusWithoutAccountSchema } from '../status';
+import { dateSchema, filteredArray } from '../utils';
+
+import { adminAccountSchema } from './account';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Admin_Report/} */
+const adminReportSchema = z.preprocess((report: any) => {
+ if (report.actor) {
+ /**
+ * Convert Pleroma report schema
+ * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminreports}
+ */
+ return {
+ action_taken: report.state !== 'open',
+ comment: report.content,
+ updated_at: report.created_at,
+ account: report.actor,
+ target_account: report.account,
+ ...(pick(report, ['id', 'assigned_account', 'created_at', 'rules', 'statuses'])),
+ };
+ }
+ return report;
+}, z.object({
+ id: z.string(),
+ action_taken: z.boolean().optional().catch(undefined),
+ action_taken_at: dateSchema.nullable().catch(null),
+ category: z.string().optional().catch(undefined),
+ comment: z.string().optional().catch(undefined),
+ forwarded: z.boolean().optional().catch(undefined),
+ created_at: dateSchema.optional().catch(undefined),
+ updated_at: dateSchema.optional().catch(undefined),
+ account: adminAccountSchema,
+ target_account: adminAccountSchema,
+ assigned_account: adminAccountSchema.nullable().catch(null),
+ action_taken_by_account: adminAccountSchema.nullable().catch(null),
+ statuses: filteredArray(statusWithoutAccountSchema),
+ rules: filteredArray(ruleSchema),
+}));
+
+type AdminReport = z.infer;
+
+export { adminReportSchema, type AdminReport };
diff --git a/packages/pl-api/lib/entities/admin/rule.ts b/packages/pl-api/lib/entities/admin/rule.ts
new file mode 100644
index 000000000..a3dc1d2ab
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/rule.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminrules} */
+const adminRuleSchema = z.object({
+ id: z.string(),
+ text: z.string().catch(''),
+ hint: z.string().catch(''),
+ priority: z.number().nullable().catch(null),
+});
+
+type AdminRule = z.infer;
+
+export { adminRuleSchema, type AdminRule };
diff --git a/packages/pl-api/lib/entities/admin/tag.ts b/packages/pl-api/lib/entities/admin/tag.ts
new file mode 100644
index 000000000..467de653a
--- /dev/null
+++ b/packages/pl-api/lib/entities/admin/tag.ts
@@ -0,0 +1,18 @@
+import { z } from 'zod';
+
+import { tagSchema } from '../tag';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Tag/#admin} */
+const adminTagSchema = tagSchema.extend({
+ id: z.string(),
+ trendable: z.boolean(),
+ usable: z.boolean(),
+ requires_review: z.boolean(),
+});
+
+type AdminTag = z.infer;
+
+export {
+ adminTagSchema,
+ type AdminTag,
+};
diff --git a/packages/pl-api/lib/entities/announcement-reaction.ts b/packages/pl-api/lib/entities/announcement-reaction.ts
new file mode 100644
index 000000000..5ca550882
--- /dev/null
+++ b/packages/pl-api/lib/entities/announcement-reaction.ts
@@ -0,0 +1,17 @@
+import { z } from 'zod';
+
+import type { Resolve } from '../utils/types';
+
+/** @see {@link https://docs.joinmastodon.org/entities/announcement/} */
+const announcementReactionSchema = z.object({
+ name: z.string().catch(''),
+ count: z.number().int().nonnegative().catch(0),
+ me: z.boolean().catch(false),
+ url: z.string().nullable().catch(null),
+ static_url: z.string().nullable().catch(null),
+ announcement_id: z.string().catch(''),
+});
+
+type AnnouncementReaction = Resolve>;
+
+export { announcementReactionSchema, type AnnouncementReaction };
diff --git a/packages/pl-api/lib/entities/announcement.ts b/packages/pl-api/lib/entities/announcement.ts
new file mode 100644
index 000000000..8ad1c570c
--- /dev/null
+++ b/packages/pl-api/lib/entities/announcement.ts
@@ -0,0 +1,35 @@
+import { z } from 'zod';
+
+import { announcementReactionSchema } from './announcement-reaction';
+import { customEmojiSchema } from './custom-emoji';
+import { mentionSchema } from './mention';
+import { tagSchema } from './tag';
+import { dateSchema, filteredArray } from './utils';
+
+import type { Resolve } from '../utils/types';
+
+/** @see {@link https://docs.joinmastodon.org/entities/announcement/} */
+const announcementSchema = z.object({
+ id: z.string(),
+ content: z.string().catch(''),
+ starts_at: z.string().datetime().nullable().catch(null),
+ ends_at: z.string().datetime().nullable().catch(null),
+ all_day: z.boolean().catch(false),
+ read: z.boolean().catch(false),
+ published_at: dateSchema,
+ reactions: filteredArray(announcementReactionSchema),
+ statuses: z.preprocess(
+ (statuses: any) => Array.isArray(statuses)
+ ? Object.fromEntries(statuses.map((status: any) => [status.url, status.account?.acct]) || [])
+ : statuses,
+ z.record(z.string(), z.string()),
+ ),
+ mentions: filteredArray(mentionSchema),
+ tags: filteredArray(tagSchema),
+ emojis: filteredArray(customEmojiSchema),
+ updated_at: dateSchema,
+});
+
+type Announcement = Resolve>;
+
+export { announcementSchema, type Announcement };
diff --git a/packages/pl-api/lib/entities/application.ts b/packages/pl-api/lib/entities/application.ts
new file mode 100644
index 000000000..8c2b629c3
--- /dev/null
+++ b/packages/pl-api/lib/entities/application.ts
@@ -0,0 +1,21 @@
+import { z } from 'zod';
+
+import type { Resolve } from '../utils/types';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Application/} */
+const applicationSchema = z.object({
+ name: z.string().catch(''),
+ website: z.string().optional().catch(undefined),
+ client_id: z.string().optional().catch(undefined),
+ client_secret: z.string().optional().catch(undefined),
+ redirect_uri: z.string().optional().catch(undefined),
+
+ id: z.string().optional().catch(undefined),
+
+ /** @deprecated */
+ vapid_key: z.string().optional().catch(undefined),
+});
+
+type Application = Resolve>;
+
+export { applicationSchema, type Application };
diff --git a/packages/pl-api/lib/entities/backup.ts b/packages/pl-api/lib/entities/backup.ts
new file mode 100644
index 000000000..685781315
--- /dev/null
+++ b/packages/pl-api/lib/entities/backup.ts
@@ -0,0 +1,19 @@
+import { z } from 'zod';
+
+import { dateSchema, mimeSchema } from './utils';
+
+import type { Resolve } from '../utils/types';
+
+/** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */
+const backupSchema = z.object({
+ id: z.coerce.string(),
+ contentType: mimeSchema,
+ file_size: z.number().catch(0),
+ inserted_at: dateSchema,
+ processed: z.boolean().catch(false),
+ url: z.string().catch(''),
+});
+
+type Backup = Resolve>;
+
+export { backupSchema, type Backup };
diff --git a/packages/pl-api/lib/entities/bookmark-folder.ts b/packages/pl-api/lib/entities/bookmark-folder.ts
new file mode 100644
index 000000000..c0f6550a4
--- /dev/null
+++ b/packages/pl-api/lib/entities/bookmark-folder.ts
@@ -0,0 +1,14 @@
+import { z } from 'zod';
+
+import type { Resolve } from '../utils/types';
+
+const bookmarkFolderSchema = z.object({
+ id: z.coerce.string(),
+ name: z.string().catch(''),
+ emoji: z.string().nullable().catch(null),
+ emoji_url: z.string().nullable().catch(null),
+});
+
+type BookmarkFolder = Resolve>;
+
+export { bookmarkFolderSchema, type BookmarkFolder };
diff --git a/packages/pl-api/lib/entities/chat-message.ts b/packages/pl-api/lib/entities/chat-message.ts
new file mode 100644
index 000000000..09a265c67
--- /dev/null
+++ b/packages/pl-api/lib/entities/chat-message.ts
@@ -0,0 +1,23 @@
+import { z } from 'zod';
+
+import { customEmojiSchema } from './custom-emoji';
+import { mediaAttachmentSchema } from './media-attachment';
+import { previewCardSchema } from './preview-card';
+import { dateSchema, filteredArray } from './utils';
+
+/** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-the-messages-for-a-chat} */
+const chatMessageSchema = z.object({
+ id: z.string(),
+ content: z.string().catch(''),
+ chat_id: z.string(),
+ account_id: z.string(),
+ created_at: dateSchema,
+ emojis: filteredArray(customEmojiSchema),
+ attachment: mediaAttachmentSchema.nullable().catch(null),
+ unread: z.boolean(),
+ card: previewCardSchema.nullable().catch(null),
+});
+
+type ChatMessage = z.infer;
+
+export { chatMessageSchema, type ChatMessage };
diff --git a/packages/pl-api/lib/entities/chat.ts b/packages/pl-api/lib/entities/chat.ts
new file mode 100644
index 000000000..1d2c1492e
--- /dev/null
+++ b/packages/pl-api/lib/entities/chat.ts
@@ -0,0 +1,18 @@
+import { z } from 'zod';
+
+import { accountSchema } from './account';
+import { chatMessageSchema } from './chat-message';
+import { dateSchema } from './utils';
+
+/** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-a-list-of-chats} */
+const chatSchema = z.object({
+ id: z.string(),
+ account: accountSchema,
+ unread: z.number().int(),
+ last_message: chatMessageSchema.nullable().catch(null),
+ created_at: dateSchema,
+});
+
+type Chat = z.infer;
+
+export { chatSchema, type Chat };
diff --git a/packages/pl-api/lib/entities/context.ts b/packages/pl-api/lib/entities/context.ts
new file mode 100644
index 000000000..a4fb0a2ef
--- /dev/null
+++ b/packages/pl-api/lib/entities/context.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { statusSchema } from './status';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Context/} */
+const contextSchema = z.object({
+ ancestors: z.array(statusSchema),
+ descendants: z.array(statusSchema),
+});
+
+type Context = Resolve>;
+
+export { contextSchema, type Context };
diff --git a/packages/pl-api/lib/entities/conversation.ts b/packages/pl-api/lib/entities/conversation.ts
new file mode 100644
index 000000000..719d0c398
--- /dev/null
+++ b/packages/pl-api/lib/entities/conversation.ts
@@ -0,0 +1,17 @@
+import { z } from 'zod';
+
+import { filteredArray } from './utils';
+
+import { accountSchema, statusSchema } from '.';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Conversation} */
+const conversationSchema = z.object({
+ id: z.string(),
+ unread: z.boolean().catch(false),
+ accounts: filteredArray(accountSchema),
+ last_status: statusSchema.nullable().catch(null),
+});
+
+type Conversation = z.infer;
+
+export { conversationSchema, type Conversation };
diff --git a/packages/pl-api/lib/entities/custom-emoji.ts b/packages/pl-api/lib/entities/custom-emoji.ts
new file mode 100644
index 000000000..1b9f2313c
--- /dev/null
+++ b/packages/pl-api/lib/entities/custom-emoji.ts
@@ -0,0 +1,17 @@
+import z from 'zod';
+
+/**
+ * Represents a custom emoji.
+ * @see {@link https://docs.joinmastodon.org/entities/CustomEmoji/}
+ */
+const customEmojiSchema = z.object({
+ shortcode: z.string(),
+ url: z.string(),
+ static_url: z.string().catch(''),
+ visible_in_picker: z.boolean().catch(true),
+ category: z.string().nullable().catch(null),
+});
+
+type CustomEmoji = z.infer;
+
+export { customEmojiSchema, type CustomEmoji };
diff --git a/packages/pl-api/lib/entities/domain-block.ts b/packages/pl-api/lib/entities/domain-block.ts
new file mode 100644
index 000000000..87f3fea0f
--- /dev/null
+++ b/packages/pl-api/lib/entities/domain-block.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/DomainBlock} */
+const domainBlockSchema = z.object({
+ domain: z.string(),
+ digest: z.string(),
+ severity: z.enum(['silence', 'suspend']),
+ comment: z.string().optional().catch(undefined),
+});
+
+type DomainBlock = z.infer;
+
+export { domainBlockSchema, type DomainBlock };
diff --git a/packages/pl-api/lib/entities/emoji-reaction.ts b/packages/pl-api/lib/entities/emoji-reaction.ts
new file mode 100644
index 000000000..2401f47ae
--- /dev/null
+++ b/packages/pl-api/lib/entities/emoji-reaction.ts
@@ -0,0 +1,27 @@
+import { z } from 'zod';
+
+import { accountSchema } from './account';
+import { emojiSchema, filteredArray } from './utils';
+
+const baseEmojiReactionSchema = z.object({
+ count: z.number().nullable().catch(null),
+ me: z.boolean().catch(false),
+ name: emojiSchema,
+ url: z.literal(undefined).catch(undefined),
+ accounts: filteredArray(accountSchema),
+});
+
+const customEmojiReactionSchema = baseEmojiReactionSchema.extend({
+ name: z.string(),
+ url: z.string().url(),
+});
+
+/**
+ * Pleroma emoji reaction.
+ * @see {@link https://docs.pleroma.social/backend/development/API/differences_in_mastoapi_responses/#statuses}
+*/
+const emojiReactionSchema = baseEmojiReactionSchema.or(customEmojiReactionSchema);
+
+type EmojiReaction = z.infer;
+
+export { emojiReactionSchema, type EmojiReaction };
diff --git a/packages/pl-api/lib/entities/extended-description.ts b/packages/pl-api/lib/entities/extended-description.ts
new file mode 100644
index 000000000..2594917ab
--- /dev/null
+++ b/packages/pl-api/lib/entities/extended-description.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+import { dateSchema } from './utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/ExtendedDescription} */
+const extendedDescriptionSchema = z.object({
+ updated_at: dateSchema,
+ content: z.string(),
+});
+
+type ExtendedDescription = z.infer;
+
+export { extendedDescriptionSchema, type ExtendedDescription };
diff --git a/packages/pl-api/lib/entities/familiar-followers.ts b/packages/pl-api/lib/entities/familiar-followers.ts
new file mode 100644
index 000000000..f61b1a01c
--- /dev/null
+++ b/packages/pl-api/lib/entities/familiar-followers.ts
@@ -0,0 +1,14 @@
+import z from 'zod';
+
+import { accountSchema } from './account';
+import { filteredArray } from './utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/FamiliarFollowers/} */
+const familiarFollowersSchema = z.object({
+ id: z.string(),
+ accounts: filteredArray(accountSchema),
+});
+
+type FamiliarFollowers = z.infer
+
+export { familiarFollowersSchema, type FamiliarFollowers };
diff --git a/packages/pl-api/lib/entities/featured-tag.ts b/packages/pl-api/lib/entities/featured-tag.ts
new file mode 100644
index 000000000..6aa8d90cb
--- /dev/null
+++ b/packages/pl-api/lib/entities/featured-tag.ts
@@ -0,0 +1,14 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/FeaturedTag/} */
+const featuredTagSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ url: z.string().optional().catch(undefined),
+ statuses_count: z.number(),
+ last_status_at: z.number(),
+});
+
+type FeaturedTag = z.infer;
+
+export { featuredTagSchema, type FeaturedTag };
diff --git a/packages/pl-api/lib/entities/filter-result.ts b/packages/pl-api/lib/entities/filter-result.ts
new file mode 100644
index 000000000..adc346fba
--- /dev/null
+++ b/packages/pl-api/lib/entities/filter-result.ts
@@ -0,0 +1,16 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { filterSchema } from './filter';
+
+/** @see {@link https://docs.joinmastodon.org/entities/FilterResult/} */
+const filterResultSchema = z.object({
+ filter: filterSchema,
+ keyword_matches: z.array(z.string()).nullable().catch(null),
+ status_matches: z.array(z.string()).nullable().catch(null),
+});
+
+type FilterResult = Resolve>;
+
+export { filterResultSchema, type FilterResult };
diff --git a/packages/pl-api/lib/entities/filter.ts b/packages/pl-api/lib/entities/filter.ts
new file mode 100644
index 000000000..dab546c3f
--- /dev/null
+++ b/packages/pl-api/lib/entities/filter.ts
@@ -0,0 +1,47 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { filteredArray } from './utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/FilterKeyword/} */
+const filterKeywordSchema = z.object({
+ id: z.string(),
+ keyword: z.string(),
+ whole_word: z.boolean(),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/FilterStatus/} */
+const filterStatusSchema = z.object({
+ id: z.string(),
+ status_id: z.string(),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Filter/} */
+const filterSchema = z.preprocess((filter: any) => {
+ if (filter.phrase) {
+ return {
+ ...filter,
+ title: filter.phrase,
+ keywords: [{
+ id: '1',
+ keyword: filter.phrase,
+ whole_word: filter.whole_word,
+ }],
+ filter_action: filter.irreversible ? 'hide' : 'warn',
+ };
+ }
+ return filter;
+}, z.object({
+ id: z.string(),
+ title: z.string(),
+ context: z.array(z.enum(['home', 'notifications', 'public', 'thread', 'account'])),
+ expires_at: z.string().datetime({ offset: true }).nullable().catch(null),
+ filter_action: z.enum(['warn', 'hide']).catch('warn'),
+ keywords: filteredArray(filterKeywordSchema),
+ statuses: filteredArray(filterStatusSchema),
+}));
+
+type Filter = Resolve>;
+
+export { filterKeywordSchema, filterStatusSchema, filterSchema, type Filter };
diff --git a/packages/pl-api/lib/entities/group-member.ts b/packages/pl-api/lib/entities/group-member.ts
new file mode 100644
index 000000000..37a736ca6
--- /dev/null
+++ b/packages/pl-api/lib/entities/group-member.ts
@@ -0,0 +1,21 @@
+import z from 'zod';
+
+import { accountSchema } from './account';
+
+enum GroupRoles {
+ OWNER = 'owner',
+ ADMIN = 'admin',
+ USER = 'user'
+}
+
+type GroupRole =`${GroupRoles}`;
+
+const groupMemberSchema = z.object({
+ id: z.string(),
+ account: accountSchema,
+ role: z.nativeEnum(GroupRoles),
+});
+
+type GroupMember = z.infer;
+
+export { groupMemberSchema, type GroupMember, GroupRoles, type GroupRole };
diff --git a/packages/pl-api/lib/entities/group-relationship.ts b/packages/pl-api/lib/entities/group-relationship.ts
new file mode 100644
index 000000000..18ad06748
--- /dev/null
+++ b/packages/pl-api/lib/entities/group-relationship.ts
@@ -0,0 +1,14 @@
+import z from 'zod';
+
+import { GroupRoles } from './group-member';
+
+const groupRelationshipSchema = z.object({
+ id: z.string(),
+ member: z.boolean().catch(false),
+ role: z.nativeEnum(GroupRoles).catch(GroupRoles.USER),
+ requested: z.boolean().catch(false),
+});
+
+type GroupRelationship = z.infer;
+
+export { groupRelationshipSchema, type GroupRelationship };
diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts
new file mode 100644
index 000000000..fdf286946
--- /dev/null
+++ b/packages/pl-api/lib/entities/group.ts
@@ -0,0 +1,33 @@
+import z from 'zod';
+
+import { customEmojiSchema } from './custom-emoji';
+import { groupRelationshipSchema } from './group-relationship';
+import { filteredArray } from './utils';
+
+const groupSchema = z.object({
+ avatar: z.string().catch(''),
+ avatar_static: z.string().catch(''),
+ created_at: z.string().datetime().catch(new Date().toUTCString()),
+ display_name: z.string().catch(''),
+ domain: z.string().catch(''),
+ emojis: filteredArray(customEmojiSchema),
+ header: z.string().catch(''),
+ header_static: z.string().catch(''),
+ id: z.coerce.string(),
+ locked: z.boolean().catch(false),
+ membership_required: z.boolean().catch(false),
+ members_count: z.number().catch(0),
+ owner: z.object({ id: z.string() }).nullable().catch(null),
+ note: z.string().transform(note => note === '' ? '' : note).catch(''),
+ relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later
+ statuses_visibility: z.string().catch('public'),
+ uri: z.string().catch(''),
+ url: z.string().catch(''),
+
+ avatar_description: z.string().catch(''),
+ header_description: z.string().catch(''),
+});
+
+type Group = z.infer;
+
+export { groupSchema, type Group };
diff --git a/packages/pl-api/lib/entities/index.ts b/packages/pl-api/lib/entities/index.ts
new file mode 100644
index 000000000..7c993597e
--- /dev/null
+++ b/packages/pl-api/lib/entities/index.ts
@@ -0,0 +1,69 @@
+export * from './account';
+export * from './account-warning';
+export * from './admin/account';
+export * from './admin/announcement';
+export * from './admin/canonical-email-block';
+export * from './admin/cohort';
+export * from './admin/dimension';
+export * from './admin/domain';
+export * from './admin/domain-allow';
+export * from './admin/domain-block';
+export * from './admin/email-domain-block';
+export * from './admin/ip';
+export * from './admin/ip-block';
+export * from './admin/measure';
+export * from './admin/moderation-log-entry';
+export * from './admin/relay';
+export * from './admin/report';
+export * from './admin/rule';
+export * from './admin/tag';
+export * from './announcement';
+export * from './announcement-reaction';
+export * from './application';
+export * from './backup';
+export * from './bookmark-folder';
+export * from './chat';
+export * from './chat-message';
+export * from './context';
+export * from './conversation';
+export * from './custom-emoji';
+export * from './domain-block';
+export * from './emoji-reaction';
+export * from './extended-description';
+export * from './familiar-followers';
+export * from './featured-tag';
+export * from './filter';
+export * from './group';
+export * from './group-member';
+export * from './group-relationship';
+export * from './instance';
+export * from './interaction-policy';
+export * from './interaction-request';
+export * from './list';
+export * from './location';
+export * from './marker';
+export * from './media-attachment';
+export * from './mention';
+export * from './notification';
+export * from './notification-policy';
+export * from './notification-request';
+export * from './oauth-token';
+export * from './poll';
+export * from './preview-card';
+export * from './relationship';
+export * from './relationship-severance-event';
+export * from './report';
+export * from './role';
+export * from './rule';
+export * from './scheduled-status';
+export * from './search';
+export * from './status';
+export * from './status-edit';
+export * from './status-source';
+export * from './streaming-event';
+export * from './suggestion';
+export * from './tag';
+export * from './token';
+export * from './translation';
+export * from './trends-link';
+export * from './web-push-subscription';
diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts
new file mode 100644
index 000000000..bd7b0bf5b
--- /dev/null
+++ b/packages/pl-api/lib/entities/instance.ts
@@ -0,0 +1,325 @@
+/* eslint sort-keys: "error" */
+import z from 'zod';
+
+import { accountSchema } from './account';
+import { ruleSchema } from './rule';
+import { coerceObject, filteredArray, mimeSchema } from './utils';
+
+const fixVersion = (version: string) => {
+ // Handle Mastodon release candidates
+ if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) {
+ version = version.split('rc').join('-rc');
+ }
+
+ // Rename Akkoma to Pleroma+akkoma
+ if (version.includes('Akkoma')) {
+ version = '2.7.2 (compatible; Pleroma 2.4.50+akkoma)';
+ }
+
+ // Set Takahē version to a Pleroma-like string
+ if (version.startsWith('takahe/')) {
+ version = `0.0.0 (compatible; Takahe ${version.slice(7)})`;
+ }
+
+ return version;
+};
+
+const configurationSchema = coerceObject({
+ accounts: z.object({
+ allow_custom_css: z.boolean(),
+ max_featured_tags: z.number().int(),
+ max_profile_fields: z.number().int(),
+ }).nullable().catch(null),
+ chats: coerceObject({
+ max_characters: z.number().catch(5000),
+ }),
+ groups: coerceObject({
+ max_characters_description: z.number().catch(160),
+ max_characters_name: z.number().catch(50),
+ }),
+ media_attachments: coerceObject({
+ image_matrix_limit: z.number().optional().catch(undefined),
+ image_size_limit: z.number().optional().catch(undefined),
+ supported_mime_types: mimeSchema.array().optional().catch(undefined),
+ video_duration_limit: z.number().optional().catch(undefined),
+ video_frame_rate_limit: z.number().optional().catch(undefined),
+ video_matrix_limit: z.number().optional().catch(undefined),
+ video_size_limit: z.number().optional().catch(undefined),
+ }),
+ polls: coerceObject({
+ max_characters_per_option: z.number().optional().catch(undefined),
+ max_expiration: z.number().optional().catch(undefined),
+ max_options: z.number().optional().catch(undefined),
+ min_expiration: z.number().optional().catch(undefined),
+ }),
+ reactions: coerceObject({
+ max_reactions: z.number().catch(0),
+ }),
+ statuses: coerceObject({
+ characters_reserved_per_url: z.number().optional().catch(undefined),
+ max_characters: z.number().optional().catch(undefined),
+ max_media_attachments: z.number().optional().catch(undefined),
+
+ }),
+ translation: coerceObject({
+ enabled: z.boolean().catch(false),
+ }),
+ urls: coerceObject({
+ streaming: z.string().url().optional().catch(undefined),
+ }),
+});
+
+const contactSchema = coerceObject({
+ contact_account: accountSchema.optional().catch(undefined),
+ email: z.string().email().catch(''),
+});
+
+const pleromaSchema = coerceObject({
+ metadata: coerceObject({
+ account_activation_required: z.boolean().catch(false),
+ birthday_min_age: z.number().catch(0),
+ birthday_required: z.boolean().catch(false),
+ description_limit: z.number().catch(1500),
+ features: z.string().array().catch([]),
+ federation: coerceObject({
+ enabled: z.boolean().catch(true), // Assume true unless explicitly false
+ mrf_policies: z.string().array().optional().catch(undefined),
+ mrf_simple: coerceObject({
+ accept: z.string().array().catch([]),
+ avatar_removal: z.string().array().catch([]),
+ banner_removal: z.string().array().catch([]),
+ federated_timeline_removal: z.string().array().catch([]),
+ followers_only: z.string().array().catch([]),
+ media_nsfw: z.string().array().catch([]),
+ media_removal: z.string().array().catch([]),
+ reject: z.string().array().catch([]),
+ reject_deletes: z.string().array().catch([]),
+ report_removal: z.string().array().catch([]),
+ }),
+ }),
+ fields_limits: coerceObject({
+ max_fields: z.number().nonnegative().catch(4),
+ name_length: z.number().nonnegative().catch(255),
+ value_length: z.number().nonnegative().catch(2047),
+ }),
+ markup: coerceObject({
+ allow_headings: z.boolean().catch(false),
+ allow_inline_images: z.boolean().catch(false),
+ }),
+ migration_cooldown_period: z.number().optional().catch(undefined),
+ multitenancy: coerceObject({
+ domains: z
+ .array(
+ z.object({
+ domain: z.coerce.string(),
+ id: z.string(),
+ public: z.boolean().catch(false),
+ }),
+ )
+ .optional(),
+ enabled: z.boolean().catch(false),
+ }),
+ post_formats: z.string().array().optional().catch(undefined),
+ restrict_unauthenticated: coerceObject({
+ activities: coerceObject({
+ local: z.boolean().catch(false),
+ remote: z.boolean().catch(false),
+ }),
+ profiles: coerceObject({
+ local: z.boolean().catch(false),
+ remote: z.boolean().catch(false),
+ }),
+ timelines: coerceObject({
+ bubble: z.boolean().catch(false),
+ federated: z.boolean().catch(false),
+ local: z.boolean().catch(false),
+ }),
+ }),
+ translation: coerceObject({
+ allow_remote: z.boolean().catch(true),
+ allow_unauthenticated: z.boolean().catch(false),
+ source_languages: z.string().array().optional().catch(undefined),
+ target_languages: z.string().array().optional().catch(undefined),
+ }),
+ }),
+ oauth_consumer_strategies: z.string().array().catch([]),
+ stats: coerceObject({
+ mau: z.number().optional().catch(undefined),
+ }),
+ vapid_public_key: z.string().catch(''),
+});
+
+const pleromaPollLimitsSchema = coerceObject({
+ max_expiration: z.number().optional().catch(undefined),
+ max_option_chars: z.number().optional().catch(undefined),
+ max_options: z.number().optional().catch(undefined),
+ min_expiration: z.number().optional().catch(undefined),
+});
+
+const registrations = coerceObject({
+ approval_required: z.boolean().catch(false),
+ enabled: z.boolean().catch(false),
+ message: z.string().optional().catch(undefined),
+});
+
+const statsSchema = coerceObject({
+ domain_count: z.number().optional().catch(undefined),
+ status_count: z.number().optional().catch(undefined),
+ user_count: z.number().optional().catch(undefined),
+});
+
+const thumbnailSchema = coerceObject({
+ url: z.string().catch(''),
+});
+
+const usageSchema = coerceObject({
+ users: coerceObject({
+ active_month: z.number().catch(0),
+ }),
+});
+
+const instanceV1Schema = coerceObject({
+ account_domain: z.string().catch(''),
+ approval_required: z.boolean().catch(false),
+ configuration: configurationSchema,
+ contact_account: accountSchema.optional().catch(undefined),
+ description: z.string().catch(''),
+ description_limit: z.number().catch(1500),
+ email: z.string().email().catch(''),
+ feature_quote: z.boolean().catch(false),
+ fedibird_capabilities: z.array(z.string()).catch([]),
+ languages: z.string().array().catch([]),
+ max_media_attachments: z.number().optional().catch(undefined),
+ max_toot_chars: z.number().optional().catch(undefined),
+ pleroma: pleromaSchema,
+ poll_limits: pleromaPollLimitsSchema,
+ registrations: z.boolean().catch(false),
+ rules: filteredArray(ruleSchema),
+ short_description: z.string().catch(''),
+ stats: statsSchema,
+ thumbnail: z.string().catch(''),
+ title: z.string().catch(''),
+ uri: z.string().catch(''),
+ urls: coerceObject({
+ streaming_api: z.string().url().optional().catch(undefined),
+ }),
+ usage: usageSchema,
+ version: z.string().catch('0.0.0'),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Instance/} */
+const instanceSchema = z.preprocess((data: any) => {
+ // Detect GoToSocial
+ if (typeof data.configuration?.accounts?.allow_custom_css === 'boolean') {
+ data.version = `0.0.0 (compatible; GoToSocial ${data.version})`;
+ }
+
+ if (data.domain) return { account_domain: data.domain, ...data };
+
+ const {
+ approval_required,
+ configuration,
+ contact_account,
+ description,
+ description_limit,
+ email,
+ max_media_attachments,
+ max_toot_chars,
+ poll_limits,
+ pleroma,
+ registrations,
+ short_description,
+ thumbnail,
+ uri,
+ urls,
+ ...instance
+ } = instanceV1Schema.parse(data);
+
+ return {
+ ...instance,
+ account_domain: instance.account_domain || uri,
+ configuration: {
+ ...configuration,
+ polls: {
+ ...configuration.polls,
+ max_characters_per_option: configuration.polls.max_characters_per_option ?? poll_limits.max_option_chars ?? 25,
+ max_expiration: configuration.polls.max_expiration ?? poll_limits.max_expiration ?? 2629746,
+ max_options: configuration.polls.max_options ?? poll_limits.max_options ?? 4,
+ min_expiration: configuration.polls.min_expiration ?? poll_limits.min_expiration ?? 300,
+ },
+ statuses: {
+ ...configuration.statuses,
+ max_characters: configuration.statuses.max_characters ?? max_toot_chars ?? 500,
+ max_media_attachments: configuration.statuses.max_media_attachments ?? max_media_attachments,
+ },
+ urls: {
+ streaming: urls.streaming_api,
+ },
+ },
+ contact: {
+ account: contact_account,
+ email: email,
+ },
+ description: short_description || description,
+ domain: uri,
+ pleroma: {
+ ...pleroma,
+ metadata: {
+ ...pleroma.metadata,
+ description_limit,
+ },
+ },
+ registrations: {
+ approval_required: approval_required,
+ enabled: registrations,
+ },
+ thumbnail: { url: thumbnail },
+ };
+}, coerceObject({
+ account_domain: z.string().catch(''),
+ configuration: configurationSchema,
+ contact: contactSchema,
+ description: z.string().catch(''),
+ domain: z.string().catch(''),
+ feature_quote: z.boolean().catch(false),
+ fedibird_capabilities: z.array(z.string()).catch([]),
+ languages: z.string().array().catch([]),
+ pleroma: pleromaSchema,
+ registrations: registrations,
+ rules: filteredArray(ruleSchema),
+ stats: statsSchema,
+ thumbnail: thumbnailSchema,
+ title: z.string().catch(''),
+ usage: usageSchema,
+ version: z.string().catch('0.0.0'),
+}).transform(({ configuration, ...instance }) => {
+ const version = fixVersion(instance.version);
+
+ const polls = {
+ ...configuration.polls,
+ max_characters_per_option: configuration.polls.max_characters_per_option ?? 25,
+ max_expiration: configuration.polls.max_expiration ?? 2629746,
+ max_options: configuration.polls.max_options ?? 4,
+ min_expiration: configuration.polls.min_expiration ?? 300,
+ };
+
+ const statuses = {
+ ...configuration.statuses,
+ max_characters: configuration.statuses.max_characters ?? 500,
+ max_media_attachments: configuration.statuses.max_media_attachments ?? 4,
+ };
+
+ return {
+ ...instance,
+ configuration: {
+ ...configuration,
+ polls,
+ statuses,
+ },
+ version,
+ };
+}));
+
+type Instance = z.infer;
+
+export { instanceSchema, type Instance };
diff --git a/packages/pl-api/lib/entities/interaction-policy.ts b/packages/pl-api/lib/entities/interaction-policy.ts
new file mode 100644
index 000000000..c76317cba
--- /dev/null
+++ b/packages/pl-api/lib/entities/interaction-policy.ts
@@ -0,0 +1,33 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { coerceObject } from './utils';
+
+const interactionPolicyEntrySchema = z.enum(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']);
+
+const interactionPolicyRuleSchema = coerceObject({
+ always: z.array(interactionPolicyEntrySchema).default(['public']),
+ with_approval: z.array(interactionPolicyEntrySchema).default([]),
+});
+
+/** @see {@link https://docs.gotosocial.org/en/latest/api/swagger/} */
+const interactionPolicySchema = coerceObject({
+ can_favourite: interactionPolicyRuleSchema,
+ can_reblog: interactionPolicyRuleSchema,
+ can_reply: interactionPolicyRuleSchema,
+});
+
+type InteractionPolicy = Resolve>;
+
+const interactionPoliciesSchema = coerceObject({
+ public: interactionPolicySchema,
+ unlisted: interactionPolicySchema,
+ private: interactionPolicySchema,
+ direct: interactionPolicySchema,
+});
+
+type InteractionPolicies = Resolve>;
+
+export { interactionPolicySchema, interactionPoliciesSchema, type InteractionPolicy, type InteractionPolicies };
+
diff --git a/packages/pl-api/lib/entities/interaction-request.ts b/packages/pl-api/lib/entities/interaction-request.ts
new file mode 100644
index 000000000..883a5aed5
--- /dev/null
+++ b/packages/pl-api/lib/entities/interaction-request.ts
@@ -0,0 +1,23 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { accountSchema } from './account';
+import { statusSchema } from './status';
+
+/** @see {@link https://docs.gotosocial.org/en/latest/api/swagger.yaml#/definitions/interactionRequest} */
+const interactionRequestSchema = z.object({
+ accepted_at: z.string().datetime().nullable().catch(null),
+ account: accountSchema,
+ created_at: z.string().datetime(),
+ id: z.string(),
+ rejected_at: z.string().datetime().nullable().catch(null),
+ reply: statusSchema.nullable().catch(null),
+ status: statusSchema.nullable().catch(null),
+ type: z.enum(['favourite', 'reply', 'reblog']),
+ uri: z.string().nullable().catch(null),
+});
+
+type InteractionRequest = Resolve>;
+
+export { interactionRequestSchema, type InteractionRequest };
diff --git a/packages/pl-api/lib/entities/list.ts b/packages/pl-api/lib/entities/list.ts
new file mode 100644
index 000000000..c1d5d1b7c
--- /dev/null
+++ b/packages/pl-api/lib/entities/list.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/List/} */
+const listSchema = z.object({
+ id: z.coerce.string(),
+ title: z.string(),
+ replies_policy: z.string().optional().catch(undefined),
+ exclusive: z.boolean().optional().catch(undefined),
+});
+
+type List = z.infer;
+
+export { listSchema, type List };
diff --git a/packages/pl-api/lib/entities/location.ts b/packages/pl-api/lib/entities/location.ts
new file mode 100644
index 000000000..0e5edcb1d
--- /dev/null
+++ b/packages/pl-api/lib/entities/location.ts
@@ -0,0 +1,23 @@
+import { z } from 'zod';
+
+const locationSchema = z.object({
+ url: z.string().url().catch(''),
+ description: z.string().catch(''),
+ country: z.string().catch(''),
+ locality: z.string().catch(''),
+ region: z.string().catch(''),
+ postal_code: z.string().catch(''),
+ street: z.string().catch(''),
+ origin_id: z.string().catch(''),
+ origin_provider: z.string().catch(''),
+ type: z.string().catch(''),
+ timezone: z.string().catch(''),
+ geom: z.object({
+ coordinates: z.tuple([z.number(), z.number()]).nullable().catch(null),
+ srid: z.string().catch(''),
+ }).nullable().catch(null),
+});
+
+type Location = z.infer;
+
+export { locationSchema, type Location };
diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts
new file mode 100644
index 000000000..2983ffdb1
--- /dev/null
+++ b/packages/pl-api/lib/entities/marker.ts
@@ -0,0 +1,27 @@
+import { z } from 'zod';
+
+import { dateSchema } from './utils';
+
+const markerSchema = z.preprocess((marker: any) => ({
+ unread_count: marker.pleroma?.unread_count,
+ ...marker,
+}), z.object({
+ last_read_id: z.string(),
+ version: z.number().int(),
+ updated_at: dateSchema,
+ unread_count: z.number().int().optional().catch(undefined),
+}));
+
+/** @see {@link https://docs.joinmastodon.org/entities/Marker/} */
+type Marker = z.infer;
+
+const markersSchema = z.record(markerSchema);
+
+type Markers = z.infer;
+
+export {
+ markerSchema,
+ markersSchema,
+ type Marker,
+ type Markers,
+};
diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts
new file mode 100644
index 000000000..c3aa87a5c
--- /dev/null
+++ b/packages/pl-api/lib/entities/media-attachment.ts
@@ -0,0 +1,105 @@
+import { isBlurhashValid } from 'blurhash';
+import { z } from 'zod';
+
+import { mimeSchema } from './utils';
+
+const blurhashSchema = z.string().superRefine((value, ctx) => {
+ const r = isBlurhashValid(value);
+
+ if (!r.result) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: r.errorReason,
+ });
+ }
+});
+
+const baseAttachmentSchema = z.object({
+ id: z.string(),
+ type: z.string(),
+ url: z.string().url().catch(''),
+ preview_url: z.string().url().catch(''),
+ remote_url: z.string().url().nullable().catch(null),
+ description: z.string().catch(''),
+ blurhash: blurhashSchema.nullable().catch(null),
+
+ mime_type: mimeSchema.nullable().catch(null),
+});
+
+const imageMetaSchema = z.object({
+ width: z.number(),
+ height: z.number(),
+ size: z.string().regex(/\d+x\d+$/).nullable().catch(null),
+ aspect: z.number().nullable().catch(null),
+});
+
+const imageAttachmentSchema = baseAttachmentSchema.extend({
+ type: z.literal('image'),
+ meta: z.object({
+ original: imageMetaSchema.optional().catch(undefined),
+ small: imageMetaSchema.optional().catch(undefined),
+ focus: z.object({
+ x: z.number().min(-1).max(1),
+ y: z.number().min(-1).max(1),
+ }).optional().catch(undefined),
+ }).catch({}),
+});
+
+const videoAttachmentSchema = baseAttachmentSchema.extend({
+ type: z.literal('video'),
+ meta: z.object({
+ duration: z.number().optional().catch(undefined),
+ original: imageMetaSchema.extend({
+ frame_rate: z.string().regex(/\d+\/\d+$/).nullable().catch(null),
+ duration: z.number().nonnegative().nullable().catch(null),
+ }).optional().catch(undefined),
+ small: imageMetaSchema.optional().catch(undefined),
+ // WIP: add rest
+ }).catch({}),
+});
+
+const gifvAttachmentSchema = baseAttachmentSchema.extend({
+ type: z.literal('gifv'),
+ meta: z.object({
+ duration: z.number().optional().catch(undefined),
+ original: imageMetaSchema.optional().catch(undefined),
+ }).catch({}),
+});
+
+const audioAttachmentSchema = baseAttachmentSchema.extend({
+ type: z.literal('audio'),
+ meta: z.object({
+ duration: z.number().optional().catch(undefined),
+ colors: z.object({
+ background: z.string().optional().catch(undefined),
+ foreground: z.string().optional().catch(undefined),
+ accent: z.string().optional().catch(undefined),
+ duration: z.number().optional().catch(undefined),
+ }).optional().catch(undefined),
+ original: z.object({
+ duration: z.number().optional().catch(undefined),
+ bitrate: z.number().nonnegative().optional().catch(undefined),
+ }).optional().catch(undefined),
+ }).catch({}),
+});
+
+const unknownAttachmentSchema = baseAttachmentSchema.extend({
+ type: z.literal('unknown'),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/MediaAttachment} */
+const mediaAttachmentSchema = z.preprocess((data: any) => ({
+ mime_type: data.pleroma?.mime_type,
+ preview_url: data.url,
+ ...data,
+}), z.discriminatedUnion('type', [
+ imageAttachmentSchema,
+ videoAttachmentSchema,
+ gifvAttachmentSchema,
+ audioAttachmentSchema,
+ unknownAttachmentSchema,
+]));
+
+type MediaAttachment = z.infer;
+
+export { blurhashSchema, mediaAttachmentSchema, type MediaAttachment };
diff --git a/packages/pl-api/lib/entities/mention.ts b/packages/pl-api/lib/entities/mention.ts
new file mode 100644
index 000000000..81f1f7366
--- /dev/null
+++ b/packages/pl-api/lib/entities/mention.ts
@@ -0,0 +1,19 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Status/#Mention} */
+const mentionSchema = z.object({
+ id: z.string(),
+ username: z.string().catch(''),
+ url: z.string().url().catch(''),
+ acct: z.string(),
+}).transform((mention) => {
+ if (!mention.username) {
+ mention.username = mention.acct.split('@')[0];
+ }
+
+ return mention;
+});
+
+type Mention = z.infer;
+
+export { mentionSchema, type Mention };
diff --git a/packages/pl-api/lib/entities/notification-policy.ts b/packages/pl-api/lib/entities/notification-policy.ts
new file mode 100644
index 000000000..769af206e
--- /dev/null
+++ b/packages/pl-api/lib/entities/notification-policy.ts
@@ -0,0 +1,17 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/NotificationPolicy} */
+const notificationPolicySchema = z.object({
+ filter_not_following: z.boolean(),
+ filter_not_followers: z.boolean(),
+ filter_new_accounts: z.boolean(),
+ filter_private_mentions: z.boolean(),
+ summary: z.object({
+ pending_requests_count: z.number().int(),
+ pending_notifications_count: z.number().int(),
+ }),
+});
+
+type NotificationPolicy = z.infer;
+
+export { notificationPolicySchema, type NotificationPolicy };
diff --git a/packages/pl-api/lib/entities/notification-request.ts b/packages/pl-api/lib/entities/notification-request.ts
new file mode 100644
index 000000000..33066f773
--- /dev/null
+++ b/packages/pl-api/lib/entities/notification-request.ts
@@ -0,0 +1,19 @@
+import { z } from 'zod';
+
+import { dateSchema } from './utils';
+
+import { accountSchema, statusSchema } from '.';
+
+/** @see {@link https://docs.joinmastodon.org/entities/NotificationRequest} */
+const notificationRequestSchema = z.object({
+ id: z.string(),
+ created_at: dateSchema,
+ updated_at: dateSchema,
+ account: accountSchema,
+ notifications_count: z.coerce.string(),
+ last_status: statusSchema.optional().catch(undefined),
+});
+
+type NotificationRequest = z.infer;
+
+export { notificationRequestSchema, type NotificationRequest };
diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts
new file mode 100644
index 000000000..818fb85a3
--- /dev/null
+++ b/packages/pl-api/lib/entities/notification.ts
@@ -0,0 +1,100 @@
+import pick from 'lodash.pick';
+import { z } from 'zod';
+
+import { accountSchema } from './account';
+import { accountWarningSchema } from './account-warning';
+import { chatMessageSchema } from './chat-message';
+import { relationshipSeveranceEventSchema } from './relationship-severance-event';
+import { reportSchema } from './report';
+import { statusSchema } from './status';
+import { dateSchema } from './utils';
+
+const baseNotificationSchema = z.object({
+ account: accountSchema,
+ created_at: dateSchema,
+ id: z.string(),
+ type: z.string(),
+
+ is_muted: z.boolean().optional().catch(undefined),
+ is_seen: z.boolean().optional().catch(undefined),
+});
+
+const accountNotificationSchema = baseNotificationSchema.extend({
+ type: z.enum(['follow', 'follow_request', 'admin.sign_up', 'bite']),
+});
+
+const statusNotificationSchema = baseNotificationSchema.extend({
+ type: z.enum(['mention', 'status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']),
+ status: statusSchema,
+});
+
+const reportNotificationSchema = baseNotificationSchema.extend({
+ type: z.literal('admin.report'),
+ report: reportSchema,
+});
+
+const severedRelationshipNotificationSchema = baseNotificationSchema.extend({
+ type: z.literal('severed_relationships'),
+ relationship_severance_event: relationshipSeveranceEventSchema,
+});
+
+const moderationWarningNotificationSchema = baseNotificationSchema.extend({
+ type: z.literal('moderation_warning'),
+ moderation_warning: accountWarningSchema,
+});
+
+const moveNotificationSchema = baseNotificationSchema.extend({
+ type: z.literal('move'),
+ target: accountSchema,
+});
+
+const emojiReactionNotificationSchema = baseNotificationSchema.extend({
+ type: z.literal('emoji_reaction'),
+ emoji: z.string(),
+ emoji_url: z.string().nullable().catch(null),
+ status: statusSchema,
+});
+
+const chatMentionNotificationSchema = baseNotificationSchema.extend({
+ type: z.literal('chat_mention'),
+ chat_message: chatMessageSchema,
+});
+
+const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({
+ type: z.enum(['participation_accepted', 'participation_request']),
+ status: statusSchema,
+ participation_message: z.string().nullable().catch(null),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Notification/} */
+const notificationSchema: z.ZodType = z.preprocess((notification: any) => ({
+ ...pick(notification.pleroma || {}, ['is_muted', 'is_seen']),
+ ...notification,
+ type: notification.type === 'pleroma:report'
+ ? 'admin.report'
+ : notification.type?.replace(/^pleroma:/, ''),
+}), z.discriminatedUnion('type', [
+ accountNotificationSchema,
+ statusNotificationSchema,
+ reportNotificationSchema,
+ severedRelationshipNotificationSchema,
+ moderationWarningNotificationSchema,
+ moveNotificationSchema,
+ emojiReactionNotificationSchema,
+ chatMentionNotificationSchema,
+ eventParticipationRequestNotificationSchema,
+])) as any;
+
+type Notification = z.infer<
+| typeof accountNotificationSchema
+| typeof statusNotificationSchema
+| typeof reportNotificationSchema
+| typeof severedRelationshipNotificationSchema
+| typeof moderationWarningNotificationSchema
+| typeof moveNotificationSchema
+| typeof emojiReactionNotificationSchema
+| typeof chatMentionNotificationSchema
+| typeof eventParticipationRequestNotificationSchema
+>;
+
+export { notificationSchema, type Notification };
diff --git a/packages/pl-api/lib/entities/oauth-token.ts b/packages/pl-api/lib/entities/oauth-token.ts
new file mode 100644
index 000000000..2c5afdd7e
--- /dev/null
+++ b/packages/pl-api/lib/entities/oauth-token.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apioauth_tokens} */
+const oauthTokenSchema = z.preprocess((token: any) => ({
+ ...token,
+ valid_until: token?.valid_until?.padEnd(27, 'Z'),
+}), z.object({
+ app_name: z.string(),
+ id: z.number(),
+ valid_until: z.string().datetime({ offset: true }),
+}));
+
+type OauthToken = z.infer;
+
+export { oauthTokenSchema, type OauthToken };
diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts
new file mode 100644
index 000000000..bfaf627be
--- /dev/null
+++ b/packages/pl-api/lib/entities/poll.ts
@@ -0,0 +1,32 @@
+import { z } from 'zod';
+
+import { customEmojiSchema } from './custom-emoji';
+import { filteredArray } from './utils';
+
+const pollOptionSchema = z.object({
+ title: z.string().catch(''),
+ votes_count: z.number().catch(0),
+
+ title_map: z.record(z.string()).nullable().catch(null),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Poll/} */
+const pollSchema = z.object({
+ emojis: filteredArray(customEmojiSchema),
+ expired: z.boolean().catch(false),
+ expires_at: z.string().datetime().nullable().catch(null),
+ id: z.string(),
+ multiple: z.boolean().catch(false),
+ options: z.array(pollOptionSchema).min(2),
+ voters_count: z.number().catch(0),
+ votes_count: z.number().catch(0),
+ own_votes: z.array(z.number()).nonempty().nullable().catch(null),
+ voted: z.boolean().catch(false),
+
+ non_anonymous: z.boolean().catch(false),
+});
+
+type Poll = z.infer;
+type PollOption = Poll['options'][number];
+
+export { pollSchema, type Poll, type PollOption };
diff --git a/packages/pl-api/lib/entities/preview-card.ts b/packages/pl-api/lib/entities/preview-card.ts
new file mode 100644
index 000000000..1b03eba06
--- /dev/null
+++ b/packages/pl-api/lib/entities/preview-card.ts
@@ -0,0 +1,24 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/} */
+const previewCardSchema = z.object({
+ author_name: z.string().catch(''),
+ author_url: z.string().url().catch(''),
+ blurhash: z.string().nullable().catch(null),
+ description: z.string().catch(''),
+ embed_url: z.string().url().catch(''),
+ height: z.number().catch(0),
+ html: z.string().catch(''),
+ image: z.string().nullable().catch(null),
+ image_description: z.string().catch(''),
+ provider_name: z.string().catch(''),
+ provider_url: z.string().url().catch(''),
+ title: z.string().catch(''),
+ type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'),
+ url: z.string().url(),
+ width: z.number().catch(0),
+});
+
+type PreviewCard = z.infer;
+
+export { previewCardSchema, type PreviewCard };
diff --git a/packages/pl-api/lib/entities/relationship-severance-event.ts b/packages/pl-api/lib/entities/relationship-severance-event.ts
new file mode 100644
index 000000000..7dd218b77
--- /dev/null
+++ b/packages/pl-api/lib/entities/relationship-severance-event.ts
@@ -0,0 +1,18 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { dateSchema } from './utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/} */
+const relationshipSeveranceEventSchema = z.object({
+ id: z.string(),
+ type: z.enum(['domain_block', 'user_domain_block', 'account_suspension']),
+ purged: z.string(),
+ relationships_count: z.number().optional().catch(undefined),
+ created_at: dateSchema,
+});
+
+type RelationshipSeveranceEvent = Resolve>;
+
+export { relationshipSeveranceEventSchema, type RelationshipSeveranceEvent };
diff --git a/packages/pl-api/lib/entities/relationship.ts b/packages/pl-api/lib/entities/relationship.ts
new file mode 100644
index 000000000..eae07bad4
--- /dev/null
+++ b/packages/pl-api/lib/entities/relationship.ts
@@ -0,0 +1,22 @@
+import z from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Relationship/} */
+const relationshipSchema = z.object({
+ blocked_by: z.boolean().catch(false),
+ blocking: z.boolean().catch(false),
+ domain_blocking: z.boolean().catch(false),
+ endorsed: z.boolean().catch(false),
+ followed_by: z.boolean().catch(false),
+ following: z.boolean().catch(false),
+ id: z.string(),
+ muting: z.boolean().catch(false),
+ muting_notifications: z.boolean().catch(false),
+ note: z.string().catch(''),
+ notifying: z.boolean().catch(false),
+ requested: z.boolean().catch(false),
+ showing_reblogs: z.boolean().catch(false),
+});
+
+type Relationship = z.infer;
+
+export { relationshipSchema, type Relationship };
diff --git a/packages/pl-api/lib/entities/report.ts b/packages/pl-api/lib/entities/report.ts
new file mode 100644
index 000000000..d3d1b503a
--- /dev/null
+++ b/packages/pl-api/lib/entities/report.ts
@@ -0,0 +1,22 @@
+import { z } from 'zod';
+
+import { accountSchema } from './account';
+import { dateSchema } from './utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Report/} */
+const reportSchema = z.object({
+ id: z.string(),
+ action_taken: z.boolean().optional().catch(undefined),
+ action_taken_at: dateSchema.nullable().catch(null),
+ category: z.string().optional().catch(undefined),
+ comment: z.string().optional().catch(undefined),
+ forwarded: z.boolean().optional().catch(undefined),
+ created_at: dateSchema.optional().catch(undefined),
+ status_ids: z.array(z.string()).nullable().catch(null),
+ rule_ids: z.array(z.string()).nullable().catch(null),
+ target_account: accountSchema,
+});
+
+type Report = z.infer;
+
+export { reportSchema, type Report };
diff --git a/packages/pl-api/lib/entities/role.ts b/packages/pl-api/lib/entities/role.ts
new file mode 100644
index 000000000..7a80eade0
--- /dev/null
+++ b/packages/pl-api/lib/entities/role.ts
@@ -0,0 +1,18 @@
+import { z } from 'zod';
+
+const hexSchema = z.string().regex(/^#[a-f0-9]{6}$/i);
+
+const roleSchema = z.object({
+ id: z.string().catch(''),
+ name: z.string().catch(''),
+ color: hexSchema.catch(''),
+ permissions: z.string().catch(''),
+ highlighted: z.boolean().catch(true),
+});
+
+type Role = z.infer;
+
+export {
+ roleSchema,
+ type Role,
+};
diff --git a/packages/pl-api/lib/entities/rule.ts b/packages/pl-api/lib/entities/rule.ts
new file mode 100644
index 000000000..b7393f453
--- /dev/null
+++ b/packages/pl-api/lib/entities/rule.ts
@@ -0,0 +1,17 @@
+import { z } from 'zod';
+
+const baseRuleSchema = z.object({
+ id: z.string(),
+ text: z.string().catch(''),
+ hint: z.string().catch(''),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Rule/} */
+const ruleSchema = z.preprocess((data: any) => ({
+ ...data,
+ hint: data.hint || data.subtext,
+}), baseRuleSchema);
+
+type Rule = z.infer;
+
+export { ruleSchema, type Rule };
diff --git a/packages/pl-api/lib/entities/scheduled-status.ts b/packages/pl-api/lib/entities/scheduled-status.ts
new file mode 100644
index 000000000..c93cdc2b7
--- /dev/null
+++ b/packages/pl-api/lib/entities/scheduled-status.ts
@@ -0,0 +1,38 @@
+import { z } from 'zod';
+
+import { mediaAttachmentSchema } from './media-attachment';
+import { filteredArray } from './utils';
+
+import type { Resolve } from '../utils/types';
+
+/** @see {@link https://docs.joinmastodon.org/entities/ScheduledStatus/} */
+const scheduledStatusSchema = z.object({
+ id: z.string(),
+ scheduled_at: z.string().datetime({ offset: true }),
+ params: z.object({
+ text: z.string().nullable().catch(null),
+ poll: z.object({
+ options: z.array(z.string()),
+ expires_in: z.coerce.string(),
+ multiple: z.boolean().optional().catch(undefined),
+ hide_totals: z.boolean().optional().catch(undefined),
+ }).nullable().catch(null),
+ media_ids: z.array(z.string()).nullable().catch(null),
+ sensitive: z.coerce.boolean().nullable().catch(null),
+ spoiler_text: z.string().nullable().catch(null),
+ visibility: z.string().catch('public'),
+ in_reply_to_id: z.string().nullable().catch(null),
+ language: z.string().nullable().catch(null),
+ application_id: z.number().int().nullable().catch(null),
+ scheduled_at: z.string().datetime({ offset: true }).nullable().catch(null),
+ idempotency: z.string().nullable().catch(null),
+ with_rate_limit: z.boolean().catch(false),
+
+ expires_in: z.number().nullable().catch(null),
+ }),
+ media_attachments: filteredArray(mediaAttachmentSchema),
+});
+
+type ScheduledStatus = Resolve>;
+
+export { scheduledStatusSchema, type ScheduledStatus };
diff --git a/packages/pl-api/lib/entities/search.ts b/packages/pl-api/lib/entities/search.ts
new file mode 100644
index 000000000..bd32ced96
--- /dev/null
+++ b/packages/pl-api/lib/entities/search.ts
@@ -0,0 +1,17 @@
+import { z } from 'zod';
+
+import { filteredArray } from './utils';
+
+import { accountSchema, groupSchema, statusSchema, tagSchema } from '.';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Search} */
+const searchSchema = z.object({
+ accounts: filteredArray(accountSchema),
+ statuses: filteredArray(statusSchema),
+ hashtags: filteredArray(tagSchema),
+ groups: filteredArray(groupSchema),
+});
+
+type Search = z.infer;
+
+export { searchSchema, type Search };
diff --git a/packages/pl-api/lib/entities/status-edit.ts b/packages/pl-api/lib/entities/status-edit.ts
new file mode 100644
index 000000000..595c5b696
--- /dev/null
+++ b/packages/pl-api/lib/entities/status-edit.ts
@@ -0,0 +1,28 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { accountSchema } from './account';
+import { customEmojiSchema } from './custom-emoji';
+import { mediaAttachmentSchema } from './media-attachment';
+import { dateSchema, filteredArray } from './utils';
+
+/** @see {@link https://docs.joinmastodon.org/entities/StatusEdit/} */
+const statusEditSchema = z.object({
+ content: z.string().catch(''),
+ spoiler_text: z.string().catch(''),
+ sensitive: z.coerce.boolean(),
+ created_at: dateSchema,
+ account: accountSchema,
+ poll: z.object({
+ options: z.array(z.object({
+ title: z.string(),
+ })),
+ }).nullable().catch(null),
+ media_attachments: filteredArray(mediaAttachmentSchema),
+ emojis: filteredArray(customEmojiSchema),
+});
+
+type StatusEdit = Resolve>;
+
+export { statusEditSchema, type StatusEdit };
diff --git a/packages/pl-api/lib/entities/status-source.ts b/packages/pl-api/lib/entities/status-source.ts
new file mode 100644
index 000000000..40745db0a
--- /dev/null
+++ b/packages/pl-api/lib/entities/status-source.ts
@@ -0,0 +1,22 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { locationSchema } from './location';
+
+/** @see {@link https://docs.joinmastodon.org/entities/StatusSource/} */
+const statusSourceSchema = z.object({
+ id: z.string(),
+ text: z.string().catch(''),
+ spoiler_text: z.string().catch(''),
+
+ content_type: z.string().catch('text/plain'),
+ location: locationSchema.nullable().catch(null),
+
+ text_map: z.record(z.string()).nullable().catch(null),
+ spoiler_text_map: z.record(z.string()).nullable().catch(null),
+});
+
+type StatusSource = Resolve>;
+
+export { statusSourceSchema, type StatusSource };
diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts
new file mode 100644
index 000000000..68a9e6ebe
--- /dev/null
+++ b/packages/pl-api/lib/entities/status.ts
@@ -0,0 +1,151 @@
+import pick from 'lodash.pick';
+import { z } from 'zod';
+
+import { accountSchema } from './account';
+import { customEmojiSchema } from './custom-emoji';
+import { emojiReactionSchema } from './emoji-reaction';
+import { filterResultSchema } from './filter-result';
+import { groupSchema } from './group';
+import { interactionPolicySchema } from './interaction-policy';
+import { mediaAttachmentSchema } from './media-attachment';
+import { mentionSchema } from './mention';
+import { pollSchema } from './poll';
+import { previewCardSchema } from './preview-card';
+import { tagSchema } from './tag';
+import { translationSchema } from './translation';
+import { dateSchema, filteredArray } from './utils';
+
+import type { Resolve } from '../utils/types';
+
+const statusEventSchema = z.object({
+ name: z.string().catch(''),
+ start_time: z.string().datetime().nullable().catch(null),
+ end_time: z.string().datetime().nullable().catch(null),
+ join_mode: z.enum(['free', 'restricted', 'invite']).nullable().catch(null),
+ participants_count: z.number().catch(0),
+ location: z.object({
+ name: z.string().catch(''),
+ url: z.string().url().catch(''),
+ latitude: z.number().catch(0),
+ longitude: z.number().catch(0),
+ street: z.string().catch(''),
+ postal_code: z.string().catch(''),
+ locality: z.string().catch(''),
+ region: z.string().catch(''),
+ country: z.string().catch(''),
+ }).nullable().catch(null),
+ join_state: z.enum(['pending', 'reject', 'accept']).nullable().catch(null),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Status/} */
+const baseStatusSchema = z.object({
+ id: z.string(),
+ uri: z.string().url().catch(''),
+ created_at: dateSchema,
+ account: accountSchema,
+ content: z.string().catch(''),
+ visibility: z.string().catch('public'),
+ sensitive: z.coerce.boolean(),
+ spoiler_text: z.string().catch(''),
+ media_attachments: filteredArray(mediaAttachmentSchema),
+ application: z.object({
+ name: z.string(),
+ website: z.string().url().nullable().catch(null),
+ }).nullable().catch(null),
+ mentions: filteredArray(mentionSchema),
+ tags: filteredArray(tagSchema),
+ emojis: filteredArray(customEmojiSchema),
+ reblogs_count: z.number().catch(0),
+ favourites_count: z.number().catch(0),
+ replies_count: z.number().catch(0),
+ url: z.string().url().catch(''),
+ in_reply_to_id: z.string().nullable().catch(null),
+ in_reply_to_account_id: z.string().nullable().catch(null),
+ poll: pollSchema.nullable().catch(null),
+ card: previewCardSchema.nullable().catch(null),
+ language: z.string().nullable().catch(null),
+ text: z.string().nullable().catch(null),
+ edited_at: z.string().datetime().nullable().catch(null),
+ favourited: z.coerce.boolean(),
+ reblogged: z.coerce.boolean(),
+ muted: z.coerce.boolean(),
+ bookmarked: z.coerce.boolean(),
+ pinned: z.coerce.boolean(),
+ filtered: filteredArray(filterResultSchema),
+ approval_status: z.enum(['pending', 'approval', 'rejected']).nullable().catch(null),
+ group: groupSchema.nullable().catch(null),
+ scheduled_at: z.null().catch(null),
+
+ local: z.boolean().optional().catch(undefined),
+ conversation_id: z.string().optional().catch(undefined),
+ direct_conversation_id: z.string().optional().catch(undefined),
+ in_reply_to_account_acct: z.string().optional().catch(undefined),
+ expires_at: z.string().datetime({ offset: true }).optional().catch(undefined),
+ thread_muted: z.boolean().optional().catch(undefined),
+ emoji_reactions: filteredArray(emojiReactionSchema),
+ parent_visible: z.boolean().optional().catch(undefined),
+ pinned_at: z.string().datetime({ offset: true }).nullable().catch(null),
+ quote_visible: z.boolean().optional().catch(undefined),
+ quote_url: z.string().optional().catch(undefined),
+ quotes_count: z.number().catch(0),
+ bookmark_folder: z.string().nullable().catch(null),
+
+ event: statusEventSchema.nullable().catch(null),
+ translation: translationSchema.nullable().or(z.literal(false)).catch(null),
+
+ content_map: z.record(z.string()).nullable().catch(null),
+ text_map: z.record(z.string()).nullable().catch(null),
+ spoiler_text_map: z.record(z.string()).nullable().catch(null),
+
+ dislikes_count: z.number().catch(0),
+ disliked: z.coerce.boolean().catch(false),
+
+ interaction_policy: interactionPolicySchema,
+});
+
+const preprocess = (status: any) => {
+ if (!status) return null;
+ status = {
+ ...(pick(status.pleroma || {}, [
+ 'quote',
+ 'local',
+ 'conversation_id',
+ 'direct_conversation_id',
+ 'in_reply_to_account_acct',
+ 'expires_at',
+ 'thread_muted',
+ 'emoji_reactions',
+ 'parent_visible',
+ 'pinned_at',
+ 'quotes_count',
+ 'bookmark_folder',
+
+ 'event',
+ 'translation',
+ ])),
+ ...(pick(status.friendica || {}, [
+ 'dislikes_count',
+ 'disliked',
+ ])),
+ ...status,
+ };
+
+ return status;
+};
+
+const statusSchema = z.preprocess(preprocess, baseStatusSchema.extend({
+ reblog: z.lazy(() => baseStatusSchema).nullable().catch(null),
+
+ quote: z.lazy(() => baseStatusSchema).nullable().catch(null),
+}));
+
+const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({
+ account: accountSchema.nullable().catch(null),
+ reblog: z.lazy(() => baseStatusSchema).nullable().catch(null),
+
+ quote: z.lazy(() => baseStatusSchema).nullable().catch(null),
+}));
+
+type Status = Resolve>;
+
+export { statusSchema, statusWithoutAccountSchema, type Status };
diff --git a/packages/pl-api/lib/entities/streaming-event.ts b/packages/pl-api/lib/entities/streaming-event.ts
new file mode 100644
index 000000000..f89307467
--- /dev/null
+++ b/packages/pl-api/lib/entities/streaming-event.ts
@@ -0,0 +1,117 @@
+import { z } from 'zod';
+
+import { announcementSchema } from './announcement';
+import { announcementReactionSchema } from './announcement-reaction';
+import { chatSchema } from './chat';
+import { conversationSchema } from './conversation';
+import { notificationSchema } from './notification';
+import { statusSchema } from './status';
+
+const followRelationshipUpdateSchema = z.object({
+ state: z.enum(['follow_pending', 'follow_accept', 'follow_reject']),
+ follower: z.object({
+ id: z.string(),
+ follower_count: z.number().nullable().catch(null),
+ following_count: z.number().nullable().catch(null),
+ }),
+ following: z.object({
+ id: z.string(),
+ follower_count: z.number().nullable().catch(null),
+ following_count: z.number().nullable().catch(null),
+ }),
+});
+
+type FollowRelationshipUpdate = z.infer;
+
+const baseStreamingEventSchema = z.object({
+ stream: z.array(z.string()).catch([]),
+});
+
+const statusStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.enum(['update', 'status.update']),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema),
+});
+
+const stringStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.enum(['delete', 'announcement.delete']),
+ payload: z.string(),
+});
+
+const notificationStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('notification'),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema),
+});
+
+const emptyStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('filters_changed'),
+});
+
+const conversationStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('conversation'),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema),
+});
+
+const announcementStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('announcement'),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema),
+});
+
+const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('announcement.reaction'),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema),
+});
+
+const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('chat_update'),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema),
+});
+
+const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('follow_relationships_update'),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema),
+});
+
+const respondStreamingEventSchema = baseStreamingEventSchema.extend({
+ event: z.literal('respond'),
+ payload: z.preprocess((payload: any) => JSON.parse(payload), z.object({
+ type: z.string(),
+ result: z.enum(['success', 'ignored', 'error']),
+ })),
+});
+
+/** @see {@link https://docs.joinmastodon.org/methods/streaming/#events} */
+const streamingEventSchema: z.ZodType = z.preprocess((event: any) => ({
+ ...event,
+ event: event.event?.replace(/^pleroma:/, ''),
+}), z.discriminatedUnion('event', [
+ statusStreamingEventSchema,
+ stringStreamingEventSchema,
+ notificationStreamingEventSchema,
+ emptyStreamingEventSchema,
+ conversationStreamingEventSchema,
+ announcementStreamingEventSchema,
+ announcementReactionStreamingEventSchema,
+ chatUpdateStreamingEventSchema,
+ followRelationshipsUpdateStreamingEventSchema,
+ respondStreamingEventSchema,
+])) as any;
+
+type StreamingEvent = z.infer<
+| typeof statusStreamingEventSchema
+| typeof stringStreamingEventSchema
+| typeof notificationStreamingEventSchema
+| typeof emptyStreamingEventSchema
+| typeof conversationStreamingEventSchema
+| typeof announcementStreamingEventSchema
+| typeof announcementReactionStreamingEventSchema
+| typeof chatUpdateStreamingEventSchema
+| typeof followRelationshipsUpdateStreamingEventSchema
+| typeof respondStreamingEventSchema
+>;
+
+export {
+ followRelationshipUpdateSchema,
+ streamingEventSchema,
+ type FollowRelationshipUpdate,
+ type StreamingEvent,
+};
diff --git a/packages/pl-api/lib/entities/suggestion.ts b/packages/pl-api/lib/entities/suggestion.ts
new file mode 100644
index 000000000..97c365112
--- /dev/null
+++ b/packages/pl-api/lib/entities/suggestion.ts
@@ -0,0 +1,25 @@
+import { z } from 'zod';
+
+import { accountSchema } from './account';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Suggestion} */
+const suggestionSchema = z.preprocess((suggestion: any) => {
+ /**
+ * Support `/api/v1/suggestions`
+ * @see {@link https://docs.joinmastodon.org/methods/suggestions/#v1}
+ */
+ if (suggestion?.acct) return {
+ source: 'staff',
+ sources: 'featured',
+ account: suggestion,
+ };
+ return suggestion;
+}, z.object({
+ source: z.string(),
+ sources: z.array(z.string()),
+ account: accountSchema,
+}));
+
+type Suggestion = z.infer;
+
+export { suggestionSchema, type Suggestion };
diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts
new file mode 100644
index 000000000..941b2cc66
--- /dev/null
+++ b/packages/pl-api/lib/entities/tag.ts
@@ -0,0 +1,19 @@
+import { z } from 'zod';
+
+const historySchema = z.object({
+ day: z.coerce.number(),
+ accounts: z.coerce.number(),
+ uses: z.coerce.number(),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/tag} */
+const tagSchema = z.object({
+ name: z.string().min(1),
+ url: z.string().url().catch(''),
+ history: z.array(historySchema).nullable().catch(null),
+ following: z.boolean().optional().catch(undefined),
+});
+
+type Tag = z.infer;
+
+export { historySchema, tagSchema, type Tag };
diff --git a/packages/pl-api/lib/entities/token.ts b/packages/pl-api/lib/entities/token.ts
new file mode 100644
index 000000000..cd8e50b2c
--- /dev/null
+++ b/packages/pl-api/lib/entities/token.ts
@@ -0,0 +1,18 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/Token/} */
+const tokenSchema = z.object({
+ access_token: z.string(),
+ token_type: z.string(),
+ scope: z.string(),
+ created_at: z.number().optional().catch(undefined),
+
+ id: z.number().optional().catch(undefined),
+ refresh_token: z.string().optional().catch(undefined),
+ expires_in: z.number().optional().catch(undefined),
+ me: z.string().optional().catch(undefined),
+});
+
+type Token = z.infer;
+
+export { tokenSchema, type Token };
diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts
new file mode 100644
index 000000000..8e69b13f6
--- /dev/null
+++ b/packages/pl-api/lib/entities/translation.ts
@@ -0,0 +1,43 @@
+import { z } from 'zod';
+
+import { Resolve } from '../utils/types';
+
+import { filteredArray } from './utils';
+
+const translationPollSchema = z.object({
+ id: z.string(),
+ options: z.array(z.object({
+ title: z.string(),
+ })),
+});
+
+const translationMediaAttachment = z.object({
+ id: z.string(),
+ description: z.string().catch(''),
+});
+
+/** @see {@link https://docs.joinmastodon.org/entities/Translation/} */
+const translationSchema = z.preprocess((translation: any) => {
+ /**
+ * handle Akkoma
+ * @see {@link https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/mastodon_api/controllers/status_controller.ex#L504}
+ */
+ if (translation?.text) return {
+ content: translation.text,
+ detected_source_language: translation.detected_language,
+ provider: '',
+ };
+
+ return translation;
+}, z.object({
+ content: z.string().catch(''),
+ spoiler_text: z.string().catch(''),
+ poll: translationPollSchema.optional().catch(undefined),
+ media_attachments: filteredArray(translationMediaAttachment),
+ detected_source_language: z.string(),
+ provider: z.string(),
+}));
+
+type Translation = Resolve>;
+
+export { translationSchema, type Translation };
diff --git a/packages/pl-api/lib/entities/trends-link.ts b/packages/pl-api/lib/entities/trends-link.ts
new file mode 100644
index 000000000..6c37ad420
--- /dev/null
+++ b/packages/pl-api/lib/entities/trends-link.ts
@@ -0,0 +1,29 @@
+import { z } from 'zod';
+
+import { blurhashSchema } from './media-attachment';
+import { historySchema } from './tag';
+
+/** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */
+const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), z.object({
+ id: z.string().catch(''),
+ url: z.string().url().catch(''),
+ title: z.string().catch(''),
+ description: z.string().catch(''),
+ type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'),
+ author_name: z.string().catch(''),
+ author_url: z.string().catch(''),
+ provider_name: z.string().catch(''),
+ provider_url: z.string().catch(''),
+ html: z.string().catch(''),
+ width: z.number().nullable().catch(null),
+ height: z.number().nullable().catch(null),
+ image: z.string().nullable().catch(null),
+ image_description: z.string().nullable().catch(null),
+ embed_url: z.string().catch(''),
+ blurhash: blurhashSchema.nullable().catch(null),
+ history: z.array(historySchema).nullable().catch(null),
+}));
+
+type TrendsLink = z.infer;
+
+export { trendsLinkSchema, type TrendsLink };
diff --git a/packages/pl-api/lib/entities/utils.ts b/packages/pl-api/lib/entities/utils.ts
new file mode 100644
index 000000000..d6a624b4f
--- /dev/null
+++ b/packages/pl-api/lib/entities/utils.ts
@@ -0,0 +1,26 @@
+import z from 'zod';
+
+/** Validate to Mastodon's date format, or use the current date. */
+const dateSchema = z.string().datetime({ offset: true }).catch(new Date().toUTCString());
+
+/** Validates individual items in an array, dropping any that aren't valid. */
+const filteredArray = (schema: T) =>
+ z.any().array().catch([])
+ .transform((arr) => (
+ arr.map((item) => {
+ const parsed = schema.safeParse(item);
+ return parsed.success ? parsed.data : undefined;
+ }).filter((item): item is z.infer => Boolean(item))
+ ));
+
+/** Validates the string as an emoji. */
+const emojiSchema = z.string().refine((v) => /\p{Extended_Pictographic}|[\u{1F1E6}-\u{1F1FF}]{2}/u.test(v));
+
+/** MIME schema, eg `image/png`. */
+const mimeSchema = z.string().regex(/^\w+\/[-+.\w]+$/);
+
+/** zod schema to force the value into an object, if it isn't already. */
+const coerceObject = (shape: T) =>
+ z.object({}).passthrough().catch({}).pipe(z.object(shape));
+
+export { filteredArray, emojiSchema, dateSchema, mimeSchema, coerceObject };
diff --git a/packages/pl-api/lib/entities/web-push-subscription.ts b/packages/pl-api/lib/entities/web-push-subscription.ts
new file mode 100644
index 000000000..482622e5b
--- /dev/null
+++ b/packages/pl-api/lib/entities/web-push-subscription.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+/** @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/} */
+const webPushSubscriptionSchema = z.object({
+ id: z.coerce.string(),
+ endpoint: z.string(),
+ alerts: z.record(z.boolean()),
+ server_key: z.string(),
+});
+
+type WebPushSubscription = z.infer;
+
+export { webPushSubscriptionSchema, type WebPushSubscription };
diff --git a/packages/pl-api/lib/features.ts b/packages/pl-api/lib/features.ts
new file mode 100644
index 000000000..3178dfb08
--- /dev/null
+++ b/packages/pl-api/lib/features.ts
@@ -0,0 +1,1188 @@
+import semverCoerce from 'semver/functions/coerce';
+import gte from 'semver/functions/gte';
+import semverParse from 'semver/functions/parse';
+
+import type { Instance } from './entities';
+
+/** Truthy array convenience function */
+const any = (arr: Array): boolean => arr.some(Boolean);
+
+/**
+ * Ditto, a Nostr server with Mastodon API.
+ * @see {@link https://gitlab.com/soapbox-pub/ditto}
+ */
+const DITTO = 'Ditto';
+
+/**
+ * Firefish, a fork of Misskey. Formerly known as Calckey.
+ * @see {@link https://joinfirefish.org/}
+ */
+const FIREFISH = 'Firefish';
+
+/**
+ * Friendica, decentralized social platform implementing multiple federation protocols.
+ * @see {@link https://friendi.ca/}
+ */
+const FRIENDICA = 'Friendica';
+
+/**
+ * GoToSocial, an ActivityPub server writter in Golang.
+ * @see {@link https://gotosocial.org/}
+ */
+const GOTOSOCIAL = 'GoToSocial';
+
+/**
+ * Iceshrimp, yet another Misskey fork.
+ * @see {@link https://iceshrimp.dev/}
+ */
+const ICESHRIMP = 'Iceshrimp';
+
+/**
+ * Mastodon, the software upon which this is all based.
+ * @see {@link https://joinmastodon.org/}
+ */
+const MASTODON = 'Mastodon';
+
+/**
+ * Mitra, a Rust backend with cryptocurrency integrations.
+ * @see {@link https://codeberg.org/silverpill/mitra}
+ */
+const MITRA = 'Mitra';
+
+/**
+ * Pixelfed, a federated image sharing platform.
+ * @see {@link https://pixelfed.org/}
+ */
+const PIXELFED = 'Pixelfed';
+
+/**
+ * Pleroma, a feature-rich alternative written in Elixir.
+ * @see {@link https://pleroma.social/}
+ */
+const PLEROMA = 'Pleroma';
+
+/**
+ * Takahē, backend with support for serving multiple domains.
+ * @see {@link https://jointakahe.org/}
+ */
+const TAKAHE = 'Takahe';
+
+/**
+ * Toki, a C# Fediverse server.
+ * @see {@link https://github.com/purifetchi/Toki}
+ */
+const TOKI = 'Toki';
+
+/**
+ * Akkoma, a Pleroma fork.
+ * @see {@link https://akkoma.dev/AkkomaGang/akkoma}
+ */
+const AKKOMA = 'akkoma';
+
+/**
+ * glitch-soc, fork of Mastodon with a number of experimental features.
+ * @see {@link https://glitch-soc.github.io/docs/}
+ */
+const GLITCH = 'glitch';
+
+/**
+ * Rebased, the recommended backend for Soapbox.
+ * @see {@link https://gitlab.com/soapbox-pub/rebased}
+ */
+// NOTE: Rebased is named 'soapbox' for legacy reasons.
+const REBASED = 'soapbox';
+
+/**
+ * Pl, fork of Pleroma developed by pl-api author.
+ * @see {@link https://github.com/mkljczk/pl}
+ */
+const PL = 'pl';
+
+/** Backend name reserved only for tests. */
+const UNRELEASED = 'unreleased';
+
+/** Parse features for the given instance */
+const getFeatures = (instance?: Instance) => {
+ const v = parseVersion(instance?.version || '');
+ const features = instance?.pleroma.metadata.features || [];
+ const federation = !!instance?.pleroma.metadata.federation.enabled;
+
+ return {
+ version: v,
+
+ /**
+ * Ability to set description of profile avatar and header.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ accountAvatarDescription: v.software === GOTOSOCIAL && gte(v.version, '0.16.1'),
+
+ /**
+ * Pleroma backups.
+ * @see GET /api/v1/pleroma/backups
+ * @see POST /api/v1/pleroma/backups
+ */
+ accountBackups: v.software === PLEROMA,
+
+ /**
+ * The accounts API allows an acct instead of an ID.
+ * @see GET /api/v1/accounts/:acct_or_id
+ */
+ accountByUsername: v.software === PLEROMA,
+
+ /**
+ * Ability to create accounts.
+ * @see POST /api/v1/accounts
+ */
+ accountCreation: true,
+
+ /**
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ accountEnableRss: v.software === GOTOSOCIAL,
+
+ /**
+ * Ability to pin other accounts on one's profile.
+ * @see POST /api/v1/accounts/:id/pin
+ * @see POST /api/v1/accounts/:id/unpin
+ * @see GET /api/v1/pleroma/accounts/:id/endorsements
+ */
+ accountEndorsements: v.software === PLEROMA && gte(v.version, '2.5.0'),
+
+ /**
+ * Ability to set one's location on their profile.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ accountLocation: any([
+ v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.5.0'),
+ v.software === PLEROMA && v.build === PL,
+ ]),
+
+ /**
+ * Look up an account by the acct.
+ * @see GET /api/v1/accounts/lookup
+ */
+ accountLookup: any([
+ v.software === DITTO,
+ v.software === FIREFISH,
+ v.software === GOTOSOCIAL,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA && gte(v.version, '2.5.0'),
+ v.software === TAKAHE && gte(v.version, '0.6.1'),
+ v.software === TOKI,
+ ]),
+
+ /**
+ * Move followers to a different ActivityPub account.
+ * @see POST /api/pleroma/move_account
+ */
+ accountMoving: v.software === PLEROMA && gte(v.version, '2.5.0'),
+
+ /**
+ * Ability to subscribe to notifications every time an account posts.
+ * @see POST /api/v1/accounts/:id/follow
+ */
+ accountNotifies: any([
+ v.software === MASTODON,
+ v.software === PLEROMA && gte(v.version, '2.5.0'),
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * Ability to manage announcements by admins.
+ * @see GET /api/v1/pleroma/admin/announcements
+ * @see GET /api/v1/pleroma/admin/announcements/:id
+ * @see POST /api/v1/pleroma/admin/announcements
+ * @see PATCH /api/v1/pleroma/admin/announcements/:id
+ * @see DELETE /api/v1/pleroma/admin/announcements/:id
+ * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminannouncements}
+ */
+ adminAnnouncements: v.software === PLEROMA,
+
+ /**
+ * Ability to manage instance rules by admins.
+ * @see GET /api/v1/pleroma/admin/rules
+ * @see POST /api/v1/pleroma/admin/rules
+ * @see PATCH /api/v1/pleroma/admin/rules/:id
+ * @see DELETE /api/v1/pleroma/admin/rules/:id
+ */
+ adminRules: any([
+ v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.5.0'),
+ v.software === PLEROMA && gte(v.version, '2.7.0'),
+ ]),
+
+ /**
+ * Ability to address a status to a list of users.
+ * @see POST /api/v1/statuses
+ */
+ addressableLists: v.software === PLEROMA && gte(v.version, '1.0.2'),
+
+ /**
+ * Can display announcements set by admins.
+ * @see GET /api/v1/announcements
+ * @see POST /api/v1/announcements/:id/dismiss
+ * @see {@link https://docs.joinmastodon.org/methods/announcements/}
+ */
+ announcements: any([
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === TAKAHE && gte(v.version, '0.7.0'),
+ ]),
+
+ /**
+ * Can emoji react to announcements set by admins.
+ * @see PUT /api/v1/announcements/:id/reactions/:name
+ * @see DELETE /api/v1/announcements/:id/reactions/:name
+ * @see {@link https://docs.joinmastodon.org/methods/announcements/}
+ */
+ announcementsReactions: v.software === MASTODON,
+
+ /**
+ * Set your birthday and view upcoming birthdays.
+ * @see GET /api/v1/pleroma/birthdays
+ * @see POST /api/v1/accounts
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ birthdays: v.software === PLEROMA,
+
+ /**
+ * Allow to bite users.
+ * see POST /api/v1/bite
+ */
+ bites: any([
+ v.software === TOKI,
+ features.includes('pleroma:bites'),
+ ]),
+
+ /** Whether people who blocked you are visible through the API. */
+ blockersVisible: features.includes('blockers_visible'),
+
+ /**
+ * Can group bookmarks in folders.
+ * @see GET /api/v1/pleroma/bookmark_folders
+ * @see POST /api/v1/pleroma/bookmark_folders
+ * @see PATCH /api/v1/pleroma/bookmark_folders/:id
+ * @see DELETE /api/v1/pleroma/bookmark_folders/:id
+ */
+ bookmarkFolders: features.includes('pleroma:bookmark_folders'),
+
+ /**
+ * Can bookmark statuses.
+ * @see POST /api/v1/statuses/:id/bookmark
+ * @see GET /api/v1/bookmarks
+ */
+ bookmarks: any([
+ v.software === DITTO,
+ v.software === FIREFISH,
+ v.software === GOTOSOCIAL,
+ v.software === ICESHRIMP,
+ v.software === FRIENDICA,
+ v.software === MASTODON,
+ v.software === PIXELFED,
+ v.software === PLEROMA,
+ v.software === TAKAHE && gte(v.version, '0.9.0'),
+ v.software === TOKI,
+ ]),
+
+ /**
+ * Accounts can be marked as bots.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ bots: any([
+ v.software === GOTOSOCIAL,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Can display a timeline of statuses from instances selected by instance admin.
+ * @see GET /api/v1/timelines/bubble
+ */
+ bubbleTimeline: features.includes('bubble_timeline'),
+
+ /**
+ * Pleroma chats API.
+ * @see {@link https://docs.pleroma.social/backend/development/API/chats/}
+ */
+ chats: features.includes('pleroma_chat_messages'),
+
+ /**
+ * Ability to delete a chat.
+ * @see DELETE /api/v1/pleroma/chats/:id
+ */
+ chatsDelete: any([
+ v.build === REBASED,
+ v.software === PLEROMA && v.build === PL,
+ ]),
+
+ /**
+ * Mastodon's newer solution for direct messaging.
+ * @see {@link https://docs.joinmastodon.org/methods/conversations/}
+ */
+ conversations: any([
+ v.software === FIREFISH,
+ v.software === FRIENDICA,
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.1'),
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PIXELFED,
+ v.software === PLEROMA,
+ v.software === TAKAHE,
+ ]),
+
+ /**
+ * @see GET /api/v1/conversations
+ */
+ conversationsByRecipients: v.software === PLEROMA,
+
+ /**
+ * @see POST /api/v1/statuses
+ */
+ createStatusExpiration: v.software === PLEROMA,
+
+ /**
+ * Ability to address recipients of a status explicitly (with `to`).
+ * @see POST /api/v1/statuses
+ */
+ createStatusExplicitAddressing: any([
+ v.software === DITTO,
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * @see POST /api/v1/statuses
+ */
+ createStatusReplyToConversation: v.software === PLEROMA,
+
+ /**
+ * @see POST /api/v1/statuses
+ */
+ createStatusListScope: v.software === PLEROMA,
+
+ /**
+ * @see POST /api/v1/statuses
+ */
+ createStatusLocalScope: v.software === PLEROMA,
+
+ /**
+ * @see POST /api/v1/statuses
+ */
+ createStatusPreview: v.software === PLEROMA,
+
+ /**
+ * Ability to add non-standard reactions to a status.
+ */
+ customEmojiReacts: any([
+ features.includes('pleroma_custom_emoji_reactions'),
+ features.includes('custom_emoji_reactions'),
+ v.software === PLEROMA && gte(v.version, '2.6.0'),
+ ]),
+
+ /**
+ * @see POST /api/v1/accounts/delete
+ * @see POST /api/pleroma/delete_account
+ */
+ deleteAccount: any([
+ v.software === GOTOSOCIAL,
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Allow to register on a given domain
+ * @see GET /api/v1/pleroma/admin/domains
+ * @see POST /api/v1/pleroma/admin/domains
+ * @see PATCH /api/v1/pleroma/admin/domains/:id
+ * @see DELETE /api/v1/pleroma/admin/domains/:id
+ */
+ domains: any([instance?.pleroma.metadata.multitenancy.enabled]),
+
+ /**
+ * Ability to edit profile information.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ editProfile: any([
+ v.software === FIREFISH,
+ v.software === FRIENDICA,
+ v.software === GOTOSOCIAL,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === MITRA,
+ v.software === PIXELFED,
+ v.software === PLEROMA,
+ v.software === TAKAHE && gte(v.version, '0.7.0'),
+ v.software === TOKI,
+ ]),
+
+ /**
+ * Ability to edit published posts.
+ * @see PUT /api/v1/statuses/:id
+ */
+ editStatuses: any([
+ v.software === FRIENDICA && gte(v.version, '2022.12.0'),
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === MITRA,
+ v.software === TAKAHE && gte(v.version, '0.8.0'),
+ features.includes('editing'),
+ ]),
+
+ /**
+ * Soapbox email list.
+ * @see POST /api/v1/accounts
+ * @see PATCH /api/v1/accounts/update_credentials
+ * @see GET /api/v1/pleroma/admin/email_list/subscribers.csv
+ * @see GET /api/v1/pleroma/admin/email_list/unsubscribers.csv
+ * @see GET /api/v1/pleroma/admin/email_list/combined.csv
+ */
+ emailList: features.includes('email_list'),
+
+ /**
+ * Ability to embed posts on external sites.
+ * @see GET /api/oembed
+ */
+ embeds: v.software === MASTODON,
+
+ /**
+ * Ability to add emoji reactions to a status.
+ * @see PUT /api/v1/pleroma/statuses/:id/reactions/:emoji
+ * @see GET /api/v1/pleroma/statuses/:id/reactions/:emoji?
+ * @see DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji
+ */
+ emojiReacts: v.software === PLEROMA,
+
+ /**
+ * Ability to add emoji reactions to a status available in Mastodon forks.
+ * @see POST /v1/statuses/:id/react/:emoji
+ * @see POST /v1/statuses/:id/unreact/:emoji
+ */
+ emojiReactsMastodon: instance ? instance.configuration.reactions.max_reactions > 0 : false,
+
+ /**
+ * Ability to create and perform actions on events.
+ * @see POST /api/v1/pleroma/events
+ * @see GET /api/v1/pleroma/events/joined_events
+ * @see PUT /api/v1/pleroma/events/:id
+ * @see GET /api/v1/pleroma/events/:id/participations
+ * @see GET /api/v1/pleroma/events/:id/participation_requests
+ * @see POST /api/v1/pleroma/events/:id/participation_requests/:participant_id/authorize
+ * @see POST /api/v1/pleroma/events/:id/participation_requests/:participant_id/reject
+ * @see POST /api/v1/pleroma/events/:id/join
+ * @see POST /api/v1/pleroma/events/:id/leave
+ * @see GET /api/v1/pleroma/events/:id/ics
+ * @see GET /api/v1/pleroma/search/location
+ */
+ events: features.includes('events'),
+
+ /** Whether to allow exporting follows/blocks/mutes to CSV by paginating the API. */
+ exportData: true,
+
+ /** Whether the accounts who favourited or emoji-reacted to a status can be viewed through the API. */
+ exposableReactions: any([
+ v.software === FIREFISH,
+ v.software === FRIENDICA,
+ v.software === GOTOSOCIAL,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === TAKAHE && gte(v.version, '0.6.1'),
+ v.software === TOKI,
+ features.includes('exposable_reactions'),
+ ]),
+
+ /**
+ * Can see accounts' followers you know
+ * @see GET /api/v1/accounts/familiar_followers
+ */
+ familiarFollowers: any([
+ v.software === DITTO,
+ v.software === MASTODON,
+ v.software === PLEROMA && gte(v.version, '2.6.0') && v.build === REBASED,
+ v.software === PLEROMA && gte(v.version, '2.7.0'),
+ v.software === TAKAHE,
+ ]),
+
+ /** Whether the instance federates. */
+ federating: federation,
+
+ /**
+ * Can edit and manage timeline filters (aka "muted words").
+ * @see {@link https://docs.joinmastodon.org/methods/filters/#v1}
+ */
+ filters: any([
+ v.software === GOTOSOCIAL,
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Can edit and manage timeline filters (aka "muted words").
+ * @see {@link https://docs.joinmastodon.org/methods/filters/}
+ */
+ filtersV2: any([
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.0'),
+ v.software === MASTODON,
+ ]),
+
+ /**
+ * Allows setting the focal point of a media attachment.
+ * @see {@link https://docs.joinmastodon.org/methods/media/}
+ */
+ focalPoint: any([
+ v.software === GOTOSOCIAL,
+ v.software === MASTODON,
+ ]),
+
+ /**
+ * Ability to follow hashtags.
+ * @see POST /api/v1/tags/:name/follow
+ * @see POST /api/v1/tags/:name/unfollow
+ */
+ followHashtags: any([
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.1'),
+ v.software === MASTODON && gte(v.compatVersion, '4.0.0'),
+ v.software === PLEROMA && v.build === AKKOMA,
+ v.software === TAKAHE && gte(v.version, '0.9.0'),
+ ]),
+
+ /**
+ * Ability to lock accounts and manually approve followers.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ followRequests: any([
+ v.software === GOTOSOCIAL,
+ v.software === MASTODON,
+ v.software === MITRA,
+ v.software === PLEROMA,
+ v.software === TOKI,
+ ]),
+
+ /**
+ * Ability to list followed hashtags.
+ * @see GET /api/v1/followed_tags
+ */
+ followedHashtagsList: v.software === MASTODON && gte(v.compatVersion, '4.1.0'),
+
+ /**
+ * Whether client settings can be retrieved from the API.
+ * @see GET /api/pleroma/frontend_configurations
+ */
+ frontendConfigurations: any([
+ v.software === DITTO,
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Groups.
+ * @see POST /api/v1/groups
+ * @see GET /api/v1/groups
+ * @see GET /api/v1/groups/:id
+ * @see POST /api/v1/groups/:id/join
+ * @see POST /api/v1/groups/:id/leave
+ * @see GET /api/v1/groups/:id/memberships
+ * @see PUT /api/v1/groups/:group_id
+ * @see DELETE /api/v1/groups/:group_id
+ * @see GET /api/v1/groups/:group_id/membership_requests
+ * @see POST /api/v1/groups/:group_id/membership_requests/:account_id/authorize
+ * @see POST /api/v1/groups/:group_id/membership_requests/:account_id/reject
+ * @see DELETE /api/v1/groups/:group_id/statuses/:id
+ * @see POST /api/v1/groups/:group_id/kick?account_ids[]=…
+ * @see GET /api/v1/groups/:group_id/blocks
+ * @see POST /api/v1/groups/:group_id/blocks?account_ids[]=…
+ * @see DELETE /api/v1/groups/:group_id/blocks?account_ids[]=…
+ * @see POST /api/v1/groups/:group_id/promote?role=new_role&account_ids[]=…
+ * @see POST /api/v1/groups/:group_id/demote?role=new_role&account_ids[]=…
+ * @see GET /api/v1/admin/groups
+ * @see GET /api/v1/admin/groups/:group_id
+ * @see POST /api/v1/admin/groups/:group_id/suspend
+ * @see POST /api/v1/admin/groups/:group_id/unsuspend
+ * @see DELETE /api/v1/admin/groups/:group_id
+ */
+ groups: features.includes('pleroma:groups'),
+
+ /**
+ * Can hide follows/followers lists and counts.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ hideNetwork: any([
+ v.software === GOTOSOCIAL && gte(v.version, '0.15.0'),
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Import a .csv file with a list of blocked users.
+ * @see POST /api/pleroma/blocks_import
+ * @see POST /api/v1/import
+ */
+ importBlocks: any([
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.1'),
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Import a .csv file with a list of followed users.
+ * @see POST /api/pleroma/follow_import
+ * @see POST /api/v1/import
+
+ */
+ importFollows: any([
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.1'),
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Import a .csv file with a list of muted users.
+ * @see POST /api/pleroma/mutes_import
+ */
+ importMutes: v.software === PLEROMA,
+
+ /**
+ * Allow to specify mode of data import to either `merge` or `overwrite`.
+ * @see POST /api/v1/import
+ */
+ importOverwrite: v.software === GOTOSOCIAL && gte(v.version, '0.16.1'),
+
+ /**
+ * View posts from specific instance.
+ * @see GET /api/v1/timelines/public
+ */
+ instanceTimeline: v.software === PLEROMA,
+
+ /**
+ * Mastodon server information API v2.
+ * @see GET /api/v2/instance
+ * @see {@link https://docs.joinmastodon.org/methods/instance/#v2}
+ */
+ instanceV2: any([
+ v.software === GOTOSOCIAL,
+ v.software === MASTODON && gte(v.compatVersion, '4.0.0'),
+ v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.6.0'),
+ v.software === PLEROMA && gte(v.version, '2.7.0'),
+ ]),
+
+ interactionRequests: v.software === GOTOSOCIAL && gte(v.version, '0.16.1'),
+
+ /**
+ * Server-side status language detection.
+ */
+ languageDetection: features.includes('pleroma:language_detection'),
+
+ /**
+ * Can translate multiple statuses in a single request.
+ * @see POST /api/v1/pl/statuses/translate
+ */
+ lazyTranslations: features.includes('pl:translations'),
+
+ /**
+ * Can create, view, and manage lists.
+ * @see {@link https://docs.joinmastodon.org/methods/lists/}
+ * @see GET /api/v1/timelines/list/:list_id
+ */
+ lists: any([
+ v.software === FIREFISH,
+ v.software === FRIENDICA,
+ v.software === GOTOSOCIAL,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Ability to post statuses that don't federate.
+ * @see POST /api/v1/statuses
+ */
+ localOnlyStatuses: federation && v.software === GOTOSOCIAL,
+
+ /**
+ * Can sign in using username instead of e-mail address.
+ */
+ logInWithUsername: any([
+ v.software === PLEROMA,
+ v.software === TOKI,
+ ]),
+
+ /**
+ * Can view and manage ActivityPub aliases through the API.
+ * @see GET /api/pleroma/aliases
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ manageAccountAliases: v.software === PLEROMA,
+
+ /**
+ * @see GET /api/pleroma/accounts/mfa
+ * @see GET /api/pleroma/accounts/mfa/backup_codes
+ * @see GET /api/pleroma/accounts/mfa/setup/:method
+ * @see POST /api/pleroma/accounts/mfa/confirm/:method
+ * @see DELETE /api/pleroma/accounts/mfa/:method
+ */
+ manageMfa: v.software === PLEROMA,
+
+ /**
+ * Can perform moderation actions with account and reports.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/}
+ * @see GET /api/v1/admin/reports
+ * @see POST /api/v1/admin/reports/:report_id/resolve
+ * @see POST /api/v1/admin/reports/:report_id/reopen
+ * @see POST /api/v1/admin/accounts/:account_id/action
+ * @see POST /api/v1/admin/accounts/:account_id/approve
+ */
+ mastodonAdmin: any([
+ v.software === DITTO,
+ v.software === GOTOSOCIAL,
+ v.software === MASTODON,
+ v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.5.0'),
+ v.software === PLEROMA && v.build === PL,
+ ]),
+
+ /**
+ * Can perform moderation actions with account and reports.
+ * @see {@link https://docs.joinmastodon.org/methods/admin/}
+ * @see GET /api/v2/admin/accounts
+ */
+ mastodonAdminV2: any([
+ v.software === MASTODON && gte(v.version, '3.5.0'),
+ ]),
+
+ /**
+ * Supports V2 media uploads.
+ * @see POST /api/v2/media
+ */
+ mediaV2: any([
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === MITRA,
+ v.software === PLEROMA,
+ v.software === TAKAHE,
+ v.software === TOKI,
+ ]),
+
+ /**
+ * Ability to include multiple language variants for a post.
+ * @see POST /api/v1/statuses
+ */
+ multiLanguage: features.includes('pleroma:multi_language'),
+
+ /**
+ * Ability to hide notifications from people you don't follow.
+ * @see PUT /api/pleroma/notification_settings
+ */
+ muteStrangers: v.software === PLEROMA,
+
+ /**
+ * Ability to mute users.
+ * @see GET /api/v1/mutes
+ * @see POST /api/v1/accounts/:id/mute
+ * @see POST /api/v1/accounts/:id/unmute
+ */
+ mutes: any([
+ v.software === FIREFISH,
+ v.software === FRIENDICA,
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.0'),
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === MITRA,
+ v.software === PIXELFED,
+ v.software === PLEROMA,
+ v.software === TAKAHE,
+ ]),
+
+ /**
+ * Ability to specify how long the account mute should last.
+ * @see PUT /api/v1/accounts/:id/mute
+ */
+ mutesDuration: any([
+ v.software === FIREFISH,
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.0'),
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === TAKAHE,
+ ]),
+
+ /**
+ * Add private notes to accounts.
+ * @see POST /api/v1/accounts/:id/note
+ * @see GET /api/v1/accounts/relationships
+ */
+ notes: any([
+ v.software === MASTODON,
+ v.software === PLEROMA && gte(v.version, '2.5.0'),
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * @see DELETE /api/v1/notifications/destroy_multiple
+ */
+ notificationsDismissMultiple: v.software === PLEROMA,
+
+ /**
+ * @see GET /api/v1/notifications
+ */
+ notificationsExcludeVisibilities: v.software === PLEROMA,
+
+ /**
+ * Allows specifying notification types to include, rather than to exclude.
+ * @see GET /api/v1/notifications
+ */
+ notificationsIncludeTypes: any([
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA && gte(v.version, '2.5.0'),
+ v.software === TAKAHE && gte(v.version, '0.6.2'),
+ v.software === GOTOSOCIAL,
+ ]),
+
+ pleromaAdminAccoumts: v.software === PLEROMA,
+
+ pleromaAdminStatuses: v.software === PLEROMA,
+
+ /**
+ * Displays a form to follow a user when logged out.
+ * @see POST /main/ostatus
+ */
+ pleromaRemoteFollow: v.software === PLEROMA,
+
+ /**
+ * Can add polls to statuses.
+ * @see POST /api/v1/statuses
+ */
+ polls: any([
+ v.software === FIREFISH,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === TAKAHE && gte(v.version, '0.8.0'),
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * Can select a language for statuses.
+ * @see POST /api/v1/statuses
+ */
+ postLanguages: any([
+ v.software === MASTODON,
+ v.software === PLEROMA && v.build === AKKOMA,
+ v.software === PLEROMA && v.build === PL,
+ v.software === PLEROMA && v.build === REBASED,
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * Can set privacy scopes on statuses.
+ * @see POST /api/v1/statuses
+ */
+ privacyScopes: true,
+
+ /**
+ * A directory of discoverable profiles from the instance.
+ * @see {@link https://docs.joinmastodon.org/methods/directory/}
+ */
+ profileDirectory: any([
+ v.software === FRIENDICA,
+ v.software === MASTODON,
+ v.software === MITRA,
+ features.includes('profile_directory'),
+ ]),
+
+ /**
+ * Ability to set custom profile fields.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ profileFields: any([
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === TAKAHE && gte(v.version, '0.7.0'),
+ v.software === MITRA,
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * Returns favorites timeline of any user
+ * @see GET /api/v1/pleroma/accounts/:id/favourites
+ */
+ publicFavourites: v.software === PLEROMA,
+
+ /**
+ * Can display a timeline of all known public statuses.
+ * Local and Fediverse timelines both use this feature.
+ * @see GET /api/v1/timelines/public
+ */
+ publicTimeline: any([
+ v.software === DITTO,
+ v.software === FIREFISH,
+ v.software === FRIENDICA,
+ v.software === GOTOSOCIAL,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === TAKAHE,
+ v.software === TOKI,
+ ]),
+
+ /**
+ * Ability to quote posts in statuses.
+ * @see POST /api/v1/statuses
+ */
+ quotePosts: any([
+ v.software === FRIENDICA && gte(v.version, '2023.3.0'),
+ v.software === PLEROMA && [REBASED, AKKOMA].includes(v.build!) && gte(v.version, '2.5.0'),
+ features.includes('quote_posting'),
+ instance?.feature_quote === true,
+ ]),
+
+ /**
+ * Interact with statuses from another instance while logged-out.
+ * @see POST /api/v1/pleroma/remote_interaction
+ */
+ remoteInteractions: v.software === PLEROMA && gte(v.version, '2.5.0'),
+
+ /**
+ * Ability to remove an account from your followers.
+ * @see POST /api/v1/accounts/:id/remove_from_followers
+ */
+ removeFromFollowers: any([
+ v.software === MASTODON,
+ v.software === PLEROMA && gte(v.version, '2.5.0'),
+ v.software === PLEROMA && v.build === AKKOMA,
+ ]),
+
+ /**
+ * Can request a password reset email through the API.
+ * @see POST /auth/password
+ */
+ resetPassword: v.software === PLEROMA,
+
+ /**
+ * Ability to post statuses in Markdown, BBCode, and HTML.
+ * @see POST /api/v1/statuses
+ */
+ richText: any([
+ v.software === MASTODON && v.build === GLITCH,
+ v.software === PLEROMA,
+ v.software === MITRA,
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * Ability to follow account feeds using RSS.
+ */
+ rssFeeds: any([
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * Can schedule statuses to be posted at a later time.
+ * @see POST /api/v1/statuses
+ * @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/}
+ */
+ scheduledStatuses: any([
+ v.software === FRIENDICA,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * Ability to search statuses from the given account.
+ * @see {@link https://docs.joinmastodon.org/methods/search/}
+ * @see POST /api/v2/search
+ */
+ searchFromAccount: any([
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ v.software === PLEROMA,
+ v.software === GOTOSOCIAL,
+ ]),
+
+ /**
+ * @see POST /api/v1/user/email_change
+ */
+ changeEmail: any([
+ v.software === GOTOSOCIAL && gte(v.version, '0.16.0'),
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * @see POST /api/v1/user/password_change
+ * @see POST /api/v1/settings/change_password
+ * @see POST /api/pleroma/change_password
+ */
+ changePassword: any([
+ v.software === GOTOSOCIAL,
+ v.software === MITRA,
+ v.software === PLEROMA,
+ ]),
+
+ /**
+ * Ability to manage account sessions.
+ * @see GET /api/oauth_tokens.json
+ * @see DELETE /api/oauth_tokens/:id
+ */
+ sessions: v.software === PLEROMA,
+
+ /**
+ * Can store client settings in the database.
+ * @see PATCH /api/v1/accounts/update_credentials
+ */
+ settingsStore: v.software === PLEROMA,
+
+ /**
+ * Can set content warnings on statuses.
+ * @see POST /api/v1/statuses
+ */
+ spoilers: true,
+
+ /**
+ * @see POST /api/friendica/statuses/:id/dislike
+ * @see POST /api/friendica/statuses/:id/undislike
+ * @see GET /api/friendica/statuses/:id/disliked_by
+ */
+ statusDislikes: v.software === FRIENDICA && gte(v.version, '2023.3.0'),
+
+ /**
+ * Can display suggested accounts.
+ * @see {@link https://docs.joinmastodon.org/methods/suggestions/}
+ */
+ suggestions: any([
+ v.software === FRIENDICA,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ features.includes('v2_suggestions'),
+ ]),
+
+ /**
+ * Supports V2 suggested accounts.
+ * @see GET /api/v2/suggestions
+ */
+ suggestionsV2: any([
+ v.software === FRIENDICA,
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ features.includes('v2_suggestions'),
+ ]),
+
+ /**
+ * Can translate statuses.
+ * @see POST /api/v1/statuses/:id/translate
+ */
+ translations: any([
+ features.includes('translation'),
+ features.includes('akkoma:machine_translation'),
+ instance?.configuration.translation.enabled,
+ ]),
+
+ /**
+ * Trending links.
+ * @see GET /api/v1/trends/links
+ */
+ trendingLinks: v.software === MASTODON && gte(v.compatVersion, '3.5.0'),
+
+ /**
+ * Trending statuses.
+ * @see GET /api/v1/trends/statuses
+ */
+ trendingStatuses: any([
+ v.software === DITTO,
+ v.software === FRIENDICA && gte(v.version, '2022.12.0'),
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ ]),
+
+ /**
+ * Can display trending hashtags.
+ * @see GET /api/v1/trends
+ */
+ trends: any([
+ v.software === DITTO,
+ v.software === FRIENDICA && gte(v.version, '2022.12.0'),
+ v.software === ICESHRIMP,
+ v.software === MASTODON,
+ ]),
+
+ /**
+ * Whether the backend allows adding users you don't follow to lists.
+ * @see POST /api/v1/lists/:id/accounts
+ */
+ unrestrictedLists: v.software === PLEROMA,
+
+ /**
+ * Ability to post statuses only to accounts with mutual relationship.
+ * @see POST /api/v1/statuses
+ */
+ visibilityMutualsOnly: v.software === GOTOSOCIAL,
+
+ /**
+ * Ability to post statuses that don't federate.
+ * @see POST /api/v1/statuses
+ */
+ visibilityLocalOnly: federation && any([
+ v.software === PLEROMA,
+ ]),
+ };
+};
+
+/** Features available from a backend */
+type Features = ReturnType;
+
+/** Fediverse backend */
+interface Backend {
+ /** Build name, if this software is a fork */
+ build: string | null;
+ /** Name of the software */
+ software: string | null;
+ /** API version number */
+ version: string;
+ /** Mastodon API version this backend is compatible with */
+ compatVersion: string;
+}
+
+/** Get information about the software from its version string */
+const parseVersion = (version: string): Backend => {
+ const regex = /^([\w+.-]*)(?: \(compatible; ([\w]*) (.*)\))?$/;
+ const match = regex.exec(version.replace('/', ' '));
+
+ const semverString = match && (match[3] || match[1]);
+ const semver = match ? semverParse(semverString) || semverCoerce(semverString, {
+ loose: true,
+ }) : null;
+ const compat = match ? semverParse(match[1]) || semverCoerce(match[1]) : null;
+ if (match && semver && compat) {
+ return {
+ build: semver.build[0],
+ compatVersion: compat.version,
+ software: match[2] || MASTODON,
+ version: semver.version.split('-')[0],
+ };
+ } else {
+ // If we can't parse the version, this is a new and exotic backend.
+ // Fall back to minimal featureset.
+ return {
+ build: null,
+ compatVersion: '0.0.0',
+ software: null,
+ version: '0.0.0',
+ };
+ }
+};
+
+export {
+ DITTO,
+ FIREFISH,
+ FRIENDICA,
+ GOTOSOCIAL,
+ ICESHRIMP,
+ MASTODON,
+ MITRA,
+ PIXELFED,
+ PLEROMA,
+ TAKAHE,
+ TOKI,
+ AKKOMA,
+ GLITCH,
+ REBASED,
+ PL,
+ UNRELEASED,
+ type Features,
+ type Backend as BackendVersion,
+ getFeatures,
+};
diff --git a/packages/pl-api/lib/main.ts b/packages/pl-api/lib/main.ts
new file mode 100644
index 000000000..b414c5e89
--- /dev/null
+++ b/packages/pl-api/lib/main.ts
@@ -0,0 +1,6 @@
+export { PlApiClient } from './client';
+export { type Response as PlApiResponse } from './request';
+export * from './entities';
+export * from './features';
+export * from './params';
+export * from './responses';
diff --git a/packages/pl-api/lib/params/accounts.ts b/packages/pl-api/lib/params/accounts.ts
new file mode 100644
index 000000000..e7e412482
--- /dev/null
+++ b/packages/pl-api/lib/params/accounts.ts
@@ -0,0 +1,67 @@
+import type { LanguageParam, OnlyEventsParam, OnlyMediaParam, PaginationParams, WithMutedParam, WithRelationshipsParam } from './common';
+
+type GetAccountParams = WithMutedParam;
+
+interface GetAccountStatusesParams extends PaginationParams, WithMutedParam, OnlyEventsParam, OnlyMediaParam, LanguageParam {
+ /** Boolean. Filter out statuses in reply to a different account. */
+ exclude_replies?: boolean;
+ /** Boolean. Filter for pinned statuses only. Defaults to false, which includes all statuses. Pinned statuses do not receive special priority in the order of the returned results. */
+ pinned?: boolean;
+ /** String. Filter for statuses using a specific hashtag. */
+ tagged?: string;
+}
+
+type GetAccountFollowersParams = PaginationParams & WithRelationshipsParam;
+type GetAccountFollowingParams = PaginationParams & WithRelationshipsParam;
+
+interface FollowAccountParams {
+ /** Boolean. Receive this account’s reblogs in home timeline? Defaults to true. */
+ reblogs?: boolean;
+ /** Boolean. Receive notifications when this account posts a status? Defaults to false. */
+ notify?: boolean;
+ /**
+ * Array of String (ISO 639-1 language two-letter code). Filter received statuses for these languages. If not provided, you will receive this account’s posts in all languages.
+ * Requires `features.followAccountLangugaes`.
+ */
+ languages?: string[];
+}
+
+interface GetRelationshipsParams {
+ /** Boolean. Whether relationships should be returned for suspended users, defaults to false. */
+ with_suspended?: boolean;
+}
+
+interface SearchAccountParams {
+ /** Integer. Maximum number of results. Defaults to 40 accounts. Max 80 accounts. */
+ limit?: number;
+ /** Integer. Skip the first n results. */
+ offset?: number;
+ /** Boolean. Attempt WebFinger lookup. Defaults to false. Use this when `q` is an exact address. */
+ resolve?: boolean;
+ /** Boolean. Limit the search to users you are following. Defaults to false. */
+ following?: boolean;
+}
+
+interface ReportAccountParams {
+ status_ids?: string[];
+ comment?: string;
+ forward?: boolean;
+ category?: 'spam' | 'legal' | 'violation' | 'other';
+ rule_ids?: string[];
+}
+
+type GetAccountEndorsementsParams = WithRelationshipsParam;
+type GetAccountFavouritesParams = PaginationParams;
+
+export type {
+ GetAccountParams,
+ GetAccountStatusesParams,
+ GetAccountFollowersParams,
+ GetAccountFollowingParams,
+ FollowAccountParams,
+ GetRelationshipsParams,
+ SearchAccountParams,
+ ReportAccountParams,
+ GetAccountEndorsementsParams,
+ GetAccountFavouritesParams,
+};
diff --git a/packages/pl-api/lib/params/admin.ts b/packages/pl-api/lib/params/admin.ts
new file mode 100644
index 000000000..326eddd72
--- /dev/null
+++ b/packages/pl-api/lib/params/admin.ts
@@ -0,0 +1,200 @@
+import type { PaginationParams } from './common';
+
+interface AdminGetAccountsParams extends PaginationParams {
+ /** String. Filter for `local` or `remote` accounts. */
+ origin?: 'local' | 'remote';
+ /** String. Filter for `active`, `pending`, `disabled`, `silenced`, or suspended accounts. */
+ status?: 'active' | 'pending' | 'disabled' | 'silenced' | 'suspended';
+ /** String. Filter for accounts with `staff` permissions (users that can manage reports). */
+ permissions?: 'staff';
+ /** Array of String. Filter for users with these roles. */
+ role_ids?: string[];
+ /** String. Lookup users invited by the account with this ID. */
+ invited_by?: string;
+ /** String. Search for the given username. */
+ username?: string;
+ /** String. Search for the given display name */
+ display_name?: string;
+ /** String. Filter by the given domain */
+ by_domain?: string;
+ /** String. Lookup a user with this email */
+ email?: string;
+ /** String. Lookup users with this IP address */
+ ip?: string;
+}
+
+type AdminAccountAction = 'none' | 'sensitive' | 'disable' | 'silence' | 'suspend';
+
+interface AdminPerformAccountActionParams {
+ /** String. The ID of an associated report that caused this action to be taken. */
+ report_id?: string;
+ /** String. The ID of a preset warning. */
+ warning_preset_id?: string;
+ /** String. Additional clarification for why this action was taken. */
+ text?: string;
+ /** Boolean. Should an email be sent to the user with the above information? */
+ send_email_notification?: boolean;
+}
+
+type AdminGetDomainBlocksParams = PaginationParams;
+
+interface AdminCreateDomainBlockParams {
+ /** String. Whether to apply a `silence`, `suspend`, or `noop` to the domain. Defaults to `silence` */
+ severity?: 'silence' | 'suspend' | 'noop';
+ /** Boolean. Whether media attachments should be rejected. Defaults to false */
+ reject_media?: boolean;
+ /** Boolean. Whether reports from this domain should be rejected. Defaults to false */
+ reject_reports?: boolean;
+ /** String. A private note about this domain block, visible only to admins. */
+ private_comment?: string;
+ /** String. A public note about this domain block, optionally shown on the about page. */
+ public_comment?: string;
+ /** Boolean. Whether to partially censor the domain when shown in public. Defaults to false */
+ obfuscate?: boolean;
+}
+
+type AdminUpdateDomainBlockParams = AdminCreateDomainBlockParams;
+
+interface AdminGetReportsParams extends PaginationParams {
+ /** Boolean. Filter for resolved reports? */
+ resolved?: boolean;
+ /** String. Filter for reports filed by this account. */
+ account_id?: string;
+ /** String. Filter for reports targeting this account. */
+ target_account_id?: string;
+}
+
+interface AdminUpdateReportParams {
+ /** String. Change the classification of the report to `spam`, `violation`, or `other`. */
+ category?: 'spam' | 'violation' | 'other';
+ /** Array of Integer. For `violation` category reports, specify the ID of the exact rules broken. Rules and their IDs are available via [GET /api/v1/instance/rules](https://docs.joinmastodon.org/methods/instance/#rules) and [GET /api/v1/instance](https://docs.joinmastodon.org/methods/instance/#get). */
+ rule_ids?: string[];
+}
+
+interface AdminGetStatusesParams {
+ limit?: number;
+ local_only?: boolean;
+ with_reblogs?: boolean;
+ with_private?: boolean;
+}
+
+interface AdminUpdateStatusParams {
+ sensitive?: boolean;
+ visibility?: 'public' | 'private' | 'unlisted';
+}
+
+type AdminGetCanonicalEmailBlocks = PaginationParams;
+
+type AdminDimensionKey = 'languages' | 'sources' | 'servers' | 'space_usage' | 'software_versions' | 'tag_servers' | 'tag_languages' | 'instance_accounts' | 'instance_languages';
+
+interface AdminGetDimensionsParams {
+ /** String (ISO 8601 Datetime). The start date for the time period. If a time is provided, it will be ignored. */
+ start_at?: string;
+ /** String (ISO 8601 Datetime). The end date for the time period. If a time is provided, it will be ignored. */
+ end_at?: string;
+ /** Integer. The maximum number of results to return for sources, servers, languages, tag or instance dimensions. */
+ limit?: number;
+ tag_servers?: {
+ /** String. When tag_servers is one of the requested keys, you must provide a trending tag ID to obtain information about which servers are posting the tag. */
+ id?: string;
+ };
+ tag_languages?: {
+ /** String. When tag_languages is one of the requested keys, you must provide a trending tag ID to obtain information about which languages are posting the tag. */
+ id?: string;
+ };
+ instance_accounts?: {
+ /** String. When instance_accounts is one of the requested keys, you must provide a domain to obtain information about popular accounts from that server. */
+ domain?: string;
+ };
+ instance_languages?: {
+ /** String. When instance_accounts is one of the requested keys, you must provide a domain to obtain information about popular languages from that server. */
+ domain?: string;
+ };
+}
+
+type AdminGetDomainAllowsParams = PaginationParams;
+
+type AdminGetEmailDomainBlocksParams = PaginationParams;
+
+type AdminGetIpBlocksParams = PaginationParams;
+
+interface AdminCreateIpBlockParams {
+ /** String. The IP address and prefix to block. Defaults to 0.0.0.0/32 */
+ ip?: string;
+ /** String. The policy to apply to this IP range: sign_up_requires_approval, sign_up_block, or no_access */
+ severity: string;
+ /** String. The reason for this IP block. */
+ comment?: string;
+ /** Integer. The number of seconds in which this IP block will expire. */
+ expires_in?: number;
+}
+
+type AdminUpdateIpBlockParams = Partial;
+
+type AdminMeasureKey = 'active_users' | 'new_users' | 'interactions' | 'opened_reports' | 'resolved_reports' | 'tag_accounts' | 'tag_uses' | 'tag_servers' | 'instance_accounts' | 'instance_media_attachments' | 'instance_reports' | 'instance_statuses' | 'instance_follows' | 'instance_followers';
+
+interface AdminGetMeasuresParams {
+ tag_accounts?: {
+ /** String. When `tag_accounts` is one of the requested keys, you must provide a tag ID to obtain the measure of how many accounts used that hashtag in at least one status within the given time period. */
+ id?: string;
+ };
+ tag_uses?: {
+ /** String. When `tag_uses` is one of the requested keys, you must provide a tag ID to obtain the measure of how many statuses used that hashtag within the given time period. */
+ id?: string;
+ };
+ tag_servers?: {
+ /** String. When `tag_servers` is one of the requested keys, you must provide a tag ID to obtain the measure of how many servers used that hashtag in at least one status within the given time period. */
+ id?: string;
+ };
+ instance_accounts?: {
+ /** String. When `instance_accounts` is one of the requested keys, you must provide a remote domain to obtain the measure of how many accounts have been discovered from that server within the given time period. */
+ domain?: string;
+ };
+ instance_media_attachments?: {
+ /** String. When `instance_media_attachments` is one of the requested keys, you must provide a remote domain to obtain the measure of how much space is used by media attachments from that server within the given time period. */
+ domain?: string;
+ };
+ instance_reports?: {
+ /** String. When `instance_reports` is one of the requested keys, you must provide a remote domain to obtain the measure of how many reports have been filed against accounts from that server within the given time period. */
+ domain?: string;
+ };
+ instance_statuses?: {
+ /** String. When `instance_statuses` is one of the requested keys, you must provide a remote domain to obtain the measure of how many statuses originate from that server within the given time period. */
+ domain?: string;
+ };
+ instance_follows?: {
+ /** String. When `instance_follows` is one of the requested keys, you must provide a remote domain to obtain the measure of how many follows were performed on accounts from that server by local accounts within the given time period. */
+ domain?: string;
+ };
+ instance_followers?: {
+ /** String. When `instance_followers` is one of the requested keys, you must provide a remote domain to obtain the measure of how many follows were performed by accounts from that server on local accounts within the given time period. */
+ domain?: string;
+ };
+}
+
+interface AdminGetGroupsParams {
+}
+
+export type {
+ AdminGetAccountsParams,
+ AdminAccountAction,
+ AdminPerformAccountActionParams,
+ AdminGetDomainBlocksParams,
+ AdminCreateDomainBlockParams,
+ AdminUpdateDomainBlockParams,
+ AdminGetReportsParams,
+ AdminUpdateReportParams,
+ AdminGetStatusesParams,
+ AdminUpdateStatusParams,
+ AdminGetCanonicalEmailBlocks,
+ AdminDimensionKey,
+ AdminGetDimensionsParams,
+ AdminGetDomainAllowsParams,
+ AdminGetEmailDomainBlocksParams,
+ AdminGetIpBlocksParams,
+ AdminCreateIpBlockParams,
+ AdminUpdateIpBlockParams,
+ AdminMeasureKey,
+ AdminGetMeasuresParams,
+ AdminGetGroupsParams,
+};
diff --git a/packages/pl-api/lib/params/apps.ts b/packages/pl-api/lib/params/apps.ts
new file mode 100644
index 000000000..538ac1a34
--- /dev/null
+++ b/packages/pl-api/lib/params/apps.ts
@@ -0,0 +1,14 @@
+interface CreateApplicationParams {
+ /** String. A name for your application */
+ client_name: string;
+ /** String. Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter. */
+ redirect_uris: string;
+ /** String. Space separated list of scopes. If none is provided, defaults to `read`. See [OAuth Scopes](https://docs.joinmastodon.org/api/oauth-scopes/) for a list of possible scopes. */
+ scopes?: string;
+ /** String. A URL to the homepage of your app */
+ website?: string;
+}
+
+export type {
+ CreateApplicationParams,
+};
diff --git a/packages/pl-api/lib/params/chats.ts b/packages/pl-api/lib/params/chats.ts
new file mode 100644
index 000000000..bf19f88e0
--- /dev/null
+++ b/packages/pl-api/lib/params/chats.ts
@@ -0,0 +1,18 @@
+import { PaginationParams, WithMutedParam } from './common';
+
+type GetChatsParams = PaginationParams & WithMutedParam;
+type GetChatMessagesParams = PaginationParams;
+
+type CreateChatMessageParams = {
+ content?: string;
+ media_id: string;
+} | {
+ content: string;
+ media_id?: string;
+};
+
+export type {
+ GetChatsParams,
+ GetChatMessagesParams,
+ CreateChatMessageParams,
+};
diff --git a/packages/pl-api/lib/params/common.ts b/packages/pl-api/lib/params/common.ts
new file mode 100644
index 000000000..ed239f0cb
--- /dev/null
+++ b/packages/pl-api/lib/params/common.ts
@@ -0,0 +1,59 @@
+interface PaginationParams {
+ /** String. All results returned will be lesser than this ID. In effect, sets an upper bound on results. */
+ max_id?: string;
+ /** String. All results returned will be greater than this ID. In effect, sets a lower bound on results. */
+ since_id?: string;
+ /** Integer. Maximum number of results to return. */
+ limit?: number;
+ /** String. Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward. */
+ min_id?: string;
+}
+
+interface WithMutedParam {
+ /**
+ * Boolean. Also show statuses from muted users. Default to false.
+ *
+ * Requires `features.timelinesWithMuted`.
+ */
+ with_muted?: boolean;
+}
+
+interface WithRelationshipsParam {
+ /**
+ * Embed relationships into accounts.
+ * Supported by Pleroma.
+ */
+ with_relationships?: boolean;
+}
+
+interface OnlyMediaParam {
+ /** Boolean. Show only statuses with media attached? Defaults to false. */
+ only_media?: boolean;
+}
+
+interface OnlyEventsParam {
+ /**
+ * Boolean. Filter out statuses without events.
+ *
+ * Requires `features.events`.
+ */
+ only_events?: boolean;
+}
+
+interface LanguageParam {
+ /**
+ * Fetch a translation in given language
+ *
+ * Requires `features.fetchStatusesWithTranslation`.
+ */
+ language?: string;
+}
+
+export type {
+ PaginationParams,
+ WithMutedParam,
+ WithRelationshipsParam,
+ OnlyMediaParam,
+ OnlyEventsParam,
+ LanguageParam,
+};
diff --git a/packages/pl-api/lib/params/events.ts b/packages/pl-api/lib/params/events.ts
new file mode 100644
index 000000000..c4120017a
--- /dev/null
+++ b/packages/pl-api/lib/params/events.ts
@@ -0,0 +1,33 @@
+import { PaginationParams } from './common';
+
+interface CreateEventParams {
+ /** name of the event */
+ name: string;
+ /** optional, description of the event */
+ status?: string;
+ /** optional, event banner attachment ID */
+ banner_id?: string;
+ /** start time of the event */
+ start_time?: string;
+ /** optional, end time of the event */
+ end_time?: string;
+ /** optional, event join mode, either free or restricted, defaults to free */
+ join_mode?: 'free' | 'restricted';
+ /** optional, location ID from the location provider used by server */
+ location_id?: string;
+ /** string, contain the MIME type of the status. */
+ content_type?: string;
+}
+
+type EditEventParams = Partial>;
+type GetJoinedEventsParams = PaginationParams;
+type GetEventParticipationsParams = PaginationParams;
+type GetEventParticipationRequestsParams = PaginationParams;
+
+export type {
+ CreateEventParams,
+ EditEventParams,
+ GetJoinedEventsParams,
+ GetEventParticipationsParams,
+ GetEventParticipationRequestsParams,
+};
diff --git a/packages/pl-api/lib/params/filtering.ts b/packages/pl-api/lib/params/filtering.ts
new file mode 100644
index 000000000..165dff6ac
--- /dev/null
+++ b/packages/pl-api/lib/params/filtering.ts
@@ -0,0 +1,48 @@
+import type { PaginationParams, WithRelationshipsParam } from './common';
+
+interface MuteAccountParams {
+ /** Boolean. Mute notifications in addition to statuses? Defaults to true. */
+ notifications?: boolean;
+ /** Number. How long the mute should last, in seconds. Defaults to 0 (indefinite). */
+ duration?: number;
+}
+
+type GetMutesParams = Omit & WithRelationshipsParam;
+type GetBlocksParams = PaginationParams & WithRelationshipsParam;
+type GetDomainBlocksParams = PaginationParams;
+
+type FilterContext = 'home' | 'notifications' | 'public' | 'thread' | 'account';
+
+interface CreateFilterParams {
+ title: string;
+ context: Array;
+ filter_action?: 'warn' | 'hide';
+ expires_in?: number;
+ keywords_attributes: Array<{
+ keyword: string;
+ whole_word?: boolean;
+ }>;
+}
+
+interface UpdateFilterParams {
+ title?: string;
+ context?: Array;
+ filter_action?: 'warn' | 'hide';
+ expires_in?: number;
+ keywords_attributes?: Array<{
+ keyword: string;
+ whole_word?: boolean;
+ id?: string;
+ _destroy?: boolean;
+ }>;
+}
+
+export type {
+ MuteAccountParams,
+ GetMutesParams,
+ GetBlocksParams,
+ GetDomainBlocksParams,
+ FilterContext,
+ CreateFilterParams,
+ UpdateFilterParams,
+};
diff --git a/packages/pl-api/lib/params/groups.ts b/packages/pl-api/lib/params/groups.ts
new file mode 100644
index 000000000..5ec218914
--- /dev/null
+++ b/packages/pl-api/lib/params/groups.ts
@@ -0,0 +1,27 @@
+import type { PaginationParams } from './common';
+
+interface CreateGroupParams {
+ display_name: string;
+ note?: string;
+ avatar?: File;
+ header?: File;
+}
+
+interface UpdateGroupParams {
+ display_name?: string;
+ note?: string;
+ avatar?: File | '';
+ header?: File | '';
+}
+
+type GetGroupMembershipsParams = Omit;
+type GetGroupMembershipRequestsParams = Omit;
+type GetGroupBlocksParams = Omit;
+
+export type {
+ CreateGroupParams,
+ UpdateGroupParams,
+ GetGroupMembershipsParams,
+ GetGroupMembershipRequestsParams,
+ GetGroupBlocksParams,
+};
diff --git a/packages/pl-api/lib/params/index.ts b/packages/pl-api/lib/params/index.ts
new file mode 100644
index 000000000..dcf70254f
--- /dev/null
+++ b/packages/pl-api/lib/params/index.ts
@@ -0,0 +1,21 @@
+export * from './accounts';
+export * from './admin';
+export * from './apps';
+export * from './chats';
+export * from './events';
+export * from './filtering';
+export * from './groups';
+export * from './instance';
+export * from './interaction-requests';
+export * from './lists';
+export * from './media';
+export * from './my-account';
+export * from './notifications';
+export * from './oauth';
+export * from './push-notifications';
+export * from './scheduled-statuses';
+export * from './search';
+export * from './settings';
+export * from './statuses';
+export * from './timelines';
+export * from './trends';
diff --git a/packages/pl-api/lib/params/instance.ts b/packages/pl-api/lib/params/instance.ts
new file mode 100644
index 000000000..c8a9fa4fa
--- /dev/null
+++ b/packages/pl-api/lib/params/instance.ts
@@ -0,0 +1,14 @@
+interface ProfileDirectoryParams {
+ /** Number. Skip the first n results. */
+ offset?: number;
+ /** Number. How many accounts to load. Defaults to 40 accounts. Max 80 accounts. */
+ limit?: number;
+ /** String. Use active to sort by most recently posted statuses (default) or new to sort by most recently created profiles. */
+ order?: string;
+ /** Boolean. If true, returns only local accounts. */
+ local?: boolean;
+}
+
+export type {
+ ProfileDirectoryParams,
+};
diff --git a/packages/pl-api/lib/params/interaction-requests.ts b/packages/pl-api/lib/params/interaction-requests.ts
new file mode 100644
index 000000000..403c05b1b
--- /dev/null
+++ b/packages/pl-api/lib/params/interaction-requests.ts
@@ -0,0 +1,16 @@
+import { PaginationParams } from './common';
+
+interface GetInteractionRequestsParams extends PaginationParams {
+ /** If set, then only interactions targeting the given status_id will be included in the results. */
+ status_id?: string;
+ /** If true or not set, pending favourites will be included in the results. At least one of favourites, replies, and reblogs must be true. */
+ favourites?: boolean;
+ /** If true or not set, pending replies will be included in the results. At least one of favourites, replies, and reblogs must be true. */
+ replies?: boolean;
+ /** If true or not set, pending reblogs will be included in the results. At least one of favourites, replies, and reblogs must be true. */
+ reblogs?: boolean;
+}
+
+export type {
+ GetInteractionRequestsParams,
+};
diff --git a/packages/pl-api/lib/params/lists.ts b/packages/pl-api/lib/params/lists.ts
new file mode 100644
index 000000000..af98e821f
--- /dev/null
+++ b/packages/pl-api/lib/params/lists.ts
@@ -0,0 +1,19 @@
+import type { PaginationParams } from './common';
+
+interface CreateListParams {
+ /** String. The title of the list to be created. */
+ title: string;
+ /** String. One of followed, list, or none. Defaults to list. */
+ replies_policy?: 'followed' | 'list' | 'none';
+ /** Boolean. Whether members of this list need to get removed from the “Home” feed */
+ exclusive?: boolean;
+}
+
+type UpdateListParams = CreateListParams;
+type GetListAccountsParams = PaginationParams;
+
+export type {
+ CreateListParams,
+ UpdateListParams,
+ GetListAccountsParams,
+};
diff --git a/packages/pl-api/lib/params/media.ts b/packages/pl-api/lib/params/media.ts
new file mode 100644
index 000000000..288f5ba8c
--- /dev/null
+++ b/packages/pl-api/lib/params/media.ts
@@ -0,0 +1,30 @@
+interface UploadMediaParams {
+ /** Object. The file to be attached, encoded using multipart form data. The file must have a MIME type. */
+ file: File;
+ /** Object. The custom thumbnail of the media to be attached, encoded using multipart form data. */
+ thumbnail?: File;
+ /** String. A plain-text description of the media, for accessibility purposes. */
+ description?: string;
+ /**
+ * String. Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. See Focal points for cropping media thumbnails for more information.
+ * @see {@link https://docs.joinmastodon.org/api/guidelines/#focal-points}
+ */
+ focus?: string;
+}
+
+interface UpdateMediaParams {
+ /** Object. The custom thumbnail of the media to be attached, encoded using multipart form data. */
+ thumbnail?: File;
+ /** String. A plain-text description of the media, for accessibility purposes. */
+ description?: string;
+ /**
+ * String. Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. See Focal points for cropping media thumbnails for more information.
+ * @see {@link https://docs.joinmastodon.org/api/guidelines/#focal-points}
+ */
+ focus?: string;
+}
+
+export type {
+ UploadMediaParams,
+ UpdateMediaParams,
+};
diff --git a/packages/pl-api/lib/params/my-account.ts b/packages/pl-api/lib/params/my-account.ts
new file mode 100644
index 000000000..d0c372e76
--- /dev/null
+++ b/packages/pl-api/lib/params/my-account.ts
@@ -0,0 +1,31 @@
+import type { PaginationParams } from './common';
+
+interface GetBookmarksParams extends PaginationParams {
+ /**
+ * Bookmark folder ID
+ * Requires `features.bookmarkFolders`.
+ */
+ folder_id?: string;
+}
+
+type GetFavouritesParams = PaginationParams;
+type GetFollowRequestsParams = Omit;
+type GetEndorsementsParams = Omit;
+type GetFollowedTagsParams = PaginationParams;
+
+interface CreateBookmarkFolderParams {
+ name: string;
+ emoji?: string;
+}
+
+type UpdateBookmarkFolderParams = Partial;
+
+export type {
+ GetBookmarksParams,
+ GetFavouritesParams,
+ GetFollowRequestsParams,
+ GetEndorsementsParams,
+ GetFollowedTagsParams,
+ CreateBookmarkFolderParams,
+ UpdateBookmarkFolderParams,
+};
diff --git a/packages/pl-api/lib/params/notifications.ts b/packages/pl-api/lib/params/notifications.ts
new file mode 100644
index 000000000..488dd807c
--- /dev/null
+++ b/packages/pl-api/lib/params/notifications.ts
@@ -0,0 +1,34 @@
+import type { PaginationParams } from './common';
+
+interface GetNotificationParams extends PaginationParams {
+ /** Array of String. Types to include in the result. */
+ types?: string[];
+ /** Array of String. Types to exclude from the results. */
+ exclude_types?: string[];
+ /** String. Return only notifications received from the specified account. */
+ account_id?: string;
+ /**
+ * will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`).
+ * Requires `features.notificationsExcludeVisibilities`.
+ */
+ exclude_visibilities?: string[];
+}
+
+interface UpdateNotificationPolicyRequest {
+ /** Boolean. Whether to filter notifications from accounts the user is not following. */
+ filter_not_following?: boolean;
+ /** Boolean. Whether to filter notifications from accounts that are not following the user. */
+ filter_not_followers?: boolean;
+ /** Boolean. Whether to filter notifications from accounts created in the past 30 days. */
+ filter_new_accounts?: boolean;
+ /** Boolean. Whether to filter notifications from private mentions. Replies to private mentions initiated by the user, as well as accounts the user follows, are never filtered. */
+ filter_private_mentions?: boolean;
+}
+
+type GetNotificationRequestsParams = PaginationParams;
+
+export type {
+ GetNotificationParams,
+ UpdateNotificationPolicyRequest,
+ GetNotificationRequestsParams,
+};
diff --git a/packages/pl-api/lib/params/oauth.ts b/packages/pl-api/lib/params/oauth.ts
new file mode 100644
index 000000000..75e6059b3
--- /dev/null
+++ b/packages/pl-api/lib/params/oauth.ts
@@ -0,0 +1,54 @@
+interface OauthAuthorizeParams {
+ /** String. Should be set equal to `code`. */
+ response_type: string;
+ /** String. The client ID, obtained during app registration. */
+ client_id: string;
+ /** String. Set a URI to redirect the user to. If this parameter is set to `urn:ietf:wg:oauth:2.0:oob` then the authorization code will be shown instead. Must match one of the `redirect_uris` declared during app registration. */
+ redirect_uri: string;
+ /** String. List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters). Must be a subset of `scopes` declared during app registration. If not provided, defaults to `read`. */
+ scope?: string;
+ /** Boolean. Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance. */
+ force_login?: boolean;
+ /** String. The ISO 639-1 two-letter language code to use while rendering the authorization form. */
+ lang?: string;
+}
+
+interface GetTokenParams {
+ /** String. Set equal to `authorization_code` if `code` is provided in order to gain user-level access. Otherwise, set equal to `client_credentials` to obtain app-level access only. */
+ grant_type: string;
+ /** String. A user authorization code, obtained via [GET /oauth/authorize](https://docs.joinmastodon.org/methods/oauth/#authorize). */
+ code?: string;
+ /** String. The client ID, obtained during app registration. */
+ client_id: string;
+ /** String. The client secret, obtained during app registration. */
+ client_secret: string;
+ /** String. Set a URI to redirect the user to. If this parameter is set to urn:ietf:wg:oauth:2.0:oob then the token will be shown instead. Must match one of the `redirect_uris` declared during app registration. */
+ redirect_uri: string;
+ /** String. List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters). If `code` was provided, then this must be equal to the `scope` requested from the user. Otherwise, it must be a subset of `scopes` declared during app registration. If not provided, defaults to `read`. */
+ scope?: string;
+}
+
+interface RevokeTokenParams {
+ /** String. The client ID, obtained during app registration. */
+ client_id: string;
+ /** String. The client secret, obtained during app registration. */
+ client_secret: string;
+ /** String. The previously obtained token, to be invalidated. */
+ token: string;
+}
+
+interface MfaChallengeParams {
+ client_id: string;
+ client_secret: string;
+ /** access token to check second step of mfa */
+ mfa_token: string;
+ challenge_type: 'totp' | 'recovery';
+ code: string;
+}
+
+export type {
+ OauthAuthorizeParams,
+ GetTokenParams,
+ RevokeTokenParams,
+ MfaChallengeParams,
+};
diff --git a/packages/pl-api/lib/params/push-notifications.ts b/packages/pl-api/lib/params/push-notifications.ts
new file mode 100644
index 000000000..67b85dd91
--- /dev/null
+++ b/packages/pl-api/lib/params/push-notifications.ts
@@ -0,0 +1,30 @@
+interface CreatePushNotificationsSubscriptionParams {
+ subscription: {
+ /** String. The endpoint URL that is called when a notification event occurs. */
+ endpoint: string;
+ keys?: {
+ /** String. User agent public key. Base64 encoded string of a public key from a ECDH keypair using the prime256v1 curve. */
+ p256dh: string;
+ /** String. Auth secret. Base64 encoded string of 16 bytes of random data. */
+ auth: string;
+ };
+ };
+ data?: {
+ alerts?: Record;
+ /** String. Specify whether to receive push notifications from `all`, `followed`, `follower`, or `none` users. */
+ policy?: string;
+ };
+}
+
+interface UpdatePushNotificationsSubscriptionParams {
+ data?: {
+ alerts?: Record;
+ };
+ /** String. Specify whether to receive push notifications from `all`, `followed`, `follower`, or `none` users. */
+ policy?: string;
+}
+
+export type {
+ CreatePushNotificationsSubscriptionParams,
+ UpdatePushNotificationsSubscriptionParams,
+};
diff --git a/packages/pl-api/lib/params/scheduled-statuses.ts b/packages/pl-api/lib/params/scheduled-statuses.ts
new file mode 100644
index 000000000..53875e490
--- /dev/null
+++ b/packages/pl-api/lib/params/scheduled-statuses.ts
@@ -0,0 +1,7 @@
+import type { PaginationParams } from './common';
+
+type GetScheduledStatusesParams = PaginationParams;
+
+export type {
+ GetScheduledStatusesParams,
+};
diff --git a/packages/pl-api/lib/params/search.ts b/packages/pl-api/lib/params/search.ts
new file mode 100644
index 000000000..ecbeb2e30
--- /dev/null
+++ b/packages/pl-api/lib/params/search.ts
@@ -0,0 +1,20 @@
+import type { PaginationParams, WithRelationshipsParam } from './common';
+
+interface SearchParams extends Exclude, WithRelationshipsParam {
+ /** String. Specify whether to search for only `accounts`, `hashtags`, `statuses` */
+ type?: 'accounts' | 'hashtags' | 'statuses' | 'groups';
+ /** Boolean. Only relevant if `type` includes `accounts`. If `true` and (a) the search query is for a remote account (e.g., `someaccount@someother.server`) and (b) the local server does not know about the account, WebFinger is used to try and resolve the account at `someother.server`. This provides the best recall at higher latency. If `false` only accounts the server knows about are returned. */
+ resolve?: boolean;
+ /** Boolean. Only include accounts that the user is following? Defaults to false. */
+ following?: boolean;
+ /** String. If provided, will only return statuses authored by this account. */
+ account_id?: string;
+ /** Boolean. Filter out unreviewed tags? Defaults to false. Use true when trying to find trending tags. */
+ exclude_unreviewed?: boolean;
+ /** Integer. Skip the first n results. */
+ offset?: number;
+}
+
+export type {
+ SearchParams,
+};
diff --git a/packages/pl-api/lib/params/settings.ts b/packages/pl-api/lib/params/settings.ts
new file mode 100644
index 000000000..9dc5a3e9a
--- /dev/null
+++ b/packages/pl-api/lib/params/settings.ts
@@ -0,0 +1,146 @@
+interface CreateAccountParams {
+ /** String. The desired username for the account */
+ username: string;
+ /** String. The email address to be used for login */
+ email: string;
+ /** String. The password to be used for login */
+ password: string;
+ /** Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE. */
+ agreement: boolean;
+ /** String. The language of the confirmation email that will be sent. */
+ locale: string;
+ /** String. If registrations require manual approval, this text will be reviewed by moderators. */
+ reason?: string;
+
+ fullname?: string;
+ bio?: string;
+ /** optional, contains provider-specific captcha solution */
+ captcha_solution?: string;
+ /** optional, contains provider-specific captcha token */
+ captcha_token?: string;
+ /** optional, contains provider-specific captcha data */
+ captcha_answer_data?: string;
+ /** invite token required when the registrations aren't public. */
+ token?: string;
+ birthday?: string;
+
+ /** optional, domain id, if multitenancy is enabled. */
+ domain?: string;
+
+ accepts_email_list?: boolean;
+}
+
+interface UpdateCredentialsParams {
+ /** String. The display name to use for the profile. */
+ display_name?: string;
+ /** String. The account bio. */
+ note?: string;
+ /** Avatar image encoded using `multipart/form-data` */
+ avatar?: File | '';
+ /** Header image encoded using `multipart/form-data` */
+ header?: File | '';
+ /** Boolean. Whether manual approval of follow requests is required. */
+ locked?: boolean;
+ /** Boolean. Whether the account has a bot flag. */
+ bot?: boolean;
+ /** Boolean. Whether the account should be shown in the profile directory. */
+ discoverable?: boolean;
+ /** Boolean. Whether to hide followers and followed accounts. */
+ hide_collections?: boolean;
+ /** Boolean. Whether public posts should be searchable to anyone. */
+ indexable?: boolean;
+ /** Hash. The profile fields to be set. Inside this hash, the key is an integer cast to a string (although the exact integer does not matter), and the value is another hash including name and value. By default, max 4 fields. */
+ fields_attributes?: Array<{
+ /** String. The name of the profile field. By default, max 255 characters. */
+ name: string;
+ /** String. The value of the profile field. By default, max 255 characters. */
+ value: string;
+ }>;
+ source?: {
+ /** String. Default post privacy for authored statuses. Can be public, unlisted, or private. */
+ privacy?: string;
+ /** Boolean. Whether to mark authored statuses as sensitive by default. */
+ sensitive?: string;
+ /** String. Default language to use for authored statuses (ISO 6391) */
+ language?: string;
+ };
+
+ /** if true, html tags are stripped from all statuses requested from the API */
+ no_rich_text?: boolean;
+ /** if true, user's followers will be hidden*/
+ hide_followers?: boolean;
+ /** if true, user's follows will be hidden */
+ hide_follows?: boolean;
+ /** if true, user's follower count will be hidden */
+ hide_followers_count?: boolean;
+ /** if true, user's follow count will be hidden */
+ hide_follows_count?: boolean;
+ /** if true, user's favorites timeline will be hidden */
+ hide_favorites?: boolean;
+ /** if true, user's role (e.g admin, moderator) will be exposed to anyone in the API */
+ show_role?: boolean;
+ /** the scope returned under privacy key in Source subentity */
+ default_scope?: string;
+ /** Opaque user settings to be saved on the backend. */
+ settings_store?: Record;
+ /** if true, skip filtering out broken threads */
+ skip_thread_containment?: boolean;
+ /** if true, allows automatically follow moved following accounts */
+ allow_following_move?: boolean;
+ /** array of ActivityPub IDs, needed for following move */
+ also_known_as?: string[];
+ /** sets the background image of the user. Can be set to "" (an empty string) to reset. */
+ background_image?: string;
+ /** the type of this account. */
+ actor_type?: string;
+ /** if false, this account will reject all chat messages. */
+ accepts_chat_messages?: boolean;
+ /** user's preferred language for receiving emails (digest, confirmation, etc.) */
+ language?: string;
+
+ /**
+ * Description of avatar image, for alt-text.
+ * Requires `features.accountAvatarDescription`.
+ */
+ avatar_description?: boolean;
+ /**
+ * Description of header image, for alt-text.
+ * Requires `features.accountAvatarDescription`.
+ */
+ header_description?: boolean;
+ /**
+ * Enable RSS feed for this account's Public posts at `/[username]/feed.rss`
+ * Requires `features.accountEnableRss`.
+ */
+ enable_rss?: boolean;
+}
+
+interface UpdateNotificationSettingsParams {
+ /**
+ * blocks notifications from accounts you do not follow
+ */
+ block_from_strangers?: boolean;
+
+ /**
+ * When set to true, it removes the contents of a message from the push notification.
+ */
+ hide_notification_contents?: boolean;
+}
+
+type UpdateInteractionPoliciesParams = Record<
+ 'public' | 'unlisted' | 'private' | 'direct',
+ Record<
+ 'can_favourite' | 'can_reblog' | 'can_reply',
+ Record<
+ 'always' | 'with_approval',
+ Array<'public' | 'followers' | 'following' | 'mutuals' | 'mentioned' | 'author' | 'me' | string>
+ >
+ >
+>;
+
+export type {
+ CreateAccountParams,
+ UpdateCredentialsParams,
+ UpdateNotificationSettingsParams,
+ UpdateInteractionPoliciesParams,
+};
diff --git a/packages/pl-api/lib/params/statuses.ts b/packages/pl-api/lib/params/statuses.ts
new file mode 100644
index 000000000..3b8a5bd68
--- /dev/null
+++ b/packages/pl-api/lib/params/statuses.ts
@@ -0,0 +1,124 @@
+import type { PaginationParams } from './common';
+
+interface CreateStatusWithContent {
+ /** The text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided. */
+ status: string;
+ /** Array of String. Include Attachment IDs to be attached as media. If provided, `status` becomes optional, and `poll` cannot be used. */
+ media_ids?: string[];
+}
+
+interface CreateStatusWithMedia {
+ /** The text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided. */
+ status?: string;
+ /** Array of String. Include Attachment IDs to be attached as media. If provided, `status` becomes optional, and `poll` cannot be used. */
+ media_ids: string[];
+}
+
+interface CreateStatusOptionalParams {
+ poll?: {
+ /** Array of String. Possible answers to the poll. If provided, `media_ids` cannot be used, and poll[expires_in] must be provided. */
+ options: string[];
+ /** Integer. Duration that the poll should be open, in seconds. If provided, media_ids cannot be used, and poll[options] must be provided. */
+ expires_in: number;
+ /** Boolean. Allow multiple choices? Defaults to false. */
+ multiple?: boolean;
+ /** Boolean. Hide vote counts until the poll ends? Defaults to false. */
+ hide_totals?: boolean;
+
+ options_map?: Array>;
+ };
+ /** String. ID of the status being replied to, if status is a reply. */
+ in_reply_to_id?: string;
+ /** Boolean. Mark status and attached media as sensitive? Defaults to false. */
+ sensitive?: boolean;
+ /** String. Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field. */
+ spoiler_text?: string;
+ /**
+ * String. Sets the visibility of the posted status to `public`, `unlisted`, `private`, `direct`.
+ * `local` — requires `features.createStatusLocalScope`.
+ * `list:LIST_ID` — requires `features.createStatusListScope`.
+ */
+ visibility?: string;
+ /** String. ISO 639 language code for this status. */
+ language?: string;
+ /** String. ISO 8601 Datetime at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future. */
+ scheduled_at?: string;
+
+ /**
+ * boolean, if set to true the post won't be actually posted, but the status entity would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
+ * Requires `features.createStatusPreview`.
+ */
+ preview?: boolean;
+ /**
+ * string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the `/api/v1/instance` endpoint.
+ */
+ content_type?: string;
+ /**
+ * A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for post visibility are not affected by this and will still apply.
+ * Requires `features.createStatusExplicitAddressing`.
+ */
+ to?: string[];
+ /**
+ * The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.
+ * Requires `features.createStatusExpiration`.
+ */
+ expires_in?: number;
+ /**
+ * Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`.
+ * Requires `features.createStatusReplyToConversation`.
+ */
+ in_reply_to_conversation_id?: string;
+ /**
+ * ID of the status being quoted, if any.
+ * Requires `features.quotePosts`.
+ */
+ quote_id?: string;
+
+ /**
+ * If set to true, this status will be "local only" and will NOT be federated beyond the local timeline(s). If set to false (default), this status will be federated to your followers beyond the local timeline(s).
+ */
+ local_only?: boolean;
+
+ group_id?: string;
+
+ status_map?: Record;
+ spoiler_text_map?: Record;
+}
+
+type CreateStatusParams = (CreateStatusWithContent | CreateStatusWithMedia) & CreateStatusOptionalParams;
+
+interface LanguageParam {
+ /** Attach translated version of a post. Requires `features.autoTranslate`. */
+ language?: string;
+}
+
+type GetStatusParams = LanguageParam;
+
+type GetStatusesParams = LanguageParam;
+
+type GetStatusContextParams = LanguageParam;
+
+type GetRebloggedByParams = Omit
+
+type GetFavouritedByParams = Omit
+
+interface EditStatusOptionalParams {
+ sensitive?: boolean;
+ spoiler_text?: string;
+ language?: string;
+}
+
+type EditStatusParams = (CreateStatusWithContent | CreateStatusWithMedia) & EditStatusOptionalParams;
+type GetStatusQuotesParams = PaginationParams;
+
+export type {
+ CreateStatusParams,
+ GetStatusParams,
+ GetStatusesParams,
+ GetStatusContextParams,
+ GetRebloggedByParams,
+ GetFavouritedByParams,
+ EditStatusParams,
+ GetStatusQuotesParams,
+};
+
diff --git a/packages/pl-api/lib/params/timelines.ts b/packages/pl-api/lib/params/timelines.ts
new file mode 100644
index 000000000..9a0377981
--- /dev/null
+++ b/packages/pl-api/lib/params/timelines.ts
@@ -0,0 +1,65 @@
+import type { LanguageParam, OnlyEventsParam, OnlyMediaParam, PaginationParams, WithMutedParam } from './common';
+
+interface PublicTimelineParams extends PaginationParams, WithMutedParam, OnlyEventsParam, OnlyMediaParam, LanguageParam {
+ /** Boolean. Show only local statuses? Defaults to false. */
+ local?: boolean;
+ /** Boolean. Show only remote statuses? Defaults to false. */
+ remote?: boolean;
+ /**
+ * Boolean. Show only statuses from the given domain.
+ *
+ * Requires `features.instanceTimeline`.
+ */
+ instance?: string;
+}
+
+interface HashtagTimelineParams extends PaginationParams, WithMutedParam, OnlyEventsParam, OnlyMediaParam, LanguageParam {
+ /** Array of String. Return statuses that contain any of these additional tags. */
+ any?: string[];
+ /** Array of String. Return statuses that contain all of these additional tags. */
+ all?: string[];
+ /** Array of String. Return statuses that contain none of these additional tags. */
+ none?: string[];
+ /** Boolean. Show only local statuses? Defaults to false. */
+ local?: boolean;
+ /** Boolean. Show only remote statuses? Defaults to false. */
+ remote?: boolean;
+}
+
+type HomeTimelineParams = PaginationParams & WithMutedParam & OnlyEventsParam & LanguageParam;
+type LinkTimelineParams = PaginationParams & WithMutedParam & LanguageParam;
+type ListTimelineParams = PaginationParams & WithMutedParam & OnlyEventsParam & LanguageParam;
+
+interface GetConversationsParams extends PaginationParams, LanguageParam {
+ /**
+ * Only return conversations with the given recipients (a list of user ids).
+ * Requires `features.conversationsByRecipients`.
+ * */
+ recipients?: string[];
+}
+
+interface SaveMarkersParams {
+ home?: {
+ /** String. ID of the last status read in the home timeline. */
+ last_read_id?: string;
+ };
+ notifications?: {
+ /** String. ID of the last notification read. */
+ last_read_id?: string;
+ };
+}
+
+type GroupTimelineParams = PaginationParams & WithMutedParam & OnlyMediaParam & LanguageParam;
+type BubbleTimelineParams = PaginationParams & WithMutedParam & OnlyEventsParam & OnlyMediaParam & LanguageParam;
+
+export type {
+ PublicTimelineParams,
+ HashtagTimelineParams,
+ HomeTimelineParams,
+ LinkTimelineParams,
+ ListTimelineParams,
+ GetConversationsParams,
+ SaveMarkersParams,
+ GroupTimelineParams,
+ BubbleTimelineParams,
+};
diff --git a/packages/pl-api/lib/params/trends.ts b/packages/pl-api/lib/params/trends.ts
new file mode 100644
index 000000000..e167f3ca3
--- /dev/null
+++ b/packages/pl-api/lib/params/trends.ts
@@ -0,0 +1,16 @@
+interface GetTrends {
+ /** Integer. Maximum number of results to return. */
+ limit?: number;
+ /** Integer. Skip the first n results. */
+ offset?: number;
+}
+
+type GetTrendingTags = GetTrends;
+type GetTrendingStatuses = GetTrends;
+type GetTrendingLinks = GetTrends;
+
+export type {
+ GetTrendingTags,
+ GetTrendingStatuses,
+ GetTrendingLinks,
+};
diff --git a/packages/pl-api/lib/request.ts b/packages/pl-api/lib/request.ts
new file mode 100644
index 000000000..e9dff6858
--- /dev/null
+++ b/packages/pl-api/lib/request.ts
@@ -0,0 +1,137 @@
+import LinkHeader from 'http-link-header';
+import { serialize } from 'object-to-formdata';
+
+import PlApiClient from './client';
+import { buildFullPath } from './utils/url';
+
+type Response = {
+ headers: Headers;
+ ok: boolean;
+ redirected: boolean;
+ status: number;
+ statusText: string;
+ type: ResponseType;
+ url: string;
+ data: string;
+ json: T;
+};
+
+/**
+ Parse Link headers, mostly for pagination.
+ @param {object} response - Fetch API response object
+ @returns {object} Link object
+ */
+const getLinks = (response: Pick): LinkHeader =>
+ new LinkHeader(response.headers?.get('link') || undefined);
+
+const getNextLink = (response: Pick): string | null =>
+ getLinks(response).refs.find(link => link.rel.toLocaleLowerCase() === 'next')?.uri || null;
+
+const getPrevLink = (response: Pick): string | null =>
+ getLinks(response).refs.find(link => link.rel.toLocaleLowerCase() === 'prev')?.uri || null;
+
+interface RequestBody> {
+ method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
+ body?: any;
+ params?: Params;
+ onUploadProgress?: (e: ProgressEvent) => void;
+ signal?: AbortSignal;
+ contentType?: string;
+ idempotencyKey?: string;
+}
+
+type RequestMeta = Pick;
+
+function request(this: PlApiClient, input: URL | RequestInfo, {
+ body,
+ method = body ? 'POST' : 'GET',
+ params,
+ onUploadProgress,
+ signal,
+ contentType = 'application/json',
+ idempotencyKey,
+}: RequestBody = {}) {
+ const fullPath = buildFullPath(input.toString(), this.baseURL, params);
+ const headers = new Headers();
+
+ if (this.accessToken) headers.set('Authorization', `Bearer ${this.accessToken}`);
+ if (contentType !== '') headers.set('Content-Type', contentType);
+ if (idempotencyKey) headers.set('Idempotency-Key', contentType);
+
+ body = body && contentType === '' ? serialize(body, { indices: true }) : JSON.stringify(body);
+
+ // Fetch API doesn't report upload progress, use XHR
+ if (onUploadProgress) {
+ return new Promise>((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+
+ xhr.addEventListener('progress', onUploadProgress!);
+ xhr.addEventListener('loadend', () => {
+ const data = xhr.response;
+ let json: T = undefined!;
+
+ if (xhr.getResponseHeader('content-type')?.includes('application/json')) {
+ try {
+ json = JSON.parse(data);
+ } catch (e) {
+ //
+ }
+ }
+
+ if (xhr.status >= 400) reject({ response: {
+ status: xhr.status,
+ statusText: xhr.statusText,
+ url: xhr.responseURL,
+ data,
+ json,
+
+ } });
+ resolve({ status: xhr.status, data, json } as any as Response);
+ });
+
+ xhr.open(method, fullPath, true);
+ headers.forEach((value, key) => xhr.setRequestHeader(key, value));
+ xhr.responseType = 'text';
+ xhr.send(body as FormData);
+ });
+ }
+
+ return fetch(fullPath, {
+ method,
+ headers,
+ body,
+ signal,
+ }).then(async (res) => {
+ const data = await res.text();
+
+ let json: T = undefined!;
+
+ if (res.headers.get('content-type')?.includes('application/json')) {
+ try {
+ json = JSON.parse(data);
+ } catch (e) {
+ //
+ }
+ }
+
+ const { headers, ok, redirected, status, statusText, type, url } = res;
+
+ const response = { headers, ok, redirected, status, statusText, type, url, data, json };
+
+ if (!ok) {
+ throw { response };
+ }
+ return response as any as Response;
+ });
+}
+
+export {
+ type Response,
+ type RequestBody,
+ type RequestMeta,
+ getLinks,
+ getNextLink,
+ getPrevLink,
+ request,
+ request as default,
+};
diff --git a/packages/pl-api/lib/responses.ts b/packages/pl-api/lib/responses.ts
new file mode 100644
index 000000000..a50480071
--- /dev/null
+++ b/packages/pl-api/lib/responses.ts
@@ -0,0 +1,11 @@
+interface PaginatedResponse {
+ previous: (() => Promise>) | null;
+ next: (() => Promise>) | null;
+ items: Array;
+ partial: boolean;
+ total?: number;
+}
+
+export type {
+ PaginatedResponse,
+};
diff --git a/packages/pl-api/lib/utils/types.ts b/packages/pl-api/lib/utils/types.ts
new file mode 100644
index 000000000..d77033a72
--- /dev/null
+++ b/packages/pl-api/lib/utils/types.ts
@@ -0,0 +1,7 @@
+/**
+ * Resolve a type into a flat POJO interface if it's been wrapped by generics.
+ * https://gleasonator.com/@alex/posts/AWfK4hyppMDCqrT2y8
+ */
+type Resolve = Pick;
+
+export type { Resolve };
diff --git a/packages/pl-api/lib/utils/url.ts b/packages/pl-api/lib/utils/url.ts
new file mode 100644
index 000000000..a4fdf6e35
--- /dev/null
+++ b/packages/pl-api/lib/utils/url.ts
@@ -0,0 +1,25 @@
+import queryString from 'query-string';
+
+// Adapted from Axios https://github.com/axios/axios/blob/v1.x/lib/core/buildFullPath.js
+const isAbsoluteURL = (url: string) => /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
+
+const combineURLs = (baseURL: string, relativeURL: string) => relativeURL
+ ? baseURL.replace(/\/?\/$/, '') + '/' + relativeURL.replace(/^\/+/, '')
+ : baseURL;
+
+const buildFullPath = (requestedURL: string, baseURL?: string, params?: Record) => {
+ const path = (baseURL && !isAbsoluteURL(requestedURL)) ? combineURLs(baseURL, requestedURL) : requestedURL;
+
+ if (params && Object.entries(params).length) {
+ const { url, query } = queryString.parseUrl(path);
+ return `${url}?${queryString.stringify({ ...query, ...params }, { arrayFormat: 'bracket' })}`;
+ }
+
+ return path;
+};
+
+export {
+ isAbsoluteURL,
+ combineURLs,
+ buildFullPath,
+};
diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json
new file mode 100644
index 000000000..b51768940
--- /dev/null
+++ b/packages/pl-api/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "pl-api",
+ "version": "0.0.22",
+ "type": "module",
+ "homepage": "https://github.com/mkljczk/pl-api",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mkljczk/pl-api"
+ },
+ "bugs": {
+ "url": "https://github.com/mkljczk/pl-api/issues"
+ },
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc --p ./tsconfig-build.json && vite build",
+ "preview": "vite preview"
+ },
+ "license": "AGPL-3.0-or-later",
+ "devDependencies": {
+ "@types/http-link-header": "^1.0.7",
+ "@types/lodash.pick": "^4.4.9",
+ "@types/node": "^20.14.12",
+ "@types/semver": "^7.5.8",
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.49.0",
+ "eslint-import-resolver-typescript": "^3.6.0",
+ "eslint-plugin-compat": "^4.2.0",
+ "eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-promise": "^6.0.0",
+ "typescript": "^5.2.2",
+ "vite": "^5.4.0",
+ "vite-plugin-dts": "^4.0.3"
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
+ "dependencies": {
+ "blurhash": "^2.0.5",
+ "http-link-header": "^1.1.3",
+ "lodash.pick": "^4.4.0",
+ "object-to-formdata": "^4.5.1",
+ "query-string": "^9.1.0",
+ "semver": "^7.6.3",
+ "zod": "^3.23.8"
+ },
+ "module": "./dist/main.es.js",
+ "types": "dist/main.d.ts",
+ "files": [
+ "dist"
+ ]
+}
diff --git a/packages/pl-api/tsconfig-build.json b/packages/pl-api/tsconfig-build.json
new file mode 100644
index 000000000..160480b19
--- /dev/null
+++ b/packages/pl-api/tsconfig-build.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["lib"]
+ }
\ No newline at end of file
diff --git a/packages/pl-api/tsconfig.json b/packages/pl-api/tsconfig.json
new file mode 100644
index 000000000..dc67c03f5
--- /dev/null
+++ b/packages/pl-api/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src", "lib"]
+}
diff --git a/packages/pl-api/vite.config.ts b/packages/pl-api/vite.config.ts
new file mode 100644
index 000000000..79e513c5f
--- /dev/null
+++ b/packages/pl-api/vite.config.ts
@@ -0,0 +1,19 @@
+import { resolve } from 'path';
+
+import { defineConfig } from 'vite';
+import dts from 'vite-plugin-dts';
+
+export default defineConfig({
+ plugins: [dts({ include: ['lib'], insertTypesEntry: true })],
+ build: {
+ copyPublicDir: false,
+ lib: {
+ entry: resolve(__dirname, 'lib/main.ts'),
+ fileName: (format) => `main.${format}.js`,
+ formats: ['es'],
+ name: 'pl-api',
+ },
+ target: 'esnext',
+ sourcemap: true,
+ },
+});
diff --git a/packages/pl-api/yarn.lock b/packages/pl-api/yarn.lock
new file mode 100644
index 000000000..25504964d
--- /dev/null
+++ b/packages/pl-api/yarn.lock
@@ -0,0 +1,2611 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/parser@^7.24.7":
+ version "7.25.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad"
+ integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==
+
+"@esbuild/aix-ppc64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
+ integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
+
+"@esbuild/android-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
+ integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
+
+"@esbuild/android-arm@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
+ integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
+
+"@esbuild/android-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
+ integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
+
+"@esbuild/darwin-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
+ integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
+
+"@esbuild/darwin-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
+ integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
+
+"@esbuild/freebsd-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
+ integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
+
+"@esbuild/freebsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
+ integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
+
+"@esbuild/linux-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
+ integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
+
+"@esbuild/linux-arm@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
+ integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
+
+"@esbuild/linux-ia32@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
+ integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
+
+"@esbuild/linux-loong64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
+ integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
+
+"@esbuild/linux-mips64el@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
+ integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
+
+"@esbuild/linux-ppc64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
+ integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
+
+"@esbuild/linux-riscv64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
+ integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
+
+"@esbuild/linux-s390x@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
+ integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
+
+"@esbuild/linux-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
+ integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
+
+"@esbuild/netbsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
+ integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
+
+"@esbuild/openbsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
+ integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
+
+"@esbuild/sunos-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
+ integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
+
+"@esbuild/win32-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
+ integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
+
+"@esbuild/win32-ia32@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
+ integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
+
+"@esbuild/win32-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
+ integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
+
+"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
+ integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
+ dependencies:
+ eslint-visitor-keys "^3.3.0"
+
+"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
+ version "4.11.0"
+ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
+ integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
+
+"@eslint/eslintrc@^2.1.4":
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
+ integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^9.6.0"
+ globals "^13.19.0"
+ ignore "^5.2.0"
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ minimatch "^3.1.2"
+ strip-json-comments "^3.1.1"
+
+"@eslint/js@8.57.0":
+ version "8.57.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
+ integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
+
+"@humanwhocodes/config-array@^0.11.14":
+ version "0.11.14"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
+ integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==
+ dependencies:
+ "@humanwhocodes/object-schema" "^2.0.2"
+ debug "^4.3.1"
+ minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^2.0.2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
+ integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
+
+"@jridgewell/sourcemap-codec@^1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
+ integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
+
+"@mdn/browser-compat-data@^5.2.34", "@mdn/browser-compat-data@^5.3.13":
+ version "5.5.42"
+ resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.5.42.tgz#440ae830c460bfe8d53bae9b2542904ebf015c22"
+ integrity sha512-qhHVgb2dxaFNT00Z1upHaDCstUEjjrgtIkrk4tr+YnDSGbTIKncbdydIpSed+RCXz0f6nb4UDD4eKEWokNom6g==
+
+"@microsoft/api-extractor-model@7.29.4":
+ version "7.29.4"
+ resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.29.4.tgz#098f94f304db98f3cea8618fd1107946e212eaf5"
+ integrity sha512-LHOMxmT8/tU1IiiiHOdHFF83Qsi+V8d0kLfscG4EvQE9cafiR8blOYr8SfkQKWB1wgEilQgXJX3MIA4vetDLZw==
+ dependencies:
+ "@microsoft/tsdoc" "~0.15.0"
+ "@microsoft/tsdoc-config" "~0.17.0"
+ "@rushstack/node-core-library" "5.5.1"
+
+"@microsoft/api-extractor@7.47.4":
+ version "7.47.4"
+ resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.47.4.tgz#1a66dc9d6f316fe86eef336e7f8004ab9222499b"
+ integrity sha512-HKm+P4VNzWwvq1Ey+Jfhhj/3MjsD+ka2hbt8L5AcRM95lu1MFOYnz3XlU7Gr79Q/ZhOb7W/imAKeYrOI0bFydg==
+ dependencies:
+ "@microsoft/api-extractor-model" "7.29.4"
+ "@microsoft/tsdoc" "~0.15.0"
+ "@microsoft/tsdoc-config" "~0.17.0"
+ "@rushstack/node-core-library" "5.5.1"
+ "@rushstack/rig-package" "0.5.3"
+ "@rushstack/terminal" "0.13.3"
+ "@rushstack/ts-command-line" "4.22.3"
+ lodash "~4.17.15"
+ minimatch "~3.0.3"
+ resolve "~1.22.1"
+ semver "~7.5.4"
+ source-map "~0.6.1"
+ typescript "5.4.2"
+
+"@microsoft/tsdoc-config@~0.17.0":
+ version "0.17.0"
+ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz#82605152b3c1d3f5cd4a11697bc298437484d55d"
+ integrity sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==
+ dependencies:
+ "@microsoft/tsdoc" "0.15.0"
+ ajv "~8.12.0"
+ jju "~1.4.0"
+ resolve "~1.22.2"
+
+"@microsoft/tsdoc@0.15.0", "@microsoft/tsdoc@~0.15.0":
+ version "0.15.0"
+ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz#f29a55df17cb6e87cfbabce33ff6a14a9f85076d"
+ integrity sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+"@rollup/pluginutils@^5.1.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0"
+ integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-walker "^2.0.2"
+ picomatch "^2.3.1"
+
+"@rollup/rollup-android-arm-eabi@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz#7746deb85e4a8fb54fbfda8ac5c102692f102476"
+ integrity sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==
+
+"@rollup/rollup-android-arm64@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz#93de4d867709d3313794723b5afd91e1e174f906"
+ integrity sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==
+
+"@rollup/rollup-darwin-arm64@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz#e41e6a81673260ab196e0f59462b9940a6ac03cd"
+ integrity sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==
+
+"@rollup/rollup-darwin-x64@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz#2b0a0aef6e8c5317d494cfc9076d7a16b099bdcb"
+ integrity sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz#e22319deb5367384ef315e66bc6de80d2bf2b3ae"
+ integrity sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==
+
+"@rollup/rollup-linux-arm-musleabihf@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz#d5dd68f5d7ae21b345a5c87208c94e5c813f54b8"
+ integrity sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==
+
+"@rollup/rollup-linux-arm64-gnu@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz#1703d3a418d33f8f025acaf93f39ca1efcd5b645"
+ integrity sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==
+
+"@rollup/rollup-linux-arm64-musl@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz#3f59c2c6e60f75ce8b1090bd841c555e3bb01f0e"
+ integrity sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==
+
+"@rollup/rollup-linux-powerpc64le-gnu@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz#3f99a0921596a6f539121a312df29af52a205f15"
+ integrity sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==
+
+"@rollup/rollup-linux-riscv64-gnu@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz#c08fb3e629d50d2eac31329347cfc559a1cf81d1"
+ integrity sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==
+
+"@rollup/rollup-linux-s390x-gnu@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz#173722cd745779d730d4b24d21386185e0e12de8"
+ integrity sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==
+
+"@rollup/rollup-linux-x64-gnu@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz#0af2b6541ab0f4954d2c4f96bcdc7947420dd28c"
+ integrity sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==
+
+"@rollup/rollup-linux-x64-musl@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz#f973f9552744764b221128f7c3629222216ace69"
+ integrity sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==
+
+"@rollup/rollup-win32-arm64-msvc@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz#21ac5ed84d914bc31821fec3dd909f7257cfb17b"
+ integrity sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==
+
+"@rollup/rollup-win32-ia32-msvc@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz#0cfe740063b35dcd5a62c4e243226631a846ce11"
+ integrity sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==
+
+"@rollup/rollup-win32-x64-msvc@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz#5f2c40d3f1b53ede80fb4e6964f840c0f8936832"
+ integrity sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==
+
+"@rushstack/node-core-library@5.5.1":
+ version "5.5.1"
+ resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-5.5.1.tgz#890db37eafaab582c79eb6bf421447b82b3a964b"
+ integrity sha512-ZutW56qIzH8xIOlfyaLQJFx+8IBqdbVCZdnj+XT1MorQ1JqqxHse8vbCpEM+2MjsrqcbxcgDIbfggB1ZSQ2A3g==
+ dependencies:
+ ajv "~8.13.0"
+ ajv-draft-04 "~1.0.0"
+ ajv-formats "~3.0.1"
+ fs-extra "~7.0.1"
+ import-lazy "~4.0.0"
+ jju "~1.4.0"
+ resolve "~1.22.1"
+ semver "~7.5.4"
+
+"@rushstack/rig-package@0.5.3":
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.5.3.tgz#ea4d8a3458540b1295500149c04e645f23134e5d"
+ integrity sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==
+ dependencies:
+ resolve "~1.22.1"
+ strip-json-comments "~3.1.1"
+
+"@rushstack/terminal@0.13.3":
+ version "0.13.3"
+ resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.13.3.tgz#9a05b8cf759f14161a49d3ccb09d556e4161caca"
+ integrity sha512-fc3zjXOw8E0pXS5t9vTiIPx9gHA0fIdTXsu9mT4WbH+P3mYvnrX0iAQ5a6NvyK1+CqYWBTw/wVNx7SDJkI+WYQ==
+ dependencies:
+ "@rushstack/node-core-library" "5.5.1"
+ supports-color "~8.1.1"
+
+"@rushstack/ts-command-line@4.22.3":
+ version "4.22.3"
+ resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.22.3.tgz#dcc75bd25b21031b32b2758ee3f2f4973b112572"
+ integrity sha512-edMpWB3QhFFZ4KtSzS8WNjBgR4PXPPOVrOHMbb7kNpmQ1UFS9HdVtjCXg1H5fG+xYAbeE+TMPcVPUyX2p84STA==
+ dependencies:
+ "@rushstack/terminal" "0.13.3"
+ "@types/argparse" "1.0.38"
+ argparse "~1.0.9"
+ string-argv "~0.3.1"
+
+"@types/argparse@1.0.38":
+ version "1.0.38"
+ resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9"
+ integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==
+
+"@types/estree@1.0.5", "@types/estree@^1.0.0":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
+ integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
+
+"@types/http-link-header@^1.0.7":
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/@types/http-link-header/-/http-link-header-1.0.7.tgz#bb1a1671a8c6d93717e0057072e9253113fdc875"
+ integrity sha512-snm5oLckop0K3cTDAiBnZDy6ncx9DJ3mCRDvs42C884MbVYPP74Tiq2hFsSDRTyjK6RyDYDIulPiW23ge+g5Lw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/json5@^0.0.29":
+ version "0.0.29"
+ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
+ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
+
+"@types/lodash.pick@^4.4.9":
+ version "4.4.9"
+ resolved "https://registry.yarnpkg.com/@types/lodash.pick/-/lodash.pick-4.4.9.tgz#06f7d88faa81a6c5665584778aea7b1374a1dc5b"
+ integrity sha512-hDpr96x9xHClwy1KX4/RXRejqjDFTEGbEMT3t6wYSYeFDzxmMnSKB/xHIbktRlPj8Nii2g8L5dtFDRaNFBEzUQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash@*":
+ version "4.17.7"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.7.tgz#2f776bcb53adc9e13b2c0dfd493dfcbd7de43612"
+ integrity sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==
+
+"@types/node@*", "@types/node@^20.14.12":
+ version "20.14.12"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49"
+ integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==
+ dependencies:
+ undici-types "~5.26.4"
+
+"@types/semver@^7.5.8":
+ version "7.5.8"
+ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
+ integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
+
+"@typescript-eslint/eslint-plugin@^7.0.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz#c8ed1af1ad2928ede5cdd207f7e3090499e1f77b"
+ integrity sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==
+ dependencies:
+ "@eslint-community/regexpp" "^4.10.0"
+ "@typescript-eslint/scope-manager" "7.17.0"
+ "@typescript-eslint/type-utils" "7.17.0"
+ "@typescript-eslint/utils" "7.17.0"
+ "@typescript-eslint/visitor-keys" "7.17.0"
+ graphemer "^1.4.0"
+ ignore "^5.3.1"
+ natural-compare "^1.4.0"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/parser@^7.0.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.17.0.tgz#be8e32c159190cd40a305a2121220eadea5a88e7"
+ integrity sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==
+ dependencies:
+ "@typescript-eslint/scope-manager" "7.17.0"
+ "@typescript-eslint/types" "7.17.0"
+ "@typescript-eslint/typescript-estree" "7.17.0"
+ "@typescript-eslint/visitor-keys" "7.17.0"
+ debug "^4.3.4"
+
+"@typescript-eslint/scope-manager@7.17.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz#e072d0f914662a7bfd6c058165e3c2b35ea26b9d"
+ integrity sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==
+ dependencies:
+ "@typescript-eslint/types" "7.17.0"
+ "@typescript-eslint/visitor-keys" "7.17.0"
+
+"@typescript-eslint/type-utils@7.17.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz#c5da78feb134c9c9978cbe89e2b1a589ed22091a"
+ integrity sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==
+ dependencies:
+ "@typescript-eslint/typescript-estree" "7.17.0"
+ "@typescript-eslint/utils" "7.17.0"
+ debug "^4.3.4"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/types@7.17.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.17.0.tgz#7ce8185bdf06bc3494e73d143dbf3293111b9cff"
+ integrity sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==
+
+"@typescript-eslint/typescript-estree@7.17.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz#dcab3fea4c07482329dd6107d3c6480e228e4130"
+ integrity sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==
+ dependencies:
+ "@typescript-eslint/types" "7.17.0"
+ "@typescript-eslint/visitor-keys" "7.17.0"
+ debug "^4.3.4"
+ globby "^11.1.0"
+ is-glob "^4.0.3"
+ minimatch "^9.0.4"
+ semver "^7.6.0"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/utils@7.17.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.17.0.tgz#815cd85b9001845d41b699b0ce4f92d6dfb84902"
+ integrity sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.4.0"
+ "@typescript-eslint/scope-manager" "7.17.0"
+ "@typescript-eslint/types" "7.17.0"
+ "@typescript-eslint/typescript-estree" "7.17.0"
+
+"@typescript-eslint/visitor-keys@7.17.0":
+ version "7.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz#680465c734be30969e564b4647f38d6cdf49bfb0"
+ integrity sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==
+ dependencies:
+ "@typescript-eslint/types" "7.17.0"
+ eslint-visitor-keys "^3.4.3"
+
+"@ungap/structured-clone@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
+ integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
+
+"@volar/language-core@2.3.4":
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.3.4.tgz#51de0263039a567a12a1eea90e02e59cdbf5de3b"
+ integrity sha512-wXBhY11qG6pCDAqDnbBRFIDSIwbqkWI7no+lj5+L7IlA7HRIjRP7YQLGzT0LF4lS6eHkMSsclXqy9DwYJasZTQ==
+ dependencies:
+ "@volar/source-map" "2.3.4"
+
+"@volar/language-core@2.4.0-alpha.18", "@volar/language-core@~2.4.0-alpha.18":
+ version "2.4.0-alpha.18"
+ resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.0-alpha.18.tgz#dafffd68ac07c26d69de16741187fd4c06bfa345"
+ integrity sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==
+ dependencies:
+ "@volar/source-map" "2.4.0-alpha.18"
+
+"@volar/source-map@2.3.4":
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.3.4.tgz#1d285610134fe565ca59a54e5a99c12befc70c93"
+ integrity sha512-C+t63nwcblqLIVTYXaVi/+gC8NukDaDIQI72J3R7aXGvtgaVB16c+J8Iz7/VfOy7kjYv7lf5GhBny6ACw9fTGQ==
+
+"@volar/source-map@2.4.0-alpha.18":
+ version "2.4.0-alpha.18"
+ resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.0-alpha.18.tgz#a2413932ff6b1821ae8efcbd9249d4da3f99f223"
+ integrity sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==
+
+"@volar/typescript@^2.3.4":
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.3.4.tgz#bfa2834c79bd0b9a38cdfdf220fea0afa8ed64b0"
+ integrity sha512-acCvt7dZECyKcvO5geNybmrqOsu9u8n5XP1rfiYsOLYGPxvHRav9BVmEdRyZ3vvY6mNyQ1wLL5Hday4IShe17w==
+ dependencies:
+ "@volar/language-core" "2.3.4"
+ path-browserify "^1.0.1"
+ vscode-uri "^3.0.8"
+
+"@volar/typescript@~2.4.0-alpha.18":
+ version "2.4.0-alpha.18"
+ resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.0-alpha.18.tgz#806aca9ce1bd7c48dc5fcd0fcf7f33bdd04e5b35"
+ integrity sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==
+ dependencies:
+ "@volar/language-core" "2.4.0-alpha.18"
+ path-browserify "^1.0.1"
+ vscode-uri "^3.0.8"
+
+"@vue/compiler-core@3.4.34":
+ version "3.4.34"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.34.tgz#4e6af7a00927284f1f67571e2e1a8a6e93ee2d1f"
+ integrity sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==
+ dependencies:
+ "@babel/parser" "^7.24.7"
+ "@vue/shared" "3.4.34"
+ entities "^4.5.0"
+ estree-walker "^2.0.2"
+ source-map-js "^1.2.0"
+
+"@vue/compiler-dom@^3.4.0":
+ version "3.4.34"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.34.tgz#fd3b8df142b063c2cc0ec3e168b76b0d7774b78c"
+ integrity sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==
+ dependencies:
+ "@vue/compiler-core" "3.4.34"
+ "@vue/shared" "3.4.34"
+
+"@vue/compiler-vue2@^2.7.16":
+ version "2.7.16"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz#2ba837cbd3f1b33c2bc865fbe1a3b53fb611e249"
+ integrity sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==
+ dependencies:
+ de-indent "^1.0.2"
+ he "^1.2.0"
+
+"@vue/language-core@2.0.29":
+ version "2.0.29"
+ resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.29.tgz#19462d786cd7a1c21dbe575b46970a57094e0357"
+ integrity sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==
+ dependencies:
+ "@volar/language-core" "~2.4.0-alpha.18"
+ "@vue/compiler-dom" "^3.4.0"
+ "@vue/compiler-vue2" "^2.7.16"
+ "@vue/shared" "^3.4.0"
+ computeds "^0.0.1"
+ minimatch "^9.0.3"
+ muggle-string "^0.4.1"
+ path-browserify "^1.0.1"
+
+"@vue/shared@3.4.34", "@vue/shared@^3.4.0":
+ version "3.4.34"
+ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.34.tgz#130858419e634a427ca82c36e1da75c66a39ba8e"
+ integrity sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==
+
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn@^8.11.3, acorn@^8.9.0:
+ version "8.12.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
+ integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
+
+ajv-draft-04@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8"
+ integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==
+
+ajv-formats@~3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578"
+ integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==
+ dependencies:
+ ajv "^8.0.0"
+
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ajv@^8.0.0:
+ version "8.17.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
+ integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ fast-uri "^3.0.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+
+ajv@~8.12.0:
+ version "8.12.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
+ integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
+ajv@~8.13.0:
+ version "8.13.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91"
+ integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.4.1"
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+argparse@~1.0.9:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+ dependencies:
+ sprintf-js "~1.0.2"
+
+array-buffer-byte-length@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f"
+ integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==
+ dependencies:
+ call-bind "^1.0.5"
+ is-array-buffer "^3.0.4"
+
+array-includes@^3.1.7:
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d"
+ integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-object-atoms "^1.0.0"
+ get-intrinsic "^1.2.4"
+ is-string "^1.0.7"
+
+array-union@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+ integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
+array.prototype.findlastindex@^1.2.3:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d"
+ integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ es-shim-unscopables "^1.0.2"
+
+array.prototype.flat@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18"
+ integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.2.0"
+ es-abstract "^1.22.1"
+ es-shim-unscopables "^1.0.0"
+
+array.prototype.flatmap@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527"
+ integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.2.0"
+ es-abstract "^1.22.1"
+ es-shim-unscopables "^1.0.0"
+
+arraybuffer.prototype.slice@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6"
+ integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==
+ dependencies:
+ array-buffer-byte-length "^1.0.1"
+ call-bind "^1.0.5"
+ define-properties "^1.2.1"
+ es-abstract "^1.22.3"
+ es-errors "^1.2.1"
+ get-intrinsic "^1.2.3"
+ is-array-buffer "^3.0.4"
+ is-shared-array-buffer "^1.0.2"
+
+ast-metadata-inferer@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz#0f94c3425e310d8da45823ab2161142e3f134343"
+ integrity sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==
+ dependencies:
+ "@mdn/browser-compat-data" "^5.2.34"
+
+available-typed-arrays@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
+ integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==
+ dependencies:
+ possible-typed-array-names "^1.0.0"
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+blurhash@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.5.tgz#efde729fc14a2f03571a6aa91b49cba80d1abe4b"
+ integrity sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+ integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+ dependencies:
+ fill-range "^7.1.1"
+
+browserslist@^4.21.10:
+ version "4.23.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed"
+ integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==
+ dependencies:
+ caniuse-lite "^1.0.30001640"
+ electron-to-chromium "^1.4.820"
+ node-releases "^2.0.14"
+ update-browserslist-db "^1.1.0"
+
+call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
+ integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.1"
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+caniuse-lite@^1.0.30001524, caniuse-lite@^1.0.30001640:
+ version "1.0.30001643"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd"
+ integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==
+
+chalk@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+compare-versions@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.1.tgz#7af3cc1099ba37d244b3145a9af5201b629148a9"
+ integrity sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==
+
+computeds@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/computeds/-/computeds-0.0.1.tgz#215b08a4ba3e08a11ff6eee5d6d8d7166a97ce2e"
+ integrity sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+confbox@^0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.7.tgz#ccfc0a2bcae36a84838e83a3b7f770fb17d6c579"
+ integrity sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==
+
+cross-spawn@^7.0.2:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+data-view-buffer@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2"
+ integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==
+ dependencies:
+ call-bind "^1.0.6"
+ es-errors "^1.3.0"
+ is-data-view "^1.0.1"
+
+data-view-byte-length@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2"
+ integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==
+ dependencies:
+ call-bind "^1.0.7"
+ es-errors "^1.3.0"
+ is-data-view "^1.0.1"
+
+data-view-byte-offset@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a"
+ integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==
+ dependencies:
+ call-bind "^1.0.6"
+ es-errors "^1.3.0"
+ is-data-view "^1.0.1"
+
+de-indent@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
+ integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
+
+debug@^3.2.7:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
+ integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+ dependencies:
+ ms "^2.1.1"
+
+debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.6:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b"
+ integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
+ dependencies:
+ ms "2.1.2"
+
+decode-uri-component@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5"
+ integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==
+
+deep-is@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
+ integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
+define-data-property@^1.0.1, define-data-property@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
+define-properties@^1.2.0, define-properties@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
+dir-glob@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+ integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+ dependencies:
+ path-type "^4.0.0"
+
+doctrine@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
+ integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
+ dependencies:
+ esutils "^2.0.2"
+
+doctrine@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+ integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+ dependencies:
+ esutils "^2.0.2"
+
+electron-to-chromium@^1.4.820:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19"
+ integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ==
+
+enhanced-resolve@^5.12.0:
+ version "5.17.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
+ integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+entities@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2:
+ version "1.23.3"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0"
+ integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==
+ dependencies:
+ array-buffer-byte-length "^1.0.1"
+ arraybuffer.prototype.slice "^1.0.3"
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.7"
+ data-view-buffer "^1.0.1"
+ data-view-byte-length "^1.0.1"
+ data-view-byte-offset "^1.0.0"
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ es-set-tostringtag "^2.0.3"
+ es-to-primitive "^1.2.1"
+ function.prototype.name "^1.1.6"
+ get-intrinsic "^1.2.4"
+ get-symbol-description "^1.0.2"
+ globalthis "^1.0.3"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+ has-proto "^1.0.3"
+ has-symbols "^1.0.3"
+ hasown "^2.0.2"
+ internal-slot "^1.0.7"
+ is-array-buffer "^3.0.4"
+ is-callable "^1.2.7"
+ is-data-view "^1.0.1"
+ is-negative-zero "^2.0.3"
+ is-regex "^1.1.4"
+ is-shared-array-buffer "^1.0.3"
+ is-string "^1.0.7"
+ is-typed-array "^1.1.13"
+ is-weakref "^1.0.2"
+ object-inspect "^1.13.1"
+ object-keys "^1.1.1"
+ object.assign "^4.1.5"
+ regexp.prototype.flags "^1.5.2"
+ safe-array-concat "^1.1.2"
+ safe-regex-test "^1.0.3"
+ string.prototype.trim "^1.2.9"
+ string.prototype.trimend "^1.0.8"
+ string.prototype.trimstart "^1.0.8"
+ typed-array-buffer "^1.0.2"
+ typed-array-byte-length "^1.0.1"
+ typed-array-byte-offset "^1.0.2"
+ typed-array-length "^1.0.6"
+ unbox-primitive "^1.0.2"
+ which-typed-array "^1.1.15"
+
+es-define-property@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
+ integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
+ dependencies:
+ get-intrinsic "^1.2.4"
+
+es-errors@^1.2.1, es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+es-object-atoms@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941"
+ integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==
+ dependencies:
+ es-errors "^1.3.0"
+
+es-set-tostringtag@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777"
+ integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==
+ dependencies:
+ get-intrinsic "^1.2.4"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.1"
+
+es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763"
+ integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==
+ dependencies:
+ hasown "^2.0.0"
+
+es-to-primitive@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
+ integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
+ dependencies:
+ is-callable "^1.1.4"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.2"
+
+esbuild@^0.21.3:
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
+ integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.21.5"
+ "@esbuild/android-arm" "0.21.5"
+ "@esbuild/android-arm64" "0.21.5"
+ "@esbuild/android-x64" "0.21.5"
+ "@esbuild/darwin-arm64" "0.21.5"
+ "@esbuild/darwin-x64" "0.21.5"
+ "@esbuild/freebsd-arm64" "0.21.5"
+ "@esbuild/freebsd-x64" "0.21.5"
+ "@esbuild/linux-arm" "0.21.5"
+ "@esbuild/linux-arm64" "0.21.5"
+ "@esbuild/linux-ia32" "0.21.5"
+ "@esbuild/linux-loong64" "0.21.5"
+ "@esbuild/linux-mips64el" "0.21.5"
+ "@esbuild/linux-ppc64" "0.21.5"
+ "@esbuild/linux-riscv64" "0.21.5"
+ "@esbuild/linux-s390x" "0.21.5"
+ "@esbuild/linux-x64" "0.21.5"
+ "@esbuild/netbsd-x64" "0.21.5"
+ "@esbuild/openbsd-x64" "0.21.5"
+ "@esbuild/sunos-x64" "0.21.5"
+ "@esbuild/win32-arm64" "0.21.5"
+ "@esbuild/win32-ia32" "0.21.5"
+ "@esbuild/win32-x64" "0.21.5"
+
+escalade@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
+ integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+eslint-import-resolver-node@^0.3.9:
+ version "0.3.9"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac"
+ integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==
+ dependencies:
+ debug "^3.2.7"
+ is-core-module "^2.13.0"
+ resolve "^1.22.4"
+
+eslint-import-resolver-typescript@^3.6.0:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa"
+ integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==
+ dependencies:
+ debug "^4.3.4"
+ enhanced-resolve "^5.12.0"
+ eslint-module-utils "^2.7.4"
+ fast-glob "^3.3.1"
+ get-tsconfig "^4.5.0"
+ is-core-module "^2.11.0"
+ is-glob "^4.0.3"
+
+eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34"
+ integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==
+ dependencies:
+ debug "^3.2.7"
+
+eslint-plugin-compat@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-compat/-/eslint-plugin-compat-4.2.0.tgz#eeaf80daa1afe495c88a47e9281295acae45c0aa"
+ integrity sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w==
+ dependencies:
+ "@mdn/browser-compat-data" "^5.3.13"
+ ast-metadata-inferer "^0.8.0"
+ browserslist "^4.21.10"
+ caniuse-lite "^1.0.30001524"
+ find-up "^5.0.0"
+ lodash.memoize "^4.1.2"
+ semver "^7.5.4"
+
+eslint-plugin-import@^2.28.1:
+ version "2.29.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643"
+ integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==
+ dependencies:
+ array-includes "^3.1.7"
+ array.prototype.findlastindex "^1.2.3"
+ array.prototype.flat "^1.3.2"
+ array.prototype.flatmap "^1.3.2"
+ debug "^3.2.7"
+ doctrine "^2.1.0"
+ eslint-import-resolver-node "^0.3.9"
+ eslint-module-utils "^2.8.0"
+ hasown "^2.0.0"
+ is-core-module "^2.13.1"
+ is-glob "^4.0.3"
+ minimatch "^3.1.2"
+ object.fromentries "^2.0.7"
+ object.groupby "^1.0.1"
+ object.values "^1.1.7"
+ semver "^6.3.1"
+ tsconfig-paths "^3.15.0"
+
+eslint-plugin-promise@^6.0.0:
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz#acd3fd7d55cead7a10f92cf698f36c0aafcd717a"
+ integrity sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==
+
+eslint-scope@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+ integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+eslint@^8.49.0:
+ version "8.57.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
+ integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.6.1"
+ "@eslint/eslintrc" "^2.1.4"
+ "@eslint/js" "8.57.0"
+ "@humanwhocodes/config-array" "^0.11.14"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@nodelib/fs.walk" "^1.2.8"
+ "@ungap/structured-clone" "^1.2.0"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
+ doctrine "^3.0.0"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^7.2.2"
+ eslint-visitor-keys "^3.4.3"
+ espree "^9.6.1"
+ esquery "^1.4.2"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ globals "^13.19.0"
+ graphemer "^1.4.0"
+ ignore "^5.2.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ is-path-inside "^3.0.3"
+ js-yaml "^4.1.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+espree@^9.6.0, espree@^9.6.1:
+ version "9.6.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+ integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
+ dependencies:
+ acorn "^8.9.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.4.1"
+
+esquery@^1.4.2:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estree-walker@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.2.9, fast-glob@^3.3.1:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
+ integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fast-uri@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134"
+ integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==
+
+fastq@^1.6.0:
+ version "1.17.1"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
+ integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
+ dependencies:
+ reusify "^1.0.4"
+
+file-entry-cache@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
+ integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+ dependencies:
+ flat-cache "^3.0.4"
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+filter-obj@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
+ integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+flat-cache@^3.0.4:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
+ integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.3"
+ rimraf "^3.0.2"
+
+flatted@^3.2.9:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
+ integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
+
+for-each@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
+ integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
+ dependencies:
+ is-callable "^1.1.3"
+
+fs-extra@~7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+ integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+fsevents@~2.3.2, fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+function.prototype.name@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd"
+ integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.2.0"
+ es-abstract "^1.22.1"
+ functions-have-names "^1.2.3"
+
+functions-have-names@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
+ integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
+
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
+ integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ hasown "^2.0.0"
+
+get-symbol-description@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5"
+ integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==
+ dependencies:
+ call-bind "^1.0.5"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.4"
+
+get-tsconfig@^4.5.0:
+ version "4.7.6"
+ resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.6.tgz#118fd5b7b9bae234cc7705a00cd771d7eb65d62a"
+ integrity sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==
+ dependencies:
+ resolve-pkg-maps "^1.0.0"
+
+glob-parent@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob@^7.1.3:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^13.19.0:
+ version "13.24.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
+ integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
+ dependencies:
+ type-fest "^0.20.2"
+
+globalthis@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236"
+ integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==
+ dependencies:
+ define-properties "^1.2.1"
+ gopd "^1.0.1"
+
+globby@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+ integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.9"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^3.0.0"
+
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4:
+ version "4.2.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
+has-bigints@^1.0.1, has-bigints@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
+ integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-proto@^1.0.1, has-proto@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
+ integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
+
+has-symbols@^1.0.2, has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
+hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
+he@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+http-link-header@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.3.tgz#b367b7a0ad1cf14027953f31aa1df40bb433da2a"
+ integrity sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==
+
+ignore@^5.2.0, ignore@^5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
+ integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
+
+import-fresh@^3.2.1:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+ integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+import-lazy@~4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
+ integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+internal-slot@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802"
+ integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==
+ dependencies:
+ es-errors "^1.3.0"
+ hasown "^2.0.0"
+ side-channel "^1.0.4"
+
+is-array-buffer@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98"
+ integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==
+ dependencies:
+ call-bind "^1.0.2"
+ get-intrinsic "^1.2.1"
+
+is-bigint@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
+ integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
+ dependencies:
+ has-bigints "^1.0.1"
+
+is-boolean-object@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
+ integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
+is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
+is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1:
+ version "2.15.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea"
+ integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==
+ dependencies:
+ hasown "^2.0.2"
+
+is-data-view@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f"
+ integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==
+ dependencies:
+ is-typed-array "^1.1.13"
+
+is-date-object@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
+ integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-negative-zero@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747"
+ integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==
+
+is-number-object@^1.0.4:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc"
+ integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-path-inside@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
+is-regex@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
+ integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
+is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688"
+ integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==
+ dependencies:
+ call-bind "^1.0.7"
+
+is-string@^1.0.5, is-string@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
+ integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
+is-symbol@^1.0.2, is-symbol@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
+ integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
+ dependencies:
+ has-symbols "^1.0.2"
+
+is-typed-array@^1.1.13:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229"
+ integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==
+ dependencies:
+ which-typed-array "^1.1.14"
+
+is-weakref@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
+ integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
+ dependencies:
+ call-bind "^1.0.2"
+
+isarray@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
+ integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+jju@~1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
+ integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+json5@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
+ integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
+ dependencies:
+ minimist "^1.2.0"
+
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+ integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+keyv@^4.5.3:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+kolorist@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
+ integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+local-pkg@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.0.tgz#093d25a346bae59a99f80e75f6e9d36d7e8c925c"
+ integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==
+ dependencies:
+ mlly "^1.4.2"
+ pkg-types "^1.0.3"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+ integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lodash.pick@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
+ integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
+
+lodash@~4.17.15:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
+magic-string@^0.30.11:
+ version "0.30.11"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954"
+ integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.0"
+
+merge2@^1.3.0, merge2@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+micromatch@^4.0.4:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5"
+ integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==
+ dependencies:
+ braces "^3.0.3"
+ picomatch "^2.3.1"
+
+minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimatch@^9.0.3, minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@~3.0.3:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1"
+ integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.0, minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+mlly@^1.4.2, mlly@^1.7.1:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.1.tgz#e0336429bb0731b6a8e887b438cbdae522c8f32f"
+ integrity sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==
+ dependencies:
+ acorn "^8.11.3"
+ pathe "^1.1.2"
+ pkg-types "^1.1.1"
+ ufo "^1.5.3"
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+ms@^2.1.1:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+muggle-string@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328"
+ integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==
+
+nanoid@^3.3.7:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
+node-releases@^2.0.14:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
+ integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
+
+object-inspect@^1.13.1:
+ version "1.13.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
+ integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
+
+object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object-to-formdata@^4.5.1:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/object-to-formdata/-/object-to-formdata-4.5.1.tgz#b6955a9c505b58df15852fee5f844b418b3eb6fe"
+ integrity sha512-QiM9D0NiU5jV6J6tjE1g7b4Z2tcUnKs1OPUi4iMb2zH+7jwlcUrASghgkFk9GtzqNNq8rTQJtT8AzjBAvLoNMw==
+
+object.assign@^4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
+ integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
+ dependencies:
+ call-bind "^1.0.5"
+ define-properties "^1.2.1"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
+
+object.fromentries@^2.0.7:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65"
+ integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-object-atoms "^1.0.0"
+
+object.groupby@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e"
+ integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+
+object.values@^1.1.7:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b"
+ integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.5"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+path-browserify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
+ integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+pathe@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
+ integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
+
+picocolors@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
+ integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
+
+picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pkg-types@^1.0.3, pkg-types@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.1.3.tgz#161bb1242b21daf7795036803f28e30222e476e3"
+ integrity sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==
+ dependencies:
+ confbox "^0.1.7"
+ mlly "^1.7.1"
+ pathe "^1.1.2"
+
+possible-typed-array-names@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
+ integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==
+
+postcss@^8.4.40:
+ version "8.4.41"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681"
+ integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
+ dependencies:
+ nanoid "^3.3.7"
+ picocolors "^1.0.1"
+ source-map-js "^1.2.0"
+
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+punycode@^2.1.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+query-string@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.1.0.tgz#5f12a4653a4ba56021e113b5cf58e56581823e7a"
+ integrity sha512-t6dqMECpCkqfyv2FfwVS1xcB6lgXW/0XZSaKdsCNGYkqMO76AFiJEg4vINzoDKcZa6MS7JX+OHIjwh06K5vczw==
+ dependencies:
+ decode-uri-component "^0.4.1"
+ filter-obj "^5.1.0"
+ split-on-first "^3.0.0"
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+regexp.prototype.flags@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334"
+ integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==
+ dependencies:
+ call-bind "^1.0.6"
+ define-properties "^1.2.1"
+ es-errors "^1.3.0"
+ set-function-name "^2.0.1"
+
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve-pkg-maps@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
+ integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
+
+resolve@^1.22.4, resolve@~1.22.1, resolve@~1.22.2:
+ version "1.22.8"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
+ integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
+ dependencies:
+ is-core-module "^2.13.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+rollup@^4.13.0:
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.1.tgz#21d865cd60d4a325172ce8b082e60caccd97b309"
+ integrity sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==
+ dependencies:
+ "@types/estree" "1.0.5"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.19.1"
+ "@rollup/rollup-android-arm64" "4.19.1"
+ "@rollup/rollup-darwin-arm64" "4.19.1"
+ "@rollup/rollup-darwin-x64" "4.19.1"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.19.1"
+ "@rollup/rollup-linux-arm-musleabihf" "4.19.1"
+ "@rollup/rollup-linux-arm64-gnu" "4.19.1"
+ "@rollup/rollup-linux-arm64-musl" "4.19.1"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.19.1"
+ "@rollup/rollup-linux-riscv64-gnu" "4.19.1"
+ "@rollup/rollup-linux-s390x-gnu" "4.19.1"
+ "@rollup/rollup-linux-x64-gnu" "4.19.1"
+ "@rollup/rollup-linux-x64-musl" "4.19.1"
+ "@rollup/rollup-win32-arm64-msvc" "4.19.1"
+ "@rollup/rollup-win32-ia32-msvc" "4.19.1"
+ "@rollup/rollup-win32-x64-msvc" "4.19.1"
+ fsevents "~2.3.2"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+safe-array-concat@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb"
+ integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==
+ dependencies:
+ call-bind "^1.0.7"
+ get-intrinsic "^1.2.4"
+ has-symbols "^1.0.3"
+ isarray "^2.0.5"
+
+safe-regex-test@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
+ integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==
+ dependencies:
+ call-bind "^1.0.6"
+ es-errors "^1.3.0"
+ is-regex "^1.1.4"
+
+semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+semver@^7.5.4, semver@^7.6.0, semver@^7.6.3:
+ version "7.6.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+semver@~7.5.4:
+ version "7.5.4"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
+ integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
+ dependencies:
+ lru-cache "^6.0.0"
+
+set-function-length@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+
+set-function-name@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985"
+ integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ functions-have-names "^1.2.3"
+ has-property-descriptors "^1.0.2"
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+side-channel@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
+ integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
+ dependencies:
+ call-bind "^1.0.7"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.4"
+ object-inspect "^1.13.1"
+
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
+source-map-js@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
+ integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
+
+source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+split-on-first@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7"
+ integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+ integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+
+string-argv@~0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
+ integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
+
+string.prototype.trim@^1.2.9:
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4"
+ integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.0"
+ es-object-atoms "^1.0.0"
+
+string.prototype.trimend@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229"
+ integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
+string.prototype.trimstart@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde"
+ integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
+strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+ integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
+
+strip-json-comments@^3.1.1, strip-json-comments@~3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@~8.1.1:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+tapable@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
+text-table@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+ integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+ts-api-utils@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
+ integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
+
+tsconfig-paths@^3.15.0:
+ version "3.15.0"
+ resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
+ integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==
+ dependencies:
+ "@types/json5" "^0.0.29"
+ json5 "^1.0.2"
+ minimist "^1.2.6"
+ strip-bom "^3.0.0"
+
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
+typed-array-buffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3"
+ integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==
+ dependencies:
+ call-bind "^1.0.7"
+ es-errors "^1.3.0"
+ is-typed-array "^1.1.13"
+
+typed-array-byte-length@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67"
+ integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==
+ dependencies:
+ call-bind "^1.0.7"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-proto "^1.0.3"
+ is-typed-array "^1.1.13"
+
+typed-array-byte-offset@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063"
+ integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.7"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-proto "^1.0.3"
+ is-typed-array "^1.1.13"
+
+typed-array-length@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3"
+ integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==
+ dependencies:
+ call-bind "^1.0.7"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-proto "^1.0.3"
+ is-typed-array "^1.1.13"
+ possible-typed-array-names "^1.0.0"
+
+typescript@5.4.2:
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372"
+ integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==
+
+typescript@^5.2.2:
+ version "5.5.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
+ integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
+
+ufo@^1.5.3:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"
+ integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==
+
+unbox-primitive@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
+ integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
+ dependencies:
+ call-bind "^1.0.2"
+ has-bigints "^1.0.2"
+ has-symbols "^1.0.3"
+ which-boxed-primitive "^1.0.2"
+
+undici-types@~5.26.4:
+ version "5.26.5"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
+ integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
+update-browserslist-db@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"
+ integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==
+ dependencies:
+ escalade "^3.1.2"
+ picocolors "^1.0.1"
+
+uri-js@^4.2.2, uri-js@^4.4.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+vite-plugin-dts@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-4.0.3.tgz#0f452be5ea81a50fa27ade4e0f3a0d750873affc"
+ integrity sha512-+xnTsaONwU2kV6zhRjtbRJSGN41uFR/whqmcb4k4fftLFDJElxthp0PP5Fq8gMeM9ytWMt1yk5gGgekLREWYQQ==
+ dependencies:
+ "@microsoft/api-extractor" "7.47.4"
+ "@rollup/pluginutils" "^5.1.0"
+ "@volar/typescript" "^2.3.4"
+ "@vue/language-core" "2.0.29"
+ compare-versions "^6.1.1"
+ debug "^4.3.6"
+ kolorist "^1.8.0"
+ local-pkg "^0.5.0"
+ magic-string "^0.30.11"
+ vue-tsc "2.0.29"
+
+vite@^5.4.0:
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.0.tgz#11dca8a961369ba8b5cae42d068c7ad684d5370f"
+ integrity sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==
+ dependencies:
+ esbuild "^0.21.3"
+ postcss "^8.4.40"
+ rollup "^4.13.0"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+vscode-uri@^3.0.8:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
+ integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
+
+vue-tsc@2.0.29:
+ version "2.0.29"
+ resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.29.tgz#bf7e9605af9fadec7fd6037d242217f5c6ad2c3b"
+ integrity sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==
+ dependencies:
+ "@volar/typescript" "~2.4.0-alpha.18"
+ "@vue/language-core" "2.0.29"
+ semver "^7.5.4"
+
+which-boxed-primitive@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
+ integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
+ dependencies:
+ is-bigint "^1.0.1"
+ is-boolean-object "^1.1.0"
+ is-number-object "^1.0.4"
+ is-string "^1.0.5"
+ is-symbol "^1.0.3"
+
+which-typed-array@^1.1.14, which-typed-array@^1.1.15:
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d"
+ integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.7"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-tostringtag "^1.0.2"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+zod@^3.23.8:
+ version "3.23.8"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
+ integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==