mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
refactor progress
This commit is contained in:
8257 files changed
+1190205
-761038
No files matched your search
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {MessageAttachmentFlags, MessageAttachmentFlagsDescriptions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {FilenameType} from '@fluxer/schema/src/primitives/FileValidators';
|
||||
import {
|
||||
coerceNumberFromString,
|
||||
createBitflagInt32Type,
|
||||
createStringType,
|
||||
Int32Type,
|
||||
SnowflakeType,
|
||||
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {z} from 'zod';
|
||||
|
||||
const ClientAttachmentBase = z.object({
|
||||
title: createStringType(1, 1024).nullish().describe('A title for the attachment (1-1024 characters)'),
|
||||
description: createStringType(1, 4096)
|
||||
.nullish()
|
||||
.describe('An alt text description of the attachment (1-4096 characters)'),
|
||||
flags: coerceNumberFromString(
|
||||
createBitflagInt32Type(
|
||||
MessageAttachmentFlags,
|
||||
MessageAttachmentFlagsDescriptions,
|
||||
'Attachment flags',
|
||||
'MessageAttachmentFlags',
|
||||
),
|
||||
).default(0),
|
||||
duration: Int32Type.nullish().describe('The duration of the audio file in seconds'),
|
||||
waveform: createStringType(1, 4096).nullish().describe('Base64 encoded audio waveform data'),
|
||||
});
|
||||
|
||||
export const ClientAttachmentRequest = ClientAttachmentBase.extend({
|
||||
id: coerceNumberFromString(Int32Type).describe('The client-side identifier for this attachment'),
|
||||
filename: FilenameType.describe('The name of the file being uploaded'),
|
||||
});
|
||||
export type ClientAttachmentRequest = z.infer<typeof ClientAttachmentRequest>;
|
||||
|
||||
export const ClientAttachmentReferenceRequest = ClientAttachmentBase.extend({
|
||||
id: z
|
||||
.union([Int32Type, SnowflakeType])
|
||||
.describe('The identifier of the attachment being referenced (snowflake ID or file index)'),
|
||||
filename: FilenameType.optional().describe('A new filename for the attachment'),
|
||||
});
|
||||
export type ClientAttachmentReferenceRequest = z.infer<typeof ClientAttachmentReferenceRequest>;
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {EmbedMediaFlags, EmbedMediaFlagsDescriptions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {createBitflagInt32Type, Int32Type} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {z} from 'zod';
|
||||
|
||||
export const EmbedAuthorResponse = z.object({
|
||||
name: z.string().describe('The name of the author'),
|
||||
url: z.url().nullish().describe('The URL of the author'),
|
||||
icon_url: z.url().nullish().describe('The URL of the author icon'),
|
||||
proxy_icon_url: z.url().nullish().describe('The proxied URL of the author icon'),
|
||||
});
|
||||
|
||||
export type EmbedAuthorResponse = z.infer<typeof EmbedAuthorResponse>;
|
||||
|
||||
export const EmbedFooterResponse = z.object({
|
||||
text: z.string().describe('The footer text'),
|
||||
icon_url: z.url().nullish().describe('The URL of the footer icon'),
|
||||
proxy_icon_url: z.url().nullish().describe('The proxied URL of the footer icon'),
|
||||
});
|
||||
|
||||
export type EmbedFooterResponse = z.infer<typeof EmbedFooterResponse>;
|
||||
|
||||
export const EmbedMediaResponse = z.object({
|
||||
url: z.string().describe('The URL of the media'),
|
||||
proxy_url: z.url().nullish().describe('The proxied URL of the media'),
|
||||
content_type: z.string().nullish().describe('The MIME type of the media'),
|
||||
content_hash: z.string().nullish().describe('The hash of the media content'),
|
||||
width: Int32Type.nullish().describe('The width of the media in pixels'),
|
||||
height: Int32Type.nullish().describe('The height of the media in pixels'),
|
||||
description: z.string().nullish().describe('The description of the media'),
|
||||
placeholder: z.string().nullish().describe('The base64 encoded placeholder image for lazy loading'),
|
||||
duration: Int32Type.nullish().describe('The duration of the media in seconds'),
|
||||
flags: createBitflagInt32Type(
|
||||
EmbedMediaFlags,
|
||||
EmbedMediaFlagsDescriptions,
|
||||
'The bitwise flags for this media',
|
||||
'EmbedMediaFlags',
|
||||
),
|
||||
});
|
||||
|
||||
export type EmbedMediaResponse = z.infer<typeof EmbedMediaResponse>;
|
||||
|
||||
export const EmbedFieldResponse = z.object({
|
||||
name: z.string().describe('The name of the field'),
|
||||
value: z.string().describe('The value of the field'),
|
||||
inline: z.boolean().describe('Whether the field should be displayed inline'),
|
||||
});
|
||||
|
||||
export type EmbedFieldResponse = z.infer<typeof EmbedFieldResponse>;
|
||||
|
||||
interface MessageEmbedChildResponseData {
|
||||
type: string;
|
||||
url?: string | null;
|
||||
title?: string | null;
|
||||
color?: number | null;
|
||||
timestamp?: string | null;
|
||||
description?: string | null;
|
||||
author?: EmbedAuthorResponse | null;
|
||||
image?: EmbedMediaResponse | null;
|
||||
thumbnail?: EmbedMediaResponse | null;
|
||||
footer?: EmbedFooterResponse | null;
|
||||
fields?: Array<EmbedFieldResponse> | null;
|
||||
provider?: EmbedAuthorResponse | null;
|
||||
video?: EmbedMediaResponse | null;
|
||||
audio?: EmbedMediaResponse | null;
|
||||
nsfw?: boolean | null;
|
||||
}
|
||||
|
||||
interface MessageEmbedResponseData extends MessageEmbedChildResponseData {
|
||||
children?: Array<MessageEmbedChildResponseData> | null;
|
||||
}
|
||||
|
||||
export const MessageEmbedChildResponse = z.object({
|
||||
type: z.string().describe('The type of embed (e.g., rich, image, video, gifv, article, link)'),
|
||||
url: z.url().nullish().describe('The URL of the embed'),
|
||||
title: z.string().nullish().describe('The title of the embed'),
|
||||
color: Int32Type.nullish().describe('The color code of the embed sidebar'),
|
||||
timestamp: z.iso.datetime().nullish().describe('The ISO 8601 timestamp of the embed content'),
|
||||
description: z.string().nullish().describe('The description of the embed'),
|
||||
author: EmbedAuthorResponse.nullish().describe('The author information of the embed'),
|
||||
image: EmbedMediaResponse.nullish().describe('The image of the embed'),
|
||||
thumbnail: EmbedMediaResponse.nullish().describe('The thumbnail of the embed'),
|
||||
footer: EmbedFooterResponse.nullish().describe('The footer of the embed'),
|
||||
fields: z.array(EmbedFieldResponse).max(25).nullish().describe('The fields of the embed'),
|
||||
provider: EmbedAuthorResponse.nullish().describe('The provider of the embed (e.g., YouTube, Twitter)'),
|
||||
video: EmbedMediaResponse.nullish().describe('The video of the embed'),
|
||||
audio: EmbedMediaResponse.nullish().describe('The audio of the embed'),
|
||||
nsfw: z.boolean().nullish().describe('Whether the embed is flagged as NSFW'),
|
||||
});
|
||||
|
||||
export type MessageEmbedChildResponse = z.infer<typeof MessageEmbedChildResponse>;
|
||||
|
||||
export const MessageEmbedResponse: z.ZodType<MessageEmbedResponseData> = MessageEmbedChildResponse.extend({
|
||||
children: z
|
||||
.array(MessageEmbedChildResponse)
|
||||
.max(1)
|
||||
.nullish()
|
||||
.describe('Internal nested embeds generated by unfurlers'),
|
||||
});
|
||||
|
||||
export type MessageEmbedResponse = z.infer<typeof MessageEmbedResponse>;
|
||||
|
||||
export interface EmbedAuthor {
|
||||
readonly name: string;
|
||||
readonly url?: string;
|
||||
readonly icon_url?: string;
|
||||
readonly proxy_icon_url?: string;
|
||||
}
|
||||
|
||||
export interface EmbedFooter {
|
||||
readonly text: string;
|
||||
readonly icon_url?: string;
|
||||
readonly proxy_icon_url?: string;
|
||||
}
|
||||
|
||||
export interface EmbedMedia {
|
||||
readonly url: string;
|
||||
readonly proxy_url?: string;
|
||||
readonly content_type?: string;
|
||||
readonly content_hash?: string | null;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
readonly placeholder?: string;
|
||||
readonly flags: number;
|
||||
readonly description?: string;
|
||||
readonly duration?: number;
|
||||
readonly nsfw?: boolean;
|
||||
}
|
||||
|
||||
export interface EmbedField {
|
||||
readonly name: string;
|
||||
readonly value: string;
|
||||
readonly inline: boolean;
|
||||
}
|
||||
|
||||
export interface MessageEmbed {
|
||||
readonly id?: string;
|
||||
readonly type: string;
|
||||
readonly url?: string;
|
||||
readonly title?: string;
|
||||
readonly color?: number;
|
||||
readonly timestamp?: string;
|
||||
readonly description?: string;
|
||||
readonly author?: EmbedAuthor;
|
||||
readonly image?: EmbedMedia;
|
||||
readonly thumbnail?: EmbedMedia;
|
||||
readonly footer?: EmbedFooter;
|
||||
readonly fields?: ReadonlyArray<EmbedField>;
|
||||
readonly provider?: EmbedAuthor;
|
||||
readonly video?: EmbedMedia;
|
||||
readonly audio?: EmbedMedia;
|
||||
readonly children?: ReadonlyArray<MessageEmbedChild>;
|
||||
readonly flags?: number;
|
||||
readonly nsfw?: boolean;
|
||||
}
|
||||
|
||||
export interface MessageEmbedChild {
|
||||
readonly id?: string;
|
||||
readonly type: string;
|
||||
readonly url?: string;
|
||||
readonly title?: string;
|
||||
readonly color?: number;
|
||||
readonly timestamp?: string;
|
||||
readonly description?: string;
|
||||
readonly author?: EmbedAuthor;
|
||||
readonly image?: EmbedMedia;
|
||||
readonly thumbnail?: EmbedMedia;
|
||||
readonly footer?: EmbedFooter;
|
||||
readonly fields?: ReadonlyArray<EmbedField>;
|
||||
readonly provider?: EmbedAuthor;
|
||||
readonly video?: EmbedMedia;
|
||||
readonly audio?: EmbedMedia;
|
||||
readonly flags?: number;
|
||||
readonly nsfw?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {MessageFlags, MessageFlagsDescriptions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {
|
||||
ClientAttachmentReferenceRequest,
|
||||
ClientAttachmentRequest,
|
||||
} from '@fluxer/schema/src/domains/message/AttachmentSchemas';
|
||||
import {AllowedMentionsRequest, MessageReferenceRequest} from '@fluxer/schema/src/domains/message/SharedMessageSchemas';
|
||||
import {createQueryIntegerType, DateTimeType} from '@fluxer/schema/src/primitives/QueryValidators';
|
||||
import {
|
||||
ColorType,
|
||||
createBitflagInt32Type,
|
||||
createNamedStringLiteralUnion,
|
||||
createStringType,
|
||||
createUnboundedStringType,
|
||||
Int32Type,
|
||||
SnowflakeType,
|
||||
withOpenApiType,
|
||||
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {AttachmentURLType, URLType} from '@fluxer/schema/src/primitives/UrlValidators';
|
||||
import {z} from 'zod';
|
||||
|
||||
export const RichEmbedAuthorRequest = z.object({
|
||||
name: createStringType().describe('Name of the embed author'),
|
||||
url: URLType.nullish().describe('URL to link from the author name'),
|
||||
icon_url: URLType.nullish().describe('URL of the author icon'),
|
||||
});
|
||||
|
||||
export type RichEmbedAuthorRequest = z.infer<typeof RichEmbedAuthorRequest>;
|
||||
|
||||
export const RichEmbedMediaRequest = z.object({
|
||||
url: AttachmentURLType.describe('URL of the media (image, video, etc.)'),
|
||||
description: createStringType(1, 4096).nullish().describe('Alt text description of the media'),
|
||||
});
|
||||
|
||||
export type RichEmbedMediaRequest = z.infer<typeof RichEmbedMediaRequest>;
|
||||
|
||||
export const RichEmbedFooterRequest = z.object({
|
||||
text: createStringType(1, 2048).describe('Footer text (1-2048 characters)'),
|
||||
icon_url: URLType.nullish().describe('URL of the footer icon'),
|
||||
});
|
||||
|
||||
export type RichEmbedFooterRequest = z.infer<typeof RichEmbedFooterRequest>;
|
||||
|
||||
export const RichEmbedFieldRequest = z.object({
|
||||
name: createStringType().describe('Name of the field'),
|
||||
value: createStringType(1, 1024).describe('Value of the field (1-1024 characters)'),
|
||||
inline: z.boolean().default(false).describe('Whether the field should display inline'),
|
||||
});
|
||||
|
||||
export type RichEmbedFieldRequest = z.infer<typeof RichEmbedFieldRequest>;
|
||||
|
||||
export const RichEmbedRequest = z.object({
|
||||
url: URLType.nullish().describe('URL of the embed'),
|
||||
title: createStringType().nullish().describe('Title of the embed'),
|
||||
color: ColorType.nullish().describe('Color code of the embed (hex integer)'),
|
||||
timestamp: DateTimeType.nullish().describe('ISO8601 timestamp for the embed'),
|
||||
description: createStringType(1, 4096).nullish().describe('Description of the embed (1-4096 characters)'),
|
||||
author: RichEmbedAuthorRequest.nullish().describe('Author information'),
|
||||
image: RichEmbedMediaRequest.nullish().describe('Image to display in the embed'),
|
||||
thumbnail: RichEmbedMediaRequest.nullish().describe('Thumbnail image for the embed'),
|
||||
footer: RichEmbedFooterRequest.nullish().describe('Footer information'),
|
||||
fields: z.array(RichEmbedFieldRequest).max(25).nullish().describe('Array of field objects (max 25)'),
|
||||
});
|
||||
|
||||
export type RichEmbedRequest = z.infer<typeof RichEmbedRequest>;
|
||||
|
||||
export const MessageAuthorType = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
['user', 'user', 'A regular user account'],
|
||||
['bot', 'bot', 'An automated bot account'],
|
||||
['webhook', 'webhook', 'A webhook-generated message'],
|
||||
],
|
||||
'The type of author who sent the message',
|
||||
),
|
||||
'MessageAuthorType',
|
||||
);
|
||||
|
||||
export type MessageAuthorType = z.infer<typeof MessageAuthorType>;
|
||||
|
||||
export const MessageContentType = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
['image', 'image', 'Message contains an image attachment'],
|
||||
['sound', 'sound', 'Message contains an audio attachment'],
|
||||
['video', 'video', 'Message contains a video attachment'],
|
||||
['file', 'file', 'Message contains a file attachment'],
|
||||
['sticker', 'sticker', 'Message contains a sticker'],
|
||||
['embed', 'embed', 'Message contains an embed'],
|
||||
['link', 'link', 'Message contains a URL link'],
|
||||
['poll', 'poll', 'Message contains a poll'],
|
||||
['snapshot', 'snapshot', 'Message contains a forwarded message snapshot'],
|
||||
],
|
||||
'The type of content contained in a message',
|
||||
),
|
||||
'MessageContentType',
|
||||
);
|
||||
|
||||
export type MessageContentType = z.infer<typeof MessageContentType>;
|
||||
|
||||
export const MessageEmbedType = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
['image', 'image', 'An image embed from a linked URL'],
|
||||
['video', 'video', 'A video embed from a linked URL'],
|
||||
['sound', 'sound', 'An audio embed from a linked URL'],
|
||||
['article', 'article', 'An article or webpage embed with metadata'],
|
||||
],
|
||||
'The type of embed content',
|
||||
),
|
||||
'MessageEmbedType',
|
||||
);
|
||||
|
||||
export type MessageEmbedType = z.infer<typeof MessageEmbedType>;
|
||||
|
||||
export const MessageSortField = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
['timestamp', 'timestamp', 'Sort results by message timestamp'],
|
||||
['relevance', 'relevance', 'Sort results by search relevance score'],
|
||||
],
|
||||
'The field to sort search results by',
|
||||
),
|
||||
'MessageSortField',
|
||||
);
|
||||
|
||||
export type MessageSortField = z.infer<typeof MessageSortField>;
|
||||
|
||||
export const MessageSortOrder = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
['asc', 'asc', 'Sort in ascending order (oldest/lowest first)'],
|
||||
['desc', 'desc', 'Sort in descending order (newest/highest first)'],
|
||||
],
|
||||
'The order to sort search results',
|
||||
),
|
||||
'MessageSortOrder',
|
||||
);
|
||||
|
||||
export type MessageSortOrder = z.infer<typeof MessageSortOrder>;
|
||||
|
||||
export const MessageSearchScope = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
['current', 'current', 'Search only in the current channel or community context'],
|
||||
['open_dms', 'open_dms', 'Search across all DMs you currently have open'],
|
||||
['all_dms', 'all_dms', "Search across all DMs you've ever been in"],
|
||||
['all_guilds', 'all_guilds', "Search across all Communities you're currently in"],
|
||||
['all', 'all', "Search across all DMs you've ever been in and all Communities you're currently in"],
|
||||
[
|
||||
'open_dms_and_all_guilds',
|
||||
'open_dms_and_all_guilds',
|
||||
"Search across all DMs you currently have open and all Communities you're currently in",
|
||||
],
|
||||
],
|
||||
'Search scope for message searches',
|
||||
),
|
||||
'MessageSearchScope',
|
||||
);
|
||||
|
||||
export type MessageSearchScope = z.infer<typeof MessageSearchScope>;
|
||||
|
||||
export const MessageSearchRequest = z.object({
|
||||
hits_per_page: z.number().int().min(1).max(25).default(25).describe('Number of results per page (1-25)'),
|
||||
page: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER).default(1).describe('Page number for pagination'),
|
||||
max_id: SnowflakeType.optional().describe('Maximum message ID to include in results'),
|
||||
min_id: SnowflakeType.optional().describe('Minimum message ID to include in results'),
|
||||
|
||||
content: createStringType(1, 1024).optional().describe('Text content to search for'),
|
||||
contents: z.array(createStringType(1, 1024)).max(100).optional().describe('Multiple content queries to search for'),
|
||||
exact_phrases: z
|
||||
.array(createStringType(1, 1024))
|
||||
.max(10)
|
||||
.optional()
|
||||
.describe('Exact phrases that must appear contiguously in message content'),
|
||||
|
||||
channel_id: z.array(SnowflakeType).max(500).optional().describe('Channel IDs to search in'),
|
||||
exclude_channel_id: z.array(SnowflakeType).max(500).optional().describe('Channel IDs to exclude from search'),
|
||||
|
||||
author_type: z.array(MessageAuthorType).optional().describe('Author types to filter by'),
|
||||
exclude_author_type: z.array(MessageAuthorType).optional().describe('Author types to exclude'),
|
||||
author_id: z.array(SnowflakeType).optional().describe('Author user IDs to filter by'),
|
||||
exclude_author_id: z.array(SnowflakeType).optional().describe('Author user IDs to exclude'),
|
||||
|
||||
mentions: z.array(SnowflakeType).optional().describe('User IDs that must be mentioned'),
|
||||
exclude_mentions: z.array(SnowflakeType).optional().describe('User IDs that must not be mentioned'),
|
||||
mention_everyone: z.boolean().optional().describe('Filter by whether message mentions everyone'),
|
||||
|
||||
pinned: z.boolean().optional().describe('Filter by pinned status'),
|
||||
|
||||
has: z.array(MessageContentType).optional().describe('Content types the message must have'),
|
||||
exclude_has: z.array(MessageContentType).optional().describe('Content types the message must not have'),
|
||||
|
||||
embed_type: z.array(MessageEmbedType).optional().describe('Embed types to filter by'),
|
||||
exclude_embed_type: z.array(MessageEmbedType).optional().describe('Embed types to exclude'),
|
||||
embed_provider: z.array(createStringType(1, 256)).optional().describe('Embed providers to filter by'),
|
||||
exclude_embed_provider: z.array(createStringType(1, 256)).optional().describe('Embed providers to exclude'),
|
||||
|
||||
link_hostname: z.array(createStringType(1, 255)).optional().describe('Link hostnames to filter by'),
|
||||
exclude_link_hostname: z.array(createStringType(1, 255)).optional().describe('Link hostnames to exclude'),
|
||||
|
||||
attachment_filename: z.array(createStringType(1, 1024)).optional().describe('Attachment filenames to filter by'),
|
||||
exclude_attachment_filename: z
|
||||
.array(createStringType(1, 1024))
|
||||
.optional()
|
||||
.describe('Attachment filenames to exclude'),
|
||||
attachment_extension: z.array(createStringType(1, 32)).optional().describe('File extensions to filter by'),
|
||||
exclude_attachment_extension: z.array(createStringType(1, 32)).optional().describe('File extensions to exclude'),
|
||||
|
||||
sort_by: MessageSortField.default('timestamp').describe('Field to sort results by'),
|
||||
sort_order: MessageSortOrder.default('desc').describe('Sort order for results'),
|
||||
|
||||
include_nsfw: z.boolean().default(false).describe('Whether to include NSFW channel results'),
|
||||
scope: MessageSearchScope.optional().describe('Scope to search within when querying messages'),
|
||||
});
|
||||
|
||||
export type MessageSearchRequest = z.infer<typeof MessageSearchRequest>;
|
||||
|
||||
export const GlobalSearchMessagesRequest = MessageSearchRequest.extend({
|
||||
context_channel_id: SnowflakeType.optional().describe(
|
||||
'Channel ID for context when searching across multiple channels',
|
||||
),
|
||||
context_guild_id: SnowflakeType.optional().describe('Guild ID for context when searching across multiple guilds'),
|
||||
channel_ids: z.array(SnowflakeType).max(500).optional().describe('Specific channel IDs to search in'),
|
||||
});
|
||||
|
||||
export type GlobalSearchMessagesRequest = z.infer<typeof GlobalSearchMessagesRequest>;
|
||||
|
||||
export const MessageRequestSchema = z
|
||||
.object({
|
||||
content: createUnboundedStringType().nullish().describe('The message content (up to 2000 characters)'),
|
||||
embeds: z.array(RichEmbedRequest).describe('Array of embed objects to include in the message'),
|
||||
attachments: z.array(ClientAttachmentRequest).describe('Array of attachment objects'),
|
||||
message_reference: MessageReferenceRequest.nullish().describe(
|
||||
'Reference to another message (for replies or forwards)',
|
||||
),
|
||||
allowed_mentions: AllowedMentionsRequest.nullish().describe('Controls which mentions trigger notifications'),
|
||||
flags: createBitflagInt32Type(
|
||||
MessageFlags,
|
||||
MessageFlagsDescriptions,
|
||||
'Message flags bitfield',
|
||||
'MessageFlags',
|
||||
).default(0),
|
||||
nonce: createStringType(1, 32).describe('Client-generated identifier for the message'),
|
||||
favorite_meme_id: SnowflakeType.nullish().describe('ID of a favorite meme to attach'),
|
||||
sticker_ids: z.array(SnowflakeType).max(3).nullish().describe('Array of sticker IDs to include (max 3)'),
|
||||
tts: z.boolean().optional().describe('Whether this is a text-to-speech message'),
|
||||
})
|
||||
.partial();
|
||||
|
||||
export type MessageRequestSchemaType = z.infer<typeof MessageRequestSchema>;
|
||||
|
||||
export const MessageUpdateRequestSchema = MessageRequestSchema.pick({
|
||||
content: true,
|
||||
embeds: true,
|
||||
allowed_mentions: true,
|
||||
}).extend({
|
||||
flags: createBitflagInt32Type(
|
||||
MessageFlags,
|
||||
MessageFlagsDescriptions,
|
||||
'Message flags bitfield',
|
||||
'MessageFlags',
|
||||
).optional(),
|
||||
attachments: z
|
||||
.array(ClientAttachmentReferenceRequest)
|
||||
.optional()
|
||||
.describe('Array of attachment objects to keep or add'),
|
||||
});
|
||||
|
||||
export type MessageUpdateRequestSchemaType = z.infer<typeof MessageUpdateRequestSchema>;
|
||||
|
||||
export const MessagesQuery = z.object({
|
||||
limit: createQueryIntegerType({defaultValue: 50, minValue: 1, maxValue: 100}).describe(
|
||||
'Number of messages to return (1-100, default 50)',
|
||||
),
|
||||
before: SnowflakeType.optional().describe('Get messages before this message ID'),
|
||||
after: SnowflakeType.optional().describe('Get messages after this message ID'),
|
||||
around: SnowflakeType.optional().describe('Get messages around this message ID'),
|
||||
});
|
||||
|
||||
export type MessagesQuery = z.infer<typeof MessagesQuery>;
|
||||
|
||||
export const BulkDeleteMessagesRequest = z.object({
|
||||
message_ids: z.array(SnowflakeType).describe('Array of message IDs to delete'),
|
||||
});
|
||||
|
||||
export type BulkDeleteMessagesRequest = z.infer<typeof BulkDeleteMessagesRequest>;
|
||||
|
||||
export const MessageAckRequest = z.object({
|
||||
mention_count: Int32Type.optional().describe('Number of mentions to acknowledge'),
|
||||
manual: z.boolean().optional().describe('Whether this is a manual acknowledgement'),
|
||||
});
|
||||
|
||||
export type MessageAckRequest = z.infer<typeof MessageAckRequest>;
|
||||
|
||||
export const ChannelPinsQuerySchema = z.object({
|
||||
limit: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(50)
|
||||
.optional()
|
||||
.describe('Maximum number of pinned messages to return (1-50)'),
|
||||
before: z.coerce.date().optional().describe('Get pinned messages before this timestamp'),
|
||||
});
|
||||
export type ChannelPinsQuerySchema = z.infer<typeof ChannelPinsQuerySchema>;
|
||||
|
||||
export const ReactionUsersQuerySchema = z.object({
|
||||
limit: z.coerce.number().int().min(1).max(100).optional().describe('Maximum number of users to return (1-100)'),
|
||||
after: SnowflakeType.optional().describe('Get users after this user ID'),
|
||||
});
|
||||
export type ReactionUsersQuerySchema = z.infer<typeof ReactionUsersQuerySchema>;
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
MessageAttachmentFlags,
|
||||
MessageAttachmentFlagsDescriptions,
|
||||
MessageFlags,
|
||||
MessageFlagsDescriptions,
|
||||
} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {MAX_REACTIONS_PER_MESSAGE} from '@fluxer/constants/src/LimitConstants';
|
||||
import type {GuildMemberData} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
|
||||
import {type MessageEmbed, MessageEmbedResponse} from '@fluxer/schema/src/domains/message/EmbedSchemas';
|
||||
import {type UserPartial, UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
|
||||
import {MessageReferenceTypeSchema, MessageTypeSchema} from '@fluxer/schema/src/primitives/MessageValidators';
|
||||
import {createBitflagInt32Type, Int32Type, SnowflakeStringType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {z} from 'zod';
|
||||
|
||||
export const MessageAttachmentResponse = z.object({
|
||||
id: SnowflakeStringType.describe('The unique identifier for this attachment'),
|
||||
filename: z.string().describe('The name of the attached file'),
|
||||
title: z.string().nullish().describe('The title of the attachment'),
|
||||
description: z.string().nullish().describe('The description of the attachment'),
|
||||
content_type: z.string().nullish().describe('The MIME type of the attachment'),
|
||||
content_hash: z.string().nullish().describe('The hash of the attachment content'),
|
||||
size: Int32Type.describe('The size of the attachment in bytes'),
|
||||
url: z.string().nullish().describe('The URL of the attachment'),
|
||||
proxy_url: z.string().nullish().describe('The proxied URL of the attachment'),
|
||||
width: Int32Type.nullish().describe('The width of the attachment in pixels (for images/videos)'),
|
||||
height: Int32Type.nullish().describe('The height of the attachment in pixels (for images/videos)'),
|
||||
placeholder: z.string().nullish().describe('The base64 encoded placeholder image for lazy loading'),
|
||||
flags: createBitflagInt32Type(
|
||||
MessageAttachmentFlags,
|
||||
MessageAttachmentFlagsDescriptions,
|
||||
'The bitwise flags for this attachment',
|
||||
'MessageAttachmentFlags',
|
||||
),
|
||||
nsfw: z.boolean().nullish().describe('Whether the attachment is flagged as NSFW'),
|
||||
duration: Int32Type.nullish().describe('The duration of the media in seconds'),
|
||||
waveform: z.string().nullish().describe('The base64 encoded audio waveform data'),
|
||||
expires_at: z.string().nullish().describe('The ISO 8601 timestamp when the attachment URL expires'),
|
||||
expired: z.boolean().nullish().describe('Whether the attachment URL has expired'),
|
||||
});
|
||||
|
||||
export type MessageAttachmentResponse = z.infer<typeof MessageAttachmentResponse>;
|
||||
|
||||
export const MessageReferenceResponse = z.object({
|
||||
channel_id: SnowflakeStringType.describe('The ID of the channel containing the referenced message'),
|
||||
message_id: SnowflakeStringType.describe('The ID of the referenced message'),
|
||||
guild_id: SnowflakeStringType.nullish().describe('The ID of the guild containing the referenced message'),
|
||||
type: MessageReferenceTypeSchema,
|
||||
});
|
||||
|
||||
export type MessageReferenceResponse = z.infer<typeof MessageReferenceResponse>;
|
||||
|
||||
export const ReactionEmojiResponse = z.object({
|
||||
id: SnowflakeStringType.nullish().describe('The ID of the custom emoji (null for Unicode emojis)'),
|
||||
name: z.string().describe('The name of the emoji (or Unicode character for standard emojis)'),
|
||||
animated: z.boolean().nullish().describe('Whether the emoji is animated'),
|
||||
});
|
||||
|
||||
export type ReactionEmojiResponse = z.infer<typeof ReactionEmojiResponse>;
|
||||
|
||||
export const MessageReactionResponse = z.object({
|
||||
emoji: ReactionEmojiResponse.describe('The emoji used for the reaction'),
|
||||
count: Int32Type.describe('The total number of times this reaction has been used'),
|
||||
me: z.literal(true).nullish().describe('Whether the current user has reacted with this emoji'),
|
||||
});
|
||||
|
||||
export type MessageReactionResponse = z.infer<typeof MessageReactionResponse>;
|
||||
|
||||
export const MessageStickerResponse = z.object({
|
||||
id: SnowflakeStringType.describe('The unique identifier of the sticker'),
|
||||
name: z.string().describe('The name of the sticker'),
|
||||
animated: z.boolean().describe('Whether the sticker is animated'),
|
||||
});
|
||||
|
||||
export type MessageStickerResponse = z.infer<typeof MessageStickerResponse>;
|
||||
|
||||
export const MessageSnapshotResponse = z.object({
|
||||
content: z.string().nullish().describe('The text content of the snapshot'),
|
||||
timestamp: z.iso.datetime().describe('The ISO 8601 timestamp of when the original message was created'),
|
||||
edited_timestamp: z.iso
|
||||
.datetime()
|
||||
.nullish()
|
||||
.describe('The ISO 8601 timestamp of when the original message was last edited'),
|
||||
mentions: z.array(z.string()).max(100).nullish().describe('The user IDs mentioned in the snapshot'),
|
||||
mention_roles: z.array(z.string()).max(100).nullish().describe('The role IDs mentioned in the snapshot'),
|
||||
embeds: z.array(MessageEmbedResponse).max(10).nullish().describe('The embeds included in the snapshot'),
|
||||
attachments: z
|
||||
.array(MessageAttachmentResponse)
|
||||
.max(10)
|
||||
.nullish()
|
||||
.describe('The attachments included in the snapshot'),
|
||||
stickers: z.array(MessageStickerResponse).max(3).nullish().describe('The stickers included in the snapshot'),
|
||||
type: MessageTypeSchema,
|
||||
flags: createBitflagInt32Type(
|
||||
MessageFlags,
|
||||
MessageFlagsDescriptions,
|
||||
'The bitwise flags of the original message',
|
||||
'MessageFlags',
|
||||
),
|
||||
});
|
||||
|
||||
export type MessageSnapshotResponse = z.infer<typeof MessageSnapshotResponse>;
|
||||
|
||||
export const MessageCallResponse = z.object({
|
||||
participants: z.array(z.string()).max(100).describe('The user IDs of participants in the call'),
|
||||
ended_timestamp: z.iso.datetime().nullish().describe('The ISO 8601 timestamp of when the call ended'),
|
||||
});
|
||||
|
||||
export type MessageCallResponse = z.infer<typeof MessageCallResponse>;
|
||||
|
||||
export const MessageBaseResponseSchema = z.object({
|
||||
id: SnowflakeStringType.describe('The unique identifier (snowflake) for this message'),
|
||||
channel_id: SnowflakeStringType.describe('The ID of the channel this message was sent in'),
|
||||
author: z.lazy(() => UserPartialResponse).describe('The author of the message'),
|
||||
webhook_id: SnowflakeStringType.nullish().describe('The ID of the webhook that sent this message'),
|
||||
type: MessageTypeSchema,
|
||||
flags: createBitflagInt32Type(
|
||||
MessageFlags,
|
||||
MessageFlagsDescriptions,
|
||||
'The bitwise flags for this message',
|
||||
'MessageFlags',
|
||||
),
|
||||
content: z.string().describe('The text content of the message'),
|
||||
timestamp: z.iso.datetime().describe('The ISO 8601 timestamp of when the message was created'),
|
||||
edited_timestamp: z.iso.datetime().nullish().describe('The ISO 8601 timestamp of when the message was last edited'),
|
||||
pinned: z.boolean().describe('Whether the message is pinned'),
|
||||
mention_everyone: z.boolean().describe('Whether the message mentions @everyone'),
|
||||
tts: z.boolean().optional().describe('Whether the message was sent as text-to-speech'),
|
||||
mentions: z
|
||||
.array(z.lazy(() => UserPartialResponse))
|
||||
.max(100)
|
||||
.nullish()
|
||||
.describe('The users mentioned in the message'),
|
||||
mention_roles: z.array(z.string()).max(100).nullish().describe('The role IDs mentioned in the message'),
|
||||
embeds: z.array(MessageEmbedResponse).max(10).nullish().describe('The embeds attached to the message'),
|
||||
attachments: z.array(MessageAttachmentResponse).max(10).nullish().describe('The files attached to the message'),
|
||||
stickers: z.array(MessageStickerResponse).max(3).nullish().describe('The stickers sent with the message'),
|
||||
reactions: z
|
||||
.array(MessageReactionResponse)
|
||||
.max(MAX_REACTIONS_PER_MESSAGE)
|
||||
.nullish()
|
||||
.describe('The reactions on the message'),
|
||||
message_reference: MessageReferenceResponse.nullish().describe('Reference data for replies or forwards'),
|
||||
message_snapshots: z.array(MessageSnapshotResponse).max(10).nullish().describe('Snapshots of forwarded messages'),
|
||||
nonce: z.string().nullish().describe('A client-provided value for message deduplication'),
|
||||
call: MessageCallResponse.nullish().describe('Call information if this message represents a call'),
|
||||
});
|
||||
|
||||
export type MessageBaseResponse = z.infer<typeof MessageBaseResponseSchema>;
|
||||
|
||||
export interface MessageResponse extends MessageBaseResponse {
|
||||
referenced_message?: MessageResponse | null;
|
||||
}
|
||||
|
||||
export const MessageResponseSchema = MessageBaseResponseSchema.extend({
|
||||
referenced_message: MessageBaseResponseSchema.nullish().describe(
|
||||
'The message that this message is replying to or forwarding',
|
||||
),
|
||||
});
|
||||
|
||||
export const ChannelPinMessageResponse = MessageResponseSchema.omit({
|
||||
referenced_message: true,
|
||||
reactions: true,
|
||||
}).describe('The pinned message');
|
||||
export type ChannelPinMessageResponse = z.infer<typeof ChannelPinMessageResponse>;
|
||||
|
||||
export const ChannelPinResponse = z.object({
|
||||
message: ChannelPinMessageResponse,
|
||||
pinned_at: z.iso.datetime().describe('The ISO 8601 timestamp of when the message was pinned'),
|
||||
});
|
||||
|
||||
export type ChannelPinResponse = z.infer<typeof ChannelPinResponse>;
|
||||
|
||||
export const ChannelPinsResponse = z.object({
|
||||
items: z.array(ChannelPinResponse).describe('Pinned messages in this channel'),
|
||||
has_more: z.boolean().describe('Whether more pins can be fetched with pagination'),
|
||||
});
|
||||
|
||||
export type ChannelPinsResponse = z.infer<typeof ChannelPinsResponse>;
|
||||
|
||||
export const ReactionUsersListResponse = z.array(z.lazy(() => UserPartialResponse));
|
||||
export type ReactionUsersListResponse = z.infer<typeof ReactionUsersListResponse>;
|
||||
|
||||
export const MessageSearchResultsResponse = z.object({
|
||||
messages: z
|
||||
.array(MessageResponseSchema.omit({referenced_message: true}))
|
||||
.max(100)
|
||||
.describe('The messages matching the search query'),
|
||||
total: Int32Type.describe('The total number of messages matching the search'),
|
||||
hits_per_page: Int32Type.describe('The maximum number of messages returned per page'),
|
||||
page: Int32Type.describe('The current page number'),
|
||||
});
|
||||
|
||||
export type MessageSearchResultsResponse = z.infer<typeof MessageSearchResultsResponse>;
|
||||
|
||||
export const MessageSearchIndexingResponse = z.object({
|
||||
indexing: z.literal(true).describe('Indicates that one or more channels are being indexed'),
|
||||
});
|
||||
|
||||
export type MessageSearchIndexingResponse = z.infer<typeof MessageSearchIndexingResponse>;
|
||||
|
||||
export const MessageSearchResponse = z.union([MessageSearchResultsResponse, MessageSearchIndexingResponse]);
|
||||
|
||||
export type MessageSearchResponse = z.infer<typeof MessageSearchResponse>;
|
||||
|
||||
export const MessageListResponse = z.array(MessageResponseSchema);
|
||||
export type MessageListResponse = z.infer<typeof MessageListResponse>;
|
||||
|
||||
export interface MessageReference {
|
||||
readonly message_id: string;
|
||||
readonly channel_id: string;
|
||||
readonly guild_id?: string;
|
||||
readonly type?: number;
|
||||
}
|
||||
|
||||
export interface ReactionEmoji {
|
||||
readonly id?: string | null;
|
||||
readonly name: string;
|
||||
readonly animated?: boolean;
|
||||
readonly url?: string | null;
|
||||
}
|
||||
|
||||
export interface MessageReaction {
|
||||
readonly emoji: ReactionEmoji;
|
||||
readonly count: number;
|
||||
readonly me?: true;
|
||||
readonly me_burst?: boolean;
|
||||
readonly count_details?: {
|
||||
readonly burst: number;
|
||||
readonly normal: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageAttachment {
|
||||
readonly id: string;
|
||||
readonly filename: string;
|
||||
readonly title?: string;
|
||||
readonly description?: string;
|
||||
readonly content_type?: string;
|
||||
readonly size: number;
|
||||
readonly url: string | null;
|
||||
readonly proxy_url: string | null;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
readonly placeholder?: string;
|
||||
readonly flags: number;
|
||||
readonly duration?: number;
|
||||
readonly waveform?: string;
|
||||
readonly content_hash?: string | null;
|
||||
readonly nsfw?: boolean;
|
||||
readonly expires_at?: string | null;
|
||||
readonly expired?: boolean;
|
||||
}
|
||||
|
||||
export interface MessageCall {
|
||||
readonly participants: ReadonlyArray<string>;
|
||||
readonly ended_timestamp?: string | null;
|
||||
}
|
||||
|
||||
export interface MessageSnapshot {
|
||||
readonly type: number;
|
||||
readonly content: string;
|
||||
readonly embeds?: ReadonlyArray<MessageEmbed>;
|
||||
readonly attachments?: ReadonlyArray<MessageAttachment>;
|
||||
readonly timestamp: string;
|
||||
}
|
||||
|
||||
export interface MessageStickerItem {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly animated: boolean;
|
||||
}
|
||||
|
||||
export interface AllowedMentions {
|
||||
readonly parse?: ReadonlyArray<'roles' | 'users' | 'everyone'>;
|
||||
readonly roles?: ReadonlyArray<string>;
|
||||
readonly users?: ReadonlyArray<string>;
|
||||
readonly replied_user?: boolean;
|
||||
}
|
||||
|
||||
export interface ChannelMention {
|
||||
readonly id: string;
|
||||
readonly guild_id: string;
|
||||
readonly type: number;
|
||||
readonly name: string;
|
||||
readonly parent_id?: string | null;
|
||||
}
|
||||
|
||||
export interface MessageMention extends UserPartial {
|
||||
readonly member?: Omit<GuildMemberData, 'user'>;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
readonly id: string;
|
||||
readonly channel_id: string;
|
||||
readonly guild_id?: string;
|
||||
readonly author: UserPartial;
|
||||
readonly member?: Omit<GuildMemberData, 'user'>;
|
||||
readonly webhook_id?: string;
|
||||
readonly type: number;
|
||||
readonly flags: number;
|
||||
readonly pinned: boolean;
|
||||
readonly tts?: boolean;
|
||||
readonly mention_everyone: boolean;
|
||||
readonly content: string;
|
||||
readonly timestamp: string;
|
||||
readonly edited_timestamp?: string;
|
||||
readonly mentions?: ReadonlyArray<MessageMention>;
|
||||
readonly mention_roles?: ReadonlyArray<string>;
|
||||
readonly mention_channels?: ReadonlyArray<ChannelMention>;
|
||||
readonly embeds?: ReadonlyArray<MessageEmbed>;
|
||||
readonly attachments?: ReadonlyArray<MessageAttachment>;
|
||||
readonly stickers?: ReadonlyArray<MessageStickerItem>;
|
||||
readonly reactions?: ReadonlyArray<MessageReaction>;
|
||||
readonly message_reference?: MessageReference;
|
||||
readonly referenced_message?: Message | null;
|
||||
readonly message_snapshots?: ReadonlyArray<MessageSnapshot>;
|
||||
readonly call?: MessageCall | null;
|
||||
readonly state?: string;
|
||||
readonly nonce?: string;
|
||||
readonly blocked?: boolean;
|
||||
readonly loggingName?: string;
|
||||
readonly _allowedMentions?: AllowedMentions;
|
||||
readonly _favoriteMemeId?: string;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {MessageFlags, MessageFlagsDescriptions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {MessageEmbedResponse} from '@fluxer/schema/src/domains/message/EmbedSchemas';
|
||||
import {
|
||||
MessageAttachmentResponse,
|
||||
MessageStickerResponse,
|
||||
} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
|
||||
import {
|
||||
AllowedMentionParseTypeSchema,
|
||||
MessageReferenceTypeSchema,
|
||||
} from '@fluxer/schema/src/primitives/MessageValidators';
|
||||
import {
|
||||
createBitflagInt32Type,
|
||||
createNamedStringLiteralUnion,
|
||||
SnowflakeStringType,
|
||||
withFieldDescription,
|
||||
withOpenApiType,
|
||||
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {z} from 'zod';
|
||||
|
||||
export const ScheduledMessageAllowedMentionsSchema = z.object({
|
||||
parse: z.array(AllowedMentionParseTypeSchema).optional().describe('Types of mentions to parse from content'),
|
||||
users: z.array(SnowflakeStringType).optional().describe('Array of user IDs to mention'),
|
||||
roles: z.array(SnowflakeStringType).optional().describe('Array of role IDs to mention'),
|
||||
replied_user: z.boolean().optional().describe('Whether to mention the author of the replied message'),
|
||||
});
|
||||
|
||||
export type ScheduledMessageAllowedMentionsSchema = z.infer<typeof ScheduledMessageAllowedMentionsSchema>;
|
||||
|
||||
export const ScheduledMessageReferenceSchema = z.object({
|
||||
message_id: SnowflakeStringType.describe('ID of the message being referenced'),
|
||||
channel_id: SnowflakeStringType.optional().describe('ID of the channel containing the referenced message'),
|
||||
guild_id: SnowflakeStringType.optional().describe('ID of the guild containing the referenced message'),
|
||||
type: withFieldDescription(MessageReferenceTypeSchema, 'The type of message reference').optional(),
|
||||
});
|
||||
|
||||
export type ScheduledMessageReferenceSchema = z.infer<typeof ScheduledMessageReferenceSchema>;
|
||||
|
||||
export const ScheduledMessagePayloadResponseSchema = z.object({
|
||||
content: z.string().nullish().describe('The text content of the scheduled message'),
|
||||
tts: z.boolean().optional().describe('Whether this is a text-to-speech message'),
|
||||
embeds: z.array(MessageEmbedResponse).max(10).optional().describe('Array of embed objects attached to the message'),
|
||||
attachments: z
|
||||
.array(MessageAttachmentResponse)
|
||||
.max(10)
|
||||
.optional()
|
||||
.describe('Array of attachment objects for the message'),
|
||||
stickers: z
|
||||
.array(MessageStickerResponse)
|
||||
.max(3)
|
||||
.optional()
|
||||
.describe('Array of sticker objects attached to the message'),
|
||||
sticker_ids: z
|
||||
.array(SnowflakeStringType)
|
||||
.max(3)
|
||||
.optional()
|
||||
.describe('Array of sticker IDs to include in the message'),
|
||||
allowed_mentions: ScheduledMessageAllowedMentionsSchema.optional().describe(
|
||||
'Controls which mentions trigger notifications',
|
||||
),
|
||||
message_reference: ScheduledMessageReferenceSchema.optional().describe(
|
||||
'Reference to another message (for replies or forwards)',
|
||||
),
|
||||
flags: createBitflagInt32Type(MessageFlags, MessageFlagsDescriptions, 'Message flags', 'MessageFlags').optional(),
|
||||
nonce: z.string().optional().describe('Client-generated identifier for the message'),
|
||||
favorite_meme_id: SnowflakeStringType.optional().describe('ID of a favorite meme to attach'),
|
||||
});
|
||||
|
||||
export type ScheduledMessagePayloadResponseSchema = z.infer<typeof ScheduledMessagePayloadResponseSchema>;
|
||||
|
||||
export const ScheduledMessageStatus = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
['pending', 'pending', 'The message is pending validation and has not yet been scheduled'],
|
||||
['invalid', 'invalid', 'The message failed validation and cannot be sent'],
|
||||
['scheduled', 'scheduled', 'The message has been validated and is scheduled for delivery'],
|
||||
['sent', 'sent', 'The message has been successfully sent'],
|
||||
['failed', 'failed', 'The message failed to send after being scheduled'],
|
||||
['cancelled', 'cancelled', 'The scheduled message was cancelled by the user'],
|
||||
],
|
||||
'The current status of the scheduled message',
|
||||
),
|
||||
'ScheduledMessageStatus',
|
||||
);
|
||||
|
||||
export type ScheduledMessageStatus = z.infer<typeof ScheduledMessageStatus>;
|
||||
|
||||
export const ScheduledMessageResponseSchema = z.object({
|
||||
id: SnowflakeStringType.describe('The unique identifier for this scheduled message'),
|
||||
channel_id: SnowflakeStringType.describe('The ID of the channel this message will be sent to'),
|
||||
scheduled_at: z.string().describe('The ISO 8601 UTC timestamp when the message is scheduled to be sent'),
|
||||
scheduled_local_at: z.string().describe('The ISO 8601 timestamp in the user local timezone'),
|
||||
timezone: z.string().describe('The IANA timezone identifier used for scheduling'),
|
||||
status: ScheduledMessageStatus.describe('The current status of the scheduled message'),
|
||||
status_reason: z.string().nullable().describe('A human-readable reason for the current status, if applicable'),
|
||||
payload: ScheduledMessagePayloadResponseSchema.describe('The message content and metadata to be sent'),
|
||||
created_at: z.string().describe('The ISO 8601 timestamp when this scheduled message was created'),
|
||||
invalidated_at: z.string().nullable().describe('The ISO 8601 timestamp when the message was marked invalid'),
|
||||
});
|
||||
|
||||
export type ScheduledMessageResponseSchema = z.infer<typeof ScheduledMessageResponseSchema>;
|
||||
|
||||
export const ScheduledMessageListResponse = z
|
||||
.array(ScheduledMessageResponseSchema)
|
||||
.max(100)
|
||||
.describe('A list of scheduled messages');
|
||||
export type ScheduledMessageListResponse = z.infer<typeof ScheduledMessageListResponse>;
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {AllowedMentionParseTypes, MessageReferenceTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {
|
||||
AllowedMentionParseTypeSchema,
|
||||
MessageReferenceTypeSchema,
|
||||
} from '@fluxer/schema/src/primitives/MessageValidators';
|
||||
import {SnowflakeType, withFieldDescription} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {z} from 'zod';
|
||||
|
||||
export const ALLOWED_MENTIONS_PARSE = [
|
||||
AllowedMentionParseTypes.USERS,
|
||||
AllowedMentionParseTypes.ROLES,
|
||||
AllowedMentionParseTypes.EVERYONE,
|
||||
] as const;
|
||||
|
||||
export const AllowedMentionsRequest = z.object({
|
||||
parse: z.array(AllowedMentionParseTypeSchema).optional().describe('Types of mentions to parse from content'),
|
||||
users: z.array(SnowflakeType).max(100).optional().describe('Array of user IDs to mention (max 100)'),
|
||||
roles: z.array(SnowflakeType).max(100).optional().describe('Array of role IDs to mention (max 100)'),
|
||||
replied_user: z.boolean().optional().describe('Whether to mention the author of the replied message'),
|
||||
});
|
||||
|
||||
export type AllowedMentionsRequest = z.infer<typeof AllowedMentionsRequest>;
|
||||
|
||||
export const MessageReferenceRequest = z
|
||||
.object({
|
||||
message_id: SnowflakeType.describe('ID of the message being referenced'),
|
||||
channel_id: SnowflakeType.optional().describe('ID of the channel containing the referenced message'),
|
||||
guild_id: SnowflakeType.optional().describe('ID of the guild containing the referenced message'),
|
||||
type: withFieldDescription(MessageReferenceTypeSchema, 'Type of reference (0 = default, 1 = forward)').optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.type === MessageReferenceTypes.FORWARD) {
|
||||
return data.channel_id !== undefined && data.message_id !== undefined;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: 'Forward message reference must include channel_id and message_id',
|
||||
},
|
||||
);
|
||||
|
||||
export type MessageReferenceRequest = z.infer<typeof MessageReferenceRequest>;
|
||||
Reference in new issue
Block a user