1 Commits

Author SHA1 Message Date
Anthony Berg
a1b9fdcd4e feat(arabot): start listeners for error logging 2024-01-12 00:05:41 +00:00
82 changed files with 4909 additions and 2972 deletions

View File

@@ -1,6 +1,6 @@
.idea .idea
dist dist
node_modules node_modules
tsconfig.tsbuildinfo
npm-debug.log npm-debug.log
.env .env
*.md

View File

@@ -10,11 +10,9 @@ POSTGRES_USER=USERNAME
POSTGRES_PASSWORD=PASSWORD POSTGRES_PASSWORD=PASSWORD
POSTGRES_DB=DB POSTGRES_DB=DB
# Redis (if running everything within docker compose, use "redis" for the host and leave the rest empty) # Redis
REDIS_HOST= # URL to redis database REDIS_URL= # URL to redis database (if running everything within docker compose, use "redis")
REDIS_USER= # redis database user BULLMQ_URL # URL for redis database, but without redis:// and credentials
REDIS_PASSWORD= # redis database password
REDIS_PORT= # redis database port
# Database URL (designed for Postgres, but designed on Prisma) # Database URL (designed for Postgres, but designed on Prisma)
DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer" DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer"

3
.npmrc
View File

@@ -1,3 +0,0 @@
node-linker=hoisted
shamefully-hoist=true
public-hoist-pattern[]=@sapphire/*

View File

@@ -1,4 +1,2 @@
dist dist
node_modules node_modules
tsconfig.json
pnpm-lock.yaml

View File

@@ -1,26 +1,24 @@
FROM node:22 AS base FROM node:20
# PNPM
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
COPY . /app WORKDIR /opt/app
WORKDIR /app
FROM base AS prod-deps ENV NODE_ENV=production
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
FROM base AS build COPY --chown=node:node package.json .
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile COPY --chown=node:node package-lock.json .
RUN pnpm exec prisma generate COPY --chown=node:node tsconfig.json .
RUN pnpm run build COPY --chown=node:node prisma ./prisma/
FROM base RUN npm install
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/node_modules/.prisma /app/node_modules/.prisma
COPY --from=build /app/dist /app/dist
RUN chown node:node . COPY . .
RUN npx prisma generate
RUN npm run build
RUN chown node:node /opt/app/
USER node USER node
CMD [ "pnpm", "run", "start:migrate"]
CMD [ "npm", "run", "start:migrate"]

View File

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

4556
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,10 @@
"description": "A Discord bot for Animal Rights Advocates", "description": "A Discord bot for Animal Rights Advocates",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm",
"build": "tsc", "build": "tsc",
"cleanBuild": "rm -rf ./dist && tsc", "cleanBuild": "rm -rf ./dist && tsc",
"start": "node dist/index.js", "start": "node dist/index.js",
"start:migrate": "prisma migrate deploy && pnpm run start" "start:migrate": "prisma migrate deploy && npm run start"
}, },
"imports": { "imports": {
"#utils/*": "./dist/utils/*.js" "#utils/*": "./dist/utils/*.js"
@@ -29,35 +28,31 @@
"url": "https://github.com/veganhacktivists/arabot/issues" "url": "https://github.com/veganhacktivists/arabot/issues"
}, },
"homepage": "https://github.com/veganhacktivists/arabot#readme", "homepage": "https://github.com/veganhacktivists/arabot#readme",
"engines": {
"node": ">=20",
"pnpm": ">=9"
},
"dependencies": { "dependencies": {
"@prisma/client": "^5.22.0", "@prisma/client": "^5.7.1",
"@sapphire/discord.js-utilities": "^7.3.2", "@sapphire/discord.js-utilities": "^7.1.5",
"@sapphire/framework": "^5.3.2", "@sapphire/framework": "^5.0.5",
"@sapphire/plugin-logger": "^4.0.2", "@sapphire/plugin-logger": "^4.0.1",
"@sapphire/plugin-scheduled-tasks": "^10.0.2", "@sapphire/plugin-scheduled-tasks": "^10.0.0",
"@sapphire/plugin-subcommands": "^6.0.3", "@sapphire/plugin-subcommands": "^6.0.2",
"@sapphire/stopwatch": "^1.5.4", "@sapphire/stopwatch": "^1.5.1",
"@sapphire/time-utilities": "^1.7.14", "@sapphire/time-utilities": "^1.7.11",
"@sapphire/ts-config": "^5.0.1", "@sapphire/ts-config": "^5.0.0",
"@sapphire/utilities": "^3.18.1", "@sapphire/utilities": "^3.15.1",
"bullmq": "^5.34.10", "@types/node": "^20.10.6",
"discord.js": "^14.17.3", "bullmq": "^5.1.1",
"ioredis": "^5.4.2", "discord.js": "^14.14.1",
"ts-node": "^10.9.2", "redis": "^4.6.12",
"typescript": "~5.4.5" "ts-node": "^10.9.1",
"typescript": "^5.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.17.13", "@types/ioredis": "^5.0.0",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^6.17.0",
"eslint": "8.56.0", "eslint": "8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"prettier": "3.2.4", "prettier": "3.1.1",
"prisma": "^5.22.0" "prisma": "^5.7.1"
}, }
"packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
} }

1899
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
/*
Warnings:
- Added the required column `channelId` to the `StatRole` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "StatRole" ADD COLUMN "channelId" TEXT NOT NULL;

View File

@@ -225,7 +225,6 @@ model StatRole {
stat Stat @relation(fields: [statId], references: [id]) stat Stat @relation(fields: [statId], references: [id])
statId Int @id statId Int @id
roleId String roleId String
channelId String
} }
model ParticipantStat { model ParticipantStat {

View File

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

View File

@@ -356,12 +356,6 @@ export class PrivateCommand extends Subcommand {
} else if (user.roles.cache.has(IDs.roles.staff.eventCoordinator)) { } else if (user.roles.cache.has(IDs.roles.staff.eventCoordinator)) {
name = 'event'; name = 'event';
id = IDs.roles.staff.eventCoordinator; id = IDs.roles.staff.eventCoordinator;
} else if (user.roles.cache.has(IDs.roles.staff.mediaCoordinator)) {
name = 'media';
id = IDs.roles.staff.mediaCoordinator;
} else if (user.roles.cache.has(IDs.roles.staff.hrCoordinator)) {
name = 'hr';
id = IDs.roles.staff.hrCoordinator;
} else { } else {
name = 'coordinator'; name = 'coordinator';
id = IDs.roles.staff.coordinator; id = IDs.roles.staff.coordinator;

View File

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

View File

@@ -21,7 +21,7 @@ import { Command, RegisterBehavior } from '@sapphire/framework';
import { Time } from '@sapphire/time-utilities'; import { Time } from '@sapphire/time-utilities';
import type { User, Guild, GuildMember, Message } from 'discord.js'; import type { User, Guild, GuildMember, Message } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; 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 { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';

View File

@@ -20,7 +20,7 @@
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js'; import type { User, Guild, Message } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; 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 { EmbedBuilder, TextChannel } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js'; import { EmbedBuilder, GuildMember } from 'discord.js';
import { N1984 } from '#utils/gifs'; 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 { export class N1984Command extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
@@ -28,7 +28,7 @@ export class N1984Command extends Command {
...options, ...options,
name: '1984', name: '1984',
description: 'this is literally 1984', description: 'this is literally 1984',
preconditions: [['CoordinatorOnly', 'ModOnly']], preconditions: ['CoordinatorOnly', 'ModOnly'],
}); });
} }

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js'; import { EmbedBuilder, GuildMember } from 'discord.js';
import { Cringe } from '#utils/gifs'; 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 { export class CringeCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -27,6 +27,7 @@ export class HappyCommand extends Command {
...options, ...options,
name: 'happy', name: 'happy',
description: 'Express your happiness', description: 'Express your happiness',
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
}); });
} }

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js'; import { EmbedBuilder, GuildMember } from 'discord.js';
import { Hugs } from '#utils/gifs'; 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 { export class HugCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js'; import { EmbedBuilder, GuildMember } from 'discord.js';
import { Kill } from '#utils/gifs'; 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 { export class KillCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -20,7 +20,7 @@
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder, GuildMember } from 'discord.js'; import { EmbedBuilder, GuildMember } from 'discord.js';
import { Poke } from '#utils/gifs'; 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 { export class PokeCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -27,6 +27,7 @@ export class SadCommand extends Command {
...options, ...options,
name: 'sad', name: 'sad',
description: 'Express your sadness', description: 'Express your sadness',
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
}); });
} }

View File

@@ -27,6 +27,7 @@ export class ShrugCommand extends Command {
...options, ...options,
name: 'shrug', name: 'shrug',
description: 'Ugh... whatever... idk...', description: 'Ugh... whatever... idk...',
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
}); });
} }

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ import {
updateUser, updateUser,
fetchRoles, fetchRoles,
} from '#utils/database/dbExistingUser'; } 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 { randint } from '#utils/maths';
import { blockedRolesAfterRestricted } from '#utils/blockedRoles'; 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 { ChannelType, EmbedBuilder } from 'discord.js';
import type { Message, TextChannel, Guild, Snowflake } from 'discord.js'; import type { Message, TextChannel, Guild, Snowflake } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getRestrictions } from '#utils/database/moderation/restriction'; import { getRestrictions } from '#utils/database/restriction';
import { checkStaff } from '#utils/checker'; import { checkStaff } from '#utils/checker';
export class RestrictLogsCommand extends Command { export class RestrictLogsCommand extends Command {

View File

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

View File

@@ -25,21 +25,17 @@ import {
ButtonBuilder, ButtonBuilder,
ButtonInteraction, ButtonInteraction,
ButtonStyle, ButtonStyle,
User,
Guild,
TextChannel,
GuildMember,
Snowflake,
MessageFlagsBitField,
} from 'discord.js'; } from 'discord.js';
import type { Message } from 'discord.js'; import type { Message, GuildMember } from 'discord.js';
import { isMessageInstance } from '@sapphire/discord.js-utilities';
import { addExistingUser } from '#utils/database/dbExistingUser';
import { import {
addSusNoteDB, addToDatabase,
findNotes, findNotes,
getNote, getNote,
deactivateNote, deactivateNote,
deactivateAllNotes, deactivateAllNotes,
} from '#utils/database/moderation/sus'; } from '#utils/database/sus';
import { checkStaff } from '#utils/checker'; import { checkStaff } from '#utils/checker';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { createSusLogEmbed } from '#utils/embeds'; import { createSusLogEmbed } from '#utils/embeds';
@@ -58,8 +54,8 @@ export class SusCommand extends Subcommand {
{ {
name: 'add', name: 'add',
default: true, default: true,
chatInputRun: 'addNoteChatInput', chatInputRun: 'addNote',
messageRun: 'addNoteMessage', messageRun: 'addMessage',
}, },
{ {
name: 'view', name: 'view',
@@ -147,9 +143,7 @@ export class SusCommand extends Subcommand {
} }
// Subcommand to add sus note // Subcommand to add sus note
public async addNoteChatInput( public async addNote(interaction: Subcommand.ChatInputCommandInteraction) {
interaction: Subcommand.ChatInputCommandInteraction,
) {
// Get the arguments // Get the arguments
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const note = interaction.options.getString('note', true); const note = interaction.options.getString('note', true);
@@ -160,127 +154,40 @@ export class SusCommand extends Subcommand {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
}); });
return; return;
} }
const info = await this.addNote(user, mod, note, guild);
await interaction.reply({
content: info.message,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
}
// Non Application Command method of adding a sus note
public async addNoteMessage(message: Message, args: Args) {
// Get arguments
let user: User;
try {
user = await args.pick('user');
} catch {
await message.react('❌');
await message.reply('User was not provided!');
return;
}
const note = args.finished ? null : await args.rest('string');
const mod = message.author;
if (note === null) {
await message.react('❌');
await message.reply('No sus note was provided!');
return;
}
const guild = message.guild;
if (guild === null) {
await message.react('❌');
await message.reply(
'Could not find guild! Make sure you run this command in a server.',
);
return;
}
const info = await this.addNote(user, mod, note, guild);
if (!info.success) {
await message.react('❌');
return;
}
await message.react('✅');
}
private async addNote(user: User, mod: User, note: string, guild: Guild) {
const info = {
message: '',
success: false,
};
// Add the data to the database // Add the data to the database
await addSusNoteDB(user.id, mod.id, note);
// Gives the sus role to the user // Check if the user exists on the database
await this.addSusRole(user, guild); const member = guild.members.cache.get(user.id);
const modMember = guild.members.cache.get(mod.id);
info.message = `Added the sus note for ${user}: ${note}`; if (member === undefined || modMember === undefined) {
info.success = true; await interaction.reply({
content: 'Error fetching users!',
// Log the sus note ephemeral: true,
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as });
| TextChannel
| undefined;
if (logChannel === undefined) {
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as
| TextChannel
| undefined;
if (logChannel === undefined) {
this.container.logger.error('Sus Error: Could not fetch log channel');
info.message = `Added a sus note for ${user} but could not find the log channel. This has been logged to the database.`;
return info;
}
}
const message = new EmbedBuilder()
.setColor('#0099ff')
.setAuthor({
name: `Added sus note for ${user.tag}`,
iconURL: `${user.displayAvatarURL()}`,
})
.addFields(
{ name: 'User', value: `${user}`, inline: true },
{ name: 'Moderator', value: `${mod}`, inline: true },
{ name: 'Note', value: note },
)
.setTimestamp()
.setFooter({ text: `ID: ${user.id}` });
await logChannel.send({ embeds: [message] });
return info;
}
private async addSusRole(user: User, guild: Guild) {
// Get GuildMember for user to add a sus note for
let member = guild.members.cache.get(user.id);
// Checks if Member was not found in cache
if (member === undefined) {
// Fetches Member from API call to Discord
member = await guild.members.fetch(user.id).catch(() => undefined);
}
if (member === undefined) {
return; return;
} }
// Check if user and mod are on the database
await addExistingUser(member);
await addExistingUser(modMember);
await addToDatabase(user.id, mod.id, note);
// Give the user the sus role they don't already have the sus note // Give the user the sus role they don't already have the sus note
if (!member.roles.cache.has(IDs.roles.restrictions.sus)) { if (!member.roles.cache.has(IDs.roles.restrictions.sus)) {
await member.roles.add(IDs.roles.restrictions.sus); await member.roles.add(IDs.roles.restrictions.sus);
} }
await interaction.reply({
content: `${user} note: ${note}`,
ephemeral: true,
});
} }
public async listNote(interaction: Subcommand.ChatInputCommandInteraction) { public async listNote(interaction: Subcommand.ChatInputCommandInteraction) {
@@ -292,7 +199,7 @@ export class SusCommand extends Subcommand {
if (guild == null) { if (guild == null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
}); });
return; return;
} }
@@ -306,8 +213,8 @@ export class SusCommand extends Subcommand {
if (notes.length === 0) { if (notes.length === 0) {
await interaction.reply({ await interaction.reply({
content: `${user} has no sus notes!`, content: `${user} has no sus notes!`,
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
return; return;
} }
@@ -318,23 +225,22 @@ export class SusCommand extends Subcommand {
// Sends the notes to the user // Sends the notes to the user
await interaction.reply({ await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
flags: staffChannel ? undefined : MessageFlagsBitField.Flags.Ephemeral, ephemeral: !staffChannel,
withResponse: true, fetchReply: true,
}); });
} }
public async removeNote(interaction: Subcommand.ChatInputCommandInteraction) { public async removeNote(interaction: Subcommand.ChatInputCommandInteraction) {
// Get the arguments // Get the arguments
const noteId = interaction.options.getInteger('id', true); const noteId = interaction.options.getInteger('id', true);
const mod = interaction.user;
const { guild, channel } = interaction; const { guild, channel } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null || channel === null) { if (guild === null || channel === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild or channel!', content: 'Error fetching guild or channel!',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
return; return;
} }
@@ -346,46 +252,45 @@ export class SusCommand extends Subcommand {
if (note === null) { if (note === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching note from database!', content: 'Error fetching note from database!',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
return; return;
} }
const userId = note.userId;
const modId = note.modId;
// Get user GuildMembers for user and mod and person who ran command // Get user GuildMembers for user and mod and person who ran command
let user = guild.client.users.cache.get(userId); const member = await guild.members.cache.get(note.userId);
if (!(user instanceof User)) { const mod = await guild.members.cache.get(note.modId);
user = await guild.client.users.fetch(userId).catch(() => undefined);
} // TODO fix if user left the server
if (user === undefined) { if (member === undefined || mod === undefined) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching users from Discord!',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
return; return;
} }
let modCreator = guild.client.users.cache.get(modId); // Get user's name
if (!(modCreator instanceof User)) { let userName = note.userId;
modCreator = await guild.client.users.fetch(modId).catch(() => undefined); if (member !== undefined) {
userName = member.displayName;
} }
let modCreatorDisplay = modId; // Get mod name
if (modCreator instanceof User) { let modName = note.modId;
modCreatorDisplay = modCreator.displayName; if (mod !== undefined) {
modName = mod.displayName;
} }
// Create an embed for the note // Create an embed for the note
const noteEmbed = new EmbedBuilder() const noteEmbed = new EmbedBuilder()
.setColor('#ff0000') .setColor('#ff0000')
.setTitle(`Sus note for ${user.tag}`) .setTitle(`Sus note for ${userName}`)
.setThumbnail(user.displayAvatarURL()) .setThumbnail(member.displayAvatarURL())
.addFields({ .addFields({
name: `ID: ${noteId} | Moderator: ${modCreatorDisplay} | Date: <t:${Math.floor( name: `ID: ${noteId} | Moderator: ${modName} | Date: <t:${Math.floor(
note.time.getTime() / 1000, note.time.getTime() / 1000,
)}>`, )}>`,
value: note.note, value: note.note,
@@ -407,21 +312,16 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({ const message = await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
components: [buttons], components: [buttons],
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
// Checks if the message is not an APIMessage // Checks if the message is not an APIMessage
if (message.resource === null) { if (!isMessageInstance(message)) {
await interaction.editReply('Failed to retrieve the message :('); await interaction.editReply('Failed to retrieve the message :(');
return; return;
} }
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses // Listen for the button presses
const collector = channel.createMessageComponentCollector({ const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press max: 1, // Maximum of 1 button press
@@ -433,29 +333,19 @@ export class SusCommand extends Subcommand {
if (button.customId === `delete${noteId}`) { if (button.customId === `delete${noteId}`) {
await deactivateNote(noteId); await deactivateNote(noteId);
await interaction.editReply({ await interaction.editReply({
content: `${user}'s sus note (ID: ${noteId}) has been successfully removed`, content: `${member}'s sus note (ID: ${noteId}) has been successfully removed`,
embeds: [], embeds: [],
}); });
// TODO create a new Prisma function to only count and not to get a whole list of sus notes // TODO create a new Prisma function to only count and not to get a whole list of sus notes
// Check how many notes the user has and if 0, then remove sus note // Check how many notes the user has and if 0, then remove sus note
const notes = await findNotes(userId, true); const notes = await findNotes(member.id, true);
// Checks if there are no notes on the user and if there's none, remove the sus role // Checks if there are no notes on the user and if there's none, remove the sus role
if (notes.length === 0) { if (notes.length === 0) {
let member = guild.members.cache.get(userId);
if (!(member instanceof GuildMember)) {
member = await guild.members.fetch(userId).catch(() => undefined);
}
if (member instanceof GuildMember) {
await member.roles.remove(IDs.roles.restrictions.sus); await member.roles.remove(IDs.roles.restrictions.sus);
} }
} }
// Logs the removal of the sus note
await this.deleteNoteLogger(userId, mod, noteId, guild);
}
}); });
// Remove the buttons after they have been clicked // Remove the buttons after they have been clicked
@@ -466,66 +356,19 @@ export class SusCommand extends Subcommand {
}); });
} }
// Logs removal of 1 sus note
private async deleteNoteLogger(
userId: Snowflake,
mod: User,
noteId: number,
guild: Guild,
) {
// Find user
let user = guild.client.users.cache.get(userId);
if (user === undefined) {
user = await guild.client.users.fetch(userId).catch(() => undefined);
}
if (user === undefined) return;
// Log the sus note
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as
| TextChannel
| undefined;
if (logChannel === undefined) {
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as
| TextChannel
| undefined;
if (logChannel === undefined) {
this.container.logger.error('Sus Error: Could not fetch log channel');
return;
}
}
const embed = new EmbedBuilder()
.setColor('#28A745')
.setAuthor({
name: `Removed sus note for ${user.tag}`,
iconURL: `${user.displayAvatarURL()}`,
})
.addFields(
{ name: 'User', value: `${user}`, inline: true },
{ name: 'Moderator', value: `${mod}`, inline: true },
{ name: 'Note ID', value: `${noteId}`, inline: true },
)
.setTimestamp()
.setFooter({ text: `ID: ${user.id}` });
await logChannel.send({ embeds: [embed] });
}
public async removeAllNotes( public async removeAllNotes(
interaction: Subcommand.ChatInputCommandInteraction, interaction: Subcommand.ChatInputCommandInteraction,
) { ) {
// Get the arguments // Get the arguments
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const mod = interaction.user;
const { guild, channel } = interaction; const { guild, channel } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null || channel === null) { if (guild === null || channel === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild or channel!', content: 'Error fetching guild or channel!',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
return; return;
} }
@@ -536,8 +379,8 @@ export class SusCommand extends Subcommand {
if (member === undefined) { if (member === undefined) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching user!',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
return; return;
} }
@@ -550,8 +393,8 @@ export class SusCommand extends Subcommand {
if (notes.length === 0) { if (notes.length === 0) {
await interaction.reply({ await interaction.reply({
content: `${user} had no notes!`, content: `${user} had no notes!`,
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
return; return;
} }
@@ -601,21 +444,16 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({ const message = await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
components: [buttons], components: [buttons],
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
withResponse: true, fetchReply: true,
}); });
// Checks if the message is not an APIMessage // Checks if the message is not an APIMessage
if (message.resource === null) { if (!isMessageInstance(message)) {
await interaction.editReply('Failed to retrieve the message :('); await interaction.editReply('Failed to retrieve the message :(');
return; return;
} }
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses // Listen for the button presses
const collector = channel.createMessageComponentCollector({ const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press max: 1, // Maximum of 1 button press
@@ -632,8 +470,6 @@ export class SusCommand extends Subcommand {
embeds: [], embeds: [],
}); });
} }
await this.deleteAllNotesLogger(user, mod, guild);
}); });
// Remove the buttons after they have been clicked // Remove the buttons after they have been clicked
@@ -647,36 +483,46 @@ export class SusCommand extends Subcommand {
await member.roles.remove(IDs.roles.restrictions.sus); await member.roles.remove(IDs.roles.restrictions.sus);
} }
// Logs removal of 1 sus note // Non Application Command method of adding a sus note
private async deleteAllNotesLogger(user: User, mod: User, guild: Guild) { // xlevra begged me to add this... so I guess here it is
// Log the sus note public async addMessage(message: Message, args: Args) {
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as // Get arguments
| TextChannel let user: GuildMember;
| undefined; try {
user = await args.pick('member');
if (logChannel === undefined) { } catch {
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as await message.react('❌');
| TextChannel await message.reply('User was not provided!');
| undefined;
if (logChannel === undefined) {
this.container.logger.error('Sus Error: Could not fetch log channel');
return; return;
} }
const note = args.finished ? null : await args.rest('string');
const mod = message.member;
if (note === null) {
await message.react('❌');
await message.reply('No sus note was provided!');
return;
} }
const embed = new EmbedBuilder() if (mod === null) {
.setColor('#28A745') await message.react('❌');
.setAuthor({ await message.reply(
name: `Purged all sus notes for ${user.tag}`, 'Moderator not found! Try again or contact a developer!',
iconURL: `${user.displayAvatarURL()}`, );
}) return;
.addFields( }
{ name: 'User', value: `${user}`, inline: true },
{ name: 'Moderator', value: `${mod}`, inline: true },
)
.setTimestamp()
.setFooter({ text: `ID: ${user.id}` });
await logChannel.send({ embeds: [embed] }); // Check if user and mod are on the database
await addExistingUser(user);
await addExistingUser(mod);
await addToDatabase(user.id, mod.id, note);
// Give the user the sus role they don't already have the sus note
if (!user.roles.cache.has(IDs.roles.restrictions.sus)) {
await user.roles.add(IDs.roles.restrictions.sus);
}
await message.react('✅');
} }
} }

View File

@@ -19,11 +19,7 @@
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { GuildMember, Message } from 'discord.js'; import type { GuildMember, Message } from 'discord.js';
import { import { addMute, removeMute, checkActive } from '#utils/database/vcMute';
addMute,
removeMute,
checkActive,
} from '#utils/database/moderation/vcMute';
import { addExistingUser } from '#utils/database/dbExistingUser'; import { addExistingUser } from '#utils/database/dbExistingUser';
export class VCMuteCommand extends Command { 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 { EmbedBuilder, TextChannel } from 'discord.js';
import type { Message, Guild, User } from 'discord.js'; import type { Message, Guild, User } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { import { deleteWarning, fetchWarning } from '#utils/database/warnings';
deleteWarning,
fetchWarning,
} from '#utils/database/moderation/warnings';
import { checkStaff } from '#utils/checker'; import { checkStaff } from '#utils/checker';
export class DeleteWarningCommand extends Command { export class DeleteWarningCommand extends Command {

View File

@@ -25,7 +25,7 @@ import {
} from '@sapphire/framework'; } from '@sapphire/framework';
import type { User, Message, Snowflake, Guild, TextChannel } from 'discord.js'; import type { User, Message, Snowflake, Guild, TextChannel } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; 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 { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids'; 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 { ChannelType, EmbedBuilder } from 'discord.js';
import type { Message, Guild, User } from 'discord.js'; import type { Message, Guild, User } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { fetchWarnings } from '#utils/database/moderation/warnings'; import { fetchWarnings } from '#utils/database/warnings';
import { checkStaff } from '#utils/checker'; import { checkStaff } from '#utils/checker';
import { createWarningsEmbed } from '#utils/embeds'; import { createWarningsEmbed } from '#utils/embeds';

View File

@@ -19,7 +19,7 @@
import { Subcommand } from '@sapphire/plugin-subcommands'; import { Subcommand } from '@sapphire/plugin-subcommands';
import { RegisterBehavior } from '@sapphire/framework'; import { RegisterBehavior } from '@sapphire/framework';
import { ChannelType, PermissionsBitField, Snowflake } from 'discord.js'; import type { Snowflake } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; import { updateUser } from '#utils/database/dbExistingUser';
import { import {
addStatUser, addStatUser,
@@ -66,6 +66,7 @@ export class OutreachCommand extends Subcommand {
], ],
}, },
], ],
preconditions: ['ModOnly'],
}); });
} }
@@ -199,15 +200,15 @@ export class OutreachCommand extends Subcommand {
if (mod === undefined) { if (mod === undefined) {
await interaction.reply({ await interaction.reply({
content: 'Outreach Leader was not found!', content: 'Mod was not found!',
ephemeral: true, ephemeral: true,
}); });
return; return;
} }
if (!mod.roles.cache.has(IDs.roles.staff.outreachLeader)) { if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) {
await interaction.reply({ await interaction.reply({
content: 'You need to be an Outreach Leader to run this command!', content: 'You need to be an Outreach Coordinator to run this command!',
ephemeral: true, ephemeral: true,
}); });
return; return;
@@ -253,9 +254,9 @@ export class OutreachCommand extends Subcommand {
return; return;
} }
if (!mod.roles.cache.has(IDs.roles.staff.outreachLeader)) { if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) {
await interaction.reply({ await interaction.reply({
content: 'You need to be an Outreach Leader to run this command!', content: 'You need to be an Outreach Coordinator to run this command!',
ephemeral: true, ephemeral: true,
}); });
return; return;
@@ -274,8 +275,7 @@ export class OutreachCommand extends Subcommand {
stat.forEach(({ role }) => { stat.forEach(({ role }) => {
if (role !== null) { if (role !== null) {
guild.roles.delete(role.roleId); // Delete role guild.roles.delete(role.roleId);
guild.channels.delete(role.channelId); // Delete VC
} }
}); });
@@ -388,66 +388,14 @@ export class OutreachCommand extends Subcommand {
await updateUser(leaderMember); await updateUser(leaderMember);
// Create role for group
const role = await guild.roles.create({ const role = await guild.roles.create({
name: `Outreach Group ${groupNo}`, name: `Outreach Group ${groupNo}`,
mentionable: true,
}); });
// Create a voice channel for group await createStat(event.id, leader.id, role.id);
const channel = await guild.channels.create({
name: `Outreach Group ${groupNo}`,
type: ChannelType.GuildVoice,
parent: IDs.categories.activism,
permissionOverwrites: [
{
id: guild.roles.everyone,
deny: [
PermissionsBitField.Flags.SendMessages,
PermissionsBitField.Flags.Connect,
PermissionsBitField.Flags.ViewChannel,
],
},
{
id: IDs.roles.vegan.activist,
allow: [PermissionsBitField.Flags.ViewChannel],
},
{
id: role.id, // Permissions for the specific group
allow: [
PermissionsBitField.Flags.SendMessages,
PermissionsBitField.Flags.Connect,
],
},
{
id: IDs.roles.staff.outreachLeader,
allow: [
PermissionsBitField.Flags.SendMessages,
PermissionsBitField.Flags.Connect,
],
},
],
});
// Create stats in database
await createStat(event.id, leader.id, role.id, channel.id);
// Give group leader role
await leaderMember.roles.add(role); await leaderMember.roles.add(role);
// Send message in VC with a welcome and reminder
await channel.send(
`Welcome ${role}, ${leaderMember} is going to be the leader of your group!\n\n` +
'Remember to keep track of stats during activism with `/outreach group update` and' +
'to have these questions in mind whilst doing activism:\n' +
'- How many said would go vegan?\n' +
'- How many seriously considered being vegan?\n' +
'- How many people had anti-vegan viewpoints?\n' +
'- How many thanked you for the conversation?\n' +
'- How many said they would watch a vegan documentary?\n' +
'- How many got educated on veganism or the animal industry?',
);
await interaction.editReply({ await interaction.editReply({
content: `Created a group with the leader being ${leader}`, content: `Created a group with the leader being ${leader}`,
}); });
@@ -494,7 +442,7 @@ export class OutreachCommand extends Subcommand {
if ( if (
leader.id !== stat.stat.leaderId && leader.id !== stat.stat.leaderId &&
!leaderMember.roles.cache.has(IDs.roles.staff.outreachLeader) !leaderMember.roles.cache.has(IDs.roles.staff.outreachCoordinator)
) { ) {
await interaction.editReply({ await interaction.editReply({
content: `You are not the leader for ${group}`, content: `You are not the leader for ${group}`,

View File

@@ -29,7 +29,7 @@ export class PlusCommand extends Command {
name: 'plus', name: 'plus',
aliases: ['+'], aliases: ['+'],
description: 'Give/remove the plus role', description: 'Give/remove the plus role',
preconditions: [['CoordinatorOnly', 'VerifierOnly', 'ModOnly']], preconditions: [['CoordinatorOnly', 'ModOnly']],
}); });
} }
@@ -138,14 +138,6 @@ export class PlusCommand extends Command {
info.success = true; info.success = true;
return info; return info;
} }
// Checks if the user is vegan before giving the plus role
// If not, stop from giving the plus role
if (!member.roles.cache.has(IDs.roles.vegan.vegan)) {
info.message = `Can't give ${user} the vegan role as they are not vegan!`;
return info;
}
// Add Plus role to the user // Add Plus role to the user
await member.roles.add(plus); await member.roles.add(plus);
await roleAddLog(user.id, mod.id, plus); await roleAddLog(user.id, mod.id, plus);

View File

@@ -146,8 +146,8 @@ export class TrustedCommand extends Command {
.send( .send(
`You have been given the ${trusted.name} role by ${mod}!` + `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.' + '\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**.' + "\nMake sure that you follow the rules, and don't post anything NSFW, anything objectifying animals and follow Discord's ToS." +
`\n\nNot following these rules will result in the **immediate removal** of the ${trusted.name} role.`, `\nNot following these rules can result in the removal of the ${trusted.name} role.`,
) )
.catch(() => {}); .catch(() => {});
info.success = true; info.success = true;

View File

@@ -18,7 +18,6 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import IDs from '#utils/ids';
export class InfoCommand extends Command { export class InfoCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
@@ -95,9 +94,7 @@ export class InfoCommand extends Command {
message = message =
"If you want to have the vegan or activist role, you'll need to do a voice verification. " + "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." + "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\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.';
break; break;
case 'modMail': case 'modMail':
message = message =

View File

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

View File

@@ -20,7 +20,7 @@
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js'; import type { User, Guild, Message } from 'discord.js';
import { EmbedBuilder } 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 { export class RankCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {

View File

@@ -25,11 +25,8 @@ import { LogLevel, SapphireClient, container } from '@sapphire/framework';
import '@sapphire/plugin-scheduled-tasks/register'; import '@sapphire/plugin-scheduled-tasks/register';
import '@sapphire/plugin-logger/register'; import '@sapphire/plugin-logger/register';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { Redis } from 'ioredis'; import { createClient } from 'redis';
import type { RedisClientType } from 'redis';
const REDIS_PORT = process.env.REDIS_PORT
? parseInt(process.env.REDIS_PORT)
: undefined;
// Setting up the Sapphire client // Setting up the Sapphire client
const client = new SapphireClient({ const client = new SapphireClient({
@@ -53,10 +50,7 @@ const client = new SapphireClient({
tasks: { tasks: {
bull: { bull: {
connection: { connection: {
host: process.env.REDIS_HOST, host: process.env.BULLMQ_URL,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
}, },
}, },
}, },
@@ -69,14 +63,11 @@ const main = async () => {
client.logger.info('Logging in'); client.logger.info('Logging in');
// Create databases // Create databases
container.database = new PrismaClient(); container.database = await new PrismaClient();
container.redis = new Redis({ container.redis = createClient({
host: process.env.REDIS_HOST, url: process.env.REDIS_URL,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
db: 1,
}); });
await container.redis.connect();
// Log the bot in to Discord // Log the bot in to Discord
await client.login(token); await client.login(token);
@@ -92,7 +83,7 @@ const main = async () => {
declare module '@sapphire/pieces' { declare module '@sapphire/pieces' {
interface Container { interface Container {
database: PrismaClient; database: PrismaClient;
redis: Redis; redis: RedisClientType;
} }
} }

View File

@@ -21,14 +21,8 @@ import {
InteractionHandler, InteractionHandler,
InteractionHandlerTypes, InteractionHandlerTypes,
} from '@sapphire/framework'; } from '@sapphire/framework';
import { import type { ButtonInteraction, GuildMember, TextChannel } from 'discord.js';
ButtonInteraction,
GuildMember,
MessageFlagsBitField,
TextChannel,
} from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { checkActive } from '#utils/database/moderation/restriction';
export class WelcomeButtonHandler extends InteractionHandler { export class WelcomeButtonHandler extends InteractionHandler {
public constructor( public constructor(
@@ -60,7 +54,7 @@ export class WelcomeButtonHandler extends InteractionHandler {
await interaction.reply({ await interaction.reply({
content: content:
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.', 'There was an error giving you the role, please try again later or contact ModMail to be let into this server.',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
}); });
return; return;
} }
@@ -68,25 +62,14 @@ export class WelcomeButtonHandler extends InteractionHandler {
try { try {
member = member as GuildMember; member = member as GuildMember;
// 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 // Give non-vegan role
if (!member.voice.channel) { if (!member.voice.channel) {
await member.roles.add(IDs.roles.nonvegan.nonvegan); await member.roles.add(IDs.roles.nonvegan.nonvegan);
await general.send( await general.send(
`${member} Welcome to ARA! :D Please check <#${IDs.channels.information.roles}> ` + `${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.` + `and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussion 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}>, ` + "\n\nIf you would like to be verified as a vegan, join the 'Verification' voice channel.",
'to be verified and gain access to more channels.',
); );
return; return;
} }
@@ -94,13 +77,13 @@ export class WelcomeButtonHandler extends InteractionHandler {
await interaction.reply({ await interaction.reply({
content: 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.", "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,
}); });
} catch (error) { } catch (error) {
await interaction.reply({ await interaction.reply({
content: content:
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.', 'There was an error giving you the role, please try again later or contact ModMail to be let into this server.',
flags: MessageFlagsBitField.Flags.Ephemeral, ephemeral: true,
}); });
} }
} }

View File

@@ -20,7 +20,7 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { GuildBan } from 'discord.js'; import type { GuildBan } from 'discord.js';
import { AuditLogEvent, EmbedBuilder, TextChannel } 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 IDs from '#utils/ids';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser'; import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';

View File

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

View File

@@ -20,7 +20,7 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { GuildBan } from 'discord.js'; import type { GuildBan } from 'discord.js';
import { AuditLogEvent, EmbedBuilder, TextChannel } 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 IDs from '#utils/ids';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser'; import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';

View File

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

38
src/listeners/error.ts Normal file
View File

@@ -0,0 +1,38 @@
// 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, ListenerErrorPayload } from '@sapphire/framework';
export class ErrorListener extends Listener {
public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, {
...options,
event: 'listenerError',
});
}
public run(error: Error, payload: ListenerErrorPayload) {
this.container.logger.debug(
`TEST ERROR: ${error.stack}\n\nPAYLOAD: ${payload.piece.name}`,
);
}
}

View File

@@ -1,195 +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: true,
});
}
public async run(client: Client) {
this.container.logger.info(
'FixRolesOnReady: Preparation before starting to fix the roles for nonvegans...',
);
// 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('1329152627312824320')
.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) {
// 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 % 50 === 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 is vegan
if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
count++;
continue;
}
// 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.restrictedVegan)
) {
count++;
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);
} else {
count++;
continue;
}
}
// Give the roles to the member
if (roles.length > 0) {
await member.roles.add(roles);
}
// Log the completion
count += 1;
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(500);
}
// 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 type { GuildChannel, EmbedBuilder } from 'discord.js';
import { setTimeout } from 'timers/promises'; import { setTimeout } from 'timers/promises';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { import { checkActive, getRestrictions } from '#utils/database/restriction';
checkActive, import { findNotes } from '#utils/database/sus';
getRestrictions,
} from '#utils/database/moderation/restriction';
import { findNotes } from '#utils/database/moderation/sus';
import { import {
createRestrictLogEmbed, createRestrictLogEmbed,
createSusLogEmbed, createSusLogEmbed,
createWarningsEmbed, createWarningsEmbed,
} from '#utils/embeds'; } from '#utils/embeds';
import { fetchWarnings } from '#utils/database/moderation/warnings'; import { fetchWarnings } from '#utils/database/warnings';
export class ModMailCreateListener extends Listener { export class ModMailCreateListener extends Listener {
public constructor( public constructor(

View File

@@ -22,7 +22,6 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { Client } from 'discord.js'; import type { Client } from 'discord.js';
import IDs from '#utils/ids';
export class ReadyListener extends Listener { export class ReadyListener extends Listener {
public constructor( 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!; const { username, id } = client.user!;
this.container.logger.info(`Successfully logged in as ${username} (${id})`); 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 { ChannelType } from 'discord.js';
import { fetchRoles, getLeaveRoles } from '#utils/database/dbExistingUser'; import { fetchRoles, getLeaveRoles } from '#utils/database/dbExistingUser';
import { blockTime } from '#utils/database/verification'; import { blockTime } from '#utils/database/verification';
import { import { checkActive, getSection } from '#utils/database/restriction';
checkActive,
getSection,
} from '#utils/database/moderation/restriction';
import { blockedRoles, blockedRolesAfterRestricted } from '#utils/blockedRoles'; import { blockedRoles, blockedRolesAfterRestricted } from '#utils/blockedRoles';
import IDs from '#utils/ids'; import IDs from '#utils/ids';

View File

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

View File

@@ -19,7 +19,7 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { VoiceState } from 'discord.js'; 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 { export class VCMuteListener extends Listener {
public constructor( public constructor(

View File

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

View File

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

View File

@@ -80,10 +80,7 @@ export class VerificationReady extends Listener {
const textChannel = c as TextChannel; const textChannel = c as TextChannel;
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
emptyVC.forEach((snowflake) => { emptyVC.forEach((snowflake) => {
if ( if (textChannel.topic!.includes(snowflake)) {
textChannel.topic !== null &&
textChannel.topic.includes(snowflake)
) {
textChannel.delete(); 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 { Listener } from '@sapphire/framework';
import type { Message } from 'discord.js'; 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'; import { randint } from '#utils/maths';
export class XpListener extends Listener { export class XpListener extends Listener {
@@ -46,12 +46,6 @@ export class XpListener extends Listener {
const xp = randint(15, 25); const xp = randint(15, 25);
const level = await addXp(user.id, xp); 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);
}
} }
} }

View File

@@ -26,33 +26,34 @@ import type {
} from 'discord.js'; } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
export class OutreachLeaderOnlyPrecondition extends AllFlowsPrecondition { export class PatreonOnlyPrecondition extends AllFlowsPrecondition {
public override async messageRun(message: Message) { public override async messageRun(message: Message) {
// for message command // for message command
return this.checkCoordinator(message.member!); return this.checkPatreon(message.member!);
} }
public override async chatInputRun(interaction: CommandInteraction) { public override async chatInputRun(interaction: CommandInteraction) {
// for slash command // for slash command
return this.checkCoordinator(interaction.member! as GuildMember); return this.checkPatreon(interaction.member! as GuildMember);
} }
public override async contextMenuRun( public override async contextMenuRun(
interaction: ContextMenuCommandInteraction, interaction: ContextMenuCommandInteraction,
) { ) {
// for context menu command // for context menu command
return this.checkCoordinator(interaction.member! as GuildMember); return this.checkPatreon(interaction.member! as GuildMember);
} }
private async checkCoordinator(user: GuildMember) { private async checkPatreon(user: GuildMember) {
return user.roles.cache.has(IDs.roles.staff.outreachLeader) return user.roles.cache.has(IDs.roles.patron) ||
user.roles.cache.has(IDs.roles.patreon)
? this.ok() ? this.ok()
: this.error({ message: 'Only outreach leaders can run this command!' }); : this.error({ message: 'Only Patreon members can run this command!' });
} }
} }
declare module '@sapphire/framework' { declare module '@sapphire/framework' {
interface Preconditions { interface Preconditions {
OutreachLeaderOnly: never; PatreonOnly: never;
} }
} }

View File

@@ -52,9 +52,7 @@ export class VerifyReminder extends ScheduledTask {
await channel.send( await channel.send(
"If you want to have the vegan or activist role, you'll need to do a voice verification. " + "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." + "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\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.',
); );
// Reset the total message counter to 0 // Reset the total message counter to 0

View File

@@ -19,12 +19,9 @@
import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks'; import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { EmbedBuilder } from 'discord.js'; import { TextChannel, EmbedBuilder } from 'discord.js';
import { checkBan } from '#utils/database/moderation/ban'; import { checkBan } from '#utils/database/ban';
import { import { checkTempBan, removeTempBan } from '#utils/database/tempBan';
checkTempBan,
removeTempBan,
} from '#utils/database/moderation/tempBan';
export class TempBan extends ScheduledTask { export class TempBan extends ScheduledTask {
public constructor( public constructor(
@@ -39,9 +36,7 @@ export class TempBan extends ScheduledTask {
// Get the guild where the user is in // Get the guild where the user is in
let guild = this.container.client.guilds.cache.get(payload.guildId); let guild = this.container.client.guilds.cache.get(payload.guildId);
if (guild === undefined) { if (guild === undefined) {
guild = await this.container.client.guilds guild = await this.container.client.guilds.fetch(payload.guildId);
.fetch(payload.guildId)
.catch(() => undefined);
if (guild === undefined) { if (guild === undefined) {
this.container.logger.error('Temp Unban Task: Guild not found!'); this.container.logger.error('Temp Unban Task: Guild not found!');
return; return;
@@ -53,7 +48,7 @@ export class TempBan extends ScheduledTask {
let user = guild.client.users.cache.get(userId); let user = guild.client.users.cache.get(userId);
if (user === undefined) { if (user === undefined) {
user = await guild.client.users.fetch(userId).catch(() => undefined); user = await guild.client.users.fetch(userId);
if (user === undefined) { if (user === undefined) {
this.container.logger.error( this.container.logger.error(
'Temp Unban Task: Could not fetch banned user!', 'Temp Unban Task: Could not fetch banned user!',
@@ -75,27 +70,20 @@ export class TempBan extends ScheduledTask {
await removeTempBan(userId); await removeTempBan(userId);
// Log unban // Log unban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted); let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as
| TextChannel
| undefined;
if (logChannel === undefined) { if (logChannel === undefined) {
const logChannelFetch = await guild.channels logChannel = (await guild.channels.fetch(
.fetch(IDs.channels.logs.restricted) IDs.channels.logs.restricted,
.catch(() => null); )) as TextChannel | undefined;
if (logChannelFetch === null) { if (logChannel === undefined) {
this.container.logger.error( this.container.logger.error(
`Temp Ban Listener: Could not fetch log channel. User Snowflake: ${userId}`, `Temp Ban Listener: Could not fetch log channel. User Snowflake: ${userId}`,
); );
return; return;
} }
logChannel = logChannelFetch;
}
if (!logChannel.isTextBased()) {
this.container.logger.error(
'Temp Ban Listener: Log channel is not a text based channel!',
);
return;
} }
const log = new EmbedBuilder() const log = new EmbedBuilder()

View File

@@ -17,8 +17,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type { VoiceChannel } from 'discord.js';
import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks'; import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks';
import { ChannelType } from 'discord.js';
export class VerifyTimeout extends ScheduledTask { export class VerifyTimeout extends ScheduledTask {
public constructor( public constructor(
@@ -30,24 +30,17 @@ export class VerifyTimeout extends ScheduledTask {
public async run(payload: { channelId: string; userId: string }) { public async run(payload: { channelId: string; userId: string }) {
// Get the guild where the user is in // Get the guild where the user is in
let channel = this.container.client.channels.cache.get(payload.channelId); let channel = this.container.client.channels.cache.get(
payload.channelId,
) as VoiceChannel | undefined;
if (channel === undefined) {
channel = (await this.container.client.channels.fetch(
payload.channelId,
)) as VoiceChannel | undefined;
if (channel === undefined) { if (channel === undefined) {
const channelFetch = await this.container.client.channels
.fetch(payload.channelId)
.catch(() => null);
if (channelFetch === null) {
this.container.logger.error('verifyTimeout: Channel not found!'); this.container.logger.error('verifyTimeout: Channel not found!');
return; return;
} }
channel = channelFetch;
}
if (channel.type !== ChannelType.GuildVoice) {
this.container.logger.error(
'verifyTimeout: Channel is not a voice channel!',
);
return;
} }
if (channel.members.size < 2 && channel.members.has(payload.userId)) { if (channel.members.size < 2 && channel.members.has(payload.userId)) {

View File

@@ -32,9 +32,7 @@ export class VerifyUnblock extends ScheduledTask {
// Get the guild where the user is in // Get the guild where the user is in
let guild = this.container.client.guilds.cache.get(payload.guildId); let guild = this.container.client.guilds.cache.get(payload.guildId);
if (guild === undefined) { if (guild === undefined) {
guild = await this.container.client.guilds guild = await this.container.client.guilds.fetch(payload.guildId);
.fetch(payload.guildId)
.catch(() => undefined);
if (guild === undefined) { if (guild === undefined) {
this.container.logger.error('verifyUnblock: Guild not found!'); this.container.logger.error('verifyUnblock: Guild not found!');
return; return;
@@ -44,7 +42,7 @@ export class VerifyUnblock extends ScheduledTask {
// Find GuildMember for the user // Find GuildMember for the user
let user = guild.members.cache.get(payload.userId); let user = guild.members.cache.get(payload.userId);
if (user === undefined) { if (user === undefined) {
user = await guild.members.fetch(payload.userId).catch(() => undefined); user = await guild.members.fetch(payload.userId).catch(undefined);
if (user === undefined) { if (user === undefined) {
this.container.logger.error('verifyUnblock: GuildMember not found!'); this.container.logger.error('verifyUnblock: GuildMember not found!');
return; return;

View File

@@ -29,7 +29,6 @@ export const blockedRoles = [
IDs.roles.staff.trialVerifier, IDs.roles.staff.trialVerifier,
IDs.roles.staff.mentor, IDs.roles.staff.mentor,
IDs.roles.stageHost, IDs.roles.stageHost,
IDs.roles.booster,
]; ];
export const blockedRolesAfterRestricted = [ export const blockedRolesAfterRestricted = [

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

@@ -85,7 +85,6 @@ export async function createStat(
eventId: number, eventId: number,
leaderId: Snowflake, leaderId: Snowflake,
roleId: Snowflake, roleId: Snowflake,
channelId: Snowflake,
) { ) {
await container.database.stat.create({ await container.database.stat.create({
data: { data: {
@@ -111,7 +110,6 @@ export async function createStat(
role: { role: {
create: { create: {
roleId, roleId,
channelId,
}, },
}, },
}, },

View File

@@ -114,19 +114,6 @@ export async function getSection(userId: Snowflake) {
return restriction.section; 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 // This is only for restrictions created with the old bot
export async function unRestrictLegacy( export async function unRestrictLegacy(
userId: Snowflake, userId: Snowflake,

View File

@@ -1,7 +1,7 @@
import { container } from '@sapphire/framework'; import { container } from '@sapphire/framework';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
export async function addSusNoteDB( export async function addToDatabase(
userId: string, userId: string,
modId: string, modId: string,
message: string, message: string,
@@ -10,24 +10,14 @@ export async function addSusNoteDB(
await container.database.sus.create({ await container.database.sus.create({
data: { data: {
user: { user: {
connectOrCreate: { connect: {
where: {
id: userId, id: userId,
}, },
create: {
id: userId,
},
},
}, },
mod: { mod: {
connectOrCreate: { connect: {
where: {
id: modId, id: modId,
}, },
create: {
id: modId,
},
},
}, },
note: message, note: message,
}, },

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: { where: {
userId, userId,
}, },
@@ -63,12 +63,6 @@ export async function addXp(userId: Snowflake, xp: number) {
xpForNextLevel: xp, xpForNextLevel: xp,
}, },
}); });
if (level === 1) {
return info.level;
} else {
return null;
}
} }
export async function checkCanAddXp(userId: Snowflake) { export async function checkCanAddXp(userId: Snowflake) {

View File

@@ -18,10 +18,8 @@
*/ */
const devIDs = { const devIDs = {
guild: '999431674972618792',
roles: { roles: {
trusted: '999431675081666599', trusted: '999431675081666599',
booster: '',
nonvegan: { nonvegan: {
nonvegan: '999431675081666598', nonvegan: '999431675081666598',
vegCurious: '999431675098447932', vegCurious: '999431675098447932',
@@ -42,7 +40,6 @@ const devIDs = {
restricted2: '999431674997788676', restricted2: '999431674997788676',
restricted3: '999431674997788675', restricted3: '999431674997788675',
restricted4: '999431674997788674', restricted4: '999431674997788674',
restrictedVegan: '1075952207091994726',
restricted: [ restricted: [
'999431674997788677', // Restricted 1 '999431674997788677', // Restricted 1
'999431674997788676', // Restricted 2 '999431674997788676', // Restricted 2
@@ -60,9 +57,6 @@ const devIDs = {
verifierCoordinator: '999431675140382810', verifierCoordinator: '999431675140382810',
eventCoordinator: '999431675165556817', eventCoordinator: '999431675165556817',
outreachCoordinator: '999431675140382807', outreachCoordinator: '999431675140382807',
mediaCoordinator: '1204801056404676618',
hrCoordinator: '1204795893480431657',
outreachLeader: '999431675123597409',
restricted: '999431675123597407', restricted: '999431675123597407',
moderator: '999431675123597408', moderator: '999431675123597408',
trialModerator: '999431675123597404', trialModerator: '999431675123597404',
@@ -102,7 +96,6 @@ const devIDs = {
}, },
nonVegan: { nonVegan: {
general: '999431677325615189', general: '999431677325615189',
vcText: '999431677535338567',
}, },
vegan: { vegan: {
general: '999431677535338575', general: '999431677535338575',
@@ -128,7 +121,6 @@ const devIDs = {
}, },
logs: { logs: {
restricted: '999431681217937513', restricted: '999431681217937513',
bot: '999431681217937516',
economy: '999431681599623198', economy: '999431681599623198',
sus: '999431681599623199', sus: '999431681599623199',
}, },
@@ -137,12 +129,10 @@ const devIDs = {
staff: '999431676058927253', staff: '999431676058927253',
modMail: '1095453371411996762', modMail: '1095453371411996762',
verification: '999431677006860409', verification: '999431677006860409',
activism: '999431677795389549',
diversity: '999431679053660185', diversity: '999431679053660185',
private: '999431679527628818', private: '999431679527628818',
restricted: '999431679812845654', restricted: '999431679812845654',
}, },
modMail: '575252669443211264',
}; };
export default devIDs; export default devIDs;

View File

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

View File

@@ -0,0 +1,18 @@
// 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/>.
*/

View File

@@ -20,36 +20,33 @@
import devIDs from '#utils/devIDs'; import devIDs from '#utils/devIDs';
let IDs = { let IDs = {
guild: '730907954345279591',
roles: { roles: {
trusted: '1329089675977035879', trusted: '731563158011117590',
booster: '731213264540795012',
nonvegan: { nonvegan: {
nonvegan: '1329093962153332848', nonvegan: '774763753308815400',
vegCurious: '1329107984227369020', vegCurious: '832656046572961803',
convinced: '797132019166871612', convinced: '797132019166871612',
}, },
vegan: { vegan: {
vegan: '788114978020392982', vegan: '788114978020392982',
activist: '1329112833115295815', activist: '730915638746546257',
nvAccess: '1076857105648209971', nvAccess: '1076857105648209971',
plus: '798682625619132428', plus: '798682625619132428',
araVegan: '995394977658044506', araVegan: '995394977658044506',
}, },
restrictions: { restrictions: {
sus: '1329125130949103626', sus: '859145930640457729',
muted: '730924813681688596', muted: '730924813681688596',
softMute: '775934741139554335', softMute: '775934741139554335',
restricted1: '809769217477050369', restricted1: '809769217477050369',
restricted2: '872482843304001566', restricted2: '872482843304001566',
restricted3: '1329126085207789658', restricted3: '856582673258774538',
restricted4: '1329126181164945499', restricted4: '872472182888992858',
restrictedVegan: '1075951477379567646',
restricted: [ restricted: [
'809769217477050369', // Restricted 1 '809769217477050369', // Restricted 1
'872482843304001566', // Restricted 2 '872482843304001566', // Restricted 2
'1329126085207789658', // Restricted 3 '856582673258774538', // Restricted 3
'1329126181164945499', // Restricted 4 '872472182888992858', // Restricted 4
'1075951477379567646', // Restricted Vegan '1075951477379567646', // Restricted Vegan
], ],
}, },
@@ -62,9 +59,6 @@ let IDs = {
verifierCoordinator: '940721280376778822', verifierCoordinator: '940721280376778822',
eventCoordinator: '944732860554817586', eventCoordinator: '944732860554817586',
outreachCoordinator: '954804769476730890', outreachCoordinator: '954804769476730890',
mediaCoordinator: '1203778509449723914',
hrCoordinator: '1203802120180989993',
outreachLeader: '730915698544607232',
restricted: '851624392928264222', restricted: '851624392928264222',
moderator: '826157475815489598', moderator: '826157475815489598',
trialModerator: '982074555596152904', trialModerator: '982074555596152904',
@@ -77,7 +71,7 @@ let IDs = {
stageHost: '854893757593419786', stageHost: '854893757593419786',
patron: '765370219207852055', patron: '765370219207852055',
patreon: '993848684640997406', patreon: '993848684640997406',
verifyBlock: '1329107805130461247', verifyBlock: '1032765019269640203',
bookClub: '955516408249352212', bookClub: '955516408249352212',
debateHost: '935508325615931443', debateHost: '935508325615931443',
gameNightHost: '952779915701415966', gameNightHost: '952779915701415966',
@@ -104,7 +98,6 @@ let IDs = {
}, },
nonVegan: { nonVegan: {
general: '798967615636504657', general: '798967615636504657',
vcText: '808191982169096232',
}, },
vegan: { vegan: {
general: '787738272616808509', general: '787738272616808509',
@@ -130,7 +123,6 @@ let IDs = {
}, },
logs: { logs: {
restricted: '920993034462715925', restricted: '920993034462715925',
bot: '872126272015314966',
economy: '932050015034159174', economy: '932050015034159174',
sus: '872884989950324826', sus: '872884989950324826',
}, },
@@ -139,12 +131,10 @@ let IDs = {
staff: '768685283583328257', staff: '768685283583328257',
modMail: '867077297664426006', modMail: '867077297664426006',
verification: '797505409073676299', verification: '797505409073676299',
activism: '873918877019545640',
diversity: '933078380394459146', diversity: '933078380394459146',
private: '992581296901599302', private: '992581296901599302',
restricted: '809765577236283472', restricted: '809765577236283472',
}, },
modMail: '575252669443211264',
}; };
// Check if the bot is in development mode // Check if the bot is in development mode

View File

@@ -31,7 +31,7 @@
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "src" /* Specify the base directory to resolve non-relative module names. */, "baseUrl": "src" /* Specify the base directory to resolve non-relative module names. */,
"paths": { "paths": {
"#utils/*": ["./utils/*"], "#utils/*": ["./utils/*"]
} /* Specify a set of entries that re-map imports to additional lookup locations. */, } /* Specify a set of entries that re-map imports to additional lookup locations. */,
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
@@ -79,7 +79,7 @@
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */ /* Type Checking */
"strict": true /* Enable all strict type-checking options. */, "strict": true /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
@@ -103,5 +103,5 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
// "skipLibCheck": true /* Skip type checking all .d.ts files. */ // "skipLibCheck": true /* Skip type checking all .d.ts files. */
}, },
"include": ["src"], "include": ["src"]
} }