2 Commits

Author SHA1 Message Date
Anthony Berg
c76a514a2c feat(db): add table for logging clear commands 2024-08-06 20:48:19 +02:00
Anthony Berg
f77758c039 feat(arabot): allow mods to run. add a requirement needing delete perms 2024-08-06 20:47:31 +02:00
65 changed files with 527 additions and 1073 deletions

View File

@@ -3,18 +3,15 @@ DISCORD_TOKEN= # Bot token from: https://discord.com/developers/
# Configuration
DEFAULT_PREFIX= # Prefix used to run commands in Discord
DEVELOPMENT= # (true/false) Enables developer mode
DEVELOPMENT= # (true/false) Enables developer mode
# Docker
POSTGRES_USER=USERNAME
POSTGRES_PASSWORD=PASSWORD
POSTGRES_DB=DB
# Redis (if running everything within docker compose, use "redis" for the host and leave the rest empty)
REDIS_HOST= # URL to redis database
REDIS_USER= # redis database user
REDIS_PASSWORD= # redis database password
REDIS_PORT= # redis database port
# Redis
REDIS_URL= # URL to redis database (if running everything within docker compose, use "redis")
# Database URL (designed for Postgres, but designed on Prisma)
DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer"

View File

@@ -1,5 +0,0 @@
[phases.build]
cmds = ["pnpm prisma generate", "..."]
[start]
cmd = 'pnpm run start:migrate'

View File

@@ -33,31 +33,31 @@
"node": ">=20",
"pnpm": ">=9"
},
"packageManager": "pnpm@9.6.0",
"dependencies": {
"@prisma/client": "^5.22.0",
"@sapphire/discord.js-utilities": "^7.3.2",
"@sapphire/framework": "^5.3.2",
"@prisma/client": "^5.17.0",
"@sapphire/discord.js-utilities": "^7.3.0",
"@sapphire/framework": "^5.2.1",
"@sapphire/plugin-logger": "^4.0.2",
"@sapphire/plugin-scheduled-tasks": "^10.0.2",
"@sapphire/plugin-scheduled-tasks": "^10.0.1",
"@sapphire/plugin-subcommands": "^6.0.3",
"@sapphire/stopwatch": "^1.5.4",
"@sapphire/time-utilities": "^1.7.14",
"@sapphire/stopwatch": "^1.5.2",
"@sapphire/time-utilities": "^1.7.12",
"@sapphire/ts-config": "^5.0.1",
"@sapphire/utilities": "^3.18.1",
"bullmq": "^5.34.10",
"discord.js": "^14.17.3",
"ioredis": "^5.4.2",
"@sapphire/utilities": "^3.17.0",
"bullmq": "^5.12.0",
"discord.js": "^14.15.3",
"ioredis": "^5.4.1",
"ts-node": "^10.9.2",
"typescript": "~5.4.5"
},
"devDependencies": {
"@types/node": "^20.17.13",
"@types/node": "^20.14.14",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"eslint": "8.56.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "3.2.4",
"prisma": "^5.22.0"
},
"packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
"prisma": "^5.17.0"
}
}

658
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -66,6 +66,7 @@ model User {
TempBanEndMod TempBan[] @relation("endTbanMod")
VCMuteUser VCMute[] @relation("vcMuteUser")
VCMuteMod VCMute[] @relation("vcMuteMod")
ClearCommandMod ClearCommand[]
}
model Verify {
@@ -312,3 +313,11 @@ model VCMute {
endTime DateTime?
reason String?
}
model ClearCommand {
id Int @id @default(autoincrement())
mod User @relation(fields: [modId], references: [id])
modId String
messages Int
time DateTime @default(now())
}

View File

@@ -18,7 +18,7 @@
*/
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { Message, MessageFlagsBitField } from 'discord.js';
import type { Message } from 'discord.js';
import { ChannelType, TextChannel } from 'discord.js';
export class AnonymousCommand extends Command {
@@ -67,8 +67,8 @@ export class AnonymousCommand extends Command {
if (guild === null) {
await interaction.reply({
content: 'Error fetching guild!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -77,17 +77,8 @@ export class AnonymousCommand extends Command {
if (interaction.channel === null) {
await interaction.reply({
content: 'Error getting the channel!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
});
return;
}
if (!interaction.channel.isSendable()) {
await interaction.reply({
content: `I do not have sufficient permissions to send a message in ${interaction.channel}!`,
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -95,8 +86,8 @@ export class AnonymousCommand extends Command {
await interaction.channel.send(message);
await interaction.reply({
content: 'Sent the message',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -104,8 +95,8 @@ export class AnonymousCommand extends Command {
if (channel.type !== ChannelType.GuildText) {
await interaction.reply({
content: 'Could not send, unsupported text channel!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
}
@@ -114,8 +105,8 @@ export class AnonymousCommand extends Command {
await interaction.reply({
content: 'Sent the message',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
}
@@ -130,7 +121,7 @@ export class AnonymousCommand extends Command {
return;
}
if (channel.isSendable()) {
if (channel.isTextBased()) {
await channel.send(text);
} else {
await message.react('❌');

View File

@@ -18,6 +18,7 @@
*/
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { PermissionFlagsBits } from 'discord.js';
import type { Message } from 'discord.js';
export class ClearCommand extends Command {
@@ -26,7 +27,8 @@ export class ClearCommand extends Command {
...options,
name: 'clear',
description: 'Deletes 1-100 messages in bulk',
preconditions: ['CoordinatorOnly'],
preconditions: [['CoordinatorOnly', 'ModOnly']],
requiredUserPermissions: [PermissionFlagsBits.ManageMessages]
});
}

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser';
import { getBalance } from '#utils/database/fun/economy';
import { getBalance } from '#utils/database/economy';
import { EmbedBuilder } from 'discord.js';
export class BalanceCommand extends Command {

View File

@@ -21,7 +21,7 @@ import { Command, RegisterBehavior } from '@sapphire/framework';
import { Time } from '@sapphire/time-utilities';
import type { User, Guild, GuildMember, Message } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser';
import { daily, getLastDaily } from '#utils/database/fun/economy';
import { daily, getLastDaily } from '#utils/database/economy';
import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids';

View File

@@ -20,7 +20,7 @@
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser';
import { getBalance, transfer } from '#utils/database/fun/economy';
import { getBalance, transfer } from '#utils/database/economy';
import { EmbedBuilder, TextChannel } from 'discord.js';
import IDs from '#utils/ids';

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js';
import { N1984 } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { addFunLog, countTotal } from '#utils/database/fun';
export class N1984Command extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js';
import { Cringe } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { addFunLog, countTotal } from '#utils/database/fun';
export class CringeCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js';
import { Hugs } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { addFunLog, countTotal } from '#utils/database/fun';
export class HugCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js';
import { Kill } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { addFunLog, countTotal } from '#utils/database/fun';
export class KillCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js';
import { Poke } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { addFunLog, countTotal } from '#utils/database/fun';
export class PokeCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -21,12 +21,9 @@ import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Message, Snowflake, TextChannel, Guild } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids';
import { addBan, checkBan } from '#utils/database/moderation/ban';
import { addBan, checkBan } from '#utils/database/ban';
import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser';
import {
checkTempBan,
removeTempBan,
} from '#utils/database/moderation/tempBan';
import { checkTempBan, removeTempBan } from '#utils/database/tempBan';
export class BanCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -22,7 +22,7 @@ import { Duration, DurationFormatter } from '@sapphire/time-utilities';
import type { User, Snowflake, TextChannel, Guild } from 'discord.js';
import { EmbedBuilder, Message } from 'discord.js';
import IDs from '#utils/ids';
import { addTempBan, checkTempBan } from '#utils/database/moderation/tempBan';
import { addTempBan, checkTempBan } from '#utils/database/tempBan';
import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser';
export class TempBanCommand extends Command {

View File

@@ -28,11 +28,8 @@ import type {
} from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids';
import { removeBan, checkBan, addBan } from '#utils/database/moderation/ban';
import {
checkTempBan,
removeTempBan,
} from '#utils/database/moderation/tempBan';
import { removeBan, checkBan, addBan } from '#utils/database/ban';
import { checkTempBan, removeTempBan } from '#utils/database/tempBan';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';
export class UnbanCommand extends Command {

View File

@@ -36,7 +36,7 @@ import {
updateUser,
fetchRoles,
} from '#utils/database/dbExistingUser';
import { restrict, checkActive } from '#utils/database/moderation/restriction';
import { restrict, checkActive } from '#utils/database/restriction';
import { randint } from '#utils/maths';
import { blockedRolesAfterRestricted } from '#utils/blockedRoles';

View File

@@ -21,7 +21,7 @@ import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { ChannelType, EmbedBuilder } from 'discord.js';
import type { Message, TextChannel, Guild, Snowflake } from 'discord.js';
import IDs from '#utils/ids';
import { getRestrictions } from '#utils/database/moderation/restriction';
import { getRestrictions } from '#utils/database/restriction';
import { checkStaff } from '#utils/checker';
export class RestrictLogsCommand extends Command {

View File

@@ -26,7 +26,7 @@ import {
unRestrict,
checkActive,
unRestrictLegacy,
} from '#utils/database/moderation/restriction';
} from '#utils/database/restriction';
export class UnRestrictCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -30,16 +30,16 @@ import {
TextChannel,
GuildMember,
Snowflake,
MessageFlagsBitField,
} from 'discord.js';
import type { Message } from 'discord.js';
import { isMessageInstance } from '@sapphire/discord.js-utilities';
import {
addSusNoteDB,
findNotes,
getNote,
deactivateNote,
deactivateAllNotes,
} from '#utils/database/moderation/sus';
} from '#utils/database/sus';
import { checkStaff } from '#utils/checker';
import IDs from '#utils/ids';
import { createSusLogEmbed } from '#utils/embeds';
@@ -157,10 +157,10 @@ export class SusCommand extends Subcommand {
const { guild } = interaction;
// Checks if all the variables are of the right type
if (guild === null) {
if (!(guild instanceof Guild)) {
await interaction.reply({
content: 'Error fetching guild!',
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
return;
}
@@ -169,7 +169,7 @@ export class SusCommand extends Subcommand {
await interaction.reply({
content: info.message,
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
}
@@ -195,7 +195,7 @@ export class SusCommand extends Subcommand {
const guild = message.guild;
if (guild === null) {
if (!(guild instanceof Guild)) {
await message.react('❌');
await message.reply(
'Could not find guild! Make sure you run this command in a server.',
@@ -292,7 +292,7 @@ export class SusCommand extends Subcommand {
if (guild == null) {
await interaction.reply({
content: 'Error fetching guild!',
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
return;
}
@@ -306,8 +306,8 @@ export class SusCommand extends Subcommand {
if (notes.length === 0) {
await interaction.reply({
content: `${user} has no sus notes!`,
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -318,8 +318,8 @@ export class SusCommand extends Subcommand {
// Sends the notes to the user
await interaction.reply({
embeds: [noteEmbed],
flags: staffChannel ? undefined : MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: !staffChannel,
fetchReply: true,
});
}
@@ -333,8 +333,8 @@ export class SusCommand extends Subcommand {
if (guild === null || channel === null) {
await interaction.reply({
content: 'Error fetching guild or channel!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -346,8 +346,8 @@ export class SusCommand extends Subcommand {
if (note === null) {
await interaction.reply({
content: 'Error fetching note from database!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -363,8 +363,8 @@ export class SusCommand extends Subcommand {
if (user === undefined) {
await interaction.reply({
content: 'Error fetching user!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -407,21 +407,16 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({
embeds: [noteEmbed],
components: [buttons],
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
// Checks if the message is not an APIMessage
if (message.resource === null) {
if (!isMessageInstance(message)) {
await interaction.editReply('Failed to retrieve the message :(');
return;
}
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses
const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press
@@ -475,10 +470,10 @@ export class SusCommand extends Subcommand {
) {
// Find user
let user = guild.client.users.cache.get(userId);
if (user === undefined) {
if (!(user instanceof User)) {
user = await guild.client.users.fetch(userId).catch(() => undefined);
}
if (user === undefined) return;
if (!(user instanceof User)) return;
// Log the sus note
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as
@@ -524,8 +519,8 @@ export class SusCommand extends Subcommand {
if (guild === null || channel === null) {
await interaction.reply({
content: 'Error fetching guild or channel!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -536,8 +531,8 @@ export class SusCommand extends Subcommand {
if (member === undefined) {
await interaction.reply({
content: 'Error fetching user!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -550,8 +545,8 @@ export class SusCommand extends Subcommand {
if (notes.length === 0) {
await interaction.reply({
content: `${user} had no notes!`,
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
return;
}
@@ -601,21 +596,16 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({
embeds: [noteEmbed],
components: [buttons],
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
// Checks if the message is not an APIMessage
if (message.resource === null) {
if (!isMessageInstance(message)) {
await interaction.editReply('Failed to retrieve the message :(');
return;
}
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses
const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press

View File

@@ -19,11 +19,7 @@
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { GuildMember, Message } from 'discord.js';
import {
addMute,
removeMute,
checkActive,
} from '#utils/database/moderation/vcMute';
import { addMute, removeMute, checkActive } from '#utils/database/vcMute';
import { addExistingUser } from '#utils/database/dbExistingUser';
export class VCMuteCommand extends Command {

View File

@@ -21,10 +21,7 @@ import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, TextChannel } from 'discord.js';
import type { Message, Guild, User } from 'discord.js';
import IDs from '#utils/ids';
import {
deleteWarning,
fetchWarning,
} from '#utils/database/moderation/warnings';
import { deleteWarning, fetchWarning } from '#utils/database/warnings';
import { checkStaff } from '#utils/checker';
export class DeleteWarningCommand extends Command {

View File

@@ -25,7 +25,7 @@ import {
} from '@sapphire/framework';
import type { User, Message, Snowflake, Guild, TextChannel } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser';
import { addWarn } from '#utils/database/moderation/warnings';
import { addWarn } from '#utils/database/warnings';
import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids';

View File

@@ -21,7 +21,7 @@ import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { ChannelType, EmbedBuilder } from 'discord.js';
import type { Message, Guild, User } from 'discord.js';
import IDs from '#utils/ids';
import { fetchWarnings } from '#utils/database/moderation/warnings';
import { fetchWarnings } from '#utils/database/warnings';
import { checkStaff } from '#utils/checker';
import { createWarningsEmbed } from '#utils/embeds';

View File

@@ -146,8 +146,8 @@ export class TrustedCommand extends Command {
.send(
`You have been given the ${trusted.name} role by ${mod}!` +
'\n\nThis role allows you to post attachments to the server and stream in VCs.' +
'\nMake sure that you follow the rules, especially by **not** posting anything **NSFW**, and **no animal products or consumption of animal products**.' +
`\n\nNot following these rules will result in the **immediate removal** of the ${trusted.name} role.`,
"\nMake sure that you follow the rules, and don't post anything NSFW, anything objectifying animals and follow Discord's ToS." +
`\nNot following these rules can result in the removal of the ${trusted.name} role.`,
)
.catch(() => {});
info.success = true;

View File

@@ -18,7 +18,6 @@
*/
import { Command, RegisterBehavior } from '@sapphire/framework';
import IDs from '#utils/ids';
export class InfoCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {
@@ -95,9 +94,7 @@ export class InfoCommand extends Command {
message =
"If you want to have the vegan or activist role, you'll need to do a voice verification. " +
"To do this, hop into the 'Verification' voice channel." +
"\n\nIf there aren't any verifiers available, you'll be disconnected, and you can rejoin later." +
`\n\nAlternatively if you would like text verification, you can use \`/apply\` in <#${IDs.channels.nonVegan.vcText}> ` +
'to be able fill out a Vegan Verification form through the Appy Bot.';
"\n\nIf there aren't any verifiers available, you'll be disconnected, and you can rejoin later.";
break;
case 'modMail':
message =

View File

@@ -17,8 +17,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { isMessageInstance } from '@sapphire/discord.js-utilities';
import { Command } from '@sapphire/framework';
import { Message, MessageFlagsBitField } from 'discord.js';
import type { Message } from 'discord.js';
export class PingCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {
@@ -40,13 +41,12 @@ export class PingCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const msg = await interaction.reply({
content: 'Ping?',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
ephemeral: true,
fetchReply: true,
});
if (msg.resource !== null && msg.resource.message !== null) {
const diff =
msg.resource.message.createdTimestamp - interaction.createdTimestamp;
if (isMessageInstance(msg)) {
const diff = msg.createdTimestamp - interaction.createdTimestamp;
const ping = Math.round(this.container.client.ws.ping);
return interaction.editReply(
`Pong 🏓! (Round trip took: ${diff}ms. Heartbeat: ${ping}ms.)`,
@@ -57,11 +57,6 @@ export class PingCommand extends Command {
}
public async messageRun(message: Message) {
if (!message.channel.isSendable()) {
// TODO manage logging/errors properly
return;
}
const msg = await message.channel.send('Ping?');
const diff = msg.createdTimestamp - message.createdTimestamp;

View File

@@ -20,7 +20,7 @@
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import { getRank, xpToNextLevel } from '#utils/database/fun/xp';
import { getRank, xpToNextLevel } from '#utils/database/xp';
export class RankCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -27,10 +27,6 @@ import '@sapphire/plugin-logger/register';
import { PrismaClient } from '@prisma/client';
import { Redis } from 'ioredis';
const REDIS_PORT = process.env.REDIS_PORT
? parseInt(process.env.REDIS_PORT)
: undefined;
// Setting up the Sapphire client
const client = new SapphireClient({
defaultPrefix: process.env.DEFAULT_PREFIX,
@@ -53,10 +49,7 @@ const client = new SapphireClient({
tasks: {
bull: {
connection: {
host: process.env.REDIS_HOST,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
host: process.env.REDIS_URL,
},
},
},
@@ -69,12 +62,9 @@ const main = async () => {
client.logger.info('Logging in');
// Create databases
container.database = new PrismaClient();
container.database = await new PrismaClient();
container.redis = new Redis({
host: process.env.REDIS_HOST,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
host: process.env.REDIS_URL,
db: 1,
});

View File

@@ -21,11 +21,7 @@ import {
InteractionHandler,
InteractionHandlerTypes,
} from '@sapphire/framework';
import {
ButtonInteraction,
GuildMember,
MessageFlagsBitField,
} from 'discord.js';
import type { ButtonInteraction, GuildMember } from 'discord.js';
import IDs from '#utils/ids';
export class NonVeganAccessButtonHandler extends InteractionHandler {
@@ -55,7 +51,7 @@ export class NonVeganAccessButtonHandler extends InteractionHandler {
if (member === null) {
await interaction.reply({
content: errorMessage,
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
return;
}
@@ -66,7 +62,7 @@ export class NonVeganAccessButtonHandler extends InteractionHandler {
if (!member.roles.cache.has(IDs.roles.vegan.vegan)) {
await interaction.reply({
content: 'You need to be vegan to use this button!',
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
return;
}
@@ -77,7 +73,7 @@ export class NonVeganAccessButtonHandler extends InteractionHandler {
content:
'Your access from the non vegan section has been removed. ' +
'If you want to gain access again, click this button again.',
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
return;
}
@@ -87,13 +83,13 @@ export class NonVeganAccessButtonHandler extends InteractionHandler {
content:
'Your access to the non vegan section has been given back. ' +
'If you want to remove access again, click this button again.',
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
} catch (error) {
this.container.logger.error(`Non Vegan Access Interaction: ${error}`);
await interaction.reply({
content: errorMessage,
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
}
}

View File

@@ -21,14 +21,8 @@ import {
InteractionHandler,
InteractionHandlerTypes,
} from '@sapphire/framework';
import {
ButtonInteraction,
GuildMember,
MessageFlagsBitField,
} from 'discord.js';
import type { ButtonInteraction, GuildMember, TextChannel } from 'discord.js';
import IDs from '#utils/ids';
import { checkActive } from '#utils/database/moderation/restriction';
import { addUser } from '#utils/database/dbExistingUser';
export class WelcomeButtonHandler extends InteractionHandler {
public constructor(
@@ -48,101 +42,49 @@ export class WelcomeButtonHandler extends InteractionHandler {
}
public async run(interaction: ButtonInteraction) {
const { member } = interaction;
let general = this.container.client.channels.cache.get(
let { member } = interaction;
const general = this.container.client.channels.cache.get(
IDs.channels.nonVegan.general,
);
// Messages that are used multiple times
const roleErrorMessage =
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.';
const welcomeMessage =
`${member} Welcome to ARA! :D Please check <#${IDs.channels.information.roles}> ` +
`and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussions and debates.` +
`\n\nIf you are vegan, you can join the 'Verification' voice channel, or use \`/apply\` with the Appy bot in <#${IDs.channels.nonVegan.vcText}>, ` +
'to be verified and gain access to more channels.';
// Checks if general is not in the cache
) as TextChannel | undefined;
if (general === undefined) {
// Sends an API request to get the channel
const generalFetch = await this.container.client.channels
.fetch(IDs.channels.nonVegan.general)
.catch(() => undefined);
return;
}
// If general does not exist
if (generalFetch === null || generalFetch === undefined) {
this.container.logger.error(
'WelcomeButtonHandler: Could not find and fetch the general channel!',
if (member === null) {
await interaction.reply({
content:
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.',
ephemeral: true,
});
return;
}
try {
member = member as GuildMember;
// Give non-vegan role
if (!member.voice.channel) {
await member.roles.add(IDs.roles.nonvegan.nonvegan);
await general.send(
`${member} Welcome to ARA! :D Please check <#${IDs.channels.information.roles}> ` +
`and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussions and debates.` +
"\n\nIf you are vegan, you can join the 'Verification' voice channel to be verified and gain access to more channels.",
);
await interaction.reply({
content:
'Sorry there was a problem trying to give you access to the server. Please try again later.',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
// Replace fetched version of general with the cached version
general = generalFetch;
}
// If the member could not be found
if (!(member instanceof GuildMember)) {
await interaction.reply({
content: roleErrorMessage,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
// Checks if the user is currently restricted
if (await checkActive(member.id)) {
await interaction.reply({
content: `You are currently restricted from this server! Contact the moderators by sending a DM to <@${IDs.modMail}>.`,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
// Give non-vegan role
if (member.voice.channel) {
await interaction.reply({
content:
"You're currently in a verification, you'll have to leave the verification or get verified before being able to access the server again.",
flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
});
return;
}
// Add the user to the database
await addUser(member.id);
// Give the role to the member
const role = await member.roles
.add(IDs.roles.nonvegan.nonvegan)
.catch(() => undefined);
// If the role could not be given
if (role === undefined) {
} catch (error) {
await interaction.reply({
content: roleErrorMessage,
flags: MessageFlagsBitField.Flags.Ephemeral,
content:
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.',
ephemeral: true,
});
return;
}
if (general.isSendable()) {
await general.send(welcomeMessage);
} else {
this.container.logger.error(
'WelcomeButtonHandler: The bot does not have permission to send in general!',
);
await member.send(welcomeMessage);
}
}
}

View File

@@ -20,7 +20,7 @@
import { Listener } from '@sapphire/framework';
import type { GuildBan } from 'discord.js';
import { AuditLogEvent, EmbedBuilder, TextChannel } from 'discord.js';
import { addBan, checkBan } from '#utils/database/moderation/ban';
import { addBan, checkBan } from '#utils/database/ban';
import IDs from '#utils/ids';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';

View File

@@ -19,8 +19,8 @@
import { Listener } from '@sapphire/framework';
import type { GuildMember } from 'discord.js';
import { checkBan, getBanReason } from '#utils/database/moderation/ban';
import { checkTempBan } from '#utils/database/moderation/tempBan';
import { checkBan, getBanReason } from '#utils/database/ban';
import { checkTempBan } from '#utils/database/tempBan';
export class BanJoinListener extends Listener {
public constructor(

View File

@@ -20,7 +20,7 @@
import { Listener } from '@sapphire/framework';
import type { GuildBan } from 'discord.js';
import { AuditLogEvent, EmbedBuilder, TextChannel } from 'discord.js';
import { addBan, checkBan, removeBan } from '#utils/database/moderation/ban';
import { addBan, checkBan, removeBan } from '#utils/database/ban';
import IDs from '#utils/ids';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';

View File

@@ -21,7 +21,7 @@
import { Listener } from '@sapphire/framework';
import type { Message } from 'discord.js';
import { getLastCount, addCount } from '#utils/database/fun/counting';
import { getLastCount, addCount } from '#utils/database/counting';
import IDs from '#utils/ids';
export class XpListener extends Listener {
@@ -49,11 +49,6 @@ export class XpListener extends Listener {
// If no counts exist on the database, then create the first count from the bot
if (lastCount === null) {
if (this.container.client.id === null) {
if (!message.channel.isSendable()) {
// TODO manage logging/errors properly
return;
}
message.channel.send(
'An unexpected error occurred trying to set up the counting channel, please contact a developer!',
);
@@ -68,11 +63,6 @@ export class XpListener extends Listener {
lastCount = await getLastCount();
if (lastCount === null) {
if (!message.channel.isSendable()) {
// TODO manage logging/errors properly
return;
}
message.channel.send(
'An unexpected error occurred, please contact a developer!',
);

View File

@@ -1,189 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2025 Anthony Berg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
I used the Sapphire documentation and parts of the code from the Sapphire CLI to
create this file.
*/
import { Listener } from '@sapphire/framework';
import { DurationFormatter } from '@sapphire/time-utilities';
import { Client } from 'discord.js';
import IDs from '#utils/ids';
import { fetchRoles } from '#utils/database/dbExistingUser';
import { checkActive } from '#utils/database/moderation/restriction';
import { getUser } from '#utils/database/fun/xp';
export class FixRolesOnReady extends Listener {
public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, {
...options,
once: true,
event: 'ready',
// !!!!!!!!!!!! WARNING !!!!!!!!!!!!
// THIS SHOULD BE DISABLED BY DEFAULT
// THIS IS ONLY USED FOR RESTORING ROLES TO THE SERVER!
// ENABLING THIS UNINTENTIONALLY WILL CAUSE SLOWDOWNS TO THE BOT DUE TO RATE LIMITING!
enabled: false,
});
}
public async run(client: Client) {
this.container.logger.info(
'FixRolesOnReady: Preparation before starting to fix the roles for each user...',
);
// Fetching the Guild
const guild = await client.guilds.fetch(IDs.guild).catch(() => undefined);
if (guild === undefined) {
this.container.logger.error('FixRolesOnReady: Could not find the server');
return;
}
// Fetching the channel for the logs
// Leave the snowflake parameter empty for no logs
const logChannel = await client.channels.fetch('').catch(() => null);
const sendLogs = logChannel !== null;
if (!sendLogs) {
this.container.logger.error(
'FixRolesOnReady: Could not find the channel for bot logs.',
);
} else if (sendLogs && !logChannel.isSendable()) {
this.container.logger.info(
'FixRolesOnReady: No permission to send in bots logs channel.',
);
return;
}
// Get all the current users
this.container.logger.info('FixRolesOnReady: Fetching all the members...');
if (sendLogs) {
logChannel.send('Fetching all the users in ARA!');
}
const members = await guild.members.fetch().catch(() => undefined);
if (members === undefined) {
this.container.logger.error(
'FixRolesOnReady: Could not fetch all the members, this function is stopping now.',
);
if (sendLogs) {
logChannel.send("Never mind, something went wrong :'(");
}
return;
}
const totalMembers = members.size;
this.container.logger.info(
`FixRolesOnReady: Done fetching ${totalMembers} members!`,
);
// Giving the roles to each user
let count = 0;
const startTime = new Date().getTime();
this.container.logger.info(
'FixRolesOnReady: Starting the process of fixing the roles for every member...',
);
for (const [userId, member] of members) {
// Update the counter for the total number of users processed
count += 1;
// Send a message with an update for every 50 completions
// Checks if `channelLog` has been set to null
// The RHS of the modulo should be around 100
if (sendLogs && count % 250 === 0) {
const currentTime = new Date().getTime();
const runningTime = currentTime - startTime;
const remaining = totalMembers - count;
// Basing this on the fact that
const eta = remaining * (runningTime / count);
const estimate = new DurationFormatter().format(eta);
logChannel.send(
`Given roles to ${count} out of ${totalMembers} members. Estimated time until completion: ${estimate}`,
);
}
// Checks if the user already has vegan or non-vegan role
// Checks if the user is restricted, and skips over them if they are
const restricted = await checkActive(userId);
if (
restricted ||
member.roles.cache.has(IDs.roles.restrictions.restricted1) ||
member.roles.cache.has(IDs.roles.restrictions.restricted2) ||
member.roles.cache.has(IDs.roles.restrictions.restricted3) ||
member.roles.cache.has(IDs.roles.restrictions.restricted4) ||
member.roles.cache.has(IDs.roles.restrictions.restrictedVegan)
) {
continue;
}
// Fetch the roles for the member in the database
const dbRoles = await fetchRoles(userId);
// Filters out the roles that the member does not have
const roles = dbRoles.filter((role) => !member.roles.cache.has(role));
if (!roles.includes(IDs.roles.nonvegan.nonvegan)) {
const xp = await getUser(userId);
if (xp !== null && xp.xp > 0) {
roles.push(IDs.roles.nonvegan.nonvegan);
}
}
// Give the roles to the member
if (roles.length > 0) {
await member.roles.add(roles);
}
// Log the completion
this.container.logger.info(
`FixRolesOnReady: Given roles to ${count}/${totalMembers}.`,
);
// Add a delay so that there's around 4 users processed a second
await this.delay(1000);
}
// Send the logs that the fix has finished.
const endTime = new Date().getTime();
const totalTime = endTime - startTime;
const totalTimeWritten = new DurationFormatter().format(totalTime);
const finishMessage = `Finished fixing roles for all ${totalMembers} members! It took ${totalTimeWritten} to complete.`;
this.container.logger.info(`FixRolesOnReady: ${finishMessage}`);
if (sendLogs) {
logChannel.send(finishMessage);
}
}
private delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}

View File

@@ -22,17 +22,14 @@ import { ChannelType } from 'discord.js';
import type { GuildChannel, EmbedBuilder } from 'discord.js';
import { setTimeout } from 'timers/promises';
import IDs from '#utils/ids';
import {
checkActive,
getRestrictions,
} from '#utils/database/moderation/restriction';
import { findNotes } from '#utils/database/moderation/sus';
import { checkActive, getRestrictions } from '#utils/database/restriction';
import { findNotes } from '#utils/database/sus';
import {
createRestrictLogEmbed,
createSusLogEmbed,
createWarningsEmbed,
} from '#utils/embeds';
import { fetchWarnings } from '#utils/database/moderation/warnings';
import { fetchWarnings } from '#utils/database/warnings';
export class ModMailCreateListener extends Listener {
public constructor(

View File

@@ -22,7 +22,6 @@
import { Listener } from '@sapphire/framework';
import type { Client } from 'discord.js';
import IDs from '#utils/ids';
export class ReadyListener extends Listener {
public constructor(
@@ -36,24 +35,8 @@ export class ReadyListener extends Listener {
});
}
public async run(client: Client) {
public run(client: Client) {
const { username, id } = client.user!;
this.container.logger.info(`Successfully logged in as ${username} (${id})`);
const botLogChannel = await client.channels.fetch(IDs.channels.logs.bot);
if (botLogChannel === null) {
this.container.logger.error(
'ReadyListener: Could not find the channel for bot logs.',
);
return;
} else if (!botLogChannel.isSendable()) {
this.container.logger.info(
'ReadyListener: No permission to send in bots logs channel.',
);
return;
}
botLogChannel.send('The bot has started up!');
}
}

View File

@@ -28,10 +28,7 @@ import type {
import { ChannelType } from 'discord.js';
import { fetchRoles, getLeaveRoles } from '#utils/database/dbExistingUser';
import { blockTime } from '#utils/database/verification';
import {
checkActive,
getSection,
} from '#utils/database/moderation/restriction';
import { checkActive, getSection } from '#utils/database/restriction';
import { blockedRoles, blockedRolesAfterRestricted } from '#utils/blockedRoles';
import IDs from '#utils/ids';

View File

@@ -83,11 +83,6 @@ export class Suggestions extends Listener {
return;
}
if (!mailbox.isSendable()) {
// TODO manage logging/errors properly
return;
}
const sent = await mailbox.send({
embeds: [suggestion],
content: message.author.toString(),

View File

@@ -19,7 +19,7 @@
import { Listener } from '@sapphire/framework';
import type { VoiceState } from 'discord.js';
import { checkActive, removeMute } from '#utils/database/moderation/vcMute';
import { checkActive, removeMute } from '#utils/database/vcMute';
export class VCMuteListener extends Listener {
public constructor(

View File

@@ -50,7 +50,7 @@ import {
startVerification,
finishVerification,
} from '#utils/database/verification';
import { findNotes } from '#utils/database/moderation/sus';
import { findNotes } from '#utils/database/sus';
import { addExistingUser } from '#utils/database/dbExistingUser';
import { rolesToString } from '#utils/formatter';
import IDs from '#utils/ids';

View File

@@ -148,10 +148,7 @@ export class VerificationLeaveVCListener extends Listener {
listTextChannels.forEach((c) => {
const textChannel = c as TextChannel;
// Checks if the channel topic has the user's snowflake
if (
textChannel.topic !== null &&
textChannel.topic.includes(userSnowflake!)
) {
if (textChannel.topic!.includes(userSnowflake!)) {
textChannel.delete();
}
});

View File

@@ -80,10 +80,7 @@ export class VerificationReady extends Listener {
const textChannel = c as TextChannel;
// Checks if the channel topic has the user's snowflake
emptyVC.forEach((snowflake) => {
if (
textChannel.topic !== null &&
textChannel.topic.includes(snowflake)
) {
if (textChannel.topic!.includes(snowflake)) {
textChannel.delete();
}
});

View File

@@ -1,88 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2024 Anthony Berg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Listener } from '@sapphire/framework';
import { GuildMember } from 'discord.js';
import IDs from '#utils/ids';
import { noModHistory, userPreviouslyHadRole } from '#utils/database/memberMod';
/**
* Gives the trusted role to users who have levelled up to level 5
* and has not gotten any other warnings/restrictions prior.
*/
export class TrustedListener extends Listener {
public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, {
...options,
event: 'xpLevelUp',
});
}
public async run(member: GuildMember, level: number) {
// Checks if the member has gotten level 7
// Has been nefred. Should take around 1.5 hours to get the trusted role now
if (level !== 7) {
return;
}
// Checks if the user has been previously moderated
const noModerationHistory = await noModHistory(member.id);
if (!noModerationHistory) {
return;
}
const { guild } = member;
const trusted = guild.roles.cache.get(IDs.roles.trusted);
if (trusted === undefined) {
this.container.logger.error(
'TrustedXP Listener: the Trusted role could not be found in the guild.',
);
return;
}
// Checks if the member has previously had the trusted role given/removed
const previouslyHadRole = await userPreviouslyHadRole(
member.id,
trusted.id,
);
if (previouslyHadRole) {
return;
}
// Checks if the user already has the trusted role
if (member.roles.cache.has(trusted.id)) {
return;
}
// Gives the trusted role to the member
await member.roles.add(trusted);
// Send a DM to inform the member that they have been given the trusted role
await member.user.send(
`Hi, you have been given the ${trusted.name} as you have been interacting in ARA for a long enough time!` +
'\n\nThis role allows you to post attachments to the server and stream in VCs.' +
'\nMake sure that you follow the rules, especially by **not** posting anything **NSFW**, and **no animal products or consumption of animal products**.' +
`\n\nNot following these rules will result in the **immediate removal** of the ${trusted.name} role.`,
);
}
}

View File

@@ -19,7 +19,7 @@
import { Listener } from '@sapphire/framework';
import type { Message } from 'discord.js';
import { addXp, checkCanAddXp } from '#utils/database/fun/xp';
import { addXp, checkCanAddXp } from '#utils/database/xp';
import { randint } from '#utils/maths';
export class XpListener extends Listener {
@@ -46,12 +46,6 @@ export class XpListener extends Listener {
const xp = randint(15, 25);
const level = await addXp(user.id, xp);
// Emits that a user has leveled up
if (level !== null) {
this.container.logger.info('User is levelling up!');
this.container.client.emit('xpLevelUp', message.member, level);
}
await addXp(user.id, xp);
}
}

View File

@@ -52,9 +52,7 @@ export class VerifyReminder extends ScheduledTask {
await channel.send(
"If you want to have the vegan or activist role, you'll need to do a voice verification. " +
"To do this, hop into the 'Verification' voice channel." +
"\n\nIf there aren't any verifiers available, you'll be disconnected, and you can rejoin later." +
`\nAlternatively if you would like text verification, you can use \`/apply\` in <#${IDs.channels.nonVegan.vcText}> ` +
'to be able fill out a Vegan Verification form through the Appy Bot.',
"\n\nIf there aren't any verifiers available, you'll be disconnected, and you can rejoin later.",
);
// Reset the total message counter to 0

View File

@@ -20,11 +20,8 @@
import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks';
import IDs from '#utils/ids';
import { EmbedBuilder } from 'discord.js';
import { checkBan } from '#utils/database/moderation/ban';
import {
checkTempBan,
removeTempBan,
} from '#utils/database/moderation/tempBan';
import { checkBan } from '#utils/database/ban';
import { checkTempBan, removeTempBan } from '#utils/database/tempBan';
export class TempBan extends ScheduledTask {
public constructor(

View File

@@ -17,18 +17,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { GuildMember, GuildMemberRoleManager, Snowflake } from 'discord.js';
import type {
GuildMember,
GuildMemberRoleManager,
Snowflake,
} from 'discord.js';
import { container } from '@sapphire/framework';
import IDs from '#utils/ids';
import { Prisma } from '@prisma/client';
import { DefaultArgs, GetResult } from '@prisma/client/runtime/binary';
/**
* Checks if the user exists on the User table in the database
* @param {string} userId Snowflake for the user being checked
* @return {Promise<boolean>} If the user was found
* @return {boolean} If the user was found
*/
export async function userExists(userId: Snowflake): Promise<boolean> {
export async function userExists(userId: Snowflake) {
// Counts if the user is on the database by their snowflake
const userQuery = await container.database.user.findFirst({
where: {
@@ -64,26 +66,6 @@ function getRoles(roles: GuildMemberRoleManager) {
return rolesDict;
}
/**
* Adds a new user to the server
* @param {Snowflake} userId the `User` snowflake to be added to the database
*/
export async function addUser(userId: Snowflake) {
// Uses upsert just in case the user has joined the server previously but has not gotten the roles previously
await container.database.user.upsert({
where: {
id: userId,
},
update: {
notVegan: true,
},
create: {
id: userId,
notVegan: true,
},
});
}
/**
* Add user to the database, if they have not been added beforehand
* @param {GuildMember} member GuildMember for the user to be added to the database
@@ -173,9 +155,9 @@ export async function updateUser(member: GuildMember) {
/**
* Gets the roles that the user that is on the User table.
* @param {string} userId Snowflake of the user to fetch roles from
* @return {Promise<Snowflake[]>} Array of Role Snowflakes
* @return {Snowflake[]} Array of Role Snowflakes
*/
export async function fetchRoles(userId: Snowflake): Promise<Snowflake[]> {
export async function fetchRoles(userId: Snowflake) {
// Get the user's roles
const roleQuery = await container.database.user.findUnique({
where: {
@@ -248,26 +230,12 @@ export async function logLeaving(member: GuildMember) {
});
}
// The type returned by `getLeaveRoles`.
// Includes a list of all the role Snowflakes when the user last left the server
type GetLeaveRoles = Prisma.Prisma__LeaveLogClient<
GetResult<
Prisma.$LeaveLogPayload<DefaultArgs>,
{
select: { roles: boolean };
},
'findFirst'
> | null,
null,
DefaultArgs
>;
/**
* Get the roles that the user had prior to when they left the server.
* @param {string} userId Snowflake of the user who joined the server
* @return {GetLeaveRoles} Array of Role Snowflakes
* @return {string[]} Array of Role Snowflakes
*/
export function getLeaveRoles(userId: Snowflake): GetLeaveRoles {
export async function getLeaveRoles(userId: Snowflake) {
const roles = container.database.leaveLog.findFirst({
where: {
userId,

View File

@@ -1,55 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2024 Anthony Berg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Snowflake } from 'discord.js';
import { countWarnings } from '#utils/database/moderation/warnings';
import { countRestrictions } from '#utils/database/moderation/restriction';
import { container } from '@sapphire/framework';
/**
* Checks if the user has
* @param userId Discord Snowflake of the user to check
* @return Boolean true if no prior moderation action
*/
export async function noModHistory(userId: Snowflake) {
const warnCount = await countWarnings(userId);
const restrictCount = await countRestrictions(userId);
return warnCount === 0 && restrictCount === 0;
}
/**
* Checks if the user has previously had a role given or taken away by a moderator.
* @param userId Discord Snowflake of the user to check
* @param roleId Snowflake of the role being checked for the user
* @return Boolean true if the user has had a moderator give/remove the specified role
*/
export async function userPreviouslyHadRole(
userId: Snowflake,
roleId: Snowflake,
) {
const count = await container.database.roleLog.count({
where: {
userId,
roleId,
},
});
return count !== 0;
}

View File

@@ -114,19 +114,6 @@ export async function getSection(userId: Snowflake) {
return restriction.section;
}
/**
* Returns the amount of restrictions a user has.
* @param userId Discord Snowflake of the user to check
* @return number The amount of restrictions the user has
*/
export async function countRestrictions(userId: Snowflake) {
return container.database.restrict.count({
where: {
userId,
},
});
}
// This is only for restrictions created with the old bot
export async function unRestrictLegacy(
userId: Snowflake,

View File

@@ -61,16 +61,3 @@ export async function deleteWarning(warningId: number) {
},
});
}
/**
* Returns the amount of warnings a user has.
* @param userId Discord Snowflake of the user to check
* @return number The amount of warnings the user has
*/
export async function countWarnings(userId: Snowflake) {
return container.database.warning.count({
where: {
userId,
},
});
}

View File

@@ -36,7 +36,7 @@ export async function addXp(userId: Snowflake, xp: number) {
}
}
const info = await container.database.xp.upsert({
await container.database.xp.upsert({
where: {
userId,
},
@@ -63,12 +63,6 @@ export async function addXp(userId: Snowflake, xp: number) {
xpForNextLevel: xp,
},
});
if (level === 1) {
return info.level;
} else {
return null;
}
}
export async function checkCanAddXp(userId: Snowflake) {

View File

@@ -18,7 +18,6 @@
*/
const devIDs = {
guild: '999431674972618792',
roles: {
trusted: '999431675081666599',
booster: '',
@@ -42,7 +41,6 @@ const devIDs = {
restricted2: '999431674997788676',
restricted3: '999431674997788675',
restricted4: '999431674997788674',
restrictedVegan: '1075952207091994726',
restricted: [
'999431674997788677', // Restricted 1
'999431674997788676', // Restricted 2
@@ -102,7 +100,6 @@ const devIDs = {
},
nonVegan: {
general: '999431677325615189',
vcText: '999431677535338567',
},
vegan: {
general: '999431677535338575',
@@ -128,7 +125,6 @@ const devIDs = {
},
logs: {
restricted: '999431681217937513',
bot: '999431681217937516',
economy: '999431681599623198',
sus: '999431681599623199',
},
@@ -142,7 +138,6 @@ const devIDs = {
private: '999431679527628818',
restricted: '999431679812845654',
},
modMail: '575252669443211264',
};
export default devIDs;

View File

@@ -18,9 +18,9 @@
*/
import type { Guild, User } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import type { SusNotes } from '#utils/database/moderation/sus';
import { RestrictionLogs } from '#utils/database/moderation/restriction';
import { Warnings } from '#utils/database/moderation/warnings';
import type { SusNotes } from '#utils/database/sus';
import { RestrictionLogs } from '#utils/database/restriction';
import { Warnings } from '#utils/database/warnings';
export function createSusLogEmbed(notes: SusNotes, user: User, guild: Guild) {
const embed = new EmbedBuilder()

View File

@@ -20,36 +20,34 @@
import devIDs from '#utils/devIDs';
let IDs = {
guild: '730907954345279591',
roles: {
trusted: '1329089675977035879',
trusted: '731563158011117590',
booster: '731213264540795012',
nonvegan: {
nonvegan: '1329093962153332848',
vegCurious: '1329107984227369020',
nonvegan: '774763753308815400',
vegCurious: '832656046572961803',
convinced: '797132019166871612',
},
vegan: {
vegan: '788114978020392982',
activist: '1329112833115295815',
activist: '730915638746546257',
nvAccess: '1076857105648209971',
plus: '798682625619132428',
araVegan: '995394977658044506',
},
restrictions: {
sus: '1329125130949103626',
sus: '859145930640457729',
muted: '730924813681688596',
softMute: '775934741139554335',
restricted1: '809769217477050369',
restricted2: '872482843304001566',
restricted3: '1329126085207789658',
restricted4: '1329126181164945499',
restrictedVegan: '1075951477379567646',
restricted3: '856582673258774538',
restricted4: '872472182888992858',
restricted: [
'809769217477050369', // Restricted 1
'872482843304001566', // Restricted 2
'1329126085207789658', // Restricted 3
'1329126181164945499', // Restricted 4
'856582673258774538', // Restricted 3
'872472182888992858', // Restricted 4
'1075951477379567646', // Restricted Vegan
],
},
@@ -77,7 +75,7 @@ let IDs = {
stageHost: '854893757593419786',
patron: '765370219207852055',
patreon: '993848684640997406',
verifyBlock: '1329107805130461247',
verifyBlock: '1032765019269640203',
bookClub: '955516408249352212',
debateHost: '935508325615931443',
gameNightHost: '952779915701415966',
@@ -104,7 +102,6 @@ let IDs = {
},
nonVegan: {
general: '798967615636504657',
vcText: '808191982169096232',
},
vegan: {
general: '787738272616808509',
@@ -130,7 +127,6 @@ let IDs = {
},
logs: {
restricted: '920993034462715925',
bot: '872126272015314966',
economy: '932050015034159174',
sus: '872884989950324826',
},
@@ -144,7 +140,6 @@ let IDs = {
private: '992581296901599302',
restricted: '809765577236283472',
},
modMail: '575252669443211264',
};
// Check if the bot is in development mode