Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 54 additions & 47 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,7 @@ type HubModerator {
position HubModeratorPosition @default(network_mod)
}

type HubLogChannels {
modLogs String?
joinLeaves String?
profanity String?
reports hubLogReports?
}

type hubLogReports {
type hubLogChannelAndRole {
channelId String
roleId String?
}
Expand All @@ -58,40 +51,42 @@ enum InfractionType {
enum InfractionStatus {
ACTIVE
REVOKED
APPEALED
}

model UserInfraction {
id String @id @default(nanoid()) @map("_id")
id String @id @default(nanoid(10)) @map("_id")
userId String @db.String
hubId String @db.ObjectId
reason String
status InfractionStatus @default(ACTIVE)
type InfractionType @default(BLACKLIST)
dateIssued DateTime @default(now()) // Date when the infraction was issued
expiresAt DateTime?
revokedAt DateTime?
appealedAt DateTime?
moderatorId String?
userData UserData @relation(fields: [userId], references: [id])
hub Hub @relation(fields: [hubId], references: [id])

@@index([userId, hubId])
@@index([userId, hubId, status])
}

model ServerInfraction {
id String @id @default(nanoid()) @map("_id")
serverName String
serverId String
hubId String @db.ObjectId
reason String
status InfractionStatus @default(ACTIVE)
type InfractionType @default(BLACKLIST)
dateIssued DateTime @default(now()) // Date when the infraction was issued
expiresAt DateTime?
revokedAt DateTime?
moderatorId String?
hub Hub @relation(fields: [hubId], references: [id])

@@index([serverId, hubId])
id String @id @default(nanoid(10)) @map("_id")
serverName String
serverId String
hubId String @db.ObjectId
reason String
status InfractionStatus @default(ACTIVE)
type InfractionType @default(BLACKLIST)
dateIssued DateTime @default(now()) // Date when the infraction was issued
expiresAt DateTime?
appealedAt DateTime?
appealerUserId String?
moderatorId String?
hub Hub @relation(fields: [hubId], references: [id])

@@index([serverId, hubId, status])
}

model connectedList {
Expand All @@ -114,29 +109,41 @@ model connectedList {
}

model Hub {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
description String
rating HubRating[]
ownerId String
iconUrl String
bannerUrl String?
private Boolean @default(true)
createdAt DateTime @default(now())
// settings are stored as a number, each bit is a setting
settings Int
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
description String
rating HubRating[]
ownerId String
iconUrl String
bannerUrl String?
private Boolean @default(true)
locked Boolean @default(false)
appealCooldownHours Int @default(168) // 7 days
createdAt DateTime @default(now())
settings Int // each bit is a setting
// all the stuff below is relations to other collections
invites hubInvites[]
moderators HubModerator[]
connections connectedList[]
logChannels HubLogChannels?
locked Boolean @default(false)
// official Boolean @default(false)
originalMessages originalMessages[]
userInfractions UserInfraction[]
ServerInfraction ServerInfraction[]

@@index([id, name, ownerId, logChannels])
invites hubInvites[]
moderators HubModerator[]
connections connectedList[]
logConfig HubLogConfig[]
originalMessages originalMessages[]
userInfractions UserInfraction[]
serverInfractions ServerInfraction[]

@@index([id, name, ownerId])
}

model HubLogConfig {
id String @id @default(auto()) @map("_id") @db.ObjectId
modLogs String?
joinLeaves String?
profanity String?
appeals hubLogChannelAndRole?
reports hubLogChannelAndRole?
hub Hub @relation(fields: [hubId], references: [id])
hubId String @unique @db.ObjectId

@@index([id, hubId])
}

model hubInvites {
Expand Down
13 changes: 7 additions & 6 deletions scripts/deploy-commands.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-check
import { REST, Routes } from 'discord.js';
import { Collection, REST, Routes } from 'discord.js';
import 'dotenv/config';

const redText = (text) => `\x1b[0;31m${text}\x1b[0m`;
Expand All @@ -9,17 +9,18 @@ const TOKEN = process.env.DISCORD_TOKEN;
const CLIENT_ID = process.env.CLIENT_ID;
const SUPPORT_SERVER_ID = '770256165300338709';

if (!TOKEN || !CLIENT_ID || !SUPPORT_SERVER_ID)
throw new Error('Missing TOKEN, CLIENT_ID or SUPPORT_SERVER_ID.');
if (!TOKEN || !CLIENT_ID)
throw new Error('Missing TOKEN or CLIENT_ID.');

const commandUtils = await import('../build/utils/CommandUtls.js').catch(() => {
console.error(`${redText('✘')} Code is not build yet. Use \`pnpm build\` first.`);
const commandUtils = await import('../build/utils/CommandUtils.js').catch(() => {
console.error(`${redText('✘')} Code is not build yet. Run \`pnpm build\` first.`);
process.exit();
});

const registerAllCommands = async (staffOnly = false) => {
// make sure CommandsMap is not empty
const commandsMap = await commandUtils.loadCommandFiles();
const commandsMap = new Collection();
await commandUtils.loadCommandFiles(commandsMap, new Collection());

const rest = new REST().setToken(TOKEN);

Expand Down
3 changes: 1 addition & 2 deletions src/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ clusterManager
deleteExpiredInvites().catch(Logger.error);

// store network message timestamps to connectedList every minute
scheduler.addRecurringTask('storeMsgTimestamps', 60 * 1_000, storeMsgTimestamps);
scheduler.addRecurringTask('storeMsgTimestamps', 10 * 60 * 1000, storeMsgTimestamps);
scheduler.addRecurringTask('deleteExpiredInvites', 60 * 60 * 1000, deleteExpiredInvites);


// production only tasks
if (Constants.isDevBuild) return;

Expand Down
11 changes: 7 additions & 4 deletions src/commands/context-menu/deleteMsg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import {
} from 'discord.js';
import { isStaffOrHubMod } from '#main/utils/hub/utils.js';
import { deleteMessageFromHub, isDeleteInProgress } from '#main/utils/moderation/deleteMessage.js';
import { originalMessages, Hub, broadcastedMessages } from '@prisma/client';
import { originalMessages, Hub, broadcastedMessages, HubLogConfig } from '@prisma/client';

type OriginalMsgT = originalMessages & { hub: Hub; broadcastMsgs: broadcastedMessages[] };
type OriginalMsgT = originalMessages & {
hub: Hub & { logConfig: HubLogConfig[] };
broadcastMsgs: broadcastedMessages[];
};

export default class DeleteMessage extends BaseCommand {
readonly data: RESTPostAPIApplicationCommandsJSONBody = {
Expand All @@ -37,7 +40,7 @@ export default class DeleteMessage extends BaseCommand {
private async getOriginalMessage(messageId: string) {
const originalMsg = await db.originalMessages.findFirst({
where: { messageId },
include: { hub: true, broadcastMsgs: true },
include: { hub: { include: { logConfig: true } }, broadcastMsgs: true },
});

if (originalMsg) return originalMsg;
Expand Down Expand Up @@ -125,7 +128,7 @@ export default class DeleteMessage extends BaseCommand {

private async logDeletion(
interaction: MessageContextMenuCommandInteraction,
hub: Hub,
hub: Hub & { logConfig: HubLogConfig[] },
originalMsg: OriginalMsgT,
): Promise<void> {
if (!isStaffOrHubMod(interaction.user.id, hub)) return;
Expand Down
9 changes: 6 additions & 3 deletions src/commands/context-menu/messageInfo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Constants, { emojis } from '#main/config/Constants.js';
import BaseCommand from '#main/core/BaseCommand.js';
import { RegisterInteractionHandler } from '#main/decorators/Interaction.js';
import HubLogManager from '#main/managers/HubLogManager.js';
import { greyOutButton, greyOutButtons } from '#main/utils/ComponentUtils.js';
import { getHubConnections } from '#main/utils/ConnectedListUtils.js';
import { CustomID } from '#main/utils/CustomID.js';
Expand Down Expand Up @@ -163,7 +164,10 @@ export default class MessageInfo extends BaseCommand {
override async handleModals(interaction: ModalSubmitInteraction<CacheType>) {
const { originalMsg, messageId, locale } = await this.getModalMessageInfo(interaction);

if (!originalMsg?.hub?.logChannels?.reports) {
if (
!originalMsg?.hubId ||
!(await HubLogManager.create(originalMsg?.hubId)).config.reports?.channelId
) {
const notEnabledEmbed = new InfoEmbed().setDescription(
t({ phrase: 'msgInfo.report.notEnabled', locale }, { emoji: emojis.no }),
);
Expand Down Expand Up @@ -318,7 +322,6 @@ export default class MessageInfo extends BaseCommand {
if (!isValidDbMsgWithHubId(originalMsg)) return;
if (!originalMsg.hub || isStaffOrHubMod(interaction.user.id, originalMsg.hub)) return;


const { buttons, embed } = await modActionsPanel.buildMessage(interaction, originalMsg);
await interaction.reply({ embeds: [embed], components: buttons, ephemeral: true });
}
Expand All @@ -327,7 +330,7 @@ export default class MessageInfo extends BaseCommand {
interaction: ButtonInteraction,
{ hub, locale, messageId }: ReportOpts,
) {
if (!hub?.logChannels?.reports) {
if (!hub || !(await HubLogManager.create(hub.id)).config.reports?.channelId) {
const notEnabledEmbed = new InfoEmbed().setDescription(
t({ phrase: 'msgInfo.report.notEnabled', locale }, { emoji: emojis.no }),
);
Expand Down
2 changes: 1 addition & 1 deletion src/commands/slash/Main/connection/pause.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { emojis } from '#main/config/Constants.js';
import { fetchCommands, findCommand } from '#main/utils/CommandUtls.js';
import { fetchCommands, findCommand } from '#main/utils/CommandUtils.js';
import { updateConnection } from '#main/utils/ConnectedListUtils.js';
import db from '#main/utils/Db.js';
import { InfoEmbed } from '#main/utils/EmbedUtils.js';
Expand Down
2 changes: 1 addition & 1 deletion src/commands/slash/Main/connection/unpause.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { emojis } from '#main/config/Constants.js';
import { fetchCommands, findCommand } from '#main/utils/CommandUtls.js';
import { fetchCommands, findCommand } from '#main/utils/CommandUtils.js';
import { updateConnection } from '#main/utils/ConnectedListUtils.js';
import db from '#main/utils/Db.js';
import { t } from '#main/utils/Locale.js';
Expand Down
77 changes: 77 additions & 0 deletions src/commands/slash/Main/hub/appeal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import db from '#main/utils/Db.js';
import { Hub } from '@prisma/client';
import { ChatInputCommandInteraction } from 'discord.js';
import parse from 'parse-duration';
import HubCommand from './index.js';
import { emojis } from '#main/config/Constants.js';
import { isHubMod } from '#main/utils/hub/utils.js';
import { t } from '#main/utils/Locale.js';
import { ErrorEmbed } from '#main/utils/EmbedUtils.js';

export default class AppealCommand extends HubCommand {
async execute(interaction: ChatInputCommandInteraction) {
const subcommand = interaction.options.getSubcommand();

const hub = await this.runHubChecks(interaction);
if (!hub) return;

switch (subcommand) {
case 'set_cooldown':
await this.handleAppealCooldown(interaction, hub);
break;
default:
break;
}
}

private async handleAppealCooldown(interaction: ChatInputCommandInteraction, hub: Hub) {
const cooldown = interaction.options.getString('cooldown', true);
const appealCooldownHours = parse(cooldown, 'hour');
if (!appealCooldownHours || appealCooldownHours < 1) {
const embed = new ErrorEmbed().setDescription('Cooldown must be atleast **1 hour** long.');
await interaction.reply({ embeds: [embed], ephemeral: true });
return;
}
else if (appealCooldownHours > 8766) {
const embed = new ErrorEmbed().setDescription('Cooldown cannot be longer than **1 year**.');
await interaction.reply({ embeds: [embed], ephemeral: true });
return;
}

await db.hub.update({ where: { id: hub.id }, data: { appealCooldownHours } });

await interaction.reply({
content: `${emojis.clock_icon} Appeal cooldown has been set to **${appealCooldownHours}** hour(s).`,
ephemeral: true,
});
}

private async runHubChecks(interaction: ChatInputCommandInteraction) {
const hubName = interaction.options.getString('hub') ?? undefined;
const hub = await db.hub.findFirst({
where: {
OR: [
{ name: hubName },
{ moderators: { some: { OR: [{ position: 'manager' }, { position: 'network_mod' }] } } },
],
},
});

if (!hub || !isHubMod(interaction.user.id, hub)) {
await this.replyEmbed(
interaction,
t(
{
phrase: 'hub.notFound_mod',
locale: await interaction.client.userManager.getUserLocale(interaction.user.id),
},
{ emoji: emojis.no },
),
{ ephemeral: true },
);
return null;
}

return hub;
}
}
6 changes: 3 additions & 3 deletions src/commands/slash/Main/hub/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
TextInputBuilder,
TextInputStyle,
} from 'discord.js';
import Hub from './index.js';
import HubCommand from './index.js';

export default class Create extends Hub {
export default class Create extends HubCommand {
readonly cooldown = 10 * 60 * 1000; // 10 mins

async execute(interaction: ChatInputCommandInteraction<CacheType>) {
Expand Down Expand Up @@ -163,7 +163,7 @@ export default class Create extends Hub {
)
.setTimestamp();

const command = Hub.subcommands.get('create');
const command = HubCommand.subcommands.get('create');
command?.setUserCooldown(interaction);

await interaction.editReply({ embeds: [successEmbed] });
Expand Down
4 changes: 2 additions & 2 deletions src/commands/slash/Main/hub/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import {
ChatInputCommandInteraction,
EmbedBuilder,
} from 'discord.js';
import Hub from './index.js';
import HubCommand from './index.js';

export default class Delete extends Hub {
export default class Delete extends HubCommand {
async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const hubName = interaction.options.getString('hub', true);
const hubInDb = await db.hub.findFirst({ where: { name: hubName } });
Expand Down
Loading