Compare commits

..

6 commits

7 changed files with 2577 additions and 5611 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ bin/
config/config.json config/config.json
json/board/ json/board/
unloaded/ unloaded/
database.sqlite3

View file

@ -1,76 +0,0 @@
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
import fetch from 'node-fetch';
import fs from 'node:fs';
import os from 'node:os';
const { stableHordeApi, stableHordeID } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('img2img')
.setDescription('AI generated image with stable diffusion (If credit are low it may be slow)')
.addAttachmentOption(option =>
option.setName('image')
.setDescription('Image you want to modify')
.setRequired(true))
.addStringOption(option =>
option.setName('prompt')
.setDescription('What do you want the AI to generate?')
.setRequired(true)),
category: 'AI',
async execute(interaction, args, client) {
await interaction.deferReply();
fetch(args.image.url)
.then((res) => {
const dest = fs.createWriteStream(`${os.tmpdir()}/${args.image.name}`);
res.body.pipe(dest);
dest.on('finish', () => {
const b64Image = fs.readFileSync(`${os.tmpdir()}/${args.image.name}`, { encoding: 'base64' });
generate(interaction, args.prompt, b64Image, client);
});
});
},
};
async function generate(i, prompt, b64Img) {
const body = {
prompt: prompt,
params: {
n: 1,
width: 512,
height: 512,
},
cfg_scale: 9,
use_gfpgan: true,
use_real_esrgan: true,
use_ldsr: true,
use_upscaling: true,
steps: 50,
nsfw: i.channel.nsfw ? true : false,
censor_nsfw: i.channel.nsfw ? true : false,
source_image: b64Img,
};
const fetchParameters = {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json', 'apikey': stableHordeApi },
};
let response = await fetch('https://stablehorde.net/api/v2/generate/sync', fetchParameters);
response = await response.json();
let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`);
creditResponse = await creditResponse.json();
const stableEmbed = new EmbedBuilder()
.setColor(i.member ? i.member.displayHexColor : 'Navy')
.setTitle(prompt)
.setURL('https://aqualxx.github.io/stable-ui/')
.setFooter({ text: `**Credit left: ${creditResponse.kudos}** Seed: ${response.generations[0].seed} worker ID: ${response.generations[0].worker_id} worker name: ${response.generations[0].worker_name}` });
fs.writeFileSync(`${os.tmpdir()}/${i.id}.png`, response.generations[0].img, 'base64');
await i.editReply({ embeds: [stableEmbed], files: [`${os.tmpdir()}/${i.id}.png`] });
}

153
commands/AI/img2img.js Normal file
View file

@ -0,0 +1,153 @@
/* TODO
*
* To be merged with commands/AI/txt2img.js
*
*/
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js';
import fetch from 'node-fetch';
import os from 'node:os';
import fs from 'node:fs';
import stream from 'node:stream';
import util from 'node:util';
import db from '../../models/index.js';
const { stableHordeApi, stableHordeID } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('img2img')
.setDescription('AI generated image with stable diffusion (If credit are low it may be slow)')
.addAttachmentOption(option =>
option.setName('image')
.setDescription('Image you want to modify')
.setRequired(true))
.addStringOption(option =>
option.setName('prompt')
.setDescription('What do you want the AI to generate?')
.setRequired(true)),
category: 'AI',
async execute(interaction, args, client) {
await interaction.deferReply();
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(args.image.url);
if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.');
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${args.image.name}.webp`));
const b64Image = fs.readFileSync(`${os.tmpdir()}/${args.image.name}`, { encoding: 'base64' });
generate(interaction, args.prompt, client, b64Image);
},
};
async function generate(i, prompt, client, b64Img) {
console.log('Generating image');
const body = {
prompt: prompt,
params: {
n: 1,
width: 512,
height: 512,
},
cfg_scale: 9,
use_gfpgan: true,
use_real_esrgan: true,
use_ldsr: true,
use_upscaling: true,
steps: 50,
nsfw: i.channel.nsfw ? true : false,
censor_nsfw: i.channel.nsfw ? true : false,
source_image: b64Img,
source_processing: 'img2img',
shared: true,
};
const isOptOut = await db.optout.findOne({ where: { userID: i.user.id } });
if (isOptOut) {
body.shared = false;
}
const fetchParameters = {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json', 'apikey': stableHordeApi },
};
let response = await fetch('https://stablehorde.net/api/v2/generate/async', fetchParameters);
response = await response.json();
let wait_time = 5000;
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
const checking = setInterval(async () => {
const checkResult = await checkGeneration(checkURL);
if (checkResult === undefined) return;
if (!checkResult.done) {
if (checkResult.wait_time < 0) {
console.log(checkResult);
clearInterval(checking);
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
}
if (checkResult.wait_time === 0) {
checkURL = `https://stablehorde.net/api/v2/generate/status/${response.id}`;
}
wait_time = checkResult.wait_time;
}
else if (checkResult.done && checkResult.image) {
clearInterval(checking);
let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`);
creditResponse = await creditResponse.json();
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(checkResult.image);
if (!res.ok) return i.editReply('An error has occured while trying to download your image.');
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${i.id}.webp`));
const generatedImg = new AttachmentBuilder(`${os.tmpdir()}/${i.id}.webp`);
const stableEmbed = new EmbedBuilder()
.setColor(i.member ? i.member.displayHexColor : 'Navy')
.setTitle(prompt)
.setURL('https://aqualxx.github.io/stable-ui/')
.setImage(`attachment://${i.id}.webp`)
.setFooter({ text: `**Credit left: ${creditResponse.kudos}** Seed: ${checkResult.seed} worker ID: ${checkResult.worker_id} worker name: ${checkResult.worker_name}` });
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`regenerate${i.user.id}`)
.setLabel('🔄 Regenerate')
.setStyle(ButtonStyle.Primary),
);
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
client.once('interactionCreate', async (interactionMenu) => {
if (i.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}`) {
await interactionMenu.deferReply();
await generate(interactionMenu, prompt, client);
}
});
}
}, wait_time);
}
async function checkGeneration(url) {
let check = await fetch(url);
check = await check.json();
if (!check.is_possible) {
return { done: false, wait_time: -1 };
}
if (check.done) {
if (!check.generations) {
return { done: false, wait_time: check.wait_time * 1000 };
}
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name };
}
}

View file

@ -1,8 +1,16 @@
/* TODO
*
* To be merged with commands/AI/img2img.js
*
*/
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js'; import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import os from 'node:os'; import os from 'node:os';
import fs from 'node:fs'; import fs from 'node:fs';
import stream from 'node:stream';
import util from 'node:util';
import db from '../../models/index.js';
const { stableHordeApi, stableHordeID } = process.env; const { stableHordeApi, stableHordeID } = process.env;
@ -37,8 +45,15 @@ async function generate(i, prompt, client) {
steps: 50, steps: 50,
nsfw: i.channel.nsfw ? true : false, nsfw: i.channel.nsfw ? true : false,
censor_nsfw: i.channel.nsfw ? true : false, censor_nsfw: i.channel.nsfw ? true : false,
shared: true,
}; };
const isOptOut = await db.optout.findOne({ where: { userID: i.user.id } });
if (isOptOut) {
body.shared = false;
}
const fetchParameters = { const fetchParameters = {
method: 'post', method: 'post',
body: JSON.stringify(body), body: JSON.stringify(body),
@ -69,8 +84,10 @@ async function generate(i, prompt, client) {
let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`); let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`);
creditResponse = await creditResponse.json(); creditResponse = await creditResponse.json();
await fetch(checkResult.image) const streamPipeline = util.promisify(stream.pipeline);
.then(res => res.body.pipe(fs.createWriteStream(`${os.tmpdir()}/${i.id}.webp`))); const res = await fetch(checkResult.image);
if (!res.ok) return i.editReply('An error has occured while trying to download your image.');
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${i.id}.webp`));
const generatedImg = new AttachmentBuilder(`${os.tmpdir()}/${i.id}.webp`); const generatedImg = new AttachmentBuilder(`${os.tmpdir()}/${i.id}.webp`);

View file

@ -4,6 +4,8 @@ import Twit from 'twit';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import os from 'node:os'; import os from 'node:os';
import fs from 'node:fs'; import fs from 'node:fs';
import util from 'node:util';
import stream from 'node:stream';
import db from '../../models/index.js'; import db from '../../models/index.js';
import wordToCensor from '../../json/censor.json' assert {type: 'json'}; import wordToCensor from '../../json/censor.json' assert {type: 'json'};
@ -29,8 +31,7 @@ export default {
async execute(interaction, args, client) { async execute(interaction, args, client) {
const content = args.content; const content = args.content;
const attachment = args.image; const attachment = args.image;
console.log(args);
console.log(attachment);
if (!content && !attachment) { if (!content && !attachment) {
return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true }); return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true });
} }
@ -91,34 +92,32 @@ export default {
// Make sure there is an attachment and if its an image // Make sure there is an attachment and if its an image
if (attachment) { if (attachment) {
if (attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png') || attachment.name.toLowerCase().endsWith('.gif')) { if (attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png') || attachment.name.toLowerCase().endsWith('.gif')) {
fetch(attachment.url) const streamPipeline = util.promisify(stream.pipeline);
.then(res => { const res = await fetch(attachment.url);
const dest = fs.createWriteStream(`${os.tmpdir()}/${attachment.name}`); if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.');
res.body.pipe(dest); await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${attachment.name}`));
dest.on('finish', () => {
const file = fs.statSync(`${os.tmpdir()}/${attachment.name}`);
const fileSize = file.size / 1000000.0;
if ((attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png')) && fileSize > 5) { const file = fs.statSync(`${os.tmpdir()}/${attachment.name}`);
return interaction.editReply({ content: 'Images can\'t be larger than 5 MB!' }); const fileSize = file.size / 1000000.0;
}
else if (attachment.name.toLowerCase().endsWith('.gif') && fileSize > 15) {
return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' });
}
const b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' }); if ((attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png')) && fileSize > 5) {
T.post('media/upload', { media_data: b64Image }, function(err, data) { return interaction.editReply({ content: 'Images can\'t be larger than 5 MB!' });
if (err) { }
console.log('OH NO AN ERROR!!!!!!!'); else if (attachment.name.toLowerCase().endsWith('.gif') && fileSize > 15) {
console.error(err); return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' });
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue! ' }); }
}
else { const b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' });
Tweet(data); T.post('media/upload', { media_data: b64Image }, function(err, data) {
} if (err) {
}); console.log('OH NO AN ERROR!!!!!!!');
}); console.error(err);
}); return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue! ' });
}
else {
Tweet(data);
}
});
} }
else { else {
await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' }); await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' });
@ -168,7 +167,7 @@ export default {
} }
const tweetid = response.id_str; const tweetid = response.id_str;
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'mcDotnamejeffDotxyz', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21']; const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
const TweetLink = `https://twitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`; const TweetLink = `https://twitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
// Im too lazy for now to make an entry in config.json // Im too lazy for now to make an entry in config.json

7871
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -19,7 +19,7 @@
"dependencies": { "dependencies": {
"@discordjs/rest": "^0.4.1", "@discordjs/rest": "^0.4.1",
"discord-api-types": "^0.33.5", "discord-api-types": "^0.33.5",
"discord.js": "^14.3.0", "discord.js": "^14.9.0",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"mariadb": "^3.0.1", "mariadb": "^3.0.1",
"mysql2": "^2.3.3", "mysql2": "^2.3.3",
@ -27,7 +27,7 @@
"safe-regex": "github:davisjam/safe-regex", "safe-regex": "github:davisjam/safe-regex",
"sequelize": "^6.21.3", "sequelize": "^6.21.3",
"turndown": "^7.1.1", "turndown": "^7.1.1",
"twit": "^2.2.11", "twit": "^1.1.20",
"ytpplus-node": "github:Supositware/ytpplus-node" "ytpplus-node": "github:Supositware/ytpplus-node"
}, },
"devDependencies": { "devDependencies": {
@ -35,6 +35,7 @@
"@babel/plugin-syntax-import-assertions": "^7.18.6", "@babel/plugin-syntax-import-assertions": "^7.18.6",
"@types/node": "^18.7.3", "@types/node": "^18.7.3",
"eslint": "^8.16.0", "eslint": "^8.16.0",
"sequelize-cli": "^6.4.1" "sequelize-cli": "^6.4.1",
"sqlite3": "^5.1.6"
} }
} }