Skip to content

Commit

Permalink
improve ban command input
Browse files Browse the repository at this point in the history
  • Loading branch information
hyper-neutrino committed Jun 23, 2024
1 parent 41a2dab commit 7f7aa30
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 14 deletions.
35 changes: 27 additions & 8 deletions src/commandDetails/admin/ban.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
import { banUser } from '../../components/admin';
import { vars } from '../../config';
import { DEFAULT_EMBED_COLOUR } from '../../utils/embeds.js';
import { pluralize } from '../../utils/pluralize';
import { DurationStyle, formatDuration } from '../../utils/formatDuration.js';
import { parseDuration } from '../../utils/parseDuration.js';
import { CodeyUserError } from './../../codeyUserError';

const NOTIF_CHANNEL_ID: string = vars.NOTIF_CHANNEL_ID;
Expand All @@ -35,10 +36,22 @@ const banExecuteCommand: SapphireMessageExecuteType = async (client, messageFrom
'please enter a valid reason why you are banning the user.',
);
}
const days = <number>args['days'];
const duration = parseDuration(<string>args['duration']);

if (duration === null) {
throw new CodeyUserError(
messageFromUser,
'please enter a valid duration (e.g. 7d, 3h, 1h30m).',
);
}

if (duration > 7 * 24 * 60 * 60 * 1000) {
throw new CodeyUserError(messageFromUser, 'cannot purge more than 7 days of messages.');
}

// get Guild object corresponding to server
const guild = await client.guilds.fetch(vars.TARGET_GUILD_ID);
if (await banUser(guild, user, reason, days)) {
if (await banUser(guild, user, reason, duration)) {
const mod = getUserFromMessage(messageFromUser);
const banEmbed = new EmbedBuilder()
.setTitle('Ban')
Expand All @@ -52,14 +65,19 @@ const banExecuteCommand: SapphireMessageExecuteType = async (client, messageFrom
{ name: 'Reason', value: reason },
{
name: 'Messages Purged',
value: !days ? 'None' : `Past ${days} ${pluralize('day', days)}`,
value: !duration ? 'None' : `Past ${formatDuration(duration, DurationStyle.Blank)}`,
},
]);
(client.channels.cache.get(NOTIF_CHANNEL_ID) as TextChannel).send({
embeds: [banEmbed],
});
return `Successfully banned user ${user.tag} (id: ${user.id}) ${
days ? `and deleted their messages in the past ${days} ${pluralize('day', days)} ` : ``
duration
? `and deleted their messages in the past ${formatDuration(
duration,
DurationStyle.Blank,
)} `
: ``
}for the following reason: ${reason}`;
} else {
throw new CodeyUserError(
Expand Down Expand Up @@ -98,9 +116,10 @@ export const banCommandDetails: CodeyCommandDetails = {
required: true,
},
{
name: 'days',
description: "Messages in last 'days' days from user are deleted. Default is 0 days.",
type: CodeyCommandOptionType.INTEGER,
name: 'duration',
description:
'Messages within the specified time (e.g. 1 day, 2h30m) from user are deleted. Default 0d, max 7d.',
type: CodeyCommandOptionType.STRING,
required: false,
},
],
Expand Down
17 changes: 11 additions & 6 deletions src/components/admin.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { Guild, User } from 'discord.js';
import { vars } from '../config';
import { logger } from '../logger/default';
import { pluralize } from '../utils/pluralize';
import { DurationStyle, formatDuration } from '../utils/formatDuration.js';

const MOD_USER_ID_FOR_BAN_APPEAL: string = vars.MOD_USER_ID_FOR_BAN_APPEAL;

/* Make ban message */
const makeBanMessage = (reason: string, days?: number): string =>
const makeBanMessage = (reason: string, duration?: number): string =>
`
Uh oh, you have been banned from the UW Computer Science Club server ${
days ? `and your messages in the past ${days} ${pluralize('day', days)} have been deleted ` : ''
duration
? `and your messages in the past ${formatDuration(
duration,
DurationStyle.Blank,
)} have been deleted `
: ''
}for the following reason:
> ${reason}
Expand All @@ -25,12 +30,12 @@ export const banUser = async (
guild: Guild,
user: User,
reason: string,
days?: number,
duration?: number,
): Promise<boolean> => {
let isSuccessful = false;
try {
try {
await user.send(makeBanMessage(reason, days));
await user.send(makeBanMessage(reason, duration));
} catch (err) {
logger.error({
event: "Can't send message to user not in server",
Expand All @@ -39,7 +44,7 @@ export const banUser = async (
}
await guild.members.ban(user, {
reason: reason,
deleteMessageSeconds: days == null ? 0 : days * 86400,
deleteMessageSeconds: duration === undefined ? 0 : Math.floor(duration / 1000),
});
isSuccessful = true;
} catch (err) {
Expand Down
61 changes: 61 additions & 0 deletions src/utils/formatDuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Formats a duration in milliseconds into English text.
* @param duration - The duration in milliseconds
* @param style - The style to format the duration in
* @returns The formatted duration
*/
export const formatDuration = (duration: number, style = DurationStyle.For): string => {
if (duration === Infinity) return 'indefinitely';

duration = Math.round(duration / 1000);

if (duration < 0) {
const core = _formatDuration(-duration);
if (style === DurationStyle.Blank) return `negative ${core}`;
if (style === DurationStyle.For) return `for negative ${core}`;
if (style === DurationStyle.Until) return `until ${core} ago`;
}

if (duration === 0) {
if (style === DurationStyle.Blank) return 'no time';
if (style === DurationStyle.For) return 'for no time';
if (style === DurationStyle.Until) return 'until right now';
}

const core = _formatDuration(duration);
if (style === DurationStyle.Blank) return core;
if (style === DurationStyle.For) return `for ${core}`;
if (style === DurationStyle.Until) return `until ${core} from now`;

return '??';
};

function _formatDuration(duration: number): string {
if (duration === Infinity) return 'indefinitely';

const parts: string[] = [];

for (const [name, scale] of formatTimescales) {
if (duration >= scale) {
const amount = Math.floor(duration / scale);
duration %= scale;

parts.push(`${amount} ${name}${amount === 1 ? '' : 's'}`);
}
}

return parts.join(' ');
}

export enum DurationStyle {
Blank,
For,
Until,
}

const formatTimescales: [string, number][] = [
['day', 86400],
['hour', 3600],
['minute', 60],
['second', 1],
];
31 changes: 31 additions & 0 deletions src/utils/parseDuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Parses English text into a duration in milliseconds. Works for long for (1 day,
* 3 weeks 2 hours) and short form (1d, 3w2h).
*
* @param text - The text to parse
* @returns The duration in milliseconds or null if invalid
*/
export const parseDuration = (text: string): number | null => {
const match = text.match(
/^(\d+\s*w(eeks?)?\s*)?(\d+\s*d(ays?)?\s*)?(\d+\s*h((ou)?rs?)?\s*)?(\d+\s*m(in(ute)?s?)?\s*)?(\d+\s*s(ec(ond)?s?)?\s*)?$/,
);

if (!match) return null;

let duration = 0;

for (const [index, scale] of parseTimescales) {
const submatch = match[index]?.match(/\d+/);
if (submatch) duration += parseInt(submatch[0]) * scale;
}

return duration;
};

const parseTimescales = [
[1, 604800000],
[3, 86400000],
[5, 3600000],
[8, 60000],
[11, 1000],
];

0 comments on commit 7f7aa30

Please sign in to comment.