Initial commit
This commit is contained in:
33
dist/commands/leaderboard.js
vendored
Normal file
33
dist/commands/leaderboard.js
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||
import { withConn } from '../db/pool.js';
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('leaderboard')
|
||||
.setDescription('Show top users by XP');
|
||||
export async function execute(interaction) {
|
||||
if (!interaction.guildId)
|
||||
return interaction.reply({ content: 'Guild only command.', ephemeral: true });
|
||||
const rows = await withConn(async (conn) => {
|
||||
return conn.query('SELECT user_id, xp FROM user_xp WHERE guild_id = ? ORDER BY xp DESC LIMIT 10', [interaction.guildId]);
|
||||
});
|
||||
const list = rows;
|
||||
if (!list.length)
|
||||
return interaction.reply('No XP data yet. Start chatting!');
|
||||
const medals = ['🥇', '🥈', '🥉'];
|
||||
const XP_PER_LEVEL = 500;
|
||||
const desc = list
|
||||
.map((r, i) => {
|
||||
const rank = i + 1;
|
||||
const level = Math.floor((r.xp || 0) / XP_PER_LEVEL);
|
||||
const tag = `<@${r.user_id}>`;
|
||||
const medal = medals[i] || `#${rank}`;
|
||||
return `${medal} ${tag} — ${r.xp} XP (Lvl ${level})`;
|
||||
})
|
||||
.join('\n');
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Server Leaderboard')
|
||||
.setColor(0xFEE75C)
|
||||
.setDescription(desc)
|
||||
.setFooter({ text: interaction.guild?.name || 'Leaderboard' })
|
||||
.setTimestamp();
|
||||
await interaction.reply({ embeds: [embed], allowedMentions: { parse: [] } });
|
||||
}
|
||||
64
dist/commands/moderation.js
vendored
Normal file
64
dist/commands/moderation.js
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import { PermissionFlagsBits, SlashCommandBuilder, time, userMention } from 'discord.js';
|
||||
import ms from 'ms';
|
||||
function base() {
|
||||
return new SlashCommandBuilder()
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers | PermissionFlagsBits.KickMembers | PermissionFlagsBits.ModerateMembers);
|
||||
}
|
||||
export const banData = base()
|
||||
.setName('ban')
|
||||
.setDescription('Ban a user')
|
||||
.addUserOption(o => o.setName('user').setDescription('User to ban').setRequired(true))
|
||||
.addStringOption(o => o.setName('reason').setDescription('Reason').setRequired(false));
|
||||
export async function banExecute(interaction) {
|
||||
if (!interaction.guild)
|
||||
return interaction.reply({ content: 'Guild only.', ephemeral: true });
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason') || 'No reason provided';
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member)
|
||||
return interaction.reply({ content: 'User not found in guild.', ephemeral: true });
|
||||
await user.send(`You have been banned from ${interaction.guild.name}. Reason: ${reason}`).catch(() => null);
|
||||
await member.ban({ reason });
|
||||
await interaction.reply({ content: `Banned ${userMention(user.id)}.`, allowedMentions: { parse: [] } });
|
||||
}
|
||||
export const kickData = base()
|
||||
.setName('kick')
|
||||
.setDescription('Kick a user')
|
||||
.addUserOption(o => o.setName('user').setDescription('User to kick').setRequired(true))
|
||||
.addStringOption(o => o.setName('reason').setDescription('Reason').setRequired(false));
|
||||
export async function kickExecute(interaction) {
|
||||
if (!interaction.guild)
|
||||
return interaction.reply({ content: 'Guild only.', ephemeral: true });
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason') || 'No reason provided';
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member)
|
||||
return interaction.reply({ content: 'User not found in guild.', ephemeral: true });
|
||||
await user.send(`You have been kicked from ${interaction.guild.name}. Reason: ${reason}`).catch(() => null);
|
||||
await member.kick(reason);
|
||||
await interaction.reply({ content: `Kicked ${userMention(user.id)}.`, allowedMentions: { parse: [] } });
|
||||
}
|
||||
export const timeoutData = base()
|
||||
.setName('timeout')
|
||||
.setDescription('Timeout a user')
|
||||
.addUserOption(o => o.setName('user').setDescription('User to timeout').setRequired(true))
|
||||
.addStringOption(o => o.setName('duration').setDescription('Duration (e.g. 10m, 1h)').setRequired(true))
|
||||
.addStringOption(o => o.setName('reason').setDescription('Reason').setRequired(false));
|
||||
export async function timeoutExecute(interaction) {
|
||||
if (!interaction.guild)
|
||||
return interaction.reply({ content: 'Guild only.', ephemeral: true });
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const duration = interaction.options.getString('duration', true);
|
||||
const msDur = ms(duration);
|
||||
if (!msDur || msDur < 1000 || msDur > 28 * 24 * 60 * 60 * 1000) {
|
||||
return interaction.reply({ content: 'Invalid duration. Use between 1s and 28d.', ephemeral: true });
|
||||
}
|
||||
const reason = interaction.options.getString('reason') || 'No reason provided';
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member)
|
||||
return interaction.reply({ content: 'User not found in guild.', ephemeral: true });
|
||||
const until = new Date(Date.now() + msDur);
|
||||
await user.send(`You have been timed out in ${interaction.guild.name} until ${time(until, 'F')} UTC. Reason: ${reason}`).catch(() => null);
|
||||
await member.timeout(msDur, reason);
|
||||
await interaction.reply({ content: `Timed out ${userMention(user.id)} until ${time(until, 'F')} UTC.`, allowedMentions: { parse: [] } });
|
||||
}
|
||||
68
dist/commands/profile.js
vendored
Normal file
68
dist/commands/profile.js
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||
import { withConn } from '../db/pool.js';
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('profile')
|
||||
.setDescription('Show XP profile')
|
||||
.addUserOption(o =>
|
||||
o.setName('user').setDescription('User to view')
|
||||
);
|
||||
|
||||
export async function execute(interaction) {
|
||||
if (!interaction.guildId)
|
||||
return interaction.reply({
|
||||
content: 'Guild only command.',
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
const target = interaction.options.getUser('user') || interaction.user;
|
||||
|
||||
const data = await withConn(async conn => {
|
||||
const rows = await conn.query(
|
||||
'SELECT xp FROM user_xp WHERE guild_id = ? AND user_id = ? LIMIT 1',
|
||||
[BigInt(interaction.guildId), BigInt(target.id)]
|
||||
);
|
||||
|
||||
const xp = Number(rows[0]?.xp ?? 0);
|
||||
|
||||
const rankRows = await conn.query(
|
||||
'SELECT COUNT(*) AS ahead FROM user_xp WHERE guild_id = ? AND xp > ?',
|
||||
[BigInt(interaction.guildId), xp]
|
||||
);
|
||||
|
||||
const rank = Number(rankRows[0]?.ahead ?? 0) + 1;
|
||||
|
||||
return { xp, rank };
|
||||
});
|
||||
|
||||
const XP_PER_LEVEL = 500; // match visual example
|
||||
const level = Math.floor(data.xp / XP_PER_LEVEL);
|
||||
const currentLevelXp = data.xp % XP_PER_LEVEL;
|
||||
const progress = Math.min(
|
||||
100,
|
||||
Math.round((currentLevelXp / XP_PER_LEVEL) * 1000) / 10
|
||||
);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({
|
||||
name: `${target.username}'s Profile`,
|
||||
iconURL: target.displayAvatarURL(),
|
||||
})
|
||||
.setColor(0x5865f2)
|
||||
.setDescription(`Progress to next level: **${progress}%**`)
|
||||
.addFields(
|
||||
{ name: '🏅 Level', value: `${level}`, inline: true },
|
||||
{ name: '💎 XP', value: `${currentLevelXp} / ${XP_PER_LEVEL}`, inline: true },
|
||||
{ name: '🏆 Rank', value: `#${data.rank}`, inline: true }
|
||||
)
|
||||
.setFooter({
|
||||
text: `Server: ${interaction.guild?.name || 'Unknown'}`,
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [embed],
|
||||
allowedMentions: { parse: [] },
|
||||
});
|
||||
}
|
||||
|
||||
14
dist/commands/resetmemory.js
vendored
Normal file
14
dist/commands/resetmemory.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { withConn } from '../db/pool.js';
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('resetmemory')
|
||||
.setDescription('Reset your AI chat memory (only affects you)');
|
||||
export async function execute(interaction) {
|
||||
if (!interaction.guildId)
|
||||
return interaction.reply({ content: 'Guild only command.', ephemeral: true });
|
||||
const target = interaction.user;
|
||||
await withConn(async (conn) => {
|
||||
await conn.query('DELETE FROM user_ai_memory WHERE guild_id = ? AND user_id = ?', [interaction.guildId, target.id]);
|
||||
});
|
||||
await interaction.reply({ content: 'Your AI memory has been fully reset.', ephemeral: true });
|
||||
}
|
||||
71
dist/commands/setup.js
vendored
Normal file
71
dist/commands/setup.js
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import { SlashCommandBuilder, ChannelType, PermissionFlagsBits } from 'discord.js';
|
||||
import { withConn } from '../db/pool.js';
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('setup')
|
||||
.setDescription('Enable/disable features or set channels for this server')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
|
||||
.addStringOption(o => o
|
||||
.setName('feature')
|
||||
.setDescription('Feature to configure')
|
||||
.setRequired(true)
|
||||
.addChoices({ name: 'moderation', value: 'moderation' }, { name: 'ai', value: 'ai' }, { name: 'logs', value: 'logs' }, { name: 'qotd', value: 'qotd' }, { name: 'profanity_filter', value: 'profanity_filter' }))
|
||||
.addStringOption(o => o
|
||||
.setName('value')
|
||||
.setDescription('on|off for toggles; for logs: off|channel; for qotd: off|channel')
|
||||
.setRequired(true)
|
||||
.addChoices({ name: 'on', value: 'on' }, { name: 'off', value: 'off' }, { name: 'channel', value: 'channel' }))
|
||||
.addChannelOption(o => o
|
||||
.setName('channel')
|
||||
.setDescription('Target channel when selecting channel mode')
|
||||
.addChannelTypes(ChannelType.GuildText));
|
||||
export async function execute(interaction) {
|
||||
if (!interaction.guildId)
|
||||
return interaction.reply({ content: 'Guild only command.', ephemeral: true });
|
||||
const feature = interaction.options.getString('feature', true);
|
||||
const value = interaction.options.getString('value', true);
|
||||
const channel = interaction.options.getChannel('channel');
|
||||
await withConn(async (conn) => {
|
||||
await conn.query('INSERT IGNORE INTO guild_config (guild_id) VALUES (?)', [interaction.guildId]);
|
||||
switch (feature) {
|
||||
case 'moderation':
|
||||
case 'ai':
|
||||
case 'profanity_filter': {
|
||||
const column = feature === 'moderation' ? 'moderation_enabled' : feature === 'ai' ? 'ai_enabled' : 'profanity_filter_enabled';
|
||||
const enabled = value === 'on' ? 1 : 0;
|
||||
await conn.query(`UPDATE guild_config SET ${column} = ? WHERE guild_id = ?`, [enabled, interaction.guildId]);
|
||||
await interaction.reply({ content: `${feature} set to ${value}.`, ephemeral: true });
|
||||
break;
|
||||
}
|
||||
case 'logs': {
|
||||
if (value === 'channel') {
|
||||
if (!channel)
|
||||
return interaction.reply({ content: 'Please provide a text channel.', ephemeral: true });
|
||||
await conn.query('UPDATE guild_config SET logs_enabled = 1, logs_channel_id = ? WHERE guild_id = ?', [channel.id, interaction.guildId]);
|
||||
await interaction.reply({ content: `Logs enabled in <#${channel.id}>.`, ephemeral: true });
|
||||
}
|
||||
else {
|
||||
const enabled = value === 'on' ? 1 : 0;
|
||||
await conn.query('UPDATE guild_config SET logs_enabled = ?, logs_channel_id = NULL WHERE guild_id = ?', [enabled, interaction.guildId]);
|
||||
await interaction.reply({ content: `Logs ${value}.`, ephemeral: true });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'qotd': {
|
||||
if (value === 'channel') {
|
||||
if (!channel)
|
||||
return interaction.reply({ content: 'Please provide a text channel.', ephemeral: true });
|
||||
await conn.query('UPDATE guild_config SET qotd_enabled = 1, qotd_channel_id = ? WHERE guild_id = ?', [channel.id, interaction.guildId]);
|
||||
await interaction.reply({ content: `QOTD enabled in <#${channel.id}>.`, ephemeral: true });
|
||||
}
|
||||
else {
|
||||
const enabled = value === 'on' ? 1 : 0;
|
||||
await conn.query('UPDATE guild_config SET qotd_enabled = ?, qotd_channel_id = NULL WHERE guild_id = ?', [enabled, interaction.guildId]);
|
||||
await interaction.reply({ content: `QOTD ${value}.`, ephemeral: true });
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
await interaction.reply({ content: 'Unknown feature.', ephemeral: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user