mirror of
https://github.com/veganhacktivists/arabot.git
synced 2025-12-02 10:50:02 +01:00
Compare commits
45 Commits
feat/log-e
...
feat/new-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cf7998cd9 | ||
|
|
63a4d651af | ||
|
|
a2dba859f2 | ||
|
|
a7b772f77a | ||
|
|
1fa87b8a4a | ||
|
|
4e99a5456f | ||
|
|
62d941dfcb | ||
|
|
e6b1463a1a | ||
|
|
5793bbb461 | ||
|
|
cf8142b86a | ||
|
|
502d5c5cdf | ||
|
|
8f071b8043 | ||
|
|
36ce086532 | ||
|
|
617834833a | ||
|
|
4d92250500 | ||
|
|
f898dada56 | ||
|
|
9e2f2c7558 | ||
|
|
e15e5da5aa | ||
|
|
e89f056b94 | ||
|
|
6acd012e7a | ||
|
|
75174f711d | ||
|
|
5e69ea6126 | ||
|
|
3d8aba5577 | ||
|
|
e7839552f8 | ||
|
|
3b0666e80d | ||
|
|
3d8a9be7f2 | ||
|
|
a7f608c1f0 | ||
|
|
cb457136d4 | ||
|
|
7e984c4857 | ||
|
|
0ea9ea3f64 | ||
|
|
19e70ebdbc | ||
|
|
5409be3d75 | ||
|
|
edd3caf9c0 | ||
|
|
5a87f97a74 | ||
|
|
feab05c1ea | ||
|
|
9b505fbece | ||
|
|
a5ba493372 | ||
|
|
98e514b5e9 | ||
|
|
172508c741 | ||
|
|
730f3e6a28 | ||
|
|
fbc2944b92 | ||
|
|
6172dc6ac6 | ||
|
|
b762ae3bc8 | ||
|
|
c8eb8299dd | ||
|
|
785e844da8 |
@@ -1,6 +1,6 @@
|
||||
.idea
|
||||
dist
|
||||
node_modules
|
||||
tsconfig.tsbuildinfo
|
||||
npm-debug.log
|
||||
.env
|
||||
*.md
|
||||
|
||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
node-linker=hoisted
|
||||
shamefully-hoist=true
|
||||
public-hoist-pattern[]=@sapphire/*
|
||||
36
Dockerfile
36
Dockerfile
@@ -1,24 +1,26 @@
|
||||
FROM node:20
|
||||
FROM node:20 AS base
|
||||
# PNPM
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /opt/app
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
FROM base AS prod-deps
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
COPY --chown=node:node package.json .
|
||||
COPY --chown=node:node package-lock.json .
|
||||
COPY --chown=node:node tsconfig.json .
|
||||
COPY --chown=node:node prisma ./prisma/
|
||||
FROM base AS build
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
RUN pnpm exec prisma generate
|
||||
RUN pnpm run build
|
||||
|
||||
RUN npm install
|
||||
FROM base
|
||||
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
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npx prisma generate
|
||||
|
||||
RUN npm run build
|
||||
|
||||
RUN chown node:node /opt/app/
|
||||
RUN chown node:node .
|
||||
|
||||
USER node
|
||||
|
||||
CMD [ "npm", "run", "start:migrate"]
|
||||
CMD [ "pnpm", "run", "start:migrate"]
|
||||
|
||||
4556
package-lock.json
generated
4556
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
41
package.json
41
package.json
@@ -4,10 +4,11 @@
|
||||
"description": "A Discord bot for Animal Rights Advocates",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "tsc",
|
||||
"cleanBuild": "rm -rf ./dist && tsc",
|
||||
"start": "node dist/index.js",
|
||||
"start:migrate": "prisma migrate deploy && npm run start"
|
||||
"start:migrate": "prisma migrate deploy && pnpm run start"
|
||||
},
|
||||
"imports": {
|
||||
"#utils/*": "./dist/utils/*.js"
|
||||
@@ -28,31 +29,35 @@
|
||||
"url": "https://github.com/veganhacktivists/arabot/issues"
|
||||
},
|
||||
"homepage": "https://github.com/veganhacktivists/arabot#readme",
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.7.1",
|
||||
"@sapphire/discord.js-utilities": "^7.1.5",
|
||||
"@sapphire/framework": "^5.0.5",
|
||||
"@sapphire/plugin-logger": "^4.0.1",
|
||||
"@sapphire/plugin-scheduled-tasks": "^10.0.0",
|
||||
"@sapphire/plugin-subcommands": "^6.0.2",
|
||||
"@sapphire/stopwatch": "^1.5.1",
|
||||
"@sapphire/time-utilities": "^1.7.11",
|
||||
"@prisma/client": "^5.9.1",
|
||||
"@sapphire/discord.js-utilities": "^7.1.6",
|
||||
"@sapphire/framework": "^5.0.7",
|
||||
"@sapphire/plugin-logger": "^4.0.2",
|
||||
"@sapphire/plugin-scheduled-tasks": "^10.0.1",
|
||||
"@sapphire/plugin-subcommands": "^6.0.3",
|
||||
"@sapphire/stopwatch": "^1.5.2",
|
||||
"@sapphire/time-utilities": "^1.7.12",
|
||||
"@sapphire/ts-config": "^5.0.0",
|
||||
"@sapphire/utilities": "^3.15.1",
|
||||
"@types/node": "^20.10.6",
|
||||
"bullmq": "^5.1.1",
|
||||
"@sapphire/utilities": "^3.15.3",
|
||||
"@types/node": "^20.11.16",
|
||||
"bullmq": "^5.1.8",
|
||||
"discord.js": "^14.14.1",
|
||||
"redis": "^4.6.12",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"prettier": "3.1.1",
|
||||
"prisma": "^5.7.1"
|
||||
"ioredis": "^5.3.2",
|
||||
"prettier": "3.2.4",
|
||||
"prisma": "^5.9.1"
|
||||
}
|
||||
}
|
||||
|
||||
1744
pnpm-lock.yaml
generated
Normal file
1744
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -356,6 +356,12 @@ export class PrivateCommand extends Subcommand {
|
||||
} else if (user.roles.cache.has(IDs.roles.staff.eventCoordinator)) {
|
||||
name = 'event';
|
||||
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 {
|
||||
name = 'coordinator';
|
||||
id = IDs.roles.staff.coordinator;
|
||||
|
||||
@@ -28,7 +28,7 @@ export class N1984Command extends Command {
|
||||
...options,
|
||||
name: '1984',
|
||||
description: 'this is literally 1984',
|
||||
preconditions: ['CoordinatorOnly', 'ModOnly'],
|
||||
preconditions: [['CoordinatorOnly', 'ModOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ export class HappyCommand extends Command {
|
||||
...options,
|
||||
name: 'happy',
|
||||
description: 'Express your happiness',
|
||||
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ export class SadCommand extends Command {
|
||||
...options,
|
||||
name: 'sad',
|
||||
description: 'Express your sadness',
|
||||
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ export class ShrugCommand extends Command {
|
||||
...options,
|
||||
name: 'shrug',
|
||||
description: 'Ugh... whatever... idk...',
|
||||
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,16 @@ import {
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
User,
|
||||
Guild,
|
||||
TextChannel,
|
||||
GuildMember,
|
||||
Snowflake,
|
||||
} from 'discord.js';
|
||||
import type { Message, GuildMember } from 'discord.js';
|
||||
import type { Message } from 'discord.js';
|
||||
import { isMessageInstance } from '@sapphire/discord.js-utilities';
|
||||
import { addExistingUser } from '#utils/database/dbExistingUser';
|
||||
import {
|
||||
addToDatabase,
|
||||
addSusNoteDB,
|
||||
findNotes,
|
||||
getNote,
|
||||
deactivateNote,
|
||||
@@ -54,8 +58,8 @@ export class SusCommand extends Subcommand {
|
||||
{
|
||||
name: 'add',
|
||||
default: true,
|
||||
chatInputRun: 'addNote',
|
||||
messageRun: 'addMessage',
|
||||
chatInputRun: 'addNoteChatInput',
|
||||
messageRun: 'addNoteMessage',
|
||||
},
|
||||
{
|
||||
name: 'view',
|
||||
@@ -143,7 +147,9 @@ export class SusCommand extends Subcommand {
|
||||
}
|
||||
|
||||
// Subcommand to add sus note
|
||||
public async addNote(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
public async addNoteChatInput(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const note = interaction.options.getString('note', true);
|
||||
@@ -151,7 +157,7 @@ export class SusCommand extends Subcommand {
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
if (!(guild instanceof Guild)) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
@@ -159,35 +165,117 @@ export class SusCommand extends Subcommand {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the data to the database
|
||||
const info = await this.addNote(user, mod, note, guild);
|
||||
|
||||
// Check if the user exists on the database
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const modMember = guild.members.cache.get(mod.id);
|
||||
await interaction.reply({
|
||||
content: info.message,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (member === undefined || modMember === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching users!',
|
||||
ephemeral: true,
|
||||
});
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Check if user and mod are on the database
|
||||
await addExistingUser(member);
|
||||
await addExistingUser(modMember);
|
||||
const guild = message.guild;
|
||||
|
||||
await addToDatabase(user.id, mod.id, note);
|
||||
if (!(guild instanceof Guild)) {
|
||||
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,
|
||||
};
|
||||
|
||||
// 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);
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching user';
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the data to the database
|
||||
await addSusNoteDB(user.id, mod.id, note);
|
||||
|
||||
// Give the user the sus role they don't already have the sus note
|
||||
if (!member.roles.cache.has(IDs.roles.restrictions.sus)) {
|
||||
await member.roles.add(IDs.roles.restrictions.sus);
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
content: `${user} note: ${note}`,
|
||||
ephemeral: true,
|
||||
});
|
||||
info.message = `Added the sus note for ${user}: ${note}`;
|
||||
info.success = true;
|
||||
|
||||
// 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');
|
||||
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;
|
||||
}
|
||||
|
||||
public async listNote(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
@@ -233,6 +321,7 @@ export class SusCommand extends Subcommand {
|
||||
public async removeNote(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const noteId = interaction.options.getInteger('id', true);
|
||||
const mod = interaction.user;
|
||||
const { guild, channel } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
@@ -258,39 +347,40 @@ export class SusCommand extends Subcommand {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user GuildMembers for user and mod and person who ran command
|
||||
const member = await guild.members.cache.get(note.userId);
|
||||
const mod = await guild.members.cache.get(note.modId);
|
||||
const userId = note.userId;
|
||||
const modId = note.modId;
|
||||
|
||||
// TODO fix if user left the server
|
||||
if (member === undefined || mod === undefined) {
|
||||
// Get user GuildMembers for user and mod and person who ran command
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
if (!(user instanceof User)) {
|
||||
user = await guild.client.users.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
if (user === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching users from Discord!',
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user's name
|
||||
let userName = note.userId;
|
||||
if (member !== undefined) {
|
||||
userName = member.displayName;
|
||||
let modCreator = guild.client.users.cache.get(modId);
|
||||
if (!(modCreator instanceof User)) {
|
||||
modCreator = await guild.client.users.fetch(modId).catch(() => undefined);
|
||||
}
|
||||
|
||||
// Get mod name
|
||||
let modName = note.modId;
|
||||
if (mod !== undefined) {
|
||||
modName = mod.displayName;
|
||||
let modCreatorDisplay = modId;
|
||||
if (modCreator instanceof User) {
|
||||
modCreatorDisplay = modCreator.displayName;
|
||||
}
|
||||
|
||||
// Create an embed for the note
|
||||
const noteEmbed = new EmbedBuilder()
|
||||
.setColor('#ff0000')
|
||||
.setTitle(`Sus note for ${userName}`)
|
||||
.setThumbnail(member.displayAvatarURL())
|
||||
.setTitle(`Sus note for ${user.tag}`)
|
||||
.setThumbnail(user.displayAvatarURL())
|
||||
.addFields({
|
||||
name: `ID: ${noteId} | Moderator: ${modName} | Date: <t:${Math.floor(
|
||||
name: `ID: ${noteId} | Moderator: ${modCreatorDisplay} | Date: <t:${Math.floor(
|
||||
note.time.getTime() / 1000,
|
||||
)}>`,
|
||||
value: note.note,
|
||||
@@ -333,18 +423,28 @@ export class SusCommand extends Subcommand {
|
||||
if (button.customId === `delete${noteId}`) {
|
||||
await deactivateNote(noteId);
|
||||
await interaction.editReply({
|
||||
content: `${member}'s sus note (ID: ${noteId}) has been successfully removed`,
|
||||
content: `${user}'s sus note (ID: ${noteId}) has been successfully removed`,
|
||||
embeds: [],
|
||||
});
|
||||
|
||||
// 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
|
||||
const notes = await findNotes(member.id, true);
|
||||
const notes = await findNotes(userId, true);
|
||||
|
||||
// Checks if there are no notes on the user and if there's none, remove the sus role
|
||||
if (notes.length === 0) {
|
||||
await member.roles.remove(IDs.roles.restrictions.sus);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Logs the removal of the sus note
|
||||
await this.deleteNoteLogger(userId, mod, noteId, guild);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -356,11 +456,58 @@ 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 instanceof User)) {
|
||||
user = await guild.client.users.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
if (!(user instanceof User)) 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(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild, channel } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
@@ -470,6 +617,8 @@ export class SusCommand extends Subcommand {
|
||||
embeds: [],
|
||||
});
|
||||
}
|
||||
|
||||
await this.deleteAllNotesLogger(user, mod, guild);
|
||||
});
|
||||
|
||||
// Remove the buttons after they have been clicked
|
||||
@@ -483,46 +632,36 @@ export class SusCommand extends Subcommand {
|
||||
await member.roles.remove(IDs.roles.restrictions.sus);
|
||||
}
|
||||
|
||||
// Non Application Command method of adding a sus note
|
||||
// xlevra begged me to add this... so I guess here it is
|
||||
public async addMessage(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: GuildMember;
|
||||
try {
|
||||
user = await args.pick('member');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
const note = args.finished ? null : await args.rest('string');
|
||||
const mod = message.member;
|
||||
// Logs removal of 1 sus note
|
||||
private async deleteAllNotesLogger(user: User, mod: User, guild: Guild) {
|
||||
// Log the sus note
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (note === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('No sus note was provided!');
|
||||
return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Moderator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#28A745')
|
||||
.setAuthor({
|
||||
name: `Purged all sus notes for ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${user.id}` });
|
||||
|
||||
// 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('✅');
|
||||
await logChannel.send({ embeds: [embed] });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export class WelcomeButtonHandler extends InteractionHandler {
|
||||
|
||||
await general.send(
|
||||
`${member} Welcome to ARA! :D Please check <#${IDs.channels.information.roles}> ` +
|
||||
`and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussion and debates.` +
|
||||
`and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussions and debates.` +
|
||||
"\n\nIf you would like to be verified as a vegan, join the 'Verification' voice channel.",
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 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 { AllFlowsPrecondition } from '@sapphire/framework';
|
||||
import type {
|
||||
CommandInteraction,
|
||||
ContextMenuCommandInteraction,
|
||||
Message,
|
||||
GuildMember,
|
||||
} from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class PatreonOnlyPrecondition extends AllFlowsPrecondition {
|
||||
public override async messageRun(message: Message) {
|
||||
// for message command
|
||||
return this.checkPatreon(message.member!);
|
||||
}
|
||||
|
||||
public override async chatInputRun(interaction: CommandInteraction) {
|
||||
// for slash command
|
||||
return this.checkPatreon(interaction.member! as GuildMember);
|
||||
}
|
||||
|
||||
public override async contextMenuRun(
|
||||
interaction: ContextMenuCommandInteraction,
|
||||
) {
|
||||
// for context menu command
|
||||
return this.checkPatreon(interaction.member! as GuildMember);
|
||||
}
|
||||
|
||||
private async checkPatreon(user: GuildMember) {
|
||||
return user.roles.cache.has(IDs.roles.patron) ||
|
||||
user.roles.cache.has(IDs.roles.patreon)
|
||||
? this.ok()
|
||||
: this.error({ message: 'Only Patreon members can run this command!' });
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@sapphire/framework' {
|
||||
interface Preconditions {
|
||||
PatreonOnly: never;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks';
|
||||
import IDs from '#utils/ids';
|
||||
import { TextChannel, EmbedBuilder } from 'discord.js';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { checkBan } from '#utils/database/ban';
|
||||
import { checkTempBan, removeTempBan } from '#utils/database/tempBan';
|
||||
|
||||
@@ -36,7 +36,9 @@ export class TempBan extends ScheduledTask {
|
||||
// Get the guild where the user is in
|
||||
let guild = this.container.client.guilds.cache.get(payload.guildId);
|
||||
if (guild === undefined) {
|
||||
guild = await this.container.client.guilds.fetch(payload.guildId);
|
||||
guild = await this.container.client.guilds
|
||||
.fetch(payload.guildId)
|
||||
.catch(() => undefined);
|
||||
if (guild === undefined) {
|
||||
this.container.logger.error('Temp Unban Task: Guild not found!');
|
||||
return;
|
||||
@@ -48,7 +50,7 @@ export class TempBan extends ScheduledTask {
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = await guild.client.users.fetch(userId);
|
||||
user = await guild.client.users.fetch(userId).catch(() => undefined);
|
||||
if (user === undefined) {
|
||||
this.container.logger.error(
|
||||
'Temp Unban Task: Could not fetch banned user!',
|
||||
@@ -70,20 +72,27 @@ export class TempBan extends ScheduledTask {
|
||||
await removeTempBan(userId);
|
||||
|
||||
// Log unban
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted);
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(
|
||||
IDs.channels.logs.restricted,
|
||||
)) as TextChannel | undefined;
|
||||
if (logChannel === undefined) {
|
||||
const logChannelFetch = await guild.channels
|
||||
.fetch(IDs.channels.logs.restricted)
|
||||
.catch(() => null);
|
||||
if (logChannelFetch === null) {
|
||||
this.container.logger.error(
|
||||
`Temp Ban Listener: Could not fetch log channel. User Snowflake: ${userId}`,
|
||||
);
|
||||
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()
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
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 { ChannelType } from 'discord.js';
|
||||
|
||||
export class VerifyTimeout extends ScheduledTask {
|
||||
public constructor(
|
||||
@@ -30,17 +30,24 @@ export class VerifyTimeout extends ScheduledTask {
|
||||
|
||||
public async run(payload: { channelId: string; userId: string }) {
|
||||
// Get the guild where the user is in
|
||||
let channel = this.container.client.channels.cache.get(
|
||||
payload.channelId,
|
||||
) as VoiceChannel | undefined;
|
||||
let channel = this.container.client.channels.cache.get(payload.channelId);
|
||||
if (channel === undefined) {
|
||||
channel = (await this.container.client.channels.fetch(
|
||||
payload.channelId,
|
||||
)) as VoiceChannel | 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!');
|
||||
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)) {
|
||||
|
||||
@@ -32,7 +32,9 @@ export class VerifyUnblock extends ScheduledTask {
|
||||
// Get the guild where the user is in
|
||||
let guild = this.container.client.guilds.cache.get(payload.guildId);
|
||||
if (guild === undefined) {
|
||||
guild = await this.container.client.guilds.fetch(payload.guildId);
|
||||
guild = await this.container.client.guilds
|
||||
.fetch(payload.guildId)
|
||||
.catch(() => undefined);
|
||||
if (guild === undefined) {
|
||||
this.container.logger.error('verifyUnblock: Guild not found!');
|
||||
return;
|
||||
@@ -42,7 +44,7 @@ export class VerifyUnblock extends ScheduledTask {
|
||||
// Find GuildMember for the user
|
||||
let user = guild.members.cache.get(payload.userId);
|
||||
if (user === undefined) {
|
||||
user = await guild.members.fetch(payload.userId).catch(undefined);
|
||||
user = await guild.members.fetch(payload.userId).catch(() => undefined);
|
||||
if (user === undefined) {
|
||||
this.container.logger.error('verifyUnblock: GuildMember not found!');
|
||||
return;
|
||||
|
||||
@@ -29,6 +29,7 @@ export const blockedRoles = [
|
||||
IDs.roles.staff.trialVerifier,
|
||||
IDs.roles.staff.mentor,
|
||||
IDs.roles.stageHost,
|
||||
IDs.roles.booster,
|
||||
];
|
||||
|
||||
export const blockedRolesAfterRestricted = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { container } from '@sapphire/framework';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
export async function addToDatabase(
|
||||
export async function addSusNoteDB(
|
||||
userId: string,
|
||||
modId: string,
|
||||
message: string,
|
||||
@@ -10,13 +10,23 @@ export async function addToDatabase(
|
||||
await container.database.sus.create({
|
||||
data: {
|
||||
user: {
|
||||
connect: {
|
||||
id: userId,
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
create: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
mod: {
|
||||
connect: {
|
||||
id: modId,
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
id: modId,
|
||||
},
|
||||
create: {
|
||||
id: modId,
|
||||
},
|
||||
},
|
||||
},
|
||||
note: message,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
const devIDs = {
|
||||
roles: {
|
||||
trusted: '999431675081666599',
|
||||
booster: '',
|
||||
nonvegan: {
|
||||
nonvegan: '999431675081666598',
|
||||
vegCurious: '999431675098447932',
|
||||
@@ -57,6 +58,8 @@ const devIDs = {
|
||||
verifierCoordinator: '999431675140382810',
|
||||
eventCoordinator: '999431675165556817',
|
||||
outreachCoordinator: '999431675140382807',
|
||||
mediaCoordinator: '1204801056404676618',
|
||||
hrCoordinator: '1204795893480431657',
|
||||
restricted: '999431675123597407',
|
||||
moderator: '999431675123597408',
|
||||
trialModerator: '999431675123597404',
|
||||
|
||||
@@ -22,6 +22,7 @@ import devIDs from '#utils/devIDs';
|
||||
let IDs = {
|
||||
roles: {
|
||||
trusted: '731563158011117590',
|
||||
booster: '731213264540795012',
|
||||
nonvegan: {
|
||||
nonvegan: '774763753308815400',
|
||||
vegCurious: '832656046572961803',
|
||||
@@ -59,6 +60,8 @@ let IDs = {
|
||||
verifierCoordinator: '940721280376778822',
|
||||
eventCoordinator: '944732860554817586',
|
||||
outreachCoordinator: '954804769476730890',
|
||||
mediaCoordinator: '1203778509449723914',
|
||||
hrCoordinator: '1203802120180989993',
|
||||
restricted: '851624392928264222',
|
||||
moderator: '826157475815489598',
|
||||
trialModerator: '982074555596152904',
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
// "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. */,
|
||||
"paths": {
|
||||
"#utils/*": ["./utils/*"]
|
||||
"#utils/*": ["./utils/*"],
|
||||
} /* 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. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
@@ -79,7 +79,7 @@
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* 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. */
|
||||
// "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. */
|
||||
@@ -103,5 +103,5 @@
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
// "skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user