19 Commits

Author SHA1 Message Date
Anthony Berg
616334f123 fix(arabot): counting for fixRoles
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
ESLint / Run eslint scanning (push) Waiting to run
Prettier / Run prettier scanning (push) Waiting to run
2025-01-17 16:16:08 +01:00
Anthony Berg
cd319609b0 fix(arabot): add snowflake for channel logs 2025-01-17 16:14:09 +01:00
Anthony Berg
a1469e0596 feat(arabot): process non-vegan roles 2025-01-17 16:07:05 +01:00
Anthony Berg
ca0e43a70e feat(arabot): add role for restricted vegan 2025-01-17 15:59:54 +01:00
Anthony Berg
dc16dee92c fix(arabot): typo in fixRoles 2025-01-17 15:34:22 +01:00
Anthony Berg
71f0ee9f01 refactor(arabot): cleanup sus command from deprecated functions
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
ESLint / Run eslint scanning (push) Has been cancelled
Prettier / Run prettier scanning (push) Has been cancelled
2025-01-16 00:58:11 +01:00
Anthony Berg
881f9bfc24 fix(arabot): new sus role was put in the wrong place 2025-01-16 00:57:25 +01:00
Anthony Berg
98b9ac6fde feat(arabot): add a check if the user is restricted when trying to gain access to the server 2025-01-15 20:46:25 +01:00
Anthony Berg
1f92bf5d68 fix: update the role snowflake to new ones 2025-01-15 20:35:47 +01:00
Anthony Berg
d9f04e8d49 Revert "fix: remove accidentally given nv roles from vegans"
This reverts commit b4c8f0785c.
2025-01-15 20:31:08 +01:00
Anthony Berg
b4c8f0785c fix: remove accidentally given nv roles from vegans 2025-01-15 20:26:49 +01:00
Anthony Berg
7918f73e7d feat: turn off the fixer for the roles reassignment 2025-01-15 20:06:22 +01:00
Anthony Berg
ea211a9111 feat: add fixer for when roles get recreated 2025-01-15 19:20:07 +01:00
Anthony Berg
32776a2311 feat: add log on Discord when bot has started
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
ESLint / Run eslint scanning (push) Waiting to run
Prettier / Run prettier scanning (push) Waiting to run
2025-01-15 17:42:54 +01:00
Anthony Berg
d72b66f988 refactor: update activist role to new snowflake 2025-01-15 16:47:48 +01:00
Anthony Berg
e03bd6e85e refactor: update roles to new snowflake 2025-01-15 16:22:31 +01:00
Anthony Berg
a400cf9507 refactor: update roles to new snowflake 2025-01-15 16:13:19 +01:00
Anthony Berg
2fbb6c9265 feat: update deps and breaking changes 2025-01-15 16:07:21 +01:00
Anthony Berg
fc8c12b346 Merge pull request #212 from veganhacktivists/coolify
Coolify support
2025-01-15 15:25:03 +01:00
12 changed files with 647 additions and 396 deletions

View File

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

618
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -21,8 +21,14 @@ import {
InteractionHandler,
InteractionHandlerTypes,
} from '@sapphire/framework';
import type { ButtonInteraction, GuildMember, TextChannel } from 'discord.js';
import {
ButtonInteraction,
GuildMember,
MessageFlagsBitField,
TextChannel,
} from 'discord.js';
import IDs from '#utils/ids';
import { checkActive } from '#utils/database/moderation/restriction';
export class WelcomeButtonHandler extends InteractionHandler {
public constructor(
@@ -54,7 +60,7 @@ export class WelcomeButtonHandler extends InteractionHandler {
await interaction.reply({
content:
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.',
ephemeral: true,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
@@ -62,6 +68,16 @@ export class WelcomeButtonHandler extends InteractionHandler {
try {
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
if (!member.voice.channel) {
await member.roles.add(IDs.roles.nonvegan.nonvegan);
@@ -78,13 +94,13 @@ export class WelcomeButtonHandler extends InteractionHandler {
await interaction.reply({
content:
"You're currently in a verification, you'll have to leave the verification or get verified before being able to access the server again.",
ephemeral: true,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
} catch (error) {
await interaction.reply({
content:
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.',
ephemeral: true,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
}
}

View File

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

195
src/listeners/fixRoles.ts Normal file
View File

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

View File

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

View File

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

View File

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