Skip to content

Commit

Permalink
infractions
Browse files Browse the repository at this point in the history
  • Loading branch information
Kathund committed Jul 31, 2024
1 parent 020b4e8 commit 2e174fc
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 5 deletions.
3 changes: 2 additions & 1 deletion config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"contributorsRole": "CONTRIBUTORS_ROLE_ID",
"supportCategory": "SUPPORT_CATEGORY_ID",
"autoModBypassRole": "AUTOMOD_BYPASS_ROLE_ID",
"serverId": "SERVER_ID"
"serverId": "SERVER_ID",
"infractionLogchannel": "INFRACTION_LOG_CHANNEL"
}
4 changes: 3 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import prettier from 'eslint-config-prettier';
import ts from 'typescript-eslint';
import { Guild } from 'discord.js';
import globals from 'globals';

export default [
Expand All @@ -12,7 +13,8 @@ export default [
sourceType: 'module',
globals: {
...globals.es2022,
...globals.node
...globals.node,
guild: Guild
}
},
rules: {
Expand Down
144 changes: 144 additions & 0 deletions src/commands/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// MAX MUTE TIME 2419200000
import {
ChatInputCommandInteraction,
SlashCommandBuilder,
PermissionFlagsBits,
ActionRowBuilder,
ButtonBuilder,
EmbedBuilder,
ButtonStyle
} from 'discord.js';
import Infraction, { getUserInfractions } from '../functions/Infraction';

export const data = new SlashCommandBuilder()
.setName('user')
.setDescription('Manage Users')
.addSubcommand((subcommand) =>
subcommand
.setName('info')
.setDescription('Get info of a user')
.addUserOption((option) => option.setName('user').setDescription('The user to get info').setRequired(true))
)
.addSubcommand((subcommand) =>
subcommand
.setName('infractions')
.setDescription('Get user Infractions')
.addUserOption((option) => option.setName('user').setDescription('The user to get infractions').setRequired(true))
)
.addSubcommand((subcommand) =>
subcommand
.setName('warn')
.setDescription('Warn a user')
.addUserOption((option) => option.setName('user').setDescription('The user to warn').setRequired(true))
.addStringOption((option) =>
option.setName('reason').setDescription('The reason for the warn').setRequired(false)
)
)
.addSubcommand((subcommand) =>
subcommand
.setName('kick')
.setDescription('Kick a user')
.addUserOption((option) => option.setName('user').setDescription('The user to kick').setRequired(true))
.addStringOption((option) =>
option.setName('reason').setDescription('The reason for the kick').setRequired(false)
)
)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.setDMPermission(false);

export async function execute(interaction: ChatInputCommandInteraction): Promise<void> {
try {
if (!interaction.guild) return;
const subCommand = interaction.options.getSubcommand();
const commandUser = interaction.options.getUser('user');
if (!commandUser) {
await interaction.reply({ content: 'Please provide a valid user', ephemeral: true });
return;
}
const user = await interaction.guild.members.fetch(commandUser.id);
if (!user) {
await interaction.reply({ content: 'Please provide a valid user', ephemeral: true });
return;
}
switch (subCommand) {
case 'info': {
const embed = new EmbedBuilder()
.setTitle('User Infomation')
.setTimestamp()
.setColor(0xff8c00)
.setDescription(
`<@${user.id}>\n\nBot: ${user.user.bot}\nID: ${user.id}\n Created: <t:${
user.user.createdTimestamp
}:F> (<t:${user.user.createdTimestamp}:R>)\nJoined: <t:${
user.joinedTimestamp
}:F> (<t:${user.joinedTimestamp}:R>)\nRoles: ${user.roles.cache.map((role) => `<@&${role.id}>`)}`
);
await interaction.reply({
embeds: [embed],
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setCustomId(`logs.${user.id}`).setLabel('view logs').setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId(`kick.${user.id}`).setLabel('kick').setStyle(ButtonStyle.Danger),
new ButtonBuilder().setCustomId(`ban.${user.id}`).setLabel('ban').setStyle(ButtonStyle.Danger)
)
],
ephemeral: true
});
await interaction.reply({ content: `<@${user.id}> has been warned`, ephemeral: true });
}
case 'infractions': {
const userInfractions = await getUserInfractions(commandUser.id);
if (!userInfractions.success) {
await interaction.reply({ content: userInfractions.info, ephemeral: true });
return;
}
const embed = new EmbedBuilder()
.setTitle('User Infractions')
.setDescription(
`${userInfractions.info}\n\n${userInfractions.infractions
?.map((infraction) => infraction.toString())
.join('\n\n')}`
)
.setColor(0xff8c00)
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
}
case 'warn': {
const reason = interaction.options.getString('reason') || 'No reason provided';
new Infraction({
automatic: false,
reason: reason,
type: 'WARN',
user: { id: commandUser.id, staff: false, bot: commandUser.bot },
staff: { id: interaction.user.id, staff: true, bot: interaction.user.bot }
})
.log()
.save();
await interaction.reply({ content: `<@${commandUser.id}> has been warned`, ephemeral: true });
}
case 'kick': {
const reason = interaction.options.getString('reason') || 'No reason provided';
new Infraction({
automatic: false,
reason: reason,
type: 'KICK',
user: { id: commandUser.id, staff: false, bot: commandUser.bot },
staff: { id: interaction.user.id, staff: true, bot: interaction.user.bot }
})
.log()
.save();
await interaction.reply({ content: `<@${commandUser.id}> has been kicked`, ephemeral: true });
}
default: {
await interaction.reply({ content: 'Invalid subcommand Please provide a valid subcommand', ephemeral: true });
}
}
} catch (error) {
console.log(error);
if (interaction.replied || interaction.deferred) {
await interaction.followUp({ content: 'Something went wrong. Please try again later.', ephemeral: true });
return;
}
await interaction.reply({ content: 'Something went wrong. Please try again later.', ephemeral: true });
}
}
4 changes: 3 additions & 1 deletion src/events/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import CheckPermits from '../functions/CheckPermits';
import DeployEvents from '../functions/DeployEvents';
import { eventMessage } from '../functions/logger';
import { connectDB } from '../functions/mongo';
import { serverId } from '../../config.json';
import { Client } from 'discord.js';
import cron from 'node-cron';
export function execute(client: Client): void {
export async function execute(client: Client): Promise<void> {
try {
eventMessage(`Logged in as ${client.user?.username} (${client.user?.id})!`);
DeployEvents(client);
connectDB();
global.guild = await client.guilds.fetch(serverId);
cron.schedule(`* * * * *`, () => CheckPermits(client));
} catch (error) {
console.log(error);
Expand Down
3 changes: 1 addition & 2 deletions src/functions/CheckPermits.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { serverId, autoModBypassRole } from '../../config.json';
import { autoModBypassRole } from '../../config.json';
import { readFileSync, writeFileSync } from 'fs';
import { UserPermit } from '../commands/automod';
import { Client } from 'discord.js';

export default async function CheckPermits(client: Client) {

Check warning on line 6 in src/functions/CheckPermits.ts

View workflow job for this annotation

GitHub Actions / check linting (eslint)

Async function 'CheckPermits' has no 'await' expression
const guild = await client.guilds.fetch(serverId);
const permitData = readFileSync('data/permit.json');
if (!permitData) return;
const permit = JSON.parse(permitData.toString());
Expand Down
110 changes: 110 additions & 0 deletions src/functions/Infraction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ChannelType } from 'discord.js';
import { infractionLogchannel } from '../../config.json';
import { model, Schema } from 'mongoose';

export interface InfractionUser {
id: string;
staff: boolean;
bot: boolean;
}

export type InfractionType = 'EVENT' | 'WARN' | 'KICK' | 'BAN';

export interface InfractionInfomation {
automatic: boolean;
reason: string;
type: InfractionType;
user: InfractionUser;
staff: InfractionUser;
}

const InteractionUserSchema = new Schema({ id: String, staff: Boolean, bot: Boolean });
const InfractionSchema = new Schema({
automatic: Boolean,
reason: String,
type: String,
user: InteractionUserSchema,
staff: InteractionUserSchema
});
const InfractionModel = model('infraction', InfractionSchema);

class Infraction {
private infraction: InfractionInfomation;
constructor(infraction: InfractionInfomation) {
this.infraction = infraction;
}
public save() {
return new InfractionModel({
automatic: this.infraction.automatic,
reason: this.infraction.reason,
type: this.infraction.type,
user: this.infraction.user,
staff: this.infraction.staff
}).save();
}
public setAutomatic(automatic: boolean): this {
this.infraction.automatic = automatic;
return this;
}
public setReason(reason: string): this {
this.infraction.reason = reason;
return this;
}
public setType(type: InfractionType): this {
this.infraction.type = type;
return this;
}
public setUser(user: InfractionUser): this {
this.infraction.user = user;
return this;
}
public setStaff(staff: InfractionUser): this {
this.infraction.staff = staff;
return this;
}
public getInfraction(): InfractionInfomation {
return this.infraction;
}
public getReason(): string {
return this.infraction.reason;
}
public getType(): InfractionType {
return this.infraction.type;
}
public getUser(): InfractionUser {
return this.infraction.user;
}
public getStaff(): InfractionUser | null {
return this.infraction.staff;
}
public isAutomatic(): boolean {
return this.infraction.automatic;
}
public toString(): string {
return `Infraction: ${this.infraction.reason}\nAutomatic: ${this.infraction.automatic ? 'Yes' : 'No'}\nUser: <@${
this.infraction.user.id
}>\nStaff: ${this.infraction.staff ? `<@${this.infraction.staff.id}>` : 'None'}`;
}
public log(): this {
const channel = guild.channels.cache.get(infractionLogchannel);
if (!channel || channel.type !== ChannelType.GuildText) return this;
channel.send(this.toString());
return this;
}
}
export interface InfractionReturn {
success: boolean;
info: string;
infraction?: Infraction;
infractions?: Infraction[];
}
export async function getUserInfractions(id: string): Promise<InfractionReturn> {
const userInfractions = await InfractionModel.find({ 'user.id': id });
if (!userInfractions) return { success: false, info: 'No infractions found' };
const foundInfraction: Infraction[] = [];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
userInfractions.forEach((infraction) => foundInfraction.push(new Infraction(infraction)));
return { success: true, info: `User has ${foundInfraction.length} infractions`, infractions: foundInfraction };
}
export default Infraction;
5 changes: 5 additions & 0 deletions src/types/main.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Collection, SlashCommandBuilder, ChatInputCommandInteraction } from 'discord';
import { Guild } from 'discord.js';

export interface SlashCommand {
command: SlashCommandBuilder;
Expand All @@ -10,3 +11,7 @@ declare module 'discord.js' {
commands: Collection<string, SlashCommand>;
}
}

declare global {
var guild: Guild;

Check warning on line 16 in src/types/main.d.ts

View workflow job for this annotation

GitHub Actions / check linting (eslint)

Unexpected var, use let or const instead
}

0 comments on commit 2e174fc

Please sign in to comment.