Updated formatting

This commit is contained in:
Michael 2023-09-13 02:17:31 -04:00
parent ecbd2bdc4e
commit 25346f9ab2
11 changed files with 601 additions and 601 deletions

View file

@ -9,24 +9,24 @@ const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { clientId, botName, botOwner } = require('../config.json'); const { clientId, botName, botOwner } = require('../config.json');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('about') .setName('about')
.setDescription('Gives basic information about the bot'), .setDescription('Gives basic information about the bot'),
async execute(interaction) { async execute(interaction) {
const exampleEmbed = new EmbedBuilder() const exampleEmbed = new EmbedBuilder()
.setColor(interaction.member.displayHexColor) .setColor(interaction.member.displayHexColor)
.setTitle(`About ${botName}`) .setTitle(`About ${botName}`)
.setURL(`https://discord.com/oauth2/authorize?client_id=${clientId}&permissions=274877908992&scope=bot%20applications.commands`) .setURL(`https://discord.com/oauth2/authorize?client_id=${clientId}&permissions=274877908992&scope=bot%20applications.commands`)
.addFields( .addFields(
{ name: 'Admin of this bot', value: `${botOwner}` }, { name: 'Admin of this bot', value: `${botOwner}` },
{ name: 'Websocket Heartbeat / Ping', value: `${interaction.client.ws.ping}ms` }, { name: 'Websocket Heartbeat / Ping', value: `${interaction.client.ws.ping}ms` },
{ name: 'Invite Link', value: `https://discord.com/oauth2/authorize?client_id=${clientId}&permissions=274877908992&scope=bot%20applications.commands`, inline: true }, { name: 'Invite Link', value: `https://discord.com/oauth2/authorize?client_id=${clientId}&permissions=274877908992&scope=bot%20applications.commands`, inline: true },
{ name: 'Based on the open source Konpeki Discord Bot', value: 'https://github.com/TheShadowEevee/Konpeki-Discord-Bot', inline: true }, { name: 'Based on the open source Konpeki Discord Bot', value: 'https://github.com/TheShadowEevee/Konpeki-Discord-Bot', inline: true },
) )
.setFooter({ text: `Support for custom changes should go through the admin of this bot, ${botOwner}. Support for the underlying Konpeki Discord Bot is available at https://discord.gg/Zt8zruXexJ.` }); .setFooter({ text: `Support for custom changes should go through the admin of this bot, ${botOwner}. Support for the underlying Konpeki Discord Bot is available at https://discord.gg/Zt8zruXexJ.` });
await interaction.reply({ embeds: [exampleEmbed], ephemeral: true }); await interaction.reply({ embeds: [exampleEmbed], ephemeral: true });
}, },
}; };

View file

@ -6,62 +6,62 @@
const { SlashCommandBuilder } = require('discord.js'); const { SlashCommandBuilder } = require('discord.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('avatar') .setName('avatar')
.setDescription('Shares a users current avatar') .setDescription('Shares a users current avatar')
// Optional user selection for avatar target; target selves if none. // Optional user selection for avatar target; target selves if none.
.addUserOption(option => .addUserOption(option =>
option.setName('user') option.setName('user')
.setDescription('Select the user to get an avatar from')) .setDescription('Select the user to get an avatar from'))
// Optional choice for user to choose avatar size; 4096 if no selection. // Optional choice for user to choose avatar size; 4096 if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('format') option.setName('format')
.setDescription('Select what format you want the output to be') .setDescription('Select what format you want the output to be')
.addChoices( .addChoices(
{ name: 'WebP', value: 'webp' }, { name: 'WebP', value: 'webp' },
{ name: 'PNG', value: 'png' }, { name: 'PNG', value: 'png' },
{ name: 'JPEG', value: 'jpg' }, { name: 'JPEG', value: 'jpg' },
)) ))
// Optional choice for user to choose avatar format; webp if no selection. // Optional choice for user to choose avatar format; webp if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('size') option.setName('size')
.setDescription('Select what size you want the output to be') .setDescription('Select what size you want the output to be')
.addChoices( .addChoices(
{ name: '4096px', value: '4096' }, { name: '4096px', value: '4096' },
{ name: '2048px', value: '2048' }, { name: '2048px', value: '2048' },
{ name: '1024px', value: '1024' }, { name: '1024px', value: '1024' },
{ name: '600px', value: '600' }, { name: '600px', value: '600' },
{ name: '512px', value: '512' }, { name: '512px', value: '512' },
{ name: '300px', value: '300' }, { name: '300px', value: '300' },
{ name: '256px', value: '256' }, { name: '256px', value: '256' },
{ name: '128px', value: '128' }, { name: '128px', value: '128' },
{ name: '96px', value: '96' }, { name: '96px', value: '96' },
{ name: '64px', value: '64' }, { name: '64px', value: '64' },
{ name: '56px', value: '56' }, { name: '56px', value: '56' },
{ name: '32px', value: '32' }, { name: '32px', value: '32' },
{ name: '16px', value: '16' }, { name: '16px', value: '16' },
)) ))
// Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection. // Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('ephemeral') option.setName('ephemeral')
.setDescription('Post the avatar in the current channel') .setDescription('Post the avatar in the current channel')
.addChoices( .addChoices(
{ name: 'Send to me only', value: 'true' }, { name: 'Send to me only', value: 'true' },
{ name: 'Send in channel', value: 'false' }, { name: 'Send in channel', value: 'false' },
)), )),
async execute(interaction) { async execute(interaction) {
const discordUser = interaction.options.getUser('user') ?? interaction.user; const discordUser = interaction.options.getUser('user') ?? interaction.user;
const avatarFormat = interaction.options.getString('format') ?? 'webp'; const avatarFormat = interaction.options.getString('format') ?? 'webp';
const avatarSize = Number(interaction.options.getString('size')) ?? 4096; const avatarSize = Number(interaction.options.getString('size')) ?? 4096;
const isEphemeral = interaction.options.getString('ephemeral') ?? 'true'; const isEphemeral = interaction.options.getString('ephemeral') ?? 'true';
await interaction.reply({ content: `${discordUser.avatarURL({ extension:avatarFormat, size:avatarSize, forceStatic:false })}`, ephemeral: (isEphemeral === 'true') }); await interaction.reply({ content: `${discordUser.avatarURL({ extension:avatarFormat, size:avatarSize, forceStatic:false })}`, ephemeral: (isEphemeral === 'true') });
}, },
}; };

View file

@ -11,258 +11,258 @@ let helpFile = '';
// Import help text file // Import help text file
if (fs.existsSync('./data/help-text.json')) { if (fs.existsSync('./data/help-text.json')) {
helpFile = JSON.parse(fs.readFileSync('./data/help-text.json', 'utf8')); helpFile = JSON.parse(fs.readFileSync('./data/help-text.json', 'utf8'));
} }
// List of all commands, by category // List of all commands, by category
// Command help, listing options. // Command help, listing options.
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('help') .setName('help')
.setDescription('Provides information on avalible commands') .setDescription('Provides information on avalible commands')
// Allow choosing the help page to open // Allow choosing the help page to open
.addNumberOption(option => .addNumberOption(option =>
option.setName('page') option.setName('page')
.setDescription('Choose help page to skip to'), .setDescription('Choose help page to skip to'),
), ),
async execute(interaction) { async execute(interaction) {
let pageNumber = interaction.options.getNumber('page') ?? 1; let pageNumber = interaction.options.getNumber('page') ?? 1;
const commandsPerPage = 5; const commandsPerPage = 5;
let commandsThisPage = 0; let commandsThisPage = 0;
const numberOfCommands = Object.keys(helpFile).length; const numberOfCommands = Object.keys(helpFile).length;
const pageTotal = String(Math.ceil(numberOfCommands / commandsPerPage)); const pageTotal = String(Math.ceil(numberOfCommands / commandsPerPage));
let userRoleColor = Number('0x' + interaction.member.displayHexColor.split('#')[1]); let userRoleColor = Number('0x' + interaction.member.displayHexColor.split('#')[1]);
// This will also unintentionally catch roles with full black color (#000000), but it should be fine. // This will also unintentionally catch roles with full black color (#000000), but it should be fine.
if (userRoleColor == 0) { if (userRoleColor == 0) {
userRoleColor = Number(0x55ffff); userRoleColor = Number(0x55ffff);
} }
if (pageNumber > pageTotal) { if (pageNumber > pageTotal) {
pageNumber = 1; pageNumber = 1;
} }
let embedPartOne = { let embedPartOne = {
color: userRoleColor, color: userRoleColor,
title: 'Help Text', title: 'Help Text',
description: `Page ${pageNumber} of ${pageTotal}`, description: `Page ${pageNumber} of ${pageTotal}`,
}; };
if (pageNumber != pageTotal) { if (pageNumber != pageTotal) {
commandsThisPage = commandsPerPage; commandsThisPage = commandsPerPage;
} }
else { else {
commandsThisPage = numberOfCommands % commandsPerPage; commandsThisPage = numberOfCommands % commandsPerPage;
} }
let embedPartTwo = ''; let embedPartTwo = '';
embedPartTwo += '{"fields": ['; embedPartTwo += '{"fields": [';
for (let i = 0; i < commandsThisPage; i++) { for (let i = 0; i < commandsThisPage; i++) {
const currentCommandName = Object.keys(helpFile)[i + (commandsPerPage * (pageNumber - 1))]; const currentCommandName = Object.keys(helpFile)[i + (commandsPerPage * (pageNumber - 1))];
embedPartTwo += '{'; embedPartTwo += '{';
embedPartTwo += ' "name": "/' + currentCommandName + '",', embedPartTwo += ' "name": "/' + currentCommandName + '",',
embedPartTwo += ' "value": "' + helpFile[currentCommandName].description + '"', embedPartTwo += ' "value": "' + helpFile[currentCommandName].description + '"',
embedPartTwo += '},'; embedPartTwo += '},';
} }
embedPartTwo = embedPartTwo.substring(0, embedPartTwo.length - 1) + ']}'; embedPartTwo = embedPartTwo.substring(0, embedPartTwo.length - 1) + ']}';
let buttonList = ''; let buttonList = '';
// Change buttons based on page number. Is there an easier/shorter way to do this? // Change buttons based on page number. Is there an easier/shorter way to do this?
if (pageNumber == 1) { if (pageNumber == 1) {
buttonList = new ActionRowBuilder() buttonList = new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('back') .setCustomId('back')
.setLabel('Previous') .setLabel('Previous')
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('page') .setCustomId('page')
.setLabel(`${pageNumber}/${pageTotal}`) .setLabel(`${pageNumber}/${pageTotal}`)
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('next') .setCustomId('next')
.setLabel('Next') .setLabel('Next')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
); );
} }
else if (pageNumber == pageTotal) { else if (pageNumber == pageTotal) {
buttonList = new ActionRowBuilder() buttonList = new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('back') .setCustomId('back')
.setLabel('Previous') .setLabel('Previous')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('page') .setCustomId('page')
.setLabel(`${pageNumber}/${pageTotal}`) .setLabel(`${pageNumber}/${pageTotal}`)
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('next') .setCustomId('next')
.setLabel('Next') .setLabel('Next')
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
); );
} }
else { else {
buttonList = new ActionRowBuilder() buttonList = new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('back') .setCustomId('back')
.setLabel('Previous') .setLabel('Previous')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('page') .setCustomId('page')
.setLabel(`${pageNumber}/${pageTotal}`) .setLabel(`${pageNumber}/${pageTotal}`)
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('next') .setCustomId('next')
.setLabel('Next') .setLabel('Next')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
); );
} }
// Button code // Button code
const collector = interaction.channel.createMessageComponentCollector({ time: 300000 }); const collector = interaction.channel.createMessageComponentCollector({ time: 300000 });
collector.on('collect', async i => { collector.on('collect', async i => {
if (i.customId === 'next') { if (i.customId === 'next') {
pageNumber += 1; pageNumber += 1;
} }
if (i.customId === 'back') { if (i.customId === 'back') {
pageNumber -= 1; pageNumber -= 1;
} }
// Replicate the above code to remake the help model for the new page. Again, there has to be an easier way to do this. // Replicate the above code to remake the help model for the new page. Again, there has to be an easier way to do this.
embedPartOne = { embedPartOne = {
color: userRoleColor, color: userRoleColor,
title: 'Help Text', title: 'Help Text',
description: `Page ${pageNumber} of ${pageTotal}`, description: `Page ${pageNumber} of ${pageTotal}`,
}; };
embedPartTwo = ''; embedPartTwo = '';
if (pageNumber != pageTotal) { if (pageNumber != pageTotal) {
commandsThisPage = commandsPerPage; commandsThisPage = commandsPerPage;
} }
else { else {
commandsThisPage = numberOfCommands % commandsPerPage; commandsThisPage = numberOfCommands % commandsPerPage;
} }
embedPartTwo += '{"fields": ['; embedPartTwo += '{"fields": [';
for (let j = 0; j < commandsThisPage; j++) { for (let j = 0; j < commandsThisPage; j++) {
const currentCommandName = Object.keys(helpFile)[j + (commandsPerPage * (pageNumber - 1))]; const currentCommandName = Object.keys(helpFile)[j + (commandsPerPage * (pageNumber - 1))];
embedPartTwo += '{'; embedPartTwo += '{';
embedPartTwo += ' "name": "/' + currentCommandName + '",', embedPartTwo += ' "name": "/' + currentCommandName + '",',
embedPartTwo += ' "value": "' + helpFile[currentCommandName].description + '"', embedPartTwo += ' "value": "' + helpFile[currentCommandName].description + '"',
embedPartTwo += '},'; embedPartTwo += '},';
} }
embedPartTwo = embedPartTwo.substring(0, embedPartTwo.length - 1) + ']}'; embedPartTwo = embedPartTwo.substring(0, embedPartTwo.length - 1) + ']}';
// Change buttons based on page number. Is there an easier/shorter way to do this? // Change buttons based on page number. Is there an easier/shorter way to do this?
if (pageNumber == 1) { if (pageNumber == 1) {
buttonList = new ActionRowBuilder() buttonList = new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('back') .setCustomId('back')
.setLabel('Previous') .setLabel('Previous')
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('page') .setCustomId('page')
.setLabel(`${pageNumber}/${pageTotal}`) .setLabel(`${pageNumber}/${pageTotal}`)
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('next') .setCustomId('next')
.setLabel('Next') .setLabel('Next')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
); );
} }
else if (pageNumber == pageTotal) { else if (pageNumber == pageTotal) {
buttonList = new ActionRowBuilder() buttonList = new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('back') .setCustomId('back')
.setLabel('Previous') .setLabel('Previous')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('page') .setCustomId('page')
.setLabel(`${pageNumber}/${pageTotal}`) .setLabel(`${pageNumber}/${pageTotal}`)
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('next') .setCustomId('next')
.setLabel('Next') .setLabel('Next')
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
); );
} }
else { else {
buttonList = new ActionRowBuilder() buttonList = new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('back') .setCustomId('back')
.setLabel('Previous') .setLabel('Previous')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('page') .setCustomId('page')
.setLabel(`${pageNumber}/${pageTotal}`) .setLabel(`${pageNumber}/${pageTotal}`)
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(true), .setDisabled(true),
) )
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId('next') .setCustomId('next')
.setLabel('Next') .setLabel('Next')
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
); );
} }
await i.update({ embeds: [Object.assign({}, embedPartOne, JSON.parse(embedPartTwo))], components: [ buttonList ], ephemeral: true }); await i.update({ embeds: [Object.assign({}, embedPartOne, JSON.parse(embedPartTwo))], components: [ buttonList ], ephemeral: true });
}); });
await interaction.reply({ embeds: [Object.assign({}, embedPartOne, JSON.parse(embedPartTwo))], components: [ buttonList ], ephemeral: true }); await interaction.reply({ embeds: [Object.assign({}, embedPartOne, JSON.parse(embedPartTwo))], components: [ buttonList ], ephemeral: true });
}, },
}; };

View file

@ -9,10 +9,10 @@ const { SlashCommandBuilder } = require('discord.js');
const { botName, clientId } = require('../config.json'); const { botName, clientId } = require('../config.json');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('invite') .setName('invite')
.setDescription(`Invite ${botName} to your own server`), .setDescription(`Invite ${botName} to your own server`),
async execute(interaction) { async execute(interaction) {
await interaction.reply({ content: `Use this link to invite ${botName} (That's me!) to your own server!\nhttps://discord.com/oauth2/authorize?client_id=${clientId}&permissions=274877908992&scope=bot%20applications.commands`, ephemeral: true }); await interaction.reply({ content: `Use this link to invite ${botName} (That's me!) to your own server!\nhttps://discord.com/oauth2/authorize?client_id=${clientId}&permissions=274877908992&scope=bot%20applications.commands`, ephemeral: true });
}, },
}; };

View file

@ -7,69 +7,69 @@ const { SlashCommandBuilder } = require('discord.js');
const { randomInt } = require('node:crypto'); const { randomInt } = require('node:crypto');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('lottery') .setName('lottery')
.setDescription('Rolls a set of numbers you can use for whatever') .setDescription('Rolls a set of numbers you can use for whatever')
// Get number of numbers to roll // Get number of numbers to roll
.addNumberOption(option => .addNumberOption(option =>
option.setName('count') option.setName('count')
.setDescription('How many numbers to roll'), .setDescription('How many numbers to roll'),
) )
// Get max number to roll // Get max number to roll
.addNumberOption(option => .addNumberOption(option =>
option.setName('max') option.setName('max')
.setDescription('Set the maximum roll'), .setDescription('Set the maximum roll'),
) )
// Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection. // Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('ephemeral') option.setName('ephemeral')
.setDescription('Post the avatar in the current channel') .setDescription('Post the avatar in the current channel')
.addChoices( .addChoices(
{ name: 'Send to me only', value: 'true' }, { name: 'Send to me only', value: 'true' },
{ name: 'Send in channel', value: 'false' }, { name: 'Send in channel', value: 'false' },
)), )),
async execute(interaction) { async execute(interaction) {
const isEphemeral = interaction.options.getString('ephemeral') ?? 'true'; const isEphemeral = interaction.options.getString('ephemeral') ?? 'true';
const rollCount = interaction.options.getNumber('count') ?? 6; const rollCount = interaction.options.getNumber('count') ?? 6;
const maxRoll = interaction.options.getNumber('max') ?? 99; const maxRoll = interaction.options.getNumber('max') ?? 99;
// Limit max number rollable to prevent spam and API issues // Limit max number rollable to prevent spam and API issues
if (maxRoll > 999) { if (maxRoll > 999) {
await interaction.reply({ content: `Big roller! Unfortunatly ${maxRoll} is too big a number for me. Try somewhere below 1000!`, ephemeral: true }); await interaction.reply({ content: `Big roller! Unfortunatly ${maxRoll} is too big a number for me. Try somewhere below 1000!`, ephemeral: true });
return; return;
} }
// Prevent 0 or less numbers // Prevent 0 or less numbers
else if (maxRoll < 1) { else if (maxRoll < 1) {
await interaction.reply({ content: `The fewer numbers, the higher the odds! Unfortunatly ${maxRoll} is too low a number... Try a positive number under 1000!`, ephemeral:true }); await interaction.reply({ content: `The fewer numbers, the higher the odds! Unfortunatly ${maxRoll} is too low a number... Try a positive number under 1000!`, ephemeral:true });
return; return;
} }
// Limit numbers rollable to prevent spam and API issues // Limit numbers rollable to prevent spam and API issues
if (rollCount > 9) { if (rollCount > 9) {
await interaction.reply({ content: `You want ${rollCount} numbers?! That's a bit much, even for me...\nTry 9 or less please!`, ephemeral: true }); await interaction.reply({ content: `You want ${rollCount} numbers?! That's a bit much, even for me...\nTry 9 or less please!`, ephemeral: true });
return; return;
} }
// Prevent 0 or less numbers // Prevent 0 or less numbers
else if (rollCount < 1) { else if (rollCount < 1) {
await interaction.reply({ content: `It's kinda difficult to roll ${rollCount} numbers...\nTry a positive single digit number!`, ephemeral:true }); await interaction.reply({ content: `It's kinda difficult to roll ${rollCount} numbers...\nTry a positive single digit number!`, ephemeral:true });
return; return;
} }
const lotteryNumbers = []; const lotteryNumbers = [];
for (let i = 0; i < rollCount; i++) { for (let i = 0; i < rollCount; i++) {
lotteryNumbers.push(randomInt(1, maxRoll)); lotteryNumbers.push(randomInt(1, maxRoll));
} }
if (rollCount === 1) { if (rollCount === 1) {
await interaction.reply({ content: `Your lucky number is...\n${lotteryNumbers.toString().split(',').join(', ')}.`, ephemeral: (isEphemeral === 'true') }); await interaction.reply({ content: `Your lucky number is...\n${lotteryNumbers.toString().split(',').join(', ')}.`, ephemeral: (isEphemeral === 'true') });
} }
else { else {
await interaction.reply({ content: `Your lucky numbers are...\n${lotteryNumbers.toString().split(',').join(', ')}.`, ephemeral: (isEphemeral === 'true') }); await interaction.reply({ content: `Your lucky numbers are...\n${lotteryNumbers.toString().split(',').join(', ')}.`, ephemeral: (isEphemeral === 'true') });
} }
}, },
}; };

View file

@ -8,10 +8,10 @@
const { SlashCommandBuilder } = require('discord.js'); const { SlashCommandBuilder } = require('discord.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('ping') .setName('ping')
.setDescription('Checks to see if the bot is online, as well as it\'s ping'), .setDescription('Checks to see if the bot is online, as well as it\'s ping'),
async execute(interaction) { async execute(interaction) {
await interaction.reply({ content: `Pong! Current Websocket Heartbeat / ping is ${interaction.client.ws.ping}ms.`, ephemeral: true }); await interaction.reply({ content: `Pong! Current Websocket Heartbeat / ping is ${interaction.client.ws.ping}ms.`, ephemeral: true });
}, },
}; };

View file

@ -7,94 +7,94 @@ const { SlashCommandBuilder } = require('discord.js');
const { randomInt } = require('node:crypto'); const { randomInt } = require('node:crypto');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('roll') .setName('roll')
.setDescription('Rolls a set of dice') .setDescription('Rolls a set of dice')
// Get number of numbers to roll // Get number of numbers to roll
.addNumberOption(option => .addNumberOption(option =>
option.setName('die') option.setName('die')
.setDescription('Type of dice to roll') .setDescription('Type of dice to roll')
.addChoices( .addChoices(
{ name: 'd4', value: 4 }, { name: 'd4', value: 4 },
{ name: 'd6', value: 6 }, { name: 'd6', value: 6 },
{ name: 'd8', value: 8 }, { name: 'd8', value: 8 },
{ name: 'd10', value: 10 }, { name: 'd10', value: 10 },
{ name: 'd20', value: 20 }, { name: 'd20', value: 20 },
{ name: 'd100', value: 100 }, { name: 'd100', value: 100 },
)) ))
// Get max number to roll // Get max number to roll
.addNumberOption(option => .addNumberOption(option =>
option.setName('count') option.setName('count')
.setDescription('Amount of dice to roll'), .setDescription('Amount of dice to roll'),
) )
// Get max number to roll // Get max number to roll
.addNumberOption(option => .addNumberOption(option =>
option.setName('modifier') option.setName('modifier')
.setDescription('+/- to a dice roll'), .setDescription('+/- to a dice roll'),
) )
// Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection. // Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('ephemeral') option.setName('ephemeral')
.setDescription('Post the avatar in the current channel') .setDescription('Post the avatar in the current channel')
.addChoices( .addChoices(
{ name: 'Send to me only', value: 'true' }, { name: 'Send to me only', value: 'true' },
{ name: 'Send in channel', value: 'false' }, { name: 'Send in channel', value: 'false' },
)), )),
async execute(interaction) { async execute(interaction) {
const isEphemeral = interaction.options.getString('ephemeral') ?? 'true'; const isEphemeral = interaction.options.getString('ephemeral') ?? 'true';
const rollCount = interaction.options.getNumber('count') ?? 1; const rollCount = interaction.options.getNumber('count') ?? 1;
const dieType = interaction.options.getNumber('die') ?? 6; const dieType = interaction.options.getNumber('die') ?? 6;
const rollMod = interaction.options.getNumber('modifier') ?? 0; const rollMod = interaction.options.getNumber('modifier') ?? 0;
let modSign = ''; let modSign = '';
let rollModStr = ''; let rollModStr = '';
let rollModNota = ''; let rollModNota = '';
// Limit numbers rollable to prevent spam and API issues // Limit numbers rollable to prevent spam and API issues
if (rollCount > 100) { if (rollCount > 100) {
await interaction.reply({ content: `You want ${rollCount} numbers?! That's a bit much, even for me...\nTry 100 or less please!`, ephemeral: true }); await interaction.reply({ content: `You want ${rollCount} numbers?! That's a bit much, even for me...\nTry 100 or less please!`, ephemeral: true });
return; return;
} }
// Prevent 0 or less numbers // Prevent 0 or less numbers
else if (rollCount < 1) { else if (rollCount < 1) {
await interaction.reply({ content: `It's kinda difficult to roll ${rollCount} numbers...\nTry a positive number!`, ephemeral:true }); await interaction.reply({ content: `It's kinda difficult to roll ${rollCount} numbers...\nTry a positive number!`, ephemeral:true });
return; return;
} }
const diceRolls = []; const diceRolls = [];
let rollSum = 0; let rollSum = 0;
for (let i = 0; i < rollCount; i++) { for (let i = 0; i < rollCount; i++) {
const randomNum = randomInt(1, dieType); const randomNum = randomInt(1, dieType);
diceRolls.push(randomNum); diceRolls.push(randomNum);
rollSum += randomNum; rollSum += randomNum;
} }
if (rollMod < 0) { if (rollMod < 0) {
modSign = '-'; modSign = '-';
} }
else if (rollMod > 0) { else if (rollMod > 0) {
modSign = '+'; modSign = '+';
} }
if (rollMod != 0) { if (rollMod != 0) {
rollModStr = ' ' + modSign + ' ' + Math.abs(rollMod); rollModStr = ' ' + modSign + ' ' + Math.abs(rollMod);
rollModNota = modSign + Math.abs(rollMod); rollModNota = modSign + Math.abs(rollMod);
} }
const diceRollsString = diceRolls.toString().split(',').join(' + '); const diceRollsString = diceRolls.toString().split(',').join(' + ');
for (let i = 0; i < rollCount; i++) { for (let i = 0; i < rollCount; i++) {
diceRolls.push(randomInt(1, dieType)); diceRolls.push(randomInt(1, dieType));
} }
await interaction.reply({ content: `\`${rollCount}d${dieType}${rollModNota}\`\n\`Rolls: ( ${diceRollsString} )${rollModStr} = ${rollSum + rollMod}\``, ephemeral: (isEphemeral === 'true') }); await interaction.reply({ content: `\`${rollCount}d${dieType}${rollModNota}\`\n\`Rolls: ( ${diceRollsString} )${rollModStr} = ${rollSum + rollMod}\``, ephemeral: (isEphemeral === 'true') });
}, },
}; };

View file

@ -6,56 +6,56 @@
const { SlashCommandBuilder } = require('discord.js'); const { SlashCommandBuilder } = require('discord.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('server-icon') .setName('server-icon')
.setDescription('Shares this servers current icon') .setDescription('Shares this servers current icon')
// Optional choice for user to choose icon size; 4096 if no selection. // Optional choice for user to choose icon size; 4096 if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('format') option.setName('format')
.setDescription('Select what format you want the output to be') .setDescription('Select what format you want the output to be')
.addChoices( .addChoices(
{ name: 'WebP', value: 'webp' }, { name: 'WebP', value: 'webp' },
{ name: 'PNG', value: 'png' }, { name: 'PNG', value: 'png' },
{ name: 'JPEG', value: 'jpg' }, { name: 'JPEG', value: 'jpg' },
)) ))
// Optional choice for user to choose icon format; webp if no selection. // Optional choice for user to choose icon format; webp if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('size') option.setName('size')
.setDescription('Select what size you want the output to be') .setDescription('Select what size you want the output to be')
.addChoices( .addChoices(
{ name: '4096px', value: '4096' }, { name: '4096px', value: '4096' },
{ name: '2048px', value: '2048' }, { name: '2048px', value: '2048' },
{ name: '1024px', value: '1024' }, { name: '1024px', value: '1024' },
{ name: '600px', value: '600' }, { name: '600px', value: '600' },
{ name: '512px', value: '512' }, { name: '512px', value: '512' },
{ name: '300px', value: '300' }, { name: '300px', value: '300' },
{ name: '256px', value: '256' }, { name: '256px', value: '256' },
{ name: '128px', value: '128' }, { name: '128px', value: '128' },
{ name: '96px', value: '96' }, { name: '96px', value: '96' },
{ name: '64px', value: '64' }, { name: '64px', value: '64' },
{ name: '56px', value: '56' }, { name: '56px', value: '56' },
{ name: '32px', value: '32' }, { name: '32px', value: '32' },
{ name: '16px', value: '16' }, { name: '16px', value: '16' },
)) ))
// Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection. // Optional Ephemeral check to allow user to choose command results to be shared publicly or private; send to self only if no selection.
.addStringOption(option => .addStringOption(option =>
option.setName('ephemeral') option.setName('ephemeral')
.setDescription('Post the icon in the current channel') .setDescription('Post the icon in the current channel')
.addChoices( .addChoices(
{ name: 'Send to me only', value: 'true' }, { name: 'Send to me only', value: 'true' },
{ name: 'Send in channel', value: 'false' }, { name: 'Send in channel', value: 'false' },
)), )),
async execute(interaction) { async execute(interaction) {
const iconFormat = interaction.options.getString('format') ?? 'webp'; const iconFormat = interaction.options.getString('format') ?? 'webp';
const iconSize = Number(interaction.options.getString('size')) ?? 4096; const iconSize = Number(interaction.options.getString('size')) ?? 4096;
const isEphemeral = interaction.options.getString('ephemeral') ?? 'true'; const isEphemeral = interaction.options.getString('ephemeral') ?? 'true';
await interaction.reply({ content: `${interaction.guild.iconURL({ extension:iconFormat, size:iconSize, forceStatic:false })}`, ephemeral: (isEphemeral === 'true') }); await interaction.reply({ content: `${interaction.guild.iconURL({ extension:iconFormat, size:iconSize, forceStatic:false })}`, ephemeral: (isEphemeral === 'true') });
}, },
}; };

View file

@ -14,17 +14,17 @@ const rest = new REST({ version: '10' }).setToken(token);
// and deploy your commands! // and deploy your commands!
(async () => { (async () => {
try { try {
console.log('Started deleting all application (/) commands.'); console.log('Started deleting all application (/) commands.');
// The put method is used to fully refresh all commands // The put method is used to fully refresh all commands
rest.put(Routes.applicationCommands(clientId), { body: [] }) rest.put(Routes.applicationCommands(clientId), { body: [] })
.then(() => console.log('Successfully deleted all application (/) commands.')) .then(() => console.log('Successfully deleted all application (/) commands.'))
.catch(console.error); .catch(console.error);
} }
catch (error) { catch (error) {
// And of course, make sure you catch and log any errors! // And of course, make sure you catch and log any errors!
console.error(error); console.error(error);
} }
})(); })();

View file

@ -17,9 +17,9 @@ const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment // Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
for (const file of commandFiles) { for (const file of commandFiles) {
const command = require(`./commands/${file}`); const command = require(`./commands/${file}`);
commands.push(command.data.toJSON()); commands.push(command.data.toJSON());
commandsHelp.push(command.data); commandsHelp.push(command.data);
} }
// Construct and prepare an instance of the REST module // Construct and prepare an instance of the REST module
@ -27,85 +27,85 @@ const rest = new REST({ version: '10' }).setToken(token);
// Generate help-text.json // Generate help-text.json
try { try {
console.log(`Generating ${commands.length} help text entries.`); console.log(`Generating ${commands.length} help text entries.`);
let helpJSONString = '{\n'; let helpJSONString = '{\n';
for (let i = 0; i < commandsHelp.length; i++) { for (let i = 0; i < commandsHelp.length; i++) {
helpJSONString += ` "${commandsHelp[i].name}": {\n`; helpJSONString += ` "${commandsHelp[i].name}": {\n`;
helpJSONString += ` "description": "${commandsHelp[i].description}",\n`; helpJSONString += ` "description": "${commandsHelp[i].description}",\n`;
helpJSONString += ' "options": {\n'; helpJSONString += ' "options": {\n';
for (let j = 0; j < commandsHelp[i].options.length; j++) { for (let j = 0; j < commandsHelp[i].options.length; j++) {
helpJSONString += ` "${commandsHelp[i].options[j].name}": {\n`; helpJSONString += ` "${commandsHelp[i].options[j].name}": {\n`;
helpJSONString += ` "description": "${commandsHelp[i].options[j].description}",\n`; helpJSONString += ` "description": "${commandsHelp[i].options[j].description}",\n`;
helpJSONString += ` "required": "${commandsHelp[i].options[j].required}",\n`; helpJSONString += ` "required": "${commandsHelp[i].options[j].required}",\n`;
helpJSONString += ' "choices": {\n'; helpJSONString += ' "choices": {\n';
if (typeof commandsHelp[i].options[j].choices !== 'undefined') { if (typeof commandsHelp[i].options[j].choices !== 'undefined') {
for (let k = 0; k < commandsHelp[i].options[j].choices.length; k++) { for (let k = 0; k < commandsHelp[i].options[j].choices.length; k++) {
helpJSONString += ` "${commandsHelp[i].options[j].choices[k].name}": {\n`; helpJSONString += ` "${commandsHelp[i].options[j].choices[k].name}": {\n`;
helpJSONString += ` "value": "${commandsHelp[i].options[j].choices[k].value}",\n`; helpJSONString += ` "value": "${commandsHelp[i].options[j].choices[k].value}",\n`;
helpJSONString += ' },\n'; helpJSONString += ' },\n';
} }
} }
helpJSONString += ' },\n'; helpJSONString += ' },\n';
helpJSONString += ' },\n'; helpJSONString += ' },\n';
} }
helpJSONString += ' },\n'; helpJSONString += ' },\n';
helpJSONString += ' },\n'; helpJSONString += ' },\n';
} }
helpJSONString += '}'; helpJSONString += '}';
// Lazy way out of removing trailing commas // Lazy way out of removing trailing commas
// See https://stackoverflow.com/a/34347475 // See https://stackoverflow.com/a/34347475
const helpJSON = JSON.stringify(JSON.parse(helpJSONString.replace(/,(?!\s*?[{["'\w])/g, '')), null, 4); const helpJSON = JSON.stringify(JSON.parse(helpJSONString.replace(/,(?!\s*?[{["'\w])/g, '')), null, 4);
// Create data folder if it doesn't exist // Create data folder if it doesn't exist
if (!fs.existsSync('data')) { if (!fs.existsSync('data')) {
fs.mkdirSync('data'); fs.mkdirSync('data');
} }
// Write file to disk // Write file to disk
fs.writeFile('./data/help-text.json', helpJSON, err => { fs.writeFile('./data/help-text.json', helpJSON, err => {
if (err) { if (err) {
console.log(`Unable to write help-text.json: ${err}`); console.log(`Unable to write help-text.json: ${err}`);
console.log('Stopping...'); console.log('Stopping...');
return; return;
} }
}); });
console.log(`Successfully generated ${commandsHelp.length} help text entries.`); console.log(`Successfully generated ${commandsHelp.length} help text entries.`);
console.log(); console.log();
// Update slash commands on Discord's side // Update slash commands on Discord's side
(async () => { (async () => {
try { try {
console.log(`Started refreshing ${commands.length} application (/) commands.`); console.log(`Started refreshing ${commands.length} application (/) commands.`);
// The put method is used to fully refresh all commands // The put method is used to fully refresh all commands
const data = await rest.put( const data = await rest.put(
Routes.applicationCommands(clientId), Routes.applicationCommands(clientId),
{ body: commands }, { body: commands },
); );
console.log(`Successfully reloaded ${data.length} application (/) commands.`); console.log(`Successfully reloaded ${data.length} application (/) commands.`);
} }
catch (error) { catch (error) {
// And of course, make sure you catch and log any errors! // And of course, make sure you catch and log any errors!
console.error(error); console.error(error);
} }
})(); })();
} }
catch (error) { catch (error) {
// And of course, make sure you catch and log any errors! // And of course, make sure you catch and log any errors!
console.error(error); console.error(error);
} }

110
index.js
View file

@ -28,91 +28,91 @@ const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) { for (const file of commandFiles) {
const filePath = path.join(commandsPath, file); const filePath = path.join(commandsPath, file);
const command = require(filePath); const command = require(filePath);
// Set a new item in the Collection with the key as the command name and the value as the exported module // Set a new item in the Collection with the key as the command name and the value as the exported module
if ('data' in command && 'execute' in command) { if ('data' in command && 'execute' in command) {
client.commands.set(command.data.name, command); client.commands.set(command.data.name, command);
} }
else { else {
logger.log(logger.logLevels.WARN, `The command at ${filePath} is missing a required "data" or "execute" property.`); logger.log(logger.logLevels.WARN, `The command at ${filePath} is missing a required "data" or "execute" property.`);
} }
} }
// When the client is ready, run this code (only once) // When the client is ready, run this code (only once)
// We use 'c' for the event parameter to keep it separate from the already defined 'client' // We use 'c' for the event parameter to keep it separate from the already defined 'client'
client.once(Events.ClientReady, c => { client.once(Events.ClientReady, c => {
logger.log(logger.logLevels.INFO, `${logger.colorText('Ready!', logger.textColor.Green)} Logged in as ${logger.colorText(c.user.tag, logger.textColor.Blue)}`); logger.log(logger.logLevels.INFO, `${logger.colorText('Ready!', logger.textColor.Green)} Logged in as ${logger.colorText(c.user.tag, logger.textColor.Blue)}`);
client.user.setPresence({ activities: [{ name: activity }], status: status }); client.user.setPresence({ activities: [{ name: activity }], status: status });
// Track websocket heartbeat with PM2 Histogram // Track websocket heartbeat with PM2 Histogram
let latency = 0; let latency = 0;
setInterval(function() { setInterval(function() {
latency = c.ws.ping; latency = c.ws.ping;
metrics.websocketHeartbeatHist.update(latency); metrics.websocketHeartbeatHist.update(latency);
}, 1000); }, 1000);
// Report current server count with PM2 (Servers counted anonymously) // Report current server count with PM2 (Servers counted anonymously)
metrics.serverCount.set(c.guilds.cache.size); metrics.serverCount.set(c.guilds.cache.size);
}); });
// Client "on" Events // Client "on" Events
// Someone used an interaction // Someone used an interaction
client.on(Events.InteractionCreate, async interaction => { client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return; if (!interaction.isChatInputCommand()) return;
const command = interaction.client.commands.get(interaction.commandName); const command = interaction.client.commands.get(interaction.commandName);
if (!command) { if (!command) {
logger.log(logger.logLevels.ERROR, `No command matching ${interaction.commandName} was found.`); logger.log(logger.logLevels.ERROR, `No command matching ${interaction.commandName} was found.`);
await interaction.reply({ content: `This command no longer exists! Please contact ${botOwner} to report that this is happening!`, ephemeral: true }); await interaction.reply({ content: `This command no longer exists! Please contact ${botOwner} to report that this is happening!`, ephemeral: true });
// Report error to PM2 dashboard // Report error to PM2 dashboard
metrics.interactionErrors.inc(); metrics.interactionErrors.inc();
metrics.io.notifyError(new Error('Interaction doesn\'t exist'), { metrics.io.notifyError(new Error('Interaction doesn\'t exist'), {
custom: { custom: {
interactionCommand: interaction.commandName, interactionCommand: interaction.commandName,
}, },
}); });
return; return;
} }
try { try {
await command.execute(interaction); await command.execute(interaction);
} }
catch (error) { catch (error) {
logger.log(logger.logLevels.ERROR, error); logger.log(logger.logLevels.ERROR, error);
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
// Report error to PM2 dashboard // Report error to PM2 dashboard
metrics.interactionErrors.inc(); metrics.interactionErrors.inc();
metrics.io.notifyError(new Error('Error executing interaction'), { metrics.io.notifyError(new Error('Error executing interaction'), {
custom: { custom: {
interactionCommand: interaction.commandName, interactionCommand: interaction.commandName,
error: error, error: error,
}, },
}); });
} }
// Successful Execution, report as a PM2 metric // Successful Execution, report as a PM2 metric
// If the bot gets a lot of use, consider removing this for performance // If the bot gets a lot of use, consider removing this for performance
metrics.interactionSuccess(); metrics.interactionSuccess();
}); });
// Joined a server // Joined a server
client.on(Events.GuildCreate, guild => { client.on(Events.GuildCreate, guild => {
// Report current server count with PM2 (Servers counted anonymously) // Report current server count with PM2 (Servers counted anonymously)
metrics.serverCount.set(guild.client.guilds.cache.size); metrics.serverCount.set(guild.client.guilds.cache.size);
}); });
// Removed from a server // Removed from a server
client.on(Events.GuildDelete, guild => { client.on(Events.GuildDelete, guild => {
// Report current server count with PM2 (Servers counted anonymously) // Report current server count with PM2 (Servers counted anonymously)
metrics.serverCount.set(guild.client.guilds.cache.size); metrics.serverCount.set(guild.client.guilds.cache.size);
}); });
// Log in to Discord with your client's token // Log in to Discord with your client's token