diff --git a/package.json b/package.json index b945053..f95aa7c 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,15 @@ "@skyra/env-utilities": "^1.2.1", "colorette": "^2.0.20", "discord.js": "^14.13.0", - "openai": "^4.16.1" + "openai": "^4.17.3" }, "devDependencies": { "@flydotio/dockerfile": "^0.4.10", "@sapphire/cli": "^1.6.1", "@sapphire/prettier-config": "^2.0.0", "@sapphire/ts-config": "^5.0.0", - "@types/node": "^20.8.10", - "@types/ws": "^8.5.8", + "@types/node": "^20.9.0", + "@types/ws": "^8.5.9", "prettier": "^3.0.3", "tsc-watch": "^6.0.4", "typescript": "^5.2.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fe497a..eeaf617 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ dependencies: specifier: ^14.13.0 version: 14.13.0 openai: - specifier: ^4.16.1 - version: 4.16.1 + specifier: ^4.17.3 + version: 4.17.3 devDependencies: '@flydotio/dockerfile': @@ -47,11 +47,11 @@ devDependencies: specifier: ^5.0.0 version: 5.0.0 '@types/node': - specifier: ^20.8.10 - version: 20.8.10 + specifier: ^20.9.0 + version: 20.9.0 '@types/ws': - specifier: ^8.5.8 - version: 8.5.8 + specifier: ^8.5.9 + version: 8.5.9 prettier: specifier: ^3.0.3 version: 3.0.3 @@ -117,7 +117,7 @@ packages: '@discordjs/rest': 2.0.1 '@discordjs/util': 1.0.1 '@sapphire/async-queue': 1.5.0 - '@types/ws': 8.5.8 + '@types/ws': 8.5.9 '@vladfrangu/async_event_emitter': 2.2.2 discord-api-types: 0.37.50 tslib: 2.6.2 @@ -178,7 +178,7 @@ packages: resolution: {integrity: sha512-kIKS7J7g06s5PBiuruQY+6T7mCC84myMq8QaHLJgyuj1NeFfqr7+txJ5weSXBODPIsY3iX2WUS4kmLJgKmnOtA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dependencies: - discord-api-types: 0.37.62 + discord-api-types: 0.37.63 dev: false /@sapphire/discord.js-utilities@7.0.2: @@ -300,25 +300,25 @@ packages: /@types/node-fetch@2.6.9: resolution: {integrity: sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==} dependencies: - '@types/node': 20.8.10 + '@types/node': 20.9.0 form-data: 4.0.0 dev: false - /@types/node@18.18.8: - resolution: {integrity: sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==} + /@types/node@18.18.9: + resolution: {integrity: sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==} dependencies: undici-types: 5.26.5 dev: false - /@types/node@20.8.10: - resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} + /@types/node@20.9.0: + resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==} dependencies: undici-types: 5.26.5 - /@types/ws@8.5.8: - resolution: {integrity: sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==} + /@types/ws@8.5.9: + resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} dependencies: - '@types/node': 20.8.10 + '@types/node': 20.9.0 /@vladfrangu/async_event_emitter@2.2.2: resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==} @@ -481,8 +481,8 @@ packages: resolution: {integrity: sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==} dev: false - /discord-api-types@0.37.62: - resolution: {integrity: sha512-KpLQ6TiylGSSHj8AQW8Hz1ek1MkBbQUWs4gZhWH0rvSsWSj9tfav6EIitSxbFmqveqfu8oiPFI7vgFE4kBhTcQ==} + /discord-api-types@0.37.63: + resolution: {integrity: sha512-WbEDWj/1JGCIC1oCMIC4z9XbYY8PrWpV5eqFFQymJhJlHMqgIjqoYbU812X5oj5cwbRrEh6Va4LNLumB2Nt6IQ==} dev: false /discord.js@14.13.0: @@ -496,7 +496,7 @@ packages: '@discordjs/util': 1.0.1 '@discordjs/ws': 1.0.1 '@sapphire/snowflake': 3.5.1 - '@types/ws': 8.5.8 + '@types/ws': 8.5.9 discord-api-types: 0.37.50 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 @@ -785,11 +785,11 @@ packages: mimic-fn: 4.0.0 dev: true - /openai@4.16.1: - resolution: {integrity: sha512-Gr+uqUN1ICSk6VhrX64E+zL7skjI1TgPr/XUN+ZQuNLLOvx15+XZulx/lSW4wFEAQzgjBDlMBbBeikguGIjiMg==} + /openai@4.17.3: + resolution: {integrity: sha512-Gx9wzl9HWX5pjagkgXVu6U2BTFEPkQFdkppNnAX2n2Rpjtn2zt152wXh7NnZ5eJuVxUGYzRe66JmayAEGjzqAg==} hasBin: true dependencies: - '@types/node': 18.18.8 + '@types/node': 18.18.9 '@types/node-fetch': 2.6.9 abort-controller: 3.0.0 agentkeepalive: 4.5.0 diff --git a/src/commands/betapic.ts b/src/commands/betapic.ts new file mode 100644 index 0000000..31ad0b6 --- /dev/null +++ b/src/commands/betapic.ts @@ -0,0 +1,93 @@ +import { ApplyOptions } from '@sapphire/decorators'; +import { Args, BucketScope, Command } from '@sapphire/framework'; +import { AttachmentBuilder, Message } from 'discord.js'; +import OpenAI from 'openai'; + +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY +}); + +// @ts-ignore +@ApplyOptions({ + description: 'Shhhh!', + options: ['prompt'], + // 10mins + cooldownDelay: 400_000, + cooldownLimit: 1, + // Yes... I did hardcode myself. + cooldownFilteredUsers: ['83679718401904640'], + cooldownScope: BucketScope.User +}) +export class UserCommand extends Command { + // Register Chat Input and Context Menu command + public override registerApplicationCommands(registry: Command.Registry) { + registry.registerChatInputCommand((builder) => + builder + .setName(this.name) + .setDescription(this.description) + .addStringOption((option) => + option.setName('prompt').setDescription('The prompt you will use to generate an image!').setRequired(true) + ) + ); + } + + // Message command + public async messageRun(message: Message, args: Args) { + return this.pic(message, args.getOption('prompt') || 'Scold me for not passing any prompt in.'); + } + + // Chat Input (slash) command + public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { + return this.pic(interaction, interaction.options.getString('prompt') || 'NOTHING'); + } + + private async pic(interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction, prompt: string) { + const askMessage = + interactionOrMessage instanceof Message + ? await interactionOrMessage.channel.send({ content: '🤔 Thinking... 🤔' }) + : await interactionOrMessage.reply({ content: '🤔 Thinking... 🤔', fetchReply: true }); + + const response = await openai.images.generate({ + model: 'dall-e-3', + prompt, + n: 1, + size: '1024x1024' + }); + + const imageUrl = response.data[0].url || ''; + // get an array buffer + const imageBuffer = await fetch(imageUrl).then((r) => r.arrayBuffer()); + + if (response.data.length === 0) { + const content = `Sorry, I can't complete the prompt for: ${prompt}`; + + if (interactionOrMessage instanceof Message) { + return askMessage.edit({ content }); + } + + return interactionOrMessage.editReply({ + content: content + }); + } else { + const imageAttachment: AttachmentBuilder[] = []; + + imageAttachment.push( + new AttachmentBuilder(Buffer.from(new Uint8Array(imageBuffer)), { + name: 'response.jpg', + description: "Himbot's Response" + }) + ); + + const content = `Prompt: ${prompt}:`; + + if (interactionOrMessage instanceof Message) { + return askMessage.edit({ content, files: imageAttachment }); + } + + return interactionOrMessage.editReply({ + content, + files: imageAttachment + }); + } + } +}