diff --git a/src/api/api.ts b/src/api/api.ts index fd972c3..f976219 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,6 +1,5 @@ import { randomUUID } from 'node:crypto'; -import { Logger, delay, lazyLoadPackage } from '../common'; -import { snowflakeToTimestamp } from '../structures/extra/functions'; +import { Logger, delay, lazyLoadPackage, snowflakeToTimestamp } from '../common'; import type { WorkerData } from '../websocket'; import type { WorkerSendApiRequest } from '../websocket/discord/worker'; import { CDNRouter, type ProxyRequestMethod } from './Router'; diff --git a/src/builders/Button.ts b/src/builders/Button.ts index 4893e7e..8220730 100644 --- a/src/builders/Button.ts +++ b/src/builders/Button.ts @@ -1,6 +1,5 @@ import { type APIMessageComponentEmoji, type ButtonStyle, ComponentType, type APIButtonComponent } from '../types'; -import type { EmojiResolvable } from '../common'; -import { resolvePartialEmoji } from '../structures/extra/functions'; +import { resolvePartialEmoji, type EmojiResolvable } from '../common'; /** * Represents a button component. diff --git a/src/builders/Poll.ts b/src/builders/Poll.ts index 7a19375..a16c379 100644 --- a/src/builders/Poll.ts +++ b/src/builders/Poll.ts @@ -1,6 +1,5 @@ import { type APIPollMedia, PollLayoutType, type RESTAPIPollCreate } from '../types'; -import type { DeepPartial, EmojiResolvable, RestOrArray } from '../common'; -import { resolvePartialEmoji } from '../structures/extra/functions'; +import { resolvePartialEmoji, type DeepPartial, type EmojiResolvable, type RestOrArray } from '../common'; export class PollBuilder { constructor(public data: DeepPartial = {}) { diff --git a/src/builders/SelectMenu.ts b/src/builders/SelectMenu.ts index 99dfa2a..1b0961e 100644 --- a/src/builders/SelectMenu.ts +++ b/src/builders/SelectMenu.ts @@ -21,7 +21,7 @@ import type { StringSelectMenuInteraction, UserSelectMenuInteraction, } from '../structures'; -import { resolvePartialEmoji } from '../structures/extra/functions'; +import { resolvePartialEmoji } from '../common/it/utils'; import { BaseComponentBuilder, type OptionValuesLength } from './Base'; export type BuilderSelectMenus = diff --git a/src/common/it/formatter.ts b/src/common/it/formatter.ts index 9424f56..ea5f9de 100644 --- a/src/common/it/formatter.ts +++ b/src/common/it/formatter.ts @@ -237,4 +237,14 @@ export class Formatter { static emojiMention(emojiId: string, name: string | null, animated = false): string { return `<${animated ? 'a' : ''}:${name ?? '_'}:${emojiId}>`; } + + /** + * Formats a channel link. + * @param channelId The ID of the channel. + * @param guildId The ID of the guild. Defaults to '@me'. + * @returns The formatted channel link. + */ + static channelLink(channelId: string, guildId?: string) { + return `https://discord.com/channels/${guildId ?? '@me'}/${channelId}`; + } } diff --git a/src/common/it/utils.ts b/src/common/it/utils.ts index ca048e4..94db57f 100644 --- a/src/common/it/utils.ts +++ b/src/common/it/utils.ts @@ -1,6 +1,17 @@ import { promises } from 'node:fs'; import { basename, join } from 'node:path'; -import { EmbedColors, type ColorResolvable, type Logger, type ObjectToLower, type ObjectToSnake } from '..'; +import { + DiscordEpoch, + EmbedColors, + type EmojiResolvable, + type TypeArray, + type ColorResolvable, + type Logger, + type ObjectToLower, + type ObjectToSnake, +} from '..'; +import { type APIPartialEmoji, FormattingPatterns } from '../../types'; +import type { Cache } from '../..'; /** * Resolves the color to a numeric representation. @@ -277,3 +288,64 @@ export function isCloudfareWorker() { //@ts-expect-error return process.platform === 'browser'; } + +/** + * + * Convert a timestamp to a snowflake. + * @param id The timestamp to convert. + * @returns The snowflake. + */ +export function snowflakeToTimestamp(id: string): bigint { + return (BigInt(id) >> 22n) + DiscordEpoch; +} + +export function resolvePartialEmoji(emoji: EmojiResolvable): APIPartialEmoji | undefined { + if (typeof emoji === 'string') { + const groups: Partial | undefined = emoji.match(FormattingPatterns.Emoji)?.groups; + if (groups) { + return { animated: !!groups.animated, name: groups.name!, id: groups.id! }; + } + if (emoji.includes('%')) { + emoji = encodeURIComponent(emoji); + } + if (!(emoji.includes(':') || emoji.match(/\d{17,20}/g))) { + return { name: emoji, id: null }; + } + return; + } + + if (!(emoji.id && emoji.name)) return; + return { id: emoji.id, name: emoji.name, animated: !!emoji.animated }; +} + +export async function resolveEmoji(emoji: EmojiResolvable, cache: Cache): Promise { + const partial = resolvePartialEmoji(emoji); + if (partial) return partial; + + if (typeof emoji === 'string') { + if (!emoji.match(/\d{17,20}/g)) return; + const fromCache = await cache.emojis?.get(emoji); + return fromCache && { animated: fromCache.animated, id: fromCache.id, name: fromCache.name }; + } + + const fromCache = await cache.emojis?.get(emoji.id!); + if (fromCache) return { animated: fromCache.animated, id: fromCache.id, name: fromCache.name }; + return; +} + +export function encodeEmoji(rawEmoji: APIPartialEmoji) { + return rawEmoji.id ? `${rawEmoji.name}:${rawEmoji.id}` : `${rawEmoji.name}`; +} + +export function hasProps>(target: T, props: TypeArray): boolean { + if (Array.isArray(props)) { + return props.every(x => hasProps(target, x)); + } + if (!((props as T[number]) in target)) { + return false; + } + if (typeof target[props] === 'string' && !target[props as T[number]].length) { + return false; + } + return true; +} diff --git a/src/common/shorters/reactions.ts b/src/common/shorters/reactions.ts index ae3af20..c4c46d9 100644 --- a/src/common/shorters/reactions.ts +++ b/src/common/shorters/reactions.ts @@ -1,5 +1,5 @@ import type { RESTGetAPIChannelMessageReactionUsersQuery } from '../../types'; -import { encodeEmoji, resolveEmoji } from '../../structures/extra/functions'; +import { encodeEmoji, resolveEmoji } from '../../common/it/utils'; import type { EmojiResolvable } from '../types/resolvables'; import { BaseShorter } from './base'; import { Transformers, type UserStructure } from '../../client/transformers'; diff --git a/src/structures/Message.ts b/src/structures/Message.ts index d7474e3..e699e29 100644 --- a/src/structures/Message.ts +++ b/src/structures/Message.ts @@ -15,7 +15,7 @@ import type { ActionRowMessageComponents } from '../components'; import { MessageActionRowComponent } from '../components/ActionRow'; import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook'; import { DiscordBase } from './extra/DiscordBase'; -import { messageLink } from './extra/functions'; +import { Formatter } from '../common'; import { Embed } from '..'; import { type PollStructure, @@ -65,7 +65,7 @@ export class BaseMessage extends DiscordBase { } get url() { - return messageLink(this.channelId, this.id, this.guildId); + return Formatter.messageLink(this.guildId!, this.channelId, this.id); } guild(force = false) { diff --git a/src/structures/channels.ts b/src/structures/channels.ts index 128f076..cf3684d 100644 --- a/src/structures/channels.ts +++ b/src/structures/channels.ts @@ -39,7 +39,6 @@ import type { import type { GuildMember } from './GuildMember'; import type { GuildRole } from './GuildRole'; import { DiscordBase } from './extra/DiscordBase'; -import { channelLink } from './extra/functions'; import { Collection, Formatter, type RawFile } from '..'; import { type BaseChannelStructure, @@ -74,7 +73,7 @@ export class BaseChannel extends DiscordBase { } get url() { - return channelLink(this.id, this.guildId); + return Formatter.channelLink(this.id, this.guildId); } setPosition(position: number, reason?: string) { diff --git a/src/structures/extra/DiscordBase.ts b/src/structures/extra/DiscordBase.ts index de3a146..f5bca82 100644 --- a/src/structures/extra/DiscordBase.ts +++ b/src/structures/extra/DiscordBase.ts @@ -1,6 +1,6 @@ import type { UsingClient } from '../../commands'; import { Base } from './Base'; -import { snowflakeToTimestamp } from './functions'; +import { snowflakeToTimestamp } from '../../common/it/utils'; export class DiscordBase = { id: string }> extends Base { id: string; diff --git a/src/structures/extra/functions.ts b/src/structures/extra/functions.ts deleted file mode 100644 index 90f5b18..0000000 --- a/src/structures/extra/functions.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { Cache } from '../../cache'; -import { DiscordEpoch, type EmojiResolvable, type TypeArray } from '../../common'; -import { type APIPartialEmoji, FormattingPatterns } from '../../types'; - -/** * Convert a timestamp to a snowflake. * @param timestamp The timestamp to convert. * @returns The snowflake. */ -export function snowflakeToTimestamp(id: string): bigint { - return (BigInt(id) >> 22n) + DiscordEpoch; -} - -export function channelLink(channelId: string, guildId?: string) { - return `https://discord.com/channels/${guildId ?? '@me'}/${channelId}`; -} - -export function messageLink(channelId: string, messageId: string, guildId?: string) { - return `${channelLink(channelId, guildId)}/${messageId}`; -} - -export function resolvePartialEmoji(emoji: EmojiResolvable): APIPartialEmoji | undefined { - if (typeof emoji === 'string') { - const groups: Partial | undefined = emoji.match(FormattingPatterns.Emoji)?.groups; - if (groups) { - return { animated: !!groups.animated, name: groups.name!, id: groups.id! }; - } - if (emoji.includes('%')) { - emoji = encodeURIComponent(emoji); - } - if (!(emoji.includes(':') || emoji.match(/\d{17,20}/g))) { - return { name: emoji, id: null }; - } - return; - } - - if (!(emoji.id && emoji.name)) return; - return { id: emoji.id, name: emoji.name, animated: !!emoji.animated }; -} - -export async function resolveEmoji(emoji: EmojiResolvable, cache: Cache): Promise { - const partial = resolvePartialEmoji(emoji); - if (partial) return partial; - - if (typeof emoji === 'string') { - if (!emoji.match(/\d{17,20}/g)) return; - const fromCache = await cache.emojis?.get(emoji); - return fromCache && { animated: fromCache.animated, id: fromCache.id, name: fromCache.name }; - } - - const fromCache = await cache.emojis?.get(emoji.id!); - if (fromCache) return { animated: fromCache.animated, id: fromCache.id, name: fromCache.name }; - return; -} - -export function encodeEmoji(rawEmoji: APIPartialEmoji) { - return rawEmoji.id ? `${rawEmoji.name}:${rawEmoji.id}` : `${rawEmoji.name}`; -} - -export function hasProps>(target: T, props: TypeArray): boolean { - if (Array.isArray(props)) { - return props.every(x => hasProps(target, x)); - } - if (!((props as T[number]) in target)) { - return false; - } - if (typeof target[props] === 'string' && !target[props as T[number]].length) { - return false; - } - return true; -}