pl-api: allow importing parts of the client
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
131
packages/pl-api/lib/client-base.ts
Normal file
131
packages/pl-api/lib/client-base.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { instanceSchema } from './entities/instance';
|
||||
import { filteredArray } from './entities/utils';
|
||||
import { type Features, getFeatures } from './features';
|
||||
import request, { getNextLink, getPrevLink, type RequestBody } from './request';
|
||||
|
||||
import type { Instance } from './entities/instance';
|
||||
import type { Response as PlApiResponse } from './request';
|
||||
import type { PaginatedResponse } from './responses';
|
||||
|
||||
interface PlApiClientConstructorOpts {
|
||||
/** Instance object to use by default, to be populated eg. from cache */
|
||||
instance?: Instance;
|
||||
/** Custom authorization token to use for requests */
|
||||
customAuthorizationToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Mastodon API client.
|
||||
* For example usage, see {@link PlApiClient}.
|
||||
* @category Clients
|
||||
*/
|
||||
class PlApiBaseClient {
|
||||
baseURL: string;
|
||||
#accessToken?: string;
|
||||
#iceshrimpAccessToken?: string;
|
||||
#customAuthorizationToken?: string;
|
||||
#instance: Instance = v.parse(instanceSchema, {});
|
||||
public request = request.bind(this) as typeof request;
|
||||
public features: Features = getFeatures(this.#instance);
|
||||
/** @internal */
|
||||
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;
|
||||
};
|
||||
/** @internal */
|
||||
shoutSocket?: {
|
||||
message: (text: string) => void;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param baseURL Mastodon API-compatible server URL
|
||||
* @param accessToken OAuth token for an authorized user
|
||||
*/
|
||||
constructor(baseURL: string, accessToken?: string, opts: PlApiClientConstructorOpts = {}) {
|
||||
this.baseURL = baseURL;
|
||||
this.#accessToken = accessToken;
|
||||
this.#customAuthorizationToken = opts.customAuthorizationToken;
|
||||
|
||||
if (opts.instance) {
|
||||
this.setInstance(opts.instance);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
paginatedGet = async <T, IsArray extends true | false = true>(
|
||||
input: URL | RequestInfo,
|
||||
body: RequestBody,
|
||||
schema: v.BaseSchema<any, T, v.BaseIssue<unknown>>,
|
||||
isArray = true as IsArray,
|
||||
): Promise<PaginatedResponse<T, typeof isArray>> => {
|
||||
const targetSchema = isArray ? filteredArray(schema) : schema;
|
||||
|
||||
const processResponse = (response: PlApiResponse<any>) =>
|
||||
({
|
||||
previous: getMore(getPrevLink(response)),
|
||||
next: getMore(getNextLink(response)),
|
||||
items: v.parse(targetSchema, response.json),
|
||||
partial: response.status === 206,
|
||||
}) as PaginatedResponse<T, IsArray>;
|
||||
|
||||
const getMore = (input: string | null) =>
|
||||
input ? () => this.request(input).then(processResponse) : null;
|
||||
|
||||
const response = await this.request(input, body);
|
||||
|
||||
return processResponse(response);
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
setInstance = (instance: Instance) => {
|
||||
this.#instance = instance;
|
||||
this.features = getFeatures(this.#instance);
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
getIceshrimpAccessToken = async (): Promise<void> => {
|
||||
// No-op in the base client, overridden in PlApiClient
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
setIceshrimpAccessToken(token: string) {
|
||||
this.#iceshrimpAccessToken = token;
|
||||
}
|
||||
|
||||
get accessToken(): string | undefined {
|
||||
return this.#accessToken;
|
||||
}
|
||||
|
||||
set accessToken(accessToken: string | undefined) {
|
||||
if (this.#accessToken === accessToken) return;
|
||||
|
||||
this.socket?.close();
|
||||
this.#accessToken = accessToken;
|
||||
|
||||
this.getIceshrimpAccessToken();
|
||||
}
|
||||
|
||||
get iceshrimpAccessToken(): string | undefined {
|
||||
return this.#iceshrimpAccessToken;
|
||||
}
|
||||
|
||||
get customAuthorizationToken(): string | undefined {
|
||||
return this.#customAuthorizationToken;
|
||||
}
|
||||
|
||||
set customAuthorizationToken(token: string | undefined) {
|
||||
this.#customAuthorizationToken = token;
|
||||
}
|
||||
|
||||
get instanceInformation() {
|
||||
return this.#instance;
|
||||
}
|
||||
}
|
||||
|
||||
export { PlApiBaseClient, type PlApiClientConstructorOpts };
|
||||
File diff suppressed because it is too large
Load Diff
453
packages/pl-api/lib/client/accounts.ts
Normal file
453
packages/pl-api/lib/client/accounts.ts
Normal file
@ -0,0 +1,453 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
accountSchema,
|
||||
antennaSchema,
|
||||
circleSchema,
|
||||
familiarFollowersSchema,
|
||||
featuredTagSchema,
|
||||
listSchema,
|
||||
relationshipSchema,
|
||||
reportSchema,
|
||||
scrobbleSchema,
|
||||
statusSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { ICESHRIMP_NET, PIXELFED, PLEROMA } from '../features';
|
||||
import { type RequestMeta } from '../request';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
CreateScrobbleParams,
|
||||
FollowAccountParams,
|
||||
GetAccountEndorsementsParams,
|
||||
GetAccountFavouritesParams,
|
||||
GetAccountFollowersParams,
|
||||
GetAccountFollowingParams,
|
||||
GetAccountParams,
|
||||
GetAccountStatusesParams,
|
||||
GetAccountSubscribersParams,
|
||||
GetRelationshipsParams,
|
||||
GetScrobblesParams,
|
||||
ReportAccountParams,
|
||||
SearchAccountParams,
|
||||
} from '../params/accounts';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const accounts = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request(`/api/v1/accounts/${accountId}`, { params });
|
||||
|
||||
return v.parse(accountSchema, 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 client.request('/api/v1/accounts', { params: { id: accountId } });
|
||||
|
||||
return v.parse(filteredArray(accountSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get account’s statuses
|
||||
* Statuses posted to the given account.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/accounts/#statuses}
|
||||
*/
|
||||
getAccountStatuses: (accountId: string, params?: GetAccountStatusesParams) =>
|
||||
client.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: (accountId: string, params?: GetAccountFollowersParams) =>
|
||||
client.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: (accountId: string, params?: GetAccountFollowingParams) =>
|
||||
client.paginatedGet(`/api/v1/accounts/${accountId}/following`, { params }, accountSchema),
|
||||
|
||||
/**
|
||||
* Subscriptions to the given user.
|
||||
*
|
||||
* Requires features{@link Features.subscriptions}.
|
||||
*/
|
||||
getAccountSubscribers: (accountId: string, params?: GetAccountSubscribersParams) =>
|
||||
client.paginatedGet(`/api/v1/accounts/${accountId}/subscribers`, { params }, accountSchema),
|
||||
|
||||
/**
|
||||
* Get account’s featured tags
|
||||
* Tags featured by this account.
|
||||
*
|
||||
* Requires features{@link Features.featuredTags}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/accounts/#featured_tags}
|
||||
*/
|
||||
getAccountFeaturedTags: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/accounts/${accountId}/featured_tags`);
|
||||
|
||||
return v.parse(filteredArray(featuredTagSchema), 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 client.request(`/api/v1/accounts/${accountId}/lists`);
|
||||
|
||||
return v.parse(filteredArray(listSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get antennas containing this account
|
||||
* User antennas that you have added this account to.
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAccountAntennas: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/accounts/${accountId}/antennas`);
|
||||
|
||||
return v.parse(filteredArray(antennaSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get antennas excluding this account
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAccountExcludeAntennas: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/accounts/${accountId}/exclude_antennas`);
|
||||
|
||||
return v.parse(filteredArray(circleSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get circles including this account
|
||||
* User circles that you have added this account to.
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
getAccountCircles: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/accounts/${accountId}/circles`);
|
||||
|
||||
return v.parse(filteredArray(antennaSchema), 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 client.request(`/api/v1/accounts/${accountId}/follow`, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unfollow account
|
||||
* Unfollow the given account.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/accounts/#unfollow}
|
||||
*/
|
||||
unfollowAccount: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/accounts/${accountId}/unfollow`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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 client.request(`/api/v1/accounts/${accountId}/remove_from_followers`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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 client.request(`/api/v1/accounts/${accountId}/pin`, { method: 'POST' });
|
||||
|
||||
return v.parse(relationshipSchema, 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 client.request(`/api/v1/accounts/${accountId}/unpin`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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 client.request(`/api/v1/accounts/${accountId}/note`, {
|
||||
method: 'POST',
|
||||
body: { comment },
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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 client.request('/api/v1/accounts/relationships', {
|
||||
params: { ...params, id: accountIds },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(relationshipSchema), 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[]) => {
|
||||
let response: any;
|
||||
|
||||
if (client.features.version.software === PIXELFED) {
|
||||
const settledResponse = await Promise.allSettled(
|
||||
accountIds.map(async (accountId) => {
|
||||
const accounts = (await client.request(`/api/v1.1/accounts/mutuals/${accountId}`)).json;
|
||||
|
||||
return {
|
||||
id: accountId,
|
||||
accounts,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
response = settledResponse.map((result, index) =>
|
||||
result.status === 'fulfilled'
|
||||
? result.value
|
||||
: {
|
||||
id: accountIds[index],
|
||||
accounts: [],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
response = (
|
||||
await client.request('/api/v1/accounts/familiar_followers', { params: { id: accountIds } })
|
||||
).json;
|
||||
}
|
||||
|
||||
return v.parse(filteredArray(familiarFollowersSchema), response);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request('/api/v1/accounts/search', {
|
||||
...meta,
|
||||
params: { ...params, q },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(accountSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup account ID from Webfinger address
|
||||
* Quickly lookup a username to see if it is available, skipping WebFinger resolution.
|
||||
|
||||
* Requires features{@link Features.accountLookup}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/accounts/#lookup}
|
||||
*/
|
||||
lookupAccount: async (acct: string, meta?: RequestMeta) => {
|
||||
const response = await client.request('/api/v1/accounts/lookup', { ...meta, params: { acct } });
|
||||
|
||||
return v.parse(accountSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* File a report
|
||||
* @see {@link https://docs.joinmastodon.org/methods/reports/#post}
|
||||
*/
|
||||
reportAccount: async (accountId: string, params: ReportAccountParams) => {
|
||||
const response = await client.request('/api/v1/reports', {
|
||||
method: 'POST',
|
||||
body: { ...params, account_id: accountId },
|
||||
});
|
||||
|
||||
return v.parse(reportSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Endorsements
|
||||
* Returns endorsed accounts
|
||||
*
|
||||
* Requires features{@link Features.accountEndorsements}.
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaaccountsidendorsements}
|
||||
* @see {@link https://docs.joinmastodon.org/methods/accounts/endorsements}
|
||||
*/
|
||||
getAccountEndorsements: (accountId: string, params?: GetAccountEndorsementsParams) =>
|
||||
client.paginatedGet(
|
||||
`/api/v1/${[PLEROMA].includes(client.features.version.software as string) ? 'pleroma/' : ''}accounts/${accountId}/endorsements`,
|
||||
{ params },
|
||||
accountSchema,
|
||||
),
|
||||
|
||||
/**
|
||||
* Birthday reminders
|
||||
* Birthday reminders about users you follow.
|
||||
*
|
||||
* Requires features{@link Features.birthdays}.
|
||||
*/
|
||||
getBirthdays: async (day: number, month: number) => {
|
||||
const response = await client.request('/api/v1/pleroma/birthdays', { params: { day, month } });
|
||||
|
||||
return v.parse(filteredArray(accountSchema), 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: (accountId: string, params?: GetAccountFavouritesParams) =>
|
||||
client.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 client.request('/api/v1/pleroma/remote_interaction', {
|
||||
method: 'POST',
|
||||
body: { ap_id, profile },
|
||||
});
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
url: v.string(),
|
||||
}),
|
||||
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 (accountId: string) => {
|
||||
let response;
|
||||
switch (client.features.version.software) {
|
||||
case ICESHRIMP_NET:
|
||||
response = await client.request<EmptyObject>('/api/v1/bite', {
|
||||
method: 'POST',
|
||||
body: accountId,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request<EmptyObject>('/api/v1/bite', {
|
||||
method: 'POST',
|
||||
params: { id: accountId },
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests a list of current and recent Listen activities for an account
|
||||
*
|
||||
* Requires features{@link Features.scrobbles}
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromaaccountsidscrobbles}
|
||||
*/
|
||||
getScrobbles: (accountId: string, params?: GetScrobblesParams) =>
|
||||
client.paginatedGet(
|
||||
`/api/v1/pleroma/accounts/${accountId}/scrobbles`,
|
||||
{ params },
|
||||
scrobbleSchema,
|
||||
),
|
||||
|
||||
/**
|
||||
* Creates a new Listen activity for an account
|
||||
*
|
||||
* Requires features{@link Features.scrobbles}
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromascrobble}
|
||||
*/
|
||||
createScrobble: async (params: CreateScrobbleParams) => {
|
||||
if (params.external_link) (params as any).externalLink = params.external_link;
|
||||
|
||||
const response = await client.request('/api/v1/pleroma/scrobble', { body: params });
|
||||
|
||||
return v.parse(scrobbleSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load latest activities from outbox
|
||||
*
|
||||
* Requires features{@link Features.loadActivities}
|
||||
*/
|
||||
loadActivities: async (accountId: string) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/accounts/${accountId}/load_activities`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { accounts };
|
||||
1556
packages/pl-api/lib/client/admin.ts
Normal file
1556
packages/pl-api/lib/client/admin.ts
Normal file
File diff suppressed because it is too large
Load Diff
64
packages/pl-api/lib/client/announcements.ts
Normal file
64
packages/pl-api/lib/client/announcements.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { announcementSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const announcements = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/api/v1/announcements');
|
||||
|
||||
return v.parse(filteredArray(announcementSchema), 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 client.request<EmptyObject>(`/api/v1/announcements/${announcementId}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request<EmptyObject>(
|
||||
`/api/v1/announcements/${announcementId}/reactions/${emoji}`,
|
||||
{ method: 'PUT' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request<EmptyObject>(
|
||||
`/api/v1/announcements/${announcementId}/reactions/${emoji}`,
|
||||
{ method: 'DELETE' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { announcements };
|
||||
336
packages/pl-api/lib/client/antennas.ts
Normal file
336
packages/pl-api/lib/client/antennas.ts
Normal file
@ -0,0 +1,336 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { accountSchema, antennaSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { CreateAntennaParams, UpdateAntennaParams } from '../params/antennas';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const antennas = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
fetchAntennas: async () => {
|
||||
const response = await client.request('/api/v1/antennas');
|
||||
|
||||
return v.parse(filteredArray(antennaSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAntennas: async (antennaId: string) => {
|
||||
const response = await client.request(`/api/v1/antennas/${antennaId}`);
|
||||
|
||||
return v.parse(antennaSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
createAntenna: async (params: CreateAntennaParams) => {
|
||||
const response = await client.request('/api/v1/antennas', { method: 'POST', body: params });
|
||||
|
||||
return v.parse(antennaSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
updateAntenna: async (antennaId: string, params: UpdateAntennaParams) => {
|
||||
const response = await client.request(`/api/v1/antennas/${antennaId}`, {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(antennaSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
deleteAntenna: async (antennaId: string) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAntennaAccounts: (antennaId: string) =>
|
||||
client.paginatedGet(`/api/v1/antennas/${antennaId}/accounts`, {}, accountSchema),
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaAccounts: async (antennaId: string, accountIds: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/accounts`, {
|
||||
method: 'POST',
|
||||
body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaAccounts: async (antennaId: string, accountIds: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/accounts`, {
|
||||
method: 'DELETE',
|
||||
body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAntennaExcludedAccounts: (antennaId: string) =>
|
||||
client.paginatedGet(`/api/v1/antennas/${antennaId}/exclude_accounts`, {}, accountSchema),
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaExcludedAccounts: async (antennaId: string, accountIds: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_accounts`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: { account_ids: accountIds },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaExcludedAccounts: async (antennaId: string, accountIds: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_accounts`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
body: { account_ids: accountIds },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAntennaDomains: async (antennaId: string) => {
|
||||
const response = await client.request(`/api/v1/antennas/${antennaId}/domains`);
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
domains: filteredArray(v.string()),
|
||||
exclude_domains: filteredArray(v.string()),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaDomains: async (antennaId: string, domains: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/domains`, {
|
||||
method: 'POST',
|
||||
body: { domains },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaDomains: async (antennaId: string, domains: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/domains`, {
|
||||
method: 'DELETE',
|
||||
body: { domains },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaExcludedDomains: async (antennaId: string, domains: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_domains`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: { domains },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaExcludedDomains: async (antennaId: string, domains: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_domains`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
body: { domains },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAntennaKeywords: async (antennaId: string) => {
|
||||
const response = await client.request(`/api/v1/antennas/${antennaId}/keywords`);
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
keywords: filteredArray(v.string()),
|
||||
exclude_keywords: filteredArray(v.string()),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaKeywords: async (antennaId: string, keywords: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/keywords`, {
|
||||
method: 'POST',
|
||||
body: { keywords },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaKeywords: async (antennaId: string, keywords: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/keywords`, {
|
||||
method: 'DELETE',
|
||||
body: { keywords },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaExcludedKeywords: async (antennaId: string, keywords: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_keywords`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: { keywords },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaExcludedKeywords: async (antennaId: string, keywords: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_keywords`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
body: { keywords },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
getAntennaTags: async (antennaId: string) => {
|
||||
const response = await client.request(`/api/v1/antennas/${antennaId}/tags`);
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
tags: filteredArray(v.string()),
|
||||
exclude_tags: filteredArray(v.string()),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaTags: async (antennaId: string, tags: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/tags`, {
|
||||
method: 'POST',
|
||||
body: { tags },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaTags: async (antennaId: string, tags: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/antennas/${antennaId}/tags`, {
|
||||
method: 'DELETE',
|
||||
body: { tags },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
addAntennaExcludedTags: async (antennaId: string, tags: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_tags`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: { tags },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
removeAntennaExcludedTags: async (antennaId: string, tags: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/antennas/${antennaId}/exclude_tags`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
body: { tags },
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { antennas };
|
||||
33
packages/pl-api/lib/client/apps.ts
Normal file
33
packages/pl-api/lib/client/apps.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { applicationSchema, credentialApplicationSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { CreateApplicationParams } from '../params/apps';
|
||||
|
||||
/** Register client applications that can be used to obtain OAuth tokens. */
|
||||
const apps = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/api/v1/apps', { method: 'POST', body: params });
|
||||
|
||||
return v.parse(credentialApplicationSchema, 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 client.request('/api/v1/apps/verify_credentials');
|
||||
|
||||
return v.parse(applicationSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { apps };
|
||||
20
packages/pl-api/lib/client/async-refreshes.ts
Normal file
20
packages/pl-api/lib/client/async-refreshes.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { asyncRefreshSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
/** Experimental async refreshes API methods */
|
||||
const asyncRefreshes = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Get Status of Async Refresh
|
||||
* @see {@link https://docs.joinmastodon.org/methods/async_refreshes/#show}
|
||||
*/
|
||||
show: async (id: string) => {
|
||||
const response = await client.request(`/api/v1_alpha/async_refreshes/${id}`);
|
||||
|
||||
return v.parse(asyncRefreshSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { asyncRefreshes };
|
||||
117
packages/pl-api/lib/client/chats.ts
Normal file
117
packages/pl-api/lib/client/chats.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { chatMessageSchema, chatSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
CreateChatMessageParams,
|
||||
GetChatMessagesParams,
|
||||
GetChatsParams,
|
||||
} from '../params/chats';
|
||||
|
||||
/** @see {@link https://docs.pleroma.social/backend/development/API/chats} */
|
||||
const chats = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request(`/api/v1/pleroma/chats/by-account-id/${accountId}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(chatSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/chats/#creating-or-getting-a-chat}
|
||||
*/
|
||||
getChat: async (chatId: string) => {
|
||||
const response = await client.request(`/api/v1/pleroma/chats/${chatId}`);
|
||||
|
||||
return v.parse(chatSchema, 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 client.request(`/api/v1/pleroma/chats/${chatId}/read`, {
|
||||
method: 'POST',
|
||||
body: { last_read_id },
|
||||
});
|
||||
|
||||
return v.parse(chatSchema, 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 client.request(
|
||||
`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}/read`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return v.parse(chatSchema, 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: (params?: GetChatsParams) =>
|
||||
client.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: (chatId: string, params?: GetChatMessagesParams) =>
|
||||
client.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 client.request(`/api/v1/pleroma/chats/${chatId}/messages`, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(chatMessageSchema, 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 client.request(`/api/v1/pleroma/chats/${chatId}/messages/${messageId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return v.parse(chatMessageSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deleting a chat
|
||||
*
|
||||
* Requires features{@link Features.chatsDelete}.
|
||||
*/
|
||||
deleteChat: async (chatId: string) => {
|
||||
const response = await client.request(`/api/v1/pleroma/chats/${chatId}`, { method: 'DELETE' });
|
||||
|
||||
return v.parse(chatSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { chats };
|
||||
101
packages/pl-api/lib/client/circles.ts
Normal file
101
packages/pl-api/lib/client/circles.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { accountSchema, circleSchema, statusSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { GetCircleAccountsParams, GetCircleStatusesParams } from '../params/circles';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const circles = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
fetchCircles: async () => {
|
||||
const response = await client.request('/api/v1/circles');
|
||||
|
||||
return v.parse(filteredArray(circleSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
getCircle: async (circleId: string) => {
|
||||
const response = await client.request(`/api/v1/circles/${circleId}`);
|
||||
|
||||
return v.parse(circleSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
createCircle: async (title: string) => {
|
||||
const response = await client.request('/api/v1/circles', { method: 'POST', body: { title } });
|
||||
|
||||
return v.parse(circleSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
updateCircle: async (circleId: string, title: string) => {
|
||||
const response = await client.request(`/api/v1/circles/${circleId}`, {
|
||||
method: 'PUT',
|
||||
body: { title },
|
||||
});
|
||||
|
||||
return v.parse(circleSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
deleteCircle: async (circleId: string) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/circles/${circleId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* View accounts in a circle
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
getCircleAccounts: (circleId: string, params?: GetCircleAccountsParams) =>
|
||||
client.paginatedGet(`/api/v1/circles/${circleId}/accounts`, { params }, accountSchema),
|
||||
|
||||
/**
|
||||
* Add accounts to a circle
|
||||
* Add accounts to the given circle. Note that the user must be following these accounts.
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
addCircleAccounts: async (circleId: string, accountIds: string[]) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/circles/${circleId}/accounts`, {
|
||||
method: 'POST',
|
||||
body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove accounts from circle
|
||||
* Remove accounts from the given circle.
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
deleteCircleAccounts: async (circleId: string, accountIds: string[]) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/circles/${circleId}/accounts`, {
|
||||
method: 'DELETE',
|
||||
body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
getCircleStatuses: (circleId: string, params: GetCircleStatusesParams) =>
|
||||
client.paginatedGet(`/api/v1/circles/${circleId}/statuses`, { params }, statusSchema),
|
||||
});
|
||||
|
||||
export { circles };
|
||||
132
packages/pl-api/lib/client/drive.ts
Normal file
132
packages/pl-api/lib/client/drive.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { driveFileSchema, driveFolderSchema, driveStatusSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { UpdateFileParams } from '../params/drive';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const drive = (client: PlApiBaseClient) => ({
|
||||
getDrive: async () => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request('/api/iceshrimp/drive/folder');
|
||||
|
||||
return v.parse(driveFolderSchema, response.json);
|
||||
},
|
||||
|
||||
getFolder: async (id: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request(`/api/iceshrimp/drive/folder/${id}`);
|
||||
|
||||
return v.parse(driveFolderSchema, response.json);
|
||||
},
|
||||
|
||||
createFolder: async (name: string, parentId?: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request('/api/iceshrimp/drive/folder', {
|
||||
method: 'POST',
|
||||
body: { name, parentId: parentId || null },
|
||||
});
|
||||
|
||||
return v.parse(driveFolderSchema, response.json);
|
||||
},
|
||||
|
||||
updateFolder: async (id: string, name: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request(`/api/iceshrimp/drive/folder/${id}`, {
|
||||
method: 'PUT',
|
||||
body: name,
|
||||
});
|
||||
|
||||
return v.parse(driveFolderSchema, response.json);
|
||||
},
|
||||
|
||||
deleteFolder: async (id: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request<EmptyObject>(`/api/iceshrimp/drive/folder/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
moveFolder: async (id: string, targetFolderId?: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request(`/api/iceshrimp/drive/folder/${id}/move`, {
|
||||
method: 'POST',
|
||||
body: { folderId: targetFolderId || null },
|
||||
});
|
||||
|
||||
return v.parse(driveFolderSchema, response.json);
|
||||
},
|
||||
|
||||
getFile: async (id: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request(`/api/iceshrimp/drive/${id}`);
|
||||
|
||||
return v.parse(driveFileSchema, response.json);
|
||||
},
|
||||
|
||||
createFile: async (file: File, folderId?: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request('/api/iceshrimp/drive', {
|
||||
method: 'POST',
|
||||
body: { file },
|
||||
params: { folderId },
|
||||
contentType: '',
|
||||
});
|
||||
|
||||
return v.parse(driveFileSchema, response.json);
|
||||
},
|
||||
|
||||
updateFile: async (id: string, params: UpdateFileParams) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request(`/api/iceshrimp/drive/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(driveFileSchema, response.json);
|
||||
},
|
||||
|
||||
deleteFile: async (id: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request<Record<string, never>>(`/api/iceshrimp/drive/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
moveFile: async (id: string, targetFolderId?: string) => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request(`/api/iceshrimp/drive/${id}/move`, {
|
||||
method: 'POST',
|
||||
body: { folderId: targetFolderId || null },
|
||||
});
|
||||
|
||||
return v.parse(driveFileSchema, response.json);
|
||||
},
|
||||
|
||||
getDriveStatus: async () => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request('/api/iceshrimp/drive/status');
|
||||
|
||||
return v.parse(driveStatusSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { drive };
|
||||
16
packages/pl-api/lib/client/emails.ts
Normal file
16
packages/pl-api/lib/client/emails.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const emails = (client: PlApiBaseClient) => ({
|
||||
resendConfirmationEmail: async (email: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/v1/emails/confirmations', {
|
||||
method: 'POST',
|
||||
body: { email },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { emails };
|
||||
141
packages/pl-api/lib/client/events.ts
Normal file
141
packages/pl-api/lib/client/events.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { accountSchema, statusSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
CreateEventParams,
|
||||
EditEventParams,
|
||||
GetEventParticipationRequestsParams,
|
||||
GetEventParticipationsParams,
|
||||
GetJoinedEventsParams,
|
||||
} from '../params/events';
|
||||
|
||||
const events = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Creates an event
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events}
|
||||
*/
|
||||
createEvent: async (params: CreateEventParams) => {
|
||||
const response = await client.request('/api/v1/pleroma/events', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Edits an event
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-id}
|
||||
*/
|
||||
editEvent: async (statusId: string, params: EditEventParams) => {
|
||||
const response = await client.request(`/api/v1/pleroma/events/${statusId}`, {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets user's joined events
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-joined_events}
|
||||
*/
|
||||
getJoinedEvents: (state?: 'pending' | 'reject' | 'accept', params?: GetJoinedEventsParams) =>
|
||||
client.paginatedGet(
|
||||
'/api/v1/pleroma/events/joined_events',
|
||||
{ params: { ...params, state } },
|
||||
statusSchema,
|
||||
),
|
||||
|
||||
/**
|
||||
* Gets event participants
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-id-participations}
|
||||
*/
|
||||
getEventParticipations: (statusId: string, params?: GetEventParticipationsParams) =>
|
||||
client.paginatedGet(
|
||||
`/api/v1/pleroma/events/${statusId}/participations`,
|
||||
{ params },
|
||||
accountSchema,
|
||||
),
|
||||
|
||||
/**
|
||||
* Gets event participation requests
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-id-participation_requests}
|
||||
*/
|
||||
getEventParticipationRequests: (statusId: string, params?: GetEventParticipationRequestsParams) =>
|
||||
client.paginatedGet(
|
||||
`/api/v1/pleroma/events/${statusId}/participation_requests`,
|
||||
{ params },
|
||||
v.object({
|
||||
account: accountSchema,
|
||||
participation_message: v.fallback(v.string(), ''),
|
||||
}),
|
||||
),
|
||||
|
||||
/**
|
||||
* Accepts user to the event
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-id-participation_requests-participant_id-authorize}
|
||||
*/
|
||||
acceptEventParticipationRequest: async (statusId: string, accountId: string) => {
|
||||
const response = await client.request(
|
||||
`/api/v1/pleroma/events/${statusId}/participation_requests/${accountId}/authorize`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Rejects user from the event
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-id-participation_requests-participant_id-reject}
|
||||
*/
|
||||
rejectEventParticipationRequest: async (statusId: string, accountId: string) => {
|
||||
const response = await client.request(
|
||||
`/api/v1/pleroma/events/${statusId}/participation_requests/${accountId}/reject`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Joins the event
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-id-join}
|
||||
*/
|
||||
joinEvent: async (statusId: string, participation_message?: string) => {
|
||||
const response = await client.request(`/api/v1/pleroma/events/${statusId}/join`, {
|
||||
method: 'POST',
|
||||
body: { participation_message },
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Leaves the event
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#api-v1-pleroma-events-id-leave}
|
||||
*/
|
||||
leaveEvent: async (statusId: string) => {
|
||||
const response = await client.request(`/api/v1/pleroma/events/${statusId}/leave`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event ICS file
|
||||
* @see {@link https://codeberg.org/mkljczk/nicolex/src/branch/develop/docs/development/API/pleroma_api.md#event-ics-file}
|
||||
*/
|
||||
getEventIcs: async (statusId: string) => {
|
||||
const response = await client.request(`/api/v1/pleroma/events/${statusId}/ics`, {
|
||||
contentType: '',
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
|
||||
export { events };
|
||||
296
packages/pl-api/lib/client/experimental.ts
Normal file
296
packages/pl-api/lib/client/experimental.ts
Normal file
@ -0,0 +1,296 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
accountSchema,
|
||||
groupMemberSchema,
|
||||
groupRelationshipSchema,
|
||||
groupSchema,
|
||||
statusSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { PIXELFED } from '../features';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { GroupRole } from '../entities';
|
||||
import type { AdminGetGroupsParams } from '../params/admin';
|
||||
import type {
|
||||
CreateGroupParams,
|
||||
GetGroupBlocksParams,
|
||||
GetGroupMembershipRequestsParams,
|
||||
GetGroupMembershipsParams,
|
||||
UpdateGroupParams,
|
||||
} from '../params/groups';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
/** Routes that are not part of any stable release */
|
||||
const experimental = (client: PlApiBaseClient) => {
|
||||
const category = {
|
||||
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 client.request('/api/v1/admin/groups', { params });
|
||||
|
||||
return v.parse(filteredArray(groupSchema), response.json);
|
||||
},
|
||||
|
||||
/** return basic group information */
|
||||
getGroup: async (groupId: string) => {
|
||||
const response = await client.request(`/api/v1/admin/groups/${groupId}`);
|
||||
|
||||
return v.parse(groupSchema, response.json);
|
||||
},
|
||||
|
||||
/** suspends a group */
|
||||
suspendGroup: async (groupId: string) => {
|
||||
const response = await client.request(`/api/v1/admin/groups/${groupId}/suspend`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(groupSchema, response.json);
|
||||
},
|
||||
|
||||
/** lift a suspension */
|
||||
unsuspendGroup: async (groupId: string) => {
|
||||
const response = await client.request(`/api/v1/admin/groups/${groupId}/unsuspend`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(groupSchema, response.json);
|
||||
},
|
||||
|
||||
/** deletes an already-suspended group */
|
||||
deleteGroup: async (groupId: string) => {
|
||||
const response = await client.request(`/api/v1/admin/groups/${groupId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return v.parse(groupSchema, 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 () => {
|
||||
let response;
|
||||
if (client.features.version.software === PIXELFED) {
|
||||
response = await client.request('/api/v0/groups/self/list');
|
||||
} else {
|
||||
response = await client.request('/api/v1/groups');
|
||||
}
|
||||
|
||||
return v.parse(filteredArray(groupSchema), 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) => {
|
||||
let response;
|
||||
|
||||
if (client.features.version.software === PIXELFED) {
|
||||
response = await client.request('/api/v0/groups/create', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
...params,
|
||||
name: params.display_name,
|
||||
description: params.note,
|
||||
membership: 'public',
|
||||
},
|
||||
contentType: params.avatar || params.header ? '' : undefined,
|
||||
});
|
||||
|
||||
if (response.json?.id) {
|
||||
return category.groups.getGroup(response.json.id);
|
||||
}
|
||||
} else {
|
||||
response = await client.request('/api/v1/groups', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
contentType: params.avatar || params.header ? '' : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return v.parse(groupSchema, response.json);
|
||||
},
|
||||
|
||||
/** returns the `Group` entity describing a given group */
|
||||
getGroup: async (groupId: string) => {
|
||||
let response;
|
||||
|
||||
if (client.features.version.software === PIXELFED) {
|
||||
response = await client.request(`/api/v0/groups/${groupId}`);
|
||||
} else {
|
||||
response = await client.request(`/api/v1/groups/${groupId}`);
|
||||
}
|
||||
|
||||
return v.parse(groupSchema, response.json);
|
||||
},
|
||||
|
||||
/** update group attributes (`display_name`, `note`, `avatar` and `header`) */
|
||||
updateGroup: async (groupId: string, params: UpdateGroupParams) => {
|
||||
const response = await client.request(`/api/v1/groups/${groupId}`, {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
contentType: params.avatar || params.header ? '' : undefined,
|
||||
});
|
||||
|
||||
return v.parse(groupSchema, response.json);
|
||||
},
|
||||
|
||||
/** irreversibly deletes the group */
|
||||
deleteGroup: async (groupId: string) => {
|
||||
let response;
|
||||
|
||||
if (client.features.version.software === PIXELFED) {
|
||||
response = await client.request<EmptyObject>('/api/v0/groups/delete', {
|
||||
method: 'POST',
|
||||
params: { gid: groupId },
|
||||
});
|
||||
} else {
|
||||
response = await client.request<EmptyObject>(`/api/v1/groups/${groupId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/** Has an optional role attribute that can be used to filter by role (valid roles are `"admin"`, `"moderator"`, `"user"`). */
|
||||
getGroupMemberships: (
|
||||
groupId: string,
|
||||
role?: GroupRole,
|
||||
params?: GetGroupMembershipsParams,
|
||||
) =>
|
||||
client.paginatedGet(
|
||||
client.features.version.software === PIXELFED
|
||||
? `/api/v0/groups/members/list?gid=${groupId}`
|
||||
: `/api/v1/groups/${groupId}/memberships`,
|
||||
{ params: { ...params, role } },
|
||||
groupMemberSchema,
|
||||
),
|
||||
|
||||
/** returns an array of `Account` entities representing pending requests to join a group */
|
||||
getGroupMembershipRequests: (groupId: string, params?: GetGroupMembershipRequestsParams) =>
|
||||
client.paginatedGet(
|
||||
client.features.version.software === PIXELFED
|
||||
? `/api/v0/groups/members/requests?gid=${groupId}`
|
||||
: `/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 client.request<EmptyObject>(
|
||||
`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/** reject a pending request to become a group member */
|
||||
rejectGroupMembershipRequest: async (groupId: string, accountId: string) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/** delete a group post (actually marks it as `revoked` if it is a local post) */
|
||||
deleteGroupStatus: async (groupId: string, statusId: string) => {
|
||||
const response = await client.request(`/api/v1/groups/${groupId}/statuses/${statusId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/** list accounts blocked from interacting with the group */
|
||||
getGroupBlocks: (groupId: string, params?: GetGroupBlocksParams) =>
|
||||
client.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 client.request<EmptyObject>(`/api/v1/groups/${groupId}/blocks`, {
|
||||
method: 'POST',
|
||||
params: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/** 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 client.request<EmptyObject>(`/api/v1/groups/${groupId}/blocks`, {
|
||||
method: 'DELETE',
|
||||
params: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/** joins (or request to join) a given group */
|
||||
joinGroup: async (groupId: string) => {
|
||||
const response = await client.request(`/api/v1/groups/${groupId}/join`, { method: 'POST' });
|
||||
|
||||
return v.parse(groupRelationshipSchema, response.json);
|
||||
},
|
||||
|
||||
/** leaves a given group */
|
||||
leaveGroup: async (groupId: string) => {
|
||||
const response = await client.request(`/api/v1/groups/${groupId}/leave`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(groupRelationshipSchema, response.json);
|
||||
},
|
||||
|
||||
/** kick one or more group members */
|
||||
kickGroupUsers: async (groupId: string, accountIds: string[]) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/groups/${groupId}/kick`, {
|
||||
method: 'POST',
|
||||
params: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/** 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 client.request(`/api/v1/groups/${groupId}/promote`, {
|
||||
method: 'POST',
|
||||
params: { account_ids: accountIds, role },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(groupMemberSchema), 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 client.request(`/api/v1/groups/${groupId}/demote`, {
|
||||
method: 'POST',
|
||||
params: { account_ids: accountIds, role },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(groupMemberSchema), response.json);
|
||||
},
|
||||
|
||||
getGroupRelationships: async (groupIds: string[]) => {
|
||||
const response = await client.request('/api/v1/groups/relationships', {
|
||||
params: { id: groupIds },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(groupRelationshipSchema), response.json);
|
||||
},
|
||||
},
|
||||
};
|
||||
return category;
|
||||
};
|
||||
|
||||
export { experimental };
|
||||
373
packages/pl-api/lib/client/filtering.ts
Normal file
373
packages/pl-api/lib/client/filtering.ts
Normal file
@ -0,0 +1,373 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
blockedAccountSchema,
|
||||
filterKeywordSchema,
|
||||
filterSchema,
|
||||
filterStatusSchema,
|
||||
mutedAccountSchema,
|
||||
relationshipSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
BlockAccountParams,
|
||||
CreateFilterParams,
|
||||
GetBlocksParams,
|
||||
GetDomainBlocksParams,
|
||||
GetMutesParams,
|
||||
MuteAccountParams,
|
||||
UpdateFilterParams,
|
||||
} from '../params/filtering';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const filtering = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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}
|
||||
* `duration` parameter requires features{@link Features.blocksDuration}.
|
||||
*/
|
||||
blockAccount: async (accountId: string, params?: BlockAccountParams) => {
|
||||
const response = await client.request(`/api/v1/accounts/${accountId}/block`, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unblock account
|
||||
* Unblock the given account.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/accounts/#unblock}
|
||||
*/
|
||||
unblockAccount: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/accounts/${accountId}/unblock`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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 client.request(`/api/v1/accounts/${accountId}/mute`, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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 client.request(`/api/v1/accounts/${accountId}/unmute`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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: (params?: GetMutesParams) =>
|
||||
client.paginatedGet('/api/v1/mutes', { params }, mutedAccountSchema),
|
||||
|
||||
/**
|
||||
* View blocked users
|
||||
* @see {@link https://docs.joinmastodon.org/methods/blocks/#get}
|
||||
*/
|
||||
getBlocks: (params?: GetBlocksParams) =>
|
||||
client.paginatedGet('/api/v1/blocks', { params }, blockedAccountSchema),
|
||||
|
||||
/**
|
||||
* Get domain blocks
|
||||
* View domains the user has blocked.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/domain_blocks/#get}
|
||||
*/
|
||||
getDomainBlocks: (params?: GetDomainBlocksParams) =>
|
||||
client.paginatedGet('/api/v1/domain_blocks', { params }, v.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 client.request<EmptyObject>('/api/v1/domain_blocks', {
|
||||
method: 'POST',
|
||||
body: { domain },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request<EmptyObject>('/api/v1/domain_blocks', {
|
||||
method: 'DELETE',
|
||||
body: { domain },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request(
|
||||
client.features.filtersV2 ? '/api/v2/filters' : '/api/v1/filters',
|
||||
);
|
||||
|
||||
return v.parse(filteredArray(filterSchema), 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 client.request(
|
||||
client.features.filtersV2 ? `/api/v2/filters/${filterId}` : `/api/v1/filters/${filterId}`,
|
||||
);
|
||||
|
||||
return v.parse(filterSchema, 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 } = client.features;
|
||||
const response = await client.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 v.parse(filterSchema, 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 } = client.features;
|
||||
const response = await client.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 v.parse(filterSchema, 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 client.request<EmptyObject>(
|
||||
client.features.filtersV2 ? `/api/v2/filters/${filterId}` : `/api/v1/filters/${filterId}`,
|
||||
{ method: 'DELETE' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request(`/api/v2/filters/${filterId}/keywords`);
|
||||
|
||||
return v.parse(filteredArray(filterKeywordSchema), 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 client.request(`/api/v2/filters/${filterId}/keywords`, {
|
||||
method: 'POST',
|
||||
body: { keyword, whole_word },
|
||||
});
|
||||
|
||||
return v.parse(filterKeywordSchema, 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 client.request(`/api/v2/filters/keywords/${filterId}`);
|
||||
|
||||
return v.parse(filterKeywordSchema, 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 client.request(`/api/v2/filters/keywords/${filterId}`, {
|
||||
method: 'PUT',
|
||||
body: { keyword, whole_word },
|
||||
});
|
||||
|
||||
return v.parse(filterKeywordSchema, 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 client.request<EmptyObject>(`/api/v2/filters/keywords/${filterId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request(`/api/v2/filters/${filterId}/statuses`);
|
||||
|
||||
return v.parse(filteredArray(filterStatusSchema), 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 client.request(`/api/v2/filters/${filterId}/statuses`, {
|
||||
method: 'POST',
|
||||
body: { status_id: statusId },
|
||||
});
|
||||
|
||||
return v.parse(filterStatusSchema, 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 client.request(`/api/v2/filters/statuses/${statusId}`);
|
||||
|
||||
return v.parse(filterStatusSchema, 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 client.request<EmptyObject>(`/api/v2/filters/statuses/${statusId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { filtering };
|
||||
242
packages/pl-api/lib/client/grouped-notifications.ts
Normal file
242
packages/pl-api/lib/client/grouped-notifications.ts
Normal file
@ -0,0 +1,242 @@
|
||||
import omit from 'lodash.omit';
|
||||
import pick from 'lodash.pick';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { accountSchema, groupedNotificationsResultsSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { type RequestMeta } from '../request';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
Account,
|
||||
GroupedNotificationsResults,
|
||||
Notification,
|
||||
NotificationGroup,
|
||||
Status,
|
||||
} from '../entities';
|
||||
import type {
|
||||
GetGroupedNotificationsParams,
|
||||
GetUnreadNotificationGroupCountParams,
|
||||
} from '../params/grouped-notifications';
|
||||
import type { PaginatedResponse } from '../responses';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const GROUPED_TYPES = [
|
||||
'favourite',
|
||||
'reblog',
|
||||
'emoji_reaction',
|
||||
'event_reminder',
|
||||
'participation_accepted',
|
||||
'participation_request',
|
||||
];
|
||||
|
||||
const _groupNotifications = (
|
||||
{ previous, next, items, ...response }: PaginatedResponse<Notification>,
|
||||
params?: GetGroupedNotificationsParams,
|
||||
): PaginatedResponse<GroupedNotificationsResults, false> => {
|
||||
const notificationGroups: Array<NotificationGroup> = [];
|
||||
|
||||
for (const notification of items) {
|
||||
let existingGroup: NotificationGroup | undefined;
|
||||
if ((params?.grouped_types || GROUPED_TYPES).includes(notification.type)) {
|
||||
existingGroup = notificationGroups.find(
|
||||
(notificationGroup) =>
|
||||
notificationGroup.type === notification.type &&
|
||||
(notification.type === 'emoji_reaction' && notificationGroup.type === 'emoji_reaction'
|
||||
? notification.emoji === notificationGroup.emoji
|
||||
: true) &&
|
||||
// @ts-expect-error used optional chaining
|
||||
notificationGroup.status_id === notification.status?.id,
|
||||
);
|
||||
}
|
||||
|
||||
if (existingGroup) {
|
||||
existingGroup.notifications_count += 1;
|
||||
existingGroup.page_min_id = notification.id;
|
||||
existingGroup.sample_account_ids.push(notification.account.id);
|
||||
} else {
|
||||
notificationGroups.push({
|
||||
...omit(notification, ['account', 'status', 'target']),
|
||||
group_key: notification.id,
|
||||
notifications_count: 1,
|
||||
most_recent_notification_id: notification.id,
|
||||
page_min_id: notification.id,
|
||||
page_max_id: notification.id,
|
||||
latest_page_notification_at: notification.created_at,
|
||||
sample_account_ids: [notification.account.id],
|
||||
// @ts-expect-error used optional chaining
|
||||
status_id: notification.status?.id,
|
||||
// @ts-expect-error used optional chaining
|
||||
target_id: notification.target?.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const groupedNotificationsResults: GroupedNotificationsResults = {
|
||||
accounts: Object.values(
|
||||
items.reduce<Record<string, Account>>((accounts, notification) => {
|
||||
accounts[notification.account.id] = notification.account;
|
||||
if ('target' in notification) accounts[notification.target.id] = notification.target;
|
||||
|
||||
return accounts;
|
||||
}, {}),
|
||||
),
|
||||
statuses: Object.values(
|
||||
items.reduce<Record<string, Status>>((statuses, notification) => {
|
||||
if ('status' in notification && notification.status)
|
||||
statuses[notification.status.id] = notification.status;
|
||||
return statuses;
|
||||
}, {}),
|
||||
),
|
||||
notification_groups: notificationGroups,
|
||||
};
|
||||
|
||||
return {
|
||||
...response,
|
||||
previous: previous ? async () => _groupNotifications(await previous(), params) : null,
|
||||
next: next ? async () => _groupNotifications(await next(), params) : null,
|
||||
items: groupedNotificationsResults,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* It is recommended to only use this with features{@link Features.groupedNotifications} available. However, there is a fallback that groups the notifications client-side.
|
||||
*/
|
||||
const groupedNotifications = (
|
||||
client: PlApiBaseClient & {
|
||||
notifications: ReturnType<typeof import('./notifications').notifications>;
|
||||
},
|
||||
) => {
|
||||
const category = {
|
||||
/**
|
||||
* Get all grouped notifications
|
||||
* Return grouped 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.
|
||||
*
|
||||
* Requires features{@link Features.groupedNotifications}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#get-grouped}
|
||||
*/
|
||||
getGroupedNotifications: async (params: GetGroupedNotificationsParams, meta?: RequestMeta) => {
|
||||
if (client.features.groupedNotifications) {
|
||||
return client.paginatedGet(
|
||||
'/api/v2/notifications',
|
||||
{ ...meta, params },
|
||||
groupedNotificationsResultsSchema,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
const response = await client.notifications.getNotifications(
|
||||
pick(params, [
|
||||
'max_id',
|
||||
'since_id',
|
||||
'limit',
|
||||
'min_id',
|
||||
'types',
|
||||
'exclude_types',
|
||||
'account_id',
|
||||
'include_filtered',
|
||||
]),
|
||||
);
|
||||
|
||||
return _groupNotifications(response, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a single notification group
|
||||
* View information about a specific notification group with a given group key.
|
||||
*
|
||||
* Requires features{@link Features.groupedNotifications}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#get-notification-group}
|
||||
*/
|
||||
getNotificationGroup: async (groupKey: string) => {
|
||||
if (client.features.groupedNotifications) {
|
||||
const response = await client.request(`/api/v2/notifications/${groupKey}`);
|
||||
|
||||
return v.parse(groupedNotificationsResultsSchema, response.json);
|
||||
}
|
||||
|
||||
const response = await client.request(`/api/v1/notifications/${groupKey}`);
|
||||
|
||||
return _groupNotifications({
|
||||
previous: null,
|
||||
next: null,
|
||||
items: [response.json],
|
||||
partial: false,
|
||||
}).items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss a single notification group
|
||||
* Dismiss a single notification group from the server.
|
||||
*
|
||||
* Requires features{@link Features.groupedNotifications}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group}
|
||||
*/
|
||||
dismissNotificationGroup: async (groupKey: string) => {
|
||||
if (client.features.groupedNotifications) {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v2/notifications/${groupKey}/dismiss`,
|
||||
{
|
||||
method: 'POST',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
}
|
||||
|
||||
return client.notifications.dismissNotification(groupKey);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get accounts of all notifications in a notification group
|
||||
*
|
||||
* Requires features{@link Features.groupedNotifications}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#get-group-accounts}
|
||||
*/
|
||||
getNotificationGroupAccounts: async (groupKey: string) => {
|
||||
if (client.features.groupedNotifications) {
|
||||
const response = await client.request(`/api/v2/notifications/${groupKey}/accounts`);
|
||||
|
||||
return v.parse(filteredArray(accountSchema), response.json);
|
||||
}
|
||||
|
||||
return (await category.getNotificationGroup(groupKey)).accounts;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of unread notifications
|
||||
* Get the (capped) number of unread notification groups for the current user. A notification is considered unread if it is more recent than the notifications read marker. Because the count is dependant on the parameters, it is computed every time and is thus a relatively slow operation (although faster than getting the full corresponding notifications), therefore the number of returned notifications is capped.
|
||||
*
|
||||
* Requires features{@link Features.groupedNotifications}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#unread-group-count}
|
||||
*/
|
||||
getUnreadNotificationGroupCount: async (params: GetUnreadNotificationGroupCountParams) => {
|
||||
if (client.features.groupedNotifications) {
|
||||
const response = await client.request('/api/v2/notifications/unread_count', { params });
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
count: v.number(),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
}
|
||||
|
||||
return client.notifications.getUnreadNotificationCount(
|
||||
pick(params || {}, [
|
||||
'max_id',
|
||||
'since_id',
|
||||
'limit',
|
||||
'min_id',
|
||||
'types',
|
||||
'exclude_types',
|
||||
'account_id',
|
||||
]),
|
||||
);
|
||||
},
|
||||
};
|
||||
return category;
|
||||
};
|
||||
|
||||
export { groupedNotifications };
|
||||
210
packages/pl-api/lib/client/instance.ts
Normal file
210
packages/pl-api/lib/client/instance.ts
Normal file
@ -0,0 +1,210 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
accountSchema,
|
||||
customEmojiSchema,
|
||||
domainBlockSchema,
|
||||
extendedDescriptionSchema,
|
||||
instanceSchema,
|
||||
privacyPolicySchema,
|
||||
ruleSchema,
|
||||
termsOfServiceSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { AKKOMA, MITRA } from '../features';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { ProfileDirectoryParams } from '../params/instance';
|
||||
|
||||
const instance = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/api/v2/instance');
|
||||
} catch (e) {
|
||||
response = await client.request('/api/v1/instance');
|
||||
}
|
||||
|
||||
const instance = v.parse(v.pipe(instanceSchema, v.readonly()), response.json);
|
||||
client.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 client.request('/api/v1/instance/peers');
|
||||
|
||||
return v.parse(v.array(v.string()), 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 client.request('/api/v1/instance/activity');
|
||||
|
||||
return v.parse(
|
||||
v.array(
|
||||
v.object({
|
||||
week: v.string(),
|
||||
statuses: v.pipe(v.unknown(), v.transform(String)),
|
||||
logins: v.pipe(v.unknown(), v.transform(String)),
|
||||
registrations: v.pipe(v.unknown(), v.transform(String)),
|
||||
}),
|
||||
),
|
||||
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 client.request('/api/v1/instance/rules');
|
||||
|
||||
return v.parse(filteredArray(ruleSchema), 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 client.request('/api/v1/instance/rules');
|
||||
|
||||
return v.parse(filteredArray(domainBlockSchema), 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 client.request('/api/v1/instance/extended_description');
|
||||
|
||||
return v.parse(extendedDescriptionSchema, 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 (client.features.version.software === AKKOMA) {
|
||||
const response = await client.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 client.request('/api/v1/instance/translation_languages');
|
||||
|
||||
return v.parse(v.record(v.string(), v.array(v.string())), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* View profile directory
|
||||
* List accounts visible in the directory.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/directory/#get}
|
||||
*
|
||||
* Requires features{@link Features.profileDirectory}.
|
||||
*/
|
||||
profileDirectory: async (params?: ProfileDirectoryParams) => {
|
||||
const response = await client.request('/api/v1/directory', { params });
|
||||
|
||||
return v.parse(filteredArray(accountSchema), 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 client.request('/api/v1/custom_emojis');
|
||||
|
||||
return v.parse(filteredArray(customEmojiSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Dump frontend configurations
|
||||
*
|
||||
* Requires features{@link Features.frontendConfigurations}.
|
||||
*/
|
||||
getFrontendConfigurations: async () => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case MITRA:
|
||||
response = (await client.request('/api/v1/accounts/verify_credentials')).json
|
||||
?.client_config;
|
||||
break;
|
||||
default:
|
||||
response = (await client.request('/api/pleroma/frontend_configurations')).json;
|
||||
}
|
||||
|
||||
return v.parse(v.fallback(v.record(v.string(), v.record(v.string(), v.any())), {}), response);
|
||||
},
|
||||
|
||||
/**
|
||||
* View privacy policy
|
||||
* Obtain the contents of this server's privacy policy.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/instance/privacy_policy}
|
||||
*/
|
||||
getInstancePrivacyPolicy: async () => {
|
||||
const response = await client.request('/api/v1/instance/privacy_policy');
|
||||
|
||||
return v.parse(privacyPolicySchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* View terms of service
|
||||
* Obtain the contents of this server's terms of service, if configured.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/instance/terms_of_service}
|
||||
*/
|
||||
getInstanceTermsOfService: async () => {
|
||||
const response = await client.request('/api/v1/instance/terms_of_service');
|
||||
|
||||
return v.parse(termsOfServiceSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* View a specific version of the terms of service
|
||||
* Obtain the contents of this server's terms of service, for a specified date, if configured.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/instance/terms_of_service_date}
|
||||
*/
|
||||
getInstanceTermsOfServiceForDate: async (date: string) => {
|
||||
const response = await client.request(`/api/v1/instance/terms_of_service/${date}`);
|
||||
|
||||
return v.parse(termsOfServiceSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { instance };
|
||||
57
packages/pl-api/lib/client/interaction-requests.ts
Normal file
57
packages/pl-api/lib/client/interaction-requests.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { interactionRequestSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { GetInteractionRequestsParams } from '../params/interaction-requests';
|
||||
|
||||
const interactionRequests = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Get an array of interactions requested on your statuses by other accounts, and pending your approval.
|
||||
*
|
||||
* Requires features{@link Features.interactionRequests}.
|
||||
*/
|
||||
getInteractionRequests: (params?: GetInteractionRequestsParams) =>
|
||||
client.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 client.request(`/api/v1/interaction_requests/${interactionRequestId}`);
|
||||
|
||||
return v.parse(interactionRequestSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Accept/authorize/approve an interaction request with the given ID.
|
||||
*
|
||||
* Requires features{@link Features.interactionRequests}.
|
||||
*/
|
||||
authorizeInteractionRequest: async (interactionRequestId: string) => {
|
||||
const response = await client.request(
|
||||
`/api/v1/interaction_requests/${interactionRequestId}/authorize`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return v.parse(interactionRequestSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reject an interaction request with the given ID.
|
||||
*
|
||||
* Requires features{@link Features.interactionRequests}.
|
||||
*/
|
||||
rejectInteractionRequest: async (interactionRequestId: string) => {
|
||||
const response = await client.request(
|
||||
`/api/v1/interaction_requests/${interactionRequestId}/authorize`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return v.parse(interactionRequestSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { interactionRequests };
|
||||
131
packages/pl-api/lib/client/lists.ts
Normal file
131
packages/pl-api/lib/client/lists.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { accountSchema, listSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { CreateListParams, GetListAccountsParams, UpdateListParams } from '../params/lists';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const lists = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* View your lists
|
||||
* Fetch all lists that the user owns.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/lists/#get}
|
||||
*/
|
||||
getLists: async () => {
|
||||
const response = await client.request('/api/v1/lists');
|
||||
|
||||
return v.parse(filteredArray(listSchema), 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 client.request(`/api/v1/lists/${listId}`);
|
||||
|
||||
return v.parse(listSchema, 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 client.request('/api/v1/lists', { method: 'POST', body: params });
|
||||
|
||||
return v.parse(listSchema, 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 client.request(`/api/v1/lists/${listId}`, {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(listSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a list
|
||||
* @see {@link https://docs.joinmastodon.org/methods/lists/#delete}
|
||||
*/
|
||||
deleteList: async (listId: string) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/lists/${listId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* View accounts in a list
|
||||
* @see {@link https://docs.joinmastodon.org/methods/lists/#accounts}
|
||||
*/
|
||||
getListAccounts: (listId: string, params?: GetListAccountsParams) =>
|
||||
client.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 client.request<EmptyObject>(`/api/v1/lists/${listId}/accounts`, {
|
||||
method: 'POST',
|
||||
body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request<EmptyObject>(`/api/v1/lists/${listId}/accounts`, {
|
||||
method: 'DELETE',
|
||||
body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a list to favourites
|
||||
*
|
||||
* Requires features{@link Features.listsFavourite}.
|
||||
*/
|
||||
favouriteList: async (listId: string) => {
|
||||
const response = await client.request(`/api/v1/lists/${listId}/favourite`, { method: 'POST' });
|
||||
|
||||
return v.parse(listSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a list from favourites
|
||||
*
|
||||
* Requires features{@link Features.listsFavourite}.
|
||||
*/
|
||||
unfavouriteList: async (listId: string) => {
|
||||
const response = await client.request(`/api/v1/lists/${listId}/unfavourite`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(listSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { lists };
|
||||
68
packages/pl-api/lib/client/media.ts
Normal file
68
packages/pl-api/lib/client/media.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { mediaAttachmentSchema } from '../entities';
|
||||
import { type RequestMeta } from '../request';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { UpdateMediaParams, UploadMediaParams } from '../params/media';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const media = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request(
|
||||
client.features.mediaV2 ? '/api/v2/media' : '/api/v1/media',
|
||||
{ ...meta, method: 'POST', body: params, contentType: '' },
|
||||
);
|
||||
|
||||
return v.parse(mediaAttachmentSchema, 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 client.request(`/api/v1/media/${attachmentId}`);
|
||||
|
||||
return v.parse(mediaAttachmentSchema, 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 client.request(`/api/v1/media/${attachmentId}`, {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
contentType: params.thumbnail ? '' : undefined,
|
||||
});
|
||||
|
||||
return v.parse(mediaAttachmentSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update media attachment
|
||||
* Update a MediaAttachment’s parameters, before it is attached to a status and posted.
|
||||
*
|
||||
* Requires features{@link Features.deleteMedia}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/media/delete}
|
||||
*/
|
||||
deleteMedia: async (attachmentId: string) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/media/${attachmentId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { media };
|
||||
371
packages/pl-api/lib/client/my-account.ts
Normal file
371
packages/pl-api/lib/client/my-account.ts
Normal file
@ -0,0 +1,371 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
accountSchema,
|
||||
bookmarkFolderSchema,
|
||||
featuredTagSchema,
|
||||
relationshipSchema,
|
||||
statusSchema,
|
||||
suggestionSchema,
|
||||
tagSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { GOTOSOCIAL, ICESHRIMP_NET, PIXELFED, PLEROMA } from '../features';
|
||||
import { getNextLink, getPrevLink } from '../request';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { Account } from '../entities';
|
||||
import type {
|
||||
CreateBookmarkFolderParams,
|
||||
GetBookmarksParams,
|
||||
GetEndorsementsParams,
|
||||
GetFavouritesParams,
|
||||
GetFollowRequestsParams,
|
||||
GetFollowedTagsParams,
|
||||
UpdateBookmarkFolderParams,
|
||||
} from '../params/my-account';
|
||||
import type { PaginatedResponse } from '../responses';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const paginatedIceshrimpAccountsList = async <T>(
|
||||
client: PlApiBaseClient & { accounts: ReturnType<typeof import('./accounts').accounts> },
|
||||
url: string,
|
||||
fn: (body: T) => Array<string>,
|
||||
): Promise<PaginatedResponse<Account>> => {
|
||||
await client.getIceshrimpAccessToken();
|
||||
|
||||
const response = await client.request<T>(url);
|
||||
const ids = fn(response.json);
|
||||
|
||||
const items = await client.accounts.getAccounts(ids);
|
||||
|
||||
const prevLink = getPrevLink(response);
|
||||
const nextLink = getNextLink(response);
|
||||
|
||||
return {
|
||||
previous: prevLink ? () => paginatedIceshrimpAccountsList(client, prevLink, fn) : null,
|
||||
next: nextLink ? () => paginatedIceshrimpAccountsList(client, nextLink, fn) : null,
|
||||
items,
|
||||
partial: response.status === 206,
|
||||
};
|
||||
};
|
||||
|
||||
const myAccount = (
|
||||
client: PlApiBaseClient & { accounts: ReturnType<typeof import('./accounts').accounts> },
|
||||
) => ({
|
||||
/**
|
||||
* View bookmarked statuses
|
||||
* Statuses the user has bookmarked.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/bookmarks/#get}
|
||||
*/
|
||||
getBookmarks: (params?: GetBookmarksParams) =>
|
||||
client.paginatedGet(
|
||||
client.features.bookmarkFoldersMultiple && params?.folder_id
|
||||
? `/api/v1/bookmark_categories/${params.folder_id}/statuses`
|
||||
: '/api/v1/bookmarks',
|
||||
{ params },
|
||||
statusSchema,
|
||||
),
|
||||
|
||||
/**
|
||||
* View favourited statuses
|
||||
* Statuses the user has favourited.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/favourites/#get}
|
||||
*/
|
||||
getFavourites: (params?: GetFavouritesParams) =>
|
||||
client.paginatedGet('/api/v1/favourites', { params }, statusSchema),
|
||||
|
||||
/**
|
||||
* View pending follow requests
|
||||
* @see {@link https://docs.joinmastodon.org/methods/follow_requests/#get}
|
||||
*/
|
||||
getFollowRequests: (params?: GetFollowRequestsParams) =>
|
||||
client.paginatedGet('/api/v1/follow_requests', { params }, accountSchema),
|
||||
|
||||
/**
|
||||
* View outgoing follow requests
|
||||
*
|
||||
* Requires features{@link Features.outgoingFollowRequests}.
|
||||
*/
|
||||
getOutgoingFollowRequests: (params?: GetFollowRequestsParams) => {
|
||||
if (client.features.version.software === ICESHRIMP_NET) {
|
||||
return paginatedIceshrimpAccountsList(
|
||||
client,
|
||||
'/api/iceshrimp/follow_requests/outgoing',
|
||||
(response: Array<{ user: { id: string } }>) => response.map(({ user }) => user.id),
|
||||
);
|
||||
}
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
return client.paginatedGet('/api/v1/follow_requests/outgoing', { params }, accountSchema);
|
||||
|
||||
default:
|
||||
return client.paginatedGet(
|
||||
'/api/v1/pleroma/outgoing_follow_requests',
|
||||
{ params },
|
||||
accountSchema,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Accept follow request
|
||||
* @see {@link https://docs.joinmastodon.org/methods/follow_requests/#accept}
|
||||
*/
|
||||
acceptFollowRequest: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/follow_requests/${accountId}/authorize`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reject follow request
|
||||
* @see {@link https://docs.joinmastodon.org/methods/follow_requests/#reject}
|
||||
*/
|
||||
rejectFollowRequest: async (accountId: string) => {
|
||||
const response = await client.request(`/api/v1/follow_requests/${accountId}/reject`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(relationshipSchema, 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: (params?: GetEndorsementsParams) =>
|
||||
client.paginatedGet('/api/v1/endorsements', { params }, accountSchema),
|
||||
|
||||
/**
|
||||
* View your featured tags
|
||||
* List all hashtags featured on your profile.
|
||||
*
|
||||
* Requires features{@link Features.featuredTags}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/featured_tags/#get}
|
||||
*/
|
||||
getFeaturedTags: async () => {
|
||||
const response = await client.request('/api/v1/featured_tags');
|
||||
|
||||
return v.parse(filteredArray(featuredTagSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Feature a tag
|
||||
* Promote a hashtag on your profile.
|
||||
*
|
||||
* Requires features{@link Features.featuredTags}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/featured_tags/#feature}
|
||||
*/
|
||||
featureTag: async (name: string) => {
|
||||
const response = await client.request('/api/v1/featured_tags', {
|
||||
method: 'POST',
|
||||
body: { name },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(featuredTagSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unfeature a tag
|
||||
* Stop promoting a hashtag on your profile.
|
||||
*
|
||||
* Requires features{@link Features.featuredTags}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/featured_tags/#unfeature}
|
||||
*/
|
||||
unfeatureTag: async (name: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/v1/featured_tags', {
|
||||
method: 'DELETE',
|
||||
body: { name },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* View suggested tags to feature
|
||||
* Shows up to 10 recently-used tags.
|
||||
*
|
||||
* Requires features{@link Features.featuredTags}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/featured_tags/#suggestions}
|
||||
*/
|
||||
getFeaturedTagsSuggestions: async () => {
|
||||
const response = await client.request('/api/v1/featured_tags/suggestions');
|
||||
|
||||
return v.parse(filteredArray(tagSchema), 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: (params?: GetFollowedTagsParams) =>
|
||||
client.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 client.request(`/api/v1/tags/${tagId}`);
|
||||
|
||||
return v.parse(tagSchema, 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 client.request(`/api/v1/tags/${tagId}/follow`, { method: 'POST' });
|
||||
|
||||
return v.parse(tagSchema, 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 client.request(`/api/v1/tags/${tagId}/unfollow`, { method: 'POST' });
|
||||
|
||||
return v.parse(tagSchema, 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 client.request(
|
||||
client.features.version.software === PIXELFED
|
||||
? '/api/v1.1/discover/accounts/popular'
|
||||
: client.features.suggestionsV2
|
||||
? '/api/v2/suggestions'
|
||||
: '/api/v1/suggestions',
|
||||
{ params: { limit } },
|
||||
);
|
||||
|
||||
return v.parse(filteredArray(suggestionSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a suggestion
|
||||
* Remove an account from follow suggestions.
|
||||
*
|
||||
* Requires features{@link Features.suggestionsDismiss}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/suggestions/#remove}
|
||||
*/
|
||||
dismissSuggestions: async (accountId: string) => {
|
||||
const response = await client.request<EmptyObject>(`/api/v1/suggestions/${accountId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request(
|
||||
client.features.version.software === PLEROMA
|
||||
? '/api/v1/pleroma/bookmark_folders'
|
||||
: '/api/v1/bookmark_categories',
|
||||
);
|
||||
|
||||
return v.parse(filteredArray(bookmarkFolderSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a bookmark folder
|
||||
*
|
||||
* Requires features{@link Features.bookmarkFolders}.
|
||||
* Specifying folder emoji requires features{@link Features.bookmarkFolderEmojis}.
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabookmark_folders}
|
||||
*/
|
||||
createBookmarkFolder: async (params: CreateBookmarkFolderParams) => {
|
||||
const response = await client.request(
|
||||
client.features.version.software === PLEROMA
|
||||
? '/api/v1/pleroma/bookmark_folders'
|
||||
: '/api/v1/bookmark_categories',
|
||||
{ method: 'POST', body: { title: params.name, ...params } },
|
||||
);
|
||||
|
||||
return v.parse(bookmarkFolderSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a bookmark folder
|
||||
*
|
||||
* Requires features{@link Features.bookmarkFolders}.
|
||||
* Specifying folder emoji requires features{@link Features.bookmarkFolderEmojis}.
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#patch-apiv1pleromabookmark_foldersid}
|
||||
*/
|
||||
updateBookmarkFolder: async (bookmarkFolderId: string, params: UpdateBookmarkFolderParams) => {
|
||||
const response = await client.request(
|
||||
`${client.features.version.software === PLEROMA ? '/api/v1/pleroma/bookmark_folders' : '/api/v1/bookmark_categories'}/${bookmarkFolderId}`,
|
||||
{ method: 'PATCH', body: { title: params.name, ...params } },
|
||||
);
|
||||
|
||||
return v.parse(bookmarkFolderSchema, 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 client.request(
|
||||
`${client.features.version.software === PLEROMA ? '/api/v1/pleroma/bookmark_folders' : '/api/v1/bookmark_categories'}/${bookmarkFolderId}`,
|
||||
{ method: 'DELETE' },
|
||||
);
|
||||
|
||||
return v.parse(bookmarkFolderSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.bookmarkFoldersMultiple}.
|
||||
*/
|
||||
addBookmarkToFolder: async (statusId: string, folderId: string) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/bookmark_categories/${folderId}/statuses`,
|
||||
{ method: 'POST', params: { status_ids: [statusId] } },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.bookmarkFoldersMultiple}.
|
||||
*/
|
||||
removeBookmarkFromFolder: async (statusId: string, folderId: string) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/bookmark_categories/${folderId}/statuses`,
|
||||
{ method: 'DELETE', params: { status_ids: [statusId] } },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { myAccount };
|
||||
255
packages/pl-api/lib/client/notifications.ts
Normal file
255
packages/pl-api/lib/client/notifications.ts
Normal file
@ -0,0 +1,255 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
notificationPolicySchema,
|
||||
notificationRequestSchema,
|
||||
notificationSchema,
|
||||
} from '../entities';
|
||||
import { type RequestMeta } from '../request';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
GetNotificationParams,
|
||||
GetNotificationRequestsParams,
|
||||
GetUnreadNotificationCountParams,
|
||||
UpdateNotificationPolicyRequest,
|
||||
} from '../params/notifications';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const notifications = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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: (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 client.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 client.request(`/api/v1/notifications/${notificationId}`);
|
||||
|
||||
return v.parse(notificationSchema, 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 client.request<EmptyObject>('/api/v1/notifications/clear', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request<EmptyObject>(
|
||||
`/api/v1/notifications/${notificationId}/dismiss`,
|
||||
{
|
||||
method: 'POST',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of unread notification
|
||||
* Get the (capped) number of unread notifications for the current user.
|
||||
*
|
||||
* Requires features{@link Features.notificationsGetUnreadCount}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/notifications/#unread-count}
|
||||
*/
|
||||
getUnreadNotificationCount: async (params?: GetUnreadNotificationCountParams) => {
|
||||
const response = await client.request('/api/v1/notifications/unread_count', { params });
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
count: v.number(),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the filtering policy for notifications
|
||||
* Notifications filtering policy for the user.
|
||||
*
|
||||
* Requires features{@link Features.notificationsPolicy}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/notifications/#get-policy}
|
||||
*/
|
||||
getNotificationPolicy: async () => {
|
||||
const response = await client.request('/api/v2/notifications/policy');
|
||||
|
||||
return v.parse(notificationPolicySchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the filtering policy for notifications
|
||||
* Update the user’s notifications filtering policy.
|
||||
*
|
||||
* Requires features{@link Features.notificationsPolicy}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/notifications/#update-the-filtering-policy-for-notifications}
|
||||
*/
|
||||
updateNotificationPolicy: async (params: UpdateNotificationPolicyRequest) => {
|
||||
const response = await client.request('/api/v2/notifications/policy', {
|
||||
method: 'PATCH',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(notificationPolicySchema, 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: (params?: GetNotificationRequestsParams) =>
|
||||
client.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 client.request(
|
||||
`/api/v1/notifications/requests/${notificationRequestId}`,
|
||||
);
|
||||
|
||||
return v.parse(notificationRequestSchema, 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 client.request<EmptyObject>(
|
||||
`/api/v1/notifications/requests/${notificationRequestId}/dismiss`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request<EmptyObject>(
|
||||
`/api/v1/notifications/requests/${notificationRequestId}/dismiss`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Accept multiple notification requests
|
||||
* Accepts multiple notification requests, which merges the filtered notifications from those users back into the main notifications and accepts any future notification from them.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/notifications/#accept-multiple-requests}
|
||||
* Requires features{@link Features.notificationsRequestsAcceptMultiple}.
|
||||
*/
|
||||
acceptMultipleNotificationRequests: async (notificationRequestIds: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>('/api/v1/notifications/requests/accept', {
|
||||
method: 'POST',
|
||||
body: { id: notificationRequestIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss multiple notification requests
|
||||
* Dismiss multiple notification requests, which hides them and prevent them from contributing to the pending notification requests count.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/notifications/#dismiss-multiple-requests}
|
||||
* Requires features{@link Features.notificationsRequestsAcceptMultiple}.
|
||||
*/
|
||||
dismissMultipleNotificationRequests: async (notificationRequestIds: Array<string>) => {
|
||||
const response = await client.request<EmptyObject>('/api/v1/notifications/requests/dismiss', {
|
||||
method: 'POST',
|
||||
body: { id: notificationRequestIds },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if accepted notification requests have been merged
|
||||
* Check whether accepted notification requests have been merged. Accepting notification requests schedules a background job to merge the filtered notifications back into the normal notification list. When that process has finished, the client should refresh the notifications list at its earliest convenience. This is communicated by the `notifications_merged` streaming event but can also be polled using this endpoint.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/notifications/#requests-merged}
|
||||
* Requires features{@link Features.notificationsRequestsAcceptMultiple}.
|
||||
*/
|
||||
checkNotificationRequestsMerged: async () => {
|
||||
const response = await client.request('/api/v1/notifications/requests/merged');
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
merged: v.boolean(),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request<EmptyObject>('/api/v1/notifications/destroy_multiple', {
|
||||
params: { ids: notificationIds },
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { notifications };
|
||||
145
packages/pl-api/lib/client/oauth.ts
Normal file
145
packages/pl-api/lib/client/oauth.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { authorizationServerMetadataSchema, tokenSchema, userInfoSchema } from '../entities';
|
||||
import { ICESHRIMP_NET } from '../features';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
GetTokenParams,
|
||||
MfaChallengeParams,
|
||||
OauthAuthorizeParams,
|
||||
RevokeTokenParams,
|
||||
} from '../params/oauth';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const oauth = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/oauth/authorize', { params, contentType: '' });
|
||||
|
||||
return v.parse(v.string(), 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) => {
|
||||
if (client.features.version.software === ICESHRIMP_NET && params.grant_type === 'password') {
|
||||
const loginResponse = (
|
||||
await client.request<{
|
||||
token: string;
|
||||
}>('/api/iceshrimp/auth/login', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
},
|
||||
})
|
||||
).json;
|
||||
client.setIceshrimpAccessToken(loginResponse.token);
|
||||
|
||||
const mastodonTokenResponse = (
|
||||
await client.request<{
|
||||
id: string;
|
||||
token: string;
|
||||
created_at: string;
|
||||
scopes: Array<string>;
|
||||
}>('/api/iceshrimp/sessions/mastodon', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
appName: params.client_id,
|
||||
scopes: params.scope?.split(' '),
|
||||
flags: {
|
||||
supportsHtmlFormatting: true,
|
||||
autoDetectQuotes: false,
|
||||
isPleroma: true,
|
||||
supportsInlineMedia: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
).json;
|
||||
|
||||
return v.parse(tokenSchema, {
|
||||
access_token: mastodonTokenResponse.token,
|
||||
token_type: 'Bearer',
|
||||
scope: mastodonTokenResponse.scopes.join(' '),
|
||||
created_at: new Date(mastodonTokenResponse.created_at).getTime(),
|
||||
id: mastodonTokenResponse.id,
|
||||
});
|
||||
}
|
||||
const response = await client.request('/oauth/token', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
contentType: '',
|
||||
});
|
||||
|
||||
return v.parse(tokenSchema, { scope: params.scope || '', ...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 client.request<EmptyObject>('/oauth/revoke', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
contentType: '',
|
||||
});
|
||||
|
||||
client.socket?.close();
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve user information
|
||||
* Retrieves standardised OIDC claims about the currently authenticated user.
|
||||
* see {@link https://docs.joinmastodon.org/methods/oauth/#userinfo}
|
||||
*/
|
||||
userinfo: async () => {
|
||||
const response = await client.request('/oauth/userinfo');
|
||||
|
||||
return v.parse(userInfoSchema, response.json);
|
||||
},
|
||||
|
||||
authorizationServerMetadata: async () => {
|
||||
const response = await client.request('/.well-known/oauth-authorization-server');
|
||||
|
||||
return v.parse(authorizationServerMetadataSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a new captcha
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromacaptcha}
|
||||
*/
|
||||
getCaptcha: async () => {
|
||||
const response = await client.request('/api/pleroma/captcha');
|
||||
|
||||
return v.parse(
|
||||
v.intersect([
|
||||
v.object({
|
||||
type: v.string(),
|
||||
}),
|
||||
v.record(v.string(), v.any()),
|
||||
]),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
|
||||
mfaChallenge: async (params: MfaChallengeParams) => {
|
||||
const response = await client.request('/oauth/mfa/challenge', { method: 'POST', body: params });
|
||||
|
||||
return v.parse(tokenSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { oauth };
|
||||
31
packages/pl-api/lib/client/oembed.ts
Normal file
31
packages/pl-api/lib/client/oembed.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
const oembed = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/api/oembed', { params: { url, maxwidth, maxheight } });
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
type: v.fallback(v.string(), 'rich'),
|
||||
version: v.fallback(v.string(), ''),
|
||||
author_name: v.fallback(v.string(), ''),
|
||||
author_url: v.fallback(v.string(), ''),
|
||||
provider_name: v.fallback(v.string(), ''),
|
||||
provider_url: v.fallback(v.string(), ''),
|
||||
cache_age: v.number(),
|
||||
html: v.string(),
|
||||
width: v.fallback(v.nullable(v.number()), null),
|
||||
height: v.fallback(v.nullable(v.number()), null),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export { oembed };
|
||||
34
packages/pl-api/lib/client/polls.ts
Normal file
34
packages/pl-api/lib/client/polls.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { pollSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
const polls = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request(`/api/v1/polls/${pollId}`);
|
||||
|
||||
return v.parse(pollSchema, 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 client.request(`/api/v1/polls/${pollId}/votes`, {
|
||||
method: 'POST',
|
||||
body: { choices },
|
||||
});
|
||||
|
||||
return v.parse(pollSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { polls };
|
||||
67
packages/pl-api/lib/client/push-notifications.ts
Normal file
67
packages/pl-api/lib/client/push-notifications.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { webPushSubscriptionSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
CreatePushNotificationsSubscriptionParams,
|
||||
UpdatePushNotificationsSubscriptionParams,
|
||||
} from '../params/push-notifications';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const pushNotifications = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/api/v1/push/subscription', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(webPushSubscriptionSchema, 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 client.request('/api/v1/push/subscription');
|
||||
|
||||
return v.parse(webPushSubscriptionSchema, 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 client.request('/api/v1/push/subscription', {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(webPushSubscriptionSchema, 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 client.request<EmptyObject>('/api/v1/push/subscription', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { pushNotifications };
|
||||
45
packages/pl-api/lib/client/rss-feed-subscriptions.ts
Normal file
45
packages/pl-api/lib/client/rss-feed-subscriptions.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { rssFeedSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const rssFeedSubscriptions = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Requires features{@link Features.rssFeedSubscriptions}.
|
||||
*/
|
||||
fetchRssFeedSubscriptions: async () => {
|
||||
const response = await client.request('/api/v1/pleroma/rss_feed_subscriptions');
|
||||
|
||||
return v.parse(filteredArray(rssFeedSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.rssFeedSubscriptions}.
|
||||
*/
|
||||
createRssFeedSubscription: async (url: string) => {
|
||||
const response = await client.request('/api/v1/pleroma/rss_feed_subscriptions', {
|
||||
method: 'POST',
|
||||
body: { url },
|
||||
});
|
||||
|
||||
return v.parse(rssFeedSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.rssFeedSubscriptions}.
|
||||
*/
|
||||
deleteRssFeedSubscription: async (url: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/v1/pleroma/rss_feed_subscriptions', {
|
||||
method: 'DELETE',
|
||||
body: { url },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { rssFeedSubscriptions };
|
||||
57
packages/pl-api/lib/client/scheduled-statuses.ts
Normal file
57
packages/pl-api/lib/client/scheduled-statuses.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { scheduledStatusSchema } from '../entities';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { GetScheduledStatusesParams } from '../params/scheduled-statuses';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const scheduledStatuses = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* View scheduled statuses
|
||||
* @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/#get}
|
||||
*/
|
||||
getScheduledStatuses: (params?: GetScheduledStatusesParams) =>
|
||||
client.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 client.request(`/api/v1/scheduled_statuses/${scheduledStatusId}`);
|
||||
|
||||
return v.parse(scheduledStatusSchema, 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 client.request(`/api/v1/scheduled_statuses/${scheduledStatusId}`, {
|
||||
method: 'PUT',
|
||||
body: { scheduled_at },
|
||||
});
|
||||
|
||||
return v.parse(scheduledStatusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel a scheduled status
|
||||
* @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/#cancel}
|
||||
*/
|
||||
cancelScheduledStatus: async (scheduledStatusId: string) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/scheduled_statuses/${scheduledStatusId}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { scheduledStatuses };
|
||||
51
packages/pl-api/lib/client/search.ts
Normal file
51
packages/pl-api/lib/client/search.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { locationSchema, searchSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { type RequestMeta } from '../request';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { SearchParams } from '../params/search';
|
||||
|
||||
const search = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Perform a search
|
||||
* @see {@link https://docs.joinmastodon.org/methods/search/#v2}
|
||||
*/
|
||||
search: async (q: string, params?: SearchParams, meta?: RequestMeta) => {
|
||||
const response = await client.request('/api/v2/search', { ...meta, params: { ...params, q } });
|
||||
|
||||
const parsedSearch = v.parse(searchSchema, response.json);
|
||||
|
||||
// A workaround for Pleroma/Akkoma getting into a loop of returning the same account/status when resolve === true.
|
||||
if (params && params.resolve && params.offset && params.offset > 0) {
|
||||
const firstAccount = parsedSearch.accounts[0];
|
||||
if (firstAccount && [firstAccount.url, firstAccount.acct].includes(q)) {
|
||||
parsedSearch.accounts = parsedSearch.accounts.slice(1);
|
||||
}
|
||||
const firstStatus = parsedSearch.statuses[0];
|
||||
if (firstStatus && [firstStatus.uri, firstStatus.url].includes(q)) {
|
||||
parsedSearch.statuses = parsedSearch.statuses.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
return parsedSearch;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request('/api/v1/pleroma/search/location', {
|
||||
...meta,
|
||||
params: { q },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(locationSchema), response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { search };
|
||||
823
packages/pl-api/lib/client/settings.ts
Normal file
823
packages/pl-api/lib/client/settings.ts
Normal file
@ -0,0 +1,823 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
backupSchema,
|
||||
credentialAccountSchema,
|
||||
interactionPoliciesSchema,
|
||||
oauthTokenSchema,
|
||||
tokenSchema,
|
||||
} from '../entities';
|
||||
import { coerceObject, filteredArray } from '../entities/utils';
|
||||
import { GOTOSOCIAL, ICESHRIMP_NET, MITRA, PIXELFED } from '../features';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
CreateAccountParams,
|
||||
UpdateCredentialsParams,
|
||||
UpdateInteractionPoliciesParams,
|
||||
UpdateNotificationSettingsParams,
|
||||
} from '../params/settings';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const settings = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/api/v1/accounts', {
|
||||
method: 'POST',
|
||||
body: { language: params.locale, birthday: params.date_of_birth, ...params },
|
||||
});
|
||||
|
||||
if ('identifier' in response.json)
|
||||
return v.parse(
|
||||
v.object({
|
||||
message: v.string(),
|
||||
identifier: v.string(),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
return v.parse(tokenSchema, 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 client.request('/api/v1/accounts/verify_credentials');
|
||||
|
||||
return v.parse(credentialAccountSchema, 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;
|
||||
|
||||
if (client.features.version.software === MITRA) {
|
||||
await client.request('/api/v1/settings/client_config', {
|
||||
method: 'POST',
|
||||
body: params.settings_store,
|
||||
});
|
||||
}
|
||||
|
||||
delete params.settings_store;
|
||||
}
|
||||
|
||||
const response = await client.request('/api/v1/accounts/update_credentials', {
|
||||
method: 'PATCH',
|
||||
contentType:
|
||||
client.features.version.software === GOTOSOCIAL ||
|
||||
client.features.version.software === ICESHRIMP_NET ||
|
||||
params.avatar ||
|
||||
params.header
|
||||
? ''
|
||||
: undefined,
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(credentialAccountSchema, 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 client.request('/api/v1/profile/avatar', { method: 'DELETE' });
|
||||
|
||||
return v.parse(credentialAccountSchema, 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 client.request('/api/v1/profile/header', { method: 'DELETE' });
|
||||
|
||||
return v.parse(credentialAccountSchema, 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 client.request('/api/v1/preferences');
|
||||
|
||||
return response.json as Record<string, any>;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a user backup archive
|
||||
*
|
||||
* Requires features{@link Features.accountBackups}.
|
||||
*/
|
||||
createBackup: async () => {
|
||||
const response = await client.request('/api/v1/pleroma/backups', { method: 'POST' });
|
||||
|
||||
return v.parse(backupSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* List user backups
|
||||
*
|
||||
* Requires features{@link Features.accountBackups}.
|
||||
*/
|
||||
getBackups: async () => {
|
||||
const response = await client.request('/api/v1/pleroma/backups');
|
||||
|
||||
return v.parse(filteredArray(backupSchema), 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 client.request('/api/pleroma/aliases');
|
||||
|
||||
return v.parse(v.object({ aliases: filteredArray(v.string()) }), 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 client.request('/api/pleroma/aliases', {
|
||||
method: 'PUT',
|
||||
body: { alias },
|
||||
});
|
||||
|
||||
return v.parse(v.object({ status: v.literal('success') }), 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 client.request('/api/pleroma/aliases', {
|
||||
method: 'DELETE',
|
||||
body: { alias },
|
||||
});
|
||||
|
||||
return v.parse(v.object({ status: v.literal('success') }), 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: () => {
|
||||
let url;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
url = '/api/v1/tokens';
|
||||
break;
|
||||
case MITRA:
|
||||
url = '/api/v1/settings/sessions';
|
||||
break;
|
||||
default:
|
||||
url = '/api/oauth_tokens';
|
||||
break;
|
||||
}
|
||||
|
||||
return client.paginatedGet(url, {}, oauthTokenSchema);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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: string) => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request<EmptyObject>(`/api/v1/tokens/${oauthTokenId}/invalidate`, {
|
||||
method: 'POST',
|
||||
});
|
||||
break;
|
||||
case MITRA:
|
||||
response = await client.request<EmptyObject>(`/api/v1/settings/sessions/${oauthTokenId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request<EmptyObject>(`/api/oauth_tokens/${oauthTokenId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change account password
|
||||
*
|
||||
* Requires features{@link Features.changePassword}.
|
||||
* @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 (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request<EmptyObject>('/api/v1/user/password_change', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
old_password: current_password,
|
||||
new_password,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case ICESHRIMP_NET:
|
||||
await client.getIceshrimpAccessToken();
|
||||
response = await client.request<EmptyObject>('/api/iceshrimp/auth/change-password', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
oldPassword: current_password,
|
||||
newPassword: new_password,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case MITRA:
|
||||
response = await client.request<EmptyObject>('/api/v1/settings/change_password', {
|
||||
method: 'POST',
|
||||
body: { new_password },
|
||||
});
|
||||
break;
|
||||
case PIXELFED:
|
||||
response = await client.request<EmptyObject>('/api/v1.1/accounts/change-password', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
current_password,
|
||||
new_password,
|
||||
confirm_password: new_password,
|
||||
},
|
||||
});
|
||||
if (response.redirected) throw response;
|
||||
break;
|
||||
default:
|
||||
response = await client.request<EmptyObject>('/api/pleroma/change_password', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
password: current_password,
|
||||
new_password,
|
||||
new_password_confirmation: new_password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Request password reset e-mail
|
||||
*
|
||||
* Requires features{@link Features.resetPassword}.
|
||||
*/
|
||||
resetPassword: async (email?: string, nickname?: string) => {
|
||||
const response = await client.request<EmptyObject>('/auth/password', {
|
||||
method: 'POST',
|
||||
body: { email, nickname },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.changeEmail}.
|
||||
*/
|
||||
changeEmail: async (email: string, password: string) => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request<EmptyObject>('/api/v1/user/email_change', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
new_email: email,
|
||||
password,
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request<EmptyObject>('/api/pleroma/change_email', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
email,
|
||||
password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.deleteAccount}.
|
||||
*/
|
||||
deleteAccount: async (password: string) => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request<EmptyObject>('/api/v1/accounts/delete', {
|
||||
method: 'POST',
|
||||
body: { password },
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request<EmptyObject>('/api/pleroma/delete_account', {
|
||||
method: 'POST',
|
||||
body: { password },
|
||||
});
|
||||
}
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.deleteAccountWithoutPassword}.
|
||||
*/
|
||||
deleteAccountWithoutPassword: async () => {
|
||||
const response = await client.request<EmptyObject>('/api/v1/settings/delete_account', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable an account
|
||||
*
|
||||
* Requires features{@link Features.disableAccount}.
|
||||
*/
|
||||
disableAccount: async (password: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/pleroma/disable_account', {
|
||||
method: 'POST',
|
||||
body: { password },
|
||||
});
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.accountMoving}.
|
||||
*/
|
||||
moveAccount: async (target_account: string, password: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/pleroma/move_account', {
|
||||
method: 'POST',
|
||||
body: { password, target_account },
|
||||
});
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
mfa: {
|
||||
/**
|
||||
* Requires features{@link Features.manageMfa}.
|
||||
*/
|
||||
getMfaSettings: async () => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/user').then(({ json }) => ({
|
||||
settings: {
|
||||
enabled: !!json?.two_factor_enabled_at,
|
||||
totp: !!json?.two_factor_enabled_at,
|
||||
},
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
response = (await client.request('/api/pleroma/accounts/mfa')).json;
|
||||
}
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
settings: coerceObject({
|
||||
enabled: v.boolean(),
|
||||
totp: v.boolean(),
|
||||
}),
|
||||
}),
|
||||
response,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.manageMfa}.
|
||||
*/
|
||||
getMfaBackupCodes: async () => {
|
||||
const response = await client.request('/api/pleroma/accounts/mfa/backup_codes');
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
codes: v.array(v.string()),
|
||||
}),
|
||||
response.json,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.manageMfa}.
|
||||
*/
|
||||
getMfaSetup: async (method: 'totp') => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/user/2fa/qruri').then(({ data }) => ({
|
||||
provisioning_uri: data,
|
||||
key: new URL(data).searchParams.get('secret'),
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
response = (await client.request(`/api/pleroma/accounts/mfa/setup/${method}`)).json;
|
||||
}
|
||||
|
||||
return v.parse(
|
||||
v.object({
|
||||
key: v.fallback(v.string(), ''),
|
||||
provisioning_uri: v.string(),
|
||||
}),
|
||||
response,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.manageMfa}.
|
||||
*/
|
||||
confirmMfaSetup: async (method: 'totp', code: string, password: string) => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/user/2fa/enable', {
|
||||
method: 'POST',
|
||||
body: { code },
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = (
|
||||
await client.request(`/api/pleroma/accounts/mfa/confirm/${method}`, {
|
||||
method: 'POST',
|
||||
body: { code, password },
|
||||
})
|
||||
).json;
|
||||
}
|
||||
|
||||
if (response?.error) throw response.error;
|
||||
|
||||
return response as EmptyObject;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.manageMfa}.
|
||||
*/
|
||||
disableMfa: async (method: 'totp', password: string) => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request<EmptyObject>('/api/v1/user/2fa/disable', {
|
||||
method: 'POST',
|
||||
body: { password },
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request<EmptyObject>(`/api/pleroma/accounts/mfa/${method}`, {
|
||||
method: 'DELETE',
|
||||
body: { password },
|
||||
});
|
||||
}
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
|
||||
return response.json;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/import', {
|
||||
method: 'POST',
|
||||
body: { data: list, type: 'following', mode },
|
||||
contentType: '',
|
||||
});
|
||||
break;
|
||||
case MITRA:
|
||||
response = await client.request('/api/v1/settings/import_follows', {
|
||||
method: 'POST',
|
||||
body: { follows_csv: typeof list === 'string' ? list : await list.text() },
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request('/api/pleroma/follow_import', {
|
||||
method: 'POST',
|
||||
body: { list },
|
||||
contentType: '',
|
||||
});
|
||||
}
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Move followers from remote alias. (experimental?)
|
||||
*
|
||||
* Requires features{@link Features.importFollowers}.
|
||||
*/
|
||||
importFollowers: async (list: File | string, actorId: string) => {
|
||||
const response = await client.request('/api/v1/settings/import_followers', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
from_actor_id: actorId,
|
||||
followers_csv: typeof list === 'string' ? list : await list.text(),
|
||||
},
|
||||
});
|
||||
|
||||
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 (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/import', {
|
||||
method: 'POST',
|
||||
body: { data: list, type: 'blocks', mode },
|
||||
contentType: '',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request('/api/pleroma/blocks_import', {
|
||||
method: 'POST',
|
||||
body: { list },
|
||||
contentType: '',
|
||||
});
|
||||
}
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Imports your mutes.
|
||||
*
|
||||
* Requires features{@link Features.importMutes}.
|
||||
* `overwrite` mode requires features{@link Features.importOverwrite}.
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apipleromamutes_import}
|
||||
*/
|
||||
importMutes: async (list: File | string, mode?: 'merge' | 'overwrite') => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/import', {
|
||||
method: 'POST',
|
||||
body: { data: list, type: 'blocks', mode },
|
||||
contentType: '',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request('/api/pleroma/mutes_import', {
|
||||
method: 'POST',
|
||||
body: { list },
|
||||
contentType: '',
|
||||
});
|
||||
}
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Export followers to CSV file
|
||||
*
|
||||
* Requires features{@link Features.exportFollowers}.
|
||||
*/
|
||||
exportFollowers: async () => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/exports/followers.csv', {
|
||||
method: 'GET',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request('/api/v1/settings/export_followers', {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Export follows to CSV file
|
||||
*
|
||||
* Requires features{@link Features.exportFollows}.
|
||||
*/
|
||||
exportFollows: async () => {
|
||||
let response;
|
||||
|
||||
switch (client.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await client.request('/api/v1/exports/following.csv', {
|
||||
method: 'GET',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = await client.request('/api/v1/settings/export_follows', {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Export lists to CSV file
|
||||
*
|
||||
* Requires features{@link Features.exportLists}.
|
||||
*/
|
||||
exportLists: async () => {
|
||||
const response = await client.request('/api/v1/exports/lists.csv', {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Export blocks to CSV file
|
||||
*
|
||||
* Requires features{@link Features.exportBlocks}.
|
||||
*/
|
||||
exportBlocks: async () => {
|
||||
const response = await client.request('/api/v1/exports/blocks.csv', {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Export mutes to CSV file
|
||||
*
|
||||
* Requires features{@link Features.exportMutes}.
|
||||
*/
|
||||
exportMutes: async () => {
|
||||
const response = await client.request('/api/v1/exports/mutes.csv', {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 client.request('/api/pleroma/notification_settings', {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
});
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
|
||||
return v.parse(v.object({ status: v.string() }), 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 client.request('/api/v1/interaction_policies/defaults');
|
||||
|
||||
return v.parse(interactionPoliciesSchema, 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 client.request('/api/v1/interaction_policies/defaults', {
|
||||
method: 'PATCH',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(interactionPoliciesSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* List frontend setting profiles
|
||||
*
|
||||
* Requires features{@link Features.preferredFrontends}.
|
||||
*/
|
||||
getAvailableFrontends: async () => {
|
||||
const response = await client.request('/api/v1/akkoma/preferred_frontend/available');
|
||||
|
||||
return v.parse(v.array(v.string()), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update preferred frontend setting
|
||||
*
|
||||
* Store preferred frontend in cookies
|
||||
*
|
||||
* Requires features{@link Features.preferredFrontends}.
|
||||
*/
|
||||
setPreferredFrontend: async (frontendName: string) => {
|
||||
const response = await client.request('/api/v1/akkoma/preferred_frontend', {
|
||||
method: 'PUT',
|
||||
body: { frontend_name: frontendName },
|
||||
});
|
||||
|
||||
return v.parse(v.object({ frontend_name: v.string() }), response.json);
|
||||
},
|
||||
|
||||
authorizeIceshrimp: async () => {
|
||||
const response = await client.request<string>('/api/v1/accounts/authorize_iceshrimp', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { settings };
|
||||
69
packages/pl-api/lib/client/shoutbox.ts
Normal file
69
packages/pl-api/lib/client/shoutbox.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { WebSocket } from 'isows';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { shoutMessageSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { buildFullPath } from '../utils/url';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { ShoutMessage } from '../entities';
|
||||
|
||||
const shoutbox = (client: PlApiBaseClient) => ({
|
||||
connect: (
|
||||
token: string,
|
||||
{
|
||||
onMessage,
|
||||
onMessages,
|
||||
}: {
|
||||
onMessages: (messages: Array<ShoutMessage>) => void;
|
||||
onMessage: (message: ShoutMessage) => void;
|
||||
},
|
||||
) => {
|
||||
let counter = 2;
|
||||
let intervalId: NodeJS.Timeout;
|
||||
if (client.shoutSocket) return client.shoutSocket;
|
||||
|
||||
const path = buildFullPath('/socket/websocket', client.baseURL, { token, vsn: '2.0.0' });
|
||||
|
||||
const ws = new WebSocket(path);
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const [_, __, ___, type, payload] = JSON.parse(event.data as string);
|
||||
if (type === 'new_msg') {
|
||||
const message = v.parse(shoutMessageSchema, payload);
|
||||
onMessage(message);
|
||||
} else if (type === 'messages') {
|
||||
const messages = v.parse(filteredArray(shoutMessageSchema), payload.messages);
|
||||
onMessages(messages);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify(['3', `${++counter}`, 'chat:public', 'phx_join', {}]));
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
ws.send(JSON.stringify([null, `${++counter}`, 'phoenix', 'heartbeat', {}]));
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
|
||||
client.shoutSocket = {
|
||||
message: (text: string) => {
|
||||
// guess this is meant to be incremented on each call but idk
|
||||
ws.send(JSON.stringify(['3', `${++counter}`, 'chat:public', 'new_msg', { text: text }]));
|
||||
},
|
||||
close: () => {
|
||||
ws.close();
|
||||
client.shoutSocket = undefined;
|
||||
clearInterval(intervalId);
|
||||
},
|
||||
};
|
||||
|
||||
return client.shoutSocket;
|
||||
},
|
||||
});
|
||||
|
||||
export { shoutbox };
|
||||
600
packages/pl-api/lib/client/statuses.ts
Normal file
600
packages/pl-api/lib/client/statuses.ts
Normal file
@ -0,0 +1,600 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
accountSchema,
|
||||
bookmarkFolderSchema,
|
||||
contextSchema,
|
||||
emojiReactionSchema,
|
||||
partialStatusSchema,
|
||||
scheduledStatusSchema,
|
||||
statusEditSchema,
|
||||
statusSchema,
|
||||
statusSourceSchema,
|
||||
translationSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { AKKOMA, ICESHRIMP_NET, MITRA, PLEROMA } from '../features';
|
||||
import { getAsyncRefreshHeader } from '../request';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
CreateStatusParams,
|
||||
EditInteractionPolicyParams,
|
||||
EditStatusParams,
|
||||
GetFavouritedByParams,
|
||||
GetRebloggedByParams,
|
||||
GetStatusContextParams,
|
||||
GetStatusMentionedUsersParams,
|
||||
GetStatusParams,
|
||||
GetStatusQuotesParams,
|
||||
GetStatusReferencesParams,
|
||||
GetStatusesParams,
|
||||
} from '../params/statuses';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const statuses = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Post a new status
|
||||
* Publish a status with the given parameters.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/statuses/#create}
|
||||
*/
|
||||
createStatus: async (params: CreateStatusParams) => {
|
||||
type ExtendedCreateStatusParams = CreateStatusParams & {
|
||||
markdown?: boolean;
|
||||
circle_id?: string | null;
|
||||
};
|
||||
|
||||
const fixedParams: ExtendedCreateStatusParams = params;
|
||||
|
||||
if (
|
||||
params.content_type === 'text/markdown' &&
|
||||
client.instanceInformation.api_versions['kmyblue_markdown.fedibird.pl-api'] >= 1
|
||||
) {
|
||||
fixedParams.markdown = true;
|
||||
}
|
||||
if (params.visibility?.startsWith('api/v1/bookmark_categories')) {
|
||||
fixedParams.circle_id = params.visibility.slice(7);
|
||||
fixedParams.visibility = 'circle';
|
||||
}
|
||||
if (params.quote_id && client.instanceInformation.api_versions.mastodon >= 7)
|
||||
params.quoted_status_id = params.quote_id;
|
||||
else if (params.quoted_status_id && (client.instanceInformation.api_versions.mastodon || 0) < 7)
|
||||
params.quote_id = params.quoted_status_id;
|
||||
|
||||
const input =
|
||||
params.preview && client.features.version.software === MITRA
|
||||
? '/api/v1/statuses/preview'
|
||||
: '/api/v1/statuses';
|
||||
|
||||
const response = await client.request(input, {
|
||||
method: 'POST',
|
||||
body: fixedParams,
|
||||
});
|
||||
|
||||
if (response.json?.scheduled_at) return v.parse(scheduledStatusSchema, response.json);
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.createStatusPreview}.
|
||||
*/
|
||||
previewStatus: async (params: CreateStatusParams) => {
|
||||
const input =
|
||||
client.features.version.software === PLEROMA || client.features.version.software === AKKOMA
|
||||
? '/api/v1/statuses'
|
||||
: '/api/v1/statuses/preview';
|
||||
|
||||
if (
|
||||
client.features.version.software === PLEROMA ||
|
||||
client.features.version.software === AKKOMA
|
||||
) {
|
||||
params.preview = true;
|
||||
}
|
||||
|
||||
const response = await client.request(input, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(v.partial(partialStatusSchema), 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 client.request(`/api/v1/statuses/${statusId}`, { params });
|
||||
|
||||
return v.parse(statusSchema, 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 client.request('/api/v1/statuses', {
|
||||
params: { ...params, id: statusIds },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(statusSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a status
|
||||
* Delete one of your own statuses.
|
||||
*
|
||||
* `delete_media` parameter requires features{@link Features.deleteMedia}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/statuses/#delete}
|
||||
*/
|
||||
deleteStatus: async (statusId: string, deleteMedia?: boolean) => {
|
||||
const response = await client.request(`/api/v1/statuses/${statusId}`, {
|
||||
method: 'DELETE',
|
||||
params: { delete_media: deleteMedia },
|
||||
});
|
||||
|
||||
return v.parse(statusSourceSchema, 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 client.request(`/api/v1/statuses/${statusId}/context`, { params });
|
||||
|
||||
const asyncRefreshHeader = getAsyncRefreshHeader(response);
|
||||
|
||||
return { asyncRefreshHeader, ...v.parse(contextSchema, 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 (client.features.version.software === AKKOMA) {
|
||||
response = await client.request(`/api/v1/statuses/${statusId}/translations/${lang}`);
|
||||
} else {
|
||||
response = await client.request(`/api/v1/statuses/${statusId}/translate`, {
|
||||
method: 'POST',
|
||||
body: { lang },
|
||||
});
|
||||
}
|
||||
|
||||
return v.parse(translationSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate multiple statuses into given language.
|
||||
*
|
||||
* Requires features{@link Features.lazyTranslations}.
|
||||
*/
|
||||
translateStatuses: async (statusIds: Array<string>, lang: string) => {
|
||||
const response = await client.request('/api/v1/pl/statuses/translate', {
|
||||
method: 'POST',
|
||||
body: { ids: statusIds, lang },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(translationSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* See who boosted a status
|
||||
* View who boosted a given status.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/statuses/#reblogged_by}
|
||||
*/
|
||||
getRebloggedBy: (statusId: string, params?: GetRebloggedByParams) =>
|
||||
client.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: (statusId: string, params?: GetFavouritedByParams) =>
|
||||
client.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 client.request(`/api/v1/statuses/${statusId}/favourite`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/unfavourite`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Boost a status
|
||||
* Reshare a status on your own profile.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/statuses/#reblog}
|
||||
*
|
||||
* Specifying reblog visibility requires features{@link Features.reblogVisibility}.
|
||||
*/
|
||||
reblogStatus: async (statusId: string, visibility?: string) => {
|
||||
const response = await client.request(`/api/v1/statuses/${statusId}/reblog`, {
|
||||
method: 'POST',
|
||||
body: { visibility },
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/unreblog`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/bookmark`, {
|
||||
method: 'POST',
|
||||
body: { folder_id: folderId },
|
||||
});
|
||||
|
||||
if (folderId && client.features.bookmarkFoldersMultiple) {
|
||||
await client.request(`/api/v1/bookmark_categories/${folderId}/statuses`, {
|
||||
method: 'POST',
|
||||
params: { status_ids: [statusId] },
|
||||
});
|
||||
}
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/unbookmark`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Revoke a quote post
|
||||
* Revoke quote authorization of status `quoting_status_id`, detaching status `id`.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/statuses/#revoke_quote}
|
||||
*/
|
||||
revokeQuote: async (statusId: string, quotingStatusId: string) => {
|
||||
const response = await client.request(
|
||||
`/api/v1/statuses/${statusId}/quotes/${quotingStatusId}/revoke`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/mute`, { method: 'POST' });
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/unmute`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/pin`, { method: 'POST' });
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/unpin`, { method: 'POST' });
|
||||
|
||||
return v.parse(statusSchema, 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) => {
|
||||
type ExtendedEditStatusParams = EditStatusParams & {
|
||||
markdown?: boolean;
|
||||
};
|
||||
|
||||
const fixedParams: ExtendedEditStatusParams = params;
|
||||
|
||||
if (
|
||||
params.content_type === 'text/markdown' &&
|
||||
client.instanceInformation.api_versions['kmyblue_markdown.fedibird.pl-api'] >= 1
|
||||
) {
|
||||
fixedParams.markdown = true;
|
||||
}
|
||||
|
||||
const response = await client.request(`/api/v1/statuses/${statusId}`, {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit a status' interaction policies
|
||||
* Edit a given status to change its interaction policies. Currently, this means changing its quote approval policy.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/statuses/#edit_interaction_policy}
|
||||
*/
|
||||
editInteractionPolicy: async (statusId: string, params: EditInteractionPolicyParams) => {
|
||||
const response = await client.request(`/api/v1/statuses/${statusId}`, {
|
||||
method: 'PUT',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/v1/statuses/${statusId}/history`);
|
||||
|
||||
return v.parse(filteredArray(statusEditSchema), 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 client.request(`/api/v1/statuses/${statusId}/source`);
|
||||
|
||||
return v.parse(statusSourceSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an object of emoji to account mappings with accounts that reacted to the post
|
||||
*
|
||||
* Requires features{@link Features.emojiReactsList}.
|
||||
* @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 apiVersions = client.instanceInformation.api_versions;
|
||||
|
||||
let response;
|
||||
if (
|
||||
apiVersions['emoji_reactions.pleroma.pl-api'] >= 1 ||
|
||||
client.features.version.software === ICESHRIMP_NET
|
||||
) {
|
||||
response = await client.request(
|
||||
`/api/v1/pleroma/statuses/${statusId}/reactions${emoji ? `/${emoji}` : ''}`,
|
||||
);
|
||||
} else {
|
||||
if (apiVersions['emoji_reaction.fedibird.pl-api'] >= 1) {
|
||||
response = await client.request(`/api/v1/statuses/${statusId}/emoji_reactioned_by`);
|
||||
} else {
|
||||
response = await client.request(`/api/v1/statuses/${statusId}/reactions`, {
|
||||
params: { emoji },
|
||||
});
|
||||
}
|
||||
response.json = response.json?.reduce((acc: Array<any>, cur: any) => {
|
||||
if (emoji && cur.name !== emoji) return acc;
|
||||
|
||||
const existing = acc.find((reaction) => reaction.name === cur.name);
|
||||
|
||||
if (existing) {
|
||||
existing.accounts.push(cur.account);
|
||||
existing.account_ids.push(cur.account.id);
|
||||
existing.count += 1;
|
||||
} else
|
||||
acc.push({ count: 1, accounts: [cur.account], account_ids: [cur.account.id], ...cur });
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
return v.parse(filteredArray(emojiReactionSchema), 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 apiVersions = client.instanceInformation.api_versions;
|
||||
|
||||
let response;
|
||||
if (
|
||||
apiVersions['emoji_reactions.pleroma.pl-api'] >= 1 ||
|
||||
client.features.version.software === MITRA
|
||||
) {
|
||||
response = await client.request(
|
||||
`/api/v1/pleroma/statuses/${statusId}/reactions/${encodeURIComponent(emoji)}`,
|
||||
{ method: 'PUT' },
|
||||
);
|
||||
} else {
|
||||
response = await client.request(
|
||||
`/api/v1/statuses/${statusId}/react/${encodeURIComponent(emoji)}`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
}
|
||||
|
||||
return v.parse(statusSchema, 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 apiVersions = client.instanceInformation.api_versions;
|
||||
|
||||
let response;
|
||||
if (
|
||||
apiVersions['emoji_reactions.pleroma.pl-api'] >= 1 ||
|
||||
client.features.version.software === MITRA
|
||||
) {
|
||||
response = await client.request(`/api/v1/pleroma/statuses/${statusId}/reactions/${emoji}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} else {
|
||||
response = await client.request(
|
||||
`/api/v1/statuses/${statusId}/unreact/${encodeURIComponent(emoji)}`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
}
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* View quotes for a given status
|
||||
*
|
||||
* Requires features{@link Features.quotePosts}.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/statuses/#quotes}
|
||||
*/
|
||||
getStatusQuotes: (statusId: string, params?: GetStatusQuotesParams) =>
|
||||
client.paginatedGet(
|
||||
client.instanceInformation.api_versions.mastodon >= 7
|
||||
? `/api/v1/statuses/${statusId}/quotes`
|
||||
: `/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: (statusId: string) =>
|
||||
client.paginatedGet(`/api/v1/statuses/${statusId}/disliked_by`, {}, accountSchema),
|
||||
|
||||
/**
|
||||
* 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 client.request(`/api/friendica/statuses/${statusId}/dislike`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, 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 client.request(`/api/friendica/statuses/${statusId}/undislike`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(statusSchema, response.json);
|
||||
},
|
||||
|
||||
getStatusReferences: (statusId: string, params?: GetStatusReferencesParams) =>
|
||||
client.paginatedGet(`/api/v1/statuses/${statusId}/referred_by`, { params }, statusSchema),
|
||||
|
||||
getStatusMentionedUsers: (statusId: string, params?: GetStatusMentionedUsersParams) =>
|
||||
client.paginatedGet(`/api/v1/statuses/${statusId}/mentioned_by`, { params }, accountSchema),
|
||||
|
||||
/**
|
||||
* Load conversation from a remote server.
|
||||
*
|
||||
* Requires features{@link Features.loadConversation}.
|
||||
*/
|
||||
loadConversation: async (statusId: string) => {
|
||||
const response = await client.request<EmptyObject>(
|
||||
`/api/v1/statuses/${statusId}/load_conversation`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.bookmarkFoldersMultiple}.
|
||||
*/
|
||||
getStatusBookmarkFolders: async (statusId: string) => {
|
||||
const response = await client.request(`/api/v1/statuses/${statusId}/bookmark_categories`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(bookmarkFolderSchema), response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { statuses };
|
||||
147
packages/pl-api/lib/client/stories.ts
Normal file
147
packages/pl-api/lib/client/stories.ts
Normal file
@ -0,0 +1,147 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
accountSchema,
|
||||
storyCarouselItemSchema,
|
||||
storyMediaSchema,
|
||||
storyProfileSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
CreateStoryParams,
|
||||
CreateStoryPollParams,
|
||||
CropStoryPhotoParams,
|
||||
StoryReportType,
|
||||
} from '../params/stories';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const stories = (client: PlApiBaseClient) => ({
|
||||
getRecentStories: async () => {
|
||||
const response = await client.request('/api/web/stories/v1/recent');
|
||||
|
||||
return v.parse(filteredArray(storyCarouselItemSchema), response.json);
|
||||
},
|
||||
|
||||
getStoryViewers: async (storyId: string) => {
|
||||
const response = await client.request('/api/web/stories/v1/viewers', {
|
||||
params: { sid: storyId },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(accountSchema), response.json);
|
||||
},
|
||||
|
||||
getStoriesForProfile: async (accountId: string) => {
|
||||
const response = await client.request(`/api/web/stories/v1/profile/${accountId}`);
|
||||
|
||||
return v.parse(filteredArray(storyProfileSchema), response.json);
|
||||
},
|
||||
|
||||
storyExists: async (accountId: string) => {
|
||||
const response = await client.request(`/api/web/stories/v1/exists/${accountId}`);
|
||||
|
||||
return v.parse(v.boolean(), response.json);
|
||||
},
|
||||
|
||||
getStoryPollResults: async (storyId: string) => {
|
||||
const response = await client.request('/api/web/stories/v1/poll/results', {
|
||||
params: { sid: storyId },
|
||||
});
|
||||
|
||||
return v.parse(v.array(v.number()), response.json);
|
||||
},
|
||||
|
||||
markStoryAsViewed: async (storyId: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/viewed', {
|
||||
method: 'POST',
|
||||
body: { id: storyId },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStoryReaction: async (storyId: string, emoji: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/react', {
|
||||
method: 'POST',
|
||||
body: { sid: storyId, reaction: emoji },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStoryComment: async (storyId: string, comment: string) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/comment', {
|
||||
method: 'POST',
|
||||
body: { sid: storyId, caption: comment },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStoryPoll: async (params: CreateStoryPollParams) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/publish/poll', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
storyPollVote: async (storyId: string, choiceId: number) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/publish/poll', {
|
||||
method: 'POST',
|
||||
body: { sid: storyId, ci: choiceId },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
reportStory: async (storyId: string, type: StoryReportType) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/report', {
|
||||
method: 'POST',
|
||||
body: { id: storyId, type },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
addMedia: async (file: File) => {
|
||||
const response = await client.request('/api/web/stories/v1/add', {
|
||||
method: 'POST',
|
||||
body: { file },
|
||||
contentType: '',
|
||||
});
|
||||
|
||||
return v.parse(storyMediaSchema, response.json);
|
||||
},
|
||||
|
||||
cropPhoto: async (mediaId: string, params: CropStoryPhotoParams) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/crop', {
|
||||
method: 'POST',
|
||||
body: { media_id: mediaId, ...params },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStory: async (mediaId: string, params: CreateStoryParams) => {
|
||||
const response = await client.request<EmptyObject>('/api/web/stories/v1/publish', {
|
||||
method: 'POST',
|
||||
body: { media_id: mediaId, ...params },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
deleteStory: async (storyId: string) => {
|
||||
const response = await client.request<EmptyObject>(`/api/web/stories/v1/delete/${storyId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
});
|
||||
|
||||
export { stories };
|
||||
75
packages/pl-api/lib/client/streaming.ts
Normal file
75
packages/pl-api/lib/client/streaming.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { WebSocket } from 'isows';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { streamingEventSchema } from '../entities';
|
||||
import { buildFullPath } from '../utils/url';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { StreamingEvent } from '../entities';
|
||||
|
||||
const streaming = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request('/api/v1/streaming/health');
|
||||
|
||||
return v.parse(v.literal('OK'), 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 (client.socket) return client.socket;
|
||||
|
||||
const path = buildFullPath(
|
||||
'/api/v1/streaming',
|
||||
client.instanceInformation?.configuration.urls.streaming,
|
||||
{ access_token: client.accessToken },
|
||||
);
|
||||
|
||||
const ws = new WebSocket(path, client.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 = v.parse(streamingEventSchema, JSON.parse(event.data as string));
|
||||
|
||||
listeners.filter(
|
||||
({ listener, stream }) => (!stream || message.stream.includes(stream)) && listener(message),
|
||||
);
|
||||
};
|
||||
|
||||
ws.onopen = () => {
|
||||
queue.forEach((fn) => fn());
|
||||
};
|
||||
|
||||
client.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();
|
||||
client.socket = undefined;
|
||||
},
|
||||
};
|
||||
|
||||
return client.socket;
|
||||
},
|
||||
});
|
||||
|
||||
export { streaming };
|
||||
129
packages/pl-api/lib/client/subscriptions.ts
Normal file
129
packages/pl-api/lib/client/subscriptions.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import {
|
||||
accountSchema,
|
||||
subscriptionDetailsSchema,
|
||||
subscriptionInvoiceSchema,
|
||||
subscriptionOptionSchema,
|
||||
} from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
const subscriptions = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* Add subscriber or extend existing subscription. Can be used if blockchain integration is not enabled.
|
||||
*
|
||||
* Requires features{@link Features.subscriptions}.
|
||||
* @param subscriberId - The subscriber ID.
|
||||
* @param duration - The subscription duration (in seconds).
|
||||
*/
|
||||
createSubscription: async (subscriberId: string, duration: number) => {
|
||||
const response = await client.request('/api/v1/subscriptions', {
|
||||
method: 'POST',
|
||||
body: { subscriber_id: subscriberId, duration },
|
||||
});
|
||||
|
||||
return v.parse(subscriptionDetailsSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get list of subscription options
|
||||
*
|
||||
* Requires features{@link Features.subscriptions}.
|
||||
*/
|
||||
getSubscriptionOptions: async () => {
|
||||
const response = await client.request('/api/v1/subscriptions/options');
|
||||
|
||||
return v.parse(filteredArray(subscriptionOptionSchema), response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable subscriptions or update subscription settings
|
||||
*
|
||||
* Requires features{@link Features.subscriptions}.
|
||||
* @param type - Subscription type
|
||||
* @param chainId - CAIP-2 chain ID.
|
||||
* @param price - Subscription price (only for Monero)
|
||||
* @param payoutAddress - Payout address (only for Monero)
|
||||
*/
|
||||
updateSubscription: async (
|
||||
type: 'monero',
|
||||
chainId?: string,
|
||||
price?: number,
|
||||
payoutAddress?: string,
|
||||
) => {
|
||||
const response = await client.request('/api/v1/subscriptions/options', {
|
||||
method: 'POST',
|
||||
body: { type, chain_id: chainId, price, payout_address: payoutAddress },
|
||||
});
|
||||
|
||||
return v.parse(accountSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Find subscription by sender and recipient
|
||||
*
|
||||
* Requires features{@link Features.subscriptions}.
|
||||
* @param senderId - Sender ID.
|
||||
* @param recipientId - Recipient ID.
|
||||
*/
|
||||
findSubscription: async (senderId: string, recipientId: string) => {
|
||||
const response = await client.request('/api/v1/subscriptions/find', {
|
||||
params: { sender_id: senderId, recipient_id: recipientId },
|
||||
});
|
||||
|
||||
return v.parse(subscriptionDetailsSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create invoice
|
||||
*
|
||||
* Requires features{@link Features.subscriptions}.
|
||||
* @param senderId - Sender ID.
|
||||
* @param recipientId - Recipient ID.
|
||||
* @param chainId - CAIP-2 chain ID.
|
||||
* @param amount - Requested payment amount (in atomic units).
|
||||
*/
|
||||
createInvoice: async (senderId: string, recipientId: string, chainId: string, amount: number) => {
|
||||
const response = await client.request('/api/v1/subscriptions/invoices', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
sender_id: senderId,
|
||||
recipient_id: recipientId,
|
||||
chain_id: chainId,
|
||||
amount,
|
||||
},
|
||||
});
|
||||
|
||||
return v.parse(subscriptionInvoiceSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* View information about an invoice.
|
||||
*
|
||||
* Requires features{@link Features.invoices}.
|
||||
* @param invoiceId - Invoice ID
|
||||
*/
|
||||
getInvoice: async (invoiceId: string) => {
|
||||
const response = await client.request(`/api/v1/subscriptions/invoices/${invoiceId}`);
|
||||
|
||||
return v.parse(subscriptionInvoiceSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel invoice.
|
||||
*
|
||||
* Requires features{@link Features.invoices}.
|
||||
* @param invoiceId - Invoice ID
|
||||
*/
|
||||
cancelInvoice: async (invoiceId: string) => {
|
||||
const response = await client.request(`/api/v1/subscriptions/invoices/${invoiceId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return v.parse(subscriptionInvoiceSchema, response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { subscriptions };
|
||||
150
packages/pl-api/lib/client/timelines.ts
Normal file
150
packages/pl-api/lib/client/timelines.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { conversationSchema, markersSchema, statusSchema } from '../entities';
|
||||
import { PIXELFED } from '../features';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type {
|
||||
AntennaTimelineParams,
|
||||
BubbleTimelineParams,
|
||||
GetConversationsParams,
|
||||
GroupTimelineParams,
|
||||
HashtagTimelineParams,
|
||||
HomeTimelineParams,
|
||||
LinkTimelineParams,
|
||||
ListTimelineParams,
|
||||
PublicTimelineParams,
|
||||
SaveMarkersParams,
|
||||
WrenchedTimelineParams,
|
||||
} from '../params/timelines';
|
||||
|
||||
type EmptyObject = Record<string, never>;
|
||||
|
||||
const timelines = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* View public timeline
|
||||
* View public statuses.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/timelines/#public}
|
||||
*/
|
||||
publicTimeline: (params?: PublicTimelineParams) =>
|
||||
client.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) =>
|
||||
client.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) =>
|
||||
client.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?: LinkTimelineParams) =>
|
||||
client.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) =>
|
||||
client.paginatedGet(`/api/v1/timelines/list/${listId}`, { params }, statusSchema),
|
||||
|
||||
/**
|
||||
* View all conversations
|
||||
* @see {@link https://docs.joinmastodon.org/methods/conversations/#get}
|
||||
*/
|
||||
getConversations: (params?: GetConversationsParams) =>
|
||||
client.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 client.request<EmptyObject>(`/api/v1/conversations/${conversationId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* Mark a conversation as read
|
||||
* @see {@link https://docs.joinmastodon.org/methods/conversations/#read}
|
||||
*/
|
||||
markConversationRead: async (conversationId: string) => {
|
||||
const response = await client.request(`/api/v1/conversations/${conversationId}/read`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return v.parse(conversationSchema, 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 client.request('/api/v1/markers', { params: { timeline: timelines } });
|
||||
|
||||
return v.parse(markersSchema, 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 client.request('/api/v1/markers', { method: 'POST', body: params });
|
||||
|
||||
return v.parse(markersSchema, response.json);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.groups}.
|
||||
*/
|
||||
groupTimeline: (groupId: string, params?: GroupTimelineParams) =>
|
||||
client.paginatedGet(
|
||||
client.features.version.software === PIXELFED
|
||||
? `/api/v0/groups/${groupId}/feed`
|
||||
: `/api/v1/timelines/group/${groupId}`,
|
||||
{ params },
|
||||
statusSchema,
|
||||
),
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.bubbleTimeline}.
|
||||
*/
|
||||
bubbleTimeline: (params?: BubbleTimelineParams) =>
|
||||
client.paginatedGet('/api/v1/timelines/bubble', { params }, statusSchema),
|
||||
|
||||
/**
|
||||
* View antenna timeline
|
||||
* Requires features{@link Features.antennas}.
|
||||
*/
|
||||
antennaTimeline: (antennaId: string, params?: AntennaTimelineParams) =>
|
||||
client.paginatedGet(`/api/v1/timelines/antenna/${antennaId}`, { params }, statusSchema),
|
||||
|
||||
/**
|
||||
* Requires features{@link Features.wrenchedTimeline}.
|
||||
*/
|
||||
wrenchedTimeline: (params?: WrenchedTimelineParams) =>
|
||||
client.paginatedGet('/api/v1/pleroma/timelines/wrenched', { params }, statusSchema),
|
||||
});
|
||||
|
||||
export { timelines };
|
||||
55
packages/pl-api/lib/client/trends.ts
Normal file
55
packages/pl-api/lib/client/trends.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { statusSchema, tagSchema, trendsLinkSchema } from '../entities';
|
||||
import { filteredArray } from '../entities/utils';
|
||||
import { PIXELFED } from '../features';
|
||||
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
import type { GetTrendingLinks, GetTrendingStatuses, GetTrendingTags } from '../params/trends';
|
||||
|
||||
const trends = (client: PlApiBaseClient) => ({
|
||||
/**
|
||||
* 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 client.request(
|
||||
client.features.version.software === PIXELFED
|
||||
? '/api/v1.1/discover/posts/hashtags'
|
||||
: '/api/v1/trends/tags',
|
||||
{ params },
|
||||
);
|
||||
|
||||
return v.parse(filteredArray(tagSchema), 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 client.request(
|
||||
client.features.version.software === PIXELFED
|
||||
? '/api/pixelfed/v2/discover/posts/trending'
|
||||
: '/api/v1/trends/statuses',
|
||||
{ params },
|
||||
);
|
||||
|
||||
return v.parse(filteredArray(statusSchema), 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 client.request('/api/v1/trends/links', { params });
|
||||
|
||||
return v.parse(filteredArray(trendsLinkSchema), response.json);
|
||||
},
|
||||
});
|
||||
|
||||
export { trends };
|
||||
7
packages/pl-api/lib/client/utils.ts
Normal file
7
packages/pl-api/lib/client/utils.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { PlApiBaseClient } from '../client-base';
|
||||
|
||||
const utils = (client: PlApiBaseClient) => ({
|
||||
paginatedGet: client.paginatedGet.bind(client),
|
||||
});
|
||||
|
||||
export { utils };
|
||||
@ -1,4 +1,5 @@
|
||||
export { default as PlApiClient } from './client';
|
||||
export { PlApiBaseClient } from './client-base';
|
||||
export { PlApiDirectoryClient } from './directory-client';
|
||||
export { type Response as PlApiResponse } from './request';
|
||||
export * from './entities';
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import LinkHeader from 'http-link-header';
|
||||
import { serialize } from 'object-to-formdata';
|
||||
|
||||
import PlApiClient from './client';
|
||||
import { buildFullPath } from './utils/url';
|
||||
|
||||
import type { PlApiBaseClient } from './client-base';
|
||||
|
||||
type Response<T = any> = {
|
||||
headers: Headers;
|
||||
ok: boolean;
|
||||
@ -84,7 +85,7 @@ type RequestMeta = Pick<RequestBody, 'idempotencyKey' | 'onUploadProgress' | 'si
|
||||
|
||||
function request<T = any>(
|
||||
this: Pick<
|
||||
PlApiClient,
|
||||
PlApiBaseClient,
|
||||
'accessToken' | 'customAuthorizationToken' | 'iceshrimpAccessToken' | 'baseURL'
|
||||
>,
|
||||
input: URL | RequestInfo,
|
||||
|
||||
Reference in New Issue
Block a user