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! 🎉' });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dotenv/config';
|
||||
import { Client, Collection, Events, GatewayIntentBits } from 'discord.js';
|
||||
import { loadCommandModules } from './utilities/commandModules.js';
|
||||
|
||||
@@ -42,4 +43,4 @@ client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
});
|
||||
|
||||
client.login(process.env.DISCORD_TOKEN);
|
||||
client.login(process.env.DISCORD_API_KEY);
|
||||
|
||||
132
src/music/music.js
Normal file
132
src/music/music.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { Interaction } from 'discord.js';
|
||||
|
||||
// Maps to track queues and voice connections per guild
|
||||
const queueMap = new Map(); // { guildId: [track1, track2, ...] }
|
||||
const connectionMap = new Map(); // { guildId: VoiceConnection }
|
||||
|
||||
const data = new SlashCommandBuilder()
|
||||
.setName('music')
|
||||
.setDescription('Perform ButlerBot music operations.')
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand
|
||||
.setName('play')
|
||||
.setDescription('Play a track.')
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('track')
|
||||
.setDescription('The track to play.')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('provider')
|
||||
.setDescription('The provider of the track.')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: 'YouTube', value: 'youtube' },
|
||||
{ name: 'Spotify', value: 'spotify' },
|
||||
{ name: 'Local', value: 'local' }
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('pause').setDescription('Pause the current track.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('resume').setDescription('Resume the current track.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('skip').setDescription('Skip the current track.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('stop').setDescription('Stop the current track.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('queue').setDescription('Show the current track queue.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('clear').setDescription('Clear the current track queue.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('leave').setDescription('Leave the voice channel.')
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand.setName('join').setDescription('Join the voice channel.')
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles the music command.
|
||||
* @param {Interaction} interaction The interaction object.
|
||||
*/
|
||||
async function execute(interaction) {
|
||||
await interaction.deferReply();
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
const guildId = interaction.guild.id;
|
||||
|
||||
switch (subcommand) {
|
||||
case 'play':
|
||||
await handlePlay(interaction, guildId);
|
||||
break;
|
||||
case 'pause':
|
||||
await handlePause(interaction, guildId);
|
||||
break;
|
||||
case 'resume':
|
||||
await handleResume(interaction, guildId);
|
||||
break;
|
||||
case 'skip':
|
||||
await handleSkip(interaction, guildId);
|
||||
break;
|
||||
case 'stop':
|
||||
await handleStop(interaction, guildId);
|
||||
break;
|
||||
case 'queue':
|
||||
await handleQueue(interaction, guildId);
|
||||
break;
|
||||
case 'clear':
|
||||
await handleClear(interaction, guildId);
|
||||
break;
|
||||
case 'leave':
|
||||
await handleLeave(interaction, guildId);
|
||||
break;
|
||||
case 'join':
|
||||
await handleJoin(interaction, guildId);
|
||||
break;
|
||||
default:
|
||||
await interaction.editReply('Invalid music subcommand.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the play subcommand.
|
||||
* @param {Interaction} interaction The interaction object.
|
||||
* @param {string} guildId The guild ID.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
async function handlePlay(interaction, guildId) {
|
||||
const track = interaction.options.getString('track');
|
||||
const provider = interaction.options.getString('provider') || 'youtube';
|
||||
|
||||
// Check if the user is in a voice channel
|
||||
const voiceChannel = interaction.member.voice.channel;
|
||||
if (!voiceChannel) {
|
||||
await interaction.editReply(
|
||||
'You need to be in a voice channel to play music.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the queue for the guild
|
||||
const queue = queueMap.get(guildId) || [];
|
||||
queue.push({ track, provider });
|
||||
queueMap.set(guildId, queue);
|
||||
|
||||
// Join the voice channel
|
||||
await handleJoin(interaction, guildId);
|
||||
|
||||
// Play the track
|
||||
await interaction.editReply(`Playing track: ${track}`);
|
||||
}
|
||||
|
||||
export default { data, execute };
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dotenv/config';
|
||||
import { REST, Routes } from 'discord.js';
|
||||
import { loadCommandModules } from './utilities/commandModules.js';
|
||||
|
||||
@@ -7,7 +8,7 @@ const commands = commandModules.map((commandModule) =>
|
||||
commandModule.data.toJSON()
|
||||
);
|
||||
|
||||
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
|
||||
const rest = new REST().setToken(process.env.DISCORD_API_KEY);
|
||||
|
||||
try {
|
||||
console.log('Started refreshing application (/) commands.');
|
||||
@@ -19,6 +20,8 @@ try {
|
||||
);
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||
// Exit the process.
|
||||
process.exit();
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.error(error);
|
||||
|
||||
37
src/utils/db/game.js
Normal file
37
src/utils/db/game.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
if (!process.env.MONGODB_URI) {
|
||||
throw new Error('MONGODB_URI is not set in the environment variables.');
|
||||
}
|
||||
|
||||
const client = new MongoClient(process.env.MONGODB_URI);
|
||||
await client.connect();
|
||||
|
||||
const database = client.db('butler_db');
|
||||
const collection = database.collection('GameFeatures');
|
||||
|
||||
export async function listGameNames() {
|
||||
return collection.find({}, { projection: { game: 1 } }).toArray();
|
||||
}
|
||||
|
||||
export async function getGame(game) {
|
||||
return collection.findOne({ game });
|
||||
}
|
||||
|
||||
export async function setGame(game, key, value) {
|
||||
// If game is not found, create a new document with game field set to game, and key field set to value.
|
||||
// Overwrite the value of the key field if it already exists.
|
||||
return collection.updateOne(
|
||||
{ game },
|
||||
{ $set: { game, [key]: value } },
|
||||
{ upsert: true }
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteGame(game) {
|
||||
return collection.deleteOne({ game });
|
||||
}
|
||||
|
||||
export async function deleteField(game, key) {
|
||||
return collection.updateOne({ game }, { $unset: { [key]: '' } });
|
||||
}
|
||||
Reference in New Issue
Block a user