Development (#91)
diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts
index 6997393..9c9430d 100644
--- a/src/actions/roleMenu.ts
+++ b/src/actions/roleMenu.ts
@@ -45,7 +45,8 @@
export const configToDropdown = (
placeholder: string,
currentPageData: ObjectSchema,
- selectedRoles?: string[]
+ selectedRoles?: string[],
+ disabled?: boolean
): ActionRowBuilder<StringSelectMenuBuilder> => {
return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
new StringSelectMenuBuilder()
@@ -53,6 +54,7 @@
.setPlaceholder(placeholder)
.setMinValues(currentPageData.min)
.setMaxValues(currentPageData.max)
+ .setDisabled(disabled)
.addOptions(
currentPageData.options.map((option: { name: string; description: string | null; role: string }) => {
const builder = new StringSelectMenuOptionBuilder()
diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts
index c95e78c..8c32ef6 100644
--- a/src/commands/nucleus/guide.ts
+++ b/src/commands/nucleus/guide.ts
@@ -6,7 +6,7 @@
builder.setName("guide").setDescription("Shows the welcome guide for the bot");
const callback = async (interaction: CommandInteraction) => {
- await guide(interaction.guild!, interaction);
+ await guide(interaction.guild, interaction);
};
export { command };
diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts
index fed2964..5894850 100644
--- a/src/commands/nucleus/premium.ts
+++ b/src/commands/nucleus/premium.ts
@@ -165,7 +165,10 @@
const gaveUser = await client.users.fetch(hasPremium[1]);
premiumGuild = `**This server has premium! It was ${
hasPremium[2] === 3 && hasPremium[3]
- ? `automatically applied by ${gaveUser.username}#${gaveUser.discriminator}`
+ ? `automatically applied by ` +
+ (gaveUser.discriminator !== "0"
+ ? `${gaveUser.username}#${gaveUser.discriminator}`
+ : `@${gaveUser.username}`)
: `given by <@${hasPremium[1]}>`
}**\n\n`;
}
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index c1f0312..1110b9a 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -19,7 +19,6 @@
builder.setName("suggest").setDescription("Sends a suggestion to the developers");
const callback = async (interaction: CommandInteraction): Promise<void> => {
- await interaction.guild?.members.fetch(interaction.member!.user.id);
await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
let closed = false;
let suggestionTitle: string | null = null;
@@ -90,18 +89,25 @@
suggestionTitle = suggestionTitle ? suggestionTitle : `${suggestionDesc.substring(0, 70)}`;
const channel = client.channels.cache.get(config.suggestionChannel) as Discord.TextChannel;
const m = await channel.send({ embeds: LoadingEmbed });
- const issue = await client.GitHub.rest.issues.create({
- owner: "ClicksMinutePer",
- repo: "Nucleus",
- title: suggestionTitle,
- body: `Linked Suggestion in Private Developer Channel: [Message](${
- m.url
- })\n\n**Suggestion:**\n> ${suggestionDesc
- .replaceAll("@", "@<!-- -->")
- .replaceAll("/issues", "/issues<!-- -->")
- .replaceAll("/pull", "/pull<!-- -->")}\n\n`,
- labels: ["🤖 Auto", "📝 Suggestion"]
- });
+ let issueNumber: number | null = null;
+ try {
+ const issue = await client.GitHub.rest.issues.create({
+ owner: "ClicksMinutePer",
+ repo: "Nucleus",
+ title: suggestionTitle,
+ body: `Linked Suggestion in Private Developer Channel: [Message](${
+ m.url
+ })\n\n**Suggestion:**\n> ${suggestionDesc
+ .replaceAll("@", "@<!-- -->")
+ .replaceAll("/issues", "/issues<!-- -->")
+ .replaceAll("/pull", "/pull<!-- -->")}\n\n`,
+ labels: ["🤖 Auto", "📝 Suggestion"]
+ });
+ issueNumber = issue.data.number;
+ } catch (_e) {
+ console.log("Could not connect to GitHub");
+ }
+ const disabled = issueNumber ? false : true;
await m.edit({
embeds: [
new EmojiEmbed()
@@ -109,25 +115,47 @@
.setTitle(`Suggestion from ${interaction.user.tag} (${interaction.user.id})`)
.setDescription(`**Suggestion:**\n> ${suggestionDesc}\n\n`)
.setStatus("Success")
- .setFooter({ text: `${issue.data.number}` })
+ .setFooter({ text: `${issueNumber ? issueNumber : "Could not connect to GitHub"}` })
],
components: [
new Discord.ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder().setCustomId("accept:Suggestion").setLabel("Accept").setStyle(ButtonStyle.Success),
- new ButtonBuilder().setCustomId("deny:Suggestion").setLabel("Deny").setStyle(ButtonStyle.Danger),
- new ButtonBuilder().setCustomId("close:Suggestion").setLabel("Close").setStyle(ButtonStyle.Secondary),
+ new ButtonBuilder()
+ .setCustomId("accept:Suggestion")
+ .setLabel("Accept")
+ .setStyle(ButtonStyle.Success)
+ .setDisabled(disabled),
+ new ButtonBuilder()
+ .setCustomId("deny:Suggestion")
+ .setLabel("Deny")
+ .setStyle(ButtonStyle.Danger)
+ .setDisabled(disabled),
+ new ButtonBuilder()
+ .setCustomId("close:Suggestion")
+ .setLabel("Close")
+ .setStyle(ButtonStyle.Secondary)
+ .setDisabled(disabled),
new ButtonBuilder()
.setCustomId("implemented:Suggestion")
.setLabel("Implemented")
- .setStyle(ButtonStyle.Secondary),
+ .setStyle(ButtonStyle.Secondary)
+ .setDisabled(disabled),
new ButtonBuilder()
- .setLabel(`Open Issue #${issue.data.number}`)
+ .setLabel(`Open Issue #${issueNumber ? issueNumber : "0"}`)
.setStyle(ButtonStyle.Link)
- .setURL(`https://github.com/ClicksMinutePer/Nucleus/issues/${issue.data.number}`)
+ .setURL(`https://github.com/ClicksMinutePer/Nucleus/issues/${issueNumber}`)
+ .setDisabled(disabled)
),
new Discord.ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder().setCustomId("lock:Suggestion").setLabel("Lock").setStyle(ButtonStyle.Danger),
- new ButtonBuilder().setCustomId("spam:Suggestion").setLabel("Mark as Spam").setStyle(ButtonStyle.Danger)
+ new ButtonBuilder()
+ .setCustomId("lock:Comment")
+ .setLabel("Lock")
+ .setStyle(ButtonStyle.Danger)
+ .setDisabled(disabled),
+ new ButtonBuilder()
+ .setCustomId("spam:Suggestion")
+ .setLabel("Mark as Spam")
+ .setStyle(ButtonStyle.Danger)
+ .setDisabled(disabled)
)
]
});
diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts
index 7da9dfe..0b41854 100644
--- a/src/commands/settings/rolemenu.ts
+++ b/src/commands/settings/rolemenu.ts
@@ -120,6 +120,7 @@
.setStyle(TextInputStyle.Short)
.setValue(name ?? "")
.setRequired(true)
+ .setMaxLength(100)
),
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
@@ -128,6 +129,8 @@
.setPlaceholder("A short description of the role (e.g. A role for people who code)")
.setStyle(TextInputStyle.Short)
.setValue(description ?? "")
+ .setRequired(false)
+ .setMaxLength(100)
)
);
const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
@@ -159,15 +162,15 @@
if (!out) return [name, description];
if (out.isButton()) return [name, description];
name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
- description = out.fields.fields.find((f) => f.customId === "description")?.value ?? description;
+ description = out.fields.fields.find((f) => f.customId === "description")?.value ?? "";
return [name, description];
};
const defaultRoleMenuData = {
- name: "Role Menu Page",
- description: "A new role menu page",
+ name: "New Page",
+ description: "",
min: 0,
- max: 0,
+ max: 1,
options: []
};
@@ -196,17 +199,20 @@
);
let back = false;
- if (data.options.length === 0) {
- data.options = [{ name: "Role 1", description: null, role: "No role set" }];
- }
do {
- const previewSelect = configToDropdown("Edit Roles", {
- name: data.name,
- description: data.description,
- min: 1,
- max: 1,
- options: data.options
- });
+ const noRoles = data.options.length === 0;
+ const previewSelect = configToDropdown(
+ "Edit Roles",
+ {
+ name: data.name,
+ description: data.description,
+ min: 1,
+ max: 1,
+ options: noRoles ? [{ name: "Role 1", description: null, role: "No role set" }] : data.options
+ },
+ undefined,
+ noRoles
+ );
const embed = new EmojiEmbed()
.setTitle(`${data.name}`)
.setStatus("Success")
@@ -215,7 +221,8 @@
`**Min:** ${data.min}` +
(data.min === 0 ? " (Members will be given a skip button)" : "") +
"\n" +
- `**Max:** ${data.max}\n`
+ `**Max:** ${data.max}\n` +
+ `\n**Roles:** ${data.options.length === 0 ? "*No roles set*" : data.options.length}`
);
await interaction.editReply({ embeds: [embed], components: [previewSelect, buttons] });
@@ -237,7 +244,8 @@
await createRoleMenuOptionPage(
interaction,
m,
- data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0])
+ data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0]),
+ false
);
}
} else if (i.isButton()) {
@@ -255,7 +263,8 @@
}
case "addRole": {
await i.deferUpdate();
- data.options.push(await createRoleMenuOptionPage(interaction, m));
+ const out = await createRoleMenuOptionPage(interaction, m, undefined, true);
+ if (out) data.options.push(out);
break;
}
}
@@ -268,8 +277,10 @@
const createRoleMenuOptionPage = async (
interaction: StringSelectMenuInteraction | ButtonInteraction,
m: Message,
- data?: { name: string; description: string | null; role: string }
+ data?: { name: string; description: string | null; role: string },
+ newRole: boolean = false
) => {
+ const initialData = _.cloneDeep(data);
const { renderRole } = client.logger;
if (!data)
data = {
@@ -281,19 +292,29 @@
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("back")
- .setLabel("Back")
- .setStyle(ButtonStyle.Secondary)
- .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+ .setLabel(newRole ? "Add" : "Back")
+ .setStyle(newRole ? ButtonStyle.Success : ButtonStyle.Secondary)
+ .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji),
new ButtonBuilder()
.setCustomId("edit")
.setLabel("Edit Details")
.setStyle(ButtonStyle.Primary)
- .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji)
+ .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+ new ButtonBuilder()
+ .setCustomId("delete")
+ .setLabel("Delete")
+ .setStyle(ButtonStyle.Danger)
+ .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji),
+ new ButtonBuilder()
+ .setCustomId("cancel")
+ .setLabel("Cancel")
+ .setStyle(ButtonStyle.Secondary)
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
);
do {
const roleSelect = new RoleSelectMenuBuilder()
.setCustomId("role")
- .setPlaceholder(data.role ? "Set role to" : "Set the role");
+ .setPlaceholder(data.role ? "Change role to" : "Select a role");
const embed = new EmojiEmbed()
.setTitle(`${data.name}`)
.setStatus("Success")
@@ -325,6 +346,12 @@
if (i.customId === "role") {
await i.deferUpdate();
data.role = (i as RoleSelectMenuInteraction).values[0]!;
+ await interaction.editReply({
+ embeds: [
+ new EmojiEmbed().setTitle(`Applying changes`).setStatus("Danger").setEmoji("NUCLEUS.LOADING")
+ ],
+ components: []
+ });
}
} else if (i.isButton()) {
switch (i.customId) {
@@ -334,7 +361,6 @@
break;
}
case "edit": {
- await i.deferUpdate();
const [name, description] = await editNameDescription(
i,
interaction,
@@ -345,6 +371,15 @@
data.description = description ? description : data.description;
break;
}
+ case "delete": {
+ await i.deferUpdate();
+ return null;
+ }
+ case "cancel": {
+ await i.deferUpdate();
+ if (newRole) return null;
+ else return initialData;
+ }
}
}
} while (!back);
@@ -383,6 +418,7 @@
.setValue("delete")
.setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
);
+ console.log(page);
const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("back")
@@ -422,7 +458,7 @@
actionSelect.setDisabled(true);
pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No role menu pages").setValue("none"));
} else {
- page = Math.min(page, Object.keys(currentObject).length - 1);
+ page = Math.max(Math.min(page, currentObject.length - 1), 0);
current = currentObject[page]!;
embed.setDescription(
`**Currently Editing:** ${current.name}\n\n` +
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
index 98ffcff..cd4778a 100644
--- a/src/events/messageCreate.ts
+++ b/src/events/messageCreate.ts
@@ -32,9 +32,10 @@
await message.crosspost();
} else {
await singleNotify(
- `Nucleus does not have Manage Messages in <#${message.channel.id}>`,
+ "crosspost.noManageMessages",
message.guild.id,
- true
+ `Nucleus does not have Manage Messages in <#${message.channel.id}>`,
+ "Warning"
);
}
}
diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts
index 59c12c7..93978e6 100644
--- a/src/reflex/guide.ts
+++ b/src/reflex/guide.ts
@@ -16,35 +16,49 @@
import createPageIndicator from "../utils/createPageIndicator.js";
import { Embed } from "../utils/defaults.js";
-export default async (guild: Guild, interaction?: CommandInteraction) => {
- let c: GuildTextBasedChannel | null = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel;
- c = c
- ? c
- : (guild.channels.cache.find(
- (ch) =>
- [
- ChannelType.GuildText,
- ChannelType.GuildAnnouncement,
- ChannelType.PublicThread,
- ChannelType.PrivateThread,
- ChannelType.AnnouncementThread
- ].includes(ch.type) &&
- ch.permissionsFor(guild.roles.everyone).has("SendMessages") &&
- ch.permissionsFor(guild.members.me!).has("EmbedLinks")
- ) as GuildTextBasedChannel | undefined) ?? null;
- if (interaction) c = interaction.channel as GuildTextBasedChannel;
- if (!c) {
- return;
- }
+export default async (guild: Guild | null, interaction?: CommandInteraction) => {
let m: Message;
- if (interaction) {
- m = (await interaction.reply({
- embeds: LoadingEmbed,
- fetchReply: true,
- ephemeral: true
- })) as Message;
+ if (guild) {
+ let c: GuildTextBasedChannel | null = guild.publicUpdatesChannel
+ ? guild.publicUpdatesChannel
+ : guild.systemChannel;
+ c = c
+ ? c
+ : (guild.channels.cache.find(
+ (ch) =>
+ [
+ ChannelType.GuildText,
+ ChannelType.GuildAnnouncement,
+ ChannelType.PublicThread,
+ ChannelType.PrivateThread,
+ ChannelType.AnnouncementThread
+ ].includes(ch.type) &&
+ ch.permissionsFor(guild.roles.everyone).has("SendMessages") &&
+ ch.permissionsFor(guild.members.me!).has("EmbedLinks")
+ ) as GuildTextBasedChannel | undefined) ?? null;
+ if (interaction) c = interaction.channel as GuildTextBasedChannel;
+ if (!c) {
+ return;
+ }
+ if (interaction) {
+ m = (await interaction.reply({
+ embeds: LoadingEmbed,
+ fetchReply: true,
+ ephemeral: true
+ })) as Message;
+ } else {
+ m = await c.send({ embeds: LoadingEmbed });
+ }
} else {
- m = await c.send({ embeds: LoadingEmbed });
+ if (interaction) {
+ m = (await interaction.reply({
+ embeds: LoadingEmbed,
+ fetchReply: true,
+ ephemeral: true
+ })) as Message;
+ } else {
+ return;
+ }
}
let page = 0;
const pages = [
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 0e4a9b4..fc29b56 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -226,7 +226,8 @@
try {
component = await m.awaitMessageComponent({
filter: (i) =>
- i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id,
+ i.user.id === this.interaction.user.id &&
+ (i.channel ? i.channel!.id === this.interaction.channel!.id : true),
time: 300000
});
} catch (e) {
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 77ff875..f24416a 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -192,7 +192,7 @@
interface TranscriptAuthor {
username: string;
- discriminator: number;
+ discriminator: string | undefined;
nickname?: string;
id: string;
iconURL?: string;
@@ -390,7 +390,7 @@
type: type,
for: {
username: member!.user.username,
- discriminator: parseInt(member!.user.discriminator),
+ discriminator: member!.user.discriminator === "0" ? undefined : member!.user.discriminator,
id: member!.user.id,
topRole: {
color: member!.roles.highest.color
@@ -404,7 +404,7 @@
createdTimestamp: Date.now(),
createdBy: {
username: interaction.user.username,
- discriminator: parseInt(interaction.user.discriminator),
+ discriminator: interaction.user.discriminator === "0" ? undefined : interaction.user.discriminator,
id: interaction.user.id,
topRole: {
color: interactionMember?.roles.highest.color ?? 0x000000
@@ -420,7 +420,7 @@
id: message.id,
author: {
username: message.author.username,
- discriminator: parseInt(message.author.discriminator),
+ discriminator: message.author.discriminator === "0" ? undefined : message.author.discriminator,
id: message.author.id,
topRole: {
color: message.member ? message.member.roles.highest.color : 0x000000
@@ -499,9 +499,7 @@
out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
} else out += `> [Reply To] ${message.referencedMessage}\n`;
}
- out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
- message.author.id
- }) (${message.id})`;
+ out += `${message.author.nickname ?? message.author.username} (${message.author.id}) (${message.id})`;
out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
out += "\n";
diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts
index e96ff5d..b9f2c23 100644
--- a/src/utils/dualCollector.ts
+++ b/src/utils/dualCollector.ts
@@ -54,7 +54,7 @@
return i.channel!.id === m.channel!.id && i.user.id === user.id;
}
function defaultModalFilter(i: ModalSubmitInteraction, user: User, m: Message) {
- return i.channel!.id === m.channel!.id && i.user.id === user.id;
+ return (i.channel ? i.channel!.id === m.channel!.id : true) && i.user.id === user.id;
}
export async function modalInteractionCollector(