Neste tutorial, você aprenderá como usar um provedor StableDiffusion e o Discord.js para criar um Bot do Discord que gera uma arte única a partir de prompts de texto.
Imagem gerada com o prompt "Casa voadora fotorrealista, muitos detalhes, ultra detalhada, renderizada com o render Octane, por Alexander Jansson".
Pré-Requisitos E Software Necessários.
- Conhecimento prévio de como criar um Bot do Discord.
- Node.js.
- Um editor de código - Usarei o Visual Studio Code.
- Uma conta no Discord e na Discord Developer com um aplicativo do Discord já registrado.
- Requisitos avançados - Stable Diffusion Docker (imagem Docker) ou RunPod (GPU Cloud) para executar sua própria interface de usuário da web Stable Diffusion em sua máquina ou nuvem (Cloud). Você pode ver o comando avançado que usa esse método.
Passo 1: Clonar e Configurar.
Primeiro, clone este repositório em sua máquina e abra-o com seu editor de código. Este repositório é um modelo básico para qualquer Bot do Discord usando o Discord.js. É similar ao guia Discord.js para construir o seu primeiro bot.
Passo 2: Configurar o Ambiente
Para começar a programar seu bot, abra o seu editor de código preferido.
Abra o seu Terminal e execute o comando yarn
para instalar os pacotes do arquivo package.json
.
"Em seguida, renomeie o arquivo .env-sample para .env e, dentro dele, no campo DISCORD_TOKEN=
, coloque o token do seu bot da seção Bot do desenvolvedor do Discord.
Por último, vamos dar uma olhada no arquivo package.json
para desenvolvimento. Abra o arquivo package.json
.
Você notará dois comandos dentro de "scripts"
.
"deploy": "node deploy-commands.js",
"start": "node index.js"
Esses são os comandos para implantar novos comandos para o seu bot e iniciar o bot posteriormente.
Em seguida, crie uma pasta chamada "Commands".
Dentro dessa pasta, crie um arquivo generate.js
e um arquivo advanced.js
.
Com tudo configurado, vamos começar o desenvolvimento!
Passo 3: Codificando o Bot.
Vamos iniciar o nosso bot. Para fazer isso, abra o terminal, vá para o diretório da pasta do seu projeto e execute o comando yarn start
.
Você deverá ver uma mensagem ao longo das linhas de como "Ready!" (pronto). Se essa mensagem não aparecer, verifique se todos os pacotes estão instalados e se não há erros de sintaxe no arquivo package.json.
Vamos abrir o arquivo generate.js
primeiro e começar.
Coloque todos os pacotes necessários na parte superior do arquivo. Certifique-se de que o dotenv
está inicializado primeiro e antes de tudo.
const dotenv = require('dotenv');
dotenv.config();
const WebSocket = require('ws');
const { SlashCommandBuilder, AttachmentBuilder } = require('discord.js');
const createHash = require('hash-generator');
Vamos criar uma função que retorna um hash gerado. Esse hash é específico para a demonstração StableDiffusion 1.5 usada pelo provedor aqui:
A demonstração Stable Diffusion 1.5 requer um hash que deve ser enviado através de Web Sockets quando solicitado.
Para fornecer um exemplo, abaixo você verá que tanto fn_index:2
quanto session_hash
estão dentro de "session_hash". Em seguida, incluiremos esses parâmetros em nosso arquivo generate.js
.
function generateHash() {
let hash = createHash(12)
return {
session_hash: hash,
fn_index: 2
}
}
Agora, precisamos construir um SlashCommandBuilder
.
Vamos modularizar esse comando com module.exports
.
module.exports = {
data: new SlashCommandBuilder()
.setName('generate')
.setDescription('Generates an image from a text prompt using Stable Diffusion 1.5')
.addStringOption(option => option
.setName('prompt')
.setDescription('generate image prompt')
),
};
Mas espere! Não execute o código ainda.
Você pode ter percebido que não temos um execute
no final. Vamos adicioná-lo a seguir.
async execute(interaction) {
},
};
Vamos juntar tudo agora.
Em seguida, queremos buscar a interação e a string a partir da execução do comando /generate prompt
.
const prompt = interaction.options.getString('prompt');
console.log('What to generate?', prompt);
Agora vamos envolver o próximo trecho de código em um try/catch
e definir algumas constantes com uma resposta imediata à interação.
A primeira constante é para Web Sockets. Isso conecta diretamente ao /queue/join
e nos coloca na fila da interface do usuário da Web ao receber um prompt de geração.
try {
interaction.reply("I'm generating...");
const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
const hash = generateHash();
} catch (error) {
interaction.reply(error);
console.error(error);
}console.error(error);
Agora devemos aplicar nossos Web Sockets e fazer coisas divertidas!
ws.on('open', () => {});
ws.on('message', async (message) => {});
ws.on('error', async (error) => {
interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem',
});
console.error(error);
});
Seu arquivo generate.js
deve estar parecido com isso agora.
module.exports = {
data: new SlashCommandBuilder()
.setName('generate')
.setDescription('Gera uma imagem a partir de um prompt de texto usando o Stable Diffusion 1.5')
.addStringOption(option => option
.setName('prompt')
.setDescription('generate image prompt')
),
async execute(interaction) {
const prompt = interaction.options.getString('prompt');
console.log('O que Gerar?', prompt);
try {
interaction.reply("Estou gerando...");
const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
const hash = generateHash();
ws.on('open', () => {});
ws.on('message', async (message) => {});
ws.on('error', async (error) => {
interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem',
});
console.error(error);
});
} catch (error) {
interaction.reply(error);
console.error(error);
}
},
};
Dentro do ws.on('message')
, primeiro, faremos um JSON.parse
na message
(mensagem) retornada e determinaremos se o valor de msg
= 'send_data' ou se é ='process_completed'.
Note que mais mensagens são enviadas, mas essas duas são o foco principal deste tutorial.
Para fazer isso, primeiro criamos uma constante chamada msg
que contém um JSON.parse
da mensagem retornada:
const msg = JSON.parse(`${message}`);
if (msg.msg === 'send_data') {
} else if (msg.msg === 'process_completed') {
} else {}
Em seguida, queremos adicionar os 'dados' que queremos enviar. Criaremos uma constante de dados contendo tanto a solicitação dos usuários do Discord envolvida em uma matriz quanto a constante hash que fizemos.
const data = {
data: [prompt],
...hash,
};
ws.send(JSON.stringify(data));
Você pode ter percebido que adicionei um ws.send
também. Isso é especificamente para enviar o pacote de dados com tudo o que é necessário para iniciar o processo de geração.
Agora, dê uma olhada em seu código e verifique se ele corresponde a isso:
const dotenv = require('dotenv');
dotenv.config();
const WebSocket = require('ws');
const {
SlashCommandBuilder,
AttachmentBuilder
} = require('discord.js');
const createHash = require('hash-generator');
function generateHash() {
let hash = createHash(12)
return {
session_hash: hash,
fn_index: 2
}
}
module.exports = {
data: new SlashCommandBuilder()
.setName('generate')
.setDescription('Gera uma imagem a partir de um prompt de texto usando o Stable Diffusion 1.5')
.addStringOption(option => option
.setName('prompt')
.setDescription('generate image prompt')
),
async execute(interaction) {
const prompt = interaction.options.getString('prompt');
console.log('O que Gerar?', prompt);
try {
interaction.reply("Estou gerando...");
const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
const hash = generateHash();
ws.on('open', () => {});
ws.on('message', async (message) => {
const msg = JSON.parse(`${message}`);
if (msg.msg === 'send_data') {
const data = {
data: [prompt],
...hash,
};
ws.send(JSON.stringify(data));
} else if (msg.msg === 'process_completed') {
} else {}
});
ws.on('error', async (error) => {
console.error(error);
interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem',
});
});
} catch (error) {
console.error(error);
}
},
};
Ótimo! Você está quase lá!
Agora vamos adicionar um try/catch
e a buscar as imagens que nos são enviadas depois de msg
= ‘process_completed’.
try {
} catch (error) {
console.error(error);
interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem',
});
}
A seguir, dentro do bloco try
, vamos buscar o resultado da variável msg
. Além disso, estamos criando uma constante chamada ‘attachments’ para um array (matriz). Lembre-se de que várias imagens serão enviadas de volta para nós.
try {
const results = msg.output.data[0];
const attachments = [];
} catch (error) {
console.error(error);
interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem',
});
}
Em seguida, queremos percorrer os resultados e dividi-los em novas linhas para cada vírgula ,
encontrada.
Adicione ao buffer a constante ‘results’ e anexe-as ao AttachmentBuilder do Discord.
Certifique-se de adicionar cada anexo no array usando attachments.push(attachment).
Seu código deve se parecer com o seguinte:
try {
const results = msg.output.data[0];
const attachments = [];
for (let i = 0; i < results.length; i++) {
const data = results[i].split(',')[1];
const buffer = Buffer.from(data, 'base64');
const attachment = new AttachmentBuilder(buffer, {
name: 'generate.png',
});
attachments.push(attachment);
}
} catch (error) {
console.error(error);
interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem',
});
}
Por fim, devemos enviar os anexos para o Discord para que o usuário possa visualizá-los!
Observação: estamos usando editReply
porque originalmente enviamos uma resposta (reply
) quando iniciamos o processo de geração.
interaction.editReply({
content: `You asked me for ${prompt}`,
files: attachments,
});
O seu código final para o arquivo generate.js
deve ser semelhante ao seguinte:
const dotenv = require('dotenv');
dotenv.config();
const WebSocket = require('ws');
const {
SlashCommandBuilder,
AttachmentBuilder
} = require('discord.js');
const createHash = require('hash-generator');
function generateHash() {
let hash = createHash(12)
return {
session_hash: hash,
fn_index: 2
}
}
module.exports = {
data: new SlashCommandBuilder()
.setName('generate')
.setDescription('Gera uma imagem a partir de um prompt de texto usando o Stable Diffusion 1.5')
.addStringOption(option => option
.setName('prompt')
.setDescription('generate image prompt')
),
async execute(interaction) {
const prompt = interaction.options.getString('prompt');
console.log('O que gerar?', prompt);
try {
await interaction.reply("I'm generating...");
const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
const hash = generateHash();
ws.on('open', () => {});
ws.on('message', async (message) => {
const msg = JSON.parse(`${message}`);
if (msg.msg === 'send_hash') {
ws.send(JSON.stringify(hash));
} else if (msg.msg === 'send_data') {
const data = {
data: [prompt],
...hash,
};
ws.send(JSON.stringify(data));
} else if (msg.msg === 'process_completed') {
try {
const results = msg.output.data[0];
const attachments = [];
for (let i = 0; i < results.length; i++) {
const data = results[i].split(',')[1];
const buffer = Buffer.from(data, 'base64');
const attachment = new AttachmentBuilder(buffer, {
name: 'generate.png',
});
attachments.push(attachment);
}
interaction.editReply({
content: `You asked me for ${prompt}`,
files: attachments,
});
} catch (error) {
console.error(error);
await interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem',
});
}
}
});
ws.on('error', async (error) => {
console.error(error);
await interaction.editReply({
content: 'Ocorreu um erro ao gerar a imagem'',
});
});
} catch (error) {
console.error(error);
}
},
};
Agora, vamos executar o comando e vê-lo em ação!
Sucesso! Você gerou seu primeiro conjunto de imagens usando o Stable Diffusion através do seu próprio Bot personalizado do Discord! Este é apenas um exemplo do que é possível com comandos do Discord e Web Sockets. Por favor, observe que isso é melhor usado para fins de demonstração, e eu recomendo que você hospede sua própria interface do usuário da web do Stable Diffusion.
Você pode encontrar o código final do bot abaixo, juntamente com um comando avançado que se conecta a uma interface do usuário da web que você mesmo hospeda com um serviço de nuvem GPU ou uma imagem do docker, todos utilizando o pacote request
: https://github.com/f00d4tehg0dz/stable-diffusion-discord-bot-template/tree/final
Obrigado por ler. Por favor, confira meu bot do Discord, Arti, e experimente alguns comandos mais avançados em ação!
Esse artigo é uma tradução feita por @bananlabs. Você pode encontrar o artigo original aqui.
Top comments (0)