Skip to content

Commit

Permalink
Merge pull request #602 from aternosorg/dev
Browse files Browse the repository at this point in the history
Add comment field to moderations
  • Loading branch information
JulianVennen authored Jun 3, 2024
2 parents ebed2bf + c254e5f commit 71e2f39
Show file tree
Hide file tree
Showing 17 changed files with 178 additions and 104 deletions.
9 changes: 6 additions & 3 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: Build and push Docker image

on:
push:
tags:
- 'v*'
branches:
- master
release:
types:
- published
- dev

env:
REGISTRY: ghcr.io
Expand Down Expand Up @@ -35,6 +35,9 @@ jobs:
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
- name: Build and push
uses: docker/build-push-action@v4
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ async function start() {
await database.connect();
await logger.info('Creating database tables');
await database.createTables();
await database.runMigrations();
await logger.notice('Logging into discord');
await bot.start();
await logger.info('Online');
Expand Down
31 changes: 25 additions & 6 deletions src/bot/Database.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as mysql from 'mysql2/promise';
import logger from './Logger.js';
import config from './Config.js';
import CommentFieldMigration from '../database/migrations/CommentFieldMigration.js';
import {asyncFilter} from '../util/util.js';

export class Database {
/**
* @type {import("mysql2").Connection}
* @type {import('mysql2').Connection}
*/
#connection = null;

Expand Down Expand Up @@ -55,17 +57,15 @@ export class Database {
waiting.resolve();
}
this.#waiting = [];
}
catch (error) {
} catch (error) {
return this.#handleFatalError(error);
}
}

#handleConnectionError(err) {
if (err.fatal) {
this.#handleFatalError(err);
}
else {
} else {
logger.error('A database error occurred', err)
.catch(console.error);
}
Expand Down Expand Up @@ -102,11 +102,30 @@ export class Database {
await this.query('CREATE TABLE IF NOT EXISTS `users` (`id` VARCHAR(20) NOT NULL, `config` TEXT NOT NULL, PRIMARY KEY (`id`))');
await this.query('CREATE TABLE IF NOT EXISTS `responses` (`id` int PRIMARY KEY AUTO_INCREMENT, `guildid` VARCHAR(20) NOT NULL, `trigger` TEXT NOT NULL, `response` TEXT NOT NULL, `global` BOOLEAN NOT NULL, `channels` TEXT NULL DEFAULT NULL)');
await this.query('CREATE TABLE IF NOT EXISTS `badWords` (`id` int PRIMARY KEY AUTO_INCREMENT, `guildid` VARCHAR(20) NOT NULL, `trigger` TEXT NOT NULL, `punishment` TEXT NOT NULL, `response` TEXT NOT NULL, `global` BOOLEAN NOT NULL, `channels` TEXT NULL DEFAULT NULL, `priority` int NULL)');
await this.query('CREATE TABLE IF NOT EXISTS `moderations` (`id` int PRIMARY KEY AUTO_INCREMENT, `guildid` VARCHAR(20) NOT NULL, `userid` VARCHAR(20) NOT NULL, `action` VARCHAR(10) NOT NULL,`created` bigint NOT NULL, `value` int DEFAULT 0,`expireTime` bigint NULL DEFAULT NULL, `reason` TEXT,`moderator` VARCHAR(20) NULL DEFAULT NULL, `active` BOOLEAN DEFAULT TRUE)');
await this.query('CREATE TABLE IF NOT EXISTS `moderations` (`id` int PRIMARY KEY AUTO_INCREMENT, `guildid` VARCHAR(20) NOT NULL, `userid` VARCHAR(20) NOT NULL, `action` VARCHAR(10) NOT NULL, `created` bigint NOT NULL, `value` int DEFAULT 0, `expireTime` bigint NULL DEFAULT NULL, `reason` TEXT, `comment` TEXT NULL DEFAULT NULL, `moderator` VARCHAR(20) NULL DEFAULT NULL, `active` BOOLEAN DEFAULT TRUE)');
await this.query('CREATE TABLE IF NOT EXISTS `confirmations` (`id` int PRIMARY KEY AUTO_INCREMENT, `data` TEXT NOT NULL, `expires` bigint NOT NULL)');
await this.query('CREATE TABLE IF NOT EXISTS `safeSearch` (`hash` CHAR(64) PRIMARY KEY, `data` TEXT NOT NULL)');
}

async getMigrations() {
return await asyncFilter([
new CommentFieldMigration(this)
], async migration => await migration.check());
}

async runMigrations() {
const migrations = await this.getMigrations();

if (migrations.length === 0) {
return;
}

await logger.info(`Running ${migrations.length} migrations`);
for (let migration of migrations) {
await migration.run();
}
}

/**
* Execute query and return all results
*
Expand Down
18 changes: 14 additions & 4 deletions src/commands/moderation/ModerationEditCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export default class ModerationEditCommand extends CompletingModerationCommand {
.setDescription('New moderation reason')
.setRequired(false)
);
builder.addStringOption(option =>
option.setName('comment')
.setDescription('New moderation comment')
.setRequired(false)
);
builder.addStringOption(option =>
option.setName('duration')
.setDescription('New moderation duration (since moderation creation)')
Expand All @@ -41,11 +46,12 @@ export default class ModerationEditCommand extends CompletingModerationCommand {
return;
}

const reason = interaction.options.getString('reason');
let duration = interaction.options.getString('duration'),
count = interaction.options.getInteger('count');
const reason = interaction.options.getString('reason'),
comment = interaction.options.getString('comment'),
duration = interaction.options.getString('duration');
let count = interaction.options.getInteger('count');

if (!reason && !duration && !count) {
if (!reason && !duration && !count && !comment) {
await interaction.reply(ErrorEmbed.message('You need to provide at least one option you want to change'));
return;
}
Expand All @@ -54,6 +60,10 @@ export default class ModerationEditCommand extends CompletingModerationCommand {
moderation.reason = reason;
}

if (comment) {
moderation.comment = comment;
}

if (duration) {
if (!moderation.active) {
await interaction.reply(ErrorEmbed.message('You can\'t update the duration of inactive moderations!'));
Expand Down
4 changes: 2 additions & 2 deletions src/commands/moderation/ModerationListCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ export default class ModerationListCommand extends SubCommand {
}

const limit = Math.min(
EMBED_TOTAL_LIMIT / MODERATIONS_PER_PAGE - lines.join('\n').length,
EMBED_TOTAL_LIMIT / MODERATIONS_PER_PAGE,
EMBED_FIELD_LIMIT
);
) - lines.join('\n').length;
const reason = moderation.reason.length < limit ? moderation.reason
: moderation.reason.slice(0, limit - 3) + '...';
lines.push(`Reason: ${reason}`);
Expand Down
15 changes: 2 additions & 13 deletions src/commands/user/BanCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,7 @@ import {deferReplyOnce, replyOrEdit} from '../../util/interaction.js';
export default class BanCommand extends UserCommand {

buildOptions(builder) {
builder.addUserOption(option =>
option
.setName('user')
.setDescription('The user you want to ban')
.setRequired(true)
);
builder.addStringOption(option =>
option.setName('reason')
.setDescription('Ban reason')
.setRequired(false)
.setAutocomplete(true)
);
super.buildOptions(builder);
builder.addStringOption(option =>
option.setName('duration')
.setDescription('Ban duration')
Expand All @@ -42,7 +31,7 @@ export default class BanCommand extends UserCommand {
.setDescription('Delete message history for this time frame')
.setRequired(false)
);
return super.buildOptions(builder);
return builder;
}

getDefaultMemberPermissions() {
Expand Down
16 changes: 0 additions & 16 deletions src/commands/user/KickCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,6 @@ import {deferReplyOnce, replyOrEdit} from '../../util/interaction.js';

export default class KickCommand extends UserCommand {

buildOptions(builder) {
builder.addUserOption(option =>
option
.setName('user')
.setDescription('The user you want to kick')
.setRequired(true)
);
builder.addStringOption(option =>
option.setName('reason')
.setDescription('Kick reason')
.setRequired(false)
.setAutocomplete(true)
);
return super.buildOptions(builder);
}

getDefaultMemberPermissions() {
return new PermissionsBitField()
.add(PermissionFlagsBits.KickMembers);
Expand Down
15 changes: 2 additions & 13 deletions src/commands/user/MuteCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,14 @@ export default class MuteCommand extends UserCommand {


buildOptions(builder) {
builder.addUserOption(option =>
option
.setName('user')
.setDescription('The user you want to mute')
.setRequired(true)
);
builder.addStringOption(option =>
option.setName('reason')
.setDescription('Mute reason')
.setRequired(false)
.setAutocomplete(true)
);
super.buildOptions(builder);
builder.addStringOption(option =>
option.setName('duration')
.setDescription('Mute duration')
.setRequired(false)
.setAutocomplete(true)
);
return super.buildOptions(builder);
return builder;
}

getDefaultMemberPermissions() {
Expand Down
15 changes: 2 additions & 13 deletions src/commands/user/SoftBanCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,13 @@ import {deferReplyOnce, replyOrEdit} from '../../util/interaction.js';
export default class SoftBanCommand extends UserCommand {

buildOptions(builder) {
builder.addUserOption(option =>
option
.setName('user')
.setDescription('The user you want to soft-ban')
.setRequired(true)
);
builder.addStringOption(option =>
option.setName('reason')
.setDescription('Soft-ban reason')
.setRequired(false)
.setAutocomplete(true)
);
super.buildOptions(builder);
builder.addStringOption(option =>
option.setName('delete')
.setDescription('Delete message history for this time frame')
.setRequired(false)
);
return super.buildOptions(builder);
return builder;
}

getDefaultMemberPermissions() {
Expand Down
15 changes: 2 additions & 13 deletions src/commands/user/StrikeCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,15 @@ export default class StrikeCommand extends UserCommand {
* @return {import('discord.js').SlashCommandBuilder}
*/
buildOptions(builder) {
builder.addUserOption(option =>
option
.setName('user')
.setDescription('The user you want to strike')
.setRequired(true)
);
builder.addStringOption(option =>
option.setName('reason')
.setDescription('Strike reason')
.setRequired(false)
.setAutocomplete(true)
);
super.buildOptions(builder);
builder.addIntegerOption(option =>
option.setName('count')
.setDescription('Strike count')
.setRequired(false)
.setMinValue(1)
.setMaxValue(100)
);
return super.buildOptions(builder);
return builder;
}

getDefaultMemberPermissions() {
Expand Down
65 changes: 51 additions & 14 deletions src/commands/user/UserCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@ const CONFIRMATION_DURATION = 15 * 60;
* @abstract
*/
export default class UserCommand extends Command {
buildOptions(builder) {
builder.addUserOption(option =>
option
.setName('user')
.setDescription('The target user')
.setRequired(true)
);
builder.addStringOption(option =>
option.setName('reason')
.setDescription('Reason for the moderation shown to the user')
.setRequired(false)
.setAutocomplete(true)
);
builder.addStringOption(option =>
option.setName('comment')
.setDescription('Internal comment for moderators')
.setRequired(false)
.setAutocomplete(true)
);
return super.buildOptions(builder);
}

/**
* check if this member can be moderated by this moderator
* @param {import('discord.js').Interaction} interaction
Expand Down Expand Up @@ -97,7 +119,8 @@ export default class UserCommand extends Command {
.addPair('Timestamp', time(result.created, TimestampStyles.ShortTime))
.addPairIf(result.expireTime, 'Duration', formatTime(result.getDuration()))
.addPairIf(result.value, 'Strikes', result.value)
.addPair('Reason', result.reason.slice(0, 200))
.addPairIf(result.reason, 'Reason', result.reason?.slice(0, 200))
.addPairIf(result.comment, 'Comment', result.comment?.slice(0, 200))
.newLine();
}

Expand All @@ -110,19 +133,9 @@ export default class UserCommand extends Command {
async complete(interaction) {
const focussed = interaction.options.getFocused(true);
switch (focussed.name) {
case 'reason': {
if (focussed.value?.length > AUTOCOMPLETE_NAME_LIMIT) {
return [];
}

const options = await database.queryAll(
'SELECT reason, COUNT(*) AS count FROM (SELECT reason FROM moderations WHERE moderator = ? AND guildid = ? AND action = ? LIMIT 500) AS reasons WHERE reason LIKE CONCAT(\'%\', ?, \'%\') AND LENGTH(reason) <= ? GROUP BY reason ORDER BY count DESC LIMIT 5;',
interaction.user.id, interaction.guild.id, this.getName(), focussed.value, AUTOCOMPLETE_NAME_LIMIT);
if (focussed.value) {
options.unshift({reason: focussed.value});
}
return options.map(data => ({name: data.reason, value: data.reason}));
}
case 'reason':
case 'comment':
return this.completeFromHistory(interaction, focussed, focussed.name);

case 'duration':{
let options = await database.queryAll(
Expand All @@ -141,4 +154,28 @@ export default class UserCommand extends Command {

return super.complete(interaction);
}

/**
* Complete an option using the history of previous values for this option
* @param {import('discord.js').AutocompleteInteraction} interaction
* @param {import('discord.js').AutocompleteFocusedOption} focussed
* @param {string} column the database column name
* @returns {Promise<{name: *, value: *}[]|*[]>}
*/
async completeFromHistory(interaction, focussed, column) {
if (focussed.value?.length > AUTOCOMPLETE_NAME_LIMIT) {
return [];
}

const options = await database.queryAll(
'SELECT value, COUNT(*) AS count FROM (' +
`SELECT ${database.escapeId(column)} AS value FROM moderations WHERE moderator = ? AND guildid = ? AND action = ? LIMIT 500` +
') AS results WHERE value LIKE CONCAT(\'%\', ?, \'%\') AND LENGTH(value) <= ? ' +
'GROUP BY value ORDER BY count DESC LIMIT 5;',
interaction.user.id, interaction.guild.id, this.getName(), focussed.value, AUTOCOMPLETE_NAME_LIMIT);
if (focussed.value) {
options.unshift({reason: focussed.value});
}
return options.map(data => ({name: data.value, value: data.value}));
}
}
2 changes: 2 additions & 0 deletions src/commands/user/UserInfoCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export default class UserInfoCommand extends Command {
if (mute.end) {
embed.addPair(inlineEmojiIfExists('mute') + 'Muted until', time(Math.floor(mute.end / 1_000)));
}
embed.addPairIf(mute.comment, inlineEmojiIfExists('mute') + 'Mute comment', mute.comment);
actionRow.addComponents(
/** @type {*} */ new BetterButtonBuilder()
.setLabel('Unmute')
Expand Down Expand Up @@ -160,6 +161,7 @@ export default class UserInfoCommand extends Command {
if (ban.end) {
embed.addPair(inlineEmojiIfExists('ban') + 'Banned until', time(Math.floor(ban.end / 1_000)));
}
embed.addPairIf(ban.comment, inlineEmojiIfExists('ban') + 'Ban comment', ban.comment);
actionRow.addComponents(
/** @type {*} */ new BetterButtonBuilder()
.setLabel('Unban')
Expand Down
Loading

0 comments on commit 71e2f39

Please sign in to comment.