Initial commit

This commit is contained in:
Baipyrus 2024-01-26 13:28:02 +00:00
commit c222b0db1d
16 changed files with 2101 additions and 0 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
TOKEN=
CLIENT=

11
.eslintignore Normal file
View File

@ -0,0 +1,11 @@
.DS_Store
node_modules
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

12
.eslintrc.json Normal file
View File

@ -0,0 +1,12 @@
{
"env": {
"node": true,
"es2021": true
},
"extends": ["eslint:recommended", "prettier"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {}
}

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.DS_Store
node_modules
.env
.env.*
!.env.example

11
.prettierignore Normal file
View File

@ -0,0 +1,11 @@
.DS_Store
node_modules
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": [],
"overrides": []
}

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# A DiscordJS Template
## Getting started
- Create a new repository from this template.
- Remove example commands:
- Either permanently delete directory `commands/examples/`
- Or move `commands/examples/` elsewhere, commit deletion, add to [`.gitignore`](.gitignore) and move back in
- Look through, copy and/or modify the [examples](commands/examples/).
- Either read a [guide](https://discordjs.guide/)!
- Or read the [docs](https://discord.js.org/docs)!

View File

@ -0,0 +1,36 @@
import {
ActionRowBuilder,
ModalBuilder,
SlashCommandBuilder,
TextInputBuilder,
TextInputStyle
} from 'discord.js';
export const data = new SlashCommandBuilder()
.setName('login')
.setDescription('Opens a login pop-up.');
export async function modalSubmit(interaction) {
await interaction.reply({ content: 'Successfully submitted Form!', ephemeral: true });
}
export async function execute(interaction) {
const modal = new ModalBuilder().setCustomId('login-modal').setTitle('Login Form');
const user = new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('user')
.setLabel('Enter username:')
.setStyle(TextInputStyle.Short)
.setRequired(true)
);
const password = new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('password')
.setLabel('Enter password:')
.setStyle(TextInputStyle.Short)
.setRequired(true)
);
modal.addComponents(user, password);
await interaction.showModal(modal);
}

View File

@ -0,0 +1,6 @@
import { SlashCommandBuilder } from 'discord.js';
export const data = new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!');
export async function execute(interaction) {
await interaction.reply({ content: 'Pong!', ephemeral: true });
}

View File

@ -0,0 +1,42 @@
import {
ActionRowBuilder,
ComponentType,
RoleSelectMenuBuilder,
SlashCommandBuilder
} from 'discord.js';
export const data = new SlashCommandBuilder()
.setName('role')
.setDMPermission(false)
.setDescription('Provides a role selector.');
export async function execute(interaction) {
const roles = await interaction.guild.roles.fetch();
const choices = roles.filter((r) => r.name.startsWith('test')).map((r) => r.id);
const button = new RoleSelectMenuBuilder()
.setMinValues(1)
.setMaxValues(25)
.setCustomId('role')
.setDefaultRoles(choices)
.setPlaceholder('Select at least one role.');
const row = new ActionRowBuilder().addComponents(button);
const response = await interaction.reply({
components: [row],
ephemeral: true
});
const collector = response.createMessageComponentCollector({
componentType: ComponentType.RoleSelect,
time: 120_000
});
collector.on('collect', async (i) => {
const selection = roles
.filter((r) => i.values.includes(r.id))
.map((r) => r.name)
.join(', ');
await i.reply({ content: `You have selected: "${selection}".`, ephemeral: true });
});
}

71
deploy.js Normal file
View File

@ -0,0 +1,71 @@
import { REST, Routes } from 'discord.js';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { promisify } from 'util';
import { config } from 'dotenv';
import { readdir } from 'fs';
config();
const readdirAsync = promisify(readdir);
const required = ['data', 'execute'];
const optional = ['autocomplete', 'modalSubmit'];
// Construct and prepare an instance of the REST module
const rest = new REST().setToken(process.env.TOKEN);
// and deploy your commands!
const putCommands = async (commands) => {
try {
console.info(`[INFO] Started refreshing ${commands.length} application (/) commands.`);
// The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put(Routes.applicationCommands(process.env.CLIENT), { body: commands });
console.info(`[INFO] Successfully reloaded ${data.length} application (/) commands.`);
} catch (error) {
// And of course, make sure you catch and log any errors!
console.error('[ERROR] Uncaught error:');
console.error(error);
}
};
// Register commands from sub-directories
const __dirname = dirname(fileURLToPath(import.meta.url));
const mainPath = join(__dirname, 'commands');
readdirAsync(mainPath)
.then(
async (categories) =>
// For each category directory
await Promise.all(
categories.map(async (category) => {
const catPath = join(mainPath, category);
const files = (await readdirAsync(catPath)).filter((file) => file.endsWith('.js') && !file.endsWith('.example.js'));
// For each command file
return await Promise.all(
files.map(async (current) => {
const filePath = join(catPath, current);
const command = await import(filePath);
// Warn incomplete commands
if (!required.every((name) => name in command)) {
console.error(
`[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.`
);
return 0;
}
const properties = optional.filter((name) => !(name in command));
if (properties.length > 0)
properties.forEach((name) =>
console.warn(
`[WARNING] The command at ${filePath} is missing a optional "${name}" property.`
)
);
// Add command to collection
return command.data.toJSON();
})
);
})
)
)
.then((commands) => commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0))
.then(putCommands);

View File

@ -0,0 +1,61 @@
import { Events } from 'discord.js';
const chatInputCommand = async (interaction, command) => {
// Try executing command
try {
console.info(`[INFO] Command ${interaction.commandName} was executed.`);
await command.execute(interaction);
} catch (error) {
console.error(error);
// Follow up/reply with error message
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
});
}
};
const genericExecute = async (interaction, command, name, description) => {
try {
console.info(`[INFO] Command ${interaction.commandName} ${description ?? `used "${name}"`}.`);
await command[name](interaction);
} catch (error) {
console.error(error);
}
};
export const name = Events.InteractionCreate;
export async function execute(interaction) {
let command = interaction.client.commands.get(interaction.commandName);
// Execute slash commands
if (interaction.isChatInputCommand()) {
await chatInputCommand(interaction, command);
return;
}
// Autocomplete input
if (interaction.isAutocomplete() && 'autocomplete' in command) {
await genericExecute(interaction, command, 'autocomplete');
return;
}
// Modeal submit event
if (interaction.isModalSubmit()) {
const name = interaction.customId.split('-')[0];
command = interaction.client.commands.get(name);
await genericExecute(interaction, command, 'modalSubmit', 'submitted a modal');
return;
}
// Check if command exists
if (interaction.commandName && !command) {
console.warn(`[WARNING] No command matching ${interaction.commandName} was found.`);
return;
}
}

7
events/ready.js Normal file
View File

@ -0,0 +1,7 @@
import { Events } from 'discord.js';
export const name = Events.ClientReady;
export const once = true;
export function execute(client) {
console.info(`[INFO] Ready! Logged in as ${client.user.tag}`);
}

81
index.js Normal file
View File

@ -0,0 +1,81 @@
import { Client, Collection, GatewayIntentBits } from 'discord.js';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { promisify } from 'util';
import { config } from 'dotenv';
import { readdir } from 'fs';
config();
const readdirAsync = promisify(readdir);
const required = ['data', 'execute'];
const optional = ['autocomplete', 'modalSubmit'];
const runClient = (commands, events) => {
// Create a new client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.commands = new Collection();
commands.forEach((c) => client.commands.set(c.data.name, c));
events.forEach((e) =>
e.once
? client.once(e.name, (...args) => e.execute(...args))
: client.on(e.name, (...args) => e.execute(...args))
);
// Log in to Discord with your client's token
client.login(process.env.TOKEN);
};
// Register commands from sub-directories
const __dirname = dirname(fileURLToPath(import.meta.url));
const cmdPath = join(__dirname, 'commands');
const evtPath = join(__dirname, 'events');
readdirAsync(cmdPath)
.then(
async (categories) =>
// For each category directory
await Promise.all(
categories.map(async (category) => {
const catPath = join(cmdPath, category);
const content = await readdirAsync(catPath);
const files = content.filter((file) => file.endsWith('.js') && !file.endsWith('.example.js'));
// For each command file
return await Promise.all(
files.map(async (current) => {
const filePath = join(catPath, current);
const command = await import(filePath);
// Warn incomplete commands
if (!required.every((name) => name in command)) {
console.error(
`[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.`
);
return 0;
}
const properties = optional.filter((name) => !(name in command));
if (properties.length > 0)
properties.forEach((name) =>
console.warn(
`[WARNING] The command at ${filePath} is missing a optional "${name}" property.`
)
);
// Add command to collection
return command;
})
);
})
)
)
.then((commands) => commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0))
.then(async (commands) => {
const content = await readdirAsync(evtPath);
const files = content.filter((file) => file.endsWith('.js'));
const events = await Promise.all(
files.map(async (current) => {
const filePath = join(evtPath, current);
const command = await import(filePath);
// Add event to collection
return command;
})
);
runClient(commands, events);
});

1711
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "discord-bot",
"version": "0.0.1",
"description": "",
"private": true,
"main": "index.js",
"scripts": {
"start": "node .",
"deploy": "node deploy.js",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"author": "",
"license": "ISC",
"dependencies": {
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"proxy-agent": "^6.3.1"
},
"devDependencies": {
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.1.1"
},
"type": "module"
}