Convert to Typescript

This commit is contained in:
2024-09-16 09:56:40 +00:00
parent a89150fc2f
commit 6a59118f4a
42 changed files with 2916 additions and 785 deletions

View File

@@ -4,3 +4,4 @@ GIPHY_API_KEY=
PLANTNET_API_KEY=
OMDB_API_KEY=
REPLICATE_API_KEY=
MONGODB_URI=

View File

@@ -13,6 +13,9 @@ USER nodejs
# Install
RUN npm install
# Build
RUN npm run build
# Start the app
ENTRYPOINT ["npm", "run"]
CMD ["start"]

1869
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,12 @@
{
"name": "butlerbotng",
"version": "1.0.0",
"main": "src/index.js",
"type": "module",
"main": "dist/index.js",
"scripts": {
"start": "node src/index.js",
"register-slash-commands": "node src/register-slash-commands.js",
"start": "node dist/index.js",
"build": "tsup src/index.ts --minify",
"register-slash-commands": "tsx src/register-slash-commands.ts",
"dev": "tsx watch src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
@@ -26,6 +27,12 @@
"replicate": "^0.32.1"
},
"devDependencies": {
"eslint": "^9.9.1"
"@types/glob": "^8.1.0",
"@types/node": "^20.4.0",
"eslint": "^9.9.1",
"ts-node": "^10.9.1",
"tsup": "^8.2.4",
"tsx": "^4.19.1",
"typescript": "^5.6.2"
}
}

33
src/commands.ts Normal file
View File

@@ -0,0 +1,33 @@
import * as birthday from './commands/birthday';
import * as corrupt from './commands/corrupt';
import * as countdown from './commands/countdown';
import * as eyecandy from './commands/eyecandy';
import * as game from './commands/game';
import * as image from './commands/image';
import * as imdb from './commands/imdb';
import * as kanye from './commands/kanye';
import * as magicEightBall from './commands/magic8Ball';
import * as payday from './commands/payday';
import * as plant from './commands/plant';
import * as reminder from './commands/reminder';
import * as servertime from './commands/servertime';
import * as taylor from './commands/taylor';
import * as twentyTwenty from './commands/twentyTwenty';
export const commands = {
birthday,
corrupt,
countdown,
eyecandy,
game,
image,
imdb,
kanye,
magicEightBall,
payday,
plant,
reminder,
servertime,
taylor,
twentyTwenty,
};

View File

@@ -1,4 +1,4 @@
import { SlashCommandBuilder } from 'discord.js';
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import {
format,
differenceInYears,
@@ -10,11 +10,21 @@ import {
const BIRTHDAY_TIMESTAMP = 1582576229;
const data = new SlashCommandBuilder()
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('birthday')
.setDescription("Returns ButlerBot's Birthday information.");
async function execute(interaction) {
console.log(`Loaded ${data.name} command.`);
/**
* Responds with ButlerBot's age and the time remaining until the next birthday.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
const today = new Date();
const birthday = new Date(BIRTHDAY_TIMESTAMP * 1000);
@@ -56,5 +66,3 @@ async function execute(interaction) {
const fullMessage = `${ageMessage} ${birthdayMessage}`;
await interaction.reply(fullMessage);
}
export default { data, execute };

24
src/commands/corrupt.ts Normal file
View File

@@ -0,0 +1,24 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
const CORRUPT_IMAGE =
'https://media.discordapp.net/attachments/506852356898422797/717395817626861638/isntthatcorrupt2.PNG';
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('corrupt')
.setDescription(
"Returns a powerful quote from Neil Breen's infamous indie film, Pass Thru (2016)."
);
console.log(`Loaded ${data.name} command.`);
/**
* Replies with an image from Neil Breen's Pass Thru (2016).
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
await interaction.reply(CORRUPT_IMAGE);
}

31
src/commands/countdown.ts Normal file
View File

@@ -0,0 +1,31 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('countdown')
.setDescription('Start a five second countdown.');
console.log(`Loaded ${data.name} command.`);
/**
* Start a five second countdown and send updates to the user.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the countdown is finished.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
await interaction.reply({ content: 'Starting countdown...' });
// 2-second delay before countdown starts
await new Promise((resolve) => setTimeout(resolve, 2000));
// Countdown from 5 to 1
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
}
// Notify the user that the countdown is over
await interaction.editReply({ content: '🎉 GO! 🎉' });
}

View File

@@ -1,32 +1,23 @@
import { SlashCommandBuilder } from 'discord.js';
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import axios from 'axios';
const GIPHY_API_URL = 'http://api.giphy.com/v1/gifs/search';
const data = new SlashCommandBuilder()
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('eyecandy')
.setDescription('Returns a random gif of Gerard Butler.');
async function fetchGif() {
const randomOffset = Math.floor(Math.random() * 100);
const giphyQueryUrl = `${GIPHY_API_URL}?api_key=${process.env.GIPHY_API_KEY}&q=gerard+butler&limit=1&offset=${randomOffset}`;
console.log(`Loaded ${data.name} command.`);
try {
const response = await axios.get(giphyQueryUrl, { timeout: 5000 });
const result = response.data;
if (result.data.length === 0) {
throw new Error('No gifs found for the given query.');
}
return result.data[0].images.original.url;
} catch (error) {
console.error('Error fetching GIF:', error.message);
throw new Error('Failed to retrieve a GIF from Giphy.');
}
}
async function execute(interaction) {
/**
* Execute the command, fetch a random Gerard Butler gif, and send it to the user.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
await interaction.deferReply();
if (!process.env.GIPHY_API_KEY) {
@@ -40,10 +31,32 @@ async function execute(interaction) {
// Reply with the gif.
await interaction.editReply(gifUrl);
} catch (error) {
} catch (error: any) {
console.error('Error executing the command:', error.message);
await interaction.editReply(`An error occurred: ${error.message}`);
}
}
export default { data, execute };
/**
* Fetch a random gif of Gerard Butler from Giphy.
* @returns A promise that resolves to the URL of the gif.
* @throws An error if no gif is found or if the request fails.
*/
async function fetchGif(): Promise<string> {
const randomOffset = Math.floor(Math.random() * 100);
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) {
throw new Error('No gifs found for the given query.');
}
return result.data[0].images.original.url;
} catch (error: any) {
console.error('Error fetching GIF:', error.message);
throw new Error('Failed to retrieve a GIF from Giphy.');
}
}

View File

@@ -1,13 +1,18 @@
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
import {
SlashCommandBuilder,
EmbedBuilder,
ChatInputCommandInteraction,
} from 'discord.js';
import {
listGameNames,
getGame,
setGame,
deleteGame,
deleteField,
} from '../../utils/db/game.js';
} from './utils/db/game';
const data = new SlashCommandBuilder()
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('game')
.setDescription('Perform ButlerBot game database operations.')
.addSubcommand((subcommand) =>
@@ -27,7 +32,7 @@ const data = new SlashCommandBuilder()
.addSubcommand((subcommand) =>
subcommand
.setName('set')
.setDescription('Set a key value pair on an game in the database.')
.setDescription('Set a key value pair on a game in the database.')
.addStringOption((option) =>
option
.setName('game')
@@ -69,24 +74,16 @@ const data = new SlashCommandBuilder()
)
);
function createEmbedFromGame(game) {
const embed = new EmbedBuilder().setTitle(game.game).setColor(0xff0000);
console.log(`Loaded ${data.name} command.`);
// 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) {
/**
* Handle the interaction for the game command.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
await interaction.deferReply();
const subcommand = interaction.options.getSubcommand();
@@ -115,7 +112,7 @@ async function execute(interaction) {
}
case 'get': {
const gameName = interaction.options.getString('game');
const gameName = interaction.options.getString('game', true);
const game = await getGame(gameName);
@@ -138,9 +135,9 @@ async function execute(interaction) {
}
case 'set': {
const gameName = interaction.options.getString('game');
const key = interaction.options.getString('key');
const value = interaction.options.getString('value');
const gameName = interaction.options.getString('game', true);
const key = interaction.options.getString('key', true);
const value = interaction.options.getString('value', true);
const oldGame = await getGame(gameName);
await setGame(gameName, key, value);
@@ -167,7 +164,7 @@ async function execute(interaction) {
}
case 'delete': {
const gameName = interaction.options.getString('game');
const gameName = interaction.options.getString('game', true);
const key = interaction.options.getString('key');
if (key) {
@@ -204,4 +201,24 @@ async function execute(interaction) {
}
}
export default { data, execute };
/**
* Create an embed from a game object.
* @param game The game object containing key-value pairs.
* @returns The embed object to be sent in the interaction.
*/
function createEmbedFromGame(game: any): EmbedBuilder {
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;
}

View File

@@ -1,16 +1,18 @@
import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js';
import {
SlashCommandBuilder,
AttachmentBuilder,
ChatInputCommandInteraction,
} 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.');
}
import Replicate, { Prediction } from 'replicate';
import config from '../config';
const replicate = new Replicate({
auth: process.env.REPLICATE_API_KEY,
auth: config.replicateApiKey,
});
const data = new SlashCommandBuilder()
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('image')
.setDescription('Generate an image based on a prompt.')
.addStringOption((option) =>
@@ -20,48 +22,19 @@ const data = new SlashCommandBuilder()
.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);
console.log(`Loaded ${data.name} command.`);
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) {
/**
* Generate an image based on a prompt and send it back to the user.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
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');
const prompt = interaction.options.get('prompt')?.value as string;
try {
// Create image generation prediction
@@ -89,11 +62,52 @@ async function execute(interaction) {
// Edit the deferred reply to include the generated image
await interaction.editReply({ files: [attachment] });
} catch (error) {
} catch (error: any) {
// 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 };
/**
* Poll the status of a prediction until it is no longer in progress.
* @param predictionId The ID of the prediction to poll.
* @param maxAttempts The maximum number of attempts to poll the prediction.
* @param interval The interval between each polling attempt in milliseconds.
* @returns The final status of the prediction.
*/
async function pollPredictionStatus(
predictionId: string,
maxAttempts = 5,
interval = 2000
): Promise<Prediction> {
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.');
}
/**
* Download an image from a URL and return it as a Buffer.
* @param url The URL of the image to download.
* @returns A Buffer containing the downloaded image.
*/
async function downloadImage(url: string): Promise<Buffer> {
try {
const response = await axios.get(url, { responseType: 'arraybuffer' });
return Buffer.from(response.data);
} catch (error) {
throw new Error('Failed to download the image.');
}
}

View File

@@ -1,11 +1,12 @@
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import {
EmbedBuilder,
SlashCommandBuilder,
ChatInputCommandInteraction,
} from 'discord.js';
import axios from 'axios';
import config from '../config';
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 OMDB_API_URL = `http://www.omdbapi.com/?apikey=${config.omdbApiKey}`;
const FIELDS = [
'Title',
'Year',
@@ -19,7 +20,7 @@ const FIELDS = [
'BoxOffice',
];
const data = new SlashCommandBuilder()
export const data = new SlashCommandBuilder()
.setName('imdb')
.setDescription('Return IMDB listing for the specified film.')
.addStringOption((option) =>
@@ -36,26 +37,20 @@ const data = new SlashCommandBuilder()
.setMaxValue(2100)
);
// 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) {
url += `&y=${filmYear}`;
}
return url;
}
console.log(`Loaded ${data.name} command.`);
async function execute(interaction) {
/**
* Executes the 'imdb' command, retrieving film data from the OMDB API and sending it to the user.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
await interaction.deferReply();
if (!process.env.OMDB_API_KEY) {
await interaction.editReply('The bot is missing the OMDB API key.');
return;
}
const filmName = interaction.options.getString('film_name');
const filmYear = interaction.options.getInteger('film_year');
const filmName = interaction.options.getString('film_name') as string;
const filmYear = interaction.options.getInteger('film_year') ?? undefined;
try {
const omdbQueryUrl = buildOmdbUrl(filmName, filmYear);
@@ -89,7 +84,7 @@ async function execute(interaction) {
// Send the result to the user
await interaction.editReply({ embeds: [embed] });
} catch (error) {
} catch (error: any) {
console.error(`Error looking up film: ${filmName}`, error);
await interaction.editReply(
'An error occurred when querying the OMDB API.'
@@ -97,4 +92,16 @@ async function execute(interaction) {
}
}
export default { data, execute };
/**
* Helper function to construct OMDB query URL.
* @param filmName The name of the film.
* @param filmYear The year the film was released.
* @returns The OMDB API URL with the specified query parameters.
*/
function buildOmdbUrl(filmName: string, filmYear?: number): string {
let url = OMDB_API_URL + `&t=${encodeURIComponent(filmName.toLowerCase())}`;
if (filmYear) {
url += `&y=${filmYear}`;
}
return url;
}

23
src/commands/kanye.ts Normal file
View File

@@ -0,0 +1,23 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import kanyeQuotes from './utils/constants/kanyeQuotes.json';
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('kanye')
.setDescription('Returns a Kanye West quote.');
console.log(`Loaded ${data.name} command.`);
/**
* Returns a random Kanye West quote.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
const randomIndex = Math.floor(Math.random() * kanyeQuotes.length);
const response = kanyeQuotes[randomIndex];
await interaction.reply(response);
}

View File

@@ -1,56 +0,0 @@
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import axios from 'axios';
const GODADDY_API_URL = 'https://api.godaddy.com/v1/domains/available';
const data = new SlashCommandBuilder()
.setName('domain')
.setDescription('Check the availability and price of a domain name.')
.addStringOption((option) =>
option
.setName('domain')
.setDescription('The domain to check')
.setRequired(true)
);
async function execute(interaction) {
const domain = interaction.options.getString('domain');
let embed = new EmbedBuilder().setTitle('Domain checker');
try {
const response = await axios.get(GODADDY_API_URL, {
params: { domain: domain },
headers: { Authorization: `sso-key ${process.env.GODADDY_API_KEY}` },
timeout: 5000,
});
console.log(`Got response for domain: ${domain}.`, response.data);
const result = response.data;
let isDomainAvailable = 'No.';
let color = 0xff0000;
let price = null;
if (response.status === 200 && result.available) {
isDomainAvailable = 'Yes!';
color = 0x00ff00;
price = (result.price / 1000000).toFixed(2);
}
embed.setColor(color).addFields({
name: 'Is the domain available?',
value: isDomainAvailable,
});
if (price) {
embed.addFields({ name: 'Price', value: `$${price}` });
}
} catch (error) {
console.error(`Error looking up domain: ${domain}.`, error);
await interaction.reply('An error occurred when querying the GoDaddy API.');
return;
}
await interaction.reply({ embeds: [embed] });
}
export default { data, execute };

View File

@@ -0,0 +1,31 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import magicEightBallResponses from './utils/constants/magicEightBallResponses.json';
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('magic8ball')
.setDescription('Returns a Magic 8 ball response.')
.addStringOption((option) =>
option
.setName('question')
.setDescription('The question to ask the Magic 8 ball')
.setRequired(true)
);
console.log(`Loaded ${data.name} command.`);
/**
* Returns a random response from the Magic 8 ball.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
const randomIndex = Math.floor(
Math.random() * magicEightBallResponses.length
);
const response = magicEightBallResponses[randomIndex];
await interaction.reply(response);
}

View File

@@ -1,14 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
const TWENTY_TWENTY_IMAGE =
'https://cdn.discordapp.com/attachments/506852356898422797/765256712063025172/unknown.png';
const data = new SlashCommandBuilder()
.setName('2020')
.setDescription('Returns a tweet from Boris, posted on 2 January 2020.');
async function execute(interaction) {
await interaction.reply(TWENTY_TWENTY_IMAGE);
}
export default { data, execute };

View File

@@ -1,45 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
const data = new SlashCommandBuilder()
.setName('8ball')
.setDescription('Returns a Magic 8 ball response.')
.addStringOption((option) =>
option
.setName('question')
.setDescription('The question to ask the Magic 8 ball')
.setRequired(true)
);
const MAGIC_EIGHT_BALL_RESPONSES = [
'As I see it, yes.',
'Ask again later.',
'Better not tell you now.',
'Cannot predict now.',
'Concentrate and ask again.',
"Don't count on it.",
'It is certain.',
'It is decidedly so.',
'Most likely.',
'My reply is no.',
'My sources say no.',
'Outlook not so good.',
'Outlook good.',
'Reply hazy, try again.',
'Signs point to yes.',
'Very doubtful.',
'Without a doubt.',
'Yes.',
'Yes - definitely.',
'You may rely on it.',
];
async function execute(interaction) {
const randomIndex = Math.floor(
Math.random() * MAGIC_EIGHT_BALL_RESPONSES.length
);
const response = MAGIC_EIGHT_BALL_RESPONSES[randomIndex];
await interaction.reply(response);
}
export default { data, execute };

View File

@@ -1,16 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
const CORRUPT_IMAGE =
'https://media.discordapp.net/attachments/506852356898422797/717395817626861638/isntthatcorrupt2.PNG';
const data = new SlashCommandBuilder()
.setName('corrupt')
.setDescription(
"Returns a powerful quote from Neil Breen's infamous indie film, Pass Thru (2016)."
);
async function execute(interaction) {
await interaction.reply(CORRUPT_IMAGE);
}
export default { data, execute };

View File

@@ -1,139 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
const KANYE_QUOTES = [
'2024.',
'All you have to be is yourself.',
'Believe in your flyness... conquer your shyness.',
'Burn that Excel spreadsheet.',
'Decentralize.',
'Distraction is the enemy of vision.',
'Everything you do in life stems from either fear or love.',
'For me giving up is way harder than trying.',
'For me, money is not my definition of success. Inspiring people is a definition of success.',
'Fur pillows are hard to actually sleep on.',
"George Bush doesn't care about black people.",
'Have you ever thought you were in love with someone but then realized you were just staring in a mirror for 20 minutes?',
'I care. I care about everything. Sometimes not giving a f#%k is caring the most.',
'I feel calm but energized.',
"I feel like I'm too busy writing history to read it.",
'I feel like me and Taylor might still have sex.',
'I give up drinking every week.',
'I leave my emojis Bart Simpson color.',
"I love sleep; it's my favorite.",
'I make awesome decisions in bike stores!!!',
"I really love my Tesla. I'm in the future. Thank you Elon.",
'I still think I am the greatest.',
"I think I do myself a disservice by comparing myself to Steve Jobs and Walt Disney and human beings that we've seen before. It should be more like Willy Wonka...and welcome to my chocolate factory.",
'I want the world to be better! All I want is positive! All I want is dopeness!',
'I wish I had a friend like me.',
"I'd like to meet with Tim Cook. I got some ideas.",
"I'll say things that are serious and put them in a joke form so people can enjoy them. We laugh to keep from crying.",
"I'm a creative genius.",
"I'm nice at ping pong.",
"I'm the best.",
"If I don't scream, if I don't say something then no one's going to say anything.",
'If I got any cooler I would freeze to death.',
"If you have the opportunity to play this game of life you need to appreciate every moment. a lot of people don't appreciate the moment until it's passed.",
'Just stop lying about shit. Just stop lying.',
'Keep squares out yo circle.',
'Keep your nose out the sky, keep your heart to god, and keep your face to the rising sun.',
"Let's be like water.",
'Man... whatever happened to my antique fish tank?',
'My dad got me a drone for Christmas.',
"My greatest award is what I'm about to do.",
'My greatest pain in life is that I will never be able to see myself perform live.',
"One day I'm gon' marry a porn star.",
"One of my favorite of many things about what the Trump hat represents to me is that people can't tell me what to do because I'm black.",
'Only free thinkers.',
"People always say that you can't please everybody. I think that's a cop-out. Why not attempt it? Cause think of all the people that you will please if you try.",
"People always tell you 'Be humble. Be humble.' When was the last time someone told you to be amazing? Be great! Be awesome! Be awesome!",
'People only get jealous when they care.',
'Perhaps I should have been more like water today.',
'Pulling up in the may bike.',
'Shut the fuck up I will fucking laser you with alien fucking eyes and explode your fucking head.',
'Sometimes I push the door close button on people running towards the elevator. I just need my own elevator sometimes. My sanctuary.',
'Sometimes you have to get rid of everything.',
'Style is genderless.',
'The thought police want to suppress freedom of thought.',
'The world is our family.',
'The world is our office.',
"Today is the best day ever and tomorrow's going to be even better.",
"Truth is my goal. Controversy is my gym. I'll do a hundred reps of controversy for a 6 pack of truth.",
'Tweeting is legal and also therapeutic.',
"We all self-conscious. I'm just the first to admit it.",
"We came into a broken world. And we're the cleanup crew.",
"You can't look at a glass half full or empty if it's overflowing.",
"I hate when I'm on a flight and I wake up with a water bottle next to me like oh great now I gotta be responsible for this water bottle.",
'All the musicians will be free.',
'Artists are founders.',
'Buy property.',
'Culture is the most powerful force in humanity under God.',
'Empathy is the glue.',
'I am one of the most famous people on the planet.',
'I am running for President of the United States.',
'I am the head of Adidas. I will bring Adidas and Puma back together and bring me and jay back together.',
'I am Warhol. I am the No. 1 most impactful artist of our generation. I am Shakespeare in the flesh.',
"I channel Will Ferrell when I'm at the daddy daughter dances.",
"I don't wanna see no woke tweets or hear no woke raps ... it's show time ... it's a whole different energy right now.",
'I hear people say this person is cool and this person is not cool. People are cool. Man has never invented anything as awesome as a an actual person but sometimes we value the objects we create over life itself.',
'I honestly need all my Royeres to be museum quality... if I see a fake Royere Ima have to Rick James your couch.',
'I love UZI. I be saying the same thing about Steve Jobs. I be feeling just like UZI.',
'I need an army of angels to cover me while I pull this sword out of the stone.',
'I spoke to Dave Chapelle for two hours this morning. He is our modern day Socrates.',
'I was just speaking with someone that told me their life story and they used to be homeless.',
'I watch Bladerunner on repeat.',
"I'm giving all Good music artists back the 50% share I have of their masters.",
"I'm going to personally see to it that Taylor Swift gets her masters back. Scooter is a close family friend.",
"I'm the new Moses.",
'Life is the ultimate gift.',
'Ma$e is one of my favorite rappers and I based a lot of my flows off of him.',
'Manga all day.',
"My first pillar when I'm on the board of adidas will be an adidas Nike collaboration to support community growth.",
"My mama was a' English teacher. I know how to use correct English but sometimes I just don't feel like it aaaand I ain't got to.",
'My memories are from the future.',
'My mother in law Kris Jenner ... makes the best music playlist.',
"People say it's enough and I got my point across ... the point isn't across until we cross the point.",
'People tried to talk me out of running for President. Never let weak controlling people kill your spirit.',
'So many of us need so much less than we have especially when so many of us are in need.',
"Speak God's truth to power.",
'The media tries to kill our heroes one at a time.',
'The world needs more Joy... this idea is super fresh.',
'There are 5 main pillars in a professional musicians business - Recording, Publishing, Touring, Merchandise & Name and likeness.',
'There are people sleeping in parking lots.',
"There's a crying need for civility across the board. We need to and will come together in the name of Jesus.",
"There's so many lonely emojis man.",
"Trust me ... I won't stop.",
'Two years ago we had 50 million people subscribed to music streaming services around the world. Today we have 400 million.',
'We are here to complete the revolution. We are building the future.',
'We as a people will heal. We will insure the well being of each other.',
'We have to evolve.',
'We must and will cure homelessness and hunger. We have the capability as a species.',
'We must form a union. We must unify.',
'We used to diss Michael Jackson the media made us call him crazy ... then they killed him.',
'We will be recognized.',
'We will change the paradigm.',
'We will cure hunger.',
'We will heal. We will cure.',
"We're going to move the entire music industry into the 21st Century.",
"We've gotten comfortable with not having what we deserve.",
'Who made up the term major label in the first place???',
'Winning is the only option.',
"For me to say I wasn't a genius I'd just be lying to you and to myself.",
"I've known my mom since I was zero years old. She is quite dope.",
"I don't expect to be understood at all.",
"I'm on the pursuit of awesomeness, excellence is the bare minimum.",
'You basically can say anything to someone on an email or text as long as you put LOL at the end.',
];
const data = new SlashCommandBuilder()
.setName('kanye')
.setDescription('Returns a random Kanye West quote.');
async function execute(interaction) {
const randomIndex = Math.floor(Math.random() * KANYE_QUOTES.length);
const quote = KANYE_QUOTES[randomIndex];
await interaction.reply(quote);
}
export default { data, execute };

View File

@@ -1,14 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
const REMINDER_IMAGE =
'https://media.discordapp.net/attachments/506852356898422797/715690132883111996/Capture.PNG';
const data = new SlashCommandBuilder()
.setName('reminder')
.setDescription('Returns an image which must always be remembered.');
async function execute(interaction) {
await interaction.reply(REMINDER_IMAGE);
}
export default { data, execute };

View File

@@ -1,6 +1,9 @@
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import {
format,
EmbedBuilder,
SlashCommandBuilder,
ChatInputCommandInteraction,
} from 'discord.js';
import {
addDays,
subDays,
getDay,
@@ -9,51 +12,27 @@ import {
set,
} from 'date-fns';
const ARM_EMPLOYEES = [141339536453140480];
const ARM_EMPLOYEES = ['141339536453140480'];
const ARM_THUMBNAIL =
'https://cdn.discordapp.com/attachments/724000975626698894/928379448301088868/unknown.png';
const BAE_THUMBNAIL =
'https://cdn.discordapp.com/attachments/724000975626698894/928380073965408306/Untitled-1.png';
// Adjust payday for weekends
function adjustPayday(payday) {
const dayOfWeek = getDay(payday);
if (dayOfWeek === 6) {
// Saturday
return subDays(payday, 1);
} else if (dayOfWeek === 0) {
// Sunday
return subDays(payday, 2);
}
return payday;
}
// Calculate next payday based on the current date
function calculateNextPayday(
payday = 22,
currentDate = new Date(),
adjust = 0
) {
let theoreticalPayday = set(currentDate, { date: payday });
let adjustedPayday = adjustPayday(theoreticalPayday);
if (adjustedPayday < currentDate) {
theoreticalPayday = set(addDays(currentDate, adjust * 30), {
date: payday,
});
adjustedPayday = adjustPayday(theoreticalPayday);
}
return adjustedPayday;
}
const data = new SlashCommandBuilder()
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('payday')
.setDescription('Calculate the number of days until the next payday.');
async function execute(interaction) {
console.log(`Loaded ${data.name} command.`);
/**
* Handle the payday command and reply with the number of days until the next payday.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
const authorId = interaction.user.id;
const embed = new EmbedBuilder().setTitle('Payday');
@@ -108,4 +87,46 @@ async function execute(interaction) {
await interaction.reply({ embeds: [embed] });
}
export default { data, execute };
/**
* Adjust payday if it falls on a weekend.
* @param payday The date of the payday.
* @returns The adjusted payday if necessary.
*/
function adjustPayday(payday: Date): Date {
const dayOfWeek = getDay(payday);
if (dayOfWeek === 6) {
// Saturday
return subDays(payday, 1);
} else if (dayOfWeek === 0) {
// Sunday
return subDays(payday, 2);
}
return payday;
}
/**
* Calculate the next payday based on the current date.
* @param payday The day of the month for payday.
* @param currentDate The current date to compare with.
* @param adjust Number of months to adjust the payday.
* @returns The next payday date.
*/
function calculateNextPayday(
payday = 22,
currentDate = new Date(),
adjust = 0
): Date {
let theoreticalPayday = set(currentDate, { date: payday });
let adjustedPayday = adjustPayday(theoreticalPayday);
if (adjustedPayday < currentDate) {
theoreticalPayday = set(addDays(currentDate, adjust * 30), {
date: payday,
});
adjustedPayday = adjustPayday(theoreticalPayday);
}
return adjustedPayday;
}

View File

@@ -1,15 +1,17 @@
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import {
EmbedBuilder,
SlashCommandBuilder,
ChatInputCommandInteraction,
} from 'discord.js';
import axios from 'axios';
import config from '../config';
const API_URL = 'https://my-api.plantnet.org/v2/identify/all?api-key={apiKey}';
const API_URL = `https://my-api.plantnet.org/v2/identify/all?api-key=${config.plantnetApiKey}`;
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()
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('plant')
.setDescription('Identify a plant by uploading an image.')
.addAttachmentOption((option) =>
@@ -19,39 +21,16 @@ 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) {
/**
* Execute the plant identification command.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
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');
try {
@@ -62,10 +41,10 @@ async function execute(interaction) {
// API call to identify the plant
const response = await axios.get(
API_URL.replace('{apiKey}', process.env.PLANTNET_API_KEY),
API_URL.replace('{apiKey}', process.env.PLANTNET_API_KEY as string),
{
params: {
images: image.url,
images: image?.url,
organs: 'leaf',
},
timeout: 5000,
@@ -99,7 +78,7 @@ async function execute(interaction) {
// Send the result to the user
await interaction.editReply({ embeds: [embed] });
} catch (error) {
} catch (error: any) {
console.error(error); // Log the error for debugging
await interaction.editReply(
'An error occurred when querying the PlantNet API.'
@@ -107,4 +86,37 @@ async function execute(interaction) {
}
}
export default { data, execute };
/**
* Generate plant details based on common name, scientific name, and confidence level.
* @param commonName The common name of the plant.
* @param scientificName The scientific name of the plant.
* @param confidence The confidence score of the identification.
* @returns A formatted string with the plant details.
*/
function generatePlantDetail(
commonName: string,
scientificName: string,
confidence: number
): string {
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;
}

22
src/commands/reminder.ts Normal file
View File

@@ -0,0 +1,22 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
const REMINDER_IMAGE =
'https://media.discordapp.net/attachments/506852356898422797/715690132883111996/Capture.PNG';
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('reminder')
.setDescription('Returns an image which must always be remembered.');
console.log(`Loaded ${data.name} command.`);
/**
* Replies with an image that must always be remembered.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
await interaction.reply(REMINDER_IMAGE);
}

View File

@@ -0,0 +1,22 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import { format } from 'date-fns';
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('servertime')
.setDescription("Returns the server's current time.");
console.log(`Loaded ${data.name} command.`);
/**
* Replies with the server's current time.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
const now = new Date();
const formattedTime = format(now, 'dd/MM/yyyy HH:mm:ss');
await interaction.reply(`The server's current time is: ${formattedTime}`);
}

23
src/commands/taylor.ts Normal file
View File

@@ -0,0 +1,23 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import taylorQuotes from './utils/constants/taylorQuotes.json';
// Initialise the command data.
export const data = new SlashCommandBuilder()
.setName('taylor')
.setDescription('Returns a Taylor Swift quote.');
console.log(`Loaded ${data.name} command.`);
/**
* Returns a random Taylor Swift quote.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
const randomIndex = Math.floor(Math.random() * taylorQuotes.length);
const response = taylorQuotes[randomIndex];
await interaction.reply(response);
}

View File

@@ -0,0 +1,22 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
const TWENTY_TWENTY_IMAGE =
'https://cdn.discordapp.com/attachments/506852356898422797/765256712063025172/unknown.png';
// Initialize the command data
export const data = new SlashCommandBuilder()
.setName('twentytwenty')
.setDescription('Returns a tweet from Boris, posted on 2 January 2020.');
console.log(`Loaded ${data.name} command.`);
/**
* Sends an image of a tweet from Boris, posted on 2 January 2020.
* @param interaction The interaction that triggered the command.
* @returns A promise that resolves when the command is finished executing.
*/
export async function execute(
interaction: ChatInputCommandInteraction
): Promise<void> {
await interaction.reply(TWENTY_TWENTY_IMAGE);
}

View File

@@ -1,20 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
const data = new SlashCommandBuilder()
.setName('countdown')
.setDescription('Start a five second countdown.');
async function execute(interaction) {
await interaction.reply({ content: 'Starting countdown...' });
await new Promise((resolve) => setTimeout(resolve, 2000)); // 2-second delay
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
}
await interaction.editReply({ content: '🎉 GO! 🎉' });
}
export default { data, execute };

View File

@@ -1,14 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
import { format } from 'date-fns';
const data = new SlashCommandBuilder()
.setName('servertime')
.setDescription("Returns the server's current time.");
async function execute(interaction) {
const now = new Date();
const formattedTime = format(now, 'dd/MM/yyyy HH:mm:ss');
await interaction.reply(`The server's current time is: ${formattedTime}`);
}
export default { data, execute };

View File

@@ -0,0 +1,124 @@
[
"2024.",
"All you have to be is yourself.",
"Believe in your flyness... conquer your shyness.",
"Burn that Excel spreadsheet.",
"Decentralize.",
"Distraction is the enemy of vision.",
"Everything you do in life stems from either fear or love.",
"For me giving up is way harder than trying.",
"For me, money is not my definition of success. Inspiring people is a definition of success.",
"Fur pillows are hard to actually sleep on.",
"George Bush doesn't care about black people.",
"Have you ever thought you were in love with someone but then realized you were just staring in a mirror for 20 minutes?",
"I care. I care about everything. Sometimes not giving a f#%k is caring the most.",
"I feel calm but energized.",
"I feel like I'm too busy writing history to read it.",
"I feel like me and Taylor might still have sex.",
"I give up drinking every week.",
"I leave my emojis Bart Simpson color.",
"I love sleep; it's my favorite.",
"I make awesome decisions in bike stores!!!",
"I really love my Tesla. I'm in the future. Thank you Elon.",
"I still think I am the greatest.",
"I think I do myself a disservice by comparing myself to Steve Jobs and Walt Disney and human beings that we've seen before. It should be more like Willy Wonka...and welcome to my chocolate factory.",
"I want the world to be better! All I want is positive! All I want is dopeness!",
"I wish I had a friend like me.",
"I'd like to meet with Tim Cook. I got some ideas.",
"I'll say things that are serious and put them in a joke form so people can enjoy them. We laugh to keep from crying.",
"I'm a creative genius.",
"I'm nice at ping pong.",
"I'm the best.",
"If I don't scream, if I don't say something then no one's going to say anything.",
"If I got any cooler I would freeze to death.",
"If you have the opportunity to play this game of life you need to appreciate every moment. a lot of people don't appreciate the moment until it's passed.",
"Just stop lying about shit. Just stop lying.",
"Keep squares out yo circle.",
"Keep your nose out the sky, keep your heart to god, and keep your face to the rising sun.",
"Let's be like water.",
"Man... whatever happened to my antique fish tank?",
"My dad got me a drone for Christmas.",
"My greatest award is what I'm about to do.",
"My greatest pain in life is that I will never be able to see myself perform live.",
"One day I'm gon' marry a porn star.",
"One of my favorite of many things about what the Trump hat represents to me is that people can't tell me what to do because I'm black.",
"Only free thinkers.",
"People always say that you can't please everybody. I think that's a cop-out. Why not attempt it? Cause think of all the people that you will please if you try.",
"People always tell you 'Be humble. Be humble.' When was the last time someone told you to be amazing? Be great! Be awesome! Be awesome!",
"People only get jealous when they care.",
"Perhaps I should have been more like water today.",
"Pulling up in the may bike.",
"Shut the fuck up I will fucking laser you with alien fucking eyes and explode your fucking head.",
"Sometimes I push the door close button on people running towards the elevator. I just need my own elevator sometimes. My sanctuary.",
"Sometimes you have to get rid of everything.",
"Style is genderless.",
"The thought police want to suppress freedom of thought.",
"The world is our family.",
"The world is our office.",
"Today is the best day ever and tomorrow's going to be even better.",
"Truth is my goal. Controversy is my gym. I'll do a hundred reps of controversy for a 6 pack of truth.",
"Tweeting is legal and also therapeutic.",
"We all self-conscious. I'm just the first to admit it.",
"We came into a broken world. And we're the cleanup crew.",
"You can't look at a glass half full or empty if it's overflowing.",
"I hate when I'm on a flight and I wake up with a water bottle next to me like oh great now I gotta be responsible for this water bottle.",
"All the musicians will be free.",
"Artists are founders.",
"Buy property.",
"Culture is the most powerful force in humanity under God.",
"Empathy is the glue.",
"I am one of the most famous people on the planet.",
"I am running for President of the United States.",
"I am the head of Adidas. I will bring Adidas and Puma back together and bring me and jay back together.",
"I am Warhol. I am the No. 1 most impactful artist of our generation. I am Shakespeare in the flesh.",
"I channel Will Ferrell when I'm at the daddy daughter dances.",
"I don't wanna see no woke tweets or hear no woke raps ... it's show time ... it's a whole different energy right now.",
"I hear people say this person is cool and this person is not cool. People are cool. Man has never invented anything as awesome as a an actual person but sometimes we value the objects we create over life itself.",
"I honestly need all my Royeres to be museum quality... if I see a fake Royere Ima have to Rick James your couch.",
"I love UZI. I be saying the same thing about Steve Jobs. I be feeling just like UZI.",
"I need an army of angels to cover me while I pull this sword out of the stone.",
"I spoke to Dave Chapelle for two hours this morning. He is our modern day Socrates.",
"I was just speaking with someone that told me their life story and they used to be homeless.",
"I watch Bladerunner on repeat.",
"I'm giving all Good music artists back the 50% share I have of their masters.",
"I'm going to personally see to it that Taylor Swift gets her masters back. Scooter is a close family friend.",
"I'm the new Moses.",
"Life is the ultimate gift.",
"Ma$e is one of my favorite rappers and I based a lot of my flows off of him.",
"Manga all day.",
"My first pillar when I'm on the board of adidas will be an adidas Nike collaboration to support community growth.",
"My mama was a' English teacher. I know how to use correct English but sometimes I just don't feel like it aaaand I ain't got to.",
"My memories are from the future.",
"My mother in law Kris Jenner ... makes the best music playlist.",
"People say it's enough and I got my point across ... the point isn't across until we cross the point.",
"People tried to talk me out of running for President. Never let weak controlling people kill your spirit.",
"So many of us need so much less than we have especially when so many of us are in need.",
"Speak God's truth to power.",
"The media tries to kill our heroes one at a time.",
"The world needs more Joy... this idea is super fresh.",
"There are 5 main pillars in a professional musicians business - Recording, Publishing, Touring, Merchandise & Name and likeness.",
"There are people sleeping in parking lots.",
"There's a crying need for civility across the board. We need to and will come together in the name of Jesus.",
"There's so many lonely emojis man.",
"Trust me ... I won't stop.",
"Two years ago we had 50 million people subscribed to music streaming services around the world. Today we have 400 million.",
"We are here to complete the revolution. We are building the future.",
"We as a people will heal. We will insure the well being of each other.",
"We have to evolve.",
"We must and will cure homelessness and hunger. We have the capability as a species.",
"We must form a union. We must unify.",
"We used to diss Michael Jackson the media made us call him crazy ... then they killed him.",
"We will be recognized.",
"We will change the paradigm.",
"We will cure hunger.",
"We will heal. We will cure.",
"We're going to move the entire music industry into the 21st Century.",
"We've gotten comfortable with not having what we deserve.",
"Who made up the term major label in the first place???",
"Winning is the only option.",
"For me to say I wasn't a genius I'd just be lying to you and to myself.",
"I've known my mom since I was zero years old. She is quite dope.",
"I don't expect to be understood at all.",
"I'm on the pursuit of awesomeness, excellence is the bare minimum.",
"You basically can say anything to someone on an email or text as long as you put LOL at the end."
]

View File

@@ -0,0 +1,22 @@
[
"As I see it, yes.",
"Ask again later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Don't count on it.",
"It is certain.",
"It is decidedly so.",
"Most likely.",
"My reply is no.",
"My sources say no.",
"Outlook not so good.",
"Outlook good.",
"Reply hazy, try again.",
"Signs point to yes.",
"Very doubtful.",
"Without a doubt.",
"Yes.",
"Yes - definitely.",
"You may rely on it."
]

View File

@@ -1,142 +1,140 @@
import { SlashCommandBuilder } from 'discord.js';
const TAYLOR_QUOTES = [
[
"I don't know what I want, so don't ask me, 'cause I'm still trying to figure it out.",
'When you think Tim McGraw, I hope you think of me.',
'So watch me strike a match on all my wasted time.',
"When you think Tim McGraw, I hope you think of me.",
"So watch me strike a match on all my wasted time.",
"I'll be strong, I'll be wrong, oh but life goes on…",
'And when you take, you take the very best of me.',
"And when you take, you take the very best of me.",
"But no one notices until it's too late to do anything.",
"Our song is the slamming screen door, sneakin' out late, tapping on your window.",
"And I don't know why, but with you I'd dance in a storm in my best dress, fearless.",
"But in your life, you'll do things greater than dating the boy on the football team…But I didn't know it at fifteen.",
"I've found time can heal most anything and you just might find who you're supposed to be.",
"Romeo, save me. They're trying to tell me how to feel. This love is difficult but it's real.",
'Why are people always leaving? I think you and I should stay the same.',
"Why are people always leaving? I think you and I should stay the same.",
"My mistake, I didn't know to be in love you had to fight to have the upper hand.",
'This is a big world, that was a small town there in my rear view mirror disappearing now.',
"This is a big world, that was a small town there in my rear view mirror disappearing now.",
"You've got a smile that could light up this whole town.",
"And we know it's never simple, never easy. Never a clean break, no one here to save me.",
'You took a swing, I took it hard. And down here from the ground I see who you are.',
"You took a swing, I took it hard. And down here from the ground I see who you are.",
"All this time I was wasting, hoping you would come around… I've been giving out chances every time and all you do is let me down.",
"And then you feel so low you can't feel nothing at all.",
"It rains when you're here and it rains when you're gone.",
'These walls that they put up to hold us back will fall down',
"These walls that they put up to hold us back will fall down…",
"My mind forgets to remind me you're a bad idea.",
"It turns out freedom ain't nothing but missing you.",
"She floats down the aisle like a pageant queen, but I know you wish it was me… don't you?",
'I lived in your chess game, but you changed the rules every day.',
"I lived in your chess game, but you changed the rules every day.",
"I'm shining like fireworks over your sad, empty town.",
"Someday I'll be living in a big, old city and all you're ever gonna be is mean.",
"I'd tell you I miss you, but I don't know how, I've never heard silence quite this loud.",
'This is looking like a contest of who can act like they care less. But I liked it better when you were on my side.',
"This is looking like a contest of who can act like they care less. But I liked it better when you were on my side.",
"And don't lose the way that you dance around in your pj's getting ready for school.",
"This night is sparkling, don't you let it go. I'm wonderstruck, blushing all the way home.",
'2AM, who do you love?',
"2AM, who do you love?",
"Your string of lights is still bright to me… Who you are is not where you've been.",
'Today is never too late to be brand new.',
"Today is never too late to be brand new.",
"You and I walk a fragile line; I have known it all this time. But I never thought I'd live to see it break.",
"I don't know how to be something you miss.",
'Bring on all the pretenders. One day, we will be remembered.',
"Bring on all the pretenders. One day, we will be remembered.",
"So don't you worry your pretty, little mind, people throw rocks at things that shine.",
"We are alone with our changing minds. We fall in love 'til it hurts or bleeds or fades in time.",
'Love is a ruthless game unless you play it good and right.',
"Love is a ruthless game unless you play it good and right.",
"He's long gone when he's next to me and I realize the blame is on me.",
"No apologies. He'll never see you cry. Pretends he doesn't know that he's the reason why.",
'The saddest fear comes creeping in - that you never loved me or her, or anyone, or anything...',
"The saddest fear comes creeping in - that you never loved me or her, or anyone, or anything...",
"…That magic's not here no more. And I might be OK, but I'm not fine at all.",
"And your mother's telling stories about you on a tee ball team. You taught me 'bout your past, thinking your future was me.",
'I forget about you long enough to forget why I needed to...',
"I forget about you long enough to forget why I needed to...",
"Maybe we got lost in translation, maybe I asked for too much. But maybe this thing was a masterpiece 'til you tore it all up. Running scared, I was there, I remember it all too well.",
'You call me up again just to break me like a promise, so casually cruel in the name of being honest.',
"You call me up again just to break me like a promise, so casually cruel in the name of being honest.",
"Time won't fly, it's like I'm paralyzed by it. I'd like to be my old self again, but I'm still trying to find it.",
"Cause there we are again, when I loved you so, back before you lost the one, real thing you've ever known.",
"Now you mail back my things and I walk home alone, but you keep my old scarf from that very first week, 'cause it reminds you of innocence and it smells like me.",
"Stay, and I'll be loving you for quite some time. No one else is gonna love me when I get mad.",
"We're happy, free, confused, and lonely at the same time. It's miserable and magical.",
"I wish I could run to you. And I hope you know that every time I don't I almost do.",
'You wear your best apology, but I was there to watch you leave.',
'But sometimes I wonder how you think about it now.',
"You wear your best apology, but I was there to watch you leave.",
"But sometimes I wonder how you think about it now.",
"But I don't wanna dance if I'm not dancing with you.",
"Words, how little they mean when you're a little too late.",
"And they tell you that you're lucky, but you're so confused, 'cause you don't feel pretty, you just feel used.",
"I've been spending the last eight months thinking all love ever does is break and burn and end...",
"And what do you do when the one who means the most to you is the one who didn't show?",
"Your close friends always seem to know when there's something really wrong",
'You called me later and said, "I\'m sorry I didn\'t make it". And I said, "I\'m sorry, too".',
'Loving him is like driving a new Maserati down a dead-end street - faster than the wind, passionate as sin, ending so suddenly.',
'The lights are so bright, but they never blind me.',
"You called me later and said, \"I'm sorry I didn't make it\". And I said, \"I'm sorry, too\".",
"Loving him is like driving a new Maserati down a dead-end street - faster than the wind, passionate as sin, ending so suddenly.",
"The lights are so bright, but they never blind me.",
"Love's a game, wanna play?",
"So it's gonna be forever or it's gonna go down in flames.",
"But you'll come back each time you leave 'cause darling, I'm a nightmare dressed like a daydream.",
"When we go crashing down, we come back every time 'cause we never go out of style.",
'I got that red lip, classic thing that you like.',
'The rest of the world was black and white, but we were in screaming color.',
'The more I think about it now, the less I know, all I know is that you drove us off the road.',
'People like you always want back the love they pushed aside, but people like me are gone forever when you say goodbye.',
"I got that red lip, classic thing that you like.",
"The rest of the world was black and white, but we were in screaming color.",
"The more I think about it now, the less I know, all I know is that you drove us off the road.",
"People like you always want back the love they pushed aside, but people like me are gone forever when you say goodbye.",
"Why'd you have to go and lock me out when I let you in?",
"While you've been getting down and out about the liars and the dirty, dirty cheats of the world… You could've been getting down to this sick beat.",
"We're a crooked love in a straight line down.",
'And I wish you knew that I miss you too much to be mad anymore.',
'You give me everything and nothing.',
'Makes you wanna run and hide, but it made us turn right back around.',
"And I wish you knew that I miss you too much to be mad anymore.",
"You give me everything and nothing.",
"Makes you wanna run and hide, but it made us turn right back around.",
"Band-aids don't fix bullet holes. You say sorry just for show.",
'Someday when you leave me, I bet these memories follow you around.',
"Someday when you leave me, I bet these memories follow you around.",
"When you're young, you just run, but you come back to what you need.",
'This love left a permanent mark.',
'Your kiss, my cheek; I watch you leave. Your smile, my ghost; I fall to my knees.',
"This love left a permanent mark.",
"Your kiss, my cheek; I watch you leave. Your smile, my ghost; I fall to my knees.",
"It was months and months of back and forth, you're still all over me like a wine-stained dress I can't wear anymore.",
"When I was drowning that's when I could finally breathe.",
"Just because you're clean, don't mean you don't miss it.",
"Didn't it all seem new and exciting? …It's all fun and games 'til somebody loses their mind.",
'You search the world for something else to make you feel like what we had. And in the end in wonderland, we both went mad.',
'Heartbreak is the national anthem, we sing it proudly.',
"You search the world for something else to make you feel like what we had. And in the end in wonderland, we both went mad.",
"Heartbreak is the national anthem, we sing it proudly.",
"They'll take their shots, but we are bulletproof.",
'So I punched a hole in the roof; let the flood carry away all my pictures of you.',
'When all you wanted was to be wanted; wish you could go back and tell yourself what you know now.',
"So I punched a hole in the roof; let the flood carry away all my pictures of you.",
"When all you wanted was to be wanted; wish you could go back and tell yourself what you know now.",
"32 and still growing up now. Who you are is not what you did. You're still an innocent.",
"We play dumb but we know exactly what we're doing.",
"Please don't ever become a stranger whose laugh I could recognize anywhere.",
'Can we always be this close forever and ever?',
"Can we always be this close forever and ever?",
"I'm only seventeen. I don't know anything but I know I miss you.",
'I was walking home on broken cobblestones just thinking of you, when she pulled up like a figment of my worst intentions.',
'You play stupid games, you win stupid prizes.',
'I had a marvelous time ruining everything.',
"I was walking home on broken cobblestones just thinking of you, when she pulled up like a figment of my worst intentions.",
"You play stupid games, you win stupid prizes.",
"I had a marvelous time ruining everything.",
"Untouchable, burning brighter than the sun, and when you're close I feel like coming undone.",
'I could build a castle out of all the bricks they threw at me.',
'Cold was the steel of my axe to grind for the boys who broke my heart. Now I send their babies presents.',
'Back when you fit in my poems like a perfect rhyme.',
"I could build a castle out of all the bricks they threw at me.",
"Cold was the steel of my axe to grind for the boys who broke my heart. Now I send their babies presents.",
"Back when you fit in my poems like a perfect rhyme.",
"I once believed love would be burning red... but it's golden.",
'The monsters turned out to be just trees, when the sun came up you were looking at me.',
"The monsters turned out to be just trees, when the sun came up you were looking at me.",
"I can't decide if it's a choice: getting swept away?",
'They told me all of my cages were mental, so I got wasted like all my potential.',
"They told me all of my cages were mental, so I got wasted like all my potential.",
"I don't like that falling feels like flying till the bone crush.",
'I persist and resist the temptation to ask you: "If one thing had been different, would everything be different today?"',
'When you are young they assume you know nothing.',
"I persist and resist the temptation to ask you: \"If one thing had been different, would everything be different today?\"",
"When you are young they assume you know nothing.",
"You drew stars around my scars, but now I'm bleeding.",
'You wear the same jewels that I gave you, as you bury me.',
"You wear the same jewels that I gave you, as you bury me.",
"We gather stones, never knowing what they'll mean - some to throw, some to make a diamond ring.",
'Bold was the waitress on our three-year trip... Getting lunch down by the Lakes, she said I looked like an American singer.',
'One single thread of gold tied me to you.',
"Bold was the waitress on our three-year trip... Getting lunch down by the Lakes, she said I looked like an American singer.",
"One single thread of gold tied me to you.",
"I swear I don't love the drama, it loves me.",
"I bury hatchets, but I keep maps of where I put 'em.",
"Love made me crazy, if it doesn't, you ain't doin' it right.",
"My name is whatever you decide, and I'm just gonna call you mine.",
"Handsome, you're a mansion with a view.",
'Sometimes I wonder when you sleep, are you ever dreaming of me?',
'The world goes on another day, another drama. But not for me, all I think about is karma.',
'You asked me for a place to sleep, locked me out and threw a feast.',
"Sometimes I wonder when you sleep, are you ever dreaming of me?",
"The world goes on another day, another drama. But not for me, all I think about is karma.",
"You asked me for a place to sleep, locked me out and threw a feast.",
"You know I'm not a bad girl, but I do bad things with you.",
"You did a number on me but, honestly, baby, who's counting?",
'Ocean blue eyes looking in mine. I feel like I might sink and drown and die.',
"Ocean blue eyes looking in mine. I feel like I might sink and drown and die.",
"Your love is a secret I'm hoping, dreaming, dying to keep.",
'Is this the end of all the endings? My broken bones are mending.',
'I loved you in spite of deep fears that the world would divide us.',
"Is this the end of all the endings? My broken bones are mending.",
"I loved you in spite of deep fears that the world would divide us.",
"Say that we got it! I'm a mess, but I'm the mess that you wanted!",
'I brought a knife to a gunfight.',
'He built a fire just to keep me warm.',
'I want to wear his initial on a chain round my neck, not because he owns me, but cause he really knows me, which is more than they can say.',
'Holding my breath, slowly, I said "You don\'t need to save me, but would you run away with me?"',
"I brought a knife to a gunfight.",
"He built a fire just to keep me warm.",
"I want to wear his initial on a chain round my neck, not because he owns me, but cause he really knows me, which is more than they can say.",
"Holding my breath, slowly, I said \"You don't need to save me, but would you run away with me?\"",
"Would've been right there, front row even if nobody came to your show.",
"I'm always waiting for you to be waiting below.",
"I don't wanna keep secrets just to keep you.",
@@ -147,38 +145,25 @@ const TAYLOR_QUOTES = [
"I'll never let you go 'cause I know this is a fight that someday we're gonna win.",
"I'm with you even if it makes me blue.",
"Without all the exes, fights, and flaws, we wouldn't be standing here so tall.",
'We were a fresh page on the desk, filling in the blanks as we go.',
'We were in the backseat drunk on something stronger than the drinks in the bar.',
'If the story is over, why am I still writing pages?',
"We were a fresh page on the desk, filling in the blanks as we go.",
"We were in the backseat drunk on something stronger than the drinks in the bar.",
"If the story is over, why am I still writing pages?",
"I ask the traffic lights if it will be alright, they say I don't know.",
"They say home is where the heart is, but that's not where mine lives.",
'I pinned your hands behind your back. Thought I had reason to attack, but no.',
"I pinned your hands behind your back. Thought I had reason to attack, but no.",
"Fighting with a true love is boxing with no gloves... Chemistry 'til it blows up, 'til there's no us.",
"And I can't talk to you when you're like this, staring out the window like I'm not your favorite town.",
'They say the road gets hard and you get lost.',
"They say the road gets hard and you get lost.",
"Remember how I said I'd die for you?",
"I come back stronger than a '90s trend.",
'"This dorm was once a madhouse." I made a joke: "Well, it\'s made for me."',
"\"This dorm was once a madhouse.\" I made a joke: \"Well, it's made for me.\"",
"Sometimes you just don't know the answer 'til someone's on their knees and asks you.",
"I can't dare to dream about you anymore.",
'I parked my car right between the Methodist and the school that used to be ours.',
"I parked my car right between the Methodist and the school that used to be ours.",
"I'll go back to L.A. and the so-called friends who'll write books about me if I ever make it and wonder about the only soul who can tell which smiles I'm fakin'.",
'What would you do if I break free and leave us in ruins, took this dagger in me and removed it, gain the weight of you then loose it?',
'Your nemesis will defeat themselves before you get the chance to swing.',
'My waves meet your shore ever and evermore.',
'I replay my footsteps on each stepping stone, trying to find the one where I went wrong.',
"He can't see the smile I'm faking and my heart's not breaking 'cause I'm not feeling anything at all.",
];
const data = new SlashCommandBuilder()
.setName('taylor')
.setDescription('Returns a random Taylor Swift quote.');
async function execute(interaction) {
const randomIndex = Math.floor(Math.random() * TAYLOR_QUOTES.length);
const quote = TAYLOR_QUOTES[randomIndex];
await interaction.reply(quote);
}
export default { data, execute };
"What would you do if I break free and leave us in ruins, took this dagger in me and removed it, gain the weight of you then loose it?",
"Your nemesis will defeat themselves before you get the chance to swing.",
"My waves meet your shore ever and evermore.",
"I replay my footsteps on each stepping stone, trying to find the one where I went wrong.",
"He can't see the smile I'm faking and my heart's not breaking 'cause I'm not feeling anything at all."
]

View File

@@ -0,0 +1,91 @@
import {
MongoClient,
Db,
Collection,
Document,
UpdateResult,
DeleteResult,
} from 'mongodb';
import config from '../../../config';
let client: MongoClient;
let database: Db;
let collection: Collection<Document>;
async function initializeDbConnection() {
if (!client) {
client = new MongoClient(config.mongodbUri);
await client.connect();
database = client.db('butler_db');
collection = database.collection('GameFeatures');
}
}
/**
* Retrieves a list of game names from the database.
*
* @returns An array of game objects containing game names.
*/
export async function listGameNames(): Promise<any[]> {
await initializeDbConnection();
return collection.find({}, { projection: { game: 1 } }).toArray();
}
/**
* Retrieves a specific game by its name from the database.
*
* @param game - The name of the game to retrieve.
* @returns The game object if found, or null if not found.
*/
export async function getGame(game: string): Promise<any | null> {
await initializeDbConnection();
return collection.findOne({ game });
}
/**
* Sets a key-value pair for a specific game in the database.
* If the game does not exist, it will be created.
*
* @param game - The name of the game to update or create.
* @param key - The key to set or update in the game object.
* @param value - The value to set for the specified key.
* @returns The result of the update operation.
*/
export async function setGame(
game: string,
key: string,
value: any
): Promise<UpdateResult> {
await initializeDbConnection();
return collection.updateOne(
{ game },
{ $set: { game, [key]: value } },
{ upsert: true }
);
}
/**
* Deletes a game from the database by its name.
*
* @param game - The name of the game to delete.
* @returns The result of the delete operation.
*/
export async function deleteGame(game: string): Promise<DeleteResult> {
await initializeDbConnection();
return collection.deleteOne({ game });
}
/**
* Deletes a specific field from a game object in the database.
*
* @param game - The name of the game to update.
* @param key - The field key to remove from the game object.
* @returns The result of the update operation.
*/
export async function deleteField(
game: string,
key: string
): Promise<UpdateResult> {
await initializeDbConnection();
return collection.updateOne({ game }, { $unset: { [key]: '' } });
}

View File

@@ -0,0 +1,81 @@
import axios from 'axios';
import { Readable } from 'stream';
/**
* Enum representing the supported music providers.
*/
export enum MusicProvider {
YouTube = 'youtube',
Spotify = 'spotify',
Local = 'local',
}
/**
* Interface representing a track's metadata.
*/
export interface Track {
provider: MusicProvider;
id: string;
title: string;
artist: string;
imageUrl: string;
}
/**
* Fetches track information based on the provider and query.
*
* @param provider - The music provider (YouTube, Spotify, Local).
* @param query - The search query for the track.
* @returns A promise that resolves to a Track object.
*/
export async function fetchTrack(
provider: MusicProvider,
query: string
): Promise<Track | null> {
try {
const response = await axios.get(
`https://music.3t.network/${provider}/${provider === MusicProvider.Local ? 'search' : 'track'}`,
{
params: { query },
}
);
const data = response.data;
if (data && data.id) {
return {
provider,
id: data.id,
title: data.title,
artist: data.artist || data.channel,
imageUrl: data.imageUrl,
};
}
return null;
} catch (error) {
console.error(`Error fetching track from ${provider}:`, error);
return null;
}
}
/**
* Streams the audio file from the download endpoint based on the provider and track ID.
*
* @param track - The track to stream.
* @returns A Readable stream of the audio.
*/
export function streamAudio(track: Track): Readable {
const downloadUrl =
track.provider === MusicProvider.YouTube
? `https://music.3t.network/youtube/video/${track.id}/download`
: track.provider === MusicProvider.Spotify
? `https://music.3t.network/spotify/track/${track.id}/download`
: `https://music.3t.network/local/${track.id}/download`;
return axios({
method: 'get',
url: downloadUrl,
responseType: 'stream',
}).then((response) => response.data as Readable);
}

33
src/config.ts Normal file
View File

@@ -0,0 +1,33 @@
import dotenv from 'dotenv';
dotenv.config(); // Load variables from .env file
interface Config {
discordApplicationId: string;
discordApiKey: string;
giphyApiKey: string;
plantnetApiKey: string;
omdbApiKey: string;
replicateApiKey: string;
mongodbUri: string;
}
const getEnv = (key: string, required = true): string | undefined => {
const value = process.env[key];
if (!value && required) {
throw new Error(`${key} is not set in the environment variables.`);
}
return value;
};
const config: Config = {
discordApplicationId: getEnv('DISCORD_APPLICATION_ID') as string,
discordApiKey: getEnv('DISCORD_API_KEY') as string,
giphyApiKey: getEnv('GIPHY_API_KEY') as string,
plantnetApiKey: getEnv('PLANTNET_API_KEY') as string,
omdbApiKey: getEnv('OMDB_API_KEY') as string,
replicateApiKey: getEnv('REPLICATE_API_KEY') as string,
mongodbUri: getEnv('MONGODB_URI') as string,
};
export default config;

View File

@@ -1,46 +0,0 @@
import 'dotenv/config';
import { Client, Collection, Events, GatewayIntentBits } from 'discord.js';
import { loadCommandModules } from './utilities/commandModules.js';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// Load all command modules and set them in the client.
client.commands = new Collection();
const commandModules = await loadCommandModules();
commandModules.forEach((module) => {
client.commands.set(module.data.name, module);
});
// Register the event listener for command interactions.
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) {
console.warn(`Command ${interaction.commandName} not found.`);
return;
}
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: 'There was an error while executing this command!',
ephemeral: true,
});
} else {
await interaction.reply({
content: 'There was an error while executing this command!',
ephemeral: true,
});
}
}
});
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.login(process.env.DISCORD_API_KEY);

35
src/index.ts Normal file
View File

@@ -0,0 +1,35 @@
import 'dotenv/config';
import {
Client,
Collection,
Events,
GatewayIntentBits,
Interaction,
} from 'discord.js';
import { commands } from './commands';
// Define an extended version of the Client interface to include commands
interface ExtendedClient extends Client {
commands: Collection<string, any>;
}
const client: ExtendedClient = new Client({
intents: [GatewayIntentBits.Guilds],
}) as ExtendedClient;
// Register the event listener for command interactions.
client.on(Events.InteractionCreate, async (interaction: Interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commands[commandName as keyof typeof commands]) {
commands[commandName as keyof typeof commands].execute(interaction);
}
});
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.login(process.env.DISCORD_API_KEY);

View File

@@ -1,28 +0,0 @@
import 'dotenv/config';
import { REST, Routes } from 'discord.js';
import { loadCommandModules } from './utilities/commandModules.js';
// Register all slash commands, globally across all Guilds.
const commandModules = await loadCommandModules();
const commands = commandModules.map((commandModule) =>
commandModule.data.toJSON()
);
const rest = new REST().setToken(process.env.DISCORD_API_KEY);
try {
console.log('Started refreshing application (/) commands.');
// Refresh all slash commands globally.
const data = await rest.put(
Routes.applicationCommands(process.env.DISCORD_APPLICATION_ID),
{ body: commands }
);
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);
}

View File

@@ -0,0 +1,25 @@
import { REST, Routes } from 'discord.js';
import { commands } from './commands';
import config from './config';
// Register all slash commands, globally across all Guilds.
const commandData = Object.values(commands).map(
(command) => command.data.toJSON() as any
);
const rest = new REST().setToken(config.discordApiKey);
try {
console.log('Started refreshing application (/) commands.');
// Refresh all slash commands globally.
rest.put(Routes.applicationCommands(config.discordApplicationId), {
body: commandData,
});
// Exit the process.
process.exit();
} catch (error) {
// And of course, make sure you catch and log any errors!
console.error(error);
}

View File

@@ -1,46 +0,0 @@
import path from 'path';
import { glob } from 'glob';
import { ApplicationCommand } from 'discord.js';
/**
* Get all command modules from the commands directory.
* @returns {Promise<string[]>} The file paths of all command modules.
*/
async function getCommandModulePaths() {
return await glob(path.join(process.cwd(), 'src/commands/**/*.js'));
}
/**
* Load a single command module.
* @param {string} modulePath The path to the command module.
* @returns {Promise<ApplicationCommand>} The loaded command module.
*/
async function loadCommandModule(modulePath) {
try {
const module = await import(path.resolve(modulePath));
const commandModule = module.default;
if (!commandModule.data || !commandModule.execute) {
console.warn(`Invalid command module at ${modulePath}`);
return;
}
console.info(`Loaded command module: ${commandModule.data.name}`);
return commandModule;
} catch (error) {
console.error(`Error loading module at ${modulePath}.`, error);
}
}
/**
* Load all command modules.
* @returns {Promise<ApplicationCommand[]>} The loaded command modules.
*/
export async function loadCommandModules() {
const commandModulePaths = await getCommandModulePaths();
const commandModules = await Promise.all(
commandModulePaths.map(loadCommandModule)
);
return commandModules.filter((module) => module);
}

View File

@@ -1,37 +0,0 @@
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]: '' } });
}

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}