diff --git a/package.json b/package.json index bed5698..b1ef5a1 100644 --- a/package.json +++ b/package.json @@ -11,20 +11,21 @@ "dependencies": { "@sapphire/decorators": "^6.0.2", "@sapphire/discord.js-utilities": "7.1.2", - "@sapphire/framework": "^4.8.2", + "@sapphire/framework": "^4.8.4", "@sapphire/plugin-logger": "^3.0.7", "@sapphire/utilities": "^3.13.0", "@skyra/env-utilities": "^1.2.1", "colorette": "^2.0.20", "discord.js": "^14.14.1", - "openai": "^4.20.1" + "openai": "^4.20.1", + "replicate": "^0.22.0" }, "devDependencies": { "@flydotio/dockerfile": "^0.4.11", - "@sapphire/cli": "^1.8.0", + "@sapphire/cli": "^1.9.0", "@sapphire/prettier-config": "^2.0.0", "@sapphire/ts-config": "^5.0.0", - "@types/node": "^20.10.0", + "@types/node": "^20.10.2", "@types/ws": "^8.5.10", "prettier": "^3.1.0", "tsc-watch": "^6.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8af47d6..b1a2fe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ dependencies: specifier: 7.1.2 version: 7.1.2 '@sapphire/framework': - specifier: ^4.8.2 - version: 4.8.2 + specifier: ^4.8.4 + version: 4.8.4 '@sapphire/plugin-logger': specifier: ^3.0.7 version: 3.0.7 @@ -32,14 +32,17 @@ dependencies: openai: specifier: ^4.20.1 version: 4.20.1 + replicate: + specifier: ^0.22.0 + version: 0.22.0 devDependencies: '@flydotio/dockerfile': specifier: ^0.4.11 version: 0.4.11 '@sapphire/cli': - specifier: ^1.8.0 - version: 1.8.0 + specifier: ^1.9.0 + version: 1.9.0 '@sapphire/prettier-config': specifier: ^2.0.0 version: 2.0.0 @@ -47,8 +50,8 @@ devDependencies: specifier: ^5.0.0 version: 5.0.0 '@types/node': - specifier: ^20.10.0 - version: 20.10.0 + specifier: ^20.10.2 + version: 20.10.2 '@types/ws': specifier: ^8.5.10 version: 8.5.10 @@ -102,7 +105,7 @@ packages: '@discordjs/util': 1.0.2 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.5.1 - '@vladfrangu/async_event_emitter': 2.2.2 + '@vladfrangu/async_event_emitter': 2.2.4 discord-api-types: 0.37.61 magic-bytes.js: 1.5.0 tslib: 2.6.2 @@ -123,7 +126,7 @@ packages: '@discordjs/util': 1.0.2 '@sapphire/async-queue': 1.5.0 '@types/ws': 8.5.10 - '@vladfrangu/async_event_emitter': 2.2.2 + '@vladfrangu/async_event_emitter': 2.2.4 discord-api-types: 0.37.61 tslib: 2.6.2 ws: 8.14.2 @@ -161,12 +164,13 @@ packages: engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false - /@sapphire/cli@1.8.0: - resolution: {integrity: sha512-lOxt3cgMU8/2VChUE85SO46m09AadtM/PmSubSDHrgPwtGcERlHrDj1lPut15sb9t07+PNNfwc0yXEuxwQxDNw==} + /@sapphire/cli@1.9.0: + resolution: {integrity: sha512-2WNMscS9c1+Y3W3Aem+BdgqgMnkbGrwmGaYr7kH+aGGG2Od12Drkq+umdNzF8DbiAiSaB6CK8sHe6HOqBvXfOA==} engines: {node: '>=v18'} hasBin: true dependencies: '@favware/colorette-spinner': 1.0.1 + '@sapphire/node-utilities': 1.0.0 '@sapphire/result': 2.6.4 colorette: 2.0.20 commander: 11.1.0 @@ -206,8 +210,8 @@ packages: engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false - /@sapphire/framework@4.8.2: - resolution: {integrity: sha512-MJH6NkdZtZ9DgcPD3ExQnjO7UYdZm2WQBC29jb9A6kmYzd/y+8/unSjURgDQQhw6+60QWN7tPJXCVQJmmlPVCw==} + /@sapphire/framework@4.8.4: + resolution: {integrity: sha512-ErnsCcvgtC/8/KS2gvV88EFtN4uteFzLKDfEjYebdXU7AVr9AoOBJtymYu42GHVZJ3eaYKGadUBJn6015LxaLw==} engines: {node: '>=v18', npm: '>=7'} dependencies: '@discordjs/builders': 1.7.0 @@ -228,6 +232,11 @@ packages: '@sapphire/result': 2.6.4 dev: false + /@sapphire/node-utilities@1.0.0: + resolution: {integrity: sha512-xFC4UyzSKs6juyFtsUV4VNybg0KIpwaThED2TH3TGtNT5b0VFpILTXQtqXpPf+Rfmj+O/mLCm319xy4ohsQqxQ==} + engines: {node: '>=v16.0.0', npm: '>=7.0.0'} + dev: true + /@sapphire/pieces@3.10.0: resolution: {integrity: sha512-iBaux50dA+VYjtBqmaceWcskdmw7ua51ojEPkyaSJyg2t9ln/Wc9NqYoQheRCWltZeDTERCUBIYYMqDuCs1Okw==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -310,34 +319,34 @@ packages: /@types/node-fetch@2.6.9: resolution: {integrity: sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==} dependencies: - '@types/node': 20.10.0 + '@types/node': 20.10.2 form-data: 4.0.0 dev: false - /@types/node@18.18.13: - resolution: {integrity: sha512-vXYZGRrSCreZmq1rEjMRLXJhiy8MrIeVasx+PCVlP414N7CJLHnMf+juVvjdprHyH+XRy3zKZLHeNueOpJCn0g==} + /@types/node@18.19.1: + resolution: {integrity: sha512-mZJ9V11gG5Vp0Ox2oERpeFDl+JvCwK24PGy76vVY/UgBtjwJWc5rYBThFxmbnYOm9UPZNm6wEl/sxHt2SU7x9A==} dependencies: undici-types: 5.26.5 dev: false - /@types/node@20.10.0: - resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==} + /@types/node@20.10.2: + resolution: {integrity: sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw==} dependencies: undici-types: 5.26.5 /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 20.10.0 + '@types/node': 20.10.2 /@types/ws@8.5.9: resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} dependencies: - '@types/node': 20.10.0 + '@types/node': 20.10.2 dev: false - /@vladfrangu/async_event_emitter@2.2.2: - resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==} + /@vladfrangu/async_event_emitter@2.2.4: + resolution: {integrity: sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false @@ -798,7 +807,7 @@ packages: resolution: {integrity: sha512-Dd3q8EvINfganZFtg6V36HjrMaihqRgIcKiHua4Nq9aw/PxOP48dhbsk8x5klrxajt5Lpnc1KTOG5i1S6BKAJA==} hasBin: true dependencies: - '@types/node': 18.18.13 + '@types/node': 18.19.1 '@types/node-fetch': 2.6.9 abort-controller: 3.0.0 agentkeepalive: 4.5.0 @@ -868,6 +877,11 @@ packages: event-stream: 3.3.4 dev: true + /replicate@0.22.0: + resolution: {integrity: sha512-jUfvYCcnnzG0l3NzgNLUBRfv7e5ZcZpBWzxGqx3v8bkvJmY0jK19JRtFtBgJFRBye61ggH1zC9cubGkburF82g==} + engines: {git: '>=2.11.0', node: '>=18.0.0', npm: '>=7.19.0', yarn: '>=1.7.0'} + dev: false + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} diff --git a/src/.env.example b/src/.env.example index cb27056..bf374c0 100644 --- a/src/.env.example +++ b/src/.env.example @@ -1,4 +1,4 @@ # Tokens DISCORD_TOKEN= OPENAI_API_KEY= -STABILITY_API_KEY= \ No newline at end of file +REPLICATE_API_TOKEN= \ No newline at end of file diff --git a/src/commands/credits.ts b/src/commands/credits.ts deleted file mode 100644 index ecbafd9..0000000 --- a/src/commands/credits.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ApplyOptions } from '@sapphire/decorators'; -import { Command } from '@sapphire/framework'; -import { Message } from 'discord.js'; - -// @ts-ignore -@ApplyOptions({ - description: 'Check how many credits I have left!' -}) -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)); - } - - // Message command - public async messageRun(message: Message) { - return this.credits(message); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - return this.credits(interaction); - } - - private async credits(interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction) { - const askMessage = - interactionOrMessage instanceof Message - ? await interactionOrMessage.channel.send({ content: 'šŸ¤” Thinking... šŸ¤”' }) - : await interactionOrMessage.reply({ content: 'šŸ¤” Thinking... šŸ¤”', fetchReply: true }); - - const creditCountResponse = await fetch(`https://api.stability.ai/v1/user/balance`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${process.env.STABILITY_API_KEY}` - } - }); - - const balance = ((await creditCountResponse.json()) as { credits: number }).credits || 0; - - const content = `I have ${balance} credits remaining for image generation!`; - - if (interactionOrMessage instanceof Message) { - return askMessage.edit({ content }); - } - - return interactionOrMessage.editReply({ - content: content - }); - } -} diff --git a/src/commands/dad.ts b/src/commands/dad.ts deleted file mode 100644 index 74d0d3a..0000000 --- a/src/commands/dad.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ApplyOptions } from '@sapphire/decorators'; -import { Command } from '@sapphire/framework'; -import { Message } from 'discord.js'; - -// @ts-ignore -@ApplyOptions({ - description: 'Dad joke for daddies only!' -}) -export class UserCommand extends Command { - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - // Register Chat Input command - registry.registerChatInputCommand({ - name: this.name, - description: this.description - }); - } - - // Message command - public async messageRun(message: Message) { - return this.sendJoke(message); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - return this.sendJoke(interaction); - } - - private async sendJoke(interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction) { - const jokeResponse = await fetch('https://icanhazdadjoke.com/', { - headers: { - Accept: 'application/json' - } - }); - const jokeData = (await jokeResponse.json()) as { status: number; joke: string }; - - interactionOrMessage instanceof Message - ? await interactionOrMessage.channel.send({ - content: jokeData.status === 200 ? jokeData.joke : '404 Joke Not Found' - }) - : await interactionOrMessage.reply({ - content: jokeData.status === 200 ? jokeData.joke : '404 Joke Not Found', - fetchReply: true - }); - } -} diff --git a/src/commands/fancypic.ts b/src/commands/hdpic.ts similarity index 96% rename from src/commands/fancypic.ts rename to src/commands/hdpic.ts index e1e74e0..25ea176 100644 --- a/src/commands/fancypic.ts +++ b/src/commands/hdpic.ts @@ -9,9 +9,9 @@ const openai = new OpenAI({ // @ts-ignore @ApplyOptions({ - description: 'Pic... but better! Cooldown of 8 Minutes!', + description: 'Generate an image using OpenAI! Cooldown of 10 Minutes due to cost!', options: ['prompt'], - cooldownDelay: 1_000_000, + cooldownDelay: 480_000, cooldownLimit: 1, // Yes... I did hardcode myself. cooldownFilteredUsers: ['83679718401904640'], diff --git a/src/commands/fancytts.ts b/src/commands/hdtts.ts similarity index 100% rename from src/commands/fancytts.ts rename to src/commands/hdtts.ts diff --git a/src/commands/pic.ts b/src/commands/pic.ts index b2699da..17cc5a2 100644 --- a/src/commands/pic.ts +++ b/src/commands/pic.ts @@ -1,15 +1,18 @@ import { ApplyOptions } from '@sapphire/decorators'; import { Args, BucketScope, Command } from '@sapphire/framework'; -import { AttachmentBuilder, Message } from 'discord.js'; +import { Message } from 'discord.js'; +import Replicate from 'replicate'; -// This is literally the worlds messiest TS code. Please don't judge me... +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN +}); // @ts-ignore @ApplyOptions({ - description: 'Make a picture... but high res!', - options: ['prompt', 'number of pictures'], + description: 'Generate an image using Stability AI! Cooldown 1 Minute to prevent spam!', + options: ['prompt'], // 10mins - cooldownDelay: 100, + cooldownDelay: 100_000, cooldownLimit: 1, // Yes... I did hardcode myself. cooldownFilteredUsers: ['83679718401904640'], @@ -25,135 +28,39 @@ export class UserCommand extends Command { .addStringOption((option) => option.setName('prompt').setDescription('The prompt you will use to generate an image!').setRequired(true) ) - .addStringOption((option) => - option - .setName('amount') - .setDescription('The number of images you would like to generate. Maximum 2.') - .setChoices( - ...[ - { name: '1', value: '1' }, - { name: '2', value: '2' } - ] - ) - ) ); } // Message command public async messageRun(message: Message, args: Args) { - const amount = Math.abs(Number(args.getOption('amount'))); - return this.picHr(message, args.getOption('prompt') || 'Scold me for not passing any prompt in.', amount <= 2 ? amount : 1); + return this.picHr(message, args.getOption('prompt') || 'Scold me for not passing any prompt in.'); } // Chat Input (slash) command public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const amount = Number(interaction.options.getString('amount')); - return this.picHr(interaction, interaction.options.getString('prompt') || 'NOTHING', amount || 1); + return this.picHr(interaction, interaction.options.getString('prompt') || 'NOTHING'); } - private async picHr( - interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction, - prompt: string, - amount: number - ) { + private async picHr(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 creditCountResponse = await fetch(`https://api.stability.ai/v1/user/balance`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${process.env.STABILITY_API_KEY}` + let result = (await replicate.run('stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b', { + input: { + prompt, + disable_safety_checker: true, + refine: 'expert_ensemble_refiner', + num_inference_steps: 50, + scheduler: 'K_EULER', + width: 1024, + height: 1024 } - }); + })) as string[]; - const balance = ((await creditCountResponse.json()) as { credits: number }).credits || 0; - - if (balance > 5) { - const imageGenResponse = await fetch(`https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - Authorization: `Bearer ${process.env.STABILITY_API_KEY}` - }, - body: JSON.stringify({ - text_prompts: [ - { - text: prompt - } - ], - cfg_scale: 6, - clip_guidance_preset: 'FAST_BLUE', - height: 1024, - width: 1024, - samples: amount, - steps: 32, - seed: Number(String(interactionOrMessage.member?.user.id).substring(0, 5)) || 0 - }) - }); - - interface GenerationResponse { - artifacts: Array<{ - base64: string; - seed: number; - finishReason: string; - }>; - } - - if (!imageGenResponse.ok) { - 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 responseJSON = (await imageGenResponse.json()) as GenerationResponse; - const imageAttachment: AttachmentBuilder[] = []; - - for (let i = 0; i < responseJSON.artifacts.length; i++) { - imageAttachment.push( - new AttachmentBuilder(Buffer.from(responseJSON.artifacts[i].base64, 'base64'), { - name: 'response.jpg', - description: "Himbot's Response" - }) - ); - } - - const newCreditCountResponse = await fetch(`https://api.stability.ai/v1/user/balance`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${process.env.STABILITY_API_KEY}` - } - }); - - const newBalance = ((await newCreditCountResponse.json()) as { credits: number }).credits || 0; - - const content = - `Credits Used: ${balance - newBalance}\nPrompt: ${prompt}${ - balance <= 300 - ? `\n\nāš ļøI am now at ${balance} credits. If you'd like to help fund this command, please type "/support" for details!` - : '' - }` || 'ERROR!'; - - if (interactionOrMessage instanceof Message) { - return askMessage.edit({ content, files: imageAttachment }); - } - - return interactionOrMessage.editReply({ - content, - files: imageAttachment - }); - } - } else { - const content = `Oops! We're out of credits for this. If you'd like to help fund this command, please type "/support" for details!`; + if (result.length <= 0) { + const content = `Sorry, I can't complete the prompt for: ${prompt}`; if (interactionOrMessage instanceof Message) { return askMessage.edit({ content }); @@ -162,6 +69,16 @@ export class UserCommand extends Command { return interactionOrMessage.editReply({ content: content }); + } else { + const content = `Prompt: ${prompt}\n URL: ${result[0]}`; + + if (interactionOrMessage instanceof Message) { + return askMessage.edit({ content }); + } + + return interactionOrMessage.editReply({ + content + }); } } }