mirror of https://github.com/BRAVO68WEB/shx.git
448 lines
12 KiB
TypeScript
448 lines
12 KiB
TypeScript
import type { Message, PermissionResolvable, TextChannel } from 'discord.js';
|
|
|
|
import type { TextCommand } from '../sturctures/command';
|
|
import type { GuildConfig } from '../sturctures/database';
|
|
import type { DiscordEvent } from '../sturctures/event';
|
|
import { cooldownCache } from '../utils/cache';
|
|
import { getCommandHelpInfo, resembleCommandCheck } from '../utils/cmds';
|
|
import { isDev } from '../utils/constants';
|
|
import { parseMsToVisibleText } from '../utils/formatters';
|
|
import { callbackEmbed } from '../utils/messages';
|
|
import MYGuildConfig from '../utils/guild.config';
|
|
import { handleAttachmentUpload } from '../commands/mentions/uploader';
|
|
|
|
async function reject(message: Message, usage: string, missing: string) {
|
|
const postMessage = await message.reply({
|
|
content: `Usage: \`${usage}\`\nMissing: \`${missing}\``,
|
|
allowedMentions: { repliedUser: true },
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (postMessage.deletable) postMessage.delete().catch(() => undefined);
|
|
}, 6000);
|
|
}
|
|
|
|
export const event: DiscordEvent = {
|
|
name: 'messageCreate',
|
|
// eslint-disable-next-line
|
|
run: async (message: Message) => {
|
|
const {
|
|
content,
|
|
channel,
|
|
author,
|
|
webhookId,
|
|
member,
|
|
guild,
|
|
client,
|
|
attachments,
|
|
} = message;
|
|
|
|
if (
|
|
author.bot ||
|
|
channel.isVoiceBased() ||
|
|
webhookId ||
|
|
author.id === client.user.id
|
|
)
|
|
return;
|
|
|
|
const prefix = 'x!';
|
|
|
|
if (guild) {
|
|
if (!member) await guild.members.fetch(author.id);
|
|
}
|
|
|
|
const guildDatabase: GuildConfig = MYGuildConfig;
|
|
|
|
const mentionReg = new RegExp(`^(<@!?${client.user.id}>)`);
|
|
const mentionTest = mentionReg.test(content);
|
|
|
|
if (attachments && mentionTest) {
|
|
await handleAttachmentUpload(message);
|
|
return;
|
|
}
|
|
|
|
if (mentionTest) {
|
|
await channel.send(`Hey! My prefix is \`${prefix}\``);
|
|
return;
|
|
}
|
|
|
|
const prefixReg = new RegExp(`^${prefix}`);
|
|
const prefixTest = content.match(prefixReg);
|
|
if (prefixTest) {
|
|
const [prefix] = prefixTest;
|
|
const parsedContent = content.slice(prefix.length);
|
|
if (!parsedContent) return;
|
|
|
|
const command = parsedContent.split(' ')[0];
|
|
// Command Content Parsing.
|
|
let cmd: TextCommand | undefined;
|
|
|
|
let cmdName = command;
|
|
|
|
// Fetch command destination.
|
|
for (let index = 0; index < 2; index++) {
|
|
const commandMatching = client.commands.get(cmdName);
|
|
const aliasesMatching = client.aliases.get(cmdName);
|
|
|
|
if (commandMatching) {
|
|
cmd = commandMatching;
|
|
cmdName = cmd?.data.name;
|
|
break;
|
|
} else if (aliasesMatching) {
|
|
cmd = client.commands.get(aliasesMatching);
|
|
if (cmd) {
|
|
cmdName = cmd.data.name;
|
|
}
|
|
break;
|
|
} else {
|
|
if (index === 0) {
|
|
const expectedCommandName = await resembleCommandCheck(
|
|
message,
|
|
cmdName
|
|
);
|
|
if (expectedCommandName) {
|
|
cmdName = expectedCommandName.name;
|
|
message.createdTimestamp += expectedCommandName.timeTaken;
|
|
continue;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// Reject if no.
|
|
if (!cmd) return;
|
|
|
|
const cmdData = cmd.data;
|
|
|
|
/**
|
|
* Command's eligibility vaildation.
|
|
*/
|
|
|
|
if (cmd.enabled === false) return;
|
|
// Reject if not in development mode
|
|
if (cmdData.developmentOnly === true && !isDev) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
cmdData.ownerOnly === true &&
|
|
author.id !== process.env.DISCORD_OWNER_ID
|
|
)
|
|
return;
|
|
|
|
// Reject if dm mode while configurated to guild.
|
|
if (!guild && !cmdData.directMessageAllowed) return;
|
|
|
|
// Reject if command can executed NSFW channel when it's not in.
|
|
if (
|
|
cmdData.nsfwChannelRequired &&
|
|
(!guild || !channel.isTextBased()) &&
|
|
!(channel as TextChannel).nsfw
|
|
) {
|
|
const cEmbed = callbackEmbed({
|
|
text: 'You must be in **NSFW** channel before executing this commmand.',
|
|
color: 'Red',
|
|
mode: 'error',
|
|
});
|
|
author
|
|
.send({
|
|
embeds: [cEmbed],
|
|
})
|
|
.catch(() => undefined);
|
|
return;
|
|
}
|
|
|
|
// Reject if dm mode while configurated to guild.
|
|
if (!guild && !cmdData.directMessageAllowed) return;
|
|
|
|
// Reject when Target disabled or didn't pass.
|
|
if (guild) {
|
|
const commandDatasbase = guildDatabase?.commands;
|
|
// GUILD specifies disabled command.
|
|
if (
|
|
!commandDatasbase ||
|
|
commandDatasbase.global.disabled.includes(cmdName)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Specified CATAGORIES
|
|
if (
|
|
cmd.data.catagory &&
|
|
commandDatasbase.global.disabledCatagories.includes(cmd.data.catagory)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Specified CHANNEL
|
|
if (
|
|
commandDatasbase.channelDisabled
|
|
.find(x => x.id === channel.id)
|
|
?.cmds.includes(cmdName)
|
|
) {
|
|
author
|
|
.send({
|
|
content: `Command cannot be executed in this channel (#${channel.id})!`,
|
|
allowedMentions: { repliedUser: true },
|
|
})
|
|
.catch(() => undefined);
|
|
return;
|
|
}
|
|
|
|
// Specified ROLE
|
|
if (member?.roles.cache) {
|
|
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
for (const role of member.roles.cache) {
|
|
const hasRole = commandDatasbase.roleDisabled
|
|
.find(x => x.id === role[1].id)
|
|
?.cmds.includes(cmdName);
|
|
if (hasRole) return;
|
|
}
|
|
}
|
|
|
|
// Specified USER
|
|
if (
|
|
commandDatasbase.userDisabled
|
|
.find(x => x.id === author.id)
|
|
?.cmds.includes(cmdName)
|
|
) {
|
|
author
|
|
.send({
|
|
content: 'You are disabled from executing this command!',
|
|
allowedMentions: { repliedUser: true },
|
|
})
|
|
.catch(() => undefined);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
cmd.data.inVoiceChannelRequired === true &&
|
|
!member?.voice.channel
|
|
) {
|
|
const cEmbed = callbackEmbed({
|
|
text: 'You must be in voice channel before executing this commmand.',
|
|
color: 'Red',
|
|
mode: 'error',
|
|
});
|
|
await message.reply({
|
|
embeds: [cEmbed],
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* END of Command's eligibility vaildation.
|
|
*/
|
|
|
|
// Cooldown Validation
|
|
const now = Date.now();
|
|
const keyName = `CMD_${author.id}_${cmdName}`;
|
|
|
|
const cooldownInterval = cmd.data.cooldownInterval ?? 3000;
|
|
|
|
// Reject if user exists in cooldown.
|
|
if (cooldownCache.has(keyName)) {
|
|
const expectedEnd = cooldownCache.get(keyName);
|
|
if (expectedEnd && now < Number(expectedEnd)) {
|
|
const timeleft = parseMsToVisibleText(Number(expectedEnd) - now);
|
|
const postMessage = await message.reply({
|
|
content: `Before using this command, please wait for **${timeleft}**.`,
|
|
allowedMentions: { repliedUser: true },
|
|
});
|
|
setTimeout(() => {
|
|
if (postMessage.deletable)
|
|
postMessage.delete().catch(() => undefined);
|
|
}, 6000);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Set cooldown.
|
|
cooldownCache.set(
|
|
keyName,
|
|
now + cooldownInterval,
|
|
cooldownInterval / 1000
|
|
);
|
|
|
|
// Reject if excess usage.
|
|
if (cmd.data.intervalLimit) {
|
|
const key1 = 'INTERVAL' + keyName;
|
|
|
|
let doRejection = { is: false, which: '' };
|
|
const customTTL = {
|
|
minute: 60 * 1000,
|
|
hour: 60 * 60 * 1000,
|
|
day: 24 * 60 * 60 * 1000,
|
|
};
|
|
const intervalList = cmd.data.intervalLimit;
|
|
for (const [key, ms] of Object.entries(intervalList)) {
|
|
if (!ms) continue;
|
|
const keyTyped = key as keyof typeof intervalList;
|
|
if (!intervalList[keyTyped]) continue;
|
|
|
|
const userFeq = cooldownCache.get(keyTyped + key1) ?? '0';
|
|
|
|
// Do Rejection if number reached specified amount of allowed cooldown.
|
|
if (Number(userFeq) === intervalList[keyTyped]) {
|
|
doRejection = { is: true, which: keyTyped };
|
|
break;
|
|
}
|
|
|
|
// Set to Database with TTL.
|
|
cooldownCache.set(
|
|
keyTyped + key1,
|
|
(Number(userFeq) + 1).toString(),
|
|
customTTL[keyTyped]
|
|
);
|
|
}
|
|
|
|
if (doRejection.is) {
|
|
const postMessage = await message.reply({
|
|
content: `You have reached the maxmium usage in 1 **${doRejection.which}**!`,
|
|
allowedMentions: { repliedUser: true },
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (postMessage.deletable)
|
|
postMessage.delete().catch(() => undefined);
|
|
}, 6000);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Permission Check (BOT)
|
|
const requestPermsClient = cmd.data.clientRequiredPermissions;
|
|
if (guild && requestPermsClient) {
|
|
const permMissing: PermissionResolvable[] = [];
|
|
for (const perm of requestPermsClient) {
|
|
const botId = client.user.id;
|
|
if (botId) {
|
|
const isOwned = guild.members.cache
|
|
.get(botId)
|
|
?.permissions.has(perm);
|
|
if (!isOwned) permMissing.push(perm);
|
|
}
|
|
}
|
|
|
|
// Reject if BOT doesn't own permission(s).
|
|
if (permMissing.length > 0) {
|
|
const perms = permMissing
|
|
.map(index => `\`${Number(index)}\``)
|
|
.join(', ');
|
|
|
|
await message.reply({
|
|
content: `I don't have **PERMISSIONS**: ${perms}`,
|
|
});
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Permission Check (AUTHOR)
|
|
const requestPermsAuthor = cmd.data.authorRequiredPermissions;
|
|
if (guild && requestPermsAuthor) {
|
|
const permMissing: PermissionResolvable[] = [];
|
|
for (const perm of requestPermsAuthor) {
|
|
const isOwned = member?.permissions.has(perm);
|
|
if (!isOwned) permMissing.push(perm);
|
|
}
|
|
|
|
// Reject if AUTHOR doesn't own permission(s).
|
|
if (permMissing.length > 0) {
|
|
const perms = permMissing
|
|
.map(index => `\`${Number(index)}\``)
|
|
.join(', ');
|
|
|
|
await message.reply({
|
|
content: `You do not have required **PERMISSIONS**: ${perms}`,
|
|
});
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Pass
|
|
console.log(
|
|
`[CMD] ${author.tag} executed ${cmdName} in ${guild?.name ?? 'DM'}.`
|
|
);
|
|
const arguments_ = parsedContent.split(' ').slice(1);
|
|
|
|
if (arguments_[0] === 'help') {
|
|
if (
|
|
cmd.data.nsfwChannelRequired === true &&
|
|
(channel as TextChannel).nsfw
|
|
) {
|
|
const helpInfo = getCommandHelpInfo(cmd);
|
|
await message.reply({
|
|
embeds: [helpInfo],
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Arguments Checking
|
|
const requiredArugments = cmd.data.requiredArgs;
|
|
if (requiredArugments && requiredArugments.length > 0) {
|
|
let usage = `${prefix}${cmd.data.name}`;
|
|
for (const _argument_ of requiredArugments) {
|
|
let namedArguments: string = _argument_.type;
|
|
if (_argument_.type === 'STRING' && _argument_.text) {
|
|
namedArguments = _argument_.text.join(' | ');
|
|
} else if (_argument_.name) {
|
|
namedArguments += `(${_argument_.name})`;
|
|
}
|
|
usage += ` [${namedArguments}]`;
|
|
}
|
|
|
|
for (let index = 0, l = requiredArugments.length; index < l; index++) {
|
|
const _argument = requiredArugments[index];
|
|
const userArgument = arguments_[index];
|
|
|
|
switch (_argument.type) {
|
|
case 'STRING': {
|
|
if (_argument.required) {
|
|
if (!userArgument || userArgument.length === 0) {
|
|
return reject(message, usage, index.toString());
|
|
}
|
|
if (_argument.text && !_argument.text.includes(userArgument)) {
|
|
const text = `${index.toString()} (NOT_MATCH)`;
|
|
return reject(message, usage, text);
|
|
}
|
|
if (
|
|
_argument.customLength &&
|
|
(content.length > _argument.customLength.max! ||
|
|
content.length < _argument.customLength.min!)
|
|
) {
|
|
const text = `${index.toString()} (ERR_Length)`;
|
|
return reject(message, usage, text);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 'NUMBER': {
|
|
if (_argument.required && Number.isNaN(Number(userArgument))) {
|
|
return reject(message, usage, index.toString());
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_argument.rest) break;
|
|
}
|
|
}
|
|
|
|
// Run the actual command.
|
|
try {
|
|
return cmd.run({ message, args: arguments_ });
|
|
} catch (error) {
|
|
if (error instanceof Error) console.error(error);
|
|
}
|
|
}
|
|
},
|
|
};
|