Skip to content

Commit

Permalink
feat: allow users to create and edit categories to have images and co…
Browse files Browse the repository at this point in the history
…lors
  • Loading branch information
Uhuh committed Aug 19, 2023
1 parent 5496812 commit f0a8e12
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 70 deletions.
79 changes: 57 additions & 22 deletions commands/category/create.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import {
ApplicationCommandOptionType,
ChatInputCommandInteraction,
} from 'discord.js';
import { ApplicationCommandOptionType, ChatInputCommandInteraction, } from 'discord.js';
import { DisplayType } from '../../src/database/entities/category.entity';
import { CREATE_GUILD_CATEGORY, GET_CATEGORY_BY_NAME, } from '../../src/database/queries/category.query';
import {
CREATE_GUILD_CATEGORY,
GET_CATEGORY_BY_NAME,
} from '../../src/database/queries/category.query';
import {
getDisplayCommandValues,
getDisplayCommandChoices,
getImageTypeCommandChoices,
parseDisplayString,
parseImageTypeString,
} from '../../utilities/utils';
import { SlashSubCommand } from '../command';

Expand All @@ -20,6 +16,9 @@ const enum CommandOptionNames {
RequiredRole = 'required-role',
ExcludedRole = 'excluded-role',
DisplayOrder = 'display-order',
ImageType = 'image-type',
ImageUrl = 'image-url',
EmbedColor = 'embed-color',
}

export class CreateSubCommand extends SlashSubCommand {
Expand Down Expand Up @@ -61,7 +60,23 @@ export class CreateSubCommand extends SlashSubCommand {
name: CommandOptionNames.DisplayOrder,
description: 'Change how the category displays the react roles.',
type: ApplicationCommandOptionType.String,
choices: getDisplayCommandValues(),
choices: getDisplayCommandChoices(),
},
{
name: CommandOptionNames.ImageType,
description: 'How images will layout in your embed.',
type: ApplicationCommandOptionType.String,
choices: getImageTypeCommandChoices(),
},
{
name: CommandOptionNames.ImageUrl,
description: 'Use an image hosting site and link it here, imgur for example.',
type: ApplicationCommandOptionType.String,
},
{
name: CommandOptionNames.EmbedColor,
description: 'The hexcode you want the embed sidebar to be. Don\'t include the #.',
type: ApplicationCommandOptionType.String,
},
]
);
Expand All @@ -76,37 +91,52 @@ export class CreateSubCommand extends SlashSubCommand {
ephemeral: true,
});

const name = interaction.options.getString(CommandOptionNames.Name);
// Essentials
const name = this.expect(interaction.options.getString(CommandOptionNames.Name), {
message: 'Hey! The category name is required when creating a category.',
prop: CommandOptionNames.Name,
});
const description = interaction.options.getString(CommandOptionNames.Description);

// "Permissions"
const mutuallyExclusive =
interaction.options.getBoolean(CommandOptionNames.MutuallyExclusive) ?? false;
const requiredRoleId =
interaction.options.getRole(CommandOptionNames.RequiredRole)?.id ?? null;
const excludedRoleId =
interaction.options.getRole(CommandOptionNames.ExcludedRole)?.id ?? null;
const displayString = interaction.options.getString(CommandOptionNames.DisplayOrder);


// Embed styling options
const imageTypeString = interaction.options.getString(CommandOptionNames.ImageType);
const imageUrl = interaction.options.getString(CommandOptionNames.ImageUrl);
let embedColor = interaction.options.getString(CommandOptionNames.EmbedColor);

const imageType = parseImageTypeString(imageTypeString);
const displayOrder = parseDisplayString(
displayString as keyof typeof DisplayType
);

if (!name) {
return interaction.editReply(
`Hey! The category name is required when creating a category.`
);
} else if (name.length > 90) {
if (name.length > 90) {
// Discord max embed title is 100 so let's be safe and make it smaller.
return interaction.editReply(
`Hey! Discord only allows 100 characters max for their embed titles. Try making the category name simple and make the rest the category description!`
);
}

if (await GET_CATEGORY_BY_NAME(interaction.guildId, name)) {
} else if (await GET_CATEGORY_BY_NAME(interaction.guildId, name)) {
return interaction.editReply(
`Hey! It turns out you already have a category with that name made. Try checking it out.`
);
}

const hexRegex = new RegExp(/[0-9A-F]{6}$/gi);
const isCorrectHex = hexRegex.test(embedColor ?? '');

// If the user input an incorrect hex value, just default to whatever
if (!isCorrectHex && embedColor) {
// This is a shade of purple I like :)
embedColor = '945ad2';
}

try {
await CREATE_GUILD_CATEGORY({
guildId: interaction.guildId,
Expand All @@ -115,16 +145,21 @@ export class CreateSubCommand extends SlashSubCommand {
mutuallyExclusive,
requiredRoleId,
excludedRoleId,
displayOrder,
displayOrder: displayOrder ?? DisplayType.alpha,
imageType: imageType ?? 'card',
imageUrl,
embedColor,
});

this.log.info(
`Successfully created category[${name}]`,
interaction.guildId
);

const invalidHex = `\n\nAn invalid hex code was provided. Remember, hex codes look like this \`ff0000\`. Use an online tool to make one.`;

await interaction.editReply(
`Hey! I successfully created the category \`${name}\` for you!`
`Hey! I successfully created the category \`${name}\` for you!${(embedColor && !isCorrectHex) ? invalidHex : ''}`
);
} catch (e) {
this.log.error(
Expand Down
128 changes: 80 additions & 48 deletions commands/category/edit.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import {
ApplicationCommandOptionType,
AutocompleteInteraction,
ChatInputCommandInteraction,
} from 'discord.js';
import {
DisplayType,
ICategory,
} from '../../src/database/entities/category.entity';
import {
EDIT_CATEGORY_BY_ID,
GET_CATEGORY_BY_ID,
} from '../../src/database/queries/category.query';
import { ApplicationCommandOptionType, AutocompleteInteraction, ChatInputCommandInteraction, } from 'discord.js';
import { DisplayType, ICategory, } from '../../src/database/entities/category.entity';
import { EDIT_CATEGORY_BY_ID, GET_CATEGORY_BY_ID, } from '../../src/database/queries/category.query';
import { handleAutocompleteCategory } from '../../utilities/utilAutocomplete';
import {
getDisplayCommandValues,
getDisplayCommandChoices,
getImageTypeCommandChoices,
parseDisplayString,
parseImageTypeString,
} from '../../utilities/utils';
import { SlashSubCommand } from '../command';

Expand All @@ -27,6 +19,9 @@ const enum CommandOptionNames {
RequiredRole = 'new-required-role',
ExcludedRole = 'new-excluded-role',
DisplayOrder = 'display-order',
ImageType = 'image-type',
ImageUrl = 'image-url',
EmbedColor = 'embed-color',
}

export class EditSubCommand extends SlashSubCommand {
Expand Down Expand Up @@ -78,7 +73,23 @@ export class EditSubCommand extends SlashSubCommand {
name: CommandOptionNames.DisplayOrder,
description: 'Change display order',
type: ApplicationCommandOptionType.String,
choices: getDisplayCommandValues(),
choices: getDisplayCommandChoices(),
},
{
name: CommandOptionNames.ImageType,
description: 'How images will layout in your embed.',
type: ApplicationCommandOptionType.String,
choices: getImageTypeCommandChoices(),
},
{
name: CommandOptionNames.ImageUrl,
description: 'Use an image hosting site and link it here, use [remove] to remove the image.',
type: ApplicationCommandOptionType.String,
},
{
name: CommandOptionNames.EmbedColor,
description: 'A hexcode without the #. Example `FFFFFF` for white, use [remove] to remove the color.',
type: ApplicationCommandOptionType.String,
},
]);
}
Expand All @@ -104,38 +115,13 @@ export class EditSubCommand extends SlashSubCommand {
ephemeral: true,
});

const categoryId = this.expect(interaction.options.getString(CommandOptionNames.Category), {
message: 'Category appears to be invalid!',
prop: CommandOptionNames.Category,
});

/**
* All the options from the slash command.
* The options on data are the options for this current command
* If the length is less than 1, we know no options were passed therefore there is nothing to edit.
*/
let newDesc = interaction.options.getString(CommandOptionNames.Description);
const newName = interaction.options.getString(CommandOptionNames.Name);
const mutuallyExclusive =
interaction.options.getBoolean(CommandOptionNames.MutuallyExclusive);
const removeRoleType = interaction.options.getString(CommandOptionNames.RemoveRoleType);
const newRequiredRoleId =
interaction.options.getRole(CommandOptionNames.RequiredRole)?.id ?? undefined;
const newExcludedRoleId =
interaction.options.getRole(CommandOptionNames.ExcludedRole)?.id ?? undefined;
const displayString = interaction.options.getString(CommandOptionNames.DisplayOrder);
const { options } = interaction.options.data[0];

const displayOrder = parseDisplayString(
displayString as keyof typeof DisplayType,
);

if (
!newName &&
!newDesc &&
!displayString &&
!newRequiredRoleId &&
!newExcludedRoleId &&
removeRoleType === null &&
mutuallyExclusive === null
) {
if (options && options.length <= 1) {
this.log.info(
`User didn't change anything about the category`,
interaction.guildId,
Expand All @@ -146,6 +132,12 @@ export class EditSubCommand extends SlashSubCommand {
);
}

// Now that we know there is something to edit, get the category!
const categoryId = this.expect(interaction.options.getString(CommandOptionNames.Category), {
message: 'Category appears to be invalid!',
prop: CommandOptionNames.Category,
});

const category = await GET_CATEGORY_BY_ID(Number(categoryId));

if (!category) {
Expand All @@ -159,16 +151,55 @@ export class EditSubCommand extends SlashSubCommand {
);
}

/**
* All the options from the slash command.
*/
let newDesc = interaction.options.getString(CommandOptionNames.Description);
const newName = interaction.options.getString(CommandOptionNames.Name);
const mutuallyExclusive =
interaction.options.getBoolean(CommandOptionNames.MutuallyExclusive);
const removeRoleType = interaction.options.getString(CommandOptionNames.RemoveRoleType);
const newRequiredRoleId =
interaction.options.getRole(CommandOptionNames.RequiredRole)?.id ?? undefined;
const newExcludedRoleId =
interaction.options.getRole(CommandOptionNames.ExcludedRole)?.id ?? undefined;
const displayString = interaction.options.getString(CommandOptionNames.DisplayOrder);

// Embed styling options
const imageTypeString = interaction.options.getString(CommandOptionNames.ImageType);
const imageUrl = interaction.options.getString(CommandOptionNames.ImageUrl);
let embedColor = interaction.options.getString(CommandOptionNames.EmbedColor);

const imageType = parseImageTypeString(imageTypeString);
const displayOrder = parseDisplayString(
displayString as keyof typeof DisplayType,
);

const requiredRoleId = newRequiredRoleId ?? category.requiredRoleId;
const excludedRoleId = newExcludedRoleId ?? category.excludedRoleId;

// Check if the user wants to remove the description.
// Check for removals
newDesc = newDesc?.trim() === '[remove]' ? '' : newDesc;

const removeImageUrl = imageUrl?.trim() === '[remove]';
const removeEmbedColor = embedColor?.trim() === '[remove]';

const hexRegex = new RegExp(/[0-9A-F]{6}$/gi);
const isCorrectHex = hexRegex.test(embedColor ?? '');

// If the user input an incorrect hex value, just default to whatever
if (!isCorrectHex && embedColor) {
// This is a shade of purple I like :)
embedColor = '945ad2';
}

const updatedCategory: Partial<ICategory> = {
name: newName ?? category.name,
description: newDesc ?? category.description,
mutuallyExclusive: mutuallyExclusive ?? category.mutuallyExclusive,
displayOrder: displayOrder ?? category.displayOrder,
imageType: imageType ?? category.imageType,
imageUrl: removeImageUrl ? null : imageUrl ?? category.imageUrl,
embedColor: removeEmbedColor ? null : embedColor ?? category.embedColor,
requiredRoleId:
removeRoleType === 'required-role'
? null
Expand All @@ -177,7 +208,6 @@ export class EditSubCommand extends SlashSubCommand {
removeRoleType === 'excluded-role'
? null
: excludedRoleId,
displayOrder,
};

EDIT_CATEGORY_BY_ID(category.id, updatedCategory)
Expand All @@ -187,8 +217,10 @@ export class EditSubCommand extends SlashSubCommand {
interaction.guildId,
);

const invalidHex = `\n\nAn invalid hex code was provided. Remember, hex codes look like this \`#ff0000\`. Use an online tool to make one.`;

return interaction.editReply(
`Hey! I successfully updated the category \`${category.name}\` for you.`,
`Hey! I successfully updated the category \`${category.name}\` for you.${(embedColor && !isCorrectHex) ? invalidHex : ''}`
);
})
.catch((e) =>
Expand Down
1 change: 1 addition & 0 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default class RoleBot extends Discord.Client {
Discord.Partials.Channel,
Discord.Partials.Reaction,
],
// RoleBot does a lot of role "pings" for visuals, don't allow it to actually mention roles.
allowedMentions: { parse: [] },
});
this.config = config;
Expand Down

0 comments on commit f0a8e12

Please sign in to comment.