pl-api: More blind search and replace before actual testing

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2024-10-14 20:54:44 +02:00
parent a6bc160caa
commit ea3addf388
47 changed files with 253 additions and 251 deletions

View File

@ -380,7 +380,7 @@ class PlApiClient {
return v.parse(v.intersect([v.object({
type: v.string(),
}), z.record(v.any())]), response.json);
}), v.record(v.string(), v.any())]), response.json);
},
mfaChallenge: async (params: MfaChallengeParams) => {
@ -2773,7 +2773,7 @@ class PlApiClient {
const response = await this.request('/api/v1/instance/translation_languages');
return v.parse(z.record(v.array(v.string())), response.json);
return v.parse(v.record(v.string(), v.array(v.string())), response.json);
},
/**
@ -2806,7 +2806,7 @@ class PlApiClient {
getFrontendConfigurations: async () => {
const response = await this.request('/api/pleroma/frontend_configurations');
return v.parse(z.record(z.record(v.any())).catch({}), response.json);
return v.parse(v.fallback(v.record(v.string(), v.record(v.string(), v.any())), {}), response.json);
},
};

View File

@ -64,31 +64,31 @@ const preprocessAccount = (account: any) => {
const fieldSchema = v.object({
name: v.string(),
value: v.string(),
verified_at: z.string().datetime({ offset: true }).nullable().catch(null),
verified_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null),
});
const baseAccountSchema = v.object({
id: v.string(),
username: v.fallback(v.string(), ''),
acct: v.fallback(v.string(), ''),
url: z.string().url(),
url: v.pipe(v.string(), v.url()),
display_name: v.fallback(v.string(), ''),
note: v.fallback(v.string(), ''),
avatar: v.fallback(v.string(), ''),
avatar_static: z.string().url().catch(''),
header: z.string().url().catch(''),
header_static: z.string().url().catch(''),
avatar_static: v.fallback(v.pipe(v.string(), v.url()), ''),
header: v.fallback(v.pipe(v.string(), v.url()), ''),
header_static: v.fallback(v.pipe(v.string(), v.url()), ''),
locked: v.fallback(v.boolean(), false),
fields: filteredArray(fieldSchema),
emojis: filteredArray(customEmojiSchema),
bot: v.fallback(v.boolean(), false),
group: v.fallback(v.boolean(), false),
discoverable: v.fallback(v.boolean(), false),
noindex: v.fallback(v.optional(v.nullable()), null),
noindex: v.fallback(v.nullable(v.boolean()), null),
suspended: v.fallback(v.optional(v.boolean()), undefined),
limited: v.fallback(v.optional(v.boolean()), undefined),
created_at: z.string().datetime().catch(new Date().toUTCString()),
last_status_at: z.string().date().nullable().catch(null),
last_status_at: v.fallback(v.nullable(z.string().date()), null),
statuses_count: v.fallback(v.number(), 0),
followers_count: v.fallback(v.number(), 0),
following_count: v.fallback(v.number(), 0),
@ -97,7 +97,7 @@ const baseAccountSchema = v.object({
fqn: v.fallback(v.nullable(v.string()), null),
ap_id: v.fallback(v.nullable(v.string()), null),
background_image: v.fallback(v.nullable(v.string()), null),
relationship: relationshipSchema.optional().catch(undefined),
relationship: v.fallback(v.optional(relationshipSchema), undefined),
is_moderator: v.fallback(v.optional(v.boolean()), undefined),
is_admin: v.fallback(v.optional(v.boolean()), undefined),
is_suggested: v.fallback(v.optional(v.boolean()), undefined),
@ -106,7 +106,7 @@ const baseAccountSchema = v.object({
hide_follows: v.fallback(v.optional(v.boolean()), undefined),
hide_followers_count: v.fallback(v.optional(v.boolean()), undefined),
hide_follows_count: v.fallback(v.optional(v.boolean()), undefined),
accepts_chat_messages: v.fallback(v.optional(v.nullable()), null),
accepts_chat_messages: v.fallback(v.nullable(v.boolean()), null),
favicon: v.fallback(v.optional(v.string()), undefined),
birthday: z.string().date().optional().catch(undefined),
deactivated: v.fallback(v.optional(v.boolean()), undefined),
@ -121,13 +121,13 @@ const baseAccountSchema = v.object({
verified: v.fallback(v.optional(v.boolean()), undefined),
__meta: coerceObject({
pleroma: z.any().optional().catch(undefined),
source: z.any().optional().catch(undefined),
pleroma: v.fallback(v.any(), undefined),
source: v.fallback(v.any(), undefined),
}),
});
const accountWithMovedAccountSchema = baseAccountSchema.extend({
moved: z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any).nullable().catch(null),
moved: v.fallback(v.nullable(z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any)), null),
});
/** @see {@link https://docs.joinmastodon.org/entities/Account/} */
@ -145,7 +145,7 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi
source: v.object({
note: v.fallback(v.string(), ''),
fields: filteredArray(fieldSchema),
privacy: z.enum(['public', 'unlisted', 'private', 'direct']),
privacy: v.picklist(['public', 'unlisted', 'private', 'direct']),
sensitive: v.fallback(v.boolean(), false),
language: v.fallback(v.nullable(v.string()), null),
follow_requests_count: z.number().int().nonnegative().catch(0),
@ -158,15 +158,15 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi
}).nullable().catch(null),
role: v.fallback(v.nullable(roleSchema), null),
settings_store: z.record(z.any()).optional().catch(undefined),
settings_store: v.record(v.string(), v.any()).optional().catch(undefined),
chat_token: v.fallback(v.optional(v.string()), undefined),
allow_following_move: v.fallback(v.optional(v.boolean()), undefined),
unread_conversation_count: z.number().optional().catch(undefined),
unread_notifications_count: z.number().optional().catch(undefined),
notification_settings: v.object({
unread_conversation_count: v.fallback(v.optional(v.number()), undefined),
unread_notifications_count: v.fallback(v.optional(v.number()), undefined),
notification_settings: v.fallback(v.optional(v.object({
block_from_strangers: v.fallback(v.boolean(), false),
hide_notification_contents: v.fallback(v.boolean(), false),
}).optional().catch(undefined),
})), undefined),
}));
type CredentialAccount = v.InferOutput<typeof untypedCredentialAccountSchema> & WithMoved;

View File

@ -42,7 +42,7 @@ const adminAccountSchema = z.preprocess((account: any) => {
domain: v.fallback(v.nullable(v.string()), null),
created_at: dateSchema,
email: v.fallback(v.nullable(v.string()), null),
ip: z.string().ip().nullable().catch(null),
ip: v.fallback(v.nullable(z.string().ip()), null),
ips: filteredArray(adminIpSchema),
locale: v.fallback(v.nullable(v.string()), null),
invite_request: v.fallback(v.nullable(v.string()), null),
@ -58,7 +58,7 @@ const adminAccountSchema = z.preprocess((account: any) => {
actor_type: v.fallback(v.nullable(v.string()), null),
display_name: v.fallback(v.nullable(v.string()), null),
suggested: v.fallback(v.optional(v.nullable()), null),
suggested: v.fallback(v.nullable(v.boolean()), null),
}));
type AdminAccount = v.InferOutput<typeof adminAccountSchema>;

View File

@ -3,11 +3,11 @@ import * as v from 'valibot';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/} */
const adminCohortSchema = v.object({
period: z.string().datetime({ offset: true }),
frequency: z.enum(['day', 'month']),
frequency: v.picklist(['day', 'month']),
data: z.array(v.object({
date: z.string().datetime({ offset: true }),
rate: z.number(),
value: z.number().int(),
rate: v.number(),
value: v.pipe(v.number(), v.integer()),
})),
});

View File

@ -8,7 +8,7 @@ const adminDomainBlockSchema = v.object({
domain: v.string(),
digest: v.string(),
created_at: dateSchema,
severity: z.enum(['silence', 'suspend', 'noop']),
severity: v.picklist(['silence', 'suspend', 'noop']),
reject_media: v.boolean(),
reject_reports: v.boolean(),
private_comment: v.fallback(v.nullable(v.string()), null),

View File

@ -6,7 +6,7 @@ import { dateSchema } from '../utils';
const adminIpBlockSchema = v.object({
id: v.string(),
ip: z.string().ip(),
severity: z.enum(['sign_up_requires_approval', 'sign_up_block', 'no_access']),
severity: v.picklist(['sign_up_requires_approval', 'sign_up_block', 'no_access']),
comment: v.fallback(v.string(), ''),
created_at: dateSchema,
expires_at: z.string().datetime({ offset: true }),

View File

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

View File

@ -1,8 +1,8 @@
import * as v from 'valibot';
const pleromaConfigSchema = v.object({
configs: z.array(v.object({
value: z.any(),
configs: v.array(v.object({
value: v.any(),
group: v.string(),
key: v.string(),
})),

View File

@ -31,8 +31,8 @@ const adminReportSchema = z.preprocess((report: any) => {
category: v.fallback(v.optional(v.string()), undefined),
comment: v.fallback(v.optional(v.string()), undefined),
forwarded: v.fallback(v.optional(v.boolean()), undefined),
created_at: dateSchema.optional().catch(undefined),
updated_at: dateSchema.optional().catch(undefined),
created_at: v.fallback(v.optional(dateSchema), undefined),
updated_at: v.fallback(v.optional(dateSchema), undefined),
account: adminAccountSchema,
target_account: adminAccountSchema,
assigned_account: v.fallback(v.nullable(adminAccountSchema), null),

View File

@ -10,8 +10,8 @@ import { dateSchema, filteredArray } from './utils';
const announcementSchema = v.object({
id: v.string(),
content: v.fallback(v.string(), ''),
starts_at: z.string().datetime().nullable().catch(null),
ends_at: z.string().datetime().nullable().catch(null),
starts_at: v.fallback(v.nullable(z.string().datetime()), null),
ends_at: v.fallback(v.nullable(z.string().datetime()), null),
all_day: v.fallback(v.boolean(), false),
read: v.fallback(v.boolean(), false),
published_at: dateSchema,
@ -20,7 +20,7 @@ const announcementSchema = v.object({
(statuses: any) => Array.isArray(statuses)
? Object.fromEntries(statuses.map((status: any) => [status.url, status.account?.acct]) || [])
: statuses,
z.record(v.string(), v.string()),
v.record(v.string(), v.string(), v.string()),
),
mentions: filteredArray(mentionSchema),
tags: filteredArray(tagSchema),

View File

@ -8,7 +8,7 @@ import { dateSchema } from './utils';
const chatSchema = v.object({
id: v.string(),
account: accountSchema,
unread: z.number().int(),
unread: v.pipe(v.number(), v.integer()),
last_message: v.fallback(v.nullable(chatMessageSchema), null),
created_at: dateSchema,
});

View File

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

View File

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

View File

@ -7,7 +7,7 @@ const directoryServerSchema = v.object({
languages: z.array(v.string()),
region: v.string(),
categories: z.array(v.string()),
proxied_thumbnail: z.string().url().nullable().catch(null),
proxied_thumbnail: v.fallback(v.nullable(z.string().url()), null),
blurhash: v.fallback(v.nullable(v.string()), null),
total_users: z.coerce.number(),
last_week_users: z.coerce.number(),

View File

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

View File

@ -4,7 +4,7 @@ import * as v from 'valibot';
const domainBlockSchema = v.object({
domain: v.string(),
digest: v.string(),
severity: z.enum(['silence', 'suspend']),
severity: v.picklist(['silence', 'suspend']),
comment: v.fallback(v.optional(v.string()), undefined),
});

View File

@ -7,16 +7,16 @@ const baseEmojiReactionSchema = v.object({
count: v.fallback(v.nullable(v.number()), null),
me: v.fallback(v.boolean(), false),
name: emojiSchema,
url: z.literal(undefined).catch(undefined),
static_url: z.literal(undefined).catch(undefined),
url: v.fallback(v.undefined(), undefined),
static_url: v.fallback(v.undefined(), undefined),
accounts: filteredArray(accountSchema),
account_ids: filteredArray(v.string()).catch([]),
});
const customEmojiReactionSchema = baseEmojiReactionSchema.extend({
name: v.string(),
url: z.string().url(),
static_url: z.string().url(),
url: v.pipe(v.string(), v.url()),
static_url: v.pipe(v.string(), v.url()),
});
/**

View File

@ -5,8 +5,8 @@ const featuredTagSchema = v.object({
id: v.string(),
name: v.string(),
url: v.fallback(v.optional(v.string()), undefined),
statuses_count: z.number(),
last_status_at: z.number(),
statuses_count: v.number(),
last_status_at: v.number(),
});
type FeaturedTag = v.InferOutput<typeof featuredTagSchema>;

View File

@ -5,8 +5,8 @@ import { filterSchema } from './filter';
/** @see {@link https://docs.joinmastodon.org/entities/FilterResult/} */
const filterResultSchema = v.object({
filter: filterSchema,
keyword_matches: z.array(v.string()).nullable().catch(null),
status_matches: z.array(v.string()).nullable().catch(null),
keyword_matches: v.fallback(v.nullable(v.string()), null),
status_matches: v.fallback(v.nullable(v.string()), null),
});
type FilterResult = v.InferOutput<typeof filterResultSchema>;

View File

@ -33,9 +33,9 @@ const filterSchema = z.preprocess((filter: any) => {
}, v.object({
id: v.string(),
title: v.string(),
context: z.array(z.enum(['home', 'notifications', 'public', 'thread', 'account'])),
expires_at: z.string().datetime({ offset: true }).nullable().catch(null),
filter_action: z.enum(['warn', 'hide']).catch('warn'),
context: v.array(v.picklist(['home', 'notifications', 'public', 'thread', 'account'])),
expires_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null),
filter_action: v.fallback(v.picklist(['warn', 'hide']), 'warn'),
keywords: filteredArray(filterKeywordSchema),
statuses: filteredArray(filterStatusSchema),
}));

View File

@ -17,10 +17,10 @@ const groupSchema = v.object({
locked: v.fallback(v.boolean(), false),
membership_required: v.fallback(v.boolean(), false),
members_count: v.fallback(v.number(), 0),
owner: v.object({ id: z.string() }).nullable().catch(null),
owner: v.object({ id: v.string() }).nullable().catch(null),
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later
statuses_visibility: z.string().catch('public'),
statuses_visibility: v.fallback(v.string(), 'public'),
uri: v.fallback(v.string(), ''),
url: v.fallback(v.string(), ''),

View File

@ -108,47 +108,47 @@ const fixVersion = (version: string) => {
};
const configurationSchema = coerceObject({
accounts: v.object({
accounts: v.fallback(v.nullable(v.object({
allow_custom_css: v.boolean(),
max_featured_tags: z.number().int(),
max_profile_fields: z.number().int(),
}).nullable().catch(null),
max_featured_tags: v.pipe(v.number(), v.integer()),
max_profile_fields: v.pipe(v.number(), v.integer()),
})), null),
chats: coerceObject({
max_characters: z.number().catch(5000),
max_characters: v.fallback(v.number(), 5000),
}),
groups: coerceObject({
max_characters_description: z.number().catch(160),
max_characters_name: z.number().catch(50),
max_characters_description: v.fallback(v.number(), 160),
max_characters_name: v.fallback(v.number(), 50),
}),
media_attachments: coerceObject({
image_matrix_limit: z.number().optional().catch(undefined),
image_size_limit: z.number().optional().catch(undefined),
supported_mime_types: mimeSchema.array().optional().catch(undefined),
video_duration_limit: z.number().optional().catch(undefined),
video_frame_rate_limit: z.number().optional().catch(undefined),
video_matrix_limit: z.number().optional().catch(undefined),
video_size_limit: z.number().optional().catch(undefined),
image_matrix_limit: v.fallback(v.optional(v.number()), undefined),
image_size_limit: v.fallback(v.optional(v.number()), undefined),
supported_mime_types: v.fallback(v.optional(v.array(mimeSchema)), undefined),
video_duration_limit: v.fallback(v.optional(v.number()), undefined),
video_frame_rate_limit: v.fallback(v.optional(v.number()), undefined),
video_matrix_limit: v.fallback(v.optional(v.number()), undefined),
video_size_limit: v.fallback(v.optional(v.number()), undefined),
}),
polls: coerceObject({
max_characters_per_option: z.number().catch(25),
max_expiration: z.number().catch(2629746),
max_options: z.number().catch(4),
min_expiration: z.number().catch(300),
max_characters_per_option: v.fallback(v.number(), 25),
max_expiration: v.fallback(v.number(), 2629746),
max_options: v.fallback(v.number(), 4),
min_expiration: v.fallback(v.number(), 300),
}),
reactions: coerceObject({
max_reactions: v.fallback(v.number(), 0),
}),
statuses: coerceObject({
characters_reserved_per_url: z.number().optional().catch(undefined),
max_characters: z.number().catch(500),
max_media_attachments: z.number().catch(4),
characters_reserved_per_url: v.fallback(v.optional(v.number()), undefined),
max_characters: v.fallback(v.number(), 500),
max_media_attachments: v.fallback(v.number(), 4),
}),
translation: coerceObject({
enabled: v.fallback(v.boolean(), false),
}),
urls: coerceObject({
streaming: z.string().url().optional().catch(undefined),
streaming: v.fallback(v.optional(v.pipe(v.string(), v.url())), undefined),
}),
vapid: coerceObject({
public_key: v.fallback(v.string(), ''),
@ -156,8 +156,8 @@ const configurationSchema = coerceObject({
});
const contactSchema = coerceObject({
contact_account: accountSchema.optional().catch(undefined),
email: z.string().email().catch(''),
contact_account: v.fallback(v.optional(accountSchema), undefined),
email: v.fallback(v.pipe(v.string(), v.email()), ''),
});
const pleromaSchema = coerceObject({
@ -165,11 +165,11 @@ const pleromaSchema = coerceObject({
account_activation_required: v.fallback(v.boolean(), false),
birthday_min_age: v.fallback(v.number(), 0),
birthday_required: v.fallback(v.boolean(), false),
description_limit: z.number().catch(1500),
description_limit: v.fallback(v.number(), 1500),
features: v.fallback(v.array(v.string()), []),
federation: coerceObject({
enabled: v.fallback(v.boolean(), true), // Assume true unless explicitly false
mrf_policies: z.string().array().optional().catch(undefined),
mrf_policies: v.fallback(v.optional(v.array(v.string())), undefined),
mrf_simple: coerceObject({
accept: v.fallback(v.array(v.string()), []),
avatar_removal: v.fallback(v.array(v.string()), []),
@ -192,17 +192,15 @@ const pleromaSchema = coerceObject({
allow_headings: v.fallback(v.boolean(), false),
allow_inline_images: v.fallback(v.boolean(), false),
}),
migration_cooldown_period: z.number().optional().catch(undefined),
migration_cooldown_period: v.fallback(v.optional(v.number()), undefined),
multitenancy: coerceObject({
domains: z
.array(
v.object({
domain: z.coerce.string(),
id: v.string(),
public: v.fallback(v.boolean(), false),
}),
)
.optional(),
domains: v.optional(v.array(
v.object({
domain: z.coerce.string(),
id: v.string(),
public: v.fallback(v.boolean(), false),
}),
)),
enabled: v.fallback(v.boolean(), false),
}),
post_formats: z.string().array().optional().catch(undefined),
@ -230,16 +228,16 @@ const pleromaSchema = coerceObject({
}),
oauth_consumer_strategies: v.fallback(v.array(v.string()), []),
stats: coerceObject({
mau: z.number().optional().catch(undefined),
mau: v.fallback(v.optional(v.number()), undefined),
}),
vapid_public_key: v.fallback(v.string(), ''),
});
const pleromaPollLimitsSchema = coerceObject({
max_expiration: z.number().optional().catch(undefined),
max_option_chars: z.number().optional().catch(undefined),
max_options: z.number().optional().catch(undefined),
min_expiration: z.number().optional().catch(undefined),
max_expiration: v.fallback(v.optional(v.number()), undefined),
max_option_chars: v.fallback(v.optional(v.number()), undefined),
max_options: v.fallback(v.optional(v.number()), undefined),
min_expiration: v.fallback(v.optional(v.number()), undefined),
});
const registrations = coerceObject({
@ -249,9 +247,9 @@ const registrations = coerceObject({
});
const statsSchema = coerceObject({
domain_count: z.number().optional().catch(undefined),
status_count: z.number().optional().catch(undefined),
user_count: z.number().optional().catch(undefined),
domain_count: v.fallback(v.optional(v.number()), undefined),
status_count: v.fallback(v.optional(v.number()), undefined),
user_count: v.fallback(v.optional(v.number()), undefined),
});
const thumbnailSchema = coerceObject({
@ -268,15 +266,15 @@ const instanceV1Schema = coerceObject({
account_domain: v.fallback(v.string(), ''),
approval_required: v.fallback(v.boolean(), false),
configuration: configurationSchema,
contact_account: accountSchema.optional().catch(undefined),
contact_account: v.fallback(v.optional(accountSchema), undefined),
description: v.fallback(v.string(), ''),
description_limit: z.number().catch(1500),
email: z.string().email().catch(''),
description_limit: v.fallback(v.number(), 1500),
email: v.fallback(v.pipe(v.string(), v.email()), ''),
feature_quote: v.fallback(v.boolean(), false),
fedibird_capabilities: z.array(v.string()).catch([]),
fedibird_capabilities: v.fallback(v.array(v.string()), []),
languages: v.fallback(v.array(v.string()), []),
max_media_attachments: z.number().optional().catch(undefined),
max_toot_chars: z.number().optional().catch(undefined),
max_media_attachments: v.fallback(v.optional(v.number()), undefined),
max_toot_chars: v.fallback(v.optional(v.number()), undefined),
pleroma: pleromaSchema,
poll_limits: pleromaPollLimitsSchema,
registrations: v.fallback(v.boolean(), false),
@ -285,13 +283,13 @@ const instanceV1Schema = coerceObject({
stats: statsSchema,
thumbnail: v.fallback(v.string(), ''),
title: v.fallback(v.string(), ''),
upload_limit: z.number().optional().catch(undefined),
upload_limit: v.fallback(v.optional(v.number()), undefined),
uri: v.fallback(v.string(), ''),
urls: coerceObject({
streaming_api: z.string().url().optional().catch(undefined),
streaming_api: v.fallback(v.optional(v.pipe(v.string(), v.url())), undefined),
}),
usage: usageSchema,
version: z.string().catch('0.0.0'),
version: v.fallback(v.string(), '0.0.0'),
});
/** @see {@link https://docs.joinmastodon.org/entities/Instance/} */
@ -308,13 +306,13 @@ const instanceSchema = z.preprocess((data: any) => {
return instanceV1ToV2({ ...data, api_versions: apiVersions });
}, coerceObject({
account_domain: v.fallback(v.string(), ''),
api_versions: z.record(z.number()).catch({}),
api_versions: v.fallback(v.record(v.string(), v.number()), {}),
configuration: configurationSchema,
contact: contactSchema,
description: v.fallback(v.string(), ''),
domain: v.fallback(v.string(), ''),
feature_quote: v.fallback(v.boolean(), false),
fedibird_capabilities: z.array(v.string()).catch([]),
fedibird_capabilities: v.fallback(v.array(v.string()), []),
languages: v.fallback(v.array(v.string()), []),
pleroma: pleromaSchema,
registrations: registrations,
@ -323,7 +321,7 @@ const instanceSchema = z.preprocess((data: any) => {
thumbnail: thumbnailSchema,
title: v.fallback(v.string(), ''),
usage: usageSchema,
version: z.string().catch('0.0.0'),
version: v.fallback(v.string(), '0.0.0'),
}).transform((instance) => {
const version = fixVersion(instance.version);

View File

@ -2,11 +2,11 @@ import * as v from 'valibot';
import { coerceObject } from './utils';
const interactionPolicyEntrySchema = z.enum(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']);
const interactionPolicyEntrySchema = v.picklist(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']);
const interactionPolicyRuleSchema = coerceObject({
always: z.array(interactionPolicyEntrySchema).default(['public']),
with_approval: z.array(interactionPolicyEntrySchema).default([]),
always: v.fallback(v.array(interactionPolicyEntrySchema), ['public']),
with_approval: v.fallback(v.array(interactionPolicyEntrySchema), []),
});
/** @see {@link https://docs.gotosocial.org/en/latest/api/swagger/} */

View File

@ -5,14 +5,14 @@ import { statusSchema } from './status';
/** @see {@link https://docs.gotosocial.org/en/latest/api/swagger.yaml#/definitions/interactionRequest} */
const interactionRequestSchema = v.object({
accepted_at: z.string().datetime().nullable().catch(null),
accepted_at: v.fallback(v.nullable(z.string().datetime()), null),
account: accountSchema,
created_at: z.string().datetime(),
id: v.string(),
rejected_at: z.string().datetime().nullable().catch(null),
rejected_at: v.fallback(v.nullable(z.string().datetime()), null),
reply: v.fallback(v.nullable(statusSchema), null),
status: v.fallback(v.nullable(statusSchema), null),
type: z.enum(['favourite', 'reply', 'reblog']),
type: v.picklist(['favourite', 'reply', 'reblog']),
uri: v.fallback(v.nullable(v.string()), null),
});

View File

@ -1,7 +1,7 @@
import * as v from 'valibot';
const locationSchema = v.object({
url: z.string().url().catch(''),
url: v.fallback(v.pipe(v.string(), v.url()), ''),
description: v.fallback(v.string(), ''),
country: v.fallback(v.string(), ''),
locality: v.fallback(v.string(), ''),
@ -12,10 +12,10 @@ const locationSchema = v.object({
origin_provider: v.fallback(v.string(), ''),
type: v.fallback(v.string(), ''),
timezone: v.fallback(v.string(), ''),
geom: v.object({
coordinates: z.tuple([z.number(), z.number()]).nullable().catch(null),
geom: v.fallback(v.nullable(v.object({
coordinates: v.fallback(v.nullable(z.tuple([v.number(), v.number()])), null),
srid: v.fallback(v.string(), ''),
}).nullable().catch(null),
})), null),
});
type Location = v.InferOutput<typeof locationSchema>;

View File

@ -7,7 +7,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({
...marker,
}) : null, v.object({
last_read_id: v.string(),
version: z.number().int(),
version: v.pipe(v.number(), v.integer()),
updated_at: dateSchema,
unread_count: z.number().int().optional().catch(undefined),
}));
@ -15,7 +15,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({
/** @see {@link https://docs.joinmastodon.org/entities/Marker/} */
type Marker = v.InferOutput<typeof markerSchema>;
const markersSchema = z.record(markerSchema);
const markersSchema = v.record(v.string(), markerSchema);
type Markers = v.InferOutput<typeof markersSchema>;

View File

@ -17,9 +17,9 @@ const blurhashSchema = z.string().superRefine((value, ctx) => {
const baseAttachmentSchema = v.object({
id: v.string(),
type: v.string(),
url: z.string().url().catch(''),
preview_url: z.string().url().catch(''),
remote_url: z.string().url().nullable().catch(null),
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),
description: v.fallback(v.string(), ''),
blurhash: v.fallback(v.nullable(blurhashSchema), null),
@ -27,64 +27,64 @@ const baseAttachmentSchema = v.object({
});
const imageMetaSchema = v.object({
width: z.number(),
height: z.number(),
width: v.number(),
height: v.number(),
size: z.string().regex(/\d+x\d+$/).nullable().catch(null),
aspect: v.fallback(v.nullable(v.number()), null),
});
const imageAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('image'),
meta: v.object({
original: imageMetaSchema.optional().catch(undefined),
small: imageMetaSchema.optional().catch(undefined),
focus: v.object({
type: v.literal('image'),
meta: v.fallback(v.object({
original: v.fallback(v.optional(imageMetaSchema), undefined),
small: v.fallback(v.optional(imageMetaSchema), undefined),
focus: v.fallback(v.optional(v.object({
x: z.number().min(-1).max(1),
y: z.number().min(-1).max(1),
}).optional().catch(undefined),
}).catch({}),
})), undefined),
}), {}),
});
const videoAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('video'),
meta: v.object({
duration: z.number().optional().catch(undefined),
original: imageMetaSchema.extend({
frame_rate: z.string().regex(/\d+\/\d+$/).nullable().catch(null),
duration: z.number().nonnegative().nullable().catch(null),
}).optional().catch(undefined),
small: imageMetaSchema.optional().catch(undefined),
type: v.literal('video'),
meta: v.fallback(v.object({
duration: v.fallback(v.optional(v.number()), undefined),
original: v.fallback(v.optional(imageMetaSchema.extend({
frame_rate: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+\/\d+$/))), null),
duration: v.fallback(v.nullable(z.number().nonnegative()), null),
})), undefined),
small: v.fallback(v.optional(imageMetaSchema), undefined),
// WIP: add rest
}).catch({}),
}), {}),
});
const gifvAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('gifv'),
meta: v.object({
duration: z.number().optional().catch(undefined),
original: imageMetaSchema.optional().catch(undefined),
}).catch({}),
type: v.literal('gifv'),
meta: v.fallback(v.object({
duration: v.fallback(v.optional(v.number()), undefined),
original: v.fallback(v.optional(imageMetaSchema), undefined),
}), {}),
});
const audioAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('audio'),
meta: v.object({
duration: z.number().optional().catch(undefined),
colors: v.object({
type: v.literal('audio'),
meta: v.fallback(v.object({
duration: v.fallback(v.optional(v.number()), undefined),
colors: v.fallback(v.optional(v.object({
background: v.fallback(v.optional(v.string()), undefined),
foreground: v.fallback(v.optional(v.string()), undefined),
accent: v.fallback(v.optional(v.string()), undefined),
duration: z.number().optional().catch(undefined),
}).optional().catch(undefined),
original: v.object({
duration: z.number().optional().catch(undefined),
duration: v.fallback(v.optional(v.number()), undefined),
})), undefined),
original: v.fallback(v.optional(v.object({
duration: v.fallback(v.optional(v.number()), undefined),
bitrate: z.number().nonnegative().optional().catch(undefined),
}).optional().catch(undefined),
}).catch({}),
})), undefined),
}), {}),
});
const unknownAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('unknown'),
type: v.literal('unknown'),
});
/** @see {@link https://docs.joinmastodon.org/entities/MediaAttachment} */

View File

@ -4,7 +4,7 @@ import * as v from 'valibot';
const mentionSchema = v.object({
id: v.string(),
username: v.fallback(v.string(), ''),
url: z.string().url().catch(''),
url: v.fallback(v.pipe(v.string(), v.url()), ''),
acct: v.string(),
}).transform((mention) => {
if (!mention.username) {

View File

@ -7,8 +7,8 @@ const notificationPolicySchema = v.object({
filter_new_accounts: v.boolean(),
filter_private_mentions: v.boolean(),
summary: v.object({
pending_requests_count: z.number().int(),
pending_notifications_count: z.number().int(),
pending_requests_count: v.pipe(v.number(), v.integer()),
pending_notifications_count: v.pipe(v.number(), v.integer()),
}),
});

View File

@ -11,7 +11,7 @@ const notificationRequestSchema = v.object({
updated_at: dateSchema,
account: accountSchema,
notifications_count: z.coerce.string(),
last_status: statusSchema.optional().catch(undefined),
last_status: v.fallback(v.optional(statusSchema), undefined),
});
type NotificationRequest = v.InferOutput<typeof notificationRequestSchema>;

View File

@ -21,54 +21,54 @@ const baseNotificationSchema = v.object({
});
const accountNotificationSchema = baseNotificationSchema.extend({
type: z.enum(['follow', 'follow_request', 'admin.sign_up', 'bite']),
type: v.picklist(['follow', 'follow_request', 'admin.sign_up', 'bite']),
});
const mentionNotificationSchema = baseNotificationSchema.extend({
type: z.literal('mention'),
subtype: z.enum(['reply']).nullable().catch(null),
type: v.literal('mention'),
subtype: v.fallback(v.nullable(v.picklist(['reply'])), null),
status: statusSchema,
});
const statusNotificationSchema = baseNotificationSchema.extend({
type: z.enum(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']),
type: v.picklist(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']),
status: statusSchema,
});
const reportNotificationSchema = baseNotificationSchema.extend({
type: z.literal('admin.report'),
type: v.literal('admin.report'),
report: reportSchema,
});
const severedRelationshipNotificationSchema = baseNotificationSchema.extend({
type: z.literal('severed_relationships'),
type: v.literal('severed_relationships'),
relationship_severance_event: relationshipSeveranceEventSchema,
});
const moderationWarningNotificationSchema = baseNotificationSchema.extend({
type: z.literal('moderation_warning'),
type: v.literal('moderation_warning'),
moderation_warning: accountWarningSchema,
});
const moveNotificationSchema = baseNotificationSchema.extend({
type: z.literal('move'),
type: v.literal('move'),
target: accountSchema,
});
const emojiReactionNotificationSchema = baseNotificationSchema.extend({
type: z.literal('emoji_reaction'),
type: v.literal('emoji_reaction'),
emoji: v.string(),
emoji_url: v.fallback(v.nullable(v.string()), null),
status: statusSchema,
});
const chatMentionNotificationSchema = baseNotificationSchema.extend({
type: z.literal('chat_mention'),
type: v.literal('chat_mention'),
chat_message: chatMessageSchema,
});
const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({
type: z.enum(['participation_accepted', 'participation_request']),
type: v.picklist(['participation_accepted', 'participation_request']),
status: statusSchema,
participation_message: v.fallback(v.nullable(v.string()), null),
});

View File

@ -6,7 +6,7 @@ const oauthTokenSchema = z.preprocess((token: any) => ({
valid_until: token?.valid_until?.padEnd(27, 'Z'),
}), v.object({
app_name: v.string(),
id: z.number(),
id: v.number(),
valid_until: z.string().datetime({ offset: true }),
}));

View File

@ -7,20 +7,20 @@ const pollOptionSchema = v.object({
title: v.fallback(v.string(), ''),
votes_count: v.fallback(v.number(), 0),
title_map: z.record(v.string()).nullable().catch(null),
title_map: v.record(v.string(), v.string()).nullable().catch(null),
});
/** @see {@link https://docs.joinmastodon.org/entities/Poll/} */
const pollSchema = v.object({
emojis: filteredArray(customEmojiSchema),
expired: v.fallback(v.boolean(), false),
expires_at: z.string().datetime().nullable().catch(null),
expires_at: v.fallback(v.nullable(z.string().datetime()), null),
id: v.string(),
multiple: v.fallback(v.boolean(), false),
options: z.array(pollOptionSchema).min(2),
voters_count: v.fallback(v.number(), 0),
votes_count: v.fallback(v.number(), 0),
own_votes: z.array(z.number()).nonempty().nullable().catch(null),
own_votes: v.fallback(v.nullable(z.number()).nonempty(), null),
voted: v.fallback(v.boolean(), false),
non_anonymous: v.fallback(v.boolean(), false),

View File

@ -3,19 +3,19 @@ import * as v from 'valibot';
/** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/} */
const previewCardSchema = v.object({
author_name: v.fallback(v.string(), ''),
author_url: z.string().url().catch(''),
author_url: v.fallback(v.pipe(v.string(), v.url()), ''),
blurhash: v.fallback(v.nullable(v.string()), null),
description: v.fallback(v.string(), ''),
embed_url: z.string().url().catch(''),
embed_url: v.fallback(v.pipe(v.string(), v.url()), ''),
height: v.fallback(v.number(), 0),
html: v.fallback(v.string(), ''),
image: v.fallback(v.nullable(v.string()), null),
image_description: v.fallback(v.string(), ''),
provider_name: v.fallback(v.string(), ''),
provider_url: z.string().url().catch(''),
provider_url: v.fallback(v.pipe(v.string(), v.url()), ''),
title: v.fallback(v.string(), ''),
type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'),
url: z.string().url(),
type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'),
url: v.pipe(v.string(), v.url()),
width: v.fallback(v.number(), 0),
});

View File

@ -5,9 +5,9 @@ import { dateSchema } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/} */
const relationshipSeveranceEventSchema = v.object({
id: v.string(),
type: z.enum(['domain_block', 'user_domain_block', 'account_suspension']),
type: v.picklist(['domain_block', 'user_domain_block', 'account_suspension']),
purged: v.string(),
relationships_count: z.number().optional().catch(undefined),
relationships_count: v.fallback(v.optional(v.number()), undefined),
created_at: dateSchema,
});

View File

@ -11,9 +11,9 @@ const reportSchema = v.object({
category: v.fallback(v.optional(v.string()), undefined),
comment: v.fallback(v.optional(v.string()), undefined),
forwarded: v.fallback(v.optional(v.boolean()), undefined),
created_at: dateSchema.optional().catch(undefined),
status_ids: z.array(v.string()).nullable().catch(null),
rule_ids: z.array(v.string()).nullable().catch(null),
created_at: v.fallback(v.optional(dateSchema), undefined),
status_ids: v.fallback(v.nullable(v.string()), null),
rule_ids: v.fallback(v.nullable(v.string()), null),
target_account: v.fallback(v.nullable(accountSchema), null),
});

View File

@ -15,14 +15,14 @@ const scheduledStatusSchema = v.object({
multiple: v.fallback(v.optional(v.boolean()), undefined),
hide_totals: v.fallback(v.optional(v.boolean()), undefined),
}).nullable().catch(null),
media_ids: z.array(v.string()).nullable().catch(null),
sensitive: z.coerce.boolean().nullable().catch(null),
media_ids: v.fallback(v.nullable(v.string()), null),
sensitive: v.fallback(v.nullable(z.coerce.boolean()), null),
spoiler_text: v.fallback(v.nullable(v.string()), null),
visibility: z.string().catch('public'),
in_reply_to_id: v.fallback(v.nullable(v.string()), null),
language: v.fallback(v.nullable(v.string()), null),
application_id: z.number().int().nullable().catch(null),
scheduled_at: z.string().datetime({ offset: true }).nullable().catch(null),
application_id: v.fallback(v.nullable(z.number().int()), null),
scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null),
idempotency: v.fallback(v.nullable(v.string()), null),
with_rate_limit: v.fallback(v.boolean(), false),

View File

@ -11,8 +11,8 @@ const statusSourceSchema = v.object({
content_type: z.string().catch('text/plain'),
location: v.fallback(v.nullable(locationSchema), null),
text_map: z.record(v.string()).nullable().catch(null),
spoiler_text_map: z.record(v.string()).nullable().catch(null),
text_map: v.record(v.string(), v.string()).nullable().catch(null),
spoiler_text_map: v.record(v.string(), v.string()).nullable().catch(null),
});
type StatusSource = v.InferOutput<typeof statusSourceSchema>;

View File

@ -17,13 +17,13 @@ import { dateSchema, filteredArray } from './utils';
const statusEventSchema = v.object({
name: v.fallback(v.string(), ''),
start_time: z.string().datetime().nullable().catch(null),
end_time: z.string().datetime().nullable().catch(null),
join_mode: z.enum(['free', 'restricted', 'invite']).nullable().catch(null),
start_time: v.fallback(v.nullable(z.string().datetime()), null),
end_time: v.fallback(v.nullable(z.string().datetime()), null),
join_mode: v.fallback(v.nullable(v.picklist(['free', 'restricted', 'invite'])), null),
participants_count: v.fallback(v.number(), 0),
location: v.object({
location: v.fallback(v.nullable(v.object({
name: v.fallback(v.string(), ''),
url: z.string().url().catch(''),
url: v.fallback(v.pipe(v.string(), v.url()), ''),
latitude: v.fallback(v.number(), 0),
longitude: v.fallback(v.number(), 0),
street: v.fallback(v.string(), ''),
@ -31,14 +31,14 @@ const statusEventSchema = v.object({
locality: v.fallback(v.string(), ''),
region: v.fallback(v.string(), ''),
country: v.fallback(v.string(), ''),
}).nullable().catch(null),
join_state: z.enum(['pending', 'reject', 'accept']).nullable().catch(null),
})), null),
join_state: v.fallback(v.nullable(v.picklist(['pending', 'reject', 'accept'])), null),
});
/** @see {@link https://docs.joinmastodon.org/entities/Status/} */
const baseStatusSchema = v.object({
id: v.string(),
uri: z.string().url().catch(''),
uri: v.fallback(v.pipe(v.string(), v.url()), ''),
created_at: dateSchema,
account: accountSchema,
content: v.fallback(v.string(), ''),
@ -46,55 +46,55 @@ const baseStatusSchema = v.object({
sensitive: z.coerce.boolean(),
spoiler_text: v.fallback(v.string(), ''),
media_attachments: filteredArray(mediaAttachmentSchema),
application: v.object({
application: v.fallback(v.nullable(v.object({
name: v.string(),
website: z.string().url().nullable().catch(null),
}).nullable().catch(null),
website: v.fallback(v.nullable(z.string().url()), null),
})), null),
mentions: filteredArray(mentionSchema),
tags: filteredArray(tagSchema),
emojis: filteredArray(customEmojiSchema),
reblogs_count: v.fallback(v.number(), 0),
favourites_count: v.fallback(v.number(), 0),
replies_count: v.fallback(v.number(), 0),
url: z.string().url().catch(''),
url: v.fallback(v.pipe(v.string(), v.url()), ''),
in_reply_to_id: v.fallback(v.nullable(v.string()), null),
in_reply_to_account_id: v.fallback(v.nullable(v.string()), null),
poll: v.fallback(v.nullable(pollSchema), null),
card: v.fallback(v.nullable(previewCardSchema), null),
language: v.fallback(v.nullable(v.string()), null),
text: v.fallback(v.nullable(v.string()), null),
edited_at: z.string().datetime().nullable().catch(null),
edited_at: v.fallback(v.nullable(z.string().datetime()), null),
favourited: z.coerce.boolean(),
reblogged: z.coerce.boolean(),
muted: z.coerce.boolean(),
bookmarked: z.coerce.boolean(),
pinned: z.coerce.boolean(),
filtered: filteredArray(filterResultSchema),
approval_status: z.enum(['pending', 'approval', 'rejected']).nullable().catch(null),
approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null),
group: v.fallback(v.nullable(groupSchema), null),
scheduled_at: z.null().catch(null),
scheduled_at: v.fallback(v.null(), null),
quote_id: v.fallback(v.nullable(v.string()), null),
local: v.fallback(v.optional(v.boolean()), undefined),
conversation_id: v.fallback(v.optional(v.string()), undefined),
direct_conversation_id: v.fallback(v.optional(v.string()), undefined),
in_reply_to_account_acct: v.fallback(v.optional(v.string()), undefined),
expires_at: z.string().datetime({ offset: true }).optional().catch(undefined),
expires_at: v.fallback(v.optional(z.string().datetime({ offset: true })), undefined),
thread_muted: v.fallback(v.optional(v.boolean()), undefined),
emoji_reactions: filteredArray(emojiReactionSchema),
parent_visible: v.fallback(v.optional(v.boolean()), undefined),
pinned_at: z.string().datetime({ offset: true }).nullable().catch(null),
pinned_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null),
quote_visible: v.fallback(v.optional(v.boolean()), undefined),
quote_url: v.fallback(v.optional(v.string()), undefined),
quotes_count: v.fallback(v.number(), 0),
bookmark_folder: v.fallback(v.nullable(v.string()), null),
event: v.fallback(v.nullable(statusEventSchema), null),
translation: translationSchema.nullable().or(z.literal(false)).catch(null),
translation: translationSchema.nullable().or(v.literal(false)).catch(null),
content_map: z.record(v.string()).nullable().catch(null),
text_map: z.record(v.string()).nullable().catch(null),
spoiler_text_map: z.record(v.string()).nullable().catch(null),
content_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null),
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),
disliked: z.coerce.boolean().catch(false),
@ -135,16 +135,16 @@ const preprocess = (status: any) => {
};
const statusSchema: z.ZodType<Status> = z.preprocess(preprocess, baseStatusSchema.extend({
reblog: z.lazy(() => statusSchema).nullable().catch(null),
reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null),
quote: z.lazy(() => statusSchema).nullable().catch(null),
quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null),
})) as any;
const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({
account: v.fallback(v.nullable(accountSchema), null),
reblog: z.lazy(() => statusSchema).nullable().catch(null),
reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null),
quote: z.lazy(() => statusSchema).nullable().catch(null),
quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null),
}));
type Status = v.InferOutput<typeof baseStatusSchema> & {

View File

@ -9,7 +9,7 @@ import { notificationSchema } from './notification';
import { statusSchema } from './status';
const followRelationshipUpdateSchema = v.object({
state: z.enum(['follow_pending', 'follow_accept', 'follow_reject']),
state: v.picklist(['follow_pending', 'follow_accept', 'follow_reject']),
follower: v.object({
id: v.string(),
follower_count: v.fallback(v.nullable(v.number()), null),
@ -29,59 +29,59 @@ const baseStreamingEventSchema = v.object({
});
const statusStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.enum(['update', 'status.update']),
event: v.picklist(['update', 'status.update']),
payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema),
});
const stringStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.enum(['delete', 'announcement.delete']),
event: v.picklist(['delete', 'announcement.delete']),
payload: v.string(),
});
const notificationStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('notification'),
event: v.literal('notification'),
payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema),
});
const emptyStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('filters_changed'),
event: v.literal('filters_changed'),
});
const conversationStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('conversation'),
event: v.literal('conversation'),
payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema),
});
const announcementStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('announcement'),
event: v.literal('announcement'),
payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema),
});
const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('announcement.reaction'),
event: v.literal('announcement.reaction'),
payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema),
});
const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('chat_update'),
event: v.literal('chat_update'),
payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema),
});
const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('follow_relationships_update'),
event: v.literal('follow_relationships_update'),
payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema),
});
const respondStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('respond'),
event: v.literal('respond'),
payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({
type: v.string(),
result: z.enum(['success', 'ignored', 'error']),
result: v.picklist(['success', 'ignored', 'error']),
})),
});
const markerStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('marker'),
event: v.literal('marker'),
payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema),
});

View File

@ -9,8 +9,8 @@ const historySchema = v.object({
/** @see {@link https://docs.joinmastodon.org/entities/tag} */
const tagSchema = v.object({
name: z.string().min(1),
url: z.string().url().catch(''),
history: z.array(historySchema).nullable().catch(null),
url: v.fallback(v.pipe(v.string(), v.url()), ''),
history: v.fallback(v.nullable(historySchema), null),
following: v.fallback(v.optional(v.boolean()), undefined),
});

View File

@ -5,11 +5,11 @@ const tokenSchema = v.object({
access_token: v.string(),
token_type: v.string(),
scope: v.string(),
created_at: z.number().optional().catch(undefined),
created_at: v.fallback(v.optional(v.number()), undefined),
id: z.number().optional().catch(undefined),
id: v.fallback(v.optional(v.number()), undefined),
refresh_token: v.fallback(v.optional(v.string()), undefined),
expires_in: z.number().optional().catch(undefined),
expires_in: v.fallback(v.optional(v.number()), undefined),
me: v.fallback(v.optional(v.string()), undefined),
});

View File

@ -31,7 +31,7 @@ const translationSchema = z.preprocess((translation: any) => {
id: v.fallback(v.nullable(v.string()), null),
content: v.fallback(v.string(), ''),
spoiler_text: v.fallback(v.string(), ''),
poll: translationPollSchema.optional().catch(undefined),
poll: v.fallback(v.optional(translationPollSchema), undefined),
media_attachments: filteredArray(translationMediaAttachment),
detected_source_language: v.string(),
provider: v.string(),

View File

@ -6,10 +6,10 @@ import { historySchema } from './tag';
/** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */
const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), v.object({
id: v.fallback(v.string(), ''),
url: z.string().url().catch(''),
url: v.fallback(v.pipe(v.string(), v.url()), ''),
title: v.fallback(v.string(), ''),
description: v.fallback(v.string(), ''),
type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'),
type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'),
author_name: v.fallback(v.string(), ''),
author_url: v.fallback(v.string(), ''),
provider_name: v.fallback(v.string(), ''),
@ -21,7 +21,7 @@ const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url })
image_description: v.fallback(v.nullable(v.string()), null),
embed_url: v.fallback(v.string(), ''),
blurhash: v.fallback(v.nullable(blurhashSchema), null),
history: z.array(historySchema).nullable().catch(null),
history: v.fallback(v.nullable(historySchema), null),
}));
type TrendsLink = v.InferOutput<typeof trendsLinkSchema>;

View File

@ -14,13 +14,17 @@ const filteredArray = <T extends z.ZodTypeAny>(schema: T) =>
));
/** Validates the string as an emoji. */
const emojiSchema = z.string().refine((v) => /\p{Extended_Pictographic}|[\u{1F1E6}-\u{1F1FF}]{2}/u.test(v));
const emojiSchema = v.pipe(v.string(), v.emoji());
/** MIME schema, eg `image/png`. */
const mimeSchema = z.string().regex(/^\w+\/[-+.\w]+$/);
const mimeSchema = v.pipe(v.string(), v.regex(/^\w+\/[-+.\w]+$/));
/** zod schema to force the value into an object, if it isn't already. */
const coerceObject = <T extends z.ZodRawShape>(shape: T) =>
v.object({}).passthrough().catch({}).pipe(z.object(shape));
/** valibot schema to force the value into an object, if it isn't already. */
const coerceObject = <T extends v.ObjectEntries>(shape: T) =>
v.pipe(
v.any(),
v.transform((input) => typeof input === 'object' ? input : {}),
v.object(shape),
);
export { filteredArray, emojiSchema, dateSchema, mimeSchema, coerceObject };

View File

@ -4,7 +4,7 @@ import * as v from 'valibot';
const webPushSubscriptionSchema = v.object({
id: z.coerce.string(),
endpoint: v.string(),
alerts: z.record(z.boolean()),
alerts: v.record(v.string(), z.boolean()),
server_key: v.string(),
});