Compare commits
9 Commits
9e2e35f595
...
e3ad17d25e
| Author | SHA1 | Date | |
|---|---|---|---|
| e3ad17d25e | |||
| ace276b2a8 | |||
| c9c88baa11 | |||
| 3e0654047d | |||
| ae6296e0ae | |||
| 20540f7b53 | |||
| 207b16e1cd | |||
| f69a62a314 | |||
| 6ad0ba5340 |
+1
-1
@@ -5,5 +5,5 @@ import { defineConfig } from "eslint/config";
|
|||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
{ files: ["src/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
|
{ files: ["src/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended, { ignores: ["dist/**", "**/dist/**"] }
|
||||||
]);
|
]);
|
||||||
|
|||||||
Generated
+917
-7
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,9 @@
|
|||||||
"typescript-eslint": "^8.52.0"
|
"typescript-eslint": "^8.52.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@discordjs/opus": "^0.10.0",
|
||||||
|
"@discordjs/voice": "^0.19.0",
|
||||||
|
"@snazzah/davey": "^0.1.9",
|
||||||
"colorts": "^0.1.63",
|
"colorts": "^0.1.63",
|
||||||
"discord.js": "^14.25.1",
|
"discord.js": "^14.25.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
|
|||||||
+32
-26
@@ -18,10 +18,11 @@ import {
|
|||||||
import { Logger } from './utils/log';
|
import { Logger } from './utils/log';
|
||||||
import { config } from './utils/config';
|
import { config } from './utils/config';
|
||||||
import { Bot } from './bot';
|
import { Bot } from './bot';
|
||||||
|
import { isModule } from './utils/misc';
|
||||||
|
|
||||||
export interface Command {
|
export interface Command {
|
||||||
name?: string;
|
name?: string;
|
||||||
requiresAdmin: boolean;
|
requiresAdmin?: boolean;
|
||||||
ownerOnly?: boolean;
|
ownerOnly?: boolean;
|
||||||
execute?: (interaction: ChatInputCommandInteraction) => Promise<void>;
|
execute?: (interaction: ChatInputCommandInteraction) => Promise<void>;
|
||||||
autocomplete?: (interaction: AutocompleteInteraction) => Promise<void>;
|
autocomplete?: (interaction: AutocompleteInteraction) => Promise<void>;
|
||||||
@@ -33,7 +34,7 @@ export interface Command {
|
|||||||
builder?: SlashCommandOptionsOnlyBuilder;
|
builder?: SlashCommandOptionsOnlyBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommandCategoryInfo {
|
export interface CommandCategoryInfo {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
@@ -51,8 +52,8 @@ export class CommandManager {
|
|||||||
private categories: Array<CommandCategory> = [];
|
private categories: Array<CommandCategory> = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
public
|
public
|
||||||
*/
|
*/
|
||||||
public constructor(cmdFolder: string) {
|
public constructor(cmdFolder: string) {
|
||||||
this.folderPath = path.resolve(__dirname, cmdFolder);
|
this.folderPath = path.resolve(__dirname, cmdFolder);
|
||||||
this.log = new Logger('Command manager');
|
this.log = new Logger('Command manager');
|
||||||
@@ -95,8 +96,8 @@ export class CommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
internal
|
internal
|
||||||
*/
|
*/
|
||||||
private async populateCommands() {
|
private async populateCommands() {
|
||||||
if (!fs.existsSync(this.folderPath))
|
if (!fs.existsSync(this.folderPath))
|
||||||
throw new Error(`Command directory not found: ${this.folderPath}`);
|
throw new Error(`Command directory not found: ${this.folderPath}`);
|
||||||
@@ -144,12 +145,21 @@ export class CommandManager {
|
|||||||
catPath: string
|
catPath: string
|
||||||
): Promise<CommandCategoryInfo | undefined> {
|
): Promise<CommandCategoryInfo | undefined> {
|
||||||
try {
|
try {
|
||||||
const descriptorPath = path.join(catPath, 'category.json');
|
const extensions = ['js', 'mjs', 'cjs', 'ts'];
|
||||||
if (!fs.existsSync(descriptorPath))
|
|
||||||
throw new Error('Missing categoryinfo.json');
|
|
||||||
|
|
||||||
const content = await fs.promises.readFile(descriptorPath, 'utf-8');
|
for (const ext of extensions) {
|
||||||
return JSON.parse(content) as CommandCategoryInfo;
|
const descriptorPath = path.join(catPath, `_category.${ext}`);
|
||||||
|
if (!fs.existsSync(descriptorPath)) continue;
|
||||||
|
|
||||||
|
const module = await import(`file://${descriptorPath}`);
|
||||||
|
return (
|
||||||
|
module.default?.default ||
|
||||||
|
module.default ||
|
||||||
|
(module as CommandCategoryInfo)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Missing categoryinfo.json');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('Error loading category info at %s: %s', catPath, err);
|
this.log.error('Error loading category info at %s: %s', catPath, err);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -159,7 +169,7 @@ export class CommandManager {
|
|||||||
private async loadCatCommands(catPath: string): Promise<Array<Command>> {
|
private async loadCatCommands(catPath: string): Promise<Array<Command>> {
|
||||||
const promises = fs
|
const promises = fs
|
||||||
.readdirSync(catPath)
|
.readdirSync(catPath)
|
||||||
.filter((file) => this.isValidCommandFile(file))
|
.filter((file) => isModule(file))
|
||||||
.map(
|
.map(
|
||||||
async (file) => await this.attemptLoadCommand(path.join(catPath, file))
|
async (file) => await this.attemptLoadCommand(path.join(catPath, file))
|
||||||
);
|
);
|
||||||
@@ -167,12 +177,8 @@ export class CommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
cmd parsing
|
cmd parsing
|
||||||
*/
|
*/
|
||||||
private isValidCommandFile(file: string): boolean {
|
|
||||||
return file.endsWith('.js') || file.endsWith('.ts');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async attemptLoadCommand(filePath: string): Promise<Command | null> {
|
private async attemptLoadCommand(filePath: string): Promise<Command | null> {
|
||||||
try {
|
try {
|
||||||
const module = await import(`file://${filePath}`);
|
const module = await import(`file://${filePath}`);
|
||||||
@@ -188,8 +194,8 @@ export class CommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
misc functions
|
misc functions
|
||||||
*/
|
*/
|
||||||
private async registerSlashCommands(): Promise<void> {
|
private async registerSlashCommands(): Promise<void> {
|
||||||
this.log.info('Registering slash commands...');
|
this.log.info('Registering slash commands...');
|
||||||
|
|
||||||
@@ -214,8 +220,8 @@ export class CommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
interaction handling
|
interaction handling
|
||||||
*/
|
*/
|
||||||
private async executeCommandInteraction(
|
private async executeCommandInteraction(
|
||||||
interaction: ChatInputCommandInteraction
|
interaction: ChatInputCommandInteraction
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -290,12 +296,12 @@ export class CommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
event listeners
|
event listeners
|
||||||
*/
|
*/
|
||||||
private async onInteraction(interaction: BaseInteraction): Promise<void> {
|
private async onInteraction(interaction: BaseInteraction): Promise<void> {
|
||||||
/*
|
/*
|
||||||
cmd execution
|
cmd execution
|
||||||
*/
|
*/
|
||||||
if (interaction.isChatInputCommand())
|
if (interaction.isChatInputCommand())
|
||||||
return this.executeCommandInteraction(interaction);
|
return this.executeCommandInteraction(interaction);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { CommandCategoryInfo } from '../../commands';
|
||||||
|
|
||||||
|
const info: CommandCategoryInfo = {
|
||||||
|
name: 'Voice',
|
||||||
|
description: 'Voice chat related commands'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default info;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { ChatInputCommandInteraction, GuildMember, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { Command } from "../../commands";
|
||||||
|
import { CreateVoiceConnectionOptions, getVoiceConnection, joinVoiceChannel, JoinVoiceChannelOptions } from "@discordjs/voice";
|
||||||
|
|
||||||
|
const builder = new SlashCommandBuilder()
|
||||||
|
.setName("join")
|
||||||
|
.setDescription("Makes the bot join your current voice channel");
|
||||||
|
|
||||||
|
const cmd: Command = {
|
||||||
|
name: builder.name,
|
||||||
|
builder: builder,
|
||||||
|
execute: async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||||
|
const member = interaction.member as GuildMember;
|
||||||
|
if (!member || !interaction.guild) {
|
||||||
|
interaction.reply("This command only works on guilds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!member.voice.channelId) {
|
||||||
|
interaction.reply("You are not currently on a voice channel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const me = interaction.guild.members.me as GuildMember;
|
||||||
|
|
||||||
|
if (getVoiceConnection(interaction.guild.id) && me.voice.channelId === member.voice.channelId) {
|
||||||
|
interaction.reply("Already connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const voiceOptions: JoinVoiceChannelOptions & CreateVoiceConnectionOptions = {
|
||||||
|
channelId: member.voice.channelId,
|
||||||
|
guildId: interaction.guild.id,
|
||||||
|
adapterCreator: interaction.guild.voiceAdapterCreator
|
||||||
|
};
|
||||||
|
|
||||||
|
const connection = await joinVoiceChannel(voiceOptions);
|
||||||
|
if (!connection) {
|
||||||
|
interaction.reply("Unable to join");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
interaction.reply("Joined");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cmd;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { ChatInputCommandInteraction, GuildMember, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { Command } from "../../commands";
|
||||||
|
import { getVoiceConnection } from "@discordjs/voice";
|
||||||
|
|
||||||
|
const builder = new SlashCommandBuilder()
|
||||||
|
.setName("leave")
|
||||||
|
.setDescription("Makes the bot leave its current voice channel");
|
||||||
|
|
||||||
|
const cmd: Command = {
|
||||||
|
name: builder.name,
|
||||||
|
builder: builder,
|
||||||
|
execute: async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||||
|
const member = interaction.member as GuildMember;
|
||||||
|
if (!member || interaction.guild === null) {
|
||||||
|
interaction.reply("This command only works on guilds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = getVoiceConnection(interaction.guildId as string);
|
||||||
|
if (!connection) {
|
||||||
|
interaction.reply('currently not connected to a voice channel')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.disconnect();
|
||||||
|
connection.destroy();
|
||||||
|
|
||||||
|
interaction.reply("Disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cmd;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { VoiceState } from "discord.js";
|
||||||
|
import { Command } from "../../commands";
|
||||||
|
import { getVoiceConnection } from "@discordjs/voice";
|
||||||
|
|
||||||
|
const cmd: Command = {
|
||||||
|
voiceStateListener: async function (oldState: VoiceState): Promise<void> {
|
||||||
|
const guild = oldState.guild;
|
||||||
|
if (!guild) return;
|
||||||
|
|
||||||
|
const voiceConnection = getVoiceConnection(guild.id);
|
||||||
|
if (!voiceConnection) return;
|
||||||
|
|
||||||
|
const me = guild.members.me;
|
||||||
|
if (!me)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!me.voice.channel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (me.voice.channel.members.size > 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
voiceConnection.disconnect();
|
||||||
|
voiceConnection.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cmd;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const MOD_EXCLUDE: Array<string> = ['.d.ts'];
|
||||||
|
const MOD_INCLUDE: Array<string> = ['.ts', '.js', '.mjs', '.cjs'];
|
||||||
|
export function isModule(path: string): boolean {
|
||||||
|
for (const ext of MOD_EXCLUDE) if (path.endsWith(ext)) return false;
|
||||||
|
|
||||||
|
for (const ext of MOD_INCLUDE) if (path.endsWith(ext)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user