@ovencord/formatters 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,827 @@
1
+ import type { Snowflake } from 'discord-api-types/globals';
2
+
3
+ /**
4
+ * Wraps the content inside a code block with no language.
5
+ *
6
+ * @typeParam Content - This is inferred by the supplied content
7
+ * @param content - The content to wrap
8
+ */
9
+ export function codeBlock<Content extends string>(content: Content): `\`\`\`\n${Content}\n\`\`\``;
10
+
11
+ /**
12
+ * Wraps the content inside a code block with the specified language.
13
+ *
14
+ * @typeParam Language - This is inferred by the supplied language
15
+ * @typeParam Content - This is inferred by the supplied content
16
+ * @param language - The language for the code block
17
+ * @param content - The content to wrap
18
+ */
19
+ export function codeBlock<Language extends string, Content extends string>(
20
+ language: Language,
21
+ content: Content,
22
+ ): `\`\`\`${Language}\n${Content}\n\`\`\``;
23
+
24
+ export function codeBlock(language: string, content?: string): string {
25
+ return content === undefined ? `\`\`\`\n${language}\n\`\`\`` : `\`\`\`${language}\n${content}\n\`\`\``;
26
+ }
27
+
28
+ /**
29
+ * Wraps the content inside \`backticks\` which formats it as inline code.
30
+ *
31
+ * @typeParam Content - This is inferred by the supplied content
32
+ * @param content - The content to wrap
33
+ */
34
+ export function inlineCode<Content extends string>(content: Content): `\`${Content}\`` {
35
+ return `\`${content}\``;
36
+ }
37
+
38
+ /**
39
+ * Formats the content into italic text.
40
+ *
41
+ * @typeParam Content - This is inferred by the supplied content
42
+ * @param content - The content to wrap
43
+ */
44
+ export function italic<Content extends string>(content: Content): `_${Content}_` {
45
+ return `_${content}_`;
46
+ }
47
+
48
+ /**
49
+ * Formats the content into bold text.
50
+ *
51
+ * @typeParam Content - This is inferred by the supplied content
52
+ * @param content - The content to wrap
53
+ */
54
+ export function bold<Content extends string>(content: Content): `**${Content}**` {
55
+ return `**${content}**`;
56
+ }
57
+
58
+ /**
59
+ * Formats the content into underlined text.
60
+ *
61
+ * @typeParam Content - This is inferred by the supplied content
62
+ * @param content - The content to wrap
63
+ */
64
+ export function underline<Content extends string>(content: Content): `__${Content}__` {
65
+ return `__${content}__`;
66
+ }
67
+
68
+ /**
69
+ * Formats the content into strike-through text.
70
+ *
71
+ * @typeParam Content - This is inferred by the supplied content
72
+ * @param content - The content to wrap
73
+ */
74
+ export function strikethrough<Content extends string>(content: Content): `~~${Content}~~` {
75
+ return `~~${content}~~`;
76
+ }
77
+
78
+ /**
79
+ * Formats the content into a quote.
80
+ *
81
+ * @remarks This needs to be at the start of the line for Discord to format it.
82
+ * @typeParam Content - This is inferred by the supplied content
83
+ * @param content - The content to wrap
84
+ */
85
+ export function quote<Content extends string>(content: Content): `> ${Content}` {
86
+ return `> ${content}`;
87
+ }
88
+
89
+ /**
90
+ * Formats the content into a block quote.
91
+ *
92
+ * @remarks This needs to be at the start of the line for Discord to format it.
93
+ * @typeParam Content - This is inferred by the supplied content
94
+ * @param content - The content to wrap
95
+ */
96
+ export function blockQuote<Content extends string>(content: Content): `>>> ${Content}` {
97
+ return `>>> ${content}`;
98
+ }
99
+
100
+ /**
101
+ * Wraps the URL into `<>` which stops it from embedding.
102
+ *
103
+ * @typeParam Content - This is inferred by the supplied content
104
+ * @param url - The URL to wrap
105
+ */
106
+ export function hideLinkEmbed<Content extends string>(url: Content): `<${Content}>`;
107
+
108
+ /**
109
+ * Wraps the URL into `<>` which stops it from embedding.
110
+ *
111
+ * @param url - The URL to wrap
112
+ */
113
+ export function hideLinkEmbed(url: URL): `<${string}>`;
114
+
115
+ export function hideLinkEmbed(url: URL | string) {
116
+ return `<${url}>`;
117
+ }
118
+
119
+ /**
120
+ * Formats the content and the URL into a masked URL.
121
+ *
122
+ * @typeParam Content - This is inferred by the supplied content
123
+ * @param content - The content to display
124
+ * @param url - The URL the content links to
125
+ */
126
+ export function hyperlink<Content extends string>(content: Content, url: URL): `[${Content}](${string})`;
127
+
128
+ /**
129
+ * Formats the content and the URL into a masked URL.
130
+ *
131
+ * @typeParam Content - This is inferred by the supplied content
132
+ * @typeParam Url - This is inferred by the supplied URL
133
+ * @param content - The content to display
134
+ * @param url - The URL the content links to
135
+ */
136
+ export function hyperlink<Content extends string, Url extends string>(
137
+ content: Content,
138
+ url: Url,
139
+ ): `[${Content}](${Url})`;
140
+
141
+ /**
142
+ * Formats the content and the URL into a masked URL with a custom tooltip.
143
+ *
144
+ * @typeParam Content - This is inferred by the supplied content
145
+ * @typeParam Title - This is inferred by the supplied title
146
+ * @param content - The content to display
147
+ * @param url - The URL the content links to
148
+ * @param title - The title shown when hovering on the masked link
149
+ */
150
+ export function hyperlink<Content extends string, Title extends string>(
151
+ content: Content,
152
+ url: URL,
153
+ title: Title,
154
+ ): `[${Content}](${string} "${Title}")`;
155
+
156
+ /**
157
+ * Formats the content and the URL into a masked URL with a custom tooltip.
158
+ *
159
+ * @typeParam Content - This is inferred by the supplied content
160
+ * @typeParam Url - This is inferred by the supplied URL
161
+ * @typeParam Title - This is inferred by the supplied title
162
+ * @param content - The content to display
163
+ * @param url - The URL the content links to
164
+ * @param title - The title shown when hovering on the masked link
165
+ */
166
+ export function hyperlink<Content extends string, Url extends string, Title extends string>(
167
+ content: Content,
168
+ url: Url,
169
+ title: Title,
170
+ ): `[${Content}](${Url} "${Title}")`;
171
+
172
+ export function hyperlink(content: string, url: URL | string, title?: string) {
173
+ return title ? `[${content}](${url} "${title}")` : `[${content}](${url})`;
174
+ }
175
+
176
+ /**
177
+ * Formats the content into a spoiler.
178
+ *
179
+ * @typeParam Content - This is inferred by the supplied content
180
+ * @param content - The content to wrap
181
+ */
182
+ export function spoiler<Content extends string>(content: Content): `||${Content}||` {
183
+ return `||${content}||`;
184
+ }
185
+
186
+ /**
187
+ * Formats a user id into a user mention.
188
+ *
189
+ * @typeParam UserId - This is inferred by the supplied user id
190
+ * @param userId - The user id to format
191
+ */
192
+ export function userMention<UserId extends Snowflake>(userId: UserId): `<@${UserId}>` {
193
+ return `<@${userId}>`;
194
+ }
195
+
196
+ /**
197
+ * Formats a channel id into a channel mention.
198
+ *
199
+ * @typeParam ChannelId - This is inferred by the supplied channel id
200
+ * @param channelId - The channel id to format
201
+ */
202
+ export function channelMention<ChannelId extends Snowflake>(channelId: ChannelId): `<#${ChannelId}>` {
203
+ return `<#${channelId}>`;
204
+ }
205
+
206
+ /**
207
+ * Formats a role id into a role mention.
208
+ *
209
+ * @typeParam RoleId - This is inferred by the supplied role id
210
+ * @param roleId - The role id to format
211
+ */
212
+ export function roleMention<RoleId extends Snowflake>(roleId: RoleId): `<@&${RoleId}>` {
213
+ return `<@&${roleId}>`;
214
+ }
215
+
216
+ /**
217
+ * Formats a role id into a linked role mention.
218
+ *
219
+ * @typeParam RoleId - This is inferred by the supplied role id
220
+ * @param roleId - The role id to format
221
+ */
222
+ export function linkedRoleMention<RoleId extends Snowflake>(roleId: RoleId): `<id:linked-roles:${RoleId}>` {
223
+ return `<id:linked-roles:${roleId}>`;
224
+ }
225
+
226
+ /**
227
+ * Formats an application command name and id into an application command mention.
228
+ *
229
+ * @typeParam CommandId - This is inferred by the supplied command id
230
+ * @typeParam CommandName - This is inferred by the supplied command name
231
+ * @param commandId - The application command id to format
232
+ * @param commandName - The application command name to format
233
+ */
234
+ export function chatInputApplicationCommandMention<CommandId extends Snowflake, CommandName extends string>(
235
+ commandId: CommandId,
236
+ commandName: CommandName,
237
+ ): `</${CommandName}:${CommandId}>`;
238
+
239
+ /**
240
+ * Formats an application command name, subcommand name, and id into an application command mention.
241
+ *
242
+ * @typeParam CommandId - This is inferred by the supplied command id
243
+ * @typeParam CommandName - This is inferred by the supplied command name
244
+ * @typeParam SubcommandName - This is inferred by the supplied subcommand name
245
+ * @param commandId - The application command id to format
246
+ * @param commandName - The application command name to format
247
+ * @param subcommandName - The subcommand name to format
248
+ */
249
+ export function chatInputApplicationCommandMention<
250
+ CommandId extends Snowflake,
251
+ CommandName extends string,
252
+ SubcommandName extends string,
253
+ >(
254
+ commandId: CommandId,
255
+ commandName: CommandName,
256
+ subcommandName: SubcommandName,
257
+ ): `</${CommandName} ${SubcommandName}:${CommandId}>`;
258
+
259
+ /**
260
+ * Formats an application command name, subcommand group name, subcommand name, and id into an application command mention.
261
+ *
262
+ * @typeParam CommandId - This is inferred by the supplied command id
263
+ * @typeParam CommandName - This is inferred by the supplied command name
264
+ * @typeParam SubcommandName - This is inferred by the supplied subcommand name
265
+ * @typeParam SubcommandGroupName - This is inferred by the supplied subcommand group name
266
+ * @param commandId - The application command id to format
267
+ * @param commandName - The application command name to format
268
+ * @param subcommandName - The subcommand name to format
269
+ * @param subcommandGroupName - The subcommand group name to format
270
+ */
271
+ export function chatInputApplicationCommandMention<
272
+ CommandId extends Snowflake,
273
+ CommandName extends string,
274
+ SubcommandName extends string,
275
+ SubcommandGroupName extends string,
276
+ >(
277
+ commandId: CommandId,
278
+ commandName: CommandName,
279
+ subcommandName: SubcommandName,
280
+ subcommandGroupName: SubcommandGroupName,
281
+ ): `</${CommandName} ${SubcommandGroupName} ${SubcommandName}:${CommandId}>`;
282
+
283
+ export function chatInputApplicationCommandMention<
284
+ CommandId extends Snowflake,
285
+ CommandName extends string,
286
+ SubcommandName extends string,
287
+ SubcommandGroupName extends string,
288
+ >(
289
+ commandId: CommandId,
290
+ commandName: CommandName,
291
+ subcommandName?: SubcommandName | undefined,
292
+ subcommandGroupName?: SubcommandGroupName | undefined,
293
+ ):
294
+ | `</${CommandName} ${SubcommandGroupName} ${SubcommandName}:${CommandId}>`
295
+ | `</${CommandName} ${SubcommandName}:${CommandId}>`
296
+ | `</${CommandName}:${CommandId}>` {
297
+ if (subcommandGroupName !== undefined && subcommandName !== undefined) {
298
+ return `</${commandName} ${subcommandGroupName} ${subcommandName}:${commandId}>`;
299
+ }
300
+
301
+ if (subcommandName !== undefined) {
302
+ return `</${commandName} ${subcommandName}:${commandId}>`;
303
+ }
304
+
305
+ return `</${commandName}:${commandId}>`;
306
+ }
307
+
308
+ /**
309
+ * Formats a non-animated emoji id into a fully qualified emoji identifier.
310
+ *
311
+ * @typeParam EmojiId - This is inferred by the supplied emoji id
312
+ * @param emojiId - The emoji id to format
313
+ */
314
+ export function formatEmoji<EmojiId extends Snowflake>(emojiId: EmojiId, animated?: false): `<:emoji:${EmojiId}>`;
315
+
316
+ /**
317
+ * Formats an animated emoji id into a fully qualified emoji identifier.
318
+ *
319
+ * @typeParam EmojiId - This is inferred by the supplied emoji id
320
+ * @param emojiId - The emoji id to format
321
+ * @param animated - Whether the emoji is animated
322
+ */
323
+ export function formatEmoji<EmojiId extends Snowflake>(emojiId: EmojiId, animated?: true): `<a:emoji:${EmojiId}>`;
324
+
325
+ /**
326
+ * Formats an emoji id into a fully qualified emoji identifier.
327
+ *
328
+ * @typeParam EmojiId - This is inferred by the supplied emoji id
329
+ * @param emojiId - The emoji id to format
330
+ * @param animated - Whether the emoji is animated
331
+ */
332
+ export function formatEmoji<EmojiId extends Snowflake>(
333
+ emojiId: EmojiId,
334
+ animated?: boolean,
335
+ ): `<:emoji:${EmojiId}>` | `<a:emoji:${EmojiId}>`;
336
+
337
+ /**
338
+ * Formats a non-animated emoji id and name into a fully qualified emoji identifier.
339
+ *
340
+ * @typeParam EmojiId - This is inferred by the supplied emoji id
341
+ * @typeParam EmojiName - This is inferred by the supplied name
342
+ * @param options - The options for formatting an emoji
343
+ */
344
+ export function formatEmoji<EmojiId extends Snowflake, EmojiName extends string>(
345
+ options: FormatEmojiOptions<EmojiId, EmojiName> & { animated: true },
346
+ ): `<a:${EmojiName}:${EmojiId}>`;
347
+
348
+ /**
349
+ * Formats an animated emoji id and name into a fully qualified emoji identifier.
350
+ *
351
+ * @typeParam EmojiId - This is inferred by the supplied emoji id
352
+ * @typeParam EmojiName - This is inferred by the supplied name
353
+ * @param options - The options for formatting an emoji
354
+ */
355
+ export function formatEmoji<EmojiId extends Snowflake, EmojiName extends string>(
356
+ options: FormatEmojiOptions<EmojiId, EmojiName> & { animated?: false },
357
+ ): `<:${EmojiName}:${EmojiId}>`;
358
+
359
+ /**
360
+ * Formats an emoji id and name into a fully qualified emoji identifier.
361
+ *
362
+ * @typeParam EmojiId - This is inferred by the supplied emoji id
363
+ * @typeParam EmojiName - This is inferred by the supplied emoji name
364
+ * @param options - The options for formatting an emoji
365
+ */
366
+ export function formatEmoji<EmojiId extends Snowflake, EmojiName extends string>(
367
+ options: FormatEmojiOptions<EmojiId, EmojiName>,
368
+ ): `<:${EmojiName}:${EmojiId}>` | `<a:${EmojiName}:${EmojiId}>`;
369
+
370
+ export function formatEmoji<EmojiId extends Snowflake, EmojiName extends string>(
371
+ emojiIdOrOptions: EmojiId | FormatEmojiOptions<EmojiId, EmojiName>,
372
+ animated?: boolean,
373
+ ): `<:${string}:${EmojiId}>` | `<a:${string}:${EmojiId}>` {
374
+ const options =
375
+ typeof emojiIdOrOptions === 'string'
376
+ ? {
377
+ id: emojiIdOrOptions,
378
+ animated: animated ?? false,
379
+ }
380
+ : emojiIdOrOptions;
381
+
382
+ const { id, animated: isAnimated, name: emojiName } = options;
383
+
384
+ return `<${isAnimated ? 'a' : ''}:${emojiName ?? 'emoji'}:${id}>`;
385
+ }
386
+
387
+ /**
388
+ * The options for formatting an emoji.
389
+ *
390
+ * @typeParam EmojiId - This is inferred by the supplied emoji id
391
+ * @typeParam EmojiName - This is inferred by the supplied emoji name
392
+ */
393
+ export interface FormatEmojiOptions<EmojiId extends Snowflake, EmojiName extends string> {
394
+ /**
395
+ * Whether the emoji is animated
396
+ */
397
+ animated?: boolean;
398
+ /**
399
+ * The emoji id to format
400
+ */
401
+ id: EmojiId;
402
+ /**
403
+ * The name of the emoji
404
+ */
405
+ name?: EmojiName;
406
+ }
407
+
408
+ /**
409
+ * Formats a channel link for a direct message channel.
410
+ *
411
+ * @typeParam ChannelId - This is inferred by the supplied channel id
412
+ * @param channelId - The channel's id
413
+ */
414
+ export function channelLink<ChannelId extends Snowflake>(
415
+ channelId: ChannelId,
416
+ ): `https://discord.com/channels/@me/${ChannelId}`;
417
+
418
+ /**
419
+ * Formats a channel link for a guild channel.
420
+ *
421
+ * @typeParam ChannelId - This is inferred by the supplied channel id
422
+ * @typeParam GuildId - This is inferred by the supplied guild id
423
+ * @param channelId - The channel's id
424
+ * @param guildId - The guild's id
425
+ */
426
+ export function channelLink<ChannelId extends Snowflake, GuildId extends Snowflake>(
427
+ channelId: ChannelId,
428
+ guildId: GuildId,
429
+ ): `https://discord.com/channels/${GuildId}/${ChannelId}`;
430
+
431
+ export function channelLink<ChannelId extends Snowflake, GuildId extends Snowflake>(
432
+ channelId: ChannelId,
433
+ guildId?: GuildId,
434
+ ): `https://discord.com/channels/@me/${ChannelId}` | `https://discord.com/channels/${GuildId}/${ChannelId}` {
435
+ return `https://discord.com/channels/${guildId ?? '@me'}/${channelId}`;
436
+ }
437
+
438
+ /**
439
+ * Formats a message link for a direct message channel.
440
+ *
441
+ * @typeParam ChannelId - This is inferred by the supplied channel id
442
+ * @typeParam MessageId - This is inferred by the supplied message id
443
+ * @param channelId - The channel's id
444
+ * @param messageId - The message's id
445
+ */
446
+ export function messageLink<ChannelId extends Snowflake, MessageId extends Snowflake>(
447
+ channelId: ChannelId,
448
+ messageId: MessageId,
449
+ ): `https://discord.com/channels/@me/${ChannelId}/${MessageId}`;
450
+
451
+ /**
452
+ * Formats a message link for a guild channel.
453
+ *
454
+ * @typeParam ChannelId - This is inferred by the supplied channel id
455
+ * @typeParam MessageId - This is inferred by the supplied message id
456
+ * @typeParam GuildId - This is inferred by the supplied guild id
457
+ * @param channelId - The channel's id
458
+ * @param messageId - The message's id
459
+ * @param guildId - The guild's id
460
+ */
461
+ export function messageLink<ChannelId extends Snowflake, MessageId extends Snowflake, GuildId extends Snowflake>(
462
+ channelId: ChannelId,
463
+ messageId: MessageId,
464
+ guildId: GuildId,
465
+ ): `https://discord.com/channels/${GuildId}/${ChannelId}/${MessageId}`;
466
+
467
+ export function messageLink<ChannelId extends Snowflake, MessageId extends Snowflake, GuildId extends Snowflake>(
468
+ channelId: ChannelId,
469
+ messageId: MessageId,
470
+ guildId?: GuildId,
471
+ ):
472
+ | `https://discord.com/channels/@me/${ChannelId}/${MessageId}`
473
+ | `https://discord.com/channels/${GuildId}/${ChannelId}/${MessageId}` {
474
+ return `${guildId === undefined ? channelLink(channelId) : channelLink(channelId, guildId)}/${messageId}`;
475
+ }
476
+
477
+ /**
478
+ * The heading levels for expanded markdown.
479
+ */
480
+ export enum HeadingLevel {
481
+ /**
482
+ * The first heading level.
483
+ */
484
+ One = 1,
485
+ /**
486
+ * The second heading level.
487
+ */
488
+ Two,
489
+ /**
490
+ * The third heading level.
491
+ */
492
+ Three,
493
+ }
494
+
495
+ /**
496
+ * Formats the content into a heading level.
497
+ *
498
+ * @typeParam Content - This is inferred by the supplied content
499
+ * @param content - The content to wrap
500
+ * @param level - The heading level
501
+ */
502
+ export function heading<Content extends string>(content: Content, level?: HeadingLevel.One): `# ${Content}`;
503
+
504
+ /**
505
+ * Formats the content into a heading level.
506
+ *
507
+ * @typeParam Content - This is inferred by the supplied content
508
+ * @param content - The content to wrap
509
+ * @param level - The heading level
510
+ */
511
+ export function heading<Content extends string>(content: Content, level: HeadingLevel.Two): `## ${Content}`;
512
+
513
+ /**
514
+ * Formats the content into a heading level.
515
+ *
516
+ * @typeParam Content - This is inferred by the supplied content
517
+ * @param content - The content to wrap
518
+ * @param level - The heading level
519
+ */
520
+ export function heading<Content extends string>(content: Content, level: HeadingLevel.Three): `### ${Content}`;
521
+
522
+ export function heading(content: string, level?: HeadingLevel) {
523
+ // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
524
+ switch (level) {
525
+ case HeadingLevel.Three:
526
+ return `### ${content}`;
527
+ case HeadingLevel.Two:
528
+ return `## ${content}`;
529
+ default:
530
+ return `# ${content}`;
531
+ }
532
+ }
533
+
534
+ /**
535
+ * A type that recursively traverses into arrays.
536
+ */
537
+ export type RecursiveArray<ItemType> = readonly (ItemType | RecursiveArray<ItemType>)[];
538
+
539
+ /**
540
+ * Callback function for list formatters.
541
+ *
542
+ * @internal
543
+ */
544
+ function listCallback(element: RecursiveArray<string>, startNumber?: number, depth = 0): string {
545
+ if (Array.isArray(element)) {
546
+ return element.map((element) => listCallback(element, startNumber, depth + 1)).join('\n');
547
+ }
548
+
549
+ return `${' '.repeat(depth - 1)}${startNumber ? `${startNumber}.` : '-'} ${element}`;
550
+ }
551
+
552
+ /**
553
+ * Formats the elements in the array to an ordered list.
554
+ *
555
+ * @param list - The array of elements to list
556
+ * @param startNumber - The starting number for the list
557
+ */
558
+ export function orderedList(list: RecursiveArray<string>, startNumber = 1): string {
559
+ return listCallback(list, Math.max(startNumber, 1));
560
+ }
561
+
562
+ /**
563
+ * Formats the elements in the array to an unordered list.
564
+ *
565
+ * @param list - The array of elements to list
566
+ */
567
+ export function unorderedList(list: RecursiveArray<string>): string {
568
+ return listCallback(list);
569
+ }
570
+
571
+ /**
572
+ * Formats the content into a subtext.
573
+ *
574
+ * @typeParam Content - This is inferred by the supplied content
575
+ * @param content - The content to wrap
576
+ */
577
+ export function subtext<Content extends string>(content: Content): `-# ${Content}` {
578
+ return `-# ${content}`;
579
+ }
580
+
581
+ /**
582
+ * Formats a date into a short date-time string.
583
+ *
584
+ * @param date - The date to format. Defaults to the current time
585
+ */
586
+ export function time(date?: Date): `<t:${bigint}>`;
587
+
588
+ /**
589
+ * Formats a date given a format style.
590
+ *
591
+ * @typeParam Style - This is inferred by the supplied {@link TimestampStylesString}
592
+ * @param date - The date to format
593
+ * @param style - The style to use
594
+ */
595
+ export function time<Style extends TimestampStylesString>(date: Date, style: Style): `<t:${bigint}:${Style}>`;
596
+
597
+ /**
598
+ * Formats the given timestamp into a short date-time string.
599
+ *
600
+ * @typeParam Seconds - This is inferred by the supplied timestamp
601
+ * @param seconds - A Unix timestamp in seconds
602
+ */
603
+ export function time<Seconds extends number>(seconds: Seconds): `<t:${Seconds}>`;
604
+
605
+ /**
606
+ * Formats the given timestamp into a short date-time string.
607
+ *
608
+ * @typeParam Seconds - This is inferred by the supplied timestamp
609
+ * @typeParam Style - This is inferred by the supplied {@link TimestampStylesString}
610
+ * @param seconds - A Unix timestamp in seconds
611
+ * @param style - The style to use
612
+ */
613
+ export function time<Seconds extends number, Style extends TimestampStylesString>(
614
+ seconds: Seconds,
615
+ style: Style,
616
+ ): `<t:${Seconds}:${Style}>`;
617
+
618
+ export function time(timeOrSeconds?: Date | number, style?: TimestampStylesString): string {
619
+ if (typeof timeOrSeconds !== 'number') {
620
+ // eslint-disable-next-line no-param-reassign
621
+ timeOrSeconds = Math.floor((timeOrSeconds?.getTime() ?? Date.now()) / 1_000);
622
+ }
623
+
624
+ return typeof style === 'string' ? `<t:${timeOrSeconds}:${style}>` : `<t:${timeOrSeconds}>`;
625
+ }
626
+
627
+ /**
628
+ * Formats an application directory link.
629
+ *
630
+ * @typeParam ApplicationId - This is inferred by the supplied application id
631
+ * @param applicationId - The application id
632
+ */
633
+ export function applicationDirectory<ApplicationId extends Snowflake>(
634
+ applicationId: ApplicationId,
635
+ ): `https://discord.com/application-directory/${ApplicationId}/store`;
636
+
637
+ /**
638
+ * Formats an application directory SKU link.
639
+ *
640
+ * @typeParam ApplicationId - This is inferred by the supplied application id
641
+ * @typeParam SKUId - This is inferred by the supplied SKU id
642
+ * @param applicationId - The application id
643
+ * @param skuId - The SKU id
644
+ */
645
+ export function applicationDirectory<ApplicationId extends Snowflake, SKUId extends Snowflake>(
646
+ applicationId: ApplicationId,
647
+ skuId: SKUId,
648
+ ): `https://discord.com/application-directory/${ApplicationId}/store/${SKUId}`;
649
+
650
+ export function applicationDirectory<ApplicationId extends Snowflake, SKUId extends Snowflake>(
651
+ applicationId: ApplicationId,
652
+ skuId?: SKUId,
653
+ ):
654
+ | `https://discord.com/application-directory/${ApplicationId}/store/${SKUId}`
655
+ | `https://discord.com/application-directory/${ApplicationId}/store` {
656
+ const url = `https://discord.com/application-directory/${applicationId}/store` as const;
657
+ return skuId ? `${url}/${skuId}` : url;
658
+ }
659
+
660
+ /**
661
+ * Formats an email address into an email mention.
662
+ *
663
+ * @typeParam Email - This is inferred by the supplied email address
664
+ * @param email - The email address to format
665
+ */
666
+ export function email<Email extends string>(email: Email): `<${Email}>`;
667
+
668
+ /**
669
+ * Formats an email address and headers into an email mention.
670
+ *
671
+ * @typeParam Email - This is inferred by the supplied email address
672
+ * @param email - The email address to format
673
+ * @param headers - Optional headers to include in the email mention
674
+ */
675
+ export function email<Email extends string>(
676
+ email: Email,
677
+ headers: Record<string, string | readonly string[]> | undefined,
678
+ ): `<${Email}?${string}>`;
679
+
680
+ /**
681
+ * Formats an email address into an email mention.
682
+ *
683
+ * @typeParam Email - This is inferred by the supplied email address
684
+ * @param email - The email address to format
685
+ * @param headers - Optional headers to include in the email mention
686
+ */
687
+ export function email<Email extends string>(email: Email, headers?: Record<string, string | readonly string[]>) {
688
+ if (headers) {
689
+ const searchParams = new URLSearchParams(
690
+ Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])),
691
+ );
692
+
693
+ return `<${email}?${searchParams.toString()}>` as const;
694
+ }
695
+
696
+ return `<${email}>` as const;
697
+ }
698
+
699
+ /**
700
+ * Formats a phone number into a phone number mention.
701
+ *
702
+ * @typeParam PhoneNumber - This is inferred by the supplied phone number
703
+ * @param phoneNumber - The phone number to format. Must start with a `+` sign.
704
+ */
705
+ export function phoneNumber<PhoneNumber extends `+${string}`>(phoneNumber: PhoneNumber) {
706
+ if (!phoneNumber.startsWith('+')) {
707
+ throw new Error('Phone number must start with a "+" sign.');
708
+ }
709
+
710
+ return `<${phoneNumber}>` as const;
711
+ }
712
+
713
+ /**
714
+ * The {@link https://discord.com/developers/docs/reference#message-formatting-timestamp-styles | message formatting timestamp styles}
715
+ * supported by Discord.
716
+ */
717
+ export const TimestampStyles = {
718
+ /**
719
+ * Short time format, consisting of hours and minutes.
720
+ *
721
+ * @example `16:20`
722
+ */
723
+ ShortTime: 't',
724
+
725
+ /**
726
+ * Medium time format, consisting of hours, minutes, and seconds.
727
+ *
728
+ * @example `16:20:30`
729
+ */
730
+ MediumTime: 'T',
731
+
732
+ /**
733
+ * Short date format, consisting of day, month, and year.
734
+ *
735
+ * @example `20/04/2021`
736
+ */
737
+ ShortDate: 'd',
738
+
739
+ /**
740
+ * Long date format, consisting of day, month, and year.
741
+ *
742
+ * @example `April 20, 2021`
743
+ */
744
+ LongDate: 'D',
745
+
746
+ /**
747
+ * Long date-short time format, consisting of long date and short time.
748
+ *
749
+ * @example `April 20, 2021 at 16:20`
750
+ */
751
+ LongDateShortTime: 'f',
752
+
753
+ /**
754
+ * Full date-short time format, consisting of full date and short time.
755
+ *
756
+ * @example `Tuesday, April 20, 2021 at 16:20`
757
+ */
758
+ FullDateShortTime: 'F',
759
+
760
+ /**
761
+ * Short date, short time format, consisting of short date and short time.
762
+ *
763
+ * @example `20/04/2021, 16:20`
764
+ */
765
+ ShortDateShortTime: 's',
766
+
767
+ /**
768
+ * Short date, medium time format, consisting of short date and medium time.
769
+ *
770
+ * @example `20/04/2021, 16:20:30`
771
+ */
772
+ ShortDateMediumTime: 'S',
773
+
774
+ /**
775
+ * Relative time format, consisting of a relative duration format.
776
+ *
777
+ * @example `2 months ago`
778
+ */
779
+ RelativeTime: 'R',
780
+ } as const satisfies Record<string, string>;
781
+
782
+ /**
783
+ * The possible {@link TimestampStyles} values.
784
+ */
785
+ export type TimestampStylesString = (typeof TimestampStyles)[keyof typeof TimestampStyles];
786
+
787
+ /**
788
+ * All the available faces from Discord's native slash commands.
789
+ */
790
+ export enum Faces {
791
+ /**
792
+ * `¯\_(ツ)_/¯`
793
+ */
794
+ Shrug = '¯\\_(ツ)_/¯',
795
+
796
+ /**
797
+ * `(╯°□°)╯︵ ┻━┻`
798
+ */
799
+ Tableflip = '(╯°□°)╯︵ ┻━┻',
800
+
801
+ /**
802
+ * `┬─┬ノ( º _ ºノ)`
803
+ */
804
+ Unflip = '┬─┬ノ( º _ ºノ)',
805
+ }
806
+
807
+ /**
808
+ * All the available guild navigation mentions.
809
+ */
810
+ export enum GuildNavigationMentions {
811
+ /**
812
+ * Browse Channels tab.
813
+ */
814
+ Browse = '<id:browse>',
815
+ /**
816
+ * Customize tab with the server's {@link https://discord.com/developers/docs/resources/guild#guild-onboarding-object | onboarding prompts}.
817
+ */
818
+ Customize = '<id:customize>',
819
+ /**
820
+ * {@link https://support.discord.com/hc/articles/13497665141655 | Server Guide} tab.
821
+ */
822
+ Guide = '<id:guide>',
823
+ /**
824
+ * {@link https://support.discord.com/hc/articles/10388356626711 | Linked Roles} tab.
825
+ */
826
+ LinkedRoles = '<id:linked-roles>',
827
+ }