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
12 changes: 11 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ type HubModerator {
// honestly... I might as well make permissions lol
}

// type HubSettings {
// useNicknames Boolean @default(false)
// allowInvites Boolean @default(true)
// allowLinks Boolean @default(true)
// allowReactions Boolean @default(true)
// profanityFilter Boolean @default(false)
// spamFilter Boolean @default(true)
// }

model blacklistedServers {
id String @id @default(auto()) @map("_id") @db.ObjectId
serverId String
Expand Down Expand Up @@ -86,7 +95,8 @@ model hubs {
bannerUrl String?
private Boolean @default(true)
createdAt DateTime @default(now())
useNicknames Boolean @default(false)
// settings are stored as a number, each bit is a setting
settings Int
// all the stuff below is relations to other collections
invites hubInvites[]
messages messageData[]
Expand Down
13 changes: 12 additions & 1 deletion src/Commands/Main/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,17 @@ export default {
.setAutocomplete(true)
.setRequired(true),
),
) .addSubcommand(subcommand =>
subcommand
.setName('settings')
.setDescription('Manage hub settings')
.addStringOption(stringOption =>
stringOption
.setName('hub')
.setDescription('The name of the hub')
.setAutocomplete(true)
.setRequired(true),
),
),
async execute(interaction: ChatInputCommandInteraction) {
const subcommand = interaction.options.getSubcommand();
Expand All @@ -263,7 +274,7 @@ export default {
});

}
else if (subcommand === 'manage' || subcommand === 'networks') {
else if (subcommand === 'manage' || subcommand === 'networks' || subcommand === 'settings') {
hubChoices = await getDb().hubs.findMany({
where: {
name: {
Expand Down
17 changes: 15 additions & 2 deletions src/Commands/Network/editMsg.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ContextMenuCommandBuilder, MessageContextMenuCommandInteraction, ApplicationCommandType, ModalBuilder, ActionRowBuilder, TextInputBuilder, TextInputStyle, WebhookClient, EmbedBuilder } from 'discord.js';
import { networkMsgUpdate } from '../../Scripts/networkLogs/msgUpdate';
import { checkIfStaff, getDb, getGuildName, topgg } from '../../Utils/functions/utils';
import { checkIfStaff, getDb, getGuildName, replaceLinks, topgg } from '../../Utils/functions/utils';
import wordFiler from '../../Utils/functions/wordFilter';
import { captureException } from '@sentry/node';
import { HubSettingsBitField } from '../../Utils/hubs/hubSettingsBitfield';

export default {
description: 'Edit a message that was sent in the network.',
Expand All @@ -23,6 +24,7 @@ export default {
const db = getDb();
const messageInDb = await db.messageData.findFirst({
where: { channelAndMessageIds: { some: { messageId: { equals: target.id } } } },
include: { hub: true },
});

if (!messageInDb) {
Expand Down Expand Up @@ -71,10 +73,21 @@ export default {

if (!editInteraction) return;

const hubSettings = new HubSettingsBitField(messageInDb.hub?.settings);
// get the input from user
const newMessage = editInteraction.fields.getTextInputValue('newMessage');
const newMessage = hubSettings.has('HideLinks')
? replaceLinks(editInteraction.fields.getTextInputValue('newMessage'))
: editInteraction.fields.getTextInputValue('newMessage');
const censoredNewMessage = wordFiler.censor(newMessage);

if (newMessage.includes('discord.gg') || newMessage.includes('discord.com/invite') || newMessage.includes('dsc.gg')) {
editInteraction.reply({
content: `${interaction.client.emotes.normal.no} Do not advertise or promote servers in the network. Set an invite in \`/network manage\` instead!`,
ephemeral: true,
});
return;
}

// if the message being edited is in compact mode
// then we create a new embed with the new message and old reply
// else we just use the old embed and replace the description
Expand Down
10 changes: 7 additions & 3 deletions src/Events/messageCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { APIMessage, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder,
import { getDb, colors } from '../Utils/functions/utils';
import { censor } from '../Utils/functions/wordFilter';
import { messageData } from '@prisma/client';
import { HubSettingsBitField } from '../Utils/hubs/hubSettingsBitfield';

export interface NetworkMessage extends Message {
censored_content: string,
Expand All @@ -27,7 +28,8 @@ export default {
});

if (channelInDb && channelInDb?.hub) {
if (!await checks.execute(message, channelInDb)) return;
const settings = new HubSettingsBitField(channelInDb.hub?.settings);
if (!await checks.execute(message, channelInDb, settings)) return;

message.censored_content = censor(message.content);
const attachment = message.attachments.first();
Expand All @@ -53,11 +55,13 @@ export default {
}
}

const useNicknames = settings.has('UseNicknames');

// for nicknames setting
const displayNameOrUsername = channelInDb.hub.useNicknames
const displayNameOrUsername = useNicknames
? message.member?.displayName || message.author.displayName
: message.author.username;
const avatarURL = channelInDb.hub.useNicknames
const avatarURL = useNicknames
? message.member?.user.displayAvatarURL()
: message.author.displayAvatarURL();

Expand Down
4 changes: 4 additions & 0 deletions src/Scripts/hub/create.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ChatInputCommandInteraction, ModalBuilder, TextInputBuilder, EmbedBuilder, ActionRowBuilder, TextInputStyle, Collection } from 'discord.js';
import { getDb } from '../../Utils/functions/utils';
import { HubSettingsBits } from '../../Utils/hubs/hubSettingsBitfield';

const cooldowns = new Collection<string, number>();

Expand Down Expand Up @@ -82,6 +83,8 @@ export async function execute(interaction: ChatInputCommandInteraction) {
const description = submitIntr.fields.getTextInputValue('description');
const tags = submitIntr.fields.getTextInputValue('tags');

// FIXME: settings is a required field, add the fields to every collection
// in prod db before pushing it
await db.hubs.create({
data: {
name: hubName,
Expand All @@ -91,6 +94,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
ownerId: submitIntr.user.id,
iconUrl: imgurIcons[0],
bannerUrl: imgurBanners?.[0],
settings: HubSettingsBits.SpamFilter,
},
});

Expand Down
14 changes: 0 additions & 14 deletions src/Scripts/hub/manage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ export async function execute(interaction: ChatInputCommandInteraction) {
${hub.description}
- __**Tags:**__ ${hub.tags.join(', ')}
- __**Public:**__ ${hub.private ? emotes.normal.no : emotes.normal.yes}
- __**Use Nicknames:**__ ${hub.useNicknames ? emotes.normal.yes : emotes.normal.no}
`)
.setThumbnail(hub.iconUrl)
.setImage(hub.bannerUrl)
Expand Down Expand Up @@ -194,19 +193,6 @@ export async function execute(interaction: ChatInputCommandInteraction) {
break;
}

case 'nickname': {
await db.hubs.update({
where: { id: hubInDb?.id },
data: { useNicknames: !hubInDb?.useNicknames },
});

await i.reply({
content: `**${hubInDb?.useNicknames ? 'Usernames' : 'Display Names'}** will now be displayed for user names on messages instead.`,
ephemeral: true,
});
break;
}

case 'description': {
const modal = new ModalBuilder()
.setCustomId(i.id)
Expand Down
103 changes: 103 additions & 0 deletions src/Scripts/hub/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { ActionRowBuilder, ChatInputCommandInteraction, ComponentType, EmbedBuilder, StringSelectMenuBuilder } from 'discord.js';
import { colors, getDb } from '../../Utils/functions/utils';
import { hubs } from '@prisma/client';
import { HubSettingsBitField, HubSettingsString } from '../../Utils/hubs/hubSettingsBitfield';

const genSettingsEmbed = (hub: hubs, yesEmoji: string, noEmoji: string) => {
const settings = new HubSettingsBitField(hub.settings);
const settingDescriptions = {
HideLinks: '**Hide Links** - Redact links sent by users.',
Reactions: '**Reactions** - Allow users to react to messages.',
BlockInvites: '**Block Invites** - Prevent users from sending Discord invites.',
SpamFilter: '**Spam Filter** - Automatically blacklist spammers for 5 minutes.',
UseNicknames: '**Use Nicknames** - Use server nicknames as the network usernames.',
};

return new EmbedBuilder()
.setAuthor({ name: `${hub.name} Settings`, iconURL: hub.iconUrl })
.setDescription(Object.entries(settingDescriptions).map(([key, value]) => {
const flag = settings.has(key as HubSettingsString);
return `${flag ? yesEmoji : noEmoji} ${value}`;
}).join('\n'))
.setFooter({ text: 'Use the select menu below to toggle.' })
.setColor(colors('chatbot'))
.setTimestamp();
};

const genSelectMenu = (
hubSettings: HubSettingsBitField,
disabledEmote: string,
enabledEmote: string,
) => {
return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
new StringSelectMenuBuilder()
.setCustomId('hub_settings')
.setPlaceholder('Select an option')
.addOptions(
Object.keys(HubSettingsBitField.Flags).map((key) => {
const flag = hubSettings.has(key as HubSettingsString);
const emoji = flag ? disabledEmote : enabledEmote;
return {
label: `${flag ? 'Disable' : 'Enable'} ${key}`,
value: key,
emoji,
};
}),
),
);
};

export async function execute(interaction: ChatInputCommandInteraction) {
const hubName = interaction.options.getString('hub', true);

const db = getDb();
let hub = await db.hubs.findUnique({
where: {
name: hubName,
OR: [
{
moderators: { some: { userId: interaction.user.id, position: 'manager' } },
},
{ ownerId: interaction.user.id },
],
},
});

if (!hub) {
return interaction.reply({
content: 'Hub not found.',
ephemeral: true,
});
}

const hubSettings = new HubSettingsBitField(hub.settings);
const emotes = interaction.client.emotes.normal;
const embed = genSettingsEmbed(hub, emotes.enabled, emotes.disabled);
const selects = genSelectMenu(hubSettings, emotes.disabled, emotes.enabled);

const initReply = await interaction.reply({ embeds: [embed], components: [selects] });

const collector = initReply.createMessageComponentCollector({
time: 60 * 1000,
filter: (i) => i.user.id === interaction.user.id,
componentType: ComponentType.StringSelect,
});

// respond to select menu
collector.on('collect', async (i) => {
const selected = i.values[0] as HubSettingsString;

hub = await db.hubs.update({
where: { name: hub?.name },
data: { settings: hubSettings.toggle(selected).bitfield },
});

const newEmbed = genSettingsEmbed(hub, emotes.enabled, emotes.disabled);
const newSelects = genSelectMenu(hubSettings, emotes.disabled, emotes.enabled);

await i.update({
embeds: [newEmbed],
components: [newSelects],
});
});
}
49 changes: 26 additions & 23 deletions src/Scripts/message/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ import wordFilter from '../../Utils/functions/wordFilter';
import antiSpam from './antispam';
import { Message } from 'discord.js';
import { slurs } from '../../Utils/JSON/badwords.json';
import { addUserBlacklist, getDb } from '../../Utils/functions/utils';
import { addUserBlacklist, getDb, replaceLinks } from '../../Utils/functions/utils';
import { connectedList } from '@prisma/client';
import { HubSettingsBitField } from '../../Utils/hubs/hubSettingsBitfield';

export = {
async execute(message: Message, networkData: connectedList) {
async execute(message: Message, networkData: connectedList, settings: HubSettingsBitField) {
// true = pass, false = fail (checks)

if (!networkData.hubId) {
message.reply('Using InterChat without a joining hub is no longer supported. Join a hub by using `/hub join` and explore hubs using `/hub browse`.');
return false;
}

const db = getDb();
const userInBlacklist = await db.blacklistedUsers?.findFirst({
where: { hubId: networkData.hubId, userId: message.author.id },
Expand All @@ -31,17 +27,18 @@ export = {
}
if (serverInBlacklist) return false;

const antiSpamResult = antiSpam(message.author, 3);
if (antiSpamResult) {
if (antiSpamResult.infractions >= 3) {
addUserBlacklist(networkData.hubId, message.client.user, message.author, 'Auto-blacklisted for spamming.', 60 * 5000);
if (settings.has('SpamFilter')) {
const antiSpamResult = antiSpam(message.author, 3);
if (antiSpamResult) {
if (antiSpamResult.infractions >= 3) addUserBlacklist(networkData.hubId, message.client.user, message.author, 'Auto-blacklisted for spamming.', 60 * 5000);
message.react(message.client.emotes.icons.timeout);
return false;
}

if (message.content.length > 1000) {
message.reply('Please keep your message shorter than 1000 characters long.');
return false;
}
message.react(message.client.emotes.icons.timeout);
return false;
}
if (message.content.length > 1000) {
message.reply('Please keep your message shorter than 1000 characters long.');
return false;
}

// check if message contains slurs
Expand All @@ -51,6 +48,7 @@ export = {
}

if (
settings.has('BlockInvites') &&
message.content.includes('discord.gg') ||
message.content.includes('discord.com/invite') ||
message.content.includes('dsc.gg')
Expand All @@ -59,12 +57,6 @@ export = {
return false;
}

// dont send message if guild name is inappropriate
if (wordFilter.check(message.guild?.name)) {
message.channel.send('I have detected words in the server name that are potentially offensive, Please fix it before using this chat!');
return false;
}

if (message.stickers.size > 0 && !message.content) {
message.reply('Sending stickers in the network is not possible due to discord\'s limitations.');
return false;
Expand All @@ -84,8 +76,19 @@ export = {
return false;
}

// dont send message if guild name is inappropriate
if (wordFilter.check(message.guild?.name)) {
message.channel.send('I have detected words in the server name that are potentially offensive, Please fix it before using this chat!');
return false;
}

if (wordFilter.check(message.content)) wordFilter.log(message.content, message.author, message.guildId, networkData.hubId);

const urlRegex = /https?:\/\/(?!tenor\.com|giphy\.com)\S+/g;
if (settings.has('HideLinks') && message.content.match(urlRegex)) {
message.content = replaceLinks(message.content);
}

return true;
},
};
Loading