diff --git a/src/lib/util/functions/attachments.ts b/src/lib/util/functions/attachments.ts new file mode 100644 index 00000000000..d2ac649cb5a --- /dev/null +++ b/src/lib/util/functions/attachments.ts @@ -0,0 +1,47 @@ +import { isNullishOrEmpty, isNullishOrZero } from '@sapphire/utilities'; +import type { Attachment } from 'discord.js'; + +/** + * Checks whether an attachment is a media attachment, this is done so by checking the content type of the attachment, + * if the content type starts with `image/`, `video/` or `audio/` it is considered a media attachment. + * + * If the content type is `image/` or `video/`, it will also check if the attachment has dimensions defined to ensure + * it is a valid media attachment. + * + * @param attachment The attachment to check + * @returns Whether the attachment is a media attachment + */ +export function isMediaAttachment(attachment: Attachment): boolean { + if (isNullishOrEmpty(attachment.contentType)) return false; + + // If the attachment is an audio attachment, return true: + if (attachment.contentType.startsWith('audio/')) return true; + + // If the attachment is an image or video attachment, return true if it has dimensions defined: + return attachment.contentType.startsWith('image/') || attachment.contentType.startsWith('video/') // + ? hasDimensionsDefined(attachment) + : false; +} + +/** + * Checks whether an attachment is an image attachment, this is done so by checking the content type of the attachment, + * if the content type starts with `image/` and the attachment has dimensions defined, it is considered an image + * attachment. + * + * @param attachment The attachment to check + * @returns Whether the attachment is an image attachment + */ +export function isImageAttachment(attachment: Attachment): boolean { + return ( + // A content type is required for an image attachment: + !isNullishOrEmpty(attachment.contentType) && // + // An image attachment must have a content type starting with 'image/': + attachment.contentType.startsWith('image/') && + // An image attachment must have dimensions defined: + hasDimensionsDefined(attachment) + ); +} + +function hasDimensionsDefined(attachment: Attachment): boolean { + return !isNullishOrZero(attachment.width) && !isNullishOrZero(attachment.height); +} diff --git a/src/lib/util/functions/index.ts b/src/lib/util/functions/index.ts index 92d7457fc60..d28e2a2f9a6 100644 --- a/src/lib/util/functions/index.ts +++ b/src/lib/util/functions/index.ts @@ -1,3 +1,4 @@ +export * from '#lib/util/functions/attachments'; export * from '#lib/util/functions/channels'; export * from '#lib/util/functions/embeds'; export * from '#lib/util/functions/emojis'; diff --git a/src/lib/util/util.ts b/src/lib/util/util.ts index 24590f8f08b..5d1b53d0686 100644 --- a/src/lib/util/util.ts +++ b/src/lib/util/util.ts @@ -1,12 +1,13 @@ import { LanguageKeys } from '#lib/i18n/languageKeys'; import type { GuildMessage } from '#lib/types'; import { BrandingColors, Urls, ZeroWidthSpace } from '#lib/util/constants'; +import { isImageAttachment } from '#utils/functions'; import { EmbedBuilder } from '@discordjs/builders'; import { container } from '@sapphire/framework'; import { first } from '@sapphire/iterator-utilities/first'; import { send } from '@sapphire/plugin-editable-commands'; import type { TFunction } from '@sapphire/plugin-i18next'; -import { isNullishOrEmpty, isNullishOrZero, tryParseURL, type Nullish } from '@sapphire/utilities'; +import { isNullishOrEmpty, tryParseURL, type Nullish } from '@sapphire/utilities'; import { PermissionFlagsBits, StickerFormatType, @@ -34,14 +35,6 @@ import { */ export const IMAGE_EXTENSION = /\.(bmp|jpe?g|png|gif|webp)$/i; -/** - * Media extensions - * - ...Image extensions - * - ...Audio extensions - * - ...Video extensions - */ -export const MEDIA_EXTENSION = /\.(bmp|jpe?g|png|gifv?|web[pm]|wav|mp[34]|ogg)$/i; - /** * Get the content from a message. * @param message The Message instance to get the content from @@ -55,7 +48,7 @@ export function getContent(message: Message): string | null { return null; } -export interface ImageAttachment { +export interface ResolvedImageAttachment { url: string; proxyURL: string; height: number; @@ -64,14 +57,7 @@ export interface ImageAttachment { export function* getImages(message: Message): IterableIterator { for (const attachment of message.attachments.values()) { - // Skip if the attachment doesn't have a content type: - if (isNullishOrEmpty(attachment.contentType)) continue; - // Skip if the attachment doesn't have a size: - if (isNullishOrZero(attachment.width) || isNullishOrZero(attachment.height)) continue; - // Skip if the attachment isn't an image: - if (!attachment.contentType.startsWith('image/')) continue; - - yield attachment.proxyURL ?? attachment.url; + if (isImageAttachment(attachment)) yield attachment.proxyURL ?? attachment.url; } for (const embed of message.embeds) { diff --git a/src/listeners/messages/guildUserMessageMediaOnly.ts b/src/listeners/messages/guildUserMessageMediaOnly.ts index 227589cbec2..11fe50129d7 100644 --- a/src/listeners/messages/guildUserMessageMediaOnly.ts +++ b/src/listeners/messages/guildUserMessageMediaOnly.ts @@ -1,7 +1,6 @@ import { GuildSettings, readSettings } from '#lib/database'; import { Events, type GuildMessage } from '#lib/types'; -import { deleteMessage, isModerator } from '#utils/functions'; -import { MEDIA_EXTENSION } from '#utils/util'; +import { deleteMessage, isMediaAttachment, isModerator } from '#utils/functions'; import { ApplyOptions } from '@sapphire/decorators'; import { Listener } from '@sapphire/framework'; @@ -32,6 +31,6 @@ export class UserListener extends Listener { } private hasMediaAttachments(message: GuildMessage) { - return message.attachments.some((att) => MEDIA_EXTENSION.test(att.url)); + return message.attachments.some((attachment) => isMediaAttachment(attachment)); } }