diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt
index d754312..f5e0cc5 100644
--- a/.cspell/custom-dictionary-workspace.txt
+++ b/.cspell/custom-dictionary-workspace.txt
@@ -23,6 +23,7 @@ pino
Poäng
Profil
rando
+Repliable
satta
senaste
Sifell
diff --git a/README.md b/README.md
index c6ca509..c73ca8a 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-
An multi-purpose bot built with discord.js
+A privacy-focused bot built with discord.js
@@ -15,61 +15,7 @@
- About
•
- Features
+ Documentation
•
- Installation
- •
- License
- •
- Credits
-
-## ❓ 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).
diff --git a/src/handlers/deployCommands.ts b/src/handlers/deployCommands/index.ts
similarity index 88%
rename from src/handlers/deployCommands.ts
rename to src/handlers/deployCommands/index.ts
index 4542377..492e1ab 100644
--- a/src/handlers/deployCommands.ts
+++ b/src/handlers/deployCommands/index.ts
@@ -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 = [];
diff --git a/src/handlers/devMode.ts b/src/handlers/devMode/index.ts
similarity index 79%
rename from src/handlers/devMode.ts
rename to src/handlers/devMode/index.ts
index 764fbcf..c7900a0 100644
--- a/src/handlers/devMode.ts
+++ b/src/handlers/devMode/index.ts
@@ -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) {
diff --git a/src/handlers/encryption.ts b/src/handlers/encryption/index.ts
similarity index 84%
rename from src/handlers/encryption.ts
rename to src/handlers/encryption/index.ts
index de5a7b9..8b6512c 100644
--- a/src/handlers/encryption.ts
+++ b/src/handlers/encryption/index.ts
@@ -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);
diff --git a/src/helpers/addSeconds/index.ts b/src/helpers/addSeconds/index.ts
index 91bff4e..6c47b18 100644
--- a/src/helpers/addSeconds/index.ts
+++ b/src/helpers/addSeconds/index.ts
@@ -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;
};
diff --git a/src/helpers/cooldown/index.ts b/src/helpers/cooldown/index.ts
index 2417d81..45d2729 100644
--- a/src/helpers/cooldown/index.ts
+++ b/src/helpers/cooldown/index.ts
@@ -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");
diff --git a/src/helpers/deferReply/index.ts b/src/helpers/deferReply/index.ts
index b5cfdc3..02cc413 100644
--- a/src/helpers/deferReply/index.ts
+++ b/src/helpers/deferReply/index.ts
@@ -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..."),
],
});
diff --git a/src/helpers/getEmbedConfig/index.ts b/src/helpers/getEmbedConfig/index.ts
index 22ddf29..ccd3fdb 100644
--- a/src/helpers/getEmbedConfig/index.ts
+++ b/src/helpers/getEmbedConfig/index.ts
@@ -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;
};
diff --git a/src/helpers/listDir/index.ts b/src/helpers/listDir/index.ts
index 95ed1da..382e41d 100644
--- a/src/helpers/listDir/index.ts
+++ b/src/helpers/listDir/index.ts
@@ -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);
});
};
diff --git a/src/helpers/updatePresence/index.ts b/src/helpers/updatePresence/index.ts
index a8c4a35..334a3e2 100644
--- a/src/helpers/updatePresence/index.ts
+++ b/src/helpers/updatePresence/index.ts
@@ -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",
diff --git a/src/interfaces/ShopRole.ts b/src/interfaces/ShopRole.ts
new file mode 100644
index 0000000..ec62cfd
--- /dev/null
+++ b/src/interfaces/ShopRole.ts
@@ -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;
+}
diff --git a/src/jobs/shop/index.ts b/src/jobs/shop/index.ts
new file mode 100644
index 0000000..b2ef366
--- /dev/null
+++ b/src/jobs/shop/index.ts
@@ -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);
+};
diff --git a/src/jobs/shop/modules/roles/components/dueForPayment.ts b/src/jobs/shop/modules/roles/components/dueForPayment.ts
new file mode 100644
index 0000000..ad8a59c
--- /dev/null
+++ b/src/jobs/shop/modules/roles/components/dueForPayment.ts
@@ -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.`);
+};
diff --git a/src/jobs/shop/modules/roles/components/overDueForPayment.ts b/src/jobs/shop/modules/roles/components/overDueForPayment.ts
new file mode 100644
index 0000000..77ab6e0
--- /dev/null
+++ b/src/jobs/shop/modules/roles/components/overDueForPayment.ts
@@ -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);
+ });
+};
diff --git a/src/jobs/shop/modules/roles/index.ts b/src/jobs/shop/modules/roles/index.ts
new file mode 100644
index 0000000..c8e393a
--- /dev/null
+++ b/src/jobs/shop/modules/roles/index.ts
@@ -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);
+ }
+ })
+ );
+};
diff --git a/src/jobs/shopRoles.ts b/src/jobs/shopRoles.ts
deleted file mode 100644
index e9ed502..0000000
--- a/src/jobs/shopRoles.ts
+++ /dev/null
@@ -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
- );
- });
- }
- })
- );
-};
diff --git a/src/jobs/timeouts.ts b/src/jobs/timeouts/index.ts
similarity index 84%
rename from src/jobs/timeouts.ts
rename to src/jobs/timeouts/index.ts
index 3198772..ce18f09 100644
--- a/src/jobs/timeouts.ts
+++ b/src/jobs/timeouts/index.ts
@@ -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/
diff --git a/src/models/api.ts b/src/models/api.ts
index d3690df..fd5cbd7 100644
--- a/src/models/api.ts
+++ b/src/models/api.ts
@@ -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(
index: true,
},
url: {
- type: String,
- required: true,
- unique: false,
- index: true,
- default: "https://localhost/api/",
+ iv: {
+ type: String,
+ required: true,
+ unique: false,
+ index: true,
+ },
+ content: {
+ type: String,
+ required: true,
+ unique: false,
+ index: true,
+ },
},
token: {
iv: {
@@ -29,14 +36,12 @@ const apiSchema = new Schema(
required: true,
unique: false,
index: true,
- default: "token",
},
content: {
type: String,
required: true,
unique: false,
index: true,
- default: "token",
},
},
},
diff --git a/src/plugins/commands/credits/modules/top/index.ts b/src/plugins/commands/credits/modules/top/index.ts
index 29aadda..54478aa 100644
--- a/src/plugins/commands/credits/modules/top/index.ts
+++ b/src/plugins/commands/credits/modules/top/index.ts
@@ -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")}
`
diff --git a/src/plugins/commands/credits/modules/work/index.ts b/src/plugins/commands/credits/modules/work/index.ts
index a84a696..ef0a550 100644
--- a/src/plugins/commands/credits/modules/work/index.ts
+++ b/src/plugins/commands/credits/modules/work/index.ts
@@ -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,
diff --git a/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts b/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts
index 9ee2f17..acb73cb 100644
--- a/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts
+++ b/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts
@@ -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", {
diff --git a/src/plugins/commands/moderation/index.ts b/src/plugins/commands/moderation/index.ts
new file mode 100644
index 0000000..73b4994
--- /dev/null
+++ b/src/plugins/commands/moderation/index.ts
@@ -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()}`
+ );
+ }
+};
diff --git a/src/plugins/commands/moderation/modules/index.ts b/src/plugins/commands/moderation/modules/index.ts
new file mode 100644
index 0000000..e7dd532
--- /dev/null
+++ b/src/plugins/commands/moderation/modules/index.ts
@@ -0,0 +1,3 @@
+import prune from "./prune";
+
+export default { prune };
diff --git a/src/plugins/commands/moderation/modules/prune/index.ts b/src/plugins/commands/moderation/modules/prune/index.ts
new file mode 100644
index 0000000..c847e47
--- /dev/null
+++ b/src/plugins/commands/moderation/modules/prune/index.ts
@@ -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],
+ });
+ });
+ });
+ },
+};
diff --git a/src/plugins/commands/reputation/modules/give/index.ts b/src/plugins/commands/reputation/modules/give/index.ts
index c899ecb..eb58abe 100644
--- a/src/plugins/commands/reputation/modules/give/index.ts
+++ b/src/plugins/commands/reputation/modules/give/index.ts
@@ -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":
diff --git a/src/plugins/commands/shop/modules/cpgg/index.ts b/src/plugins/commands/shop/modules/cpgg/index.ts
index aeee472..90dbe70 100644
--- a/src/plugins/commands/shop/modules/cpgg/index.ts
+++ b/src/plugins/commands/shop/modules/cpgg/index.ts
@@ -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()
diff --git a/src/plugins/commands/utility/modules/about/index.ts b/src/plugins/commands/utility/modules/about/index.ts
index f89d74d..92af9cb 100644
--- a/src/plugins/commands/utility/modules/about/index.ts
+++ b/src/plugins/commands/utility/modules/about/index.ts
@@ -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")
diff --git a/src/plugins/events/interactionCreate/components/checks.ts b/src/plugins/events/interactionCreate/components/checks.ts
deleted file mode 100644
index 006a14f..0000000
--- a/src/plugins/events/interactionCreate/components/checks.ts
+++ /dev/null
@@ -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.");
- }
- }
-};
diff --git a/src/plugins/events/interactionCreate/components/isButton.ts b/src/plugins/events/interactionCreate/components/isButton.ts
deleted file mode 100644
index eaf1605..0000000
--- a/src/plugins/events/interactionCreate/components/isButton.ts
+++ /dev/null
@@ -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 }),
- ],
- });
- });
-};
diff --git a/src/plugins/events/interactionCreate/components/isCommand.ts b/src/plugins/events/interactionCreate/components/isCommand.ts
deleted file mode 100644
index 3a14ad6..0000000
--- a/src/plugins/events/interactionCreate/components/isCommand.ts
+++ /dev/null
@@ -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);
-};
diff --git a/src/plugins/events/interactionCreate/handlers/button/index.ts b/src/plugins/events/interactionCreate/handlers/button/index.ts
new file mode 100644
index 0000000..3373f09
--- /dev/null
+++ b/src/plugins/events/interactionCreate/handlers/button/index.ts
@@ -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);
+};
diff --git a/src/plugins/events/interactionCreate/handlers/command/index.ts b/src/plugins/events/interactionCreate/handlers/command/index.ts
new file mode 100644
index 0000000..b6b30ec
--- /dev/null
+++ b/src/plugins/events/interactionCreate/handlers/command/index.ts
@@ -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);
+};
diff --git a/src/plugins/events/interactionCreate/handlers/index.ts b/src/plugins/events/interactionCreate/handlers/index.ts
new file mode 100644
index 0000000..c29bfc3
--- /dev/null
+++ b/src/plugins/events/interactionCreate/handlers/index.ts
@@ -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);
+};
diff --git a/src/plugins/events/interactionCreate/index.ts b/src/plugins/events/interactionCreate/index.ts
index f7365ec..aa6e763 100644
--- a/src/plugins/events/interactionCreate/index.ts
+++ b/src/plugins/events/interactionCreate/index.ts
@@ -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 }),
],
});
- }
+ });
};