All commands and some events finished

pull/17/head
PineaFan 3 years ago
parent 73e2e6c6e0
commit e6ba788662
No known key found for this signature in database
GPG Key ID: D404018735F488C9

@ -6,7 +6,7 @@ import client from "../utils/client.js";
export async function create(
guild: Discord.Guild,
member: Discord.User,
user: Discord.User,
createdBy: Discord.User,
reason: string | null,
customReason?: string
@ -14,7 +14,7 @@ export async function create(
const config = await client.database.guilds.read(guild.id);
const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger;
const overwrites = [{
id: member,
id: user,
allow: ["ViewChannel", "SendMessages", "AttachFiles", "AddReactions", "ReadMessageHistory"],
type: OverwriteType.Member
}] as unknown as Discord.OverwriteResolvable[];
@ -30,13 +30,35 @@ export async function create(
type: OverwriteType.Role
});
}
const targetChannel: Discord.CategoryChannel | Discord.TextChannel = (await guild.channels.fetch(config.tickets.category!))! as Discord.CategoryChannel | Discord.TextChannel;
let c: Discord.TextChannel | Discord.PrivateThreadChannel;
if (targetChannel.type === Discord.ChannelType.GuildCategory) {
const overwrites = [
{
id: user,
allow: ["ViewChannel", "SendMessages", "AttachFiles", "AddReactions", "ReadMessageHistory"],
type: Discord.OverwriteType.Member
}
] as Discord.OverwriteResolvable[];
overwrites.push({
id: guild.roles.everyone,
deny: ["ViewChannel"],
type: Discord.OverwriteType.Role
});
if (config.tickets.supportRole !== null) {
overwrites.push({
id: guild.roles.cache.get(config.tickets.supportRole)!,
allow: ["ViewChannel", "SendMessages", "AttachFiles", "AddReactions", "ReadMessageHistory"],
type: Discord.OverwriteType.Role
});
}
let c;
try {
c = await guild.channels.create({
name: member.username,
name: `${user.username.toLowerCase()}`,
type: ChannelType.GuildText,
topic: `${member.id} Active`,
topic: `${user.id} Active`,
parent: config.tickets.category,
nsfw: false,
permissionOverwrites: overwrites as Discord.OverwriteResolvable[],
@ -48,9 +70,55 @@ export async function create(
try {
await c.send({
content:
`<@${member.id}>` + (config.tickets.supportRole !== null ? ` • <@&${config.tickets.supportRole}>` : ""),
`<@${user.id}>` +
(config.tickets.supportRole !== null ? ` • <@&${config.tickets.supportRole}>` : ""),
allowedMentions: {
users: [user.id],
roles: config.tickets.supportRole !== null ? [config.tickets.supportRole] : []
}
});
await c.send({
embeds: [
new EmojiEmbed()
.setTitle("New Ticket")
.setDescription(
"Ticket created by a Moderator\n" +
`**Support type:** ${customReason ? customReason : "Appeal submission"}\n` +
(reason !== null ? `**Reason:**\n> ${reason}\n` : "") +
`**Ticket ID:** \`${c.id}\`\n` +
`Type ${await getCommandMentionByName("ticket/close")} to close this ticket.`
)
.setStatus("Success")
.setEmoji("GUILD.TICKET.OPEN")
],
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents([
new ButtonBuilder()
.setLabel("Close")
.setStyle(ButtonStyle.Danger)
.setCustomId("closeticket")
.setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
])
]
});
} catch (e) {
return null;
}
} else {
c = await targetChannel.threads.create({name: `${user.username} - ${user.id} - Active`,
autoArchiveDuration: 60 * 24 * 7,
type: Discord.ChannelType.PrivateThread,
reason: "Creating ticket"
}) as Discord.PrivateThreadChannel;
c.members.add(user.id);
c.members.add(createdBy.id);
try {
await c.send({
content:
`<@${user.id}>` +
(config.tickets.supportRole !== null ? ` • <@&${config.tickets.supportRole}>` : ""),
allowedMentions: {
users: [member.id],
users: [user.id],
roles: config.tickets.supportRole !== null ? [config.tickets.supportRole] : []
}
});
@ -69,7 +137,7 @@ export async function create(
.setEmoji("GUILD.TICKET.OPEN")
],
components: [
new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
new ActionRowBuilder<ButtonBuilder>().addComponents([
new ButtonBuilder()
.setLabel("Close")
.setStyle(ButtonStyle.Danger)
@ -78,6 +146,10 @@ export async function create(
])
]
});
} catch (e) {
return null;
}
}
const data = {
meta: {
type: "ticketCreate",
@ -88,7 +160,7 @@ export async function create(
timestamp: new Date().getTime()
},
list: {
ticketFor: entry(member.id, renderUser(member)),
ticketFor: entry(user.id, renderUser(user)),
createdBy: entry(createdBy.id, renderUser(createdBy)),
created: entry((new Date().getTime()).toString(), renderDelta(new Date().getTime())),
ticketChannel: entry(c.id, renderChannel(c))
@ -98,10 +170,6 @@ export async function create(
}
};
log(data);
} catch (e) {
console.log(e);
return null;
}
return c.id;
}

@ -1,9 +1,11 @@
import type { CommandInteraction, GuildMember } from "discord.js";
import { LinkWarningFooter } from './../../utils/defaults.js';
import { ActionRowBuilder, ButtonBuilder, CommandInteraction, GuildMember, ButtonStyle, Message } from "discord.js";
import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import keyValueList from "../../utils/generateKeyValueList.js";
import client from "../../utils/client.js";
import { areTicketsEnabled, create } from "../../actions/createModActionTicket.js";
const command = (builder: SlashCommandSubcommandBuilder) => builder
@ -17,102 +19,136 @@ const command = (builder: SlashCommandSubcommandBuilder) => builder
const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const { renderUser } = client.logger;
const { log, NucleusColors, entry, renderDelta, renderUser } = client.logger;
// TODO:[Modals] Replace this with a modal
let notify = true;
let confirmation;
let timedOut = false;
let success = false;
while (!timedOut && !success) {
let createAppealTicket = false;
let firstRun = true;
do {
confirmation = await new confirmationMessage(interaction)
.setEmoji("PUNISH.NICKNAME.RED")
.setTitle("Nickname")
.setDescription(
keyValueList({
user: renderUser(interaction.options.getUser("user")),
user: renderUser(interaction.options.getUser("user")!),
"new nickname": `${
interaction.options.getString("name") ? interaction.options.getString("name") : "*No nickname*"
interaction.options.get("name")?.value as string ? interaction.options.get("name")?.value as string : "*No nickname*"
}`
}) +
`The user **will${notify ? "" : " not"}** be notified\n\n` +
`Are you sure you want to ${interaction.options.getString("name") ? "change" : "clear"} <@!${
`Are you sure you want to ${interaction.options.get("name")?.value as string ? "change" : "clear"} <@!${
(interaction.options.getMember("user") as GuildMember).id
}>'s nickname?`
)
.setColor("Danger")
.addCustomBoolean(
"appeal",
"Create appeal ticket",
!(await areTicketsEnabled(interaction.guild!.id)),
async () => await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, "Nickname changed"),
"An appeal ticket will be created",
null,
"CONTROL.TICKET",
createAppealTicket
)
.addCustomBoolean(
"notify",
"Notify user",
false,
null,
null,
"The user will be sent a DM",
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify
)
.send(interaction.options.getString("name") !== null);
.setFailedMessage("No changes were made", "Success", "PUNISH.NICKNAME.GREEN")
.send(!firstRun);
firstRun = false;
if (confirmation.cancelled) timedOut = true;
else if (confirmation.success) success = true;
else if (confirmation.success !== undefined) success = true;
else if (confirmation.components) {
notify = confirmation.components.notify.active;
}
}
if (timedOut) {
return await interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("PUNISH.NICKNAME.GREEN")
.setTitle("Nickname")
.setDescription("No changes were made")
.setStatus("Success")
],
components: []
});
notify = confirmation.components['notify']!.active;
createAppealTicket = confirmation.components["appeal"]!.active;
}
let dmd = false;
let dm;
} while (!timedOut && !success);
if (timedOut || !success) return;
let dmSent = false;
let dmMessage: Message;
const config = await client.database.guilds.read(interaction.guild!.id);
try {
if (notify) {
dm = await (interaction.options.getMember("user") as GuildMember).send({
const messageData: {
embeds: EmojiEmbed[];
components: ActionRowBuilder<ButtonBuilder>[];
} = {
embeds: [
new EmojiEmbed()
.setEmoji("PUNISH.NICKNAME.RED")
.setTitle("Nickname changed")
.setDescription(
`Your nickname was ${interaction.options.getString("name") ? "changed" : "cleared"} in ${
interaction.guild.name
`Your nickname was ${interaction.options.get("name")?.value as string ? "changed" : "cleared"} in ${
interaction.guild!.name
}.` +
(interaction.options.getString("name")
? ` it is now: ${interaction.options.getString("name")}`
(interaction.options.get("name")?.value as string
? ` it is now: ${interaction.options.get("name")?.value as string}`
: "") +
"\n\n" +
(confirmation.components.appeal.response
? `You can appeal this here: <#${confirmation.components.appeal.response}>`
(createAppealTicket
? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
: "")
)
.setStatus("Danger")
]
});
dmd = true;
], components: []
};
if (config.moderation.nick.text && config.moderation.nick.link) {
messageData.embeds[0]!.setFooter(LinkWarningFooter)
messageData.components.push(new ActionRowBuilder<ButtonBuilder>()
.addComponents(new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel(config.moderation.nick.text)
.setURL(config.moderation.nick.link.replaceAll("{id}", (interaction.options.getMember("user") as GuildMember).id))
)
)
}
dmMessage = await (interaction.options.getMember("user") as GuildMember).send(messageData);
dmSent = true;
}
} catch {
dmd = false;
dmSent = false;
}
let member: GuildMember;
let before: string | null;
let nickname: string | undefined;
try {
const member = interaction.options.getMember("user") as GuildMember;
const before = member.nickname;
const nickname = interaction.options.getString("name");
member = interaction.options.getMember("user") as GuildMember;
before = member.nickname;
nickname = interaction.options.get("name")?.value as string | undefined;
member.setNickname(nickname ?? null, "Nucleus Nickname command");
await client.database.history.create(
"nickname",
interaction.guild.id,
interaction.guild!.id,
member.user,
interaction.user,
null,
before,
nickname
);
const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
} catch {
await interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("PUNISH.NICKNAME.RED")
.setTitle("Nickname")
.setDescription("Something went wrong and the users nickname could not be changed.")
.setStatus("Danger")
],
components: []
});
if (dmSent) await dmMessage!.delete();
return;
}
const data = {
meta: {
type: "memberUpdate",
@ -124,31 +160,17 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
},
list: {
memberId: entry(member.id, `\`${member.id}\``),
before: entry(before, before ? before : "*None*"),
after: entry(nickname, nickname ? nickname : "*None*"),
before: entry(before, before ?? "*No nickname set*"),
after: entry(nickname ?? null, nickname ?? "*No nickname set*"),
updated: entry(new Date().getTime(), renderDelta(new Date().getTime())),
updatedBy: entry(interaction.user.id, renderUser(interaction.user))
},
hidden: {
guild: interaction.guild.id
guild: interaction.guild!.id
}
};
log(data);
} catch {
await interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("PUNISH.NICKNAME.RED")
.setTitle("Nickname")
.setDescription("Something went wrong and the users nickname could not be changed.")
.setStatus("Danger")
],
components: []
});
if (dmd) await dm.delete();
return;
}
const failed = !dmd && notify;
const failed = !dmSent && notify;
await interaction.editReply({
embeds: [
new EmojiEmbed()
@ -157,8 +179,8 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setDescription(
"The members nickname was changed" +
(failed ? ", but was not notified" : "") +
(confirmation.components.appeal.response
? ` and an appeal ticket was opened in <#${confirmation.components.appeal.response}>`
(confirmation.components!["appeal"]!.response !== null
? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
: "")
)
.setStatus(failed ? "Warning" : "Success")

@ -67,7 +67,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
createAppealTicket = confirmation.components["appeal"]!.active;
}
} while (!timedOut && !success)
if (timedOut || !confirmation.success) return;
if (timedOut || !success) return;
let dmSent = false;
const config = await client.database.guilds.read(interaction.guild.id);
try {

@ -1,39 +1,40 @@
import { GuildChannel, AuditLogEvent } from 'discord.js';
import humanizeDuration from "humanize-duration";
import type { NucleusClient } from "../utils/client.js";
import getEmojiByName from "../utils/getEmojiByName.js";
export const event = "channelUpdate";
export async function callback(client, oc, nc) {
const config = await client.memory.readGuildInfo(nc.guild.id);
return;
export async function callback(client: NucleusClient, oldChannel: GuildChannel, newChannel: GuildChannel) {
const config = await client.memory.readGuildInfo(newChannel.guild.id);
const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderChannel } = client.logger;
if (nc.parent && nc.parent.id === config.tickets.category) return;
if (newChannel.parent && newChannel.parent.id === config.tickets.category) return;
const auditLog = await getAuditLog(nc.guild, "CHANNEL_UPDATE");
const audit = auditLog.entries.filter((entry) => entry.target.id === nc.id).first();
const auditLog = await getAuditLog(newChannel.guild, "CHANNEL_UPDATE");
const audit = auditLog.entries.filter((entry) => entry.target.id === newChannel.id).first();
if (audit.executor.id === client.user.id) return;
let emoji: string;
let readableType: string;
let displayName: string;
const changes = {
channelId: entry(nc.id, `\`${nc.id}\``),
channel: entry(nc.id, renderChannel(nc)),
channelId: entry(newChannel.id, `\`${newChannel.id}\``),
channel: entry(newChannel.id, renderChannel(newChannel)),
edited: entry(new Date().getTime(), renderDelta(new Date().getTime())),
editedBy: entry(audit.executor.id, renderUser((await nc.guild.members.fetch(audit.executor.id)).user))
editedBy: entry(audit.executor.id, renderUser((await newChannel.guild.members.fetch(audit.executor.id)).user))
};
if (oc.name !== nc.name) changes.name = entry([oc.name, nc.name], `${oc.name} -> ${nc.name}`);
if (oc.position !== nc.position)
changes.position = entry([oc.position, nc.position], `${oc.position} -> ${nc.position}`);
if (oldChannel.name !== newChannel.name) changes.name = entry([oldChannel.name, newChannel.name], `${oldChannel.name} -> ${newChannel.name}`);
if (oldChannel.position !== newChannel.position)
changes.position = entry([oldChannel.position, newChannel.position], `${oldChannel.position} -> ${newChannel.position}`);
switch (nc.type) {
switch (newChannel.type) {
case "GUILD_TEXT": {
emoji = "CHANNEL.TEXT.EDIT";
readableType = "Text";
displayName = "Text Channel";
let oldTopic = oc.topic,
newTopic = nc.topic;
let oldTopic = oldChannel.topic,
newTopic = newChannel.topic;
if (oldTopic) {
if (oldTopic.length > 256)
oldTopic = `\`\`\`\n${oldTopic.replace("`", "'").substring(0, 253) + "..."}\n\`\`\``;
@ -49,15 +50,15 @@ export async function callback(client, oc, nc) {
newTopic = "None";
}
const nsfw = ["", ""];
nsfw[0] = oc.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
nsfw[1] = nc.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
if (oc.topic !== nc.topic)
changes.description = entry([oc.topic, nc.topic], `\nBefore: ${oldTopic}\nAfter: ${newTopic}`);
if (oc.nsfw !== nc.nsfw) changes.nsfw = entry([oc.nsfw, nc.nsfw], `${nsfw[0]} -> ${nsfw[1]}`);
if (oc.rateLimitPerUser !== nc.rateLimitPerUser)
nsfw[0] = oldChannel.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
nsfw[1] = newChannel.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
if (oldChannel.topic !== newChannel.topic)
changes.description = entry([oldChannel.topic, newChannel.topic], `\nBefore: ${oldTopic}\nAfter: ${newTopic}`);
if (oldChannel.nsfw !== newChannel.nsfw) changes.nsfw = entry([oldChannel.nsfw, newChannel.nsfw], `${nsfw[0]} -> ${nsfw[1]}`);
if (oldChannel.rateLimitPerUser !== newChannel.rateLimitPerUser)
changes.rateLimitPerUser = entry(
[oc.rateLimitPerUser, nc.rateLimitPerUser],
`${humanizeDuration(oc.rateLimitPerUser * 1000)} -> ${humanizeDuration(nc.rateLimitPerUser * 1000)}`
[oldChannel.rateLimitPerUser, newChannel.rateLimitPerUser],
`${humanizeDuration(oldChannel.rateLimitPerUser * 1000)} -> ${humanizeDuration(newChannel.rateLimitPerUser * 1000)}`
);
break;
@ -66,8 +67,8 @@ export async function callback(client, oc, nc) {
emoji = "CHANNEL.TEXT.EDIT";
readableType = "News";
displayName = "News Channel";
let oldTopic = oc.topic,
newTopic = nc.topic;
let oldTopic = oldChannel.topic,
newTopic = newChannel.topic;
if (oldTopic) {
if (oldTopic.length > 256)
oldTopic = `\`\`\`\n${oldTopic.replace("`", "'").substring(0, 253) + "..."}\n\`\`\``;
@ -82,25 +83,25 @@ export async function callback(client, oc, nc) {
} else {
newTopic = "None";
}
if (oc.nsfw !== nc.nsfw)
changes.nsfw = entry([oc.nsfw, nc.nsfw], `${oc.nsfw ? "On" : "Off"} -> ${nc.nsfw ? "On" : "Off"}`);
if (oldChannel.nsfw !== newChannel.nsfw)
changes.nsfw = entry([oldChannel.nsfw, newChannel.nsfw], `${oldChannel.nsfw ? "On" : "Off"} -> ${newChannel.nsfw ? "On" : "Off"}`);
break;
}
case "GUILD_VOICE": {
emoji = "CHANNEL.VOICE.EDIT";
readableType = "Voice";
displayName = "Voice Channel";
if (oc.bitrate !== nc.bitrate)
changes.bitrate = entry([oc.bitrate, nc.bitrate], `${oc.bitrate} -> ${nc.bitrate}`);
if (oc.userLimit !== nc.userLimit)
if (oldChannel.bitrate !== newChannel.bitrate)
changes.bitrate = entry([oldChannel.bitrate, newChannel.bitrate], `${oldChannel.bitrate} -> ${newChannel.bitrate}`);
if (oldChannel.userLimit !== newChannel.userLimit)
changes.maxUsers = entry(
[oc.userLimit, nc.userLimit],
`${oc.userLimit ? oc.userLimit : "Unlimited"} -> ${nc.userLimit}`
[oldChannel.userLimit, newChannel.userLimit],
`${oldChannel.userLimit ? oldChannel.userLimit : "Unlimited"} -> ${newChannel.userLimit}`
);
if (oc.rtcRegion !== nc.rtcRegion)
if (oldChannel.rtcRegion !== newChannel.rtcRegion)
changes.region = entry(
[oc.rtcRegion, nc.rtcRegion],
`${oc.rtcRegion || "Automatic"} -> ${nc.rtcRegion || "Automatic"}`
[oldChannel.rtcRegion, newChannel.rtcRegion],
`${oldChannel.rtcRegion || "Automatic"} -> ${newChannel.rtcRegion || "Automatic"}`
);
break;
}
@ -108,8 +109,8 @@ export async function callback(client, oc, nc) {
emoji = "CHANNEL.VOICE.EDIT";
readableType = "Stage";
displayName = "Stage Channel";
let oldTopic = oc.topic,
newTopic = nc.topic;
let oldTopic = oldChannel.topic,
newTopic = newChannel.topic;
if (oldTopic) {
if (oldTopic.length > 256)
oldTopic = `\`\`\`\n${oldTopic.replace("`", "'").substring(0, 253) + "..."}\n\`\`\``;
@ -124,17 +125,17 @@ export async function callback(client, oc, nc) {
} else {
newTopic = "None";
}
if (oc.bitrate !== nc.bitrate)
changes.bitrate = entry([oc.bitrate, nc.bitrate], `${oc.bitrate} -> ${nc.bitrate}`);
if (oc.userLimit !== nc.userLimit)
if (oldChannel.bitrate !== newChannel.bitrate)
changes.bitrate = entry([oldChannel.bitrate, newChannel.bitrate], `${oldChannel.bitrate} -> ${newChannel.bitrate}`);
if (oldChannel.userLimit !== newChannel.userLimit)
changes.maxUsers = entry(
[oc.userLimit, nc.userLimit],
`${oc.userLimit ? oc.userLimit : "Unlimited"} -> ${nc.userLimit}`
[oldChannel.userLimit, newChannel.userLimit],
`${oldChannel.userLimit ? oldChannel.userLimit : "Unlimited"} -> ${newChannel.userLimit}`
);
if (oc.rtcRegion !== nc.rtcRegion)
if (oldChannel.rtcRegion !== newChannel.rtcRegion)
changes.region = entry(
[oc.rtcRegion, nc.rtcRegion],
`${oc.rtcRegion || "Automatic"} -> ${nc.rtcRegion || "Automatic"}`
[oldChannel.rtcRegion, newChannel.rtcRegion],
`${oldChannel.rtcRegion || "Automatic"} -> ${newChannel.rtcRegion || "Automatic"}`
);
break;
}
@ -150,9 +151,9 @@ export async function callback(client, oc, nc) {
displayName = "Channel";
}
}
const t = oc.type.split("_")[1];
if (oc.type !== nc.type)
changes.type = entry([oc.type, nc.type], `${t[0] + t.splice(1).toLowerCase()} -> ${readableType}`);
const t = oldChannel.type.split("_")[1];
if (oldChannel.type !== newChannel.type)
changes.type = entry([oldChannel.type, newChannel.type], `${t[0] + t.splice(1).toLowerCase()} -> ${readableType}`);
if (!(Object.values(changes).length - 4)) return;
const data = {
meta: {
@ -165,7 +166,7 @@ export async function callback(client, oc, nc) {
},
list: changes,
hidden: {
guild: nc.guild.id
guild: newChannel.guild.id
}
};
log(data);

@ -1,10 +1,14 @@
import { AuditLogEvent } from 'discord.js';
import type { NucleusClient } from "../utils/client.js";
import type { GuildEmoji, GuildAuditLogsEntry } from 'discord.js'
export const event = "emojiCreate";
export async function callback(client, emoji) {
const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = emoji.client.logger;
const auditLog = await getAuditLog(emoji.guild, "EMOJI_CREATE");
const audit = auditLog.entries.filter((entry) => entry.target.id === emoji.id).first();
if (audit.executor.id === client.user.id) return;
export async function callback(client: NucleusClient, emoji: GuildEmoji) {
const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger;
const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiCreate))
.filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0];
if (!auditLog) return;
if (auditLog.executor!.id === client.user!.id) return;
const data = {
meta: {
type: "emojiCreate",
@ -17,7 +21,7 @@ export async function callback(client, emoji) {
list: {
emojiId: entry(emoji.id, `\`${emoji.id}\``),
emoji: entry(emoji.name, renderEmoji(emoji)),
createdBy: entry(audit.executor.id, renderUser(audit.executor)),
createdBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
created: entry(emoji.createdTimestamp, renderDelta(emoji.createdTimestamp))
},
hidden: {
@ -26,3 +30,4 @@ export async function callback(client, emoji) {
};
log(data);
}

@ -1,10 +1,14 @@
import { AuditLogEvent } from 'discord.js';
import type { NucleusClient } from "../utils/client.js";
import type { GuildEmoji, GuildAuditLogsEntry } from 'discord.js'
export const event = "emojiDelete";
export async function callback(client, emoji) {
const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = emoji.client.logger;
const auditLog = await getAuditLog(emoji.guild, "EMOJI_DELETE");
const audit = auditLog.entries.filter((entry) => entry.target.id === emoji.id).first();
if (audit.executor.id === client.user.id) return;
export async function callback(client: NucleusClient, emoji: GuildEmoji) {
const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger;
const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiCreate))
.filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0];
if (!auditLog) return;
if (auditLog.executor!.id === client.user!.id) return;
const data = {
meta: {
type: "emojiDelete",
@ -12,14 +16,14 @@ export async function callback(client, emoji) {
calculateType: "emojiUpdate",
color: NucleusColors.red,
emoji: "GUILD.EMOJI.DELETE",
timestamp: audit.createdTimestamp
timestamp: auditLog.createdTimestamp
},
list: {
emojiId: entry(emoji.id, `\`${emoji.id}\``),
emoji: entry(emoji.name, renderEmoji(emoji)),
deletedBy: entry(audit.executor.id, renderUser(audit.executor)),
deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
created: entry(emoji.createdTimestamp, renderDelta(emoji.createdTimestamp)),
deleted: entry(audit.createdTimestamp, renderDelta(audit.createdTimestamp))
deleted: entry(auditLog.createdTimestamp, renderDelta(auditLog.createdTimestamp))
},
hidden: {
guild: emoji.guild.id

@ -1,19 +1,22 @@
import { AuditLogEvent } from 'discord.js';
import type { NucleusClient } from "../utils/client.js";
import type { GuildEmoji, GuildAuditLogsEntry } from 'discord.js'
export const event = "emojiUpdate";
export async function callback(client, oe, ne) {
export async function callback(client: NucleusClient, oldEmoji: GuildEmoji, newEmoji: GuildEmoji) {
const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderEmoji } = client.logger;
if (oe.name === ne.name) return;
const auditLog = await getAuditLog(ne.guild, "EMOJI_UPDATE");
const audit = auditLog.entries.first();
if (audit.executor.id === client.user.id) return;
const auditLog = (await getAuditLog(newEmoji.guild, AuditLogEvent.EmojiCreate))
.filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === newEmoji.id)[0];
if (!auditLog) return;
if (auditLog.executor!.id === client.user!.id) return;
const changes = {
emojiId: entry(ne.id, `\`${ne.id}\``),
emoji: entry(ne.id, renderEmoji(ne)),
edited: entry(ne.createdTimestamp, renderDelta(ne.createdTimestamp)),
editedBy: entry(audit.executor.id, renderUser((await ne.guild.members.fetch(audit.executor.id)).user)),
name: entry([oe.name, ne.name], `\`:${oe.name}:\` -> \`:${ne.name}:\``)
emojiId: entry(newEmoji.id, `\`${newEmoji.id}\``),
emoji: entry(newEmoji.id, renderEmoji(newEmoji)),
edited: entry(newEmoji.createdTimestamp, renderDelta(newEmoji.createdTimestamp)),
editedBy: entry(auditLog.executor!.id, renderUser((await newEmoji.guild.members.fetch(auditLog.executor!.id)).user)),
name: entry([oldEmoji.name!, newEmoji.name!], `\`:${oldEmoji.name}:\` -> \`:${newEmoji.name}:\``)
};
const data = {
meta: {
@ -22,11 +25,11 @@ export async function callback(client, oe, ne) {
calculateType: "emojiUpdate",
color: NucleusColors.yellow,
emoji: "GUILD.EMOJI.EDIT",
timestamp: audit.createdTimestamp
timestamp: auditLog.createdTimestamp
},
list: changes,
hidden: {
guild: ne.guild.id
guild: newEmoji.guild.id
}
};
log(data);

@ -1,4 +1,5 @@
import type { GuildAuditLogsEntry, GuildBan } from "discord.js";
import type { GuildAuditLogsEntry, GuildBan, User } from "discord.js";
import { AuditLogEvent } from 'discord.js';
import { purgeByUser } from "../actions/tickets/delete.js";
import { callback as statsChannelRemove } from "../reflex/statsChannelUpdate.js";
import type { NucleusClient } from "../utils/client.js";
@ -7,12 +8,13 @@ export const event = "guildBanAdd";
export async function callback(client: NucleusClient, ban: GuildBan) {
await statsChannelRemove(client, undefined, ban.guild, ban.user);
purgeByUser(ban.user.id, ban.guild);
purgeByUser(ban.user.id, ban.guild.id);
const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger;
const auditLog = await getAuditLog(ban.guild, "MEMBER_BAN_ADD");
const audit = auditLog.entries.filter((entry: GuildAuditLogsEntry) => entry.target!.id === ban.user.id).first();
if (audit.executor.id === client.user.id) return;
await client.database.history.create("ban", ban.guild.id, ban.user, audit.executor, audit.reason);
const auditLog: GuildAuditLogsEntry | undefined = (await getAuditLog(ban.guild, AuditLogEvent.EmojiCreate))
.filter((entry: GuildAuditLogsEntry) => ((entry.target! as User).id === ban.user.id))[0];
if (!auditLog) return;
if (auditLog.executor!.id === client.user!.id) return;
await client.database.history.create("ban", ban.guild.id, ban.user, auditLog.executor, auditLog.reason);
const data = {
meta: {
type: "memberBan",
@ -26,9 +28,9 @@ export async function callback(client: NucleusClient, ban: GuildBan) {
memberId: entry(ban.user.id, `\`${ban.user.id}\``),
name: entry(ban.user.id, renderUser(ban.user)),
banned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
bannedBy: entry(audit.executor.id, renderUser(audit.executor)),
reason: entry(audit.reason, audit.reason ? `\n> ${audit.reason}` : "*No reason provided.*"),
accountCreated: entry(ban.user.createdAt, renderDelta(ban.user.createdAt)),
bannedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
reason: entry(auditLog.reason, auditLog.reason ? `\n> ${auditLog.reason}` : "*No reason provided.*"),
accountCreated: entry(ban.user.createdTimestamp, renderDelta(ban.user.createdTimestamp)),
serverMemberCount: ban.guild.memberCount
},
hidden: {

@ -1,16 +1,18 @@
import type { GuildAuditLogsEntry, GuildBan } from "discord.js";
import type { GuildAuditLogsEntry, GuildBan, User } from "discord.js";
import { AuditLogEvent } from "discord.js";
import { purgeByUser } from "../actions/tickets/delete.js";
import type { NucleusClient } from "../utils/client.js";
export const event = "guildBanRemove";
export async function callback(client: NucleusClient, ban: GuildBan) {
purgeByUser(ban.user.id, ban.guild);
purgeByUser(ban.user.id, ban.guild.id);
const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger;
const auditLog = await getAuditLog(ban.guild, "MEMBER_BAN_REMOVE");
const audit = auditLog.entries.filter((entry: GuildAuditLogsEntry) => entry.target!.id === ban.user.id).first();
if (audit.executor.id === client.user.id) return;
await client.database.history.create("unban", ban.guild.id, ban.user, audit.executor, audit.reason);
const auditLog = (await getAuditLog(ban.guild, AuditLogEvent.EmojiCreate))
.filter((entry: GuildAuditLogsEntry) => ((entry.target! as User).id === ban.user.id))[0];
if (!auditLog) return;
if (auditLog.executor!.id === client.user!.id) return;
await client.database.history.create("unban", ban.guild.id, ban.user, auditLog.executor, auditLog.reason);
const data = {
meta: {
type: "memberUnban",
@ -24,8 +26,8 @@ export async function callback(client: NucleusClient, ban: GuildBan) {
memberId: entry(ban.user.id, `\`${ban.user.id}\``),
name: entry(ban.user.id, renderUser(ban.user)),
unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
unbannedBy: entry(audit.executor.id, renderUser(audit.executor)),
accountCreated: entry(ban.user.createdAt, renderDelta(ban.user.createdAt))
unbannedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
accountCreated: entry(ban.user.createdTimestamp, renderDelta(ban.user.createdTimestamp))
},
hidden: {
guild: ban.guild.id

@ -1,3 +1,4 @@
import { getCommandMentionByName } from './../utils/getCommandMentionByName';
import client from "../utils/client.js";
import keyValueList from "../utils/generateKeyValueList.js";
import singleNotify from "../utils/singleNotify.js";
@ -37,7 +38,7 @@ export default async function logAttachment(message: Message): Promise<Attachmen
singleNotify(
"noAttachmentLogChannel",
message.guild.id,
"No channel set for attachment logging",
`No channel set for attachment logging. You can set one with ${await getCommandMentionByName("settings/logs/attachments")}`,
"Warning"
);
return { files: attachments };
@ -47,7 +48,7 @@ export default async function logAttachment(message: Message): Promise<Attachmen
singleNotify(
"attachmentLogChannelDeleted",
message.guild.id,
"Attachment history channel was deleted",
`Your attachment history channel was deleted or is not longer accessible. You can set a new one with ${await getCommandMentionByName("settings/logs/attachments")}`,
"Warning"
);
return { files: attachments };

@ -3,45 +3,18 @@ import Discord, {
ActionRowBuilder,
ButtonBuilder,
MessageComponentInteraction,
MessageSelectOptionData,
StringSelectMenuInteraction,
Guild,
CommandInteraction,
GuildTextBasedChannel,
Message,
SelectMenuInteraction,
ButtonStyle
ButtonStyle,
ChannelType
} from "discord.js";
import EmojiEmbed from "../utils/generateEmojiEmbed.js";
import getEmojiByName from "../utils/getEmojiByName.js";
import createPageIndicator from "../utils/createPageIndicator.js";
class Embed {
embed: Discord.EmbedBuilder;
title: string;
description = "";
pageId = 0;
constructor() {
this.embed = new Discord.EmbedBuilder();
this.title = "";
}
setEmbed(embed: Discord.EmbedBuilder) {
this.embed = embed;
return this;
}
setTitle(title: string) {
this.title = title;
return this;
}
setDescription(description: string) {
this.description = description;
return this;
}
setPageId(pageId: number) {
this.pageId = pageId;
return this;
}
}
import { Embed } from "../utils/defaults.js";
export default async (guild: Guild, interaction?: CommandInteraction) => {
let c: GuildTextBasedChannel | null = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel;
@ -50,14 +23,14 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
: (guild.channels.cache.find(
(ch) =>
[
"GUILD_TEXT",
"GUILD_NEWS",
"GUILD_NEWS_THREAD",
"GUILD_PRIVATE_THREAD",
"GUILD_PUBLIC_THREAD"
ChannelType.GuildText,
ChannelType.GuildAnnouncement,
ChannelType.PublicThread,
ChannelType.PrivateThread,
ChannelType.AnnouncementThread
].includes(ch.type) &&
ch.permissionsFor(guild.roles.everyone).has("SEND_MESSAGES") &&
ch.permissionsFor(guild.me!).has("EMBED_LINKS")
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) {
@ -226,7 +199,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
let page = 0;
const publicFilter = async (component: MessageComponentInteraction) => {
return (component.member as Discord.GuildMember).permissions.has("MANAGE_GUILD");
return (component.member as Discord.GuildMember).permissions.has("ManageGuild");
};
let selectPaneOpen = false;
@ -234,20 +207,20 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
let cancelled = false;
let timedOut = false;
while (!cancelled && !timedOut) {
let selectPane: ActionRowBuilder[] = [];
let selectPane: ActionRowBuilder<Discord.StringSelectMenuBuilder | ButtonBuilder>[] = [];
if (selectPaneOpen) {
const options: MessageSelectOptionData[] = [];
const options: Discord.StringSelectMenuOptionBuilder[] = [];
pages.forEach((embed) => {
options.push({
label: embed.title,
value: embed.pageId.toString(),
description: embed.description || ""
});
options.push(new Discord.StringSelectMenuOptionBuilder()
.setLabel(embed.title)
.setValue(embed.pageId.toString())
.setDescription(embed.description || "")
);
});
selectPane = [
new ActionRowBuilder().addComponents([
new Discord.SelectMenuBuilder()
new ActionRowBuilder<Discord.StringSelectMenuBuilder>().addComponents([
new Discord.StringSelectMenuBuilder()
.addOptions(options)
.setCustomId("page")
.setMaxValues(1)
@ -255,8 +228,8 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
])
];
}
const components = selectPane.concat([
new ActionRowBuilder().addComponents([
const components: ActionRowBuilder<ButtonBuilder | Discord.StringSelectMenuBuilder>[] = selectPane.concat([
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("left")
.setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
@ -272,18 +245,18 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
.setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
.setStyle(ButtonStyle.Secondary)
.setDisabled(page === pages.length - 1)
])
)
]);
if (interaction) {
const em = new Discord.EmbedBuilder(pages[page]!.embed);
em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page));
await interaction.editReply({
embeds: [em],
components: components
});
} else {
const em = new Discord.EmbedBuilder(pages[page]!.embed);
em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page));
(await m.edit({
embeds: [em],
components: components
@ -313,7 +286,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
} else if (i.component.customId === "select") {
selectPaneOpen = !selectPaneOpen;
} else if (i.component.customId === "page") {
page = parseInt((i as SelectMenuInteraction).values[0]!);
page = parseInt((i as StringSelectMenuInteraction).values[0]!);
selectPaneOpen = false;
} else {
cancelled = true;
@ -322,7 +295,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
if (timedOut) {
if (interaction) {
const em = new Discord.EmbedBuilder(pages[page]!.embed);
em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page)).setFooter({
em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page)).setFooter({
text: "Message timed out"
});
await interaction.editReply({
@ -331,7 +304,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
});
} else {
const em = new Discord.EmbedBuilder(pages[page]!.embed);
em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page)).setFooter({
em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page)).setFooter({
text: "Message timed out"
});
await m.edit({
@ -342,7 +315,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => {
} else {
if (interaction) {
const em = new Discord.EmbedBuilder(pages[page]!.embed);
em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page));
em.setFooter({ text: "Message closed" });
interaction.editReply({
embeds: [em],

@ -1,3 +1,4 @@
import { getCommandMentionByName } from '../utils/getCommandMentionByName.js';
import type { Guild, User } from "discord.js";
import type { NucleusClient } from "../utils/client.js";
import type { GuildMember } from "discord.js";
@ -31,7 +32,7 @@ export async function callback(client: NucleusClient, member?: GuildMember, guil
return singleNotify(
"statsChannelDeleted",
guild!.id,
"One or more of your stats channels have been deleted. Please use `/settings stats` if you wish to add the channel again.\n" +
`One or more of your stats channels have been deleted. You can use ${await getCommandMentionByName("settings/stats")}.\n` +
`The channels name was: ${deleted!.name}`,
"Critical"
);

@ -1,3 +1,4 @@
import { getCommandMentionByName } from './../utils/getCommandMentionByName.js';
import type { NucleusClient } from "../utils/client.js";
import convertCurlyBracketString from "../utils/convertCurlyBracketString.js";
import client from "../utils/client.js";
@ -26,7 +27,7 @@ export async function callback(_client: NucleusClient, member: GuildMember) {
});
} else {
const channel: GuildChannel | null = await member.guild.channels.fetch(config.welcome.channel) as GuildChannel | null;
if (!channel) return; // TODO: SEN
if (!channel) return await singleNotify("welcomeChannelDeleted", member.guild.id, `The welcome channel has been deleted or is no longer accessible. Use ${await getCommandMentionByName("settings/welcome")} to set a new one`, "Warning")
if (!(channel instanceof BaseGuildTextChannel)) return;
if (channel.guild.id !== member.guild.id) return;
try {
@ -38,7 +39,7 @@ export async function callback(_client: NucleusClient, member: GuildMember) {
singleNotify(
"welcomeChannelDeleted",
member.guild.id,
"The welcome channel has been deleted or is no longer accessible. Use /settings welcome to set a new one",
`The welcome channel has been deleted or is no longer accessible. Use ${await getCommandMentionByName("settings/welcome")} to set a new one`,
"Warning"
)
}

@ -313,6 +313,10 @@ export interface GuildConfig {
text: null;
link: null;
};
nick: {
text: string | null;
link: string | null;
}
};
tracks: {
name: string;

@ -25,7 +25,7 @@ export const Logger = {
const delta = num2 - num1;
return `${num1} -> ${num2} (${delta > 0 ? "+" : ""}${delta})`;
},
entry(value: string | number | boolean | null, displayValue: string): { value: string | boolean | null; displayValue: string } {
entry(value: string | number | boolean | null | string[], displayValue: string): { value: string | boolean | null | string[]; displayValue: string } {
if (typeof value === "number") value = value.toString();
return { value: value, displayValue: displayValue };
},

Loading…
Cancel
Save