Merge pull request #357 from VermiumSifell/dev
First User Experience, Stability & Developer Experience & Moderation command
This commit is contained in:
commit
bfdba0ce4c
35 changed files with 466 additions and 461 deletions
|
@ -23,6 +23,7 @@ pino
|
|||
Poäng
|
||||
Profil
|
||||
rando
|
||||
Repliable
|
||||
satta
|
||||
senaste
|
||||
Sifell
|
||||
|
|
58
README.md
58
README.md
|
@ -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).
|
||||
|
|
|
@ -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> = [];
|
|
@ -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) {
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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..."),
|
||||
],
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
9
src/interfaces/ShopRole.ts
Normal file
9
src/interfaces/ShopRole.ts
Normal 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
12
src/jobs/shop/index.ts
Normal 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);
|
||||
};
|
10
src/jobs/shop/modules/roles/components/dueForPayment.ts
Normal file
10
src/jobs/shop/modules/roles/components/dueForPayment.ts
Normal 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.`);
|
||||
};
|
85
src/jobs/shop/modules/roles/components/overDueForPayment.ts
Normal file
85
src/jobs/shop/modules/roles/components/overDueForPayment.ts
Normal 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);
|
||||
});
|
||||
};
|
32
src/jobs/shop/modules/roles/index.ts
Normal file
32
src/jobs/shop/modules/roles/index.ts
Normal 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);
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -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/
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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")}
|
||||
`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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", {
|
||||
|
|
22
src/plugins/commands/moderation/index.ts
Normal file
22
src/plugins/commands/moderation/index.ts
Normal 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()}`
|
||||
);
|
||||
}
|
||||
};
|
3
src/plugins/commands/moderation/modules/index.ts
Normal file
3
src/plugins/commands/moderation/modules/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import prune from "./prune";
|
||||
|
||||
export default { prune };
|
91
src/plugins/commands/moderation/modules/prune/index.ts
Normal file
91
src/plugins/commands/moderation/modules/prune/index.ts
Normal 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],
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
|
@ -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":
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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 }),
|
||||
],
|
||||
});
|
||||
});
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
11
src/plugins/events/interactionCreate/handlers/index.ts
Normal file
11
src/plugins/events/interactionCreate/handlers/index.ts
Normal 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);
|
||||
};
|
|
@ -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 }),
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue