diff --git a/packages/formatters/__tests__/formatters.test.ts b/packages/formatters/__tests__/formatters.test.ts index 6286838139b2..20ad29d85e06 100644 --- a/packages/formatters/__tests__/formatters.test.ts +++ b/packages/formatters/__tests__/formatters.test.ts @@ -30,6 +30,8 @@ import { underline, unorderedList, userMention, + email, + phoneNumber, } from '../src/index.js'; describe('Message formatters', () => { @@ -348,6 +350,33 @@ describe('Message formatters', () => { }); }); + describe('email', () => { + test('GIVEN an email THEN returns "<[email]>"', () => { + expect<''>(email('test@example.com')).toEqual(''); + }); + + test('GIVEN an email AND headers THEN returns "<[email]?[headers]>"', () => { + expect<``>(email('test@example.com', { subject: 'Hello', body: 'World' })).toEqual( + '', + ); + }); + }); + + describe('phoneNumber', () => { + test('GIVEN a phone number with + THEN returns "<[phoneNumber]>"', () => { + expect<'<+1234567890>'>(phoneNumber('+1234567890')).toEqual('<+1234567890>'); + }); + + test('GIVEN a phone number without + THEN throws', () => { + expect(() => + phoneNumber( + // @ts-expect-error - Invalid input + '1234567890', + ), + ).toThrowError(); + }); + }); + describe('Faces', () => { test('GIVEN Faces.Shrug THEN returns "¯\\_(ツ)_/¯"', () => { expect<'¯\\_(ツ)_/¯'>(Faces.Shrug).toEqual('¯\\_(ツ)_/¯'); diff --git a/packages/formatters/src/formatters.ts b/packages/formatters/src/formatters.ts index 405854d21bf1..bff6a6474739 100644 --- a/packages/formatters/src/formatters.ts +++ b/packages/formatters/src/formatters.ts @@ -658,6 +658,60 @@ export function applicationDirectory(email: Email): `<${Email}>`; + +/** + * Formats an email address and headers into an email mention. + * + * @typeParam Email - This is inferred by the supplied email address + * @param email - The email address to format + * @param headers - Optional headers to include in the email mention + */ +export function email( + email: Email, + headers: Record | undefined, +): `<${Email}?${string}>`; + +/** + * Formats an email address into an email mention. + * + * @typeParam Email - This is inferred by the supplied email address + * @param email - The email address to format + * @param headers - Optional headers to include in the email mention + */ +export function email(email: Email, headers?: Record) { + if (headers) { + // eslint-disable-next-line n/prefer-global/url-search-params + const searchParams = new URLSearchParams( + Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])), + ); + + return `<${email}?${searchParams.toString()}>` as const; + } + + return `<${email}>` as const; +} + +/** + * Formats a phone number into a phone number mention. + * + * @typeParam PhoneNumber - This is inferred by the supplied phone number + * @param phoneNumber - The phone number to format. Must start with a `+` sign. + */ +export function phoneNumber(phoneNumber: PhoneNumber) { + if (!phoneNumber.startsWith('+')) { + throw new Error('Phone number must start with a "+" sign.'); + } + + return `<${phoneNumber}>` as const; +} + /** * The {@link https://discord.com/developers/docs/reference#message-formatting-timestamp-styles | message formatting timestamp styles} * supported by Discord.