diff --git a/bun.lockb b/bun.lockb index c84aa05..538e870 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 3d6a191..429c048 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -1,7 +1,7 @@ module.exports = { apps: [{ name: "Yuuko Production", - script: "./src/app.ts", + script: ".", max_memory_restart: "812M", watch: ["commit.hash"], interpreter: "/usr/bin/bun", @@ -10,7 +10,8 @@ module.exports = { }, env_development: { NODE_ENV: "development" - } + }, + interpreter_args: "run start:prod" }, { name: "Yuuko Production API", script: "./src/api.ts", diff --git a/package.json b/package.json index ffaf88a..d1123a4 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "main": "src/app.js", "scripts": { "api": "bun run ./src/api.ts", - "start": "pm2 start ecosystem.config.js --env production", - "dev": "NODE_ENV=development bun run --watch ./src/app.ts", - "start:prod": "NODE_ENV=production bun run ./src/app.ts", + "start": "pm2 start ecosystem.config.cjs --env production", + "dev": "NODE_ENV=development bun run --define self=undefined --watch ./src/app.ts", + "start:prod": "NODE_ENV=production bun run --define self=undefined ./src/app.ts", "test": "echo \"Error: no test specified\" && exit 1", "format": "prettier --write .", "lint": "eslint .", @@ -36,43 +36,40 @@ "bindings" ], "dependencies": { - "@acegoal07/discordjs-pagination": "^1.5.0", - "@discordjs/rest": "^0.3.0", - "@elysiajs/cors": "^1.0.2", - "@libsql/client": "^0.6.0", - "@types/bun": "^1.0.7", - "@types/dotenv-flow": "^3.3.1", - "@types/ms": "^0.7.33", - "@types/node-rsa": "^1.1.2", - "@upstash/redis": "^1.23.4", - "bun-types": "^1.0.5", + "@acegoal07/discordjs-pagination": "^1.5.9", + "@discordjs/rest": "^2.4.0", + "@elysiajs/cors": "^1.1.1", + "@libsql/client": "^0.14.0", + "@types/bun": "^1.1.12", + "@types/dotenv-flow": "^3.3.3", + "@types/ms": "^0.7.34", + "@types/node-rsa": "^1.1.4", + "@upstash/redis": "^1.34.3", + "bun-types": "^1.1.33", "colors": "^1.4.0", - "discord.js": "^14.7.1", - "dotenv-flow": "^3.2.0", - "drizzle-orm": "^0.30.8", - "drizzle-kit": "^0.20.14", - "elysia": "^1.0.7", - "humanize-duration": "^3.27.0", - "jimp": "^0.22.10", + "discord.js": "^14.16.3", + "dotenv-flow": "^4.1.0", + "drizzle-kit": "^0.26.2", + "drizzle-orm": "^0.35.3", + "elysia": "^1.1.23", + "humanize-duration": "^3.32.1", + "jimp": "^1.6.0", "ms": "^2.1.3", - "node-redis": "^0.1.7", - "node-rsa": "^1.1.1", - "pm2": "^5.3.0", - "redis": "^4.6.10", - "winston": "^3.11.0", - "zod": "^3.22.4" + "redis": "^4.7.0", + "winston": "^3.15.0", + "zod": "^3.23.8" }, "devDependencies": { - "@antfu/eslint-config": "^1.0.0-beta.26", - "@graphql-codegen/cli": "^5.0.2", - "@graphql-codegen/typescript": "^4.0.8", - "@graphql-codegen/typescript-operations": "^4.2.2", - "@graphql-codegen/typescript-resolvers": "^4.2.0", - "@types/humanize-duration": "^3.27.1", - "@types/node": "^20.6.3", - "eslint": "^8.51.0", - "prettier": "^3.0.3", - "prettier-eslint": "^15.0.1", - "typescript": "^5.2.2" + "@antfu/eslint-config": "^3.8.0", + "@graphql-codegen/cli": "^5.0.3", + "@graphql-codegen/typescript": "^4.1.0", + "@graphql-codegen/typescript-operations": "^4.3.0", + "@graphql-codegen/typescript-resolvers": "^4.3.0", + "@types/humanize-duration": "^3.27.4", + "@types/node": "^22.8.1", + "eslint": "^9.13.0", + "prettier": "^3.3.3", + "prettier-eslint": "^16.3.0", + "typescript": "^5.6.3" } } \ No newline at end of file diff --git a/src/commands/activity.ts b/src/commands/activity.ts index a3e4d99..37309cc 100644 --- a/src/commands/activity.ts +++ b/src/commands/activity.ts @@ -2,7 +2,7 @@ import { embedError, graphQLRequest, SeriesTitle, getOptions, buildPagination } import { EmbedBuilder, SlashCommandBuilder } from "discord.js"; import { mwGetUserEntry } from "#middleware/userEntry"; import type { Command } from "#structures/index"; -import type { ActivityReply } from "#graphQL/types"; +import type { ActivityReply, UserQueryVariables } from "#graphQL/types"; const name = "activity"; const usage = "activity "; @@ -20,14 +20,9 @@ export default { .addStringOption((option) => option.setName("user").setDescription("The user to search for").setRequired(false)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; - getOptions; const { user: username } = getOptions<{ user: string | undefined }>(interaction.options, ["user"]); - const vars: Partial<{ - username: string; - userid: number; - }> = { + const vars: UserQueryVariables = { username, userid: interaction.alID, }; @@ -53,86 +48,87 @@ export default { const { data: { Activity: data }, } = await graphQLRequest("Activity", vars, interaction.ALtoken); - if (data) { - const embed = new EmbedBuilder().setTimestamp(data?.createdAt * 1000); - const pageList = []; - - switch (data?.__typename) { - case "ListActivity": - embed.setURL(data?.siteUrl!); - embed.setTitle(`Here's ${data?.user?.name?.toString() || "Unknown Name"}'s most recent activity!`); - embed.setDescription( - `${capitalizeString(data?.status!)} ${data?.progress?.toLowerCase() || ""} ${data?.status!.startsWith("read" || "watched") ? "of" : ""} **[${SeriesTitle(data.media?.title || undefined)}](${data?.media - ?.siteUrl})**`, - ); - embed.setFooter({ text: `${data?.likeCount | 0} ♥ ${data?.replyCount | 0} 💬` }); - if (data.media?.bannerImage) { - embed.setImage(data.media.bannerImage); - } else { - const thumbnail = data?.media?.coverImage?.large || data?.media?.coverImage?.medium; - if (thumbnail) embed.setThumbnail(thumbnail); - } - - pageList.push(embed); - - // I couldn't find a good way to handle the type, so I couldn't extract it to a function - if (data.replies) { - const replyPages = Math.ceil(data.replyCount / 25); - for (let i = 0; i < replyPages; i++) { - const replyEmbed = new EmbedBuilder().setTitle(`Replies to ${data?.user?.name?.toString() || "Unknown Name"}'s activity!`); - - const replies = data.replies.slice(i * 25, i * 25 + 25).map((reply) => { - if (!reply || !reply.user || !reply.text) return; - const replyText = anilistToMarkdown(reply.text, 1024); - const replyName = reply.user.name - return { name: replyName, value: replyText }; - }).filter((reply) => reply !== undefined); + if (!data) { + return void interaction.editReply({ embeds: [embedError(`Couldn't find any data.`, vars)] }); + } - replyEmbed.addFields(replies); - pageList.push(replyEmbed); - } + const embed = new EmbedBuilder().setTimestamp(data?.createdAt * 1000); + const pageList = []; + + switch (data?.__typename) { + case "ListActivity": + embed.setURL(data?.siteUrl!); + embed.setTitle(`Here's ${data?.user?.name?.toString() || "Unknown Name"}'s most recent activity!`); + embed.setDescription( + `${capitalizeString(data?.status!)} ${data?.progress?.toLowerCase() || ""} ${data?.status!.startsWith("read") || data?.status!.startsWith("watched") ? "of" : ""} **[${SeriesTitle(data.media?.title || undefined)}](${data?.media + ?.siteUrl})**`, + ); + embed.setFooter({ text: `${data?.likeCount | 0} ♥ ${data?.replyCount | 0} 💬` }); + if (data.media?.bannerImage) { + embed.setImage(data.media.bannerImage); + } else { + const thumbnail = data?.media?.coverImage?.large || data?.media?.coverImage?.medium; + if (thumbnail) embed.setThumbnail(thumbnail); + } + + pageList.push(embed); + + // I couldn't find a good way to handle the type, so I couldn't extract it to a function + if (data.replies) { + const replyPages = Math.ceil(data.replyCount / 25); + for (let i = 0; i < replyPages; i++) { + const replyEmbed = new EmbedBuilder().setTitle(`Replies to ${data?.user?.name?.toString() || "Unknown Name"}'s activity!`); + + const replies = data.replies.slice(i * 25, i * 25 + 25).map((reply) => { + if (!reply || !reply.user || !reply.text) return; + const replyText = anilistToMarkdown(reply.text, 1024); + const replyName = reply.user.name + + return { name: replyName, value: replyText }; + }).filter((reply) => reply !== undefined); + + replyEmbed.addFields(replies); + pageList.push(replyEmbed); } + } - break; + break; - case "TextActivity": - embed - .setTitle(`Here's ${data?.user?.name?.toString() || "Unknown Name"}'s most recent activity!`) - .setDescription(anilistToMarkdown(data.text, 4096)) - .setThumbnail(data?.user?.avatar?.large!) - .setFooter({ text: `${data?.likeCount | 0} ♥ ${data?.replyCount | 0} 💬` }); + case "TextActivity": + embed + .setTitle(`Here's ${data?.user?.name?.toString() || "Unknown Name"}'s most recent activity!`) + .setDescription(anilistToMarkdown(data.text, 4096)) + .setThumbnail(data?.user?.avatar?.large!) + .setFooter({ text: `${data?.likeCount | 0} ♥ ${data?.replyCount | 0} 💬` }); - pageList.push(embed); + pageList.push(embed); - // I couldn't find a good way to handle the type, so I couldn't extract it to a function - if (data.replies) { - const replyPages = Math.ceil(data.replyCount / 25); - for (let i = 0; i < replyPages; i++) { - const replyEmbed = new EmbedBuilder().setTitle(`Replies to ${data?.user?.name?.toString() || "Unknown Name"}'s activity!`); + // I couldn't find a good way to handle the type, so I couldn't extract it to a function + if (data.replies) { + const replyPages = Math.ceil(data.replyCount / 25); + for (let i = 0; i < replyPages; i++) { + const replyEmbed = new EmbedBuilder().setTitle(`Replies to ${data?.user?.name?.toString() || "Unknown Name"}'s activity!`); - const replies = data.replies.slice(i * 25, i * 25 + 25).map((reply) => { - if (!reply || !reply.user || !reply.text) return; - const replyText = anilistToMarkdown(reply.text, 1024); - const replyName = reply.user.name + const replies = data.replies.slice(i * 25, i * 25 + 25).map((reply) => { + if (!reply || !reply.user || !reply.text) return; + const replyText = anilistToMarkdown(reply.text, 1024); + const replyName = reply.user.name - return { name: replyName, value: replyText }; - }).filter((reply) => reply !== undefined); + return { name: replyName, value: replyText }; + }).filter((reply) => reply !== undefined); - replyEmbed.addFields(replies); - pageList.push(replyEmbed); - } + replyEmbed.addFields(replies); + pageList.push(replyEmbed); } + } - break; + break; - case "MessageActivity": - break; - } - return void buildPagination(interaction, pageList).paginate(); - } else { - return void interaction.editReply({ embeds: [embedError(`Couldn't find any data.`, vars)] }); + case "MessageActivity": + break; } + return void buildPagination(interaction, pageList).paginate(); } catch (e: any) { console.error(e); interaction.editReply({ embeds: [embedError(e, vars)] }); diff --git a/src/commands/airing.ts b/src/commands/airing.ts index 0491450..5cb22bc 100644 --- a/src/commands/airing.ts +++ b/src/commands/airing.ts @@ -1,7 +1,7 @@ import { buildPagination, embedError, footer, graphQLRequest, SeriesTitle, getOptions } from "#utils/index"; import { EmbedBuilder, SlashCommandBuilder, TimestampStyles, time } from "discord.js"; import ms from "ms"; -import { MediaType } from "#graphQL/types"; +import { MediaType, type AiringQueryVariables } from "#graphQL/types"; import type { Command } from "#structures/index"; const name = "airing"; @@ -20,12 +20,7 @@ export default { .addStringOption((option) => option.setName("in").setDescription('Airing *in* (e.g. "1 week")')), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; - const vars: { - dateStart: number; - nextDay: number; - getID: number[] | undefined; - } = { + const vars: AiringQueryVariables = { dateStart: 0, nextDay: 0, getID: undefined @@ -91,44 +86,44 @@ export default { if (!data) return void interaction.reply({ embeds: [embedError("No airing anime found.")] }); const { airingSchedules } = data; - if (data) { - const chunkSize = 5; - const fields = []; - // Sort the airing anime alphabetically by title - if (!airingSchedules) return void interaction.reply({ embeds: [embedError("No airing anime found.")] }); - airingSchedules.sort((a, b) => (a?.timeUntilAiring || 0) - (b?.timeUntilAiring || 0)); + if (!data) { + return void interaction.reply({ + embeds: [embedError("No airing anime found.")], + }); + } - for (let i = 0; i < airingSchedules.length; i += chunkSize) { - fields.push(airingSchedules.slice(i, i + chunkSize)); - } + const chunkSize = 5; + const fields = []; + // Sort the airing anime alphabetically by title + if (!airingSchedules) return void interaction.reply({ embeds: [embedError("No airing anime found.")] }); + airingSchedules.sort((a, b) => (a?.timeUntilAiring || 0) - (b?.timeUntilAiring || 0)); - // ^ Create pages with 5 airing anime per page and then make them into embeds - const pageList: EmbedBuilder[] = []; - fields.forEach((fieldSet, index) => { - const embed = new EmbedBuilder(); - embed.setTitle(`Airing between ${day.toDateString()} to ${nextWeek.toDateString()}`); - embed.setColor("Green"); - embed.setFooter(footer(headers)); - - fieldSet.forEach((field) => { - if (!field) return; - const { media, episode, airingAt } = field; - - embed.addFields({ - name: `${SeriesTitle(media?.title || undefined)}`, - value: `> **[EP - ${episode}]** :airplane: ${(new Date(airingAt * 1000) > new Date() ? `Going to air ` : `Aired`) + time(airingAt, TimestampStyles.RelativeTime)}`, - inline: false, - }); + for (let i = 0; i < airingSchedules.length; i += chunkSize) { + fields.push(airingSchedules.slice(i, i + chunkSize)); + } + + // ^ Create pages with 5 airing anime per page and then make them into embeds + const pageList: EmbedBuilder[] = []; + fields.forEach((fieldSet, index) => { + const embed = new EmbedBuilder(); + embed.setTitle(`Airing between ${day.toDateString()} to ${nextWeek.toDateString()}`); + embed.setColor("Green"); + embed.setFooter(footer(headers)); + + fieldSet.forEach((field) => { + if (!field) return; + const { media, episode, airingAt } = field; + + embed.addFields({ + name: `${SeriesTitle(media?.title || undefined)}`, + value: `> **[EP - ${episode}]** :airplane: ${(new Date(airingAt * 1000) > new Date() ? `Going to air ` : `Aired`) + time(airingAt, TimestampStyles.RelativeTime)}`, + inline: false, }); - pageList.push(embed); }); + pageList.push(embed); + }); - buildPagination(interaction, pageList).paginate(); - } else { - interaction.reply({ - embeds: [embedError("No airing anime found.")], - }); - } + buildPagination(interaction, pageList).paginate(); } catch (e: any) { console.error(e); interaction.reply({ embeds: [embedError(e, vars)] }); diff --git a/src/commands/anime.ts b/src/commands/anime.ts index 2a47d77..5d2b9a5 100644 --- a/src/commands/anime.ts +++ b/src/commands/anime.ts @@ -1,7 +1,7 @@ import { embedError, graphQLRequest, getOptions, handleData, normalize, type CacheEntry } from "#utils/index"; import { SlashCommandBuilder } from "discord.js"; import { redis } from "#caching/redis"; -import type { AnimeQuery } from "#graphQL/types"; +import type { AnimeQuery, AnimeQueryVariables } from "#graphQL/types"; import { mwGetUserEntry } from "#middleware/userEntry"; import type { CommandWithHook } from "#structures/index"; @@ -21,17 +21,13 @@ export default { .addStringOption((option) => option.setName("query").setDescription("The query to search for").setRequired(true)), run: async ({ interaction, client, hook = false, hookdata = null }): Promise => { - if (!interaction.isCommand()) return; const { query } = getOptions<{ query: string }>(interaction.options, ["query"]); let normalizedQuery = ""; if (query) normalizedQuery = normalize(query); let animeIdFound = false; - const vars: Partial<{ - query: string; - aID: number; - }> = {}; + const vars: AnimeQueryVariables = {}; if (!hook) { if (query.length < 3) return void interaction.editReply({ embeds: [embedError(`Please enter a search query of at least 3 characters.`, null, "", false)] }); @@ -78,23 +74,25 @@ export default { data: { Media: data }, headers, } = await graphQLRequest("Anime", vars, interaction.ALtoken); - if (data) { - if (!animeIdFound) redis.set(`_animeId-${normalizedQuery}`, data.id); - const { mediaListEntry, ...redisData } = data; - redis.json.set(`_anime-${redisData.id}`, "$", redisData); - redis.expireAt(`_anime-${redisData.id}`, new Date(Date.now() + 604800000)); - for (const synonym of redisData.synonyms || []) { - if (!synonym) continue; - redis.set(`_animeId-${normalize(synonym)}`, data.id); - } - if (redisData.nextAiringEpisode?.airingAt) { - console.log(`[AnimeCmd] Expiring anime-${redisData.id} at ${redisData.nextAiringEpisode.airingAt}`); - redis.expireAt(`_anime-${data.id}`, redisData.nextAiringEpisode.airingAt); - } - return void await handleData({ media: data, headers: headers }, interaction, "ANIME", hookdata); - } else { + + if (!data) { return void interaction.editReply({ embeds: [embedError(`Couldn't find any data.`, vars)] }); } + + if (!animeIdFound) redis.set(`_animeId-${normalizedQuery}`, data.id); + const { mediaListEntry, ...redisData } = data; + redis.json.set(`_anime-${redisData.id}`, "$", redisData); + redis.expireAt(`_anime-${redisData.id}`, new Date(Date.now() + 604800000)); + for (const synonym of redisData.synonyms || []) { + if (!synonym) continue; + redis.set(`_animeId-${normalize(synonym)}`, data.id); + } + if (redisData.nextAiringEpisode?.airingAt) { + console.log(`[AnimeCmd] Expiring anime-${redisData.id} at ${redisData.nextAiringEpisode.airingAt}`); + redis.expireAt(`_anime-${data.id}`, redisData.nextAiringEpisode.airingAt); + } + return void await handleData({ media: data, headers: headers }, interaction, "ANIME", hookdata); + } catch (e: any) { console.error(e); interaction.editReply({ embeds: [embedError(e, vars)] }); diff --git a/src/commands/auth.ts b/src/commands/auth.ts index c712bec..dd50d13 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -21,7 +21,6 @@ export default { .addSubcommand((subcommand) => subcommand.setName("wipe").setDescription("Unlink your AniList token from the bot.")), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const type = getSubcommand<["token", "help", "wipe"]>(interaction.options); const { token } = getOptions<{ token: string | undefined }>(interaction.options, ["token"]); @@ -33,7 +32,7 @@ export default { embeds: [ { title: `Steps to get your AniList Token.`, - description:`To add you as an user you have to [link your Discord account with Anilist](https://auth.yuuko.dev).`, + description: `To add you as an user you have to [link your Discord account with Anilist](https://auth.yuuko.dev).`, footer: footer(), }, ], diff --git a/src/commands/birthday.ts b/src/commands/birthday.ts index 21686e5..aaad757 100644 --- a/src/commands/birthday.ts +++ b/src/commands/birthday.ts @@ -32,7 +32,6 @@ export default { ), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; if (!interaction.guild) return void interaction.reply({ content: "This command can only be used in a server.", ephemeral: true }); const subcommand = getSubcommand<["user", "list", "set", "wipe"]>(interaction.options) diff --git a/src/commands/character.ts b/src/commands/character.ts index 26468f5..d2927fe 100644 --- a/src/commands/character.ts +++ b/src/commands/character.ts @@ -17,7 +17,6 @@ export default { .addStringOption((option) => option.setName("query").setDescription("The query to search for").setRequired(true)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const { query: charName } = getOptions<{ query: string }>(interaction.options, ["query"]); @@ -35,44 +34,46 @@ export default { .replace(/<[^>]+>/g, "") .replace(/ /g, " ") .replace(/~!|!~/g, "||") /* .replace(/\n\n/g, "\n") */ || "No description available."; - if (data) { - const embedDate = new Date(); - for (let i = 0; i < Math.ceil(description.length / 4093); i++) { - // ^ Fix the description by replacing and converting HTML tags - const charEmbed = new EmbedBuilder() - .setDescription(`${description.substring(i * 4093, (i + 1) * 4093)}...` || "No description available.") - .addFields({ - name: "Character Info: \n", - value: `**Age**: ${data.age || "No age specified"}\n **Gender**: ${data.gender || "No gender specified."}`, - }) - .setURL(data.siteUrl || "https://anilist.co") - .setColor("Green") - .setFooter({ text: `${data.favourites} ♥ ${footer(headers).text}` }) - .setTimestamp(embedDate); - if (data.image?.large) charEmbed.setThumbnail(data.image.large); - if (data.name?.full) charEmbed.setTitle(data.name.full); - // data.description.split("
").forEach(line => titleEmbed.addField(line, "", true)) - // interaction.reply({ embeds: [charEmbed] }); - embeds.push(charEmbed); - if (data.media?.nodes && data.media.nodes.length > 0) { - const medias = []; - for (const media of data.media.nodes) medias.push(`[${SeriesTitle(media?.title || undefined)}](${media?.siteUrl})`); + if (!data) { + return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, charName)] }); + } + + const embedDate = new Date(); + for (let i = 0; i < Math.ceil(description.length / 4093); i++) { + // ^ Fix the description by replacing and converting HTML tags + const charEmbed = new EmbedBuilder() + .setDescription(`${description.substring(i * 4093, (i + 1) * 4093)}...` || "No description available.") + .addFields({ + name: "Character Info: \n", + value: `**Age**: ${data.age || "No age specified"}\n **Gender**: ${data.gender || "No gender specified."}`, + }) + .setURL(data.siteUrl || "https://anilist.co") + .setColor("Green") + .setFooter({ text: `${data.favourites} ♥ ${footer(headers).text}` }) + .setTimestamp(embedDate); - const charMediaEmbed = new EmbedBuilder() - .setTitle("Series character has appeared in") - .setDescription(medias.join("\n")) - .setTimestamp(embedDate) - .setFooter({ text: `${data.media.nodes.length} series ${footer(headers).text}` }); + if (data.image?.large) charEmbed.setThumbnail(data.image.large); + if (data.name?.full) charEmbed.setTitle(data.name.full); + // data.description.split("
").forEach(line => titleEmbed.addField(line, "", true)) + // interaction.reply({ embeds: [charEmbed] }); + embeds.push(charEmbed); + if (data.media?.nodes && data.media.nodes.length > 0) { + const medias = []; + for (const media of data.media.nodes) medias.push(`[${SeriesTitle(media?.title || undefined)}](${media?.siteUrl})`); - embeds.push(charMediaEmbed); - } + const charMediaEmbed = new EmbedBuilder() + .setTitle("Series character has appeared in") + .setDescription(medias.join("\n")) + .setTimestamp(embedDate) + .setFooter({ text: `${data.media.nodes.length} series ${footer(headers).text}` }); + + embeds.push(charMediaEmbed); } - const pagination = buildPagination(interaction, embeds); - pagination.paginate(); - } else { - return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, charName)] }); } + const pagination = buildPagination(interaction, embeds); + pagination.paginate(); + } catch (e: any) { console.error(e); interaction.reply({ embeds: [embedError(e, charName)] }); diff --git a/src/commands/manga.ts b/src/commands/manga.ts index fda5dcc..4630659 100644 --- a/src/commands/manga.ts +++ b/src/commands/manga.ts @@ -21,7 +21,6 @@ export default { .addStringOption((option) => option.setName("query").setDescription("The query to search for").setRequired(true)), run: async ({ interaction, client, hook = false, hookdata = null }): Promise => { - if (!interaction.isCommand()) return; const { query: manga } = getOptions<{ query: string }>(interaction.options, ["query"]); let normalizedQuery = ""; @@ -73,19 +72,20 @@ export default { data: { Media: data }, headers, } = await graphQLRequest("Manga", vars, interaction.ALtoken); - if (data) { - if (!mangaIdFound) redis.set(`_mangaId-${normalizedQuery}`, data.id); - const { mediaListEntry, ...redisData } = data; - redis.json.set(`_manga-${data.id}`, "$", redisData); - redis.expireAt(`_manga-${redisData.id}`, new Date(Date.now() + 604800000)) - for (const synonym of redisData.synonyms || []) { - if (!synonym) continue; - redis.set(`_mangaId-${normalize(synonym)}`, data.id.toString()); - } - return void handleData({ media: data, headers: headers }, interaction, "MANGA", hookdata); - } else { + + if (!data) { return void interaction.editReply({ embeds: [embedError(`Couldn't find any data.`, vars)] }); } + + if (!mangaIdFound) redis.set(`_mangaId-${normalizedQuery}`, data.id); + const { mediaListEntry, ...redisData } = data; + redis.json.set(`_manga-${data.id}`, "$", redisData); + redis.expireAt(`_manga-${redisData.id}`, new Date(Date.now() + 604800000)) + for (const synonym of redisData.synonyms || []) { + if (!synonym) continue; + redis.set(`_mangaId-${normalize(synonym)}`, data.id.toString()); + } + return void handleData({ media: data, headers: headers }, interaction, "MANGA", hookdata); } catch (e: any) { console.error(e); return void interaction.editReply({ embeds: [embedError(e, vars)] }); diff --git a/src/commands/recent.ts b/src/commands/recent.ts index f64b43a..d9119f9 100644 --- a/src/commands/recent.ts +++ b/src/commands/recent.ts @@ -1,9 +1,10 @@ import { SlashCommandBuilder, AttachmentBuilder } from "discord.js"; import { mwGetUserEntry } from "#middleware/userEntry"; -import Jimp from "jimp"; +import { HorizontalAlign, Jimp, loadFont, VerticalAlign } from "jimp"; +import { SANS_16_WHITE } from "jimp/fonts"; import type { Command } from "#structures/index"; import { CommandCategories, embedError, graphQLRequest, SeriesTitle, getOptions } from "#utils/index"; -import type { MediaList, MediaType } from "#graphQL/types"; +import type { MediaList, MediaType, RecentChartQuery, RecentChartQueryVariables } from "#graphQL/types"; const name = "recent"; const usage = "recent"; @@ -22,17 +23,11 @@ export default { .addStringOption((option) => option.setName("user").setDescription("The user to search for").setRequired(false)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const { user: userName } = getOptions<{ user: string }>(interaction.options, ["user"]); const { type } = getOptions<{ type: MediaType }>(interaction.options, ["type"]); - const vars: Partial<{ - perPage: number; - type: MediaType; - userId: number; - user?: string; - }> = { + const vars: RecentChartQueryVariables = { perPage: 9, type: type, }; @@ -56,7 +51,8 @@ export default { } = await graphQLRequest("RecentChart", vars, interaction.ALtoken); if (!data?.mediaList) return void interaction.editReply({ embeds: [embedError("Unable to find specified user", vars)] }); interaction.editReply({ embeds: [{ description: "Creating image..." }] }); - const canvas = new Jimp(999, 999); + const canvas = new Jimp({ width: 999, height: 999 }); + const useFont = await loadFont(SANS_16_WHITE); let x = 0; let y = 0; @@ -68,16 +64,15 @@ export default { const canvasImage = await Jimp.read(cover); const width = 333; - const height = (width / canvasImage.getWidth()) * canvasImage.getHeight(); - canvasImage.resize(width, height); - const infoRectangle = new Jimp(width, 60, "#000000bf"); + const height = (width / canvasImage.width) * canvasImage.height; + canvasImage.resize({ w: width, h: height }); + const infoRectangle = new Jimp({ width, height: 60, color: "#000000bf" }); - const useFont = await Jimp.loadFont(Jimp.FONT_SANS_16_WHITE); const title = SeriesTitle(media.title || undefined); const status = parseStatus(item, type); - if (status) infoRectangle.print(useFont, 0, 0, { text: status, alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER, alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE }, width, 40); - infoRectangle.print(useFont, 0, 20, { text: title, alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER, alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE }, width, 40); + if (status) infoRectangle.print({ maxWidth: width, maxHeight: 40, font: useFont, x: 0, y: 0, text: { text: status, alignmentX: HorizontalAlign.CENTER, alignmentY: VerticalAlign.MIDDLE } }); + infoRectangle.print({ maxWidth: width, maxHeight: 40, font: useFont, x: 0, y: 20, text: { text: title, alignmentX: HorizontalAlign.CENTER, alignmentY: VerticalAlign.MIDDLE } }); canvas.composite(canvasImage, x, y); canvas.composite(infoRectangle, x, y + width - 60); x += width; @@ -87,11 +82,12 @@ export default { } } - const canvasResult = await canvas.getBufferAsync(Jimp.MIME_PNG); + const canvasResult = await canvas.getBuffer("image/png"); if (!canvasResult) return void interaction.editReply({ embeds: [embedError("Encountered an error whilst trying to create the image.", vars)] }); const attachment = new AttachmentBuilder(canvasResult, { name: "recent.png" }); return void interaction.editReply({ files: [attachment], embeds: [] }); } catch (error: any) { + console.error(error); return void interaction.editReply({ embeds: [embedError(error, vars)] }); } }, diff --git a/src/commands/recommend.ts b/src/commands/recommend.ts index cf10dc3..fc67f20 100644 --- a/src/commands/recommend.ts +++ b/src/commands/recommend.ts @@ -22,7 +22,6 @@ export default { .addStringOption((option) => option.setName("genres").setDescription('A comma separated list of genres (e.g. "romance, drama")').setRequired(true)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; interaction.deferReply(); const { type } = getOptions<{ type: MediaType }>(interaction.options, ["type"]); @@ -40,44 +39,45 @@ export default { const { data: { MediaListCollection: data }, } = await graphQLRequest("GetMediaCollection", vars); - if (data && data.lists && data.lists.length > 0) { - // ^ We filter out the Planning list - for (const MediaList of data.lists.filter((MediaList) => MediaList!.name != "Planning")) { - if (MediaList && MediaList.entries) MediaList.entries.map((e) => excludeIDs.push(e!.media!.id)); - } - if (!genres.length) return void interaction.reply({ embeds: [embedError(`Please specify at least one genre.`, null, '', false)] }); - const genresArray = genres.split(",").map((genre) => genre.trim()); - const recommendationVars = { type, exclude_ids: excludeIDs, genresArray }; + if (!data || !data.lists || data.lists.length < 1) { + return void interaction.reply({ embeds: [embedError(`Couldn't find any data from the user specified. (Which was "${vars.userName}")`, null, '', false)] }); + } + + // ^ We filter out the Planning list + for (const MediaList of data.lists.filter((MediaList) => MediaList!.name != "Planning")) { + if (MediaList && MediaList.entries) MediaList.entries.map((e) => excludeIDs.push(e!.media!.id)); + } + if (!genres.length) return void interaction.reply({ embeds: [embedError(`Please specify at least one genre.`, null, '', false)] }); - try { - const { - data: { Page: data }, - } = await graphQLRequest("Recommendations", recommendationVars); - if (data && data.media) { - // ^ Filter out the Planning list - const recommendations = data.media.filter((Media) => Media!.title); - const random = Math.floor(Math.random() * Math.floor(recommendations.length)); - const recommendedSeries = recommendations[random]; - if (!recommendedSeries) return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, recommendationVars)] }); + const genresArray = genres.split(",").map((genre) => genre.trim()); + const recommendationVars = { type, exclude_ids: excludeIDs, genresArray }; - switch (type) { - case "ANIME": - AnimeCmd.run({ interaction, client, hook: true, hookdata: { title: SeriesTitle(recommendedSeries.title || undefined) } }); - break; - case "MANGA": - MangaCmd.run({ interaction, client, hook: true, hookdata: { title: SeriesTitle(recommendedSeries.title || undefined) } }); - break; - } - } else { - return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, recommendationVars)] }); - } - } catch (e: any) { - console.error(e); - interaction.reply({ embeds: [embedError(e, vars)] }); + try { + const { + data: { Page: data }, + } = await graphQLRequest("Recommendations", recommendationVars); + + if (!data || !data.media) { + return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, recommendationVars)] }); } - } else { - return void interaction.reply({ embeds: [embedError(`Couldn't find any data from the user specified. (Which was "${vars.userName}")`, null, '', false)] }); + // ^ Filter out the Planning list + const recommendations = data.media.filter((Media) => Media!.title); + const random = Math.floor(Math.random() * Math.floor(recommendations.length)); + const recommendedSeries = recommendations[random]; + if (!recommendedSeries) return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, recommendationVars)] }); + + switch (type) { + case "ANIME": + AnimeCmd.run({ interaction, client, hook: true, hookdata: { title: SeriesTitle(recommendedSeries.title || undefined) } }); + break; + case "MANGA": + MangaCmd.run({ interaction, client, hook: true, hookdata: { title: SeriesTitle(recommendedSeries.title || undefined) } }); + break; + } + } catch (e: any) { + console.error(e); + interaction.reply({ embeds: [embedError(e, vars)] }); } } catch (e: any) { console.error(e); diff --git a/src/commands/staff.ts b/src/commands/staff.ts index f6d85bb..ee5d2be 100644 --- a/src/commands/staff.ts +++ b/src/commands/staff.ts @@ -17,7 +17,6 @@ export default { .addStringOption((option) => option.setName("query").setDescription("The query to search for").setRequired(true)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const { query: staffName } = getOptions<{ query: string }>(interaction.options, ["query"]); @@ -29,59 +28,60 @@ export default { if (!data) return void interaction.reply({ embeds: [embedError(`Couldn't find this staff member.`, staffName)] }); const staffMedia = data.staffMedia; const characterMedia = data.characterMedia; - if (data) { - // Fix the description by replacing and converting HTML tags - const descLength = 1000; - const description = - data.description - ?.replace(/

/g, "\n") - .replace(/
/g, "\n") - .replace(/<[^>]+>/g, "") - .replace(/ /g, " ") - .replace(/~!|!~/g, "||") /* .replace(/\n\n/g, "\n") */ || "No description available."; - const staffEmbed = new EmbedBuilder() + + if (!data) { + return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, staffName)] }); + } + + // Fix the description by replacing and converting HTML tags + const descLength = 1000; + const description = + data.description + ?.replace(/

/g, "\n") + .replace(/
/g, "\n") + .replace(/<[^>]+>/g, "") + .replace(/ /g, " ") + .replace(/~!|!~/g, "||") /* .replace(/\n\n/g, "\n") */ || "No description available."; + const staffEmbed = new EmbedBuilder() + .setThumbnail(data.image!.large!) + .setTitle(data.name!.full!) + .setDescription(description.length > descLength ? `${description.substring(0, descLength)}...` || "No description available." : description || "No description available.") + .addFields({ name: "Staff Info: \n", value: `**Age**: ${data.age || "No age specified"} **Gender**: ${data.gender || "No gender specified."}\n **Home Town**: ${data.homeTown || "No home town specified."}` }) + .setURL(data.siteUrl || "https://anilist.co") + .setColor("Green") + .setFooter(footer(headers)); + + const pageList = [staffEmbed]; + if (staffMedia?.edges && staffMedia.edges.length > 0) { + const media = staffMedia.edges.map((edge) => { + if (!edge?.node || !edge?.staffRole) return; + return `${edge.staffRole} - [${edge.node.title?.english || edge.node.title?.romaji || edge.node.title?.native}](${edge.node.siteUrl})`; + }); + const mediaEmbed = new EmbedBuilder() .setThumbnail(data.image!.large!) - .setTitle(data.name!.full!) - .setDescription(description.length > descLength ? `${description.substring(0, descLength)}...` || "No description available." : description || "No description available.") - .addFields({ name: "Staff Info: \n", value: `**Age**: ${data.age || "No age specified"} **Gender**: ${data.gender || "No gender specified."}\n **Home Town**: ${data.homeTown || "No home town specified."}` }) + .setTitle(`${data.name!.full}'s Media`) + .setDescription(media.join("\n")) .setURL(data.siteUrl || "https://anilist.co") - .setColor("Green") - .setFooter(footer(headers)); - - const pageList = [staffEmbed]; - if (staffMedia?.edges && staffMedia.edges.length > 0) { - const media = staffMedia.edges.map((edge) => { - if (!edge?.node || !edge?.staffRole) return; - return `${edge.staffRole} - [${edge.node.title?.english || edge.node.title?.romaji || edge.node.title?.native}](${edge.node.siteUrl})`; - }); - const mediaEmbed = new EmbedBuilder() - .setThumbnail(data.image!.large!) - .setTitle(`${data.name!.full}'s Media`) - .setDescription(media.join("\n")) - .setURL(data.siteUrl || "https://anilist.co") - .setColor("Green"); - pageList.push(mediaEmbed); - } - if (characterMedia?.edges && characterMedia.edges.length > 0) { - const media = characterMedia.edges.map((node) => { - if (!node?.node || !node?.characters) return; - const work = node.characters.map((character) => { - return `${character?.name?.full || "Unknown"} - [${SeriesTitle(node.node?.title || undefined)}](${node.node?.siteUrl || "https://anilist.co"})`; - }); - return work; + .setColor("Green"); + pageList.push(mediaEmbed); + } + if (characterMedia?.edges && characterMedia.edges.length > 0) { + const media = characterMedia.edges.map((node) => { + if (!node?.node || !node?.characters) return; + const work = node.characters.map((character) => { + return `${character?.name?.full || "Unknown"} - [${SeriesTitle(node.node?.title || undefined)}](${node.node?.siteUrl || "https://anilist.co"})`; }); - const charEmbed = new EmbedBuilder() - .setThumbnail(data.image!.large!) - .setTitle(`${data.name!.full}'s Characters`) - .setDescription(media.join("\n")) - .setURL(data.siteUrl || "https://anilist.co") - .setColor("Green"); - pageList.push(charEmbed); - } - return void buildPagination(interaction, pageList).paginate(); - } else { - return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, staffName)] }); + return work; + }); + const charEmbed = new EmbedBuilder() + .setThumbnail(data.image!.large!) + .setTitle(`${data.name!.full}'s Characters`) + .setDescription(media.join("\n")) + .setURL(data.siteUrl || "https://anilist.co") + .setColor("Green"); + pageList.push(charEmbed); } + return void buildPagination(interaction, pageList).paginate(); } catch (e: any) { console.error(e); interaction.reply({ embeds: [embedError(e, staffName)] }); diff --git a/src/commands/studio.ts b/src/commands/studio.ts index b00887a..aa916d6 100644 --- a/src/commands/studio.ts +++ b/src/commands/studio.ts @@ -17,7 +17,6 @@ export default { .addStringOption((option) => option.setName("query").setDescription("The query to search for").setRequired(true)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const { query: query } = getOptions<{ query: string }>(interaction.options, ["query"]); try { @@ -25,26 +24,28 @@ export default { data: { Studio: data }, headers, } = await graphQLRequest("Studio", { query }); - if (data) { - let animes: string[] | string = []; - if (!data.media?.nodes) return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, { query })] }); - for (const anime of data.media.nodes) animes = animes.concat(`[${SeriesTitle(anime?.title || undefined)}]` + `(https://anilist.co/anime/${anime!.id})`); - - animes = animes.toString().replaceAll(",", "\n"); - - const studioEmbed = new EmbedBuilder() - // .setThumbnail(data.image.large) - .setTitle(`${data.name} | ${data.favourites} favourites`) - .setDescription(`\n${animes}`) - .setURL(data.siteUrl || "https://anilist.co") - .setColor("Green") - .setFooter(footer(headers)); - - // data.description.split("
").forEach(line => titleEmbed.addField(line, "", true)) - interaction.reply({ embeds: [studioEmbed] }); - } else { + + if (!data) { return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, { query })] }); } + + let animes: string[] | string = []; + if (!data.media?.nodes) return void interaction.reply({ embeds: [embedError(`Couldn't find any data.`, { query })] }); + for (const anime of data.media.nodes) animes = animes.concat(`[${SeriesTitle(anime?.title || undefined)}]` + `(https://anilist.co/anime/${anime!.id})`); + + animes = animes.toString().replaceAll(",", "\n"); + + const studioEmbed = new EmbedBuilder() + // .setThumbnail(data.image.large) + .setTitle(`${data.name} | ${data.favourites} favourites`) + .setDescription(`\n${animes}`) + .setURL(data.siteUrl || "https://anilist.co") + .setColor("Green") + .setFooter(footer(headers)); + + // data.description.split("
").forEach(line => titleEmbed.addField(line, "", true)) + interaction.reply({ embeds: [studioEmbed] }); + } catch (e: any) { console.error(e); interaction.reply({ embeds: [embedError(e, { query })] }); diff --git a/src/commands/synclists.ts b/src/commands/synclists.ts index d87c0df..ccbb21e 100644 --- a/src/commands/synclists.ts +++ b/src/commands/synclists.ts @@ -25,7 +25,6 @@ export default { .addSubcommand((subcommand) => subcommand.setName("sync").setDescription("Sync your lists with our bot.")) .addSubcommand((subcommand) => subcommand.setName("wipe").setDescription("Wipe your lists from our bot. (This will not wipe your AniList lists.)")), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const subcommand = getSubcommand<["sync", "wipe"]>(interaction.options); if (subcommand === "wipe") { @@ -85,7 +84,6 @@ async function handleData( interaction: UsableInteraction, type: MediaType, ) { - if (!interaction.isCommand()) return; if (!interaction.alID) return; const dataToGiveToRedis: Record = {}; diff --git a/src/commands/trace.ts b/src/commands/trace.ts index 1632a4d..bc6e348 100644 --- a/src/commands/trace.ts +++ b/src/commands/trace.ts @@ -36,15 +36,14 @@ export default { .addAttachmentOption((option) => option.setName("image").setDescription("Attach the image of the anime.").setRequired(true)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const image = interaction.options.getAttachment("image"); if (!image) return void interaction.reply({ embeds: [embedError("No image attached.")] }); try { const res = await fetch(`${baseUrl}${image.url}`) - if(!res.ok) return void interaction.reply({ embeds: [embedError(res.statusText)] }); - + if (!res.ok) return void interaction.reply({ embeds: [embedError(res.statusText)] }); + const data = await res.json() as { result: TraceMoeReturnType[] } const response = data.result[0]; if (!response) return void interaction.reply({ embeds: [embedError("No results found.", { url: image.url })] }); diff --git a/src/commands/user.ts b/src/commands/user.ts index 20c3ca9..d564c01 100644 --- a/src/commands/user.ts +++ b/src/commands/user.ts @@ -2,6 +2,7 @@ import { SlashCommandBuilder, EmbedBuilder, type ColorResolvable } from "discord import { mwGetUserEntry } from "#middleware/userEntry"; import type { Command } from "#structures/index"; import { embedError, graphQLRequest, footer, getOptions } from "#utils/index"; +import type { UserQueryVariables } from "#graphQL/types"; const name = "user"; const usage = "user "; @@ -20,14 +21,10 @@ export default { // .setRequired(true)), run: async ({ interaction, client }): Promise => { - if (!interaction.isCommand()) return; const { query: anilistUser } = getOptions<{ query: string }>(interaction.options, ["query"]); - let vars: Partial<{ - username: string; - userid: number; - }> = { + let vars: UserQueryVariables = { username: anilistUser, }; @@ -44,40 +41,42 @@ export default { try { const { data, headers } = await graphQLRequest("User", vars, interaction.ALtoken); const response = data.User; - if (response) { - const titleEmbed = new EmbedBuilder() - // TODO: Fix depricated function calls 101 - .setAuthor({ name: response.name, iconURL: "https://anilist.co/img/icons/android-chrome-512x512.png", url: response.siteUrl || "https://anilist.co" }) - .setFooter(footer(headers)); - if (response.avatar?.large) titleEmbed.setThumbnail(response.avatar.large); - if (response.bannerImage) titleEmbed.setImage(response.bannerImage); + if (!response) { + return void interaction.editReply({ embeds: [embedError(`Couldn't find any data.`, vars)] }); + } - const statistics = response.statistics; + const titleEmbed = new EmbedBuilder() + // TODO: Fix depricated function calls 101 + .setAuthor({ name: response.name, iconURL: "https://anilist.co/img/icons/android-chrome-512x512.png", url: response.siteUrl || "https://anilist.co" }) + .setFooter(footer(headers)); - if (statistics) { - titleEmbed.addFields( - { name: "< Anime >\n\n", value: `**Watched:** ${statistics.anime?.count.toString()}\n**Average score**: ${statistics.anime?.meanScore.toString()}`, inline: true }, - { name: "< Manga >\n\n", value: `**Read:** ${statistics.manga?.count.toString()}\n**Average score**: ${statistics.manga?.meanScore.toString()}`, inline: true }, - ); - } + if (response.avatar?.large) titleEmbed.setThumbnail(response.avatar.large); + if (response.bannerImage) titleEmbed.setImage(response.bannerImage); - let userColor: ColorResolvable | null; - const profileColor = response.options?.profileColor; + const statistics = response.statistics; - if (profileColor) { - userColor = profileColor.charAt(0).toUpperCase() + profileColor.slice(1); + if (statistics) { + titleEmbed.addFields( + { name: "< Anime >\n\n", value: `**Watched:** ${statistics.anime?.count.toString()}\n**Average score**: ${statistics.anime?.meanScore.toString()}`, inline: true }, + { name: "< Manga >\n\n", value: `**Read:** ${statistics.manga?.count.toString()}\n**Average score**: ${statistics.manga?.meanScore.toString()}`, inline: true }, + ); + } - if (profileColor === "pink") userColor = "LuminousVividPink"; - if (profileColor === "gray") userColor = "Grey"; - // this is just cancer - // re: yeah, this is cancer - titleEmbed.setColor(userColor as ColorResolvable); - } - interaction.editReply({ embeds: [titleEmbed] }); - } else { - return void interaction.editReply({ embeds: [embedError(`Couldn't find any data.`, vars)] }); + let userColor: ColorResolvable | null; + const profileColor = response.options?.profileColor; + + if (profileColor) { + userColor = profileColor.charAt(0).toUpperCase() + profileColor.slice(1); + + if (profileColor === "pink") userColor = "LuminousVividPink"; + if (profileColor === "gray") userColor = "Grey"; + // this is just cancer + // re: yeah, this is cancer + titleEmbed.setColor(userColor as ColorResolvable); } + interaction.editReply({ embeds: [titleEmbed] }); + } catch (e: any) { console.error(e); interaction.editReply({ embeds: [embedError(e, vars)] }); diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 41931e9..8e2ff0e 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -17,8 +17,11 @@ export const run: YuukoEvent<"interactionCreate"> = async (client, interaction) checkCooldown(client, command, interaction); const args = await runMiddlewares(command.middlewares, interaction); client.log(`Ran middleware in: ${Date.now() - interaction.createdTimestamp}ms`, "Debug"); - command.run({ interaction: args, client }); - client.log(`Ran command in: ${Date.now() - interaction.createdTimestamp}ms`, "Debug"); + + if (args.isCommand() && args.isChatInputCommand()) { + command.run({ interaction: args, client }); + client.log(`Ran command in: ${Date.now() - interaction.createdTimestamp}ms`, "Debug"); + } // Check for autocomplete } else if (interaction.isAutocomplete()) { diff --git a/src/structures/command.ts b/src/structures/command.ts index 7051665..e69a626 100644 --- a/src/structures/command.ts +++ b/src/structures/command.ts @@ -1,4 +1,4 @@ -import type { APIEmbedField, ApplicationCommandOptionType, Interaction } from "discord.js"; +import type { APIEmbedField, ApplicationCommandOptionType, CacheType, ChatInputCommandInteraction, Interaction } from "discord.js"; import type Discord from "discord.js"; import type { CommandCategories } from "#utils/commandCategories"; import type { Middleware, Client } from "./index"; @@ -27,7 +27,7 @@ export interface RunOptions { args?: Args; } -export type UsableInteraction = Interaction & { alID?: number, ALtoken?: string } +export type UsableInteraction = ChatInputCommandInteraction & { alID?: number, ALtoken?: string } interface CommandStringOption { name: string; diff --git a/src/utils/canvasHelper.ts b/src/utils/canvasHelper.ts deleted file mode 100644 index d1379d0..0000000 --- a/src/utils/canvasHelper.ts +++ /dev/null @@ -1,68 +0,0 @@ -interface roundRectOptions { - ctx: CanvasRenderingContext2D - x: number - y: number - width: number - height: number - radius?: number | Radius - fill?: boolean - stroke?: boolean -} - -interface Radius { - [key: string]: number - tl: number - tr: number - br: number - bl: number -} - -/** - * Draws a rounded rectangle using the current state of the canvas. - * If you omit the last three params, it will draw a rectangle - * outline with a 5 pixel border radius - * @param {CanvasRenderingContext2D} ctx - * @param {number} x The top left x coordinate - * @param {number} y The top left y coordinate - * @param {number} width The width of the rectangle - * @param {number} height The height of the rectangle - * @param {number} [radius] The corner radius; It can also be an object - * to specify different radii for corners - * @param {number} [radius.tl] Top left - * @param {number} [radius.tr] Top right - * @param {number} [radius.br] Bottom right - * @param {number} [radius.bl] Bottom left - * @param {boolean} [fill] Whether to fill the rectangle. - * @param {boolean} [stroke] Whether to stroke the rectangle. - */ -export function roundRect({ ctx, x, y, width, height, radius, fill, stroke }: roundRectOptions) { - if (typeof stroke === 'undefined') - stroke = true - - if (typeof radius === 'undefined') - radius = 5 - - if (typeof radius === 'number') { - radius = { tl: radius, tr: radius, br: radius, bl: radius } - } - else { - const defaultRadius: Radius = { tl: 0, tr: 0, br: 0, bl: 0 } - for (const side in defaultRadius) radius[side] = radius[side] || defaultRadius[side] - } - ctx.beginPath() - ctx.moveTo(x + radius.tl, y) - ctx.lineTo(x + width - radius.tr, y) - ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr) - ctx.lineTo(x + width, y + height - radius.br) - ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height) - ctx.lineTo(x + radius.bl, y + height) - ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl) - ctx.lineTo(x, y + radius.tl) - ctx.quadraticCurveTo(x, y, x + radius.tl, y) - ctx.closePath() - if (fill) - ctx.fill() - - if (stroke) - ctx.stroke() -} diff --git a/src/utils/handleMediaData.ts b/src/utils/handleMediaData.ts index a9a3167..ff7ac8c 100644 --- a/src/utils/handleMediaData.ts +++ b/src/utils/handleMediaData.ts @@ -201,8 +201,8 @@ function fixScoring(user: CacheEntry | null, scoreType: Maybe | und let score = "Unknown"; if (scoreValue && scoreType) { score = scoreValue.toString(); - if (scoreType === ("POINT_10_DECIMAL" || "POINT_10")) score = `${score} / 10`; - else if (scoreType === ("POINT_100" || "POINT_5")) score = `${score} / ${scoreType.split("POINT_")[1]}`; + if (scoreType === "POINT_10_DECIMAL" || scoreType === "POINT_10") score = `${score} / 10`; + else if (scoreType === "POINT_100" || scoreType === "POINT_5") score = `${score} / ${scoreType.split("POINT_")[1]}`; else if (scoreType === "POINT_3") score = score === "1" ? "☹️" : score === "2" ? "😐" : "🙂"; } else if (user && user.status) score = capitalize(user.status.toString()); return score; diff --git a/src/utils/index.ts b/src/utils/index.ts index a2609fc..089d41a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,4 @@ export * from "./buildPagination"; -export * from "./canvasHelper"; export * from "./commandCategories"; export * from "./embedError"; export * from "./footer";