Skip to content

Commit

Permalink
Added the ability to add/view file rules
Browse files Browse the repository at this point in the history
  • Loading branch information
Pickysaurus committed Jan 26, 2025
1 parent 37fe137 commit 7be8163
Show file tree
Hide file tree
Showing 6 changed files with 2,485 additions and 3,175 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nexus-bot-typescript",
"version": "3.8.0",
"version": "3.8.1",
"description": "A Discord bot for Nexus Mods, written in TypeScript",
"main": "dist/app.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src/api/automod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async function getBadFiles(): Promise<IBadFileRule[]> {
});
}

async function addBadFile(type: 'low' | 'high', func: string, test: string, flagMessage: string) {
async function addBadFile(type: 'low' | 'high', func: string, test: string, flagMessage: string): Promise<number> {
return new Promise((resolve, reject) => {
query('INSERT INTO automod_badfiles (type, test, "flagMessage", "funcName") VALUES ($1, $2, $3, $4) RETURNING id', [type, test.toLowerCase(), flagMessage, func],
(error, results?: QueryResult) => {
Expand Down
5 changes: 3 additions & 2 deletions src/api/bot-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { getAllLinks, getLinksByUser, getLinksByServer, addServerLink, deleteSer
import { getAllInfos, createInfo, deleteInfo, displayInfo } from './infos';

// AUTOMOD
import { getAutomodRules, createAutomodRule, deleteAutomodRule } from './automod';
import { getAutomodRules, createAutomodRule, deleteAutomodRule, getBadFiles, addBadFile } from './automod';

export {
getAllUsers, getUserByDiscordId, getUserByNexusModsName, getUserByNexusModsId, createUser, deleteUser, updateUser, userEmbed, userProfileEmbed,
Expand All @@ -37,5 +37,6 @@ export {
getAllLinks, getLinksByUser, getLinksByServer, addServerLink, deleteServerLink, deleteAllServerLinksByUser,
deleteServerLinksByUserSilent, deleteServerLinksByServerSilent, updateRoles, updateAllRoles, modUniqueDLTotal,
getAllInfos, createInfo, deleteInfo, displayInfo,
getAutomodRules, createAutomodRule, deleteAutomodRule
getAutomodRules, createAutomodRule, deleteAutomodRule,
getBadFiles, addBadFile
};
4 changes: 4 additions & 0 deletions src/feeds/AutoModManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export class AutoModManager {
return this.AutoModRules;
}

public async retrieveFileRules(): Promise<IBadFileRule[]> {
return this.BadFiles;
}

public clearRuleCache(): void {
this.getRules();
}
Expand Down
140 changes: 136 additions & 4 deletions src/interactions/automod.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ChatInputCommandInteraction, CommandInteraction, EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder } from "discord.js";
import { DiscordInteraction, ClientExt } from "../types/DiscordTypes";
import { logMessage } from "../api/util";
import { createAutomodRule, deleteAutomodRule } from "../api/bot-db";
import { IAutomodRule } from "../types/util";
import { createAutomodRule, deleteAutomodRule, getBadFiles, addBadFile } from "../api/bot-db";
import { IAutomodRule, IBadFileRule } from "../types/util";

const discordInteraction: DiscordInteraction = {
command: new SlashCommandBuilder()
Expand All @@ -14,6 +14,57 @@ const discordInteraction: DiscordInteraction = {
sc.setName('report')
.setDescription('See the last few mods checked by the automod.')
)
.addSubcommandGroup(sgc =>
sgc.setName('filerules')
.setDescription('Manage Automod file rules')
.addSubcommand(sc =>
sc.setName('add')
.setDescription('Add a new file rule')
.addStringOption(o =>
o.setName('level')
.setDescription('Level of trigger. High priority triggers alert moderators.')
.setRequired(true)
.setChoices([{ name: 'High', value: 'high' }, { name: 'Low', value: 'low' }])
)
.addStringOption(o =>
o.setName('filter')
.setDescription('String or regex to run the check against')
.setRequired(true)
)
.addStringOption(o =>
o.setName('function')
.setDescription('Function to use for matching')
.setRequired(true)
.setChoices([
{
name: 'includes',
value: 'includes'
},
{
name: 'startsWith',
value: 'startsWith'
},
{
name: 'endsWith',
value: 'endsWith'
},
{
name: 'match',
value: 'match'
}
])
)
.addStringOption(o =>
o.setName('message')
.setDescription('Message to show when triggered')
.setRequired(true)
)
)
.addSubcommand(sc =>
sc.setName('list')
.setDescription('List available file rules.')
)
)
.addSubcommandGroup(sgc =>
sgc.setName('rules')
.setDescription('Manage Automod rules.')
Expand All @@ -40,7 +91,7 @@ const discordInteraction: DiscordInteraction = {
)
.addSubcommand(sc =>
sc.setName('list')
.setDescription('List existing Automod rules.')
.setDescription('List existing Automod rules and file.')
)
.addSubcommand(sc =>
sc.setName('remove')
Expand Down Expand Up @@ -79,6 +130,14 @@ async function action(client: ClientExt, baseInteraction: CommandInteraction): P
default: throw new Error('Subcommand not implemented: '+command)
}
}
else if (commandGroup === 'filerules') {
switch (command) {
case 'add': return addFileRule(client, interaction);
case 'list': return listFileRules(client, interaction);
// case 'remove': return removeFileRule(client, interaction);
default: throw new Error('Subcommand not implemented: '+command)
}
}
else if (commandGroup === null && command === 'report') return showReport(client, interaction);
else throw new Error(`Unrecognised command - Group: ${commandGroup} Command: ${command}`);
}
Expand Down Expand Up @@ -132,7 +191,7 @@ async function listRules(client: ClientExt, interaction: ChatInputCommandInterac
}
}

rulePages.push(currentPage);
if (currentPage.length) rulePages.push(currentPage);
// End pagination.

const header = `| ID | Type | Filter | Added | Note |\n`
Expand Down Expand Up @@ -183,6 +242,79 @@ async function removeRule(client: ClientExt, interaction: ChatInputCommandIntera
return interaction.editReply({ content: `Rule Deleted\n\`\`\`${JSON.stringify(ruleToRemove, null, 2)}\`\`\``})
}

async function addFileRule(client: ClientExt, interaction: ChatInputCommandInteraction): Promise<any> {
// Is an admin?
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.Administrator);
if (!isAdmin) return interaction.editReply({ content: 'Adding file rules is only available to admins.' })

// Get the options passed to the command
const level: 'low' | 'high' = interaction.options.getString('level', true) as 'low' | 'high';
const filter: string = interaction.options.getString('filter', true);
const functionName: string = interaction.options.getString('function', true);
const note: string = interaction.options.getString('message', true);
let id = -1 // Update this once created.

logMessage('Adding automod file rule', { level, filter, functionName, note });

try {
id = await addBadFile(level, functionName, filter, note);
logMessage('Added new file rule with ID', id);
}
catch(err) {
logMessage('Failed to add automod rule file', err);
throw new Error('Failed to create file rule: '+(err as Error).message)
}

const success = new EmbedBuilder()
.setTitle('File Rule Created')
.setDescription(`**ID:** ${id}\n**Level:** ${level.toUpperCase()}\n**Filter:** ${filter}\n**Function: ${functionName}\n**Note:** ${note}`)
.setThumbnail(client.user?.avatarURL() || '')
.setColor(0xda8e35);

client.automod?.clearRuleCache();

return interaction.editReply({ content: null, embeds: [success] });
}

async function listFileRules(client: ClientExt, interaction: ChatInputCommandInteraction): Promise<any> {
const rules = await client.automod?.retrieveFileRules() ?? [];

// Cut up the array into pages for Discord's character limit.
const rulePages = new Array<IBadFileRule[]>()
let currentPage = [];

for (const rule of rules) {
console.log('Handling rule', rule.test)
currentPage.push(rule);
if (currentPage.length === 15) {
rulePages.push([...currentPage]);
currentPage = [];
}
}

if (currentPage.length) rulePages.push(currentPage);
// End pagination.

const header = `| ID | Type | Filter | Function | Note |\n`
+`| --- | --- | --- | --- | --- |\n`

const body = rulePages[0].map(r => `| ${r.id} | ${r.type} | ${r.test} | ${r.funcName} | ${r.flagMessage} `);

const messageText = header+body.join('\n');

await interaction.editReply(`# Automod Rules\n\`\`\`${messageText}\`\`\``);

if (rulePages.length > 1) {
// Post extra pages
rulePages.map(async (page, index) => {
if (index === 0) return;
const pageMessage = `\`\`\`${header}${page.map(r => `| ${r.id} | ${r.type} | ${r.test} | ${r.funcName} | ${r.flagMessage} `).join('\n')}\`\`\``;
await interaction.followUp({content: pageMessage, ephemeral: true});
return;
});
}
}

async function showReport(client: ClientExt, interaction: ChatInputCommandInteraction): Promise<any> {
const report = client.automod?.lastReports
const reportsMerged = report?.reduce((prev, cur) => {
Expand Down
Loading

0 comments on commit 7be8163

Please sign in to comment.