Enhance bot and WebUI functionality with new features and environment updates
- Added support for new environment variables in `.env.example` for public site and legal operator information. - Integrated core commands into the bot's command structure for improved functionality. - Implemented a new `publishBotStatus` function to update bot status in Redis, enhancing monitoring capabilities. - Updated the landing page to include features, invite links, and support server access, improving user experience. - Enhanced localization with new keys for core commands and landing page elements in both English and German. - Improved error handling and logging for bot presence updates and status publishing.
This commit is contained in:
24
apps/bot/src/modules/core/command-definitions.ts
Normal file
24
apps/bot/src/modules/core/command-definitions.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { applyCommandDescription, applyOptionDescription } from '@nexumi/shared';
|
||||
|
||||
export const helpCommandData = applyCommandDescription(
|
||||
new SlashCommandBuilder().setName('help'),
|
||||
'core.help.description'
|
||||
).addStringOption((option) =>
|
||||
applyOptionDescription(option.setName('module'), 'core.help.options.module')
|
||||
);
|
||||
|
||||
export const infoCommandData = applyCommandDescription(
|
||||
new SlashCommandBuilder().setName('info'),
|
||||
'core.info.description'
|
||||
);
|
||||
|
||||
export const inviteCommandData = applyCommandDescription(
|
||||
new SlashCommandBuilder().setName('invite'),
|
||||
'core.invite.description'
|
||||
);
|
||||
|
||||
export const supportCommandData = applyCommandDescription(
|
||||
new SlashCommandBuilder().setName('support'),
|
||||
'core.support.description'
|
||||
);
|
||||
158
apps/bot/src/modules/core/commands.ts
Normal file
158
apps/bot/src/modules/core/commands.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { t, tf } from '@nexumi/shared';
|
||||
import type { SlashCommand } from '../../types.js';
|
||||
import { getGuildLocale } from '../../i18n.js';
|
||||
import { buildBotInviteUrl, env } from '../../env.js';
|
||||
import {
|
||||
helpCommandData,
|
||||
infoCommandData,
|
||||
inviteCommandData,
|
||||
supportCommandData
|
||||
} from './command-definitions.js';
|
||||
import { HELP_MODULES } from './help-catalog.js';
|
||||
|
||||
const NEXUMI_COLOR = 0x6366f1;
|
||||
const PACKAGE_VERSION = '0.1.0';
|
||||
|
||||
function formatUptime(seconds: number): string {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
return `${h}h ${m}m ${s}s`;
|
||||
}
|
||||
|
||||
function webuiBase(): string {
|
||||
return env.WEBUI_URL ?? env.PUBLIC_BASE_URL;
|
||||
}
|
||||
|
||||
const helpCommand: SlashCommand = {
|
||||
data: helpCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
const filter = interaction.options.getString('module')?.trim().toLowerCase() ?? null;
|
||||
const modules = filter
|
||||
? HELP_MODULES.filter((module) => module.id === filter)
|
||||
: HELP_MODULES;
|
||||
|
||||
if (modules.length === 0) {
|
||||
await interaction.reply({
|
||||
content: tf(locale, 'core.help.unknownModule', {
|
||||
modules: HELP_MODULES.map((module) => module.id).join(', ')
|
||||
}),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = modules.map((module) =>
|
||||
tf(locale, 'core.help.moduleLine', {
|
||||
module: t(locale, `core.module.${module.id}`),
|
||||
commands: module.commands.map((name) => `\`/${name}\``).join(', ')
|
||||
})
|
||||
);
|
||||
|
||||
// Discord embed description limit is 4096; split across fields if needed.
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(locale, 'core.help.title'))
|
||||
.setDescription(t(locale, 'core.help.subtitle'))
|
||||
.setColor(NEXUMI_COLOR)
|
||||
.setFooter({
|
||||
text: tf(locale, 'core.help.footer', { webui: webuiBase() })
|
||||
});
|
||||
|
||||
const chunkSize = 8;
|
||||
for (let i = 0; i < lines.length; i += chunkSize) {
|
||||
const chunk = lines.slice(i, i + chunkSize);
|
||||
embed.addFields({
|
||||
name: '\u200b',
|
||||
value: chunk.join('\n')
|
||||
});
|
||||
}
|
||||
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
const infoCommand: SlashCommand = {
|
||||
data: infoCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
const shardId = context.client.shard?.ids[0] ?? 0;
|
||||
const inviteUrl = buildBotInviteUrl();
|
||||
const base = webuiBase();
|
||||
const linkLines = [
|
||||
`[${t(locale, 'core.info.link.dashboard')}](${base})`,
|
||||
`[${t(locale, 'core.info.link.invite')}](${inviteUrl})`,
|
||||
`[${t(locale, 'core.info.link.status')}](${base.replace(/\/$/, '')}/status)`
|
||||
];
|
||||
if (env.SUPPORT_SERVER_URL) {
|
||||
linkLines.push(`[${t(locale, 'core.info.link.support')}](${env.SUPPORT_SERVER_URL})`);
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(locale, 'core.info.title'))
|
||||
.setColor(NEXUMI_COLOR)
|
||||
.addFields(
|
||||
{ name: t(locale, 'core.info.version'), value: PACKAGE_VERSION, inline: true },
|
||||
{
|
||||
name: t(locale, 'core.info.uptime'),
|
||||
value: formatUptime(process.uptime()),
|
||||
inline: true
|
||||
},
|
||||
{ name: t(locale, 'core.info.shard'), value: String(shardId), inline: true },
|
||||
{
|
||||
name: t(locale, 'core.info.guilds'),
|
||||
value: String(context.client.guilds.cache.size),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: t(locale, 'core.info.latency'),
|
||||
value: `${context.client.ws.ping} ms`,
|
||||
inline: true
|
||||
},
|
||||
{ name: t(locale, 'core.info.links'), value: linkLines.join('\n') }
|
||||
)
|
||||
.setFooter({ text: 'Nexumi' });
|
||||
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
const inviteCommand: SlashCommand = {
|
||||
data: inviteCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
const inviteUrl = buildBotInviteUrl();
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(locale, 'core.invite.title'))
|
||||
.setDescription(`${t(locale, 'core.invite.body')}\n${inviteUrl}`)
|
||||
.setColor(NEXUMI_COLOR);
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
const supportCommand: SlashCommand = {
|
||||
data: supportCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!env.SUPPORT_SERVER_URL) {
|
||||
await interaction.reply({
|
||||
content: t(locale, 'core.support.missing'),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(locale, 'core.support.title'))
|
||||
.setDescription(`${t(locale, 'core.support.body')}\n${env.SUPPORT_SERVER_URL}`)
|
||||
.setColor(NEXUMI_COLOR);
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
export const coreCommands: SlashCommand[] = [
|
||||
helpCommand,
|
||||
infoCommand,
|
||||
inviteCommand,
|
||||
supportCommand
|
||||
];
|
||||
93
apps/bot/src/modules/core/help-catalog.ts
Normal file
93
apps/bot/src/modules/core/help-catalog.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/** Module id → top-level slash command names for `/help`. */
|
||||
export const HELP_MODULES: ReadonlyArray<{ id: string; commands: readonly string[] }> = [
|
||||
{ id: 'core', commands: ['help', 'info', 'invite', 'support'] },
|
||||
{
|
||||
id: 'moderation',
|
||||
commands: [
|
||||
'ban',
|
||||
'unban',
|
||||
'kick',
|
||||
'timeout',
|
||||
'untimeout',
|
||||
'warn',
|
||||
'purge',
|
||||
'slowmode',
|
||||
'lock',
|
||||
'unlock',
|
||||
'nick',
|
||||
'case',
|
||||
'modnote'
|
||||
]
|
||||
},
|
||||
{ id: 'automod', commands: ['automod'] },
|
||||
{ id: 'welcome', commands: ['welcome'] },
|
||||
{ id: 'verification', commands: ['verify'] },
|
||||
{ id: 'leveling', commands: ['rank', 'leaderboard', 'xp'] },
|
||||
{
|
||||
id: 'economy',
|
||||
commands: [
|
||||
'balance',
|
||||
'daily',
|
||||
'weekly',
|
||||
'work',
|
||||
'pay',
|
||||
'gamble',
|
||||
'slots',
|
||||
'blackjack',
|
||||
'coinflip',
|
||||
'shop',
|
||||
'inventory',
|
||||
'eco'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'utility',
|
||||
commands: [
|
||||
'userinfo',
|
||||
'serverinfo',
|
||||
'roleinfo',
|
||||
'channelinfo',
|
||||
'avatar',
|
||||
'banner',
|
||||
'poll',
|
||||
'remindme',
|
||||
'reminders',
|
||||
'afk',
|
||||
'emoji',
|
||||
'sticker',
|
||||
'timestamp',
|
||||
'snipe',
|
||||
'editsnipe',
|
||||
'embed'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'fun',
|
||||
commands: [
|
||||
'8ball',
|
||||
'dice',
|
||||
'flip',
|
||||
'rps',
|
||||
'choose',
|
||||
'trivia',
|
||||
'tictactoe',
|
||||
'connect4',
|
||||
'hangman',
|
||||
'meme',
|
||||
'cat',
|
||||
'dog'
|
||||
]
|
||||
},
|
||||
{ id: 'giveaways', commands: ['giveaway'] },
|
||||
{ id: 'tickets', commands: ['ticket'] },
|
||||
{ id: 'selfroles', commands: ['selfroles'] },
|
||||
{ id: 'tags', commands: ['tag'] },
|
||||
{ id: 'starboard', commands: ['starboard'] },
|
||||
{ id: 'suggestions', commands: ['suggest', 'suggestion'] },
|
||||
{ id: 'birthdays', commands: ['birthday'] },
|
||||
{ id: 'tempvoice', commands: ['voice'] },
|
||||
{ id: 'stats', commands: ['stats', 'invites'] },
|
||||
{ id: 'feeds', commands: ['feeds'] },
|
||||
{ id: 'scheduler', commands: ['schedule', 'announce'] },
|
||||
{ id: 'guildbackup', commands: ['backup'] }
|
||||
];
|
||||
2
apps/bot/src/modules/core/index.ts
Normal file
2
apps/bot/src/modules/core/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { coreCommands } from './commands.js';
|
||||
export { HELP_MODULES } from './help-catalog.js';
|
||||
Reference in New Issue
Block a user