Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ const config = {
// Command permission settings
command: {
disableCommand: [], // Disabled commands, all enabled by default
adminCommand: ['blacklist', 'language','server', 'status'], // Admin commands, only Admin role user can use
adminCommand: ['blacklist','server', 'status'], // Admin commands, only Admin role user can use
djCommand: ['dj', 'filter'], // DJ commands, only DJ role user can use
// Supported commands: 'skip', 'seek', 'pause'
// When a command name is listed here, only the requester of the currently playing song may use it.
Expand Down
403 changes: 203 additions & 200 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 10 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"build:dashboard": "cd dashboard && npm run generate",
"install:dashboard": "cd dashboard && npm install",
"dev": "tsc --noEmit --incremental false && tsx ./src/index.ts",
"lint": "eslint . --color"
"lint": "eslint . --color",
"typecheck": "npx tsc --noEmit"
},
"repository": {
"type": "git",
Expand All @@ -25,33 +26,31 @@
},
"homepage": "https://github.com/hmes98318/Music-Disc#readme",
"overrides": {
"brace-expansion": "^5.0.6",
"qs": "^6.15.2",
"ws": "^8.21.0"
"esbuild": "^0.28.1"
},
"dependencies": {
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.10.0",
"better-sqlite3": "^12.10.1",
"cookie": "^1.1.1",
"discord.js": "~14.26.4",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"i18next": "^26.2.0",
"i18next": "^26.3.1",
"i18next-fs-backend": "^2.6.6",
"lavashark": "^2.3.2",
"undici": "^8.3.0",
"undici": "^8.4.1",
"zod": "^4.4.3"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/better-sqlite3": "^7.6.13",
"@types/express": "^5.0.6",
"@types/node": "^25.9.1",
"eslint": "^10.4.0",
"@types/node": "^25.9.3",
"eslint": "^10.5.0",
"globals": "^17.6.0",
"tsx": "^4.22.3",
"tsx": "^4.22.4",
"typescript": "~6.0.3",
"typescript-eslint": "^8.59.4"
"typescript-eslint": "^8.61.0"
},
"engines": {
"node": ">=22.22.3"
Expand Down
2 changes: 2 additions & 0 deletions src/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { NodeOptions } from 'lavashark/typings/src/@types/index.js';
import type { Language } from '../lib/i18n/Language.js';
import type { Logger } from '../lib/Logger.js';
import type { BlacklistManager } from '../lib/BlacklistManager.js';
import type { GuildLanguageManager } from '../lib/GuildLanguageManager.js';
import type { DashboardManager } from '../lib/DashboardManager.js';
import type { CommandRegistry } from '../commands/base/CommandRegistry.js';
import type { IPBlockerConfig, SessionManagerConfig } from './SessionManager.types.js';
Expand Down Expand Up @@ -87,6 +88,7 @@ export type Bot = {
i18n: i18n;
lang: Language;
blacklistManager?: BlacklistManager;
guildLanguageManager?: GuildLanguageManager;
}

/**
Expand Down
11 changes: 11 additions & 0 deletions src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from './loader/index.js';
import { Logger } from './lib/Logger.js';
import { BlacklistManager } from './lib/BlacklistManager.js';
import { GuildLanguageManager } from './lib/GuildLanguageManager.js';
import { DashboardManager } from './lib/DashboardManager.js';
import { QueuePersistence } from './lib/QueuePersistence.js';
import { cst } from './utils/constants.js';
Expand Down Expand Up @@ -91,6 +92,10 @@ class App {
(this.#client as any).queuePersistence = new QueuePersistence(this.bot);
(this.#client as any).queuePersistence.initialize();
}

// Initialize guild language manager
this.bot.guildLanguageManager = new GuildLanguageManager(this.bot);
this.bot.guildLanguageManager.initialize();
}


Expand Down Expand Up @@ -166,6 +171,12 @@ class App {
this.bot.blacklistManager.close();
}

// Close guild language manager database
if (this.bot.guildLanguageManager) {
this.bot.logger.log( this.bot.shardId, 'Closing guild language database...');
this.bot.guildLanguageManager.close();
}

clearTimeout(timeout);
this.bot.logger.log( this.bot.shardId, 'Server closed gracefully.');

Expand Down
18 changes: 9 additions & 9 deletions src/commands/BlacklistCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class BlacklistCommand extends BaseCommand {

protected async run(bot: Bot, client: Client, context: CommandContext): Promise<void> {
if (!bot.blacklistManager) {
await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_BLACKLIST_NOT_INITIALIZED'));
await context.replyEphemeralError(bot, context.t('commands:ERROR_BLACKLIST_NOT_INITIALIZED'));
return;
}

Expand Down Expand Up @@ -99,7 +99,7 @@ export class BlacklistCommand extends BaseCommand {
const action = args[0]?.toLowerCase();

if (!action || !['add', 'remove', 'list'].includes(action)) {
await context.replyEphemeralError(bot, client.i18n.t('commands:CONFIG_BLACKLIST_USAGE'));
await context.replyEphemeralError(bot, context.t('commands:CONFIG_BLACKLIST_USAGE'));
return;
}

Expand All @@ -111,7 +111,7 @@ export class BlacklistCommand extends BaseCommand {
// Extract user ID from mention or raw ID
const userArg = args[1];
if (!userArg) {
await context.replyEphemeralError(bot, client.i18n.t('commands:CONFIG_BLACKLIST_USAGE'));
await context.replyEphemeralError(bot, context.t('commands:CONFIG_BLACKLIST_USAGE'));
return;
}

Expand All @@ -127,26 +127,26 @@ export class BlacklistCommand extends BaseCommand {
async #addUser(bot: Bot, _client: Client, context: CommandContext, userId: string): Promise<void> {
const success = bot.blacklistManager!.add(userId);
if (success) {
await context.replySuccess(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_ADDED', { userId }));
await context.replySuccess(bot, context.t('commands:MESSAGE_BLACKLIST_ADDED', { userId }));
} else {
await context.replyEphemeralError(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_ALREADY_LISTED', { userId }));
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_BLACKLIST_ALREADY_LISTED', { userId }));
}
}

async #removeUser(bot: Bot, _client: Client, context: CommandContext, userId: string): Promise<void> {
const success = bot.blacklistManager!.remove(userId);
if (success) {
await context.replySuccess(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_REMOVED', { userId }));
await context.replySuccess(bot, context.t('commands:MESSAGE_BLACKLIST_REMOVED', { userId }));
} else {
await context.replyEphemeralError(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_NOT_LISTED', { userId }));
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_BLACKLIST_NOT_LISTED', { userId }));
}
}

async #listUsers(bot: Bot, _client: Client, context: CommandContext): Promise<void> {
const users = bot.blacklistManager!.getAll();

if (users.length === 0) {
await context.replyText(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_LIST_EMPTY'));
await context.replyText(bot, context.t('commands:MESSAGE_BLACKLIST_LIST_EMPTY'));
return;
}

Expand All @@ -155,7 +155,7 @@ export class BlacklistCommand extends BaseCommand {

const embed = new EmbedBuilder()
.setColor(bot.config.bot.embedsColors.message as any)
.setTitle(_client.i18n.t('commands:MESSAGE_BLACKLIST_LIST_TITLE'))
.setTitle(context.t('commands:MESSAGE_BLACKLIST_LIST_TITLE'))
.setDescription(userList)
.setTimestamp();

Expand Down
4 changes: 2 additions & 2 deletions src/commands/ClearCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class ClearCommand extends BaseCommand {
const player = client.lavashark.getPlayer(context.guild!.id);

if (!player || !player.playing) {
await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING'));
await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING'));
return;
}

Expand All @@ -41,7 +41,7 @@ export class ClearCommand extends BaseCommand {
await context.react('👍');
}
else {
await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_CLEAR_SUCCESS'));
await context.replySuccess(bot, context.t('commands:MESSAGE_CLEAR_SUCCESS'));
}
}
}
4 changes: 2 additions & 2 deletions src/commands/DashboardCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class DashboardCommand extends BaseCommand {
const player = client.lavashark.getPlayer(context.guild!.id);

if (!player || !player.dashboardMsg) {
await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING'));
await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING'));
return;
}

Expand Down Expand Up @@ -54,7 +54,7 @@ export class DashboardCommand extends BaseCommand {
await context.react('👍');
}
else {
await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_DASHBOARD_SUCCESS'));
await context.replySuccess(bot, context.t('commands:MESSAGE_DASHBOARD_SUCCESS'));
}
}
}
26 changes: 13 additions & 13 deletions src/commands/DjCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,39 +53,39 @@ export class DjCommand extends BaseCommand {

// Check permission for adding DJ - only admins can add/remove DJs
if (!bot.config.bot.admin.includes(context.user.id)) {
await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_ADMIN_ONLY'));
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_ADMIN_ONLY'));
return;
}

// Validate user
if (targetUser.bot) {
await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_NO_BOTS'));
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_NO_BOTS'));
return;
}

// Handle different DJ modes
if (bot.config.bot.djMode === DJModeEnum.STATIC) {
await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_STATIC_MODE'));
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_STATIC_MODE'));
return;
}
// DYNAMIC mode - add to current player's DJ list
else {
if (!player) {
await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_NO_PLAYER'));
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_NO_PLAYER'));
return;
}

// Check if already DJ
if (DJManager.isDJ(bot, targetUser.id, null, player)) {
await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_ALREADY_DJ', {
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_ALREADY_DJ', {
userId: targetUser.id
}));
return;
}

// Add DJ
DJManager.addDJ(player, targetUser.id);
await context.replySuccess(bot, i18next.t('commands:MESSAGE_DJ_SUCCESS', {
await context.replySuccess(bot, context.t('commands:MESSAGE_DJ_SUCCESS', {
userId: targetUser.id
}));
}
Expand All @@ -95,41 +95,41 @@ export class DjCommand extends BaseCommand {
try {
const djInfo = await DJManager.getDJInfo(bot, client, context.guild!, player || undefined);

let description = i18next.t('commands:MESSAGE_DJ_LIST_TITLE') + '\n\n';
let description = context.t('commands:MESSAGE_DJ_LIST_TITLE') + '\n\n';

// Add admins
if (djInfo.admins.length > 0) {
description += `**${i18next.t('commands:MESSAGE_DJ_LIST_ADMINS')}**\n`;
description += `**${context.t('commands:MESSAGE_DJ_LIST_ADMINS')}**\n`;
description += djInfo.admins.map(id => `<@${id}>`).join(', ') + '\n\n';
}

// Add role-based DJs
if (djInfo.roleDJs.length > 0) {
description += `**${i18next.t('commands:MESSAGE_DJ_LIST_ROLE_DJS')}**\n`;
description += `**${context.t('commands:MESSAGE_DJ_LIST_ROLE_DJS')}**\n`;
description += djInfo.roleDJs.map(id => `<@${id}>`).join(', ') + '\n\n';
}

// Add dynamic DJs
if (djInfo.dynamicDJs.length > 0) {
description += `**${i18next.t('commands:MESSAGE_DJ_LIST_DYNAMIC_DJS')}**\n`;
description += `**${context.t('commands:MESSAGE_DJ_LIST_DYNAMIC_DJS')}**\n`;
description += djInfo.dynamicDJs.map(id => `<@${id}>`).join(', ') + '\n\n';
}

// Add DJ role info
if (bot.config.bot.djRoleId) {
description += `**DJ Role:** <@&${bot.config.bot.djRoleId}>\n`;
} else {
description += i18next.t('commands:MESSAGE_DJ_ROLE_NOT_SET') + '\n';
description += context.t('commands:MESSAGE_DJ_ROLE_NOT_SET') + '\n';
}

if (djInfo.admins.length === 0 && djInfo.roleDJs.length === 0 && djInfo.dynamicDJs.length === 0) {
description = i18next.t('commands:MESSAGE_DJ_LIST_NONE');
description = context.t('commands:MESSAGE_DJ_LIST_NONE');
}

await context.replySuccess(bot, description);
} catch (error) {
bot.logger.error( bot.shardId, `Error showing DJ list: ${error}`);
await context.replyError(bot, i18next.t('commands:MESSAGE_DJ_LIST_ERROR'));
await context.replyError(bot, context.t('commands:MESSAGE_DJ_LIST_ERROR'));
}
}
}
18 changes: 9 additions & 9 deletions src/commands/FilterCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class FilterCommand extends BaseCommand {
const player = client.lavashark.getPlayer(context.guild!.id);

if (!player || !player.playing) {
await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING'));
await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING'));
return;
}

Expand Down Expand Up @@ -81,18 +81,18 @@ export class FilterCommand extends BaseCommand {
): Promise<void> {
const select = new StringSelectMenuBuilder()
.setCustomId(SelectButtonId.Filter)
.setPlaceholder(client.i18n.t('commands:MESSAGE_FILTER_SELECT_MODE'))
.setPlaceholder(context.t('commands:MESSAGE_FILTER_SELECT_MODE'))
.setOptions([
...(Object.keys(filtersConfig).map((effectName) => ({
label: effectName,
value: effectName
}))),
{ label: client.i18n.t('commands:LABEL_FILTER_CLEAR'), value: 'clear' }
{ label: context.t('commands:LABEL_FILTER_CLEAR'), value: 'clear' }
]);

const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(select);
const msg = await context.reply({
embeds: [embeds.textMsg(bot, client.i18n.t('commands:MESSAGE_FILTER_SELECT_LIST'))],
embeds: [embeds.textMsg(bot, context.t('commands:MESSAGE_FILTER_SELECT_LIST'))],
components: [row.toJSON()],
allowedMentions: { repliedUser: false }
});
Expand All @@ -113,7 +113,7 @@ export class FilterCommand extends BaseCommand {
else {
if (!Object.keys(filtersConfig).includes(effectName)) {
await context.reply({
embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:MESSAGE_FILTER_NOT_FOUND'))],
embeds: [embeds.textErrorMsg(bot, context.t('commands:MESSAGE_FILTER_NOT_FOUND'))],
allowedMentions: { repliedUser: false }
});
collector.stop();
Expand All @@ -129,7 +129,7 @@ export class FilterCommand extends BaseCommand {

await i.deferUpdate();
await msg.edit({
embeds: [embeds.filterMsg(bot, effectName)],
embeds: [embeds.filterMsg(bot, effectName, context.language)],
components: [],
allowedMentions: { repliedUser: false }
}).catch(() =>
Expand All @@ -142,7 +142,7 @@ export class FilterCommand extends BaseCommand {
collector.on('end', async (collected: Collection<string, ButtonInteraction>, reason: string) => {
if (reason === 'time' && collected.size === 0) {
await msg.edit({
embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_TIME_EXPIRED'))],
embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_TIME_EXPIRED'))],
components: [],
allowedMentions: { repliedUser: false }
}).catch(() =>
Expand All @@ -168,7 +168,7 @@ export class FilterCommand extends BaseCommand {
}
else {
if (!Object.keys(filtersConfig).includes(effectName)) {
await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_FILTER_NOT_FOUND'));
await context.replyEphemeralError(bot, context.t('commands:MESSAGE_FILTER_NOT_FOUND'));
return;
}

Expand All @@ -180,7 +180,7 @@ export class FilterCommand extends BaseCommand {
}

await context.reply({
embeds: [embeds.filterMsg(bot, effectName)],
embeds: [embeds.filterMsg(bot, effectName, context.language)],
components: [],
allowedMentions: { repliedUser: false }
}).catch(() =>
Expand Down
Loading