Skip to content
Draft
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
Binary file modified bun.lockb
Binary file not shown.
54 changes: 54 additions & 0 deletions commands/misc/stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import { Command } from '../../types.js';
import osUtils from 'node-os-utils';
import humanizeBytes from 'pretty-bytes';
import { version } from 'discord.js';
import { Duration } from '@fleco/duration';

export default class StatsCommand extends Command {

constructor() {
super(
new SlashCommandBuilder()
.setName('stats')
.setDescription('Get the current runtime statistics of the bot'),
);
}

async execute(interaction: ChatInputCommandInteraction) {

const cpuUsage = await osUtils.cpu.usage();
const { totalMemMb, usedMemMb, usedMemPercentage } = await osUtils.mem.info();

const totalMem = humanizeBytes(totalMemMb * 1_000_000);
const usedMem = humanizeBytes(usedMemMb * 1_000_000);
const processMem = humanizeBytes(process.memoryUsage().heapUsed);

const totalUptime = new Duration({ milliseconds: this.client.uptime as number }).toString();

const statEmbed = new EmbedBuilder()
.setTitle('Statistics')
.addFields(
{
name: 'System Stats:',
value: `- CPU Usage: ${cpuUsage}%\n- Total RAM Usage: ${usedMem}/${totalMem} (${usedMemPercentage}%)\n- Process RAM Usage: ${processMem}`,
inline: true,
},
{
name: 'Runtime Info:',
value: `- Type: ${process.versions.bun ? 'Bun' : 'Node.JS'}\n- Version: ${process.versions.bun ? `v${Bun.version}` : process.version}\n- Discord.JS Version: v${version}\n- Uptime: ${totalUptime}`,
inline: true,
},
{
name: 'Bot Info:',
value: `- Guilds: ${this.client.guilds.cache.size}\n- Channels: ${this.client.channels.cache.size}\n- Users: ${this.client.users.cache.size}`,
inline: true,
},
)
.setColor('Blue');

await interaction.reply({ embeds: [ statEmbed ] });

}

}
129 changes: 129 additions & 0 deletions commands/mod/kick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { PermissionFlagsBits, SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
import { Temporal } from '@js-temporal/polyfill';
import { Command } from '../../types.js';
import { nanoid } from 'nanoid';

export default class KickCommand extends Command {

constructor() {
super(
new SlashCommandBuilder()
.setName('kick')
.setDescription('Kicks a user from the server.')
.addUserOption(option =>
option
.setName('user')
.setDescription('User to kick'),
)
.addStringOption(option =>
option
.setName('reason')
.setDescription('Reason for kick')
.setMaxLength(300),
)
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.setDMPermission(false),
);
}

async execute(interaction: ChatInputCommandInteraction) {

const memberFetch = interaction.options.getUser('user', true);
const member = await interaction.guild?.members.fetch(memberFetch.id);

if (!member) {

const memberNoExist = new EmbedBuilder()
.setDescription('Member is not in the server!')
.setColor('Red');

await interaction.reply({ embeds: [ memberNoExist ], ephemeral: true });
return;

}

const reason = interaction.options.getString('reason', false);

if (!member?.kickable) {

const cannotKickEmbed = new EmbedBuilder()
.setDescription('Cannot kick user, please check the bot permissions/hierarchy')
.setColor('Red');

await interaction.reply({ embeds: [ cannotKickEmbed ], ephemeral: true });
return;

}

await member.kick(reason ? `${reason} - ${interaction.member?.user.username}` : `Kicked by ${interaction.member?.user.username}`);

const date = Temporal.Now.instant();

const server = await this.client.db.server.findFirst({
where: {
id: interaction.guild?.id,
},
include: {
config: true,
modlogs: true,
},
});

let msg;

if (server?.config?.modlog_chan) {

const kickEmbed = new EmbedBuilder()
.setAuthor({ name: 'Fleco Modlog', iconURL: this.client.user?.displayAvatarURL({ extension: 'webp' }) })
.setTitle('Event - User Kick')
.addFields(
{
name: 'User Info:',
value: `- **ID:** ${member.user.id}\n- **Username:** ${member.user.username}`,
inline: true,
},
{
name: 'Mod Info:',
value: `- **ID:** ${interaction.member?.user.id}\n- **Username:** ${interaction.member?.user.username}`,
inline: true,
},
{
name: 'Info:',
value: `- **Reason:** ${reason ?? 'none'}\n- **Date:** <t:${date.epochSeconds}:f>`,
},
)
.setFooter({ text: `Case Number: ${server.modlogs.length + 1}` })
.setColor('Gold');

const modlogChan = await interaction.guild?.channels.fetch(server.config.modlog_chan);

if (!modlogChan || !modlogChan.isTextBased()) return;

msg = await modlogChan.send({ embeds: [ kickEmbed ] });

}

await this.client.db.modlog.create({
data: {
id: nanoid(),
serverID: interaction.guild!.id,
type: 'kick',
reason: reason ?? 'None',
userID: member.user.id,
modID: interaction.member!.user.id,
date: date.toString(),
caseNum: server!.modlogs.length + 1,
logMsgID: msg?.id,
},
});

const kickEmbed = new EmbedBuilder()
.setAuthor({ name: 'Fleco', iconURL: this.client.user?.displayAvatarURL({ extension: 'webp' }) })
.setDescription(`Kicked <@${member?.user.id}> `)
.setColor('Blue');

await interaction.reply({ embeds: [ kickEmbed ], ephemeral: true });

}

}
179 changes: 179 additions & 0 deletions commands/mod/mute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder, PermissionFlagsBits } from 'discord.js';
import { Duration } from '@fleco/duration';
import { Command } from '../../types.js';
import { nanoid } from 'nanoid';
import { Temporal } from '@js-temporal/polyfill';

export default class MuteCommand extends Command {

constructor() {
super(
new SlashCommandBuilder()
.setName('mute')
.setDescription('Mutes a user in a server')
.addUserOption((option) =>
option
.setName('user')
.setDescription('User to mute')
.setRequired(true),
)
.addStringOption((option) =>
option
.setName('duration')
.setDescription('Duration of mute')
.setRequired(true),
)
.addStringOption((option) =>
option
.setName('reason')
.setDescription('Reason for mute')
.setMaxLength(300),
)
.setDefaultMemberPermissions(PermissionFlagsBits.MuteMembers)
.setDMPermission(false),
);
}

async execute(interaction: ChatInputCommandInteraction) {

const memberFetch = interaction.options.getUser('user', true);
const member = await interaction.guild?.members.fetch(memberFetch.id);

if (!member) {

const memberNoExist = new EmbedBuilder()
.setDescription('Member is not in the server!')
.setColor('Red');

await interaction.reply({ embeds: [ memberNoExist ], ephemeral: true });
return;

}

const reason = interaction.options.getString('reason', false);

const durString = interaction.options.getString('duration', true);
let duration;

if (member?.isCommunicationDisabled()) {

const alrMutedEmbed = new EmbedBuilder()
.setDescription('User is already muted!')
.setColor('Red');

await interaction.reply({ embeds: [ alrMutedEmbed ], ephemeral: true });
return;

}

try {

duration = new Duration(durString);

}
catch {

const invalidDurEmbed = new EmbedBuilder()
.setTitle('Invalid duration')
.setDescription(`Invalid Duration Format: \`${durString}\` please use a format like this: \`1h\`, \`1h 30m\`, etc...`)
.setColor('Red');

await interaction.reply({ embeds: [ invalidDurEmbed ], ephemeral: true });
return;

}

try {

await member?.disableCommunicationUntil(duration.endDate(), reason ? `${reason} - ${interaction.member?.user.username}` : `Timed out by ${interaction.member?.user.username}`);

}
catch {

const cannotMuteEmbed = new EmbedBuilder()
.setDescription('Cannot mute user, please check the bot permissions/hierarchy')
.setColor('Red');

await interaction.reply({ embeds: [ cannotMuteEmbed ], ephemeral: true });
return;

}

const totalModlogs = await this.client.db.modlog.count({
where: {
serverID: interaction.guild?.id,
},
});

const endDate = Temporal.Now.instant().add(duration.duration);
const date = Temporal.Now.instant();

const server = await this.client.db.server.findFirst({
where: {
id: interaction.guild?.id,
},
include: {
config: true,
modlogs: true,
},
});

let msg;

if (server!.config?.modlog_chan) {

const muteEmbed = new EmbedBuilder()
.setAuthor({ name: 'Fleco Modlog', iconURL: this.client.user?.displayAvatarURL({ extension: 'webp' }) })
.setTitle('Event - User Mute')
.addFields(
{
name: 'User Info:',
value: `- **ID:** ${member?.user.id}\n- **Username:** ${member?.user.username}`,
inline: true,
},
{
name: 'Mod Info:',
value: `- **ID:** ${interaction.member?.user.id}\n- **Username:** ${interaction.member?.user.username}`,
inline: true,
},
{
name: 'Info:',
value: `- **Reason:** ${reason ?? 'None'}\n- **Muted Until:** <t:${endDate.epochSeconds}:F> **][** <t:${endDate.epochSeconds}:R>\n- **Date:** <t:${date.epochSeconds}:f>`,
},
)
.setFooter({ text: `Case Number: ${server!.modlogs.length + 1}` })
.setColor('Gold');

const modlogChan = await interaction.guild?.channels.fetch(server!.config.modlog_chan);

if (!modlogChan || !modlogChan.isTextBased()) return;

msg = await modlogChan.send({ embeds: [ muteEmbed ] });

}

await this.client.db.modlog.create({
data: {
id: nanoid(),
serverID: interaction.guild!.id,
type: 'mute',
reason: reason ?? 'None',
userID: member!.user.id,
modID: interaction.member!.user.id,
date: date.toString(),
endDate: endDate.toString(),
caseNum: totalModlogs + 1,
logMsgID: msg?.id ?? null,
},
});

const muteEmbed = new EmbedBuilder()
.setAuthor({ name: 'Fleco', iconURL: this.client.user?.displayAvatarURL({ extension: 'webp' }) })
.setDescription(`Muted <@${member?.user.id}> for ${duration.toString()}`)
.setColor('Blue');

await interaction.reply({ embeds: [ muteEmbed ], ephemeral: true });

}

}
Loading