Skip to content

Commit

Permalink
v0.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasB25 committed May 16, 2024
1 parent 0b8c607 commit b7be740
Show file tree
Hide file tree
Showing 13 changed files with 531 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TOKEN= #Discord Bot Token
CLIENT_ID= #Discord Bot Client ID
Activity=/help
61 changes: 61 additions & 0 deletions src/commands/general/About.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from 'discord.js';

import { Bot, Command } from '../../structures/index.js';

export default class About extends Command {
constructor(client: Bot) {
super(client, {
name: 'about',
nameLocalizations: {
fr: 'à-propos',
},
description: {
content: '📨 | Shows information about the bot',
usage: 'about',
examples: ['about'],
},
descriptionLocalizations: {
fr: '📨 | Affiche des informations sur le bot',
},
category: 'general',
permissions: {
dev: false,
client: ['SendMessages', 'ViewChannel', 'EmbedLinks'],
user: [],
},
cooldown: 3,
options: [],
});
}

async run(client: Bot, interaction: CommandInteraction): Promise<void> {
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel('Invite AikouTicket')
.setURL(
`https://discord.com/oauth2/authorize?client_id=${this.client.user?.id}&scope=bot%20applications.commands&permissions=8`
)
.setStyle(ButtonStyle.Link),
new ButtonBuilder()
.setLabel('Support Server')
.setURL('https://discord.gg/JeaQTqzsJw')
.setStyle(ButtonStyle.Link)
);

const embed = client
.embed()
.setColor(this.client.color)
.setAuthor({ name: 'AikouTicket' })
.addFields(
{ name: 'Creator', value: '[LucasB25](https://github.com/lucasb25)', inline: true },
{
name: 'Repository',
value: '[Here](https://github.com/lucasb25/AikouTicket)',
inline: true,
},
{ name: 'Support', value: '[Here](https://discord.gg/AhUJa2kdAr)', inline: true }
);

await interaction.reply({ embeds: [embed], components: [row] });
}
}
41 changes: 41 additions & 0 deletions src/commands/general/Invite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CommandInteraction } from 'discord.js';

import { Bot, Command } from '../../structures/index.js';

export default class Invite extends Command {
constructor(client: Bot) {
super(client, {
name: 'invite',
nameLocalizations: {
fr: 'invite',
},
description: {
content: '📨 | Get the bot invite link',
usage: 'invite',
examples: ['invite'],
},
descriptionLocalizations: {
fr: '📨 | Afficher le lien d\'invitation.',
},
category: 'general',
permissions: {
dev: false,
client: ['SendMessages', 'ViewChannel', 'EmbedLinks'],
user: [],
},
cooldown: 3,
options: [],
});
}

async run(client: Bot, interaction: CommandInteraction): Promise<void> {
const embed = client
.embed()
.setColor(this.client.color)
.setDescription(
`Invite me to your server with this link: [Invite](https://discord.com/oauth2/authorize?client_id=${client.user?.id}&scope=bot%20applications.commands&permissions=8)`
);

await interaction.reply({ embeds: [embed] });
}
}
38 changes: 38 additions & 0 deletions src/commands/general/Ping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { CommandInteraction } from 'discord.js';

import { Bot, Command } from '../../structures/index.js';

export default class Ping extends Command {
constructor(client: Bot) {
super(client, {
name: 'ping',
nameLocalizations: {
fr: 'ping',
},
description: {
content: '🏓 | Get the bot latency',
usage: 'ping',
examples: ['ping'],
},
descriptionLocalizations: {
fr: '🏓 | Obtiens la latence du bot.',
},
category: 'general',
permissions: {
dev: false,
client: ['SendMessages', 'ViewChannel', 'EmbedLinks'],
user: [],
},
cooldown: 3,
options: [],
});
}
async run(client: Bot, interaction: CommandInteraction): Promise<void> {
const embed = client
.embed()
.setColor(this.client.color)
.setDescription(`**Pong:** \`${Math.round(client.ws.ping)}ms\``);

await interaction.reply({ embeds: [embed] });
}
}
8 changes: 8 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'dotenv/config';

export default {
token: process.env.TOKEN,
clientId: process.env.CLIENT_ID,
color: 0x2f3136,
activity: process.env.Activity,
};
56 changes: 56 additions & 0 deletions src/events/client/InteractionCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { CommandInteraction } from 'discord.js';

import { Bot, Event, EventsTypes } from '../../structures/index.js';

export default class InteractionCreate extends Event {
constructor(client: Bot, file: string) {
super(client, file, {
name: EventsTypes.InteractionCreate,
});
}

public async run(interaction: CommandInteraction): Promise<void> {
try {
if (interaction.isCommand()) {
const commandName = interaction.commandName;
const command = this.client.commands.get(commandName);
if (!command) return;

await command.run(this.client, interaction);
}
} catch (error) {
this.client.logger.error(error);

if (this.isNSFWError(error)) {
await this.handleNSFWError(interaction);
} else {
await this.replyWithError(
interaction,
'There was an error while executing this command!'
);
}
}
}

private isNSFWError(error: any): boolean {
return (
error instanceof Error &&
error.message ===
'Prediction failed: NSFW content detected. Try running it again, or try a different prompt.'
);
}

private async handleNSFWError(interaction: CommandInteraction): Promise<void> {
await interaction[interaction.replied ? 'editReply' : 'reply']({
content: 'NSFW content detected. You can\'t generate NSFW images!',
ephemeral: true,
});
}

private async replyWithError(interaction: CommandInteraction, message: string): Promise<void> {
await interaction[interaction.replied ? 'editReply' : 'reply']({
content: message,
ephemeral: true,
});
}
}
17 changes: 17 additions & 0 deletions src/events/client/MessageCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Message, TextChannel } from 'discord.js';

import { Bot, Event, EventsTypes } from '../../structures/index.js';

export default class MessageCreate extends Event {
constructor(client: Bot, file: string) {
super(client, file, {
name: EventsTypes.MessageCreate,
});
}

public async run(message: Message): Promise<void> {
if (message.channel instanceof TextChannel) {
// CODE
}
}
}
33 changes: 33 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ClientOptions, Partials } from 'discord.js';

import config from './config.js';
import Bot from './structures/Client.js';

const clientOptions: ClientOptions = {
intents: 131059,
allowedMentions: {
parse: ['users', 'roles', 'everyone'],
repliedUser: false,
},
partials: [
Partials.GuildMember,
Partials.Message,
Partials.User,
Partials.ThreadMember,
Partials.Channel,
Partials.GuildScheduledEvent,
],
};

const client = new Bot(clientOptions);

client.start(config.token);

function setupEventListeners(client: Bot): void {
process.on('unhandledRejection', (error: Error) => client.logger.error(error));
process.on('uncaughtException', (error: Error) => client.logger.error(error));
process.on('warning', (warning: Error) => client.logger.warn(warning));
process.on('exit', () => client.logger.warn('Process exited!'));
}

setupEventListeners(client);
104 changes: 104 additions & 0 deletions src/structures/Client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
ApplicationCommandType,
Client,
ClientOptions,
Collection,
EmbedBuilder,
PermissionsBitField,
REST,
RESTPostAPIChatInputApplicationCommandsJSONBody,
Routes,
} from 'discord.js';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import Logger from './Logger.js';
import config from '../config.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default class Bot extends Client {
public config = config;
public logger = new Logger();
public readonly color = config.color;
public commands = new Collection<string, any>();
private data: RESTPostAPIChatInputApplicationCommandsJSONBody[] = [];

constructor(options: ClientOptions) {
super(options);
}

public async start(token: string): Promise<void> {
try {
this.logger.start('Starting bot...');
await this.loadCommands();
await this.loadEvents();
await this.login(token);
} catch (error) {
this.logger.error(error);
}
}


public embed(): EmbedBuilder {
return new EmbedBuilder().setColor(this.config.color as any);
}

private async loadEvents(): Promise<void> {
const events = fs.readdirSync(path.join(__dirname, '../events'));
for (const event of events) {
const eventFiles = fs
.readdirSync(path.join(__dirname, `../events/${event}`))
.filter(file => file.endsWith('.js'));
for (const file of eventFiles) {
const eventFile = (await import(`../events/${event}/${file}`)).default;
const eventClass = new eventFile(this, file);
this.on(eventClass.name, (...args: unknown[]) => eventClass.run(...args));
}
}
}

private async loadCommands(): Promise<void> {
const commandsPath = fs.readdirSync(path.join(__dirname, '../commands'));
for (const commandPath of commandsPath) {
const commandFiles = fs
.readdirSync(path.join(__dirname, `../commands/${commandPath}`))
.filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const commandFile = (await import(`../commands/${commandPath}/${file}`)).default;
const command = new commandFile(this, file);
this.commands.set(command.name, command);
const data: RESTPostAPIChatInputApplicationCommandsJSONBody = {
name: command.name,
description: command.description.content,
type: ApplicationCommandType.ChatInput,
options: command.options || null,
name_localizations: command.nameLocalizations || null,
description_localizations: command.descriptionLocalizations || null,
default_member_permissions:
command.permissions.user.length > 0 ? command.permissions.user : null,
};
if (command.permissions.user.length > 0) {
const permissionValue = PermissionsBitField.resolve(command.permissions.user);
data.default_member_permissions =
typeof permissionValue === 'bigint'
? permissionValue.toString()
: permissionValue;
}
this.data.push(data);
}
}

this.once('ready', async () => {
const applicationCommands = Routes.applicationCommands(this.config.clientId ?? '');
try {
const rest = new REST({ version: '10' }).setToken(this.config.token ?? '');
await rest.put(applicationCommands, { body: this.data });
this.logger.info(`Successfully loaded slash commands!`);
} catch (error) {
this.logger.error(error);
}
});
}
}
Loading

0 comments on commit b7be740

Please sign in to comment.