diff --git a/src/lib/util/util.ts b/src/lib/util/util.ts index 8ab2d541f0b..e7727236d19 100644 --- a/src/lib/util/util.ts +++ b/src/lib/util/util.ts @@ -98,14 +98,6 @@ export function getAttachment(message: Message): ImageAttachment | null { } for (const embed of message.embeds) { - if (embed.type === 'image') { - return { - url: embed.thumbnail!.url, - proxyURL: embed.thumbnail!.proxyURL!, - height: embed.thumbnail!.height!, - width: embed.thumbnail!.width! - }; - } if (embed.image) { return { url: embed.image.url, @@ -114,6 +106,15 @@ export function getAttachment(message: Message): ImageAttachment | null { width: embed.image.width! }; } + + if (embed.thumbnail) { + return { + url: embed.thumbnail.url, + proxyURL: embed.thumbnail.proxyURL!, + height: embed.thumbnail.height!, + width: embed.thumbnail.width! + }; + } } return null; diff --git a/tests/lib/util.test.ts b/tests/lib/util.test.ts index 6b5b155a0d2..730cd3567ae 100644 --- a/tests/lib/util.test.ts +++ b/tests/lib/util.test.ts @@ -4,8 +4,6 @@ import { Collection } from '@discordjs/collection'; import type { DeepPartial } from '@sapphire/utilities'; import { Message, MessageAttachment, MessageEmbed } from 'discord.js'; import { mockRandom, resetMockRandom } from 'jest-mock-random'; -import { readFile } from 'node:fs/promises'; -import { resolve } from 'node:path'; describe('Utils', () => { describe('IMAGE_EXTENSION', () => { @@ -41,7 +39,7 @@ describe('Utils', () => { }); test('GIVEN negative rational number THEN returns level 0 (😪)', () => { - expect(utils.oneToTen(2 / 3)).toStrictEqual({ color: 5968128, emoji: '😪' }); + expect(utils.oneToTen(-2 / 3)).toStrictEqual({ color: 5968128, emoji: '😪' }); }); test('GIVEN positive integer number THEN returns level 2 (😫)', () => { @@ -200,117 +198,92 @@ describe('Utils', () => { }); describe('getImage', () => { - test('GIVEN message w/ attachments w/ image w/o proxyURL attachment THEN returns url', async () => { - const filePath = resolve(__dirname, '..', 'mocks', 'image.png'); - const buffer = await readFile(filePath); - const fakeAttachment = new MessageAttachment(buffer, 'image.png'); - fakeAttachment.url = filePath; - fakeAttachment.height = 32; - fakeAttachment.width = 32; - - const fakeMessage: DeepPartial = { - attachments: new Collection([['image.png', fakeAttachment]]), - embeds: [] - }; - + const _Query = new URLSearchParams({ + ex: '651c15b6', + is: '651ac436', + hm: 'b0227f7dce067d2f83880cd01f59a5856885af9204940f8c666dd81f257796c6' + }).toString(); + + function createAttachment(name: 'image.png' | 'text.txt'): MessageAttachment { + const Data = + name === 'image.png' + ? { + id: '1111111111111111111', + filename: 'image.png', + content_type: 'image/png', + url: `https://cdn.discordapp.com/attachments/111111111111111111/111111111111111111/image.png?${_Query}&`, + proxy_url: `https://media.discordapp.net/attachments/111111111111111111/111111111111111111/image.png?${_Query}&`, + size: 2463, + width: 32, + height: 32 + } + : { + id: '1111111111111111111', + filename: 'text.txt', + content_type: 'text/plain; charset=utf-8', + url: `https://cdn.discordapp.com/attachments/111111111111111111/111111111111111111/text.txt?${_Query}&`, + proxy_url: `https://media.discordapp.net/attachments/111111111111111111/111111111111111111/text.txt?${_Query}&`, + size: 4 + }; + return new MessageAttachment(Data.url, Data.filename, Data); + } + + function createAttachments(attachment: MessageAttachment | null) { + const collection = new Collection(); + if (attachment) collection.set(attachment.id, attachment); + return collection; + } + + function createEmbed(name: 'image' | 'thumbnail'): MessageEmbed { + return new MessageEmbed({ + [name]: { + url: `https://cdn.discordapp.com/attachments/222222222222222222/222222222222222222/image.png?${_Query}&`, + proxy_url: `https://media.discordapp.net/attachments/222222222222222222/222222222222222222/image.png?${_Query}&`, + width: 32, + height: 32 + } + } as const); + } + + function getImage(message: DeepPartial) { // @ts-expect-error We're only passing partial data to not mock an entire message - expect(utils.getImage(fakeMessage)).toEqual(filePath); - }); + return utils.getImage(message); + } - test('GIVEN message w/ attachments w/ image w/ proxyURL attachment THEN returns url', async () => { - const filePath = resolve(__dirname, '..', 'mocks', 'image.png'); - const buffer = await readFile(filePath); - const fakeAttachment = new MessageAttachment(buffer, 'image.png'); - fakeAttachment.url = filePath; - fakeAttachment.proxyURL = filePath; - fakeAttachment.height = 32; - fakeAttachment.width = 32; - - const fakeMessage: DeepPartial = { - attachments: new Collection([['image.png', fakeAttachment]]), + test('GIVEN message WITH image attachment THEN returns url', () => { + const attachment = createAttachment('image.png'); + const message: DeepPartial = { + attachments: createAttachments(attachment), embeds: [] }; - // @ts-expect-error We're only passing partial data to not mock an entire message - expect(utils.getImage(fakeMessage)).toEqual(filePath); - }); - - test('GIVEN message w/ attachments w/o image attachment THEN passes through to embed checking', async () => { - const filePath = resolve(__dirname, '..', 'mocks', 'image.png'); - const buffer = await readFile(filePath); - const fakeAttachment = new MessageAttachment(buffer, 'image.png'); - fakeAttachment.url = 'not_an_image'; - fakeAttachment.proxyURL = 'not_an_image'; - fakeAttachment.height = 32; - fakeAttachment.width = 32; - - const fakeMessage: DeepPartial = { - attachments: new Collection([['image.png', fakeAttachment]]), - embeds: [ - { - type: 'image', - thumbnail: { url: 'image.png', proxyURL: 'image.png', height: 32, width: 32 } - } - ] - }; - - // @ts-expect-error We're only passing partial data to not mock an entire message - expect(utils.getImage(fakeMessage)).toEqual('image.png'); - }); - - test('GIVEN message w/o attachments w/ embed type === image THEN returns embedded image url', () => { - const fakeMessage: DeepPartial = { - attachments: new Collection(), - embeds: [ - { - type: 'image', - thumbnail: { url: 'image.png', proxyURL: 'image.png', height: 32, width: 32 } - } - ] - }; + expect(getImage(message)).toEqual(attachment.proxyURL); + }); - // @ts-expect-error We're only passing partial data to not mock an entire message - expect(utils.getImage(fakeMessage)).toEqual('image.png'); - }); - - test('GIVEN message w/o attachments w/ embed w/ image THEN returns embedded image url', () => { - const fakeMessage: DeepPartial = { - attachments: new Collection(), - embeds: [ - { - type: 'not_image', - image: { url: 'image.png', proxyURL: 'image.png', height: 32, width: 32 } - } - ] + test.each([ + { attachment: null, description: 'no' }, + { attachment: createAttachment('text.txt'), description: 'non-image' } + ])('GIVEN message WITH $description attachment AND image embed THEN returns embed image url', ({ attachment }) => { + const embed = createEmbed('image'); + const message: DeepPartial = { + attachments: createAttachments(attachment), + embeds: [embed] }; - // @ts-expect-error We're only passing partial data to not mock an entire message - expect(utils.getImage(fakeMessage)).toEqual('image.png'); - }); - - test('GIVEN message w/o attachments w/ embed w/o image THEN returns null', () => { - const fakeMessage: DeepPartial = { - attachments: new Collection(), - embeds: [ - { - type: 'not_image', - image: undefined - } - ] - }; - - // @ts-expect-error We're only passing partial data to not mock an entire message - expect(utils.getImage(fakeMessage)).toBeNull(); + expect(getImage(message)).toEqual(embed.image!.proxyURL); }); - test('GIVEN message w/o attachments w/o embed THEN returns null', () => { - const fakeMessage: DeepPartial = { - attachments: new Collection(), - embeds: [] + test.each([ + { attachment: null, description: 'no' }, + { attachment: createAttachment('text.txt'), description: 'non-image' } + ])('GIVEN message WITH $description attachment AND thumbnail embed THEN returns embed thumbnail url', ({ attachment }) => { + const embed = createEmbed('thumbnail'); + const message: DeepPartial = { + attachments: createAttachments(attachment), + embeds: [embed] }; - // @ts-expect-error We're only passing partial data to not mock an entire message - expect(utils.getImage(fakeMessage)).toBeNull(); + expect(getImage(message)).toEqual(embed.thumbnail!.proxyURL); }); }); diff --git a/tests/mocks/MockInstances.ts b/tests/mocks/MockInstances.ts index 0db689a3e6d..c1a4c4ac314 100644 --- a/tests/mocks/MockInstances.ts +++ b/tests/mocks/MockInstances.ts @@ -2,7 +2,17 @@ import { LanguageKeys } from '#lib/i18n/languageKeys'; import { SkyraCommand } from '#lib/structures'; import { CLIENT_OPTIONS } from '#root/config'; import { SapphireClient } from '@sapphire/framework'; -import { APIChannel, APIGuild, APIGuildMember, APIRole, APIUser, ChannelType, GuildFeature, GuildNSFWLevel } from 'discord-api-types/v9'; +import { + APIChannel, + APIGuild, + APIGuildMember, + APIRole, + APIUser, + ChannelType, + GuildFeature, + GuildNSFWLevel, + GuildSystemChannelFlags +} from 'discord-api-types/v9'; import { Guild, GuildMember, Role, TextChannel, User } from 'discord.js'; import { resolve } from 'node:path'; @@ -16,7 +26,7 @@ export const userData: APIUser = { }; export function createUser(data: Partial = {}) { - return new User(client, { ...userData, ...data }); + return Reflect.construct(User, [client, { ...userData, ...data }]) as User; } export const guildMemberData: APIGuildMember = { @@ -30,7 +40,11 @@ export const guildMemberData: APIGuildMember = { }; export function createGuildMember(data: Partial = {}, g: Guild = guild) { - return new GuildMember(client, { ...guildMemberData, ...data, user: { ...guildMemberData.user, ...data.user! } }, g); + return Reflect.construct(GuildMember, [ + client, + { ...guildMemberData, ...data, user: { ...guildMemberData.user, ...data.user! } }, + g + ]) as GuildMember; } export const roleData: APIRole = { @@ -45,7 +59,7 @@ export const roleData: APIRole = { }; export function createRole(data: Partial = {}, g: Guild = guild) { - const role = new Role(client, { ...roleData, ...data }, g); + const role = Reflect.construct(Role, [client, { ...roleData, ...data }, g]) as Role; g.roles.cache.set(role.id, role); return role; } @@ -79,14 +93,16 @@ export const guildData: APIGuild = { owner_id: '242043489611808769', preferred_locale: 'en-US', premium_subscription_count: 3, + premium_progress_bar_enabled: false, premium_tier: 1, public_updates_channel_id: '700806874294911067', region: 'eu-central', roles: [roleData], rules_channel_id: '409663610780909569', splash: null, + hub_type: null, stickers: [], - system_channel_flags: 0, + system_channel_flags: GuildSystemChannelFlags.SuppressJoinNotifications, system_channel_id: '254360814063058944', vanity_url_code: null, verification_level: 2, @@ -95,7 +111,7 @@ export const guildData: APIGuild = { }; export function createGuild(data: Partial = {}) { - const g = new Guild(client, { ...guildData, ...data }); + const g = Reflect.construct(Guild, [client, { ...guildData, ...data }]) as Guild; client.guilds.cache.set(g.id, g); return g; } @@ -117,7 +133,7 @@ export const textChannelData: APIChannel = { }; export function createTextChannel(data: Partial = {}, g: Guild = guild) { - const c = new TextChannel(guild, { ...textChannelData, ...data }); + const c = Reflect.construct(TextChannel, [guild, { ...textChannelData, ...data }]) as TextChannel; g.channels.cache.set(c.id, c); g.client.channels.cache.set(c.id, c); return c; @@ -175,8 +191,8 @@ addCommand( root: '/home/skyra/commands' }, { - description: LanguageKeys.Commands.Tools.DefineDescription, - detailedDescription: LanguageKeys.Commands.Tools.DefineExtended, + description: LanguageKeys.Commands.Tools.AvatarDescription, + detailedDescription: LanguageKeys.Commands.Tools.AvatarExtended, aliases: ['def', 'definition', 'defination', 'dictionary'], fullCategory: ['Tools', 'Dictionary'] }