Added Warn and Ban Commands, Added Logging, and Much More

This commit is contained in:
Ahmad 2025-02-23 21:39:49 -05:00
parent d89de72e08
commit 86adac3f08
No known key found for this signature in database
GPG key ID: 8FD8A93530D182BF
33 changed files with 2200 additions and 204 deletions

View file

@ -2,8 +2,14 @@ import {
SlashCommandBuilder,
CommandInteraction,
EmbedBuilder,
ButtonBuilder,
ActionRowBuilder,
ButtonStyle,
StringSelectMenuBuilder,
APIEmbed,
JSONEncodable,
} from 'discord.js';
import { getAllMembers } from '../../util/db.js';
import { getAllMembers } from '../../db/db.js';
interface Command {
data: Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
@ -15,16 +21,112 @@ const command: Command = {
.setName('members')
.setDescription('Lists all non-bot members of the server'),
execute: async (interaction) => {
const members = await getAllMembers();
const memberList = members
.map((m) => `**${m.discordUsername}** (${m.discordId})`)
.join('\n');
const membersEmbed = new EmbedBuilder()
.setTitle('Members')
.setDescription(memberList)
.setColor(0x0099ff)
.addFields({ name: 'Total Members', value: members.length.toString() });
await interaction.reply({ embeds: [membersEmbed] });
let members = await getAllMembers();
members = members.sort((a, b) =>
a.discordUsername.localeCompare(b.discordUsername),
);
const ITEMS_PER_PAGE = 15;
const pages: (APIEmbed | JSONEncodable<APIEmbed>)[] = [];
for (let i = 0; i < members.length; i += ITEMS_PER_PAGE) {
const pageMembers = members.slice(i, i + ITEMS_PER_PAGE);
const memberList = pageMembers
.map((m) => `**${m.discordUsername}** (${m.discordId})`)
.join('\n');
const embed = new EmbedBuilder()
.setTitle('Members')
.setDescription(memberList || 'No members to display.')
.setColor(0x0099ff)
.addFields({ name: 'Total Members', value: members.length.toString() })
.setFooter({
text: `Page ${Math.floor(i / ITEMS_PER_PAGE) + 1} of ${Math.ceil(members.length / ITEMS_PER_PAGE)}`,
});
pages.push(embed);
}
let currentPage = 0;
const getButtonActionRow = () =>
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('previous')
.setLabel('Previous')
.setStyle(ButtonStyle.Primary)
.setDisabled(currentPage === 0),
new ButtonBuilder()
.setCustomId('next')
.setLabel('Next')
.setStyle(ButtonStyle.Primary)
.setDisabled(currentPage === pages.length - 1),
);
const getSelectMenuRow = () => {
const options = pages.map((_, index) => ({
label: `Page ${index + 1}`,
value: index.toString(),
default: index === currentPage,
}));
const select = new StringSelectMenuBuilder()
.setCustomId('select_page')
.setPlaceholder('Jump to a page')
.addOptions(options);
return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
select,
);
};
const components =
pages.length > 1 ? [getButtonActionRow(), getSelectMenuRow()] : [];
await interaction.reply({
embeds: [pages[currentPage]],
components,
});
const message = await interaction.fetchReply();
if (pages.length <= 1) return;
const collector = message.createMessageComponentCollector({
time: 60000,
});
collector.on('collect', async (i) => {
if (i.user.id !== interaction.user.id) {
await i.reply({
content: 'These controls are not for you!',
flags: ['Ephemeral'],
});
return;
}
if (i.isButton()) {
if (i.customId === 'previous' && currentPage > 0) {
currentPage--;
} else if (i.customId === 'next' && currentPage < pages.length - 1) {
currentPage++;
}
}
if (i.isStringSelectMenu()) {
const selected = parseInt(i.values[0]);
if (!isNaN(selected) && selected >= 0 && selected < pages.length) {
currentPage = selected;
}
}
await i.update({
embeds: [pages[currentPage]],
components: [getButtonActionRow(), getSelectMenuRow()],
});
});
collector.on('end', async () => {
if (message.editable) {
await message.edit({ components: [] });
}
});
},
};

111
src/commands/util/rules.ts Normal file
View file

@ -0,0 +1,111 @@
import {
SlashCommandBuilder,
CommandInteraction,
EmbedBuilder,
} from 'discord.js';
interface Command {
data: Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
execute: (interaction: CommandInteraction) => Promise<void>;
}
const rulesEmbed = new EmbedBuilder()
.setColor(0x0099ff)
.setTitle('Server Rules')
.setAuthor({
name: 'Poixixel',
iconURL:
'https://cdn.discordapp.com/avatars/1052017329376071781/922947c726d7866d313744186c42ef49.webp',
})
.setDescription(
'These are the rules for the server. Please read and follow them carefully.',
)
.addFields(
{
name: '**Rule #1: Be respectful**',
value:
'Treat everyone with kindness. No harassment, bullying, hate speech, or toxic behavior.',
},
{
name: '**Rule #2: Keep it Family-Friendly**',
value:
'No explicit content, including NSFW images, language, or discussions. This is a safe space for everyone.',
},
{
name: '**Rule #3: Use Common Sense**',
value:
'Think before you act or post. If something seems questionable, it is probably best not to do it.',
},
{
name: '**Rule #4: No Spamming**',
value:
'Avoid excessive messages, emoji use, or CAPS LOCK. Keep the chat clean and readable.',
},
{
name: '**Rule #5: No Raiding**',
value:
'Do not disrupt the server or other servers with spam, unwanted content, or malicious behavior.',
},
{
name: '**Rule #6: No Self-Promotion**',
value:
'Do not advertise your own content or other servers without permission from staff.',
},
{
name: '**Rule #7: No Impersonation**',
value:
'Do not pretend to be someone else, including staff or other members.',
},
{
name: '**Rule #8: No Violence**',
value:
'Do not post or share content that is offensive, harmful, or contains violent or dangerous content.',
},
{
name: '**Rule #9: No Doxxing or Sharing Personal Information**',
value:
'Protect your privacy and the privacy of others. Do not share personal details.',
},
{
name: '**Rule #10: No Ping Abuse**',
value:
'Do not ping staff members unless it is absolutely necessary. Use pings responsibly for all members.',
},
{
name: '**Rule #11: Use Appropriate Channels**',
value:
'Post content in the right channels. Off-topic content may be moved or deleted.',
},
{
name: '**Rule #12: Follow the Discord Terms of Service and Community Guidelines**',
value:
'All members must adhere to the Discord Terms of Service and Community Guidelines.',
},
{
name: '**Rule #13: Moderator Discretion**',
value:
'Moderators reserve the right to moderate at their discretion. If you feel mistreated, please create a support ticket.',
},
{
name: '**Disclaimer:**',
value:
'**These rules may be updated at any time. It is your responsibility to review them regularly. Moderators and admins have the authority to enforce these rules and take appropriate action.**',
},
)
.setTimestamp()
.setFooter({
text: 'Sent by the Poixpixel Bot',
iconURL:
'https://cdn.discordapp.com/avatars/1052017329376071781/922947c726d7866d313744186c42ef49.webp',
});
const command: Command = {
data: new SlashCommandBuilder()
.setName('rules')
.setDescription('Sends the server rules'),
execute: async (interaction) => {
await interaction.reply({ embeds: [rulesEmbed] });
},
};
export default command;

View file

@ -11,7 +11,7 @@ const command: Command = {
.setDescription('Provides information about the server.'),
execute: async (interaction) => {
await interaction.reply(
`The server ${interaction!.guild!.name} has ${interaction!.guild!.memberCount} members and was created on ${interaction!.guild!.createdAt}. It is ${new Date().getFullYear() - interaction!.guild!.createdAt.getFullYear()!} years old.`,
`The server **${interaction!.guild!.name}** has **${interaction!.guild!.memberCount}** members and was created on **${interaction!.guild!.createdAt}**. It is **${new Date().getFullYear() - interaction!.guild!.createdAt.getFullYear()!}** years old.`,
);
},
};

View file

@ -3,8 +3,10 @@ import {
CommandInteraction,
EmbedBuilder,
SlashCommandOptionsOnlyBuilder,
GuildMember,
PermissionsBitField,
} from 'discord.js';
import { getMember } from '../../util/db.js';
import { getMember } from '../../db/db.js';
interface Command {
data: SlashCommandOptionsOnlyBuilder;
@ -14,7 +16,7 @@ interface Command {
const command: Command = {
data: new SlashCommandBuilder()
.setName('userinfo')
.setDescription('Provides information about the user.')
.setDescription('Provides information about the specified user.')
.addUserOption((option) =>
option
.setName('user')
@ -22,46 +24,127 @@ const command: Command = {
.setRequired(true),
),
execute: async (interaction) => {
const userOption = interaction.options.get('user');
if (!userOption) {
await interaction.reply('User not found');
return;
}
const userOption = interaction.options.get(
'user',
) as unknown as GuildMember;
const user = userOption.user;
if (!user) {
if (!userOption || !user) {
await interaction.reply('User not found');
return;
}
const member = await getMember(user.id);
const [memberData] = member;
if (
!interaction.memberPermissions!.has(
PermissionsBitField.Flags.ModerateMembers,
)
) {
await interaction.reply(
'You do not have permission to view member information.',
);
return;
}
const memberData = await getMember(user.id);
const numberOfWarnings = memberData?.moderations.filter(
(moderation) => moderation.action === 'warning',
).length;
const recentWarnings = memberData?.moderations
.filter((moderation) => moderation.action === 'warning')
.sort((a, b) => b.createdAt!.getTime() - a.createdAt!.getTime())
.slice(0, 5);
const numberOfMutes = memberData?.moderations.filter(
(moderation) => moderation.action === 'mute',
).length;
const currentMute = memberData?.moderations
.filter((moderation) => moderation.action === 'mute')
.sort((a, b) => b.createdAt!.getTime() - a.createdAt!.getTime())[0];
const numberOfBans = memberData?.moderations.filter(
(moderation) => moderation.action === 'ban',
).length;
const currentBan = memberData?.moderations
.filter((moderation) => moderation.action === 'ban')
.sort((a, b) => b.createdAt!.getTime() - a.createdAt!.getTime())[0];
const embed = new EmbedBuilder()
.setTitle(`User Information - ${user?.username}`)
.setColor(user.accentColor || 'Default')
.setColor(user.accentColor || '#5865F2')
.setThumbnail(user.displayAvatarURL({ size: 256 }))
.setTimestamp()
.addFields(
{ name: 'Username', value: user.username, inline: false },
{ name: 'User ID', value: user.id, inline: false },
{
name: 'Joined Server',
value:
interaction.guild?.members.cache
.get(user.id)
?.joinedAt?.toLocaleString() || 'Not available',
name: '👤 Basic Information',
value: [
`**Username:** ${user.username}`,
`**Discord ID:** ${user.id}`,
`**Account Created:** ${user.createdAt.toLocaleString()}`,
`**Joined Server:** ${
interaction.guild?.members.cache
.get(user.id)
?.joinedAt?.toLocaleString() || 'Not available'
}`,
`**Currently in Server:** ${memberData?.currentlyInServer ? '✅ Yes' : '❌ No'}`,
].join('\n'),
inline: false,
},
{
name: 'Account Created',
value: user.createdAt.toLocaleString(),
name: '🛡️ Moderation History',
value: [
`**Total Warnings:** ${numberOfWarnings || '0'} ${numberOfWarnings ? '⚠️' : ''}`,
`**Total Mutes:** ${numberOfMutes || '0'} ${numberOfMutes ? '🔇' : ''}`,
`**Total Bans:** ${numberOfBans || '0'} ${numberOfBans ? '🔨' : ''}`,
`**Currently Muted:** ${memberData?.currentlyMuted ? '🔇 Yes' : '✅ No'}`,
`**Currently Banned:** ${memberData?.currentlyBanned ? '🚫 Yes' : '✅ No'}`,
].join('\n'),
inline: false,
},
{
name: 'Number of Warnings',
value: memberData?.numberOfWarnings.toString() || '0',
},
{
name: 'Number of Bans',
value: memberData?.numberOfBans.toString() || '0',
},
);
if (recentWarnings && recentWarnings.length > 0) {
embed.addFields({
name: '⚠️ Recent Warnings',
value: recentWarnings
.map(
(warning, index) =>
`${index + 1}. \`${warning.createdAt?.toLocaleDateString() || 'Unknown'}\` - ` +
`By <@${warning.moderatorDiscordId}>\n` +
`└ Reason: ${warning.reason || 'No reason provided'}`,
)
.join('\n\n'),
inline: false,
});
}
if (memberData?.currentlyMuted && currentMute) {
embed.addFields({
name: '🔇 Current Mute Details',
value: [
`**Reason:** ${currentMute.reason || 'No reason provided'}`,
`**Duration:** ${currentMute.duration || 'Indefinite'}`,
`**Muted At:** ${currentMute.createdAt?.toLocaleString() || 'Unknown'}`,
`**Muted By:** <@${currentMute.moderatorDiscordId}>`,
].join('\n'),
inline: false,
});
}
if (memberData?.currentlyBanned && currentBan) {
embed.addFields({
name: '📌 Current Ban Details',
value: [
`**Reason:** ${currentBan.reason || 'No reason provided'}`,
`**Duration:** ${currentBan.duration || 'Permanent'}`,
`**Banned At:** ${currentBan.createdAt?.toLocaleString() || 'Unknown'}`,
].join('\n'),
inline: false,
});
}
embed.setFooter({
text: `Requested by ${interaction.user.username}`,
iconURL: interaction.user.displayAvatarURL(),
});
await interaction.reply({ embeds: [embed] });
},
};