From 67b83191468e2f35085354318c65c726439ec7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicole=20Miko=C5=82ajczyk?= Date: Tue, 27 May 2025 20:36:13 +0200 Subject: [PATCH] pl-api: Update Mastodon API basing on mastodon/documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicole Mikołajczyk --- packages/pl-api/lib/client.ts | 69 +++++++++++++++++-- packages/pl-api/lib/entities/account.ts | 3 +- packages/pl-api/lib/entities/filter.ts | 6 +- packages/pl-api/lib/entities/index.ts | 3 + packages/pl-api/lib/entities/instance.ts | 7 +- packages/pl-api/lib/entities/notification.ts | 2 +- packages/pl-api/lib/entities/rule.ts | 4 ++ .../lib/entities/web-push-subscription.ts | 1 + packages/pl-api/lib/features.ts | 5 ++ packages/pl-api/lib/params/settings.ts | 3 +- .../components/registration-form.tsx | 4 +- 11 files changed, 91 insertions(+), 16 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 088639e1f..0a1d75c8c 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -29,6 +29,7 @@ import { bookmarkFolderSchema, chatMessageSchema, chatSchema, + circleSchema, contextSchema, conversationSchema, credentialAccountSchema, @@ -44,6 +45,7 @@ import { filterKeywordSchema, filterSchema, filterStatusSchema, + groupedNotificationsResultsSchema, groupMemberSchema, groupRelationshipSchema, groupSchema, @@ -62,6 +64,7 @@ import { partialStatusSchema, pleromaConfigSchema, pollSchema, + privacyPolicySchema, relationshipSchema, reportSchema, rssFeedSchema, @@ -69,6 +72,7 @@ import { scheduledStatusSchema, scrobbleSchema, searchSchema, + shoutMessageSchema, statusEditSchema, statusSchema, statusSourceSchema, @@ -78,14 +82,12 @@ import { subscriptionOptionSchema, suggestionSchema, tagSchema, + termfsOfServiceSchema, tokenSchema, translationSchema, trendsLinkSchema, webPushSubscriptionSchema, } from './entities'; -import { circleSchema } from './entities/circle'; -import { type GroupedNotificationsResults, groupedNotificationsResultsSchema, type NotificationGroup } from './entities/grouped-notifications-results'; -import { ShoutMessage, shoutMessageSchema } from './entities/shout-message'; import { coerceObject, filteredArray } from './entities/utils'; import { AKKOMA, type Features, getFeatures, GOTOSOCIAL, ICESHRIMP_NET, MITRA, PIXELFED, PLEROMA } from './features'; import request, { getNextLink, getPrevLink, type RequestBody, type RequestMeta } from './request'; @@ -97,10 +99,13 @@ import type { AdminAnnouncement, AdminModerationLogEntry, AdminReport, + GroupedNotificationsResults, GroupRole, Instance, Notification, + NotificationGroup, PleromaConfig, + ShoutMessage, Status, StreamingEvent, } from './entities'; @@ -1278,7 +1283,7 @@ class PlApiClient { createAccount: async (params: CreateAccountParams) => { const response = await this.request('/api/v1/accounts', { method: 'POST', - body: { language: params.locale, ...params }, + body: { language: params.locale, birthday: params.date_of_birth, ...params }, }); return v.parse(tokenSchema, response.json); @@ -2352,10 +2357,12 @@ class PlApiClient { /** * Delete a status * Delete one of your own statuses. + * + * `delete_media` parameters requires features{@link Features.deleteMedia}. * @see {@link https://docs.joinmastodon.org/methods/statuses/#delete} - */ - deleteStatus: async (statusId: string) => { - const response = await this.request(`/api/v1/statuses/${statusId}`, { method: 'DELETE' }); + */ + deleteStatus: async (statusId: string, deleteMedia?: boolean) => { + const response = await this.request(`/api/v1/statuses/${statusId}`, { method: 'DELETE', params: { delete_media: deleteMedia } }); return v.parse(statusSourceSchema, response.json); }, @@ -2749,6 +2756,21 @@ class PlApiClient { 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 this.request<{}>(`/api/v1/media/${attachmentId}`, { + method: 'DELETE', + }); + + return response.json; + }, }; public readonly polls = { @@ -3607,6 +3629,39 @@ class PlApiClient { 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 this.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 this.request('/api/v1/instance/terms_of_service'); + + return v.parse(termfsOfServiceSchema, 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 this.request(`/api/v1/instance/terms_of_service/${date}`); + + return v.parse(termfsOfServiceSchema, response.json); + }, }; public readonly trends = { diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index fe958b494..5f4a85b26 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -131,6 +131,7 @@ const baseAccountSchema = v.object({ followers_count: v.fallback(v.number(), 0), following_count: v.fallback(v.number(), 0), roles: filteredArray(roleSchema), + hide_collections: v.fallback(v.optional(v.boolean()), undefined), fqn: v.string(), ap_id: v.fallback(v.nullable(v.string()), null), @@ -158,7 +159,6 @@ const baseAccountSchema = v.object({ custom_css: v.fallback(v.string(), ''), enable_rss: v.fallback(v.boolean(), false), header_description: v.fallback(v.string(), ''), - hide_collections: v.fallback(v.optional(v.boolean()), undefined), verified: v.fallback(v.optional(v.boolean()), undefined), domain: v.fallback(v.string(), ''), @@ -230,6 +230,7 @@ const accountSchema: v.BaseSchema> = untypedA const untypedCredentialAccountSchema = v.pipe(v.any(), preprocessAccount, v.object({ ...accountWithMovedAccountSchema.entries, source: v.fallback(v.nullable(v.object({ + attribution_domains: v.fallback(v.nullable(v.array(v.string())), null), note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), privacy: v.picklist(['public', 'unlisted', 'private', 'direct']), diff --git a/packages/pl-api/lib/entities/filter.ts b/packages/pl-api/lib/entities/filter.ts index ca41bfe49..9be775503 100644 --- a/packages/pl-api/lib/entities/filter.ts +++ b/packages/pl-api/lib/entities/filter.ts @@ -47,9 +47,9 @@ const filterSchema = v.pipe( title: v.string(), context: v.array(v.picklist(['home', 'notifications', 'public', 'thread', 'account'])), expires_at: v.fallback(v.nullable(datetimeSchema), null), - filter_action: v.fallback(v.picklist(['warn', 'hide']), 'warn'), - keywords: filteredArray(filterKeywordSchema), - statuses: filteredArray(filterStatusSchema), + filter_action: v.fallback(v.picklist(['warn', 'hide', 'blur']), 'warn'), + keywords: v.optional(filteredArray(filterKeywordSchema), undefined), + statuses: v.optional(filteredArray(filterStatusSchema), undefined), }), ); diff --git a/packages/pl-api/lib/entities/index.ts b/packages/pl-api/lib/entities/index.ts index d18355e4a..0422e5fec 100644 --- a/packages/pl-api/lib/entities/index.ts +++ b/packages/pl-api/lib/entities/index.ts @@ -61,6 +61,8 @@ export * from './notification-request'; export * from './oauth-token'; export * from './poll'; export * from './preview-card'; +export * from './preview-card-author'; +export * from './privacy-policy'; export * from './relationship'; export * from './relationship-severance-event'; export * from './report'; @@ -80,6 +82,7 @@ export * from './subscription-invoice'; export * from './subscription-option'; export * from './suggestion'; export * from './tag'; +export * from './terms-of-service'; export * from './token'; export * from './translation'; export * from './trends-link'; diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index 259e88183..c0ba80b46 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -1,4 +1,3 @@ -/* eslint sort-keys: "error" */ import * as v from 'valibot'; import { accountSchema } from './account'; @@ -159,10 +158,14 @@ const configurationSchema = coerceObject({ }), urls: coerceObject({ streaming: v.fallback(v.optional(v.pipe(v.string(), v.url())), undefined), + about: v.fallback(v.nullable(v.string()), null), + privacy_policy: v.fallback(v.nullable(v.string()), null), + terms_of_service: v.fallback(v.nullable(v.string()), null), }), vapid: coerceObject({ public_key: v.fallback(v.string(), ''), }), + limited_federation: v.optional(v.boolean(), undefined), }); const contactSchema = coerceObject({ @@ -253,6 +256,8 @@ const registrations = coerceObject({ approval_required: v.fallback(v.boolean(), false), enabled: v.fallback(v.boolean(), false), message: v.fallback(v.optional(v.string()), undefined), + min_age: v.fallback(v.nullable(v.number()), null), + reason_required: v.fallback(v.nullable(v.boolean()), null), }); const statsSchema = coerceObject({ diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 388f2f424..e1003f773 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -47,7 +47,7 @@ const reportNotificationSchema = v.object({ const severedRelationshipNotificationSchema = v.object({ ...baseNotificationSchema.entries, type: v.literal('severed_relationships'), - relationship_severance_event: relationshipSeveranceEventSchema, + event: relationshipSeveranceEventSchema, }); const moderationWarningNotificationSchema = v.object({ diff --git a/packages/pl-api/lib/entities/rule.ts b/packages/pl-api/lib/entities/rule.ts index 1bc5e6b40..aea0fd4f0 100644 --- a/packages/pl-api/lib/entities/rule.ts +++ b/packages/pl-api/lib/entities/rule.ts @@ -4,6 +4,10 @@ const baseRuleSchema = v.object({ id: v.string(), text: v.fallback(v.string(), ''), hint: v.fallback(v.string(), ''), + translations: v.optional(v.record(v.string(), v.object({ + text: v.fallback(v.string(), ''), + hint: v.fallback(v.string(), ''), + })), undefined), }); /** diff --git a/packages/pl-api/lib/entities/web-push-subscription.ts b/packages/pl-api/lib/entities/web-push-subscription.ts index 20455c29a..eea73660b 100644 --- a/packages/pl-api/lib/entities/web-push-subscription.ts +++ b/packages/pl-api/lib/entities/web-push-subscription.ts @@ -7,6 +7,7 @@ import * as v from 'valibot'; const webPushSubscriptionSchema = v.object({ id: v.pipe(v.unknown(), v.transform(String)), endpoint: v.string(), + standard: v.fallback(v.boolean(), false), alerts: v.record(v.string(), v.boolean()), server_key: v.string(), }); diff --git a/packages/pl-api/lib/features.ts b/packages/pl-api/lib/features.ts index a4cb60791..5d885fbae 100644 --- a/packages/pl-api/lib/features.ts +++ b/packages/pl-api/lib/features.ts @@ -585,6 +585,11 @@ const getFeatures = (instance: Instance) => { */ deleteAccountWithoutPassword: v.software === MITRA && gte(v.version, '2.14.0'), + /** + * @see DELETE /api/v1/media/:id + */ + deleteMedia: instance.api_versions.mastodon >= 4, + /** * Allow to register on a given domain * @see GET /api/v1/pleroma/admin/domains diff --git a/packages/pl-api/lib/params/settings.ts b/packages/pl-api/lib/params/settings.ts index 437f1f0ea..104bdea12 100644 --- a/packages/pl-api/lib/params/settings.ts +++ b/packages/pl-api/lib/params/settings.ts @@ -17,6 +17,8 @@ type CreateAccountParams = { locale: string; /** String. If registrations require manual approval, this text will be reviewed by moderators. */ reason?: string; + /** String ([Date](https://docs.joinmastodon.org/api/datetime-format/#date)), required if the server has a minimum age requirement */ + date_of_birth?: string; fullname?: string; bio?: string; @@ -28,7 +30,6 @@ type CreateAccountParams = { captcha_answer_data?: string; /** invite token required when the registrations aren't public. */ token?: string; - birthday?: string; /** optional, domain id, if multitenancy is enabled. */ domain?: string; diff --git a/packages/pl-fe/src/features/auth-login/components/registration-form.tsx b/packages/pl-fe/src/features/auth-login/components/registration-form.tsx index fc65c71c1..b870134d9 100644 --- a/packages/pl-fe/src/features/auth-login/components/registration-form.tsx +++ b/packages/pl-fe/src/features/auth-login/components/registration-form.tsx @@ -58,7 +58,7 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { const needsConfirmation = instance.pleroma.metadata.account_activation_required; const needsApproval = instance.registrations.approval_required; const supportsAccountLookup = features.accountLookup; - const birthdayRequired = instance.pleroma.metadata.birthday_required; + const birthdayRequired = instance.pleroma.metadata.birthday_required || instance.registrations.min_age; const domains = instance.pleroma.metadata.multitenancy.enabled ? instance.pleroma.metadata.multitenancy.domains!.filter((domain) => domain.public) : undefined; const [captchaLoading, setCaptchaLoading] = useState(true); @@ -319,7 +319,7 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { {birthdayRequired && (