Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.
Merged
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
891 changes: 773 additions & 118 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"start": "node .",
"tsc": "tsc --build",
"build": "turbo tsc",
"dev": "concurrently -k \"tsc -w\" \"nodemon .\"",
"dev:watch": "tsc --build -w",
"dev:start": "nodemon .",
"dev": "npm-run-all build -p dev:*",
"deploy": "turbo tsc && node build/src/Utils/functions/deploy-commands",
"release": "standard-version",
"lint": "eslint --cache --fix .",
Expand All @@ -49,12 +51,12 @@
"@types/node-schedule": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"concurrently": "^7.6.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.31.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.0",
"nodemon": "2.0.20",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.2",
"prisma": "^4.8.1",
"standard-version": "^9.5.0",
Expand Down
14 changes: 5 additions & 9 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,12 @@ type SetupWebhook {
url String
}

type UserDataUser {
id String
tag String
}

type UserDataWarnings {
automated Boolean
moderatorId String?
number Int
id String
reason String
timestamp Int?
timestamp DateTime
}

model blacklistedServers {
Expand Down Expand Up @@ -102,8 +97,9 @@ model userBadges {
badges String[]
}

model userData {
model userWarns {
id String @id @default(auto()) @map("_id") @db.ObjectId
user UserDataUser
userId String @unique
userTag String
warnings UserDataWarnings[]
}
69 changes: 69 additions & 0 deletions src/Commands/Information/listwarns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { stripIndents } from 'common-tags';
import { AutocompleteInteraction, ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import { checkIfStaff, getDb } from '../../Utils/functions/utils';

export default {
data: new SlashCommandBuilder()
.setName('listwarns')
.setDescription('List all warnings for a user.')
.addStringOption((option) =>
option
.setName('user')
.setDescription('The user to list warnings for. Use their ID if they are not in the server.')
.setRequired(false)
.setAutocomplete(true),
),
async execute(interaction: ChatInputCommandInteraction) {
const db = getDb();
const userId = interaction.options.getString('user') || interaction.user.id;
const userWarns = await db.userWarns.findFirst({ where: { userId } });
const user = await interaction.client.users.fetch(userId);

const emojis = interaction.client.emoji;

if (!userWarns?.warnings) {
return interaction.reply({
content: `${emojis.normal.yes} No warnings found!`,
ephemeral: true,
});
}

const warnList = userWarns.warnings.map((warn, index) => {
return {
name: `${index + 1}. ${warn.id}`,
value: stripIndents`
${emojis.normal.dotRed}Moderator: <@${warn.moderatorId}>
${emojis.normal.dotRed}Date: <t:${Math.round(warn.timestamp?.getTime() / 1000)}:d>
${emojis.normal.dotRed}Reason: ${warn.reason}`,
};
});
const embed = new EmbedBuilder()
.setAuthor({ name: `Warnings for ${user.tag}`, iconURL: user.avatarURL() || user.defaultAvatarURL })
.setDescription(`**Total Warnings:** ${userWarns.warnings.length}`)
.setFields(warnList)
.setColor('Random')
.setTimestamp();

await interaction.reply({ embeds: [embed] });
},
async autocomplete(interaction: AutocompleteInteraction) {
const allWarns = await getDb().userWarns.findMany();
const choices = allWarns.map((warn) => {
return { name: warn.userTag, value: warn.userId };
});

const staffUser = await checkIfStaff(interaction.client, interaction.user);

if (!staffUser) return interaction.respond([]);

const focusedValue = interaction.options.getFocused().toLowerCase();
const filtered = choices
.filter((choice) =>
choice.name.toLowerCase().includes(focusedValue) ||
choice.value.toLowerCase().includes(focusedValue),
)
.slice(0, 25);

interaction.respond(filtered);
},
};
69 changes: 69 additions & 0 deletions src/Commands/Staff/warn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';

export default {
staff: true,
data: new SlashCommandBuilder()
.setName('warn')
.setDescription('Warn a user. Staff-only')
.addSubcommand((subcommand) =>
subcommand
.setName('add')
.setDescription('Warn a user')
.addUserOption((option) =>
option
.setName('user')
.setDescription('The user to warn. Use their ID if they are not in the server.')
.setRequired(true),
)
.addStringOption((option) =>
option.setName('reason').setDescription('The reason for the warning.').setRequired(true),
),
)
.addSubcommand((subcommand) =>
subcommand
.setName('remove')
.setDescription('Remove a warning from a user')
.addUserOption((option) =>
option
.setName('user')
.setDescription('The user to remove a warning from. Use their ID if they are not in the server.')
.setRequired(true),
)
.addStringOption((option) =>
option
.setName('id')
.setDescription('The warning id. Use /listwarns to see the list of ids.')
.setRequired(true),
)
.addStringOption((option) =>
option
.setName('reason')
.setDescription('The reason for removing the warning.')
.setRequired(true),
),
)
.addSubcommand((subcommand) =>
subcommand
.setName('clear')
.setDescription('Clear all warnings for a user')
.addUserOption((option) =>
option
.setName('user')
.setDescription(
'The user to clear warnings for. Use their ID if they are not in the server.',
)
.setRequired(true),
)
.addStringOption((option) =>
option
.setName('reason')
.setDescription('The reason for clearing the warnings.')
.setRequired(true),
),
)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
async execute(interaction: ChatInputCommandInteraction) {
const subcommand = interaction.options.getSubcommand();
require('../../Scripts/warn/' + subcommand).execute(interaction);
},
};
43 changes: 43 additions & 0 deletions src/Scripts/warn/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ChatInputCommandInteraction } from 'discord.js';
import { getDb } from '../../Utils/functions/utils';
import crypto from 'crypto';

export = {
execute: async (interaction: ChatInputCommandInteraction) => {
await interaction.deferReply();

const db = getDb();
const user = interaction.options.getUser('user', true);
const userWarns = await db.userWarns.findFirst({ where: { userId: user.id } });

const emojis = interaction.client.emoji;

const warning = {
id: crypto.randomBytes(16).toString('hex'),
reason: interaction.options.getString('reason', true),
moderatorId: interaction.user.id,
timestamp: new Date(),
automated: false,
};

if (!userWarns) {
await db.userWarns.create({
data: { userId: user.id, userTag: user.tag, warnings: [warning] },
});
}
else {
await db.userWarns.update({
where: { userId: userWarns.userId },
data: {
userId: user.id,
userTag: user.tag,
warnings: [...userWarns.warnings, warning],
},
});
}

// TODO: Send nicer embed like elara
const notified = await user.send(`**${emojis.icons.exclamation} You have been warned in the network for:** ${warning.reason}`).catch(() => null);
await interaction.editReply(` ${emojis.normal.yes} Warned ${user.tag}! ${notified ? 'Notified them about their warn.' : 'I couldn\'t DM them.'}`);
},
};
22 changes: 22 additions & 0 deletions src/Scripts/warn/clear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ChatInputCommandInteraction } from 'discord.js';
import { getDb } from '../../Utils/functions/utils';

export = {
execute: async (interaction: ChatInputCommandInteraction) => {
const db = getDb();
const user = interaction.options.getUser('user', true);
const userWarns = await db.userWarns.findFirst({ where: { userId: user.id } });

const emojis = interaction.client.emoji;

if (!userWarns?.warnings) {
return interaction.reply({
content: `${emojis.normal.no} There are no warnings to remove!`,
ephemeral: true,
});
}

await db.userWarns.delete({ where: { userId: user.id } });
await interaction.reply(`${emojis.normal.yes} Successfully cleard all warnings from ${user.tag}!`);
},
};
30 changes: 30 additions & 0 deletions src/Scripts/warn/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ChatInputCommandInteraction } from 'discord.js';
import { getDb } from '../../Utils/functions/utils';

export = {
execute: async (interaction: ChatInputCommandInteraction) => {
const db = getDb();
const warnId = interaction.options.getString('id', true);
const user = interaction.options.getUser('user', true);
const userWarns = await db.userWarns.findFirst({ where: { userId: user.id } });

const emojis = interaction.client.emoji;

if (!userWarns?.warnings.some((warn) => warn.id === warnId)) {
return interaction.reply({
content: `${emojis.normal.no} There are no warnings to remove!`,
ephemeral: true,
});
}

await db.userWarns.update({
where: { userId: user.id },
data: {
userTag: user.tag,
warnings: { deleteMany: { where: { id: { equals: warnId } } } },
},
});

await interaction.reply(`${emojis.normal.yes} Successfully removed warning **${warnId}** from ${user.tag}!`);
},
};