Add features
This commit is contained in:
99
src/commands/ai/image.js
Normal file
99
src/commands/ai/image.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js';
|
||||
import axios from 'axios';
|
||||
import Replicate from 'replicate';
|
||||
|
||||
if (!process.env.REPLICATE_API_KEY) {
|
||||
throw new Error('REPLICATE_API_KEY is not set in the environment variables.');
|
||||
}
|
||||
|
||||
const replicate = new Replicate({
|
||||
auth: process.env.REPLICATE_API_KEY,
|
||||
});
|
||||
|
||||
const data = new SlashCommandBuilder()
|
||||
.setName('image')
|
||||
.setDescription('Generate an image based on a prompt.')
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('prompt')
|
||||
.setDescription('The prompt to generate an image from')
|
||||
.setRequired(true)
|
||||
);
|
||||
|
||||
// Helper function to poll the prediction status
|
||||
async function pollPredictionStatus(
|
||||
predictionId,
|
||||
maxAttempts = 5,
|
||||
interval = 2000
|
||||
) {
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
const latestPrediction = await replicate.predictions.get(predictionId);
|
||||
|
||||
if (
|
||||
latestPrediction.status !== 'starting' &&
|
||||
latestPrediction.status !== 'processing'
|
||||
) {
|
||||
return latestPrediction;
|
||||
}
|
||||
|
||||
// Wait before checking again
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
|
||||
throw new Error('Prediction timed out.');
|
||||
}
|
||||
|
||||
// Helper function to download the image from a URL
|
||||
async function downloadImage(url) {
|
||||
try {
|
||||
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
return Buffer.from(response.data);
|
||||
} catch (error) {
|
||||
throw new Error('Failed to download the image.');
|
||||
}
|
||||
}
|
||||
|
||||
async function execute(interaction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
if (!process.env.REPLICATE_API_KEY) {
|
||||
await interaction.reply('The bot is missing the Replicate API key.');
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = interaction.options.getString('prompt');
|
||||
|
||||
try {
|
||||
// Create image generation prediction
|
||||
const prediction = await replicate.predictions.create({
|
||||
model: 'black-forest-labs/flux-schnell',
|
||||
input: { prompt },
|
||||
});
|
||||
|
||||
// Poll until the image generation is complete
|
||||
const completedPrediction = await pollPredictionStatus(prediction.id);
|
||||
|
||||
if (!completedPrediction || !completedPrediction.output) {
|
||||
throw new Error('Failed to generate the image.');
|
||||
}
|
||||
|
||||
const imageUrl = completedPrediction.output[0];
|
||||
|
||||
// Download the generated image
|
||||
const imageBuffer = await downloadImage(imageUrl);
|
||||
|
||||
// Create an attachment to send the image back to the user
|
||||
const attachment = new AttachmentBuilder(imageBuffer, {
|
||||
name: 'image.png',
|
||||
});
|
||||
|
||||
// Edit the deferred reply to include the generated image
|
||||
await interaction.editReply({ files: [attachment] });
|
||||
} catch (error) {
|
||||
// Provide a more informative error message to the user
|
||||
console.error(error); // Log the error for debugging purposes
|
||||
await interaction.editReply(`An error occurred: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default { data, execute };
|
||||
207
src/commands/db/game.js
Normal file
207
src/commands/db/game.js
Normal file
@@ -0,0 +1,207 @@
|
||||
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||
import {
|
||||
listGameNames,
|
||||
getGame,
|
||||
setGame,
|
||||
deleteGame,
|
||||
deleteField,
|
||||
} from '../../utils/db/game.js';
|
||||
|
||||
const data = new SlashCommandBuilder()
|
||||
.setName('game')
|
||||
.setDescription('Perform ButlerBot game database operations.')
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('list').setDescription('List all games in the database.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand
|
||||
.setName('get')
|
||||
.setDescription('Get a game from the database.')
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('game')
|
||||
.setDescription('The name of the game to get.')
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand
|
||||
.setName('set')
|
||||
.setDescription('Set a key value pair on an game in the database.')
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('game')
|
||||
.setDescription('The name of the game to set the field in.')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('key')
|
||||
.setDescription('The key or label to set.')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('value')
|
||||
.setDescription('The value of the field to set.')
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(1024)
|
||||
)
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand
|
||||
.setName('delete')
|
||||
.setDescription('Delete a game, or a field from a game.')
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('game')
|
||||
.setDescription(
|
||||
'The name of the game to remove, or remove a field from.'
|
||||
)
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('key')
|
||||
.setDescription('The key or label of the field to remove.')
|
||||
.setRequired(false)
|
||||
)
|
||||
);
|
||||
|
||||
function createEmbedFromGame(game) {
|
||||
const embed = new EmbedBuilder().setTitle(game.game).setColor(0xff0000);
|
||||
|
||||
// Add fields for each key value pair - skip name, _id and guild.
|
||||
embed.addFields(
|
||||
Object.entries(game)
|
||||
.filter(([key]) => !['_id', 'game', 'guild'].includes(key))
|
||||
.map(([key, value]) => ({ name: key, value: String(value) }))
|
||||
);
|
||||
|
||||
if (embed.length > 6000) {
|
||||
throw new Error('Embed size exceeds maximum.');
|
||||
}
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
async function execute(interaction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
switch (subcommand) {
|
||||
case 'list': {
|
||||
const gameNames = await listGameNames();
|
||||
|
||||
if (gameNames.length === 0) {
|
||||
await interaction.editReply('No games found in the database.');
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Game Database')
|
||||
.setColor(0xff0000)
|
||||
.setDescription('List of games in the database.');
|
||||
|
||||
embed.addFields({
|
||||
name: 'Games',
|
||||
value: gameNames.map((game) => game.game).join('\n'),
|
||||
});
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
break;
|
||||
}
|
||||
|
||||
case 'get': {
|
||||
const gameName = interaction.options.getString('game');
|
||||
|
||||
const game = await getGame(gameName);
|
||||
|
||||
if (!game) {
|
||||
await interaction.editReply(
|
||||
`Game ${gameName} not found in the database.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const embed = createEmbedFromGame(game);
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
await interaction.editReply(
|
||||
'Embed too large to send. Game has not been retrieved.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set': {
|
||||
const gameName = interaction.options.getString('game');
|
||||
const key = interaction.options.getString('key');
|
||||
const value = interaction.options.getString('value');
|
||||
|
||||
const oldGame = await getGame(gameName);
|
||||
await setGame(gameName, key, value);
|
||||
const updatedGame = await getGame(gameName);
|
||||
|
||||
try {
|
||||
const embed = createEmbedFromGame(updatedGame);
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
// Revert game back to original state if the embed is too large.
|
||||
if (oldGame) {
|
||||
// If key already existed, revert to old value, else delete the key.
|
||||
if (oldGame[key]) {
|
||||
await setGame(gameName, key, oldGame[key]);
|
||||
} else {
|
||||
await deleteField(gameName, key);
|
||||
}
|
||||
}
|
||||
await interaction.editReply(
|
||||
'Embed too large to send. Game has not been updated.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
const gameName = interaction.options.getString('game');
|
||||
const key = interaction.options.getString('key');
|
||||
|
||||
if (key) {
|
||||
await deleteField(gameName, key);
|
||||
const updatedGame = await getGame(gameName);
|
||||
|
||||
if (!updatedGame) {
|
||||
await interaction.editReply(
|
||||
`Game ${gameName} deleted from the database.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const embed = createEmbedFromGame(updatedGame);
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
await interaction.editReply(
|
||||
'Embed too large to send. Field deleted from game but game has not been retrieved.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await deleteGame(gameName);
|
||||
await interaction.editReply(
|
||||
`Game ${gameName} deleted from the database.`
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
await interaction.editReply('Unknown subcommand.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export default { data, execute };
|
||||
@@ -1,6 +1,10 @@
|
||||
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
|
||||
import axios from 'axios';
|
||||
|
||||
if (!process.env.OMDB_API_KEY) {
|
||||
throw new Error('OMDB_API_KEY is not set in the environment variables.');
|
||||
}
|
||||
|
||||
const OMDB_API_URL = 'http://www.omdbapi.com/?apikey={apiKey}';
|
||||
const FIELDS = [
|
||||
'Title',
|
||||
@@ -32,40 +36,65 @@ const data = new SlashCommandBuilder()
|
||||
.setMaxValue(2100)
|
||||
);
|
||||
|
||||
async function execute(interaction) {
|
||||
const filmName = interaction.options.getString('film_name');
|
||||
const filmYear = interaction.options.getInteger('film_year');
|
||||
let embed = new EmbedBuilder().setTitle('IMDB').setColor(0xf5de50);
|
||||
|
||||
let omdbQueryUrl = OMDB_API_URL.replace('{apiKey}', process.env.OMDB_API_KEY);
|
||||
omdbQueryUrl += `&t=${encodeURIComponent(filmName.toLowerCase())}`;
|
||||
|
||||
// Helper function to construct OMDB query URL
|
||||
function buildOmdbUrl(filmName, filmYear) {
|
||||
let url = OMDB_API_URL.replace('{apiKey}', process.env.OMDB_API_KEY);
|
||||
url += `&t=${encodeURIComponent(filmName.toLowerCase())}`;
|
||||
if (filmYear) {
|
||||
omdbQueryUrl += `&y=${filmYear}`;
|
||||
url += `&y=${filmYear}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(omdbQueryUrl, { timeout: 5000 });
|
||||
const result = response.data;
|
||||
async function execute(interaction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
FIELDS.forEach((field) => {
|
||||
const fieldValue = result[field];
|
||||
if (fieldValue) {
|
||||
embed.addFields({ name: field, value: fieldValue });
|
||||
}
|
||||
});
|
||||
|
||||
const poster = result.Poster;
|
||||
if (poster) {
|
||||
embed.setImage(poster);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error looking up film: ${filmName}.`, error);
|
||||
await interaction.reply('An error occurred when querying the OMDB API.');
|
||||
if (!process.env.OMDB_API_KEY) {
|
||||
await interaction.editReply('The bot is missing the OMDB API key.');
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
const filmName = interaction.options.getString('film_name');
|
||||
const filmYear = interaction.options.getInteger('film_year');
|
||||
|
||||
try {
|
||||
const omdbQueryUrl = buildOmdbUrl(filmName, filmYear);
|
||||
const response = await axios.get(omdbQueryUrl, { timeout: 5000 });
|
||||
const result = response.data;
|
||||
|
||||
// Check if the movie was found
|
||||
if (result.Response === 'False') {
|
||||
await interaction.editReply(
|
||||
`No results found for the film: ${filmName}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the embed with film details
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(result.Title || 'IMDB Film Info')
|
||||
.setColor(0xf5de50);
|
||||
|
||||
// Add the fields to the embed
|
||||
FIELDS.forEach((field) => {
|
||||
if (result[field]) {
|
||||
embed.addFields({ name: field, value: result[field], inline: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Add poster image if available
|
||||
if (result.Poster && result.Poster !== 'N/A') {
|
||||
embed.setImage(result.Poster);
|
||||
}
|
||||
|
||||
// Send the result to the user
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
console.error(`Error looking up film: ${filmName}`, error);
|
||||
await interaction.editReply(
|
||||
'An error occurred when querying the OMDB API.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default { data, execute };
|
||||
|
||||
@@ -5,6 +5,10 @@ const API_URL = 'https://my-api.plantnet.org/v2/identify/all?api-key={apiKey}';
|
||||
const LEAF_THUMBNAIL =
|
||||
'https://cdn.discordapp.com/attachments/870024275556446328/1006249009201033287/monstera.png';
|
||||
|
||||
if (!process.env.PLANTNET_API_KEY) {
|
||||
throw new Error('PLANTNET_API_KEY is not set in the environment variables.');
|
||||
}
|
||||
|
||||
const data = new SlashCommandBuilder()
|
||||
.setName('plant')
|
||||
.setDescription('Identify a plant by uploading an image.')
|
||||
@@ -15,14 +19,48 @@ const data = new SlashCommandBuilder()
|
||||
.setRequired(true)
|
||||
);
|
||||
|
||||
// Helper function to generate plant details
|
||||
function generatePlantDetail(commonName, scientificName, confidence) {
|
||||
let detail = '';
|
||||
|
||||
if (0.0 <= confidence && confidence < 0.25) {
|
||||
detail = `I'm really not sure, but I think that looks like a **${commonName}**?`;
|
||||
} else if (0.25 <= confidence && confidence < 0.5) {
|
||||
detail = `I think that looks like a **${commonName}**!`;
|
||||
} else if (0.5 <= confidence && confidence < 0.75) {
|
||||
detail = `That looks like a **${commonName}**!`;
|
||||
} else if (confidence >= 0.75) {
|
||||
detail = `That is a really cool **${commonName}**!`;
|
||||
}
|
||||
|
||||
const imageUrl = `https://www.google.com/search?q=${encodeURIComponent(
|
||||
commonName || scientificName
|
||||
)}&tbm=isch`;
|
||||
|
||||
detail += `\n\n**Scientific Name**: ${scientificName}`;
|
||||
detail += `\n\n**Images**: [More images](${imageUrl})`;
|
||||
detail += `\n\n**Confidence**: ${(confidence * 100).toFixed(3)}%`;
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
async function execute(interaction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
if (!process.env.PLANTNET_API_KEY) {
|
||||
await interaction.editReply('The bot is missing the PlantNet API key.');
|
||||
return;
|
||||
}
|
||||
|
||||
const image = interaction.options.getAttachment('image');
|
||||
let embed = new EmbedBuilder()
|
||||
.setTitle('Plant Detector™️')
|
||||
.setThumbnail(LEAF_THUMBNAIL)
|
||||
.setColor(0xff0000);
|
||||
|
||||
try {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Plant Detector™️')
|
||||
.setThumbnail(LEAF_THUMBNAIL)
|
||||
.setColor(0xff0000);
|
||||
|
||||
// API call to identify the plant
|
||||
const response = await axios.get(
|
||||
API_URL.replace('{apiKey}', process.env.PLANTNET_API_KEY),
|
||||
{
|
||||
@@ -35,41 +73,38 @@ async function execute(interaction) {
|
||||
);
|
||||
|
||||
const bestMatch = response.data.results[0];
|
||||
|
||||
// Check if there is a valid result
|
||||
if (!bestMatch) {
|
||||
await interaction.editReply(
|
||||
'No plant could be identified from the image.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const commonName =
|
||||
bestMatch.species.commonNames[0] || 'No common name found';
|
||||
const scientificName = bestMatch.species.scientificNameWithoutAuthor;
|
||||
const confidence = parseFloat(bestMatch.score);
|
||||
|
||||
let detail = '';
|
||||
|
||||
if (0.0 <= confidence && confidence < 0.25) {
|
||||
detail = `I'm really not sure, but I think that looks like a **${commonName}**?`;
|
||||
} else if (0.25 <= confidence && confidence < 0.5) {
|
||||
detail = `I think that looks like a **${commonName}**!`;
|
||||
} else if (0.5 <= confidence && confidence < 0.75) {
|
||||
detail = `That looks like a **${commonName}**!`;
|
||||
} else if (confidence >= 0.75) {
|
||||
detail = `That is a really cool **${commonName}**!`;
|
||||
}
|
||||
|
||||
const imageUrl = `https://www.google.com/search?q=${encodeURIComponent(commonName || scientificName)}&tbm=isch`;
|
||||
|
||||
detail += `\n\n**Scientific Name**: ${scientificName}`;
|
||||
detail += `\n\n**Images**: [More images](${imageUrl})`;
|
||||
detail += `\n\n**Confidence**: ${(confidence * 100).toFixed(3)}%`;
|
||||
const plantDetails = generatePlantDetail(
|
||||
commonName,
|
||||
scientificName,
|
||||
confidence
|
||||
);
|
||||
|
||||
embed
|
||||
.setColor(0x00ff00)
|
||||
.addFields({ name: 'Plant Details', value: detail });
|
||||
.addFields({ name: 'Plant Details', value: plantDetails });
|
||||
|
||||
// Send the result to the user
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
console.error(`Error looking up plant: ${image.url}`, error);
|
||||
await interaction.reply(
|
||||
console.error(error); // Log the error for debugging
|
||||
await interaction.editReply(
|
||||
'An error occurred when querying the PlantNet API.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
export default { data, execute };
|
||||
|
||||
@@ -7,33 +7,42 @@ const data = new SlashCommandBuilder()
|
||||
.setName('eyecandy')
|
||||
.setDescription('Returns a random gif of Gerard Butler.');
|
||||
|
||||
async function execute(interaction) {
|
||||
const giphyApiKey = process.env.GIPHY_API_KEY;
|
||||
|
||||
if (!giphyApiKey) {
|
||||
await interaction.reply(
|
||||
'The bot has not been configured with a Giphy API key.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
async function fetchGif() {
|
||||
const randomOffset = Math.floor(Math.random() * 100);
|
||||
const giphyQueryUrl = `${GIPHY_API_URL}?api_key=${giphyApiKey}&q=gerard+butler&limit=1&offset=${randomOffset}`;
|
||||
const giphyQueryUrl = `${GIPHY_API_URL}?api_key=${process.env.GIPHY_API_KEY}&q=gerard+butler&limit=1&offset=${randomOffset}`;
|
||||
|
||||
try {
|
||||
const response = await axios.get(giphyQueryUrl, { timeout: 5000 });
|
||||
const result = response.data;
|
||||
|
||||
if (result.data.length === 0) {
|
||||
await interaction.reply('No gifs found.');
|
||||
return;
|
||||
throw new Error('No gifs found for the given query.');
|
||||
}
|
||||
|
||||
const imageUrl = result.data[0].images.original.url;
|
||||
await interaction.reply(imageUrl);
|
||||
return result.data[0].images.original.url;
|
||||
} catch (error) {
|
||||
console.error('Error querying the Giphy API:', error);
|
||||
await interaction.reply('An error occurred when querying the Giphy API.');
|
||||
console.error('Error fetching GIF:', error.message);
|
||||
throw new Error('Failed to retrieve a GIF from Giphy.');
|
||||
}
|
||||
}
|
||||
|
||||
async function execute(interaction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
if (!process.env.GIPHY_API_KEY) {
|
||||
await interaction.reply('The bot is missing the GIPHY API key.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch a random gif of Gerard Butler.
|
||||
const gifUrl = await fetchGif();
|
||||
|
||||
// Reply with the gif.
|
||||
await interaction.editReply(gifUrl);
|
||||
} catch (error) {
|
||||
console.error('Error executing the command:', error.message);
|
||||
await interaction.editReply(`An error occurred: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,19 +5,15 @@ const data = new SlashCommandBuilder()
|
||||
.setDescription('Start a five second countdown.');
|
||||
|
||||
async function execute(interaction) {
|
||||
// Initial response to acknowledge the command
|
||||
await interaction.reply({ content: 'Starting countdown...' });
|
||||
|
||||
// Initial delay before starting the countdown
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000)); // 2-second delay
|
||||
|
||||
// Edit the response with the countdown
|
||||
for (let i = 5; i > 0; i--) {
|
||||
await interaction.editReply({ content: String(i) });
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000)); // 1-second delay between numbers
|
||||
}
|
||||
|
||||
// Final message
|
||||
await interaction.editReply({ content: '🎉 GO! 🎉' });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user