pl-api: moar blind search and replace

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-14 22:50:34 +02:00
parent ea3addf388
commit 0bed543c20
32 changed files with 118 additions and 101 deletions

View file

@ -201,7 +201,7 @@ class PlApiClient {
baseURL: string; baseURL: string;
#accessToken?: string; #accessToken?: string;
#instance: Instance = instanceSchema.parse({}); #instance: Instance = v.parse(instanceSchema, {});
public request = request.bind(this) as typeof request; public request = request.bind(this) as typeof request;
public features: Features = getFeatures(this.#instance); public features: Features = getFeatures(this.#instance);
#socket?: { #socket?: {
@ -2714,9 +2714,9 @@ class PlApiClient {
return v.parse(v.array(v.object({ return v.parse(v.array(v.object({
week: v.string(), week: v.string(),
statuses: z.coerce.string(), statuses: v.pipe(v.unknown(), v.transform(String)),
logins: z.coerce.string(), logins: v.pipe(v.unknown(), v.transform(String)),
registrations: z.coerce.string(), registrations: v.pipe(v.unknown(), v.transform(String)),
})), response.json); })), response.json);
}, },

View file

@ -7,7 +7,7 @@ import { roleSchema } from './role';
import { coerceObject, dateSchema, filteredArray } from './utils'; import { coerceObject, dateSchema, filteredArray } from './utils';
const filterBadges = (tags?: string[]) => const filterBadges = (tags?: string[]) =>
tags?.filter(tag => tag.startsWith('badge:')).map(tag => roleSchema.parse({ id: tag, name: tag.replace(/^badge:/, '') })); tags?.filter(tag => tag.startsWith('badge:')).map(tag => v.parse(roleSchema, { id: tag, name: tag.replace(/^badge:/, '') }));
const preprocessAccount = (account: any) => { const preprocessAccount = (account: any) => {
if (!account?.acct) return null; if (!account?.acct) return null;
@ -112,7 +112,7 @@ const baseAccountSchema = v.object({
deactivated: v.fallback(v.optional(v.boolean()), undefined), deactivated: v.fallback(v.optional(v.boolean()), undefined),
location: v.fallback(v.optional(v.string()), undefined), location: v.fallback(v.optional(v.string()), undefined),
local: z.boolean().optional().catch(false), local: v.fallback(v.optional(v.boolean()), false),
avatar_description: v.fallback(v.string(), ''), avatar_description: v.fallback(v.string(), ''),
enable_rss: v.fallback(v.boolean(), false), enable_rss: v.fallback(v.boolean(), false),
@ -142,7 +142,7 @@ type Account = v.InferOutput<typeof accountWithMovedAccountSchema> & WithMoved;
const accountSchema: z.ZodType<Account> = untypedAccountSchema as any; const accountSchema: z.ZodType<Account> = untypedAccountSchema as any;
const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({
source: v.object({ source: v.fallback(v.nullable(v.object({
note: v.fallback(v.string(), ''), note: v.fallback(v.string(), ''),
fields: filteredArray(fieldSchema), fields: filteredArray(fieldSchema),
privacy: v.picklist(['public', 'unlisted', 'private', 'direct']), privacy: v.picklist(['public', 'unlisted', 'private', 'direct']),
@ -150,15 +150,15 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi
language: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null),
follow_requests_count: z.number().int().nonnegative().catch(0), follow_requests_count: z.number().int().nonnegative().catch(0),
show_role: z.boolean().optional().nullable().catch(undefined), show_role: v.fallback(v.nullable(v.optional(v.boolean())), undefined),
no_rich_text: z.boolean().optional().nullable().catch(undefined), no_rich_text: v.fallback(v.nullable(v.optional(v.boolean())), undefined),
discoverable: v.fallback(v.optional(v.boolean()), undefined), discoverable: v.fallback(v.optional(v.boolean()), undefined),
actor_type: v.fallback(v.optional(v.string()), undefined), actor_type: v.fallback(v.optional(v.string()), undefined),
show_birthday: v.fallback(v.optional(v.boolean()), undefined), show_birthday: v.fallback(v.optional(v.boolean()), undefined),
}).nullable().catch(null), })), null),
role: v.fallback(v.nullable(roleSchema), null), role: v.fallback(v.nullable(roleSchema), null),
settings_store: v.record(v.string(), v.any()).optional().catch(undefined), settings_store: v.fallback(v.optional(v.record(v.string(), v.any())), undefined),
chat_token: v.fallback(v.optional(v.string()), undefined), chat_token: v.fallback(v.optional(v.string()), undefined),
allow_following_move: v.fallback(v.optional(v.boolean()), undefined), allow_following_move: v.fallback(v.optional(v.boolean()), undefined),
unread_conversation_count: v.fallback(v.optional(v.number()), undefined), unread_conversation_count: v.fallback(v.optional(v.number()), undefined),

View file

@ -22,9 +22,9 @@ const adminAccountSchema = z.preprocess((account: any) => {
email: account.email, email: account.email,
invite_request: account.registration_reason, invite_request: account.registration_reason,
role: account.roles?.is_admin role: account.roles?.is_admin
? roleSchema.parse({ name: 'Admin' }) ? v.parse(roleSchema, { name: 'Admin' })
: account.roles?.moderator : account.roles?.moderator
? roleSchema.parse({ name: 'Moderator ' }) : ? v.parse(roleSchema, { name: 'Moderator ' }) :
null, null,
confirmed: account.is_confirmed, confirmed: account.is_confirmed,
approved: account.is_approved, approved: account.is_approved,

View file

@ -4,7 +4,7 @@ import * as v from 'valibot';
const adminCohortSchema = v.object({ const adminCohortSchema = v.object({
period: z.string().datetime({ offset: true }), period: z.string().datetime({ offset: true }),
frequency: v.picklist(['day', 'month']), frequency: v.picklist(['day', 'month']),
data: z.array(v.object({ data: v.array(v.object({
date: z.string().datetime({ offset: true }), date: z.string().datetime({ offset: true }),
rate: v.number(), rate: v.number(),
value: v.pipe(v.number(), v.integer()), value: v.pipe(v.number(), v.integer()),

View file

@ -2,7 +2,7 @@ import * as v from 'valibot';
const adminDomainSchema = v.object({ const adminDomainSchema = v.object({
domain: v.fallback(v.string(), ''), domain: v.fallback(v.string(), ''),
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
public: v.fallback(v.boolean(), false), public: v.fallback(v.boolean(), false),
resolves: v.fallback(v.boolean(), false), resolves: v.fallback(v.boolean(), false),
last_checked_at: z.string().datetime().catch(''), last_checked_at: z.string().datetime().catch(''),

View file

@ -8,9 +8,9 @@ const adminEmailDomainBlockSchema = v.object({
domain: v.string(), domain: v.string(),
created_at: dateSchema, created_at: dateSchema,
history: v.array(v.object({ history: v.array(v.object({
day: z.coerce.string(), day: v.pipe(v.unknown(), v.transform(String)),
accounts: z.coerce.string(), accounts: v.pipe(v.unknown(), v.transform(String)),
uses: z.coerce.string(), uses: v.pipe(v.unknown(), v.transform(String)),
})), })),
}); });

View file

@ -4,12 +4,12 @@ import * as v from 'valibot';
const adminMeasureSchema = v.object({ const adminMeasureSchema = v.object({
key: v.string(), key: v.string(),
unit: v.fallback(v.nullable(v.string()), null), unit: v.fallback(v.nullable(v.string()), null),
total: z.coerce.number(), total: v.pipe(v.unknown(), v.transform(Number)),
human_value: v.fallback(v.optional(v.string()), undefined), human_value: v.fallback(v.optional(v.string()), undefined),
previous_total: z.coerce.string().optional().catch(undefined), previous_total: v.fallback(v.optional(v.pipe(v.unknown(), v.transform(String))), undefined),
data: z.array(v.object({ data: v.array(v.object({
date: z.string().datetime({ offset: true }), date: z.string().datetime({ offset: true }),
value: z.coerce.string(), value: v.pipe(v.unknown(), v.transform(String)),
})), })),
}); });

View file

@ -2,7 +2,7 @@ import * as v from 'valibot';
/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */ /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */
const adminModerationLogEntrySchema = v.object({ const adminModerationLogEntrySchema = v.object({
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
data: v.fallback(v.record(v.string(), v.any()), {}), data: v.fallback(v.record(v.string(), v.any()), {}),
time: v.fallback(v.number(), 0), time: v.fallback(v.number(), 0),
message: v.fallback(v.string(), ''), message: v.fallback(v.string(), ''),

View file

@ -4,7 +4,7 @@ import { dateSchema, mimeSchema } from './utils';
/** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */ /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */
const backupSchema = v.object({ const backupSchema = v.object({
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
contentType: mimeSchema, contentType: mimeSchema,
file_size: v.fallback(v.number(), 0), file_size: v.fallback(v.number(), 0),
inserted_at: dateSchema, inserted_at: dateSchema,

View file

@ -1,7 +1,7 @@
import * as v from 'valibot'; import * as v from 'valibot';
const bookmarkFolderSchema = v.object({ const bookmarkFolderSchema = v.object({
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
name: v.fallback(v.string(), ''), name: v.fallback(v.string(), ''),
emoji: v.fallback(v.nullable(v.string()), null), emoji: v.fallback(v.nullable(v.string()), null),
emoji_url: v.fallback(v.nullable(v.string()), null), emoji_url: v.fallback(v.nullable(v.string()), null),

View file

@ -4,8 +4,8 @@ import { statusSchema } from './status';
/** @see {@link https://docs.joinmastodon.org/entities/Context/} */ /** @see {@link https://docs.joinmastodon.org/entities/Context/} */
const contextSchema = v.object({ const contextSchema = v.object({
ancestors: z.array(statusSchema), ancestors: v.array(statusSchema),
descendants: z.array(statusSchema), descendants: v.array(statusSchema),
}); });
type Context = v.InferOutput<typeof contextSchema>; type Context = v.InferOutput<typeof contextSchema>;

View file

@ -2,7 +2,7 @@ import * as v from 'valibot';
const directoryCategorySchema = v.object({ const directoryCategorySchema = v.object({
category: v.string(), category: v.string(),
servers_count: v.fallback(v.nullable(z.coerce.number()), null), servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
}); });
type DirectoryCategory = v.InferOutput<typeof directoryCategorySchema>; type DirectoryCategory = v.InferOutput<typeof directoryCategorySchema>;

View file

@ -3,7 +3,7 @@ import * as v from 'valibot';
const directoryLanguageSchema = v.object({ const directoryLanguageSchema = v.object({
locale: v.string(), locale: v.string(),
language: v.string(), language: v.string(),
servers_count: v.fallback(v.nullable(z.coerce.number()), null), servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
}); });
type DirectoryLanguage = v.InferOutput<typeof directoryLanguageSchema>; type DirectoryLanguage = v.InferOutput<typeof directoryLanguageSchema>;

View file

@ -4,13 +4,13 @@ const directoryServerSchema = v.object({
domain: v.string(), domain: v.string(),
version: v.string(), version: v.string(),
description: v.string(), description: v.string(),
languages: z.array(v.string()), languages: v.array(v.string()),
region: v.string(), region: v.string(),
categories: z.array(v.string()), categories: v.array(v.string()),
proxied_thumbnail: v.fallback(v.nullable(z.string().url()), null), proxied_thumbnail: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null),
blurhash: v.fallback(v.nullable(v.string()), null), blurhash: v.fallback(v.nullable(v.string()), null),
total_users: z.coerce.number(), total_users: v.pipe(v.unknown(), v.transform(Number)),
last_week_users: z.coerce.number(), last_week_users: v.pipe(v.unknown(), v.transform(Number)),
approval_required: v.boolean(), approval_required: v.boolean(),
language: v.string(), language: v.string(),
category: v.string(), category: v.string(),

View file

@ -2,9 +2,9 @@ import * as v from 'valibot';
const directoryStatisticsPeriodSchema = v.object({ const directoryStatisticsPeriodSchema = v.object({
period: z.string().date(), period: z.string().date(),
server_count: v.fallback(v.nullable(z.coerce.number()), null), server_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
user_count: v.fallback(v.nullable(z.coerce.number()), null), user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
active_user_count: v.fallback(v.nullable(z.coerce.number()), null), active_user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
}); });
type DirectoryStatisticsPeriod = v.InferOutput<typeof directoryStatisticsPeriodSchema>; type DirectoryStatisticsPeriod = v.InferOutput<typeof directoryStatisticsPeriodSchema>;

View file

@ -10,10 +10,11 @@ const baseEmojiReactionSchema = v.object({
url: v.fallback(v.undefined(), undefined), url: v.fallback(v.undefined(), undefined),
static_url: v.fallback(v.undefined(), undefined), static_url: v.fallback(v.undefined(), undefined),
accounts: filteredArray(accountSchema), accounts: filteredArray(accountSchema),
account_ids: filteredArray(v.string()).catch([]), account_ids: v.fallback(filteredArray(v.string()), []),
}); });
const customEmojiReactionSchema = baseEmojiReactionSchema.extend({ const customEmojiReactionSchema = v.object({
...baseEmojiReactionSchema.entries,
name: v.string(), name: v.string(),
url: v.pipe(v.string(), v.url()), url: v.pipe(v.string(), v.url()),
static_url: v.pipe(v.string(), v.url()), static_url: v.pipe(v.string(), v.url()),
@ -27,7 +28,7 @@ const emojiReactionSchema = z.preprocess((reaction: any) => reaction ? {
static_url: reaction.url, static_url: reaction.url,
account_ids: reaction.accounts?.map((account: any) => account?.id), account_ids: reaction.accounts?.map((account: any) => account?.id),
...reaction, ...reaction,
} : null, baseEmojiReactionSchema.or(customEmojiReactionSchema)); } : null, v.union([baseEmojiReactionSchema, customEmojiReactionSchema]);
type EmojiReaction = v.InferOutput<typeof emojiReactionSchema>; type EmojiReaction = v.InferOutput<typeof emojiReactionSchema>;

View file

@ -13,11 +13,11 @@ const groupSchema = v.object({
emojis: filteredArray(customEmojiSchema), emojis: filteredArray(customEmojiSchema),
header: v.fallback(v.string(), ''), header: v.fallback(v.string(), ''),
header_static: v.fallback(v.string(), ''), header_static: v.fallback(v.string(), ''),
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
locked: v.fallback(v.boolean(), false), locked: v.fallback(v.boolean(), false),
membership_required: v.fallback(v.boolean(), false), membership_required: v.fallback(v.boolean(), false),
members_count: v.fallback(v.number(), 0), members_count: v.fallback(v.number(), 0),
owner: v.object({ id: v.string() }).nullable().catch(null), owner: v.fallback(v.nullable(v.object({ id: v.string() })), null),
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''), note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later
statuses_visibility: v.fallback(v.string(), 'public'), statuses_visibility: v.fallback(v.string(), 'public'),

View file

@ -36,7 +36,7 @@ const instanceV1ToV2 = (data: any) => {
uri, uri,
urls, urls,
...instance ...instance
} = instanceV1Schema.parse(data); } = v.parse(instanceV1Schema, data);
return { return {
...instance, ...instance,
@ -196,14 +196,14 @@ const pleromaSchema = coerceObject({
multitenancy: coerceObject({ multitenancy: coerceObject({
domains: v.optional(v.array( domains: v.optional(v.array(
v.object({ v.object({
domain: z.coerce.string(), domain: v.pipe(v.unknown(), v.transform(String)),
id: v.string(), id: v.string(),
public: v.fallback(v.boolean(), false), public: v.fallback(v.boolean(), false),
}), }),
)), )),
enabled: v.fallback(v.boolean(), false), enabled: v.fallback(v.boolean(), false),
}), }),
post_formats: z.string().array().optional().catch(undefined), post_formats: v.fallback(v.optional(v.array(v.string())), undefined),
restrict_unauthenticated: coerceObject({ restrict_unauthenticated: coerceObject({
activities: coerceObject({ activities: coerceObject({
local: v.fallback(v.boolean(), false), local: v.fallback(v.boolean(), false),
@ -222,8 +222,8 @@ const pleromaSchema = coerceObject({
translation: coerceObject({ translation: coerceObject({
allow_remote: v.fallback(v.boolean(), true), allow_remote: v.fallback(v.boolean(), true),
allow_unauthenticated: v.fallback(v.boolean(), false), allow_unauthenticated: v.fallback(v.boolean(), false),
source_languages: z.string().array().optional().catch(undefined), source_languages: v.fallback(v.optional(v.array(v.string())), undefined),
target_languages: z.string().array().optional().catch(undefined), target_languages: v.fallback(v.optional(v.array(v.string())), undefined),
}), }),
}), }),
oauth_consumer_strategies: v.fallback(v.array(v.string()), []), oauth_consumer_strategies: v.fallback(v.array(v.string()), []),

View file

@ -2,7 +2,7 @@ import * as v from 'valibot';
/** @see {@link https://docs.joinmastodon.org/entities/List/} */ /** @see {@link https://docs.joinmastodon.org/entities/List/} */
const listSchema = v.object({ const listSchema = v.object({
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
title: v.string(), title: v.string(),
replies_policy: v.fallback(v.optional(v.string()), undefined), replies_policy: v.fallback(v.optional(v.string()), undefined),
exclusive: v.fallback(v.optional(v.boolean()), undefined), exclusive: v.fallback(v.optional(v.boolean()), undefined),

View file

@ -9,7 +9,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({
last_read_id: v.string(), last_read_id: v.string(),
version: v.pipe(v.number(), v.integer()), version: v.pipe(v.number(), v.integer()),
updated_at: dateSchema, updated_at: dateSchema,
unread_count: z.number().int().optional().catch(undefined), unread_count: v.fallback(v.optional(v.pipe(v.number(), v.integer())), undefined),
})); }));
/** @see {@link https://docs.joinmastodon.org/entities/Marker/} */ /** @see {@link https://docs.joinmastodon.org/entities/Marker/} */

View file

@ -19,7 +19,7 @@ const baseAttachmentSchema = v.object({
type: v.string(), type: v.string(),
url: v.fallback(v.pipe(v.string(), v.url()), ''), url: v.fallback(v.pipe(v.string(), v.url()), ''),
preview_url: v.fallback(v.pipe(v.string(), v.url()), ''), preview_url: v.fallback(v.pipe(v.string(), v.url()), ''),
remote_url: v.fallback(v.nullable(z.string().url()), null), remote_url: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null),
description: v.fallback(v.string(), ''), description: v.fallback(v.string(), ''),
blurhash: v.fallback(v.nullable(blurhashSchema), null), blurhash: v.fallback(v.nullable(blurhashSchema), null),
@ -29,11 +29,12 @@ const baseAttachmentSchema = v.object({
const imageMetaSchema = v.object({ const imageMetaSchema = v.object({
width: v.number(), width: v.number(),
height: v.number(), height: v.number(),
size: z.string().regex(/\d+x\d+$/).nullable().catch(null), size: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+x\d+$/))), null),
aspect: v.fallback(v.nullable(v.number()), null), aspect: v.fallback(v.nullable(v.number()), null),
}); });
const imageAttachmentSchema = baseAttachmentSchema.extend({ const imageAttachmentSchema = v.object({
...baseAttachmentSchema.entries,
type: v.literal('image'), type: v.literal('image'),
meta: v.fallback(v.object({ meta: v.fallback(v.object({
original: v.fallback(v.optional(imageMetaSchema), undefined), original: v.fallback(v.optional(imageMetaSchema), undefined),
@ -45,7 +46,8 @@ const imageAttachmentSchema = baseAttachmentSchema.extend({
}), {}), }), {}),
}); });
const videoAttachmentSchema = baseAttachmentSchema.extend({ const videoAttachmentSchema = v.object({
...baseAttachmentSchema.entries,
type: v.literal('video'), type: v.literal('video'),
meta: v.fallback(v.object({ meta: v.fallback(v.object({
duration: v.fallback(v.optional(v.number()), undefined), duration: v.fallback(v.optional(v.number()), undefined),
@ -58,7 +60,8 @@ const videoAttachmentSchema = baseAttachmentSchema.extend({
}), {}), }), {}),
}); });
const gifvAttachmentSchema = baseAttachmentSchema.extend({ const gifvAttachmentSchema = v.object({
...baseAttachmentSchema.entries,
type: v.literal('gifv'), type: v.literal('gifv'),
meta: v.fallback(v.object({ meta: v.fallback(v.object({
duration: v.fallback(v.optional(v.number()), undefined), duration: v.fallback(v.optional(v.number()), undefined),
@ -66,7 +69,8 @@ const gifvAttachmentSchema = baseAttachmentSchema.extend({
}), {}), }), {}),
}); });
const audioAttachmentSchema = baseAttachmentSchema.extend({ const audioAttachmentSchema = v.object({
...baseAttachmentSchema.entries,
type: v.literal('audio'), type: v.literal('audio'),
meta: v.fallback(v.object({ meta: v.fallback(v.object({
duration: v.fallback(v.optional(v.number()), undefined), duration: v.fallback(v.optional(v.number()), undefined),
@ -83,7 +87,8 @@ const audioAttachmentSchema = baseAttachmentSchema.extend({
}), {}), }), {}),
}); });
const unknownAttachmentSchema = baseAttachmentSchema.extend({ const unknownAttachmentSchema = v.object({
...baseAttachmentSchema.entries,
type: v.literal('unknown'), type: v.literal('unknown'),
}); });
@ -96,7 +101,7 @@ const mediaAttachmentSchema = z.preprocess((data: any) => {
preview_url: data.url, preview_url: data.url,
...data, ...data,
}; };
}, z.discriminatedUnion('type', [ }, v.variant('type', [
imageAttachmentSchema, imageAttachmentSchema,
videoAttachmentSchema, videoAttachmentSchema,
gifvAttachmentSchema, gifvAttachmentSchema,

View file

@ -10,7 +10,7 @@ const notificationRequestSchema = v.object({
created_at: dateSchema, created_at: dateSchema,
updated_at: dateSchema, updated_at: dateSchema,
account: accountSchema, account: accountSchema,
notifications_count: z.coerce.string(), notifications_count: v.pipe(v.unknown(), v.transform(String)),
last_status: v.fallback(v.optional(statusSchema), undefined), last_status: v.fallback(v.optional(statusSchema), undefined),
}); });

View file

@ -17,10 +17,10 @@ const pollSchema = v.object({
expires_at: v.fallback(v.nullable(z.string().datetime()), null), expires_at: v.fallback(v.nullable(z.string().datetime()), null),
id: v.string(), id: v.string(),
multiple: v.fallback(v.boolean(), false), multiple: v.fallback(v.boolean(), false),
options: z.array(pollOptionSchema).min(2), options: v.array(pollOptionSchema).min(2),
voters_count: v.fallback(v.number(), 0), voters_count: v.fallback(v.number(), 0),
votes_count: v.fallback(v.number(), 0), votes_count: v.fallback(v.number(), 0),
own_votes: v.fallback(v.nullable(z.number()).nonempty(), null), own_votes: v.fallback(v.nullable(v.array(v.number())).nonempty(), null),
voted: v.fallback(v.boolean(), false), voted: v.fallback(v.boolean(), false),
non_anonymous: v.fallback(v.boolean(), false), non_anonymous: v.fallback(v.boolean(), false),

View file

@ -9,19 +9,19 @@ const scheduledStatusSchema = v.object({
scheduled_at: z.string().datetime({ offset: true }), scheduled_at: z.string().datetime({ offset: true }),
params: v.object({ params: v.object({
text: v.fallback(v.nullable(v.string()), null), text: v.fallback(v.nullable(v.string()), null),
poll: v.object({ poll: v.fallback(v.nullable(v.object({
options: z.array(v.string()), options: v.array(v.string()),
expires_in: z.coerce.string(), expires_in: v.pipe(v.unknown(), v.transform(String)),
multiple: v.fallback(v.optional(v.boolean()), undefined), multiple: v.fallback(v.optional(v.boolean()), undefined),
hide_totals: v.fallback(v.optional(v.boolean()), undefined), hide_totals: v.fallback(v.optional(v.boolean()), undefined),
}).nullable().catch(null), })), null),
media_ids: v.fallback(v.nullable(v.string()), null), media_ids: v.fallback(v.nullable(v.string()), null),
sensitive: v.fallback(v.nullable(z.coerce.boolean()), null), sensitive: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Boolean))), null),
spoiler_text: v.fallback(v.nullable(v.string()), null), spoiler_text: v.fallback(v.nullable(v.string()), null),
visibility: z.string().catch('public'), visibility: v.fallback(v.string(), 'public'),
in_reply_to_id: v.fallback(v.nullable(v.string()), null), in_reply_to_id: v.fallback(v.nullable(v.string()), null),
language: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null),
application_id: v.fallback(v.nullable(z.number().int()), null), application_id: v.fallback(v.nullable(v.pipe(v.number(), v.integer())), null),
scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null),
idempotency: v.fallback(v.nullable(v.string()), null), idempotency: v.fallback(v.nullable(v.string()), null),
with_rate_limit: v.fallback(v.boolean(), false), with_rate_limit: v.fallback(v.boolean(), false),

View file

@ -6,7 +6,7 @@ const scrobbleSchema = z.preprocess((scrobble: any) => scrobble ? {
external_link: scrobble.externalLink, external_link: scrobble.externalLink,
...scrobble, ...scrobble,
} : null, v.object({ } : null, v.object({
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
account: accountSchema, account: accountSchema,
created_at: z.string().datetime({ offset: true }), created_at: z.string().datetime({ offset: true }),
title: v.string(), title: v.string(),

View file

@ -9,14 +9,14 @@ import { dateSchema, filteredArray } from './utils';
const statusEditSchema = v.object({ const statusEditSchema = v.object({
content: v.fallback(v.string(), ''), content: v.fallback(v.string(), ''),
spoiler_text: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''),
sensitive: z.coerce.boolean(), sensitive: v.pipe(v.unknown(), v.transform(Boolean)),
created_at: dateSchema, created_at: dateSchema,
account: accountSchema, account: accountSchema,
poll: v.object({ poll: v.fallback(v.nullable(v.object({
options: z.array(v.object({ options: v.array(v.object({
title: v.string(), title: v.string(),
})), })),
}).nullable().catch(null), })), null),
media_attachments: filteredArray(mediaAttachmentSchema), media_attachments: filteredArray(mediaAttachmentSchema),
emojis: filteredArray(customEmojiSchema), emojis: filteredArray(customEmojiSchema),
}); });

View file

@ -42,13 +42,13 @@ const baseStatusSchema = v.object({
created_at: dateSchema, created_at: dateSchema,
account: accountSchema, account: accountSchema,
content: v.fallback(v.string(), ''), content: v.fallback(v.string(), ''),
visibility: z.string().catch('public'), visibility: v.fallback(v.string(), 'public'),
sensitive: z.coerce.boolean(), sensitive: v.pipe(v.unknown(), v.transform(Boolean)),
spoiler_text: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''),
media_attachments: filteredArray(mediaAttachmentSchema), media_attachments: filteredArray(mediaAttachmentSchema),
application: v.fallback(v.nullable(v.object({ application: v.fallback(v.nullable(v.object({
name: v.string(), name: v.string(),
website: v.fallback(v.nullable(z.string().url()), null), website: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null),
})), null), })), null),
mentions: filteredArray(mentionSchema), mentions: filteredArray(mentionSchema),
tags: filteredArray(tagSchema), tags: filteredArray(tagSchema),
@ -64,11 +64,11 @@ const baseStatusSchema = v.object({
language: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null),
text: v.fallback(v.nullable(v.string()), null), text: v.fallback(v.nullable(v.string()), null),
edited_at: v.fallback(v.nullable(z.string().datetime()), null), edited_at: v.fallback(v.nullable(z.string().datetime()), null),
favourited: z.coerce.boolean(), favourited: v.pipe(v.unknown(), v.transform(Boolean)),
reblogged: z.coerce.boolean(), reblogged: v.pipe(v.unknown(), v.transform(Boolean)),
muted: z.coerce.boolean(), muted: v.pipe(v.unknown(), v.transform(Boolean)),
bookmarked: z.coerce.boolean(), bookmarked: v.pipe(v.unknown(), v.transform(Boolean)),
pinned: z.coerce.boolean(), pinned: v.pipe(v.unknown(), v.transform(Boolean)),
filtered: filteredArray(filterResultSchema), filtered: filteredArray(filterResultSchema),
approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null), approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null),
group: v.fallback(v.nullable(groupSchema), null), group: v.fallback(v.nullable(groupSchema), null),
@ -97,7 +97,7 @@ const baseStatusSchema = v.object({
spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null),
dislikes_count: v.fallback(v.number(), 0), dislikes_count: v.fallback(v.number(), 0),
disliked: z.coerce.boolean().catch(false), disliked: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false),
interaction_policy: interactionPolicySchema, interaction_policy: interactionPolicySchema,
}); });

View file

@ -25,54 +25,64 @@ const followRelationshipUpdateSchema = v.object({
type FollowRelationshipUpdate = v.InferOutput<typeof followRelationshipUpdateSchema>; type FollowRelationshipUpdate = v.InferOutput<typeof followRelationshipUpdateSchema>;
const baseStreamingEventSchema = v.object({ const baseStreamingEventSchema = v.object({
stream: z.array(v.string()).catch([]), stream: v.fallback(v.array(v.string()), []),
}); });
const statusStreamingEventSchema = baseStreamingEventSchema.extend({ const statusStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.picklist(['update', 'status.update']), event: v.picklist(['update', 'status.update']),
payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema),
}); });
const stringStreamingEventSchema = baseStreamingEventSchema.extend({ const stringStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.picklist(['delete', 'announcement.delete']), event: v.picklist(['delete', 'announcement.delete']),
payload: v.string(), payload: v.string(),
}); });
const notificationStreamingEventSchema = baseStreamingEventSchema.extend({ const notificationStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('notification'), event: v.literal('notification'),
payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema),
}); });
const emptyStreamingEventSchema = baseStreamingEventSchema.extend({ const emptyStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('filters_changed'), event: v.literal('filters_changed'),
}); });
const conversationStreamingEventSchema = baseStreamingEventSchema.extend({ const conversationStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('conversation'), event: v.literal('conversation'),
payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema),
}); });
const announcementStreamingEventSchema = baseStreamingEventSchema.extend({ const announcementStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('announcement'), event: v.literal('announcement'),
payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema),
}); });
const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({ const announcementReactionStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('announcement.reaction'), event: v.literal('announcement.reaction'),
payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema),
}); });
const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ const chatUpdateStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('chat_update'), event: v.literal('chat_update'),
payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema),
}); });
const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ const followRelationshipsUpdateStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('follow_relationships_update'), event: v.literal('follow_relationships_update'),
payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema),
}); });
const respondStreamingEventSchema = baseStreamingEventSchema.extend({ const respondStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('respond'), event: v.literal('respond'),
payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({ payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({
type: v.string(), type: v.string(),
@ -80,7 +90,8 @@ const respondStreamingEventSchema = baseStreamingEventSchema.extend({
})), })),
}); });
const markerStreamingEventSchema = baseStreamingEventSchema.extend({ const markerStreamingEventSchema = v.object({
...baseStreamingEventSchema.entries,
event: v.literal('marker'), event: v.literal('marker'),
payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema), payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema),
}); });
@ -89,7 +100,7 @@ const markerStreamingEventSchema = baseStreamingEventSchema.extend({
const streamingEventSchema: z.ZodType<StreamingEvent> = z.preprocess((event: any) => ({ const streamingEventSchema: z.ZodType<StreamingEvent> = z.preprocess((event: any) => ({
...event, ...event,
event: event.event?.replace(/^pleroma:/, ''), event: event.event?.replace(/^pleroma:/, ''),
}), z.discriminatedUnion('event', [ }), v.variant('event', [
statusStreamingEventSchema, statusStreamingEventSchema,
stringStreamingEventSchema, stringStreamingEventSchema,
notificationStreamingEventSchema, notificationStreamingEventSchema,

View file

@ -31,7 +31,7 @@ const suggestionSchema = z.preprocess((suggestion: any) => {
return suggestion; return suggestion;
}, v.object({ }, v.object({
source: v.fallback(v.nullable(v.string()), null), source: v.fallback(v.nullable(v.string()), null),
sources: z.array(v.string()).catch([]), sources: v.fallback(v.array(v.string()), []),
account: accountSchema, account: accountSchema,
})); }));

View file

@ -1,9 +1,9 @@
import * as v from 'valibot'; import * as v from 'valibot';
const historySchema = v.object({ const historySchema = v.object({
day: z.coerce.number(), day: v.pipe(v.unknown(), v.transform(Number)),
accounts: z.coerce.number(), accounts: v.pipe(v.unknown(), v.transform(Number)),
uses: z.coerce.number(), uses: v.pipe(v.unknown(), v.transform(Number)),
}); });
/** @see {@link https://docs.joinmastodon.org/entities/tag} */ /** @see {@link https://docs.joinmastodon.org/entities/tag} */

View file

@ -4,7 +4,7 @@ import { filteredArray } from './utils';
const translationPollSchema = v.object({ const translationPollSchema = v.object({
id: v.string(), id: v.string(),
options: z.array(v.object({ options: v.array(v.object({
title: v.string(), title: v.string(),
})), })),
}); });

View file

@ -2,9 +2,9 @@ import * as v from 'valibot';
/** @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/} */ /** @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/} */
const webPushSubscriptionSchema = v.object({ const webPushSubscriptionSchema = v.object({
id: z.coerce.string(), id: v.pipe(v.unknown(), v.transform(String)),
endpoint: v.string(), endpoint: v.string(),
alerts: v.record(v.string(), z.boolean()), alerts: v.record(v.string(), v.boolean()),
server_key: v.string(), server_key: v.string(),
}); });