Merge pull request #357 from VermiumSifell/dev

First User Experience, Stability & Developer Experience & Moderation command
This commit is contained in:
Axel Olausson Holtenäs 2022-06-12 01:11:23 +02:00 committed by GitHub
commit bfdba0ce4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 466 additions and 461 deletions

View file

@ -23,6 +23,7 @@ pino
Poäng
Profil
rando
Repliable
satta
senaste
Sifell

View file

@ -4,7 +4,7 @@
<br>
</h1>
<h3 align=center>An multi-purpose bot built with <a href=https://github.com/discordjs/discord.js>discord.js</a></h3>
<h3 align=center>A privacy-focused bot built with <a href=https://github.com/discordjs/discord.js>discord.js</a></h3>
<div align=center>
@ -15,61 +15,7 @@
</div>
<p align="center">
<a href="#about">About</a>
<a href="#Features">Features</a>
<a href="https://xyter.zyner.org">Documentation</a>
<a href="https://github.com/ZynerOrg/xyter/blob/master/docs/INSTALLATION.md">Installation</a>
<a href="#license">License</a>
<a href="#credits">Credits</a>
</p>
## ❓ About
Xyter is an open source, multi-purpose Discord bot that is develoepd by students. You can invite it to your Discord server using [this](https://bot.zyner.org) link! It comes packaged with a variety of commands and a multitude of settings that can be tailored to your server's specific needs.
**To run this on Pterodactyl** please set startup command to `./node_modules/.bin/ts-node src/index.ts`!
If you liked this repository, feel free to leave a star ⭐ to help promote Xyter!
**For a more updated documentation visit [this site](https://xyter.zyner.org/)!**
## ❗ Features
**10+** commands and counting across **13** different categories!
- 💰 **Credits**: `balance`, `gift`, `top`, `work`, `give`, `take`, `set` and `transfer`!
- 💬 **Counters**: `view`, `add`, `remove`!
- 🔨 **Settings**: `guild credits`, `guild pterodactyl`, `guild points` and `user appearence`!
- 👑 **Profile**: `view`!
- 🖼 **Reputation**: `give`!
- 💰 **Shop**: `roles buy`, `roles cancel` and `pterodactyl`!
- ❔ **Utilities**: `lookup`, `about` and `stats`!
- **Full list** of commands: [here](https://github.com/ZynerOrg/xyter/blob/master/docs/COMMANDS.md).
Xyter also comes packed with a variety of features, such as:
- **Slash Commands**
- **Multi-language support**.
- And much more! There are over **5+** settings to tweak!
## 📝 To-Do
- Bug fixes
- Code optimisation
- New discord features
- Suggestions we deem very good.
Some more is available in issues
## 📖 License
Released under the [GPL-3.0 License](https://github.com/ZynerOrg/xyter/blob/master/LICENSE) license.
## 📜 Credits
- **[Vermium#9649](https://github.com/VermiumSifell)** - Founder, creator, hoster.
- **[Mastergamer433#5762](https://github.com/Mastergamer433)** - Work command for credits.
- Want to be on this list, aswell? - Check out the [Contributing page](https://github.com/ZynerOrg/xyter/blob/master/docs/CONTRIBUTING.md).

View file

@ -1,13 +1,13 @@
import { token, clientId } from "../config/discord";
import { devMode, guildId } from "../config/other";
import { token, clientId } from "../../config/discord";
import { devMode, guildId } from "../../config/other";
import logger from "../logger";
import logger from "../../logger";
import { Client } from "discord.js";
import { REST } from "@discordjs/rest";
import { Routes } from "discord-api-types/v9";
import { RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10";
import { ICommand } from "../interfaces/Command";
import { ICommand } from "../../interfaces/Command";
export default async (client: Client) => {
const commandList: Array<RESTPostAPIApplicationCommandsJSONBody> = [];

View file

@ -1,10 +1,10 @@
// Dependencies
import { Client } from "discord.js";
import logger from "../logger";
import logger from "../../logger";
// Configuration
import { devMode, guildId } from "../config/other";
import { devMode, guildId } from "../../config/other";
export default async (client: Client) => {
if (!devMode) {

View file

@ -1,8 +1,8 @@
import crypto from "crypto";
import { secretKey, algorithm } from "../config/encryption";
import { secretKey, algorithm } from "../../config/encryption";
import { IEncryptionData } from "../interfaces/EncryptionData";
import { IEncryptionData } from "../../interfaces/EncryptionData";
const iv = crypto.randomBytes(16);

View file

@ -1,7 +1,4 @@
export default async (numOfSeconds: number, date: Date) => {
if (!numOfSeconds) throw new Error("numOfSeconds is required");
date.setSeconds(date.getSeconds() + numOfSeconds);
export default async (seconds: number, date: Date) => {
date.setSeconds(date.getSeconds() + seconds);
return date;
};

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction, Message } from "discord.js";
import { CommandInteraction, ButtonInteraction, Message } from "discord.js";
import logger from "../../logger";
@ -7,7 +7,7 @@ import getEmbedConfig from "../../helpers/getEmbedConfig";
import timeoutSchema from "../../models/timeout";
import addSeconds from "../../helpers/addSeconds";
export const interaction = async (i: CommandInteraction, cooldown: number) => {
export const command = async (i: CommandInteraction, cooldown: number) => {
const { guild, user, commandId } = i;
// Check if user has a timeout
@ -56,12 +56,57 @@ export const interaction = async (i: CommandInteraction, cooldown: number) => {
});
};
export const message = async (
message: Message,
cooldown: number,
id: string
) => {
const { guild, member } = message;
export const button = async (i: ButtonInteraction, cooldown: number) => {
const { guild, user, customId } = i;
// Check if user has a timeout
const hasTimeout = await timeoutSchema.findOne({
guildId: guild?.id || "0",
userId: user.id,
cooldown: cooldown,
timeoutId: customId,
});
// If user is not on timeout
if (hasTimeout) {
const { guildId, userId, timeoutId, createdAt } = hasTimeout;
const overDue = (await addSeconds(cooldown, createdAt)) < new Date();
if (!overDue) {
const diff = Math.round(
(new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000
);
throw new Error(
`You must wait ${diff} seconds before using this command.`
);
}
// Delete timeout
await timeoutSchema
.deleteOne({
guildId,
userId,
timeoutId,
cooldown,
})
.then(async () => {
logger.debug(
`Timeout document ${timeoutId} has been deleted from user ${userId}.`
);
});
}
// Create timeout
await timeoutSchema.create({
guildId: guild?.id || "0",
userId: user.id,
cooldown: cooldown,
timeoutId: customId,
});
};
export const message = async (msg: Message, cooldown: number, id: string) => {
const { guild, member } = msg;
if (!guild) throw new Error("Guild is undefined");
if (!member) throw new Error("Member is undefined");

View file

@ -1,25 +1,26 @@
import { CommandInteraction, MessageEmbed } from "discord.js";
import { Interaction, MessageEmbed } from "discord.js";
import getEmbedConfig from "../../helpers/getEmbedConfig";
export default async (interaction: CommandInteraction, ephemeral: boolean) => {
export default async (interaction: Interaction, ephemeral: boolean) => {
if (!interaction.isRepliable())
throw new Error(`Cannot reply to an interaction that is not repliable`);
await interaction.deferReply({
ephemeral,
});
const { waitColor, footerText, footerIcon } = await getEmbedConfig(
interaction.guild
);
const embedConfig = await getEmbedConfig(interaction.guild);
await interaction.editReply({
embeds: [
new MessageEmbed()
.setFooter({
text: footerText,
iconURL: footerIcon,
text: embedConfig.footerText,
iconURL: embedConfig.footerIcon,
})
.setTimestamp(new Date())
.setTitle("Processing your request")
.setColor(waitColor)
.setColor(embedConfig.waitColor)
.setDescription("Please wait..."),
],
});

View file

@ -3,18 +3,16 @@ import * as embedConfig from "../../config/embed";
import { Guild } from "discord.js";
export default async (guild: Guild | null) => {
if (guild == null)
return {
...embedConfig,
};
export default async (guild?: Guild | null) => {
if (!guild) {
return { ...embedConfig };
}
const guildConfig = await guildSchema.findOne({ guildId: guild.id });
if (guildConfig == null)
if (!guildConfig) {
return {
...embedConfig,
};
}
return guildConfig.embeds;
};

View file

@ -2,7 +2,7 @@ import fs from "fs";
const fsPromises = fs.promises;
export default async (path: string) => {
return fsPromises.readdir(path).catch(async (e) => {
throw new Error(`Could not list directory: ${path}`, e);
return fsPromises.readdir(path).catch(async (err) => {
throw new Error(`Could not list directory: ${path}`, err);
});
};

View file

@ -5,15 +5,12 @@ import logger from "../../logger";
// Function
export default async (client: Client) => {
if (!client?.user) throw new Error("Client's user is undefined.");
const { guilds } = client;
const memberCount = guilds.cache.reduce((a, g) => a + g.memberCount, 0);
const guildCount = guilds.cache.size;
const status = `${memberCount} users in ${guildCount} guilds.`;
client.user.setPresence({
activities: [{ type: "LISTENING", name: status }],
status: "online",

View file

@ -0,0 +1,9 @@
import { Snowflake } from "discord.js";
import { Document } from "mongoose";
export interface IShopRole extends Document {
guildId: Snowflake;
userId: Snowflake;
roleId: Snowflake;
lastPayed: Date;
}

12
src/jobs/shop/index.ts Normal file
View file

@ -0,0 +1,12 @@
// Dependencies
import { Client } from "discord.js";
import * as roles from "./modules/roles";
export const options = {
schedule: "*/5 * * * *", // https://crontab.guru/
};
export const execute = async (client: Client) => {
await roles.execute(client);
};

View file

@ -0,0 +1,10 @@
import { Client } from "discord.js";
import logger from "../../../../../logger";
import { IShopRole } from "../../../../../interfaces/ShopRole";
export const execute = async (_client: Client, role: IShopRole) => {
const { roleId } = role;
logger.silly(`Shop role ${roleId} is not due for payment.`);
};

View file

@ -0,0 +1,85 @@
import { Client } from "discord.js";
import logger from "../../../../../logger";
import { IShopRole } from "../../../../../interfaces/ShopRole";
import guildSchema from "../../../../../models/guild";
import userSchema from "../../../../../models/user";
import shopRoleSchema from "../../../../../models/shopRole";
export const execute = async (client: Client, role: IShopRole) => {
const { guildId, userId, roleId } = role;
if (!userId) throw new Error("User ID not found for shop role.");
const guildData = await guildSchema.findOne({ guildId });
if (!guildData) throw new Error("Guild not found.");
const userData = await userSchema.findOne({ guildId, userId });
if (!userData) throw new Error("User not found.");
const rGuild = client.guilds.cache.get(guildId);
if (!rGuild) throw new Error("Guild not found.");
const rMember = await rGuild.members.fetch(userId);
if (!rMember) throw new Error("Member not found.");
const rRole = rMember.roles.cache.get(roleId);
if (!rRole) throw new Error("Role not found.");
logger.debug(`Shop role ${roleId} is due for payment.`);
const { pricePerHour } = guildData.shop.roles;
if (userData.credits < pricePerHour) {
await rMember.roles
.remove(roleId)
.then(async () => {
await shopRoleSchema
.deleteOne({
userId,
roleId,
guildId,
})
.then(async () => {
logger.silly(
`Shop role document ${roleId} has been deleted from user ${userId}.`
);
})
.catch(async (err) => {
throw new Error(
`Error deleting shop role document ${roleId} from user ${userId}.`,
err
);
});
})
.catch(async (err) => {
throw new Error(
`Error removing role ${roleId} from user ${userId}.`,
err
);
});
throw new Error("User does not have enough credits.");
}
userData.credits -= pricePerHour;
await userData
.save()
.then(async () => {
logger.silly(`User ${userId} has been updated.`);
role.lastPayed = new Date();
await role
.save()
.then(async () => {
logger.silly(`Shop role ${roleId} has been updated.`);
})
.catch(async (err) => {
throw new Error(`Error updating shop role ${roleId}.`, err);
});
logger.debug(`Shop role ${roleId} has been paid.`);
})
.catch(async (err) => {
throw new Error(`Error updating user ${userId}.`, err);
});
};

View file

@ -0,0 +1,32 @@
import { Client } from "discord.js";
import { IShopRole } from "../../../../interfaces/ShopRole";
import shopRoleSchema from "../../../../models/shopRole";
import * as overDueForPayment from "./components/overDueForPayment";
import * as dueForPayment from "./components/dueForPayment";
export const execute = async (client: Client) => {
const roles = await shopRoleSchema.find();
await Promise.all(
roles.map(async (role: IShopRole) => {
const { lastPayed } = role;
const nextPayment = new Date(
lastPayed.setHours(lastPayed.getHours() + 1)
);
const now = new Date();
if (nextPayment > now) {
await dueForPayment.execute(client, role);
return;
}
if (nextPayment < now) {
await overDueForPayment.execute(client, role);
}
})
);
};

View file

@ -1,114 +0,0 @@
// Dependencies
import { Client } from "discord.js";
import logger from "../logger";
// Schemas
import userSchema from "../models/user";
import shopRoleSchema from "../models/shopRole";
import guildSchema from "../models/guild";
export const options = {
schedule: "*/5 * * * *", // https://crontab.guru/
};
export const execute = async (client: Client) => {
const roles = await shopRoleSchema.find();
await Promise.all(
roles.map(async (role) => {
const { guildId, userId, roleId } = role;
const lastPayment = new Date(role.lastPayed);
const nextPayment = new Date(
lastPayment.setHours(lastPayment.getHours() + 1)
);
if (new Date() < nextPayment) {
logger.silly(`Shop role ${roleId} is not due for payment.`);
}
const guildData = await guildSchema.findOne({ guildId });
if (!guildData) {
logger.error(`Guild ${guildId} not found.`);
return;
}
if (!userId) {
logger.error(`User ID not found for shop role ${roleId}.`);
return;
}
const userData = await userSchema.findOne({ guildId, userId });
if (!userData) {
logger.error(`User ${userId} not found for shop role ${roleId}.`);
return;
}
const rGuild = client?.guilds?.cache?.get(guildId);
const rMember = await rGuild?.members?.fetch(userId);
if (!rMember) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
return;
}
const rRole = rMember.roles.cache.get(roleId);
if (!rMember || !rRole) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
await shopRoleSchema
.deleteOne({
userId,
roleId,
guildId,
})
.then(async () => {
logger.silly(
`Shop role document ${roleId} has been deleted from user ${userId}.`
);
})
.catch(async (error) => {
logger.error(
`Error deleting shop role document ${roleId} from user ${userId}.`,
error
);
});
return;
}
if (new Date() > nextPayment) {
logger.silly(
`Shop role ${roleId} is due for payment. Withdrawing credits from user ${userId}.`
);
const { pricePerHour } = guildData.shop.roles;
if (userData.credits < pricePerHour) {
logger.error(
`User ${userId} does not have enough credits to pay for shop role ${roleId}.`
);
if (!rMember) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
return;
}
rMember.roles.remove(roleId);
return;
}
userData.credits -= pricePerHour;
await userData
.save()
.then(async () => {
role.lastPayed = new Date();
await role
.save()
.then(async () => {
logger.silly(`Shop role ${roleId} has been paid for.`);
})
.catch(async (err) => {
logger.error(
`Error saving shop role ${roleId} last payed date.`,
err
);
});
logger.silly(
`Shop role ${roleId} has been paid for. Keeping role ${roleId} for user ${userId}.`
);
})
.catch(async (err) => {
logger.error(
`Error saving user ${userId} credits for shop role ${roleId}.`,
err
);
});
}
})
);
};

View file

@ -1,8 +1,8 @@
import logger from "../logger";
import logger from "../../logger";
import timeoutSchema from "../models/timeout";
import timeoutSchema from "../../models/timeout";
import addSeconds from "../helpers/addSeconds";
import addSeconds from "../../helpers/addSeconds";
export const options = {
schedule: "*/30 * * * *", // https://crontab.guru/

View file

@ -4,7 +4,7 @@ import { IEncryptionData } from "../interfaces/EncryptionData";
export interface IApi {
guildId: Snowflake;
url: string;
url: IEncryptionData;
token: IEncryptionData;
}
@ -17,11 +17,18 @@ const apiSchema = new Schema<IApi>(
index: true,
},
url: {
iv: {
type: String,
required: true,
unique: false,
index: true,
default: "https://localhost/api/",
},
content: {
type: String,
required: true,
unique: false,
index: true,
},
},
token: {
iv: {
@ -29,14 +36,12 @@ const apiSchema = new Schema<IApi>(
required: true,
unique: false,
index: true,
default: "token",
},
content: {
type: String,
required: true,
unique: false,
index: true,
default: "token",
},
},
},

View file

@ -54,7 +54,7 @@ export default {
embeds: [
embed
.setDescription(
`Below are the top 10 users in this guild.
`Below are the top ten members in this guild.
${topTen.map(entry).join("\n")}
`

View file

@ -45,7 +45,7 @@ export default {
const guildDB = await fetchGuild(guild);
await cooldown.interaction(interaction, guildDB?.credits?.workTimeout);
await cooldown.command(interaction, guildDB?.credits?.workTimeout);
const creditsEarned = chance.integer({
min: 0,

View file

@ -75,14 +75,16 @@ export default {
if (!apiCredentials) return;
const url = encryption.decrypt(apiCredentials?.url);
const api = axios?.create({
baseURL: `${apiCredentials?.url}/api/`,
baseURL: `${url}/api/`,
headers: {
Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`,
},
});
const shopUrl = `${apiCredentials?.url}/store`;
const shopUrl = `${url}/store`;
await api
.post("vouchers", {

View file

@ -0,0 +1,22 @@
import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
import modules from "./modules";
export const moduleData = modules;
export const builder = new SlashCommandBuilder()
.setName("moderation")
.setDescription("Moderation.")
.addSubcommand(modules.prune.builder);
export const execute = async (interaction: CommandInteraction) => {
switch (interaction.options.getSubcommand()) {
case "prune":
return modules.prune.execute(interaction);
default:
throw new Error(
`Unknown subcommand: ${interaction.options.getSubcommand()}`
);
}
};

View file

@ -0,0 +1,3 @@
import prune from "./prune";
export default { prune };

View file

@ -0,0 +1,91 @@
// Dependencies
import {
CommandInteraction,
Permissions,
} from "discord.js";
// Configurations
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
metadata: {
guildOnly: true,
ephemeral: false,
permissions: [Permissions.FLAGS.MANAGE_MESSAGES],
},
builder: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("prune")
.setDescription("Prune messages!")
.addIntegerOption((option) =>
option
.setName("count")
.setDescription("How many messages you want to prune.")
.setRequired(true)
)
.addBooleanOption((option) =>
option.setName("bots").setDescription("Include bots.")
);
},
execute: async (interaction: CommandInteraction) => {
const { successColor, footerText, footerIcon } = await getEmbedConfig(
interaction.guild
);
const count = interaction.options.getInteger("count");
if (count == null) return;
const bots = interaction.options.getBoolean("bots");
if (count < 1 || count > 100) {
const interactionEmbed = {
title: "[:police_car:] Prune",
description: `You can only prune between 1 and 100 messages.`,
color: successColor,
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
};
await interaction.editReply({
embeds: [interactionEmbed],
});
return;
}
if (interaction?.channel?.type !== "GUILD_TEXT") return;
await interaction.channel.messages.fetch().then(async (messages) => {
const messagesToDelete = (
bots
? messages.filter((m) => m?.interaction?.id !== interaction.id)
: messages.filter(
(m) =>
m?.interaction?.id !== interaction.id && m?.author?.bot !== true
)
).first(count);
if (interaction?.channel?.type !== "GUILD_TEXT") return;
await interaction.channel
.bulkDelete(messagesToDelete, true)
.then(async () => {
const interactionEmbed = {
title: "[:police_car:] Prune",
description: `Successfully pruned \`${count}\` messages.`,
color: successColor,
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
};
await interaction.editReply({
embeds: [interactionEmbed],
});
});
});
},
};

View file

@ -52,7 +52,7 @@ export default {
await noSelfReputation(optionTarget, user);
// Check if user is on cooldown otherwise create one
await cooldown.interaction(interaction, timeout);
await cooldown.command(interaction, timeout);
switch (optionType) {
case "positive":

View file

@ -29,6 +29,7 @@ export default {
option
.setName("amount")
.setDescription("How much credits you want to withdraw.")
.setRequired(true)
);
},
execute: async (interaction: CommandInteraction) => {
@ -152,15 +153,16 @@ export default {
});
if (!apiCredentials) return;
const url = encryption.decrypt(apiCredentials?.url);
const api = axios?.create({
baseURL: `${apiCredentials.url}/api/`,
baseURL: `${url}/api/`,
headers: {
Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`,
},
});
const shopUrl = `${apiCredentials?.url}/store`;
const shopUrl = `${url}/store`;
const buttons = new MessageActionRow().addComponents(
new MessageButton()

View file

@ -29,6 +29,11 @@ export default {
.setStyle("LINK")
.setEmoji("📄")
.setURL("https://github.com/ZynerOrg/xyter"),
new MessageButton()
.setLabel("Documentation")
.setStyle("LINK")
.setEmoji("📚")
.setURL("https://xyter.zyner.org"),
new MessageButton()
.setLabel("Website")
.setStyle("LINK")

View file

@ -1,51 +0,0 @@
import { CommandInteraction, MessageEmbed } from "discord.js";
import * as cooldown from "../../../../helpers/cooldown";
import logger from "../../../../logger";
export default async (
interaction: CommandInteraction,
metadata: any,
embedConfig: any
) => {
if (
metadata.permissions &&
metadata.guildOnly &&
!interaction.memberPermissions?.has(metadata.permissions)
) {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:x:] Permission")
.setDescription(`You do not have the permission to manage the bot.`)
.setTimestamp(new Date())
.setColor(embedConfig.errorColor)
.setFooter({
text: embedConfig.footerText,
iconURL: embedConfig.footerIcon,
}),
],
});
}
logger.info(metadata);
if (metadata.cooldown) {
await cooldown
.interaction(interaction, metadata.cooldown)
.catch(async (error) => {
throw new Error("Cooldown error: " + error);
});
}
if (metadata.guildOnly) {
if (!interaction.guild) {
throw new Error("This command is guild only.");
}
}
if (metadata.dmOnly) {
if (interaction.guild) {
throw new Error("This command is DM only.");
}
}
};

View file

@ -1,123 +0,0 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import logger from "../../../../logger";
import deferReply from "../../../../helpers/deferReply";
import getEmbedConfig from "../../../../helpers/getEmbedConfig";
import capitalizeFirstLetter from "../../../../helpers/capitalizeFirstLetter";
import * as cooldown from "../../../../helpers/cooldown";
export default async (interaction: CommandInteraction) => {
if (!interaction.isButton()) return;
const { errorColor, footerText, footerIcon } = await getEmbedConfig(
interaction.guild
);
const { guild, customId, user, memberPermissions } = interaction;
const currentButton = await import(`../../../buttons/${customId}`);
if (currentButton == null) {
logger.silly(`Button ${customId} not found`);
}
const metadata = currentButton.metadata;
await deferReply(interaction, metadata.ephemeral || false);
if (metadata.guildOnly) {
if (!guild) {
logger.debug(`Guild is null`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:x:] Permission")
.setDescription("This command is only available for guild")
.setColor(errorColor)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
}
if (
metadata.permissions &&
metadata.guildOnly &&
!memberPermissions?.has(metadata.permissions)
) {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:x:] Permission")
.setDescription(`You do not have the permission to manage the bot.`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
if (metadata.dmOnly) {
if (guild) {
logger.silly(`Guild exist`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:x:] Permission")
.setDescription("This command is only available in DM.")
.setColor(errorColor)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
}
if (metadata.cooldown) {
await cooldown
.interaction(interaction, metadata.cooldown)
.catch(async (error) => {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:x:] Permission")
.setDescription(`${error}`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
});
}
await currentButton
.execute(interaction)
.then(async () => {
return logger?.silly(
`Button: ${customId} executed in guild: ${guild?.name} (${guild?.id}) by user: ${user?.tag} (${user?.id})`
);
})
.catch(async (error: string) => {
logger?.debug(`INTERACTION BUTTON CATCH: ${error}`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle(
`[:x:] ${capitalizeFirstLetter(
interaction.options.getSubcommand()
)}`
)
.setDescription(`${"``"}${error}${"``"}`)
.setColor(errorColor)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
});
};

View file

@ -1,48 +0,0 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import logger from "../../../../logger";
import deferReply from "../../../../helpers/deferReply";
import getEmbedConfig from "../../../../helpers/getEmbedConfig";
import getCommandMetadata from "../../../../helpers/getCommandMetadata";
import capitalizeFirstLetter from "../../../../helpers/capitalizeFirstLetter";
import * as cooldown from "../../../../helpers/cooldown";
export default async (interaction: CommandInteraction) => {
if (!interaction.isCommand()) return;
const { errorColor, footerText, footerIcon } = await getEmbedConfig(
interaction.guild
);
const { client, guild, commandName, user, memberPermissions } = interaction;
const currentCommand = client.commands.get(commandName);
if (currentCommand == null) {
logger.silly(`Command ${commandName} not found`);
}
const metadata = await getCommandMetadata(interaction, currentCommand);
await deferReply(interaction, metadata.ephemeral || false);
if (metadata.guildOnly && !guild)
throw new Error("This command is guild only.");
if (
metadata.permissions &&
metadata.guildOnly &&
!memberPermissions?.has(metadata.permissions)
)
throw new Error("You don't have the required permissions");
if (metadata.dmOnly && guild)
throw new Error("This command is only available in DM");
if (metadata.cooldown)
await cooldown.interaction(interaction, metadata.cooldown);
await currentCommand.execute(interaction);
};

View file

@ -0,0 +1,36 @@
// Dependencies
import { Interaction } from "discord.js";
import deferReply from "../../../../../helpers/deferReply";
import * as cooldown from "../../../../../helpers/cooldown";
export default async (interaction: Interaction) => {
if (!interaction.isButton()) return;
const { guild, customId, memberPermissions } = interaction;
const currentButton = await import(`../../../buttons/${customId}`);
if (!currentButton) throw new Error(`Unknown button ${customId}`);
const metadata = currentButton.metadata;
await deferReply(interaction, metadata.ephemeral || false);
if (metadata.guildOnly && !guild)
throw new Error("This command is guild only.");
if (
metadata.permissions &&
metadata.guildOnly &&
!memberPermissions?.has(metadata.permissions)
)
throw new Error("You don't have the required permissions");
if (metadata.dmOnly && guild)
throw new Error("This command is only available in DM");
if (metadata.cooldown) await cooldown.button(interaction, metadata.cooldown);
await currentButton.execute(interaction);
};

View file

@ -0,0 +1,34 @@
// Dependencies
import { Interaction } from "discord.js";
import deferReply from "../../../../../helpers/deferReply";
import getCommandMetadata from "../../../../../helpers/getCommandMetadata";
import * as cooldown from "../../../../../helpers/cooldown";
export default async (interaction: Interaction) => {
if (!interaction.isCommand()) return;
const { client, commandName } = interaction;
const currentCommand = client.commands.get(commandName);
if (!currentCommand) throw new Error(`Unknown command ${commandName}`);
const metadata = await getCommandMetadata(interaction, currentCommand);
await deferReply(interaction, metadata.ephemeral || false);
if (metadata.guildOnly && !interaction.guild)
throw new Error("This command is guild only.");
if (
metadata.permissions &&
metadata.guildOnly &&
!interaction.memberPermissions?.has(metadata.permissions)
)
throw new Error("You don't have the required permissions");
if (metadata.dmOnly && interaction.guild)
throw new Error("This command is only available in DM");
if (metadata.cooldown) await cooldown.command(interaction, metadata.cooldown);
await currentCommand.execute(interaction);
};

View file

@ -0,0 +1,11 @@
import { Interaction } from "discord.js";
import button from "./button";
import command from "./command";
import logger from "../../../../logger";
export const execute = async (interaction: Interaction) => {
await button(interaction);
await command(interaction);
};

View file

@ -2,8 +2,8 @@
import { CommandInteraction, MessageEmbed } from "discord.js";
// Dependencies
import isCommand from "../../events/interactionCreate/components/isCommand";
import isButton from "../../events/interactionCreate/components/isButton";
import * as handlers from "./handlers";
import logger from "../../../logger";
import audits from "./audits";
import { IEventOptions } from "../../../interfaces/EventOptions";
@ -27,11 +27,8 @@ export const execute = async (interaction: CommandInteraction) => {
await audits.execute(interaction);
try {
await isCommand(interaction);
await isButton(interaction);
} catch (error) {
logger.debug(`${error}`);
await handlers.execute(interaction).catch(async (err) => {
logger.debug(`${err}`);
return interaction.editReply({
embeds: [
@ -41,11 +38,11 @@ export const execute = async (interaction: CommandInteraction) => {
interaction.options.getSubcommand()
)}`
)
.setDescription(`${"``"}${error}${"``"}`)
.setDescription(`${"``"}${err}${"``"}`)
.setColor(errorColor)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
});
};