Merge branch 'dev' into renovate/lint-staged-13.x
This commit is contained in:
commit
188cf9eafc
197 changed files with 2752 additions and 3157 deletions
|
@ -1,5 +1,6 @@
|
|||
# Custom Dictionary Words
|
||||
Controlpanel
|
||||
cooldown
|
||||
cpgg
|
||||
dagen
|
||||
discordjs
|
||||
|
@ -22,6 +23,7 @@ pino
|
|||
Poäng
|
||||
Profil
|
||||
rando
|
||||
Repliable
|
||||
satta
|
||||
senaste
|
||||
Sifell
|
||||
|
|
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,32 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels: "bug"
|
||||
assignees: "VermiumSifell"
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
|
||||
- Commit: [git rev-parse HEAD]
|
||||
- Branch: [git branch --show-current]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
60
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
name: 🐞 Bug
|
||||
description: File a bug/issue
|
||||
title: "[BUG]: <short_description_of_issue>"
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. Run '...'
|
||||
3. See error...
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
examples:
|
||||
- **OS**: Ubuntu 20.04
|
||||
- **Node**: 13.14.0
|
||||
- **npm**: 7.6.3
|
||||
- **xyter**: 7d02cf9
|
||||
value: |
|
||||
- OS: `lsb_release -d`
|
||||
- Node: `node -v`
|
||||
- npm: `npm -v`
|
||||
- xyter: `git rev-parse --short HEAD`
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
48
.github/ISSUE_TEMPLATE/command_request.yaml
vendored
Normal file
48
.github/ISSUE_TEMPLATE/command_request.yaml
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
name: 🤖 Request a new command
|
||||
description: Suggest an command for this project
|
||||
title: "[New Command]: /<command category> [command_group] <command_name> "
|
||||
labels: ["enhancement", "new-command"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! We will use `/manage credits give` as an example for our placeholders.
|
||||
- type: input
|
||||
id: category
|
||||
attributes:
|
||||
label: Category
|
||||
description: Where should we put this command?
|
||||
placeholder: ex. manage
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: group
|
||||
attributes:
|
||||
label: Group
|
||||
description: Does this command belong to this group?
|
||||
placeholder: ex. credits
|
||||
- type: input
|
||||
id: command
|
||||
attributes:
|
||||
label: Command
|
||||
description: What should we call this command?
|
||||
placeholder: ex. give
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: What should the command do?
|
||||
description: Please tell us your concept, how it should work, and if there should be any additional features.
|
||||
placeholder: "I would like to have a command to give users credits, that would make it easier for me to administrate credits! I would like it to add a specified amount of credits to specified user, without taking credits from the executer. Command should require Manage Guild permission. When successful, it should return something like: Added <amount> credits to <user>!"
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com)
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -5,9 +5,10 @@ config.json
|
|||
package-lock.json
|
||||
|
||||
|
||||
**/config/*.ts
|
||||
!**/config/index.ts
|
||||
!**/config/example.*.ts
|
||||
config/
|
||||
|
||||
# Build
|
||||
build/
|
||||
|
||||
|
||||
# Logs
|
||||
|
|
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).
|
||||
|
|
13
package.json
13
package.json
|
@ -27,16 +27,16 @@
|
|||
"email": "vermium@zyner.org"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.13.0",
|
||||
"@discordjs/rest": "^0.4.0",
|
||||
"@discordjs/builders": "^0.15.0",
|
||||
"@discordjs/rest": "^0.5.0",
|
||||
"@types/i18next-fs-backend": "^1.1.2",
|
||||
"axios": "^0.27.2",
|
||||
"chance": "^1.1.8",
|
||||
"common": "^0.2.5",
|
||||
"crypto": "^1.0.1",
|
||||
"discord-api-types": "^0.33.0",
|
||||
"discord-api-types": "^0.32.0",
|
||||
"discord.js": "^13.6.0",
|
||||
"i18n": "^0.14.2",
|
||||
"i18n": "^0.15.0",
|
||||
"i18next": "^21.6.13",
|
||||
"i18next-async-backend": "^2.0.0",
|
||||
"i18next-fs-backend": "^1.1.4",
|
||||
|
@ -56,15 +56,16 @@
|
|||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"eslint": "8.15.0",
|
||||
"eslint": "8.17.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-no-loops": "^0.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"husky": "8.0.1",
|
||||
"jest": "28.0.0",
|
||||
"jest": "28.1.1",
|
||||
"lint-staged": "13.0.1",
|
||||
"nodemon": "^2.0.16",
|
||||
"prettier": "^2.6.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
|
|
@ -8,4 +8,7 @@ export const guildId = "";
|
|||
export const hosterName = "someone";
|
||||
|
||||
// Hoster Url
|
||||
export const hosterUrl = "scheme://domain.tld";
|
||||
export const hosterUrl = "https://xyter.zyner.org/customization/change-hoster";
|
||||
|
||||
// Winston log level
|
||||
export const logLevel = "info";
|
|
@ -1,22 +0,0 @@
|
|||
// 3rd party dependencies
|
||||
import mongoose from "mongoose";
|
||||
|
||||
// Dependencies
|
||||
import logger from "@logger";
|
||||
|
||||
// Configuration
|
||||
import { url } from "@config/database";
|
||||
|
||||
export default async () => {
|
||||
await mongoose.connect(url).then(async (connection) => {
|
||||
logger.info(`Connected to database: ${connection.connection.name}`);
|
||||
});
|
||||
|
||||
mongoose.connection.on("error", async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
|
||||
mongoose.connection.on("warn", async (warning) => {
|
||||
logger.warn(warning);
|
||||
});
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
// 3rd party dependencies
|
||||
import { Guild } from "discord.js";
|
||||
|
||||
// Dependencies
|
||||
import updatePresence from "@helpers/updatePresence";
|
||||
import fetchGuild from "@helpers/fetchGuild";
|
||||
import logger from "@logger";
|
||||
|
||||
export default {
|
||||
async execute(guild: Guild) {
|
||||
const { client } = guild;
|
||||
|
||||
logger?.silly(`Added to guild: ${guild.name} (${guild.id})`);
|
||||
|
||||
await fetchGuild(guild);
|
||||
await updatePresence(client);
|
||||
|
||||
logger.silly(`guildCreate: ${guild}`);
|
||||
},
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
// 3rd party dependencies
|
||||
import { Guild } from "discord.js";
|
||||
|
||||
// Dependencies
|
||||
import updatePresence from "@helpers/updatePresence";
|
||||
import dropGuild from "@helpers/dropGuild";
|
||||
import logger from "@logger";
|
||||
|
||||
export default {
|
||||
async execute(guild: Guild) {
|
||||
const { client } = guild;
|
||||
|
||||
logger?.silly(`Deleted from guild: ${guild.name} (${guild.id})`);
|
||||
|
||||
await dropGuild(guild);
|
||||
await updatePresence(client);
|
||||
|
||||
logger.silly(`guildDelete: ${guild}`);
|
||||
},
|
||||
};
|
|
@ -1,58 +0,0 @@
|
|||
import logger from "@logger";
|
||||
import { GuildMember, MessageEmbed, TextChannel } from "discord.js";
|
||||
|
||||
import guildSchema from "@schemas/guild";
|
||||
|
||||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
|
||||
export default {
|
||||
execute: async (member: GuildMember) => {
|
||||
const { footerText, footerIcon, successColor } = await getEmbedConfig(
|
||||
member.guild
|
||||
);
|
||||
|
||||
const guildData = await guildSchema.findOne({ guildId: member.guild.id });
|
||||
|
||||
const { client } = member;
|
||||
|
||||
if (guildData === null) return;
|
||||
|
||||
if (guildData.audits.status !== true) return;
|
||||
if (!guildData.audits.channelId) return;
|
||||
|
||||
const channel = client.channels.cache.get(`${guildData.audits.channelId}`);
|
||||
|
||||
if (channel === null) return;
|
||||
|
||||
(channel as TextChannel)
|
||||
.send({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setColor(successColor)
|
||||
.setAuthor({
|
||||
name: "Member Joined",
|
||||
iconURL: member.user.displayAvatarURL(),
|
||||
})
|
||||
.setDescription(`${member.user} ${member.user.tag}`)
|
||||
.addFields([
|
||||
{ name: "Account Age", value: `${member.user.createdAt}` },
|
||||
])
|
||||
.setTimestamp()
|
||||
.setFooter({
|
||||
text: footerText,
|
||||
iconURL: footerIcon,
|
||||
}),
|
||||
],
|
||||
})
|
||||
.then(async () => {
|
||||
logger.info(
|
||||
`Audit log sent for event guildMemberAdd in guild ${member.guild.name} (${member.guild.id})`
|
||||
);
|
||||
})
|
||||
.catch(async () => {
|
||||
logger.error(
|
||||
`Audit log failed to send for event guildMemberAdd in guild ${member.guild.name} (${member.guild.id})`
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
// 3rd party dependencies
|
||||
import { GuildMember } from "discord.js";
|
||||
|
||||
// Dependencies
|
||||
import updatePresence from "@helpers/updatePresence";
|
||||
import fetchUser from "@helpers/fetchUser";
|
||||
import logger from "@logger";
|
||||
import joinMessage from "../guildMemberAdd/joinMessage";
|
||||
import audits from "../guildMemberAdd/audits";
|
||||
|
||||
export default {
|
||||
async execute(member: GuildMember) {
|
||||
const { client, user, guild } = member;
|
||||
|
||||
logger?.silly(
|
||||
`New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})`
|
||||
);
|
||||
|
||||
await audits.execute(member);
|
||||
await joinMessage.execute(member);
|
||||
await fetchUser(user, guild);
|
||||
await updatePresence(client);
|
||||
},
|
||||
};
|
|
@ -1,55 +0,0 @@
|
|||
import logger from "@logger";
|
||||
import { GuildMember, MessageEmbed, TextChannel } from "discord.js";
|
||||
|
||||
import guildSchema from "@schemas/guild";
|
||||
|
||||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
|
||||
export default {
|
||||
execute: async (member: GuildMember) => {
|
||||
const { footerText, footerIcon, errorColor } = await getEmbedConfig(
|
||||
member.guild
|
||||
);
|
||||
|
||||
const guildData = await guildSchema.findOne({ guildId: member.guild.id });
|
||||
|
||||
const { client } = member;
|
||||
|
||||
if (guildData === null) return;
|
||||
|
||||
if (guildData.audits.status !== true) return;
|
||||
if (!guildData.audits.channelId) return;
|
||||
|
||||
const channel = client.channels.cache.get(`${guildData.audits.channelId}`);
|
||||
|
||||
if (channel === null) return;
|
||||
|
||||
(channel as TextChannel)
|
||||
.send({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setColor(errorColor)
|
||||
.setAuthor({
|
||||
name: "Member Left",
|
||||
iconURL: member.user.displayAvatarURL(),
|
||||
})
|
||||
.setDescription(`${member.user} ${member.user.tag}`)
|
||||
.setTimestamp()
|
||||
.setFooter({
|
||||
text: footerText,
|
||||
iconURL: footerIcon,
|
||||
}),
|
||||
],
|
||||
})
|
||||
.then(async () => {
|
||||
logger.info(
|
||||
`Audit log sent for event guildMemberRemove in guild ${member.guild.name} (${member.guild.id})`
|
||||
);
|
||||
})
|
||||
.catch(async () => {
|
||||
logger.error(
|
||||
`Audit log failed to send for event guildMemberRemove in guild ${member.guild.name} (${member.guild.id})`
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
// 3rd party dependencies
|
||||
import { GuildMember } from "discord.js";
|
||||
|
||||
// Dependencies
|
||||
import updatePresence from "@helpers/updatePresence";
|
||||
import dropUser from "@helpers/dropUser";
|
||||
import logger from "@logger";
|
||||
import leaveMessage from "./leaveMessage";
|
||||
import audits from "./audits";
|
||||
|
||||
export default {
|
||||
async execute(member: GuildMember) {
|
||||
const { client, user, guild } = member;
|
||||
|
||||
logger?.silly(
|
||||
`Removed member: ${user.tag} (${user.id}) from guild: ${guild.name} (${guild.id})`
|
||||
);
|
||||
|
||||
await audits.execute(member);
|
||||
await leaveMessage.execute(member);
|
||||
await dropUser(user, guild);
|
||||
await updatePresence(client);
|
||||
},
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
// 3rd party dependencies
|
||||
import mongoose from "mongoose";
|
||||
|
||||
// Dependencies
|
||||
import logger from "@logger";
|
||||
|
||||
// Configuration
|
||||
import { url } from "@config/database";
|
||||
|
||||
export default async () => {
|
||||
await mongoose.connect(url).then(async (connection) => {
|
||||
logger.info(`Connected to database: ${connection.connection.name}`);
|
||||
});
|
||||
|
||||
mongoose.connection.on("error", async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
|
||||
mongoose.connection.on("warn", async (warning) => {
|
||||
logger.warn(warning);
|
||||
});
|
||||
};
|
|
@ -1,102 +0,0 @@
|
|||
// Dependencies
|
||||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
|
||||
import logger from "@logger";
|
||||
|
||||
import deferReply from "@root/helpers/deferReply";
|
||||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
import getCommandMetadata from "@helpers/getCommandMetadata";
|
||||
|
||||
export default async (interaction: CommandInteraction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
if (interaction.guild == null) 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.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.guildOnly) {
|
||||
if (!guild) {
|
||||
logger.debug(`Guild is null`);
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setDescription("This command is only available for guild")
|
||||
.setColor(errorColor)
|
||||
.setTimestamp(new Date())
|
||||
.setFooter({ text: footerText, iconURL: footerIcon }),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.dmOnly) {
|
||||
if (guild) {
|
||||
logger.silly(`Guild exist`);
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setDescription("This command is only available in DM.")
|
||||
.setColor(errorColor)
|
||||
.setTimestamp(new Date())
|
||||
.setFooter({ text: footerText, iconURL: footerIcon }),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await currentCommand
|
||||
.execute(interaction)
|
||||
.then(async () => {
|
||||
return logger?.silly(
|
||||
`Command: ${commandName} executed in guild: ${guild?.name} (${guild?.id}) by user: ${user?.tag} (${user?.id})`
|
||||
);
|
||||
})
|
||||
.catch(async (error: any) => {
|
||||
logger?.error(`${error}`);
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setTitle("Error")
|
||||
.setDescription(
|
||||
`There was an error executing the command: **${currentCommand?.data?.name}**.`
|
||||
)
|
||||
.setColor(errorColor)
|
||||
.setTimestamp(new Date())
|
||||
.setFooter({ text: footerText, iconURL: footerIcon }),
|
||||
],
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
// 3rd party dependencies
|
||||
import { CommandInteraction } from "discord.js";
|
||||
|
||||
// Dependencies
|
||||
import isCommand from "@root/events/interactionCreate/components/isCommand";
|
||||
import logger from "@logger";
|
||||
import audits from "./audits";
|
||||
|
||||
export default {
|
||||
async execute(interaction: CommandInteraction) {
|
||||
const { guild, id } = interaction;
|
||||
|
||||
logger?.silly(
|
||||
`New interaction: ${id} in guild: ${guild?.name} (${guild?.id})`
|
||||
);
|
||||
|
||||
await audits.execute(interaction);
|
||||
await isCommand(interaction);
|
||||
},
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import { Message } from "discord.js";
|
||||
import modules from "@events/messageCreate/modules";
|
||||
|
||||
export default {
|
||||
async execute(message: Message) {
|
||||
await modules.credits.execute(message);
|
||||
await modules.points.execute(message);
|
||||
await modules.counters.execute(message);
|
||||
},
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
import logger from "@logger";
|
||||
import timeouts from "@schemas/timeout";
|
||||
import { Message } from "discord.js";
|
||||
|
||||
import fetchUser from "@helpers/fetchUser";
|
||||
import fetchGuild from "@helpers/fetchGuild";
|
||||
|
||||
export default {
|
||||
execute: async (message: Message) => {
|
||||
const { guild, author, content, channel } = message;
|
||||
|
||||
if (guild == null) return;
|
||||
if (author.bot) return;
|
||||
if (channel?.type !== "GUILD_TEXT") return;
|
||||
|
||||
const { id: guildId } = guild;
|
||||
const { id: userId } = author;
|
||||
|
||||
const guildData = await fetchGuild(guild);
|
||||
const userData = await fetchUser(author, guild);
|
||||
|
||||
if (content.length < guildData.credits.minimumLength) return;
|
||||
|
||||
const timeoutData = {
|
||||
guildId,
|
||||
userId,
|
||||
timeoutId: "2022-04-14-13-51-00",
|
||||
};
|
||||
|
||||
const timeout = await timeouts.findOne(timeoutData);
|
||||
|
||||
if (timeout) {
|
||||
logger.silly(
|
||||
`User ${userId} in guild ${guildId} is on timeout 2022-04-14-13-51-00`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
userData.credits += guildData.credits.rate;
|
||||
|
||||
await userData
|
||||
.save()
|
||||
.then(async () => {
|
||||
logger.silly(
|
||||
`User ${userId} in guild ${guildId} has ${userData.credits} credits`
|
||||
);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(
|
||||
`Error saving credits for user ${userId} in guild ${guildId}`,
|
||||
err
|
||||
);
|
||||
});
|
||||
|
||||
await timeouts
|
||||
.create(timeoutData)
|
||||
.then(async () => {
|
||||
logger.silly(
|
||||
`Timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId} has been created`
|
||||
);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(
|
||||
`Error creating timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId}`,
|
||||
err
|
||||
);
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
await timeouts
|
||||
.deleteOne(timeoutData)
|
||||
.then(async () => {
|
||||
logger.silly(
|
||||
`Timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId} has been deleted`
|
||||
);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(
|
||||
`Error deleting timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId}`,
|
||||
err
|
||||
);
|
||||
});
|
||||
}, guildData.credits.timeout);
|
||||
},
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
import counters from "@events/messageCreate/modules/counters";
|
||||
import credits from "@events/messageCreate/modules/credits";
|
||||
import points from "@events/messageCreate/modules/points";
|
||||
|
||||
export default {
|
||||
counters,
|
||||
credits,
|
||||
points,
|
||||
};
|
|
@ -1,89 +0,0 @@
|
|||
import logger from "@logger";
|
||||
import timeouts from "@schemas/timeout";
|
||||
|
||||
import fetchUser from "@helpers/fetchUser";
|
||||
import fetchGuild from "@helpers/fetchGuild";
|
||||
|
||||
import { Message } from "discord.js";
|
||||
export default {
|
||||
execute: async (message: Message) => {
|
||||
const { guild, author, content, channel } = message;
|
||||
|
||||
if (guild == null) return;
|
||||
if (author.bot) return;
|
||||
if (channel?.type !== "GUILD_TEXT") return;
|
||||
|
||||
const { id: guildId } = guild;
|
||||
const { id: userId } = author;
|
||||
|
||||
const guildData = await fetchGuild(guild);
|
||||
const userData = await fetchUser(author, guild);
|
||||
|
||||
if (content.length < guildData.credits.minimumLength) return;
|
||||
|
||||
const timeoutData = {
|
||||
guildId,
|
||||
userId,
|
||||
timeoutId: "2022-04-14-14-15-00",
|
||||
};
|
||||
|
||||
const timeout = await timeouts.findOne(timeoutData);
|
||||
|
||||
if (timeout) {
|
||||
logger.silly(
|
||||
`User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id} is on timeout 2022-04-14-14-15-00`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
userData.points += guildData.points.rate;
|
||||
|
||||
await userData
|
||||
.save()
|
||||
.then(async () => {
|
||||
logger.silly(
|
||||
`Successfully saved user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`
|
||||
);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(
|
||||
`Error saving points for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`,
|
||||
err
|
||||
);
|
||||
});
|
||||
|
||||
logger.silly(
|
||||
`User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id}) has ${userData.points} points`
|
||||
);
|
||||
|
||||
await timeouts
|
||||
.create(timeoutData)
|
||||
.then(async () => {
|
||||
logger.silly(
|
||||
`Successfully created timeout for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`
|
||||
);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(
|
||||
`Error creating timeout 2022-04-14-14-15-00 for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`,
|
||||
err
|
||||
);
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
await timeouts
|
||||
.deleteOne(timeoutData)
|
||||
.then(async () => {
|
||||
logger.silly(
|
||||
`Successfully deleted timeout 2022-04-14-14-15-00 for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`
|
||||
);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(
|
||||
`Error deleting timeout 2022-04-14-14-15-00 for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`,
|
||||
err
|
||||
);
|
||||
});
|
||||
}, guildData.points.timeout);
|
||||
},
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import { Message } from "discord.js";
|
||||
import audits from "@events/messageDelete/audits";
|
||||
import counter from "./modules/counter";
|
||||
|
||||
export default {
|
||||
async execute(message: Message) {
|
||||
await audits.execute(message);
|
||||
await counter(message);
|
||||
},
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
// Dependencies
|
||||
import { Message } from "discord.js";
|
||||
import logger from "@logger";
|
||||
|
||||
// Modules
|
||||
import counter from "./modules/counter";
|
||||
|
||||
import audits from "./audits";
|
||||
|
||||
export default {
|
||||
async execute(oldMessage: Message, newMessage: Message) {
|
||||
const { author, guild } = newMessage;
|
||||
|
||||
await audits.execute(oldMessage, newMessage);
|
||||
|
||||
logger?.silly(
|
||||
`Message update event fired by ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`
|
||||
);
|
||||
|
||||
if (author?.bot) return logger?.silly(`Message update event fired by bot`);
|
||||
|
||||
await counter(newMessage);
|
||||
},
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
// Dependencies
|
||||
import { Client } from "discord.js";
|
||||
import logger from "@logger";
|
||||
|
||||
// Helpers
|
||||
import updatePresence from "@helpers/updatePresence";
|
||||
import deployCommands from "@handlers/deployCommands";
|
||||
import devMode from "@handlers/devMode";
|
||||
|
||||
export default {
|
||||
once: true,
|
||||
async execute(client: Client) {
|
||||
logger.info("Ready!");
|
||||
|
||||
await updatePresence(client);
|
||||
await devMode(client);
|
||||
await deployCommands(client);
|
||||
|
||||
client.guilds?.cache.forEach((guild) => {
|
||||
logger.silly(
|
||||
`${client.user?.tag} (${client.user?.id}) is in guild: ${guild.name} (${guild.id}) with member count of ${guild.memberCount}`
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,36 +0,0 @@
|
|||
import fs from "fs"; // fs
|
||||
import { Collection } from "discord.js"; // discord.js
|
||||
import { Client } from "@root/types/common/discord";
|
||||
import logger from "@logger";
|
||||
|
||||
export default async (client: Client) => {
|
||||
client.commands = new Collection();
|
||||
|
||||
fs.readdir("./src/plugins", async (error, plugins) => {
|
||||
if (error) {
|
||||
return logger.error(`Error reading plugins: ${error}`);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
plugins.map(async (pluginName, index) => {
|
||||
const plugin = await import(`../plugins/${pluginName}`);
|
||||
|
||||
await client.commands.set(
|
||||
plugin.default.builder.name,
|
||||
plugin.default,
|
||||
plugin.default.metadata
|
||||
);
|
||||
|
||||
logger.verbose(
|
||||
`Loaded plugin ${index + 1}/${plugins.length}: ${pluginName}`
|
||||
);
|
||||
})
|
||||
)
|
||||
.then(async () => {
|
||||
logger.info(`Started all ${plugins.length} plugins.`);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(`${err}`);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,53 +0,0 @@
|
|||
// Dependencies
|
||||
import { token, clientId } from "@config/discord";
|
||||
import { devMode, guildId } from "@config/other";
|
||||
|
||||
import logger from "../logger";
|
||||
import { Client } from "@root/types/common/discord";
|
||||
import { REST } from "@discordjs/rest";
|
||||
import { Routes } from "discord-api-types/v9";
|
||||
|
||||
export default async (client: Client) => {
|
||||
const pluginList = [] as string[];
|
||||
|
||||
await Promise.all(
|
||||
client.commands.map(async (pluginData: any) => {
|
||||
pluginList.push(pluginData.builder.toJSON());
|
||||
logger.verbose(
|
||||
`Plugin is ready for deployment: ${pluginData.builder.name}`
|
||||
);
|
||||
})
|
||||
)
|
||||
.then(async () => {
|
||||
logger.info("All plugins are ready to be deployed.");
|
||||
})
|
||||
.catch(async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
|
||||
const rest = new REST({ version: "9" }).setToken(token);
|
||||
|
||||
await rest
|
||||
.put(Routes.applicationCommands(clientId), {
|
||||
body: pluginList,
|
||||
})
|
||||
.then(async () => {
|
||||
logger.info(`Successfully deployed plugins to Discord's API`);
|
||||
})
|
||||
.catch(async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
|
||||
if (devMode) {
|
||||
await rest
|
||||
.put(Routes.applicationGuildCommands(clientId, guildId), {
|
||||
body: pluginList,
|
||||
})
|
||||
.then(async () =>
|
||||
logger.info(`Successfully deployed guild plugins to Discord's API`)
|
||||
)
|
||||
.catch(async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
}
|
||||
};
|
58
src/handlers/deployCommands/index.ts
Normal file
58
src/handlers/deployCommands/index.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { token, clientId } from "../../config/discord";
|
||||
import { devMode, guildId } from "../../config/other";
|
||||
|
||||
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";
|
||||
|
||||
export default async (client: Client) => {
|
||||
const commandList: Array<RESTPostAPIApplicationCommandsJSONBody> = [];
|
||||
|
||||
if (!client.commands) {
|
||||
throw new Error("client.commands is not defined");
|
||||
}
|
||||
|
||||
logger.info("Gathering command list");
|
||||
|
||||
await Promise.all(
|
||||
client.commands.map(async (commandData: ICommand) => {
|
||||
commandList.push(commandData.builder.toJSON());
|
||||
|
||||
logger.verbose(`${commandData.builder.name} pushed to list`);
|
||||
})
|
||||
)
|
||||
.then(async () => {
|
||||
logger.info(`Finished gathering command list.`);
|
||||
})
|
||||
.catch(async (error) => {
|
||||
throw new Error(`Could not gather command list: ${error}`);
|
||||
});
|
||||
|
||||
const rest = new REST({ version: "9" }).setToken(token);
|
||||
|
||||
await rest
|
||||
.put(Routes.applicationCommands(clientId), {
|
||||
body: commandList,
|
||||
})
|
||||
.then(async () => {
|
||||
logger.info(`Finished updating command list.`);
|
||||
})
|
||||
.catch(async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
|
||||
if (devMode) {
|
||||
await rest
|
||||
.put(Routes.applicationGuildCommands(clientId, guildId), {
|
||||
body: commandList,
|
||||
})
|
||||
.then(async () => logger.info(`Finished updating guild command list.`))
|
||||
.catch(async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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,12 +1,13 @@
|
|||
import crypto from "crypto";
|
||||
|
||||
import { secretKey, algorithm } from "@config/encryption";
|
||||
import { secretKey, algorithm } from "../../config/encryption";
|
||||
|
||||
import { IEncryptionData } from "../../interfaces/EncryptionData";
|
||||
|
||||
const iv = crypto.randomBytes(16);
|
||||
|
||||
const encrypt = (text: any): { iv: any; content: any } => {
|
||||
const encrypt = (text: crypto.BinaryLike): IEncryptionData => {
|
||||
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
|
||||
|
||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
||||
|
||||
return {
|
||||
|
@ -15,7 +16,7 @@ const encrypt = (text: any): { iv: any; content: any } => {
|
|||
};
|
||||
};
|
||||
|
||||
const decrypt = (hash: any) => {
|
||||
const decrypt = (hash: IEncryptionData) => {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
algorithm,
|
||||
secretKey,
|
|
@ -1,37 +0,0 @@
|
|||
import fs from "fs"; // fs
|
||||
import { Client } from "discord.js"; // discord.js
|
||||
import logger from "@logger";
|
||||
|
||||
export default async (client: Client) => {
|
||||
fs.readdir("./src/events", async (error, events) => {
|
||||
if (error) {
|
||||
return logger.error(`Error reading plugins: ${error}`);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
events.map(async (eventName, index) => {
|
||||
const event = await import(`../events/${eventName}`);
|
||||
|
||||
logger.verbose(
|
||||
`Loaded event ${index + 1}/${events.length}: ${eventName}`
|
||||
);
|
||||
|
||||
if (event.once) {
|
||||
return client.once(eventName, async (...args) =>
|
||||
event.default.execute(...args)
|
||||
);
|
||||
}
|
||||
|
||||
return client.on(eventName, async (...args) =>
|
||||
event.default.execute(...args)
|
||||
);
|
||||
})
|
||||
)
|
||||
.then(async () => {
|
||||
logger.info(`Started all ${events.length} events.`);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
logger.error(`${err}`);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
// Dependencies
|
||||
import { Client } from "discord.js";
|
||||
import schedule from "node-schedule";
|
||||
|
||||
import logger from "@logger";
|
||||
|
||||
// Jobs
|
||||
import shopRoles from "@jobs/shopRoles";
|
||||
|
||||
export default async (client: Client) => {
|
||||
const expression = "*/5 * * * *";
|
||||
|
||||
schedule.scheduleJob(expression, async () => {
|
||||
logger.info("Running jobs.");
|
||||
|
||||
await shopRoles(client)
|
||||
.then(() => {
|
||||
logger.info("Shop roles job finished.");
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(`Shop roles job failed: ${err}`);
|
||||
});
|
||||
});
|
||||
};
|
4
src/helpers/addSeconds/index.ts
Normal file
4
src/helpers/addSeconds/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export default async (seconds: number, date: Date) => {
|
||||
date.setSeconds(date.getSeconds() + seconds);
|
||||
return date;
|
||||
};
|
3
src/helpers/capitalizeFirstLetter/index.ts
Normal file
3
src/helpers/capitalizeFirstLetter/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default (text: string): string => {
|
||||
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||
};
|
157
src/helpers/cooldown/index.ts
Normal file
157
src/helpers/cooldown/index.ts
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Dependencies
|
||||
import { CommandInteraction, ButtonInteraction, Message } from "discord.js";
|
||||
|
||||
import logger from "../../logger";
|
||||
|
||||
import getEmbedConfig from "../../helpers/getEmbedConfig";
|
||||
import timeoutSchema from "../../models/timeout";
|
||||
import addSeconds from "../../helpers/addSeconds";
|
||||
|
||||
export const command = async (i: CommandInteraction, cooldown: number) => {
|
||||
const { guild, user, commandId } = i;
|
||||
|
||||
// Check if user has a timeout
|
||||
const hasTimeout = await timeoutSchema.findOne({
|
||||
guildId: guild?.id || "0",
|
||||
userId: user.id,
|
||||
cooldown: cooldown,
|
||||
timeoutId: commandId,
|
||||
});
|
||||
|
||||
// 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: commandId,
|
||||
});
|
||||
};
|
||||
|
||||
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");
|
||||
|
||||
// Check if user has a timeout
|
||||
const hasTimeout = await timeoutSchema.findOne({
|
||||
guildId: guild?.id || "0",
|
||||
userId: member.id,
|
||||
cooldown: cooldown,
|
||||
timeoutId: id,
|
||||
});
|
||||
|
||||
// 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(
|
||||
`User: ${userId} on timeout-id: ${id} with cooldown: ${cooldown} secs with remaining: ${diff} secs.`
|
||||
);
|
||||
}
|
||||
|
||||
// Delete timeout
|
||||
await timeoutSchema
|
||||
.deleteOne({
|
||||
guildId,
|
||||
userId: member.id,
|
||||
timeoutId: id,
|
||||
cooldown,
|
||||
})
|
||||
.then(async () => {
|
||||
logger.debug(
|
||||
`Timeout document ${timeoutId} has been deleted from user ${userId}.`
|
||||
);
|
||||
});
|
||||
}
|
||||
// Create timeout
|
||||
await timeoutSchema.create({
|
||||
guildId: guild?.id || "0",
|
||||
userId: member.id,
|
||||
cooldown: cooldown,
|
||||
timeoutId: id,
|
||||
});
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
|
||||
export default async (interaction: CommandInteraction, ephemeral: boolean) => {
|
||||
if (interaction.guild == null) return;
|
||||
|
||||
await interaction.deferReply({
|
||||
ephemeral,
|
||||
});
|
||||
|
||||
const { waitColor, footerText, footerIcon } = await getEmbedConfig(
|
||||
interaction.guild
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setFooter({
|
||||
text: footerText,
|
||||
iconURL: footerIcon,
|
||||
})
|
||||
.setTimestamp(new Date())
|
||||
.setTitle("Processing your request")
|
||||
.setColor(waitColor)
|
||||
.setDescription("Please wait..."),
|
||||
],
|
||||
});
|
||||
};
|
27
src/helpers/deferReply/index.ts
Normal file
27
src/helpers/deferReply/index.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Interaction, MessageEmbed } from "discord.js";
|
||||
import getEmbedConfig from "../../helpers/getEmbedConfig";
|
||||
|
||||
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 embedConfig = await getEmbedConfig(interaction.guild);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setFooter({
|
||||
text: embedConfig.footerText,
|
||||
iconURL: embedConfig.footerIcon,
|
||||
})
|
||||
.setTimestamp(new Date())
|
||||
.setTitle("Processing your request")
|
||||
.setColor(embedConfig.waitColor)
|
||||
.setDescription("Please wait..."),
|
||||
],
|
||||
});
|
||||
};
|
|
@ -1,11 +1,11 @@
|
|||
import guildSchema from "@schemas/guild";
|
||||
import userSchema from "@schemas/user";
|
||||
import apiSchema from "@schemas/api";
|
||||
import counterSchema from "@schemas/counter";
|
||||
import shopRoleSchema from "@schemas/shopRole";
|
||||
import timeoutSchema from "@schemas/timeout";
|
||||
import guildSchema from "../../models/guild";
|
||||
import userSchema from "../../models/user";
|
||||
import apiSchema from "../../models/api";
|
||||
import counterSchema from "../../models/counter";
|
||||
import shopRoleSchema from "../../models/shopRole";
|
||||
import timeoutSchema from "../../models/timeout";
|
||||
|
||||
import logger from "@logger";
|
||||
import logger from "../../logger";
|
||||
|
||||
import { Guild } from "discord.js";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import userSchema from "@schemas/user";
|
||||
import userSchema from "../../models/user";
|
||||
|
||||
import logger from "@logger";
|
||||
import logger from "../../logger";
|
||||
|
||||
import { Guild, User } from "discord.js";
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { footerText, footerIcon } from "@config/embed";
|
||||
import { footerText, footerIcon } from "../../config/embed";
|
||||
import { MessageEmbed } from "discord.js";
|
||||
|
||||
export default new MessageEmbed()
|
|
@ -2,10 +2,10 @@
|
|||
import { Guild } from "discord.js";
|
||||
|
||||
// Models
|
||||
import guildSchema from "@schemas/guild";
|
||||
import guildSchema from "../../models/guild";
|
||||
|
||||
// Handlers
|
||||
import logger from "@logger";
|
||||
import logger from "../../logger";
|
||||
|
||||
// Function
|
||||
export default async (guild: Guild) => {
|
|
@ -2,10 +2,10 @@
|
|||
import { Guild, User } from "discord.js";
|
||||
|
||||
// Models
|
||||
import userSchema from "@schemas/user";
|
||||
import userSchema from "../../models/user";
|
||||
|
||||
// Handlers
|
||||
import logger from "@logger";
|
||||
import logger from "../../logger";
|
||||
|
||||
// Function
|
||||
export default async (user: User, guild: Guild) => {
|
|
@ -1,12 +0,0 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
|
||||
export default async (interaction: CommandInteraction, currentCommand: any) => {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
const subcommandGroup = interaction.options.getSubcommandGroup(false);
|
||||
|
||||
if (!subcommandGroup) {
|
||||
return currentCommand.modules[subcommand].metadata;
|
||||
}
|
||||
|
||||
return currentCommand.modules[subcommandGroup].modules[subcommand].metadata;
|
||||
};
|
14
src/helpers/getCommandMetadata/index.ts
Normal file
14
src/helpers/getCommandMetadata/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
import { ICommand } from "../../interfaces/Command";
|
||||
|
||||
export default async (
|
||||
interaction: CommandInteraction,
|
||||
currentCommand: ICommand
|
||||
) => {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
const subcommandGroup = interaction.options.getSubcommandGroup(false);
|
||||
|
||||
return subcommandGroup
|
||||
? currentCommand.moduleData[subcommandGroup].moduleData[subcommand].metadata
|
||||
: currentCommand.moduleData[subcommand].metadata;
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
import guildSchema from "@schemas/guild";
|
||||
|
||||
import { ColorResolvable, Guild } from "discord.js";
|
||||
|
||||
export default async (guild: Guild) => {
|
||||
const guildConfig = await guildSchema.findOne({ guildId: guild.id });
|
||||
|
||||
if (guildConfig == null)
|
||||
return {
|
||||
successColor: "#22bb33" as ColorResolvable,
|
||||
waitColor: "#f0ad4e" as ColorResolvable,
|
||||
errorColor: "#bb2124" as ColorResolvable,
|
||||
footerIcon: "https://github.com/ZynerOrg.png",
|
||||
footerText: "https://github.com/ZynerOrg/xyter",
|
||||
};
|
||||
|
||||
return guildConfig.embeds;
|
||||
};
|
18
src/helpers/getEmbedConfig/index.ts
Normal file
18
src/helpers/getEmbedConfig/index.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import guildSchema from "../../models/guild";
|
||||
import * as embedConfig from "../../config/embed";
|
||||
|
||||
import { Guild } from "discord.js";
|
||||
|
||||
export default async (guild?: Guild | null) => {
|
||||
if (!guild) {
|
||||
return { ...embedConfig };
|
||||
}
|
||||
|
||||
const guildConfig = await guildSchema.findOne({ guildId: guild.id });
|
||||
if (!guildConfig) {
|
||||
return {
|
||||
...embedConfig,
|
||||
};
|
||||
}
|
||||
return guildConfig.embeds;
|
||||
};
|
8
src/helpers/listDir/index.ts
Normal file
8
src/helpers/listDir/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import fs from "fs";
|
||||
const fsPromises = fs.promises;
|
||||
|
||||
export default async (path: string) => {
|
||||
return fsPromises.readdir(path).catch(async (err) => {
|
||||
throw new Error(`Could not list directory: ${path}`, err);
|
||||
});
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import logger from "@root/logger";
|
||||
import logger from "../../logger";
|
||||
|
||||
export default (count: number, noun: string, suffix?: string): string => {
|
||||
const result = `${count} ${noun}${count !== 1 ? suffix || "s" : ""}`;
|
|
@ -1,43 +0,0 @@
|
|||
import sleep from "@helpers/sleep";
|
||||
import logger from "@logger";
|
||||
import Chance from "chance";
|
||||
|
||||
export default async function saveUser(data: any, data2: any) {
|
||||
process.nextTick(
|
||||
async () => {
|
||||
// Chance module
|
||||
const chance = new Chance();
|
||||
|
||||
await sleep(
|
||||
chance.integer({
|
||||
min: 0,
|
||||
max: 1,
|
||||
}) *
|
||||
10 +
|
||||
1 * 100
|
||||
); // 100 - 1000 random Number generator
|
||||
data.save((_: any) =>
|
||||
_
|
||||
? logger?.error(
|
||||
`ERROR Occurred while saving data (saveUser) \n${"=".repeat(
|
||||
50
|
||||
)}\n${`${_}\n${"=".repeat(50)}`}`
|
||||
)
|
||||
: logger?.silly(`Saved user: ${data.id} (saveUser)`)
|
||||
);
|
||||
if (data2) {
|
||||
data2.save((_: any) =>
|
||||
_
|
||||
? logger?.error(
|
||||
`ERROR Occurred while saving data (saveUser) \n${"=".repeat(
|
||||
50
|
||||
)}\n${`${_}\n${"=".repeat(50)}`}`
|
||||
)
|
||||
: logger?.silly(`Saved user: ${data2.id} (saveUser)`)
|
||||
);
|
||||
}
|
||||
},
|
||||
data,
|
||||
data2
|
||||
);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import logger from "@logger";
|
||||
|
||||
export default function sleep(milliseconds: any) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, milliseconds);
|
||||
logger?.silly(`Sleeping for ${milliseconds} milliseconds`);
|
||||
});
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Dependencies
|
||||
import { Client } from "discord.js";
|
||||
import logger from "@logger";
|
||||
|
||||
// Function
|
||||
export default async (client: Client) => {
|
||||
const status = `${client?.guilds?.cache?.size} guilds.`;
|
||||
|
||||
client?.user?.setPresence({
|
||||
activities: [{ type: "WATCHING", name: status }],
|
||||
status: "online",
|
||||
});
|
||||
logger?.debug(`Updated client presence to: ${status}`);
|
||||
};
|
20
src/helpers/updatePresence/index.ts
Normal file
20
src/helpers/updatePresence/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Dependencies
|
||||
import { Client } from "discord.js";
|
||||
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",
|
||||
});
|
||||
|
||||
logger.info(`Client's presence is set to "${status}"`);
|
||||
};
|
19
src/index.ts
19
src/index.ts
|
@ -1,13 +1,10 @@
|
|||
import "tsconfig-paths/register"; // Allows using tsconfig.json paths during runtime
|
||||
|
||||
import { token, intents } from "@config/discord";
|
||||
import { token, intents } from "./config/discord";
|
||||
|
||||
import { Client } from "discord.js"; // discord.js
|
||||
|
||||
import database from "@database";
|
||||
import schedules from "@handlers/schedules";
|
||||
import events from "@handlers/events";
|
||||
import commands from "@handlers/commands";
|
||||
import * as managers from "./managers";
|
||||
|
||||
// Main process that starts all other sub processes
|
||||
const main = async () => {
|
||||
|
@ -16,17 +13,7 @@ const main = async () => {
|
|||
intents,
|
||||
});
|
||||
|
||||
// Start database manager
|
||||
await database();
|
||||
|
||||
// Start schedule manager
|
||||
await schedules(client);
|
||||
|
||||
// Start command handler
|
||||
await commands(client);
|
||||
|
||||
// Start event handler
|
||||
await events(client);
|
||||
await managers.start(client);
|
||||
|
||||
// Authorize with Discord's API
|
||||
await client.login(token);
|
||||
|
|
7
src/interfaces/Command.ts
Normal file
7
src/interfaces/Command.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
|
||||
export interface ICommand {
|
||||
builder: SlashCommandBuilder;
|
||||
moduleData: any;
|
||||
execute: Promise<void>;
|
||||
}
|
4
src/interfaces/EncryptionData.ts
Normal file
4
src/interfaces/EncryptionData.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export interface IEncryptionData {
|
||||
iv: string;
|
||||
content: string;
|
||||
}
|
6
src/interfaces/Event.ts
Normal file
6
src/interfaces/Event.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { IEventOptions } from "./EventOptions";
|
||||
|
||||
export interface IEvent {
|
||||
options: IEventOptions;
|
||||
execute: (...args: Promise<void>[]) => Promise<void>;
|
||||
}
|
3
src/interfaces/EventOptions.ts
Normal file
3
src/interfaces/EventOptions.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export interface IEventOptions {
|
||||
type: "on" | "once";
|
||||
}
|
8
src/interfaces/Job.ts
Normal file
8
src/interfaces/Job.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Client } from "discord.js";
|
||||
|
||||
export interface IJob {
|
||||
options: {
|
||||
schedule: string;
|
||||
};
|
||||
execute: (client: Client) => Promise<void>;
|
||||
}
|
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,134 +0,0 @@
|
|||
// Dependencies
|
||||
import { Client } from "discord.js";
|
||||
|
||||
import logger from "@logger";
|
||||
|
||||
// Schemas
|
||||
import userSchema from "@schemas/user";
|
||||
import shopRoleSchema from "@schemas/shopRole";
|
||||
import guildSchema from "@schemas/guild";
|
||||
|
||||
export default 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
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
35
src/jobs/timeouts/index.ts
Normal file
35
src/jobs/timeouts/index.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import logger from "../../logger";
|
||||
|
||||
import timeoutSchema from "../../models/timeout";
|
||||
|
||||
import addSeconds from "../../helpers/addSeconds";
|
||||
|
||||
export const options = {
|
||||
schedule: "*/30 * * * *", // https://crontab.guru/
|
||||
};
|
||||
|
||||
export const execute = async () => {
|
||||
const timeouts = await timeoutSchema.find();
|
||||
await Promise.all(
|
||||
timeouts.map(async (timeout) => {
|
||||
const { guildId, userId, timeoutId, cooldown, createdAt } = timeout;
|
||||
|
||||
const overDue = (await addSeconds(cooldown, createdAt)) < new Date();
|
||||
|
||||
if (overDue) {
|
||||
timeoutSchema
|
||||
.deleteOne({
|
||||
guildId,
|
||||
userId,
|
||||
timeoutId,
|
||||
cooldown,
|
||||
})
|
||||
.then(async () => {
|
||||
logger.debug(
|
||||
`Timeout document ${timeoutId} has been deleted from user ${userId}.`
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,10 +1,13 @@
|
|||
import winston from "winston";
|
||||
import "winston-daily-rotate-file";
|
||||
|
||||
const { combine, timestamp, printf, colorize, align, json } = winston.format;
|
||||
import { logLevel } from "../config/other";
|
||||
|
||||
const { combine, timestamp, printf, errors, colorize, align, json } =
|
||||
winston.format;
|
||||
|
||||
export default winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || "silly",
|
||||
level: logLevel || "info",
|
||||
transports: [
|
||||
new winston.transports.DailyRotateFile({
|
||||
filename: "logs/combined-%DATE%.log",
|
||||
|
@ -14,6 +17,7 @@ export default winston.createLogger({
|
|||
}),
|
||||
new winston.transports.Console({
|
||||
format: combine(
|
||||
errors({ stack: true, trace: true }), // <-- use errors format
|
||||
colorize({ all: true }),
|
||||
timestamp({
|
||||
format: "YYYY-MM-DD HH:MM:ss",
|
||||
|
|
35
src/managers/command/index.ts
Normal file
35
src/managers/command/index.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { Collection, Client } from "discord.js";
|
||||
import listDir from "../../helpers/listDir";
|
||||
import logger from "../../logger";
|
||||
|
||||
import { ICommand } from "../../interfaces/Command";
|
||||
|
||||
export const register = async (client: Client) => {
|
||||
client.commands = new Collection();
|
||||
|
||||
const commandNames = await listDir("plugins/commands");
|
||||
|
||||
if (!commandNames) throw new Error("Could not list commands");
|
||||
|
||||
logger.info(`Loading ${commandNames.length} commands`);
|
||||
|
||||
await Promise.all(
|
||||
commandNames.map(async (commandName) => {
|
||||
const command: ICommand = await import(
|
||||
`../../plugins/commands/${commandName}`
|
||||
).catch(async (e) => {
|
||||
throw new Error(`Could not load command: ${commandName}`, e);
|
||||
});
|
||||
|
||||
client.commands.set(command.builder.name, command);
|
||||
|
||||
logger.verbose(`${command.builder.name} loaded`);
|
||||
})
|
||||
)
|
||||
.then(async () => {
|
||||
logger.info(`Finished loading commands.`);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
throw new Error(`Could not load commands: ${err}`);
|
||||
});
|
||||
};
|
27
src/managers/database/index.ts
Normal file
27
src/managers/database/index.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 3rd party dependencies
|
||||
import mongoose from "mongoose";
|
||||
|
||||
// Dependencies
|
||||
import logger from "../../logger";
|
||||
|
||||
// Configuration
|
||||
import { url } from "../../config/database";
|
||||
|
||||
export const start = async () => {
|
||||
await mongoose
|
||||
.connect(url)
|
||||
.then(async (connection) => {
|
||||
logger.info(`Connected to database: ${connection.connection.name}`);
|
||||
})
|
||||
.catch(async (e) => {
|
||||
logger.error("Could not connect to database", e);
|
||||
});
|
||||
|
||||
mongoose.connection.on("error", async (error) => {
|
||||
logger.error(`${error}`);
|
||||
});
|
||||
|
||||
mongoose.connection.on("warn", async (warning) => {
|
||||
logger.warn(warning);
|
||||
});
|
||||
};
|
29
src/managers/event/index.ts
Normal file
29
src/managers/event/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* eslint-disable no-loops/no-loops */
|
||||
import { Client } from "discord.js";
|
||||
import listDir from "../../helpers/listDir";
|
||||
import { IEvent } from "../../interfaces/Event";
|
||||
import logger from "../../logger";
|
||||
|
||||
export const register = async (client: Client) => {
|
||||
const eventNames = await listDir("plugins/events");
|
||||
if (!eventNames) return;
|
||||
|
||||
for await (const eventName of eventNames) {
|
||||
const event: IEvent = await import(`../../plugins/events/${eventName}`);
|
||||
const eventExecutor = async (...args: Promise<void>[]) =>
|
||||
event.execute(...args).catch(async (err) => {
|
||||
logger.error(`${err}`);
|
||||
});
|
||||
if (!event.options?.type) return;
|
||||
|
||||
switch (event.options.type) {
|
||||
case "once":
|
||||
client.once(eventName, eventExecutor);
|
||||
break;
|
||||
|
||||
case "on":
|
||||
client.on(eventName, eventExecutor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
13
src/managers/index.ts
Normal file
13
src/managers/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Client } from "discord.js";
|
||||
|
||||
import * as database from "./database";
|
||||
import * as schedule from "./schedule";
|
||||
import * as event from "./event";
|
||||
import * as command from "./command";
|
||||
|
||||
export const start = async (client: Client) => {
|
||||
await database.start();
|
||||
await schedule.start(client);
|
||||
await command.register(client);
|
||||
await event.register(client);
|
||||
};
|
29
src/managers/schedule/index.ts
Normal file
29
src/managers/schedule/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import logger from "../../logger";
|
||||
import { Client } from "discord.js";
|
||||
|
||||
import { IJob } from "../../interfaces/Job";
|
||||
|
||||
import listDir from "../../helpers/listDir";
|
||||
|
||||
import schedule from "node-schedule";
|
||||
|
||||
export const start = async (client: Client) => {
|
||||
logger.info("Starting schedule manager...");
|
||||
|
||||
const jobNames = await listDir("jobs");
|
||||
if (!jobNames) return logger.info("No jobs found");
|
||||
|
||||
await Promise.all(
|
||||
jobNames.map(async (jobName) => {
|
||||
const job: IJob = await import(`../../jobs/${jobName}`);
|
||||
|
||||
schedule.scheduleJob(job.options.schedule, async () => {
|
||||
logger.info(`Executed job ${jobName}!`);
|
||||
await job.execute(client);
|
||||
});
|
||||
})
|
||||
).then(async () => {
|
||||
const list = schedule.scheduledJobs;
|
||||
logger.silly(list);
|
||||
});
|
||||
};
|
|
@ -1,10 +1,11 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import { model, Schema } from "mongoose";
|
||||
import { IEncryptionData } from "../interfaces/EncryptionData";
|
||||
|
||||
export interface IApi {
|
||||
guildId: Snowflake;
|
||||
url: string;
|
||||
token: { iv: string; content: string };
|
||||
url: IEncryptionData;
|
||||
token: IEncryptionData;
|
||||
}
|
||||
|
||||
const apiSchema = new Schema<IApi>(
|
||||
|
@ -16,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: {
|
||||
|
@ -28,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",
|
||||
},
|
||||
},
|
||||
},
|
|
@ -4,7 +4,10 @@ import { Schema, model } from "mongoose";
|
|||
export interface ITimeout {
|
||||
userId: Snowflake;
|
||||
guildId: Snowflake;
|
||||
cooldown: number;
|
||||
timeoutId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const timeoutSchema = new Schema<ITimeout>(
|
||||
|
@ -21,6 +24,12 @@ const timeoutSchema = new Schema<ITimeout>(
|
|||
unique: false,
|
||||
index: true,
|
||||
},
|
||||
cooldown: {
|
||||
type: Number,
|
||||
required: true,
|
||||
unique: false,
|
||||
index: true,
|
||||
},
|
||||
timeoutId: { type: String },
|
||||
},
|
||||
{ timestamps: true }
|
8
src/plugins/buttons/primary/index.ts
Normal file
8
src/plugins/buttons/primary/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
import logger from "../../../logger";
|
||||
|
||||
export const metadata = { guildOnly: false, ephemeral: false };
|
||||
|
||||
export const execute = async (interaction: CommandInteraction) => {
|
||||
logger.debug("primary button clicked!");
|
||||
};
|
19
src/plugins/commands/counters/index.ts
Normal file
19
src/plugins/commands/counters/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
import logger from "../../../logger";
|
||||
|
||||
import modules from "../../commands/counters/modules";
|
||||
|
||||
export const builder = new SlashCommandBuilder()
|
||||
.setName("counters")
|
||||
.setDescription("View guild counters")
|
||||
|
||||
.addSubcommand(modules.view.builder);
|
||||
|
||||
export const moduleData = modules;
|
||||
|
||||
export const execute = async (interaction: CommandInteraction) => {
|
||||
if (interaction.options.getSubcommand() === "view") {
|
||||
await modules.view.execute(interaction);
|
||||
}
|
||||
};
|
3
src/plugins/commands/counters/modules/index.ts
Normal file
3
src/plugins/commands/counters/modules/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import view from "./view";
|
||||
|
||||
export default { view };
|
|
@ -1,10 +1,10 @@
|
|||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
import { ChannelType } from "discord-api-types/v10";
|
||||
|
||||
import counterSchema from "@schemas/counter";
|
||||
import counterSchema from "../../../../../models/counter";
|
||||
|
||||
export default {
|
||||
metadata: { guildOnly: true, ephemeral: false },
|
||||
|
@ -25,7 +25,6 @@ export default {
|
|||
},
|
||||
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
if (interaction.guild == null) return;
|
||||
const { errorColor, successColor, footerText, footerIcon } =
|
||||
await getEmbedConfig(interaction.guild);
|
||||
const { options, guild } = interaction;
|
37
src/plugins/commands/credits/index.ts
Normal file
37
src/plugins/commands/credits/index.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
import { CommandInteraction } from "discord.js";
|
||||
import logger from "../../../logger";
|
||||
|
||||
import modules from "./modules";
|
||||
|
||||
export const builder = new SlashCommandBuilder()
|
||||
.setName("credits")
|
||||
.setDescription("Manage your credits.")
|
||||
|
||||
.addSubcommand(modules.balance.builder)
|
||||
.addSubcommand(modules.gift.builder)
|
||||
.addSubcommand(modules.top.builder)
|
||||
.addSubcommand(modules.work.builder);
|
||||
|
||||
export const moduleData = modules;
|
||||
|
||||
export const execute = async (interaction: CommandInteraction) => {
|
||||
const { options } = interaction;
|
||||
|
||||
switch (options.getSubcommand()) {
|
||||
case "balance":
|
||||
await modules.balance.execute(interaction);
|
||||
break;
|
||||
case "gift":
|
||||
await modules.gift.execute(interaction);
|
||||
break;
|
||||
case "top":
|
||||
await modules.top.execute(interaction);
|
||||
break;
|
||||
case "work":
|
||||
await modules.work.execute(interaction);
|
||||
break;
|
||||
default:
|
||||
logger.silly(`Unknown subcommand ${options.getSubcommand()}`);
|
||||
}
|
||||
};
|
|
@ -1,10 +1,10 @@
|
|||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
import logger from "@logger";
|
||||
import logger from "../../../../../logger";
|
||||
|
||||
import fetchUser from "@helpers/fetchUser";
|
||||
import fetchUser from "../../../../../helpers/fetchUser";
|
||||
|
||||
export default {
|
||||
metadata: { guildOnly: true, ephemeral: true },
|
||||
|
@ -19,7 +19,6 @@ export default {
|
|||
);
|
||||
},
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
if (interaction.guild == null) return;
|
||||
const { errorColor, successColor, footerText, footerIcon } =
|
||||
await getEmbedConfig(interaction.guild);
|
||||
const { options, user, guild } = interaction;
|
|
@ -2,16 +2,15 @@
|
|||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
|
||||
// Configurations
|
||||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
// Handlers
|
||||
import logger from "@logger";
|
||||
import logger from "../../../../../logger";
|
||||
|
||||
// Helpers
|
||||
import saveUser from "@helpers/saveUser";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
// Models
|
||||
import fetchUser from "@helpers/fetchUser";
|
||||
import fetchUser from "../../../../../helpers/fetchUser";
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
|
||||
// Function
|
||||
|
@ -39,7 +38,6 @@ export default {
|
|||
);
|
||||
},
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
if (interaction.guild == null) return;
|
||||
const { errorColor, successColor, footerText, footerIcon } =
|
||||
await getEmbedConfig(interaction.guild);
|
||||
const { options, user, guild, client } = interaction;
|
||||
|
@ -184,41 +182,58 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
const session = await mongoose.startSession();
|
||||
|
||||
session.startTransaction();
|
||||
|
||||
try {
|
||||
// Withdraw amount from fromUserDB
|
||||
fromUserDB.credits -= optionAmount;
|
||||
|
||||
// Deposit amount to toUserDB
|
||||
toUserDB.credits += optionAmount;
|
||||
|
||||
// Save users
|
||||
await saveUser(fromUserDB, toUserDB).then(async () => {
|
||||
// Get DM user object
|
||||
const dmUser = client.users.cache.get(optionUser.id);
|
||||
await fromUserDB.save();
|
||||
|
||||
if (dmUser == null) return;
|
||||
await toUserDB.save();
|
||||
|
||||
// Send DM to user
|
||||
await dmUser
|
||||
.send({
|
||||
await session.commitTransaction();
|
||||
} catch (error) {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
logger.error(`${error}`);
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
.setDescription(
|
||||
`${
|
||||
user.tag
|
||||
} has gifted you ${optionAmount} credits with reason: ${
|
||||
"An error occurred while trying to gift credits. Please try again."
|
||||
)
|
||||
.setColor(errorColor),
|
||||
],
|
||||
});
|
||||
} finally {
|
||||
// ending the session
|
||||
session.endSession();
|
||||
}
|
||||
|
||||
// Get DM user object
|
||||
const dmUser = client.users.cache.get(optionUser.id);
|
||||
|
||||
if (!dmUser) throw new Error("User not found");
|
||||
|
||||
// Send DM to user
|
||||
await dmUser.send({
|
||||
embeds: [
|
||||
embed
|
||||
.setDescription(
|
||||
`${user.tag} has gifted you ${optionAmount} credits with reason: ${
|
||||
optionReason || "unspecified"
|
||||
}`
|
||||
)
|
||||
.setColor(successColor),
|
||||
],
|
||||
})
|
||||
.catch(async (error) =>
|
||||
logger.error(`[Gift] Error sending DM to user: ${error}`)
|
||||
);
|
||||
|
||||
logger.silly(
|
||||
`[Gift] Successfully gifted ${optionAmount} credits to ${optionUser.tag}`
|
||||
);
|
||||
});
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
|
@ -231,6 +246,5 @@ export default {
|
|||
.setColor(successColor),
|
||||
],
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
6
src/plugins/commands/credits/modules/index.ts
Normal file
6
src/plugins/commands/credits/modules/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import balance from "./balance";
|
||||
import gift from "./gift";
|
||||
import top from "./top";
|
||||
import work from "./work";
|
||||
|
||||
export default { balance, gift, top, work };
|
|
@ -1,10 +1,10 @@
|
|||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
import logger from "@logger";
|
||||
import logger from "../../../../../logger";
|
||||
|
||||
import userSchema, { IUser } from "@schemas/user";
|
||||
import userSchema, { IUser } from "../../../../../models/user";
|
||||
|
||||
export default {
|
||||
metadata: { guildOnly: true, ephemeral: false },
|
||||
|
@ -13,7 +13,6 @@ export default {
|
|||
return command.setName("top").setDescription(`View the top users`);
|
||||
},
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
if (interaction.guild == null) return;
|
||||
const { errorColor, successColor, footerText, footerIcon } =
|
||||
await getEmbedConfig(interaction.guild);
|
||||
const { guild } = interaction;
|
||||
|
@ -55,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")}
|
||||
`
|
|
@ -4,17 +4,17 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
|||
import Chance from "chance";
|
||||
|
||||
// Configurations
|
||||
import getEmbedConfig from "@helpers/getEmbedConfig";
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
// Handlers
|
||||
import logger from "@logger";
|
||||
import logger from "../../../../../logger";
|
||||
|
||||
// Models
|
||||
import timeoutSchema from "@schemas/timeout";
|
||||
import * as cooldown from "../../../../../helpers/cooldown";
|
||||
|
||||
// Helpers
|
||||
import fetchUser from "@helpers/fetchUser";
|
||||
import fetchGuild from "@helpers/fetchGuild";
|
||||
import fetchUser from "../../../../../helpers/fetchUser";
|
||||
import fetchGuild from "../../../../../helpers/fetchGuild";
|
||||
|
||||
export default {
|
||||
metadata: { guildOnly: true, ephemeral: true },
|
||||
|
@ -23,9 +23,9 @@ export default {
|
|||
return command.setName("work").setDescription(`Work to earn credits`);
|
||||
},
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
if (interaction.guild == null) return;
|
||||
const { errorColor, successColor, footerText, footerIcon } =
|
||||
await getEmbedConfig(interaction.guild); // Destructure member
|
||||
const { successColor, footerText, footerIcon } = await getEmbedConfig(
|
||||
interaction.guild
|
||||
); // Destructure member
|
||||
const { guild, user } = interaction;
|
||||
|
||||
const embed = new MessageEmbed()
|
||||
|
@ -39,33 +39,13 @@ export default {
|
|||
// Chance module
|
||||
const chance = new Chance();
|
||||
|
||||
// Check if user has a timeout
|
||||
const isTimeout = await timeoutSchema?.findOne({
|
||||
guildId: guild?.id,
|
||||
userId: user?.id,
|
||||
timeoutId: "2022-03-15-19-16",
|
||||
});
|
||||
|
||||
if (guild === null) {
|
||||
return logger?.silly(`Guild is null`);
|
||||
}
|
||||
|
||||
const guildDB = await fetchGuild(guild);
|
||||
|
||||
// If user is not on timeout
|
||||
if (isTimeout) {
|
||||
logger?.silly(`User ${user?.id} is on timeout`);
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
.setDescription(
|
||||
`You are on timeout, please wait ${guildDB?.credits.workTimeout} seconds.`
|
||||
)
|
||||
.setColor(errorColor),
|
||||
],
|
||||
});
|
||||
}
|
||||
await cooldown.command(interaction, guildDB?.credits?.workTimeout);
|
||||
|
||||
const creditsEarned = chance.integer({
|
||||
min: 0,
|
||||
|
@ -93,23 +73,5 @@ export default {
|
|||
],
|
||||
});
|
||||
});
|
||||
|
||||
// Create a timeout for the user
|
||||
await timeoutSchema?.create({
|
||||
guildId: guild?.id,
|
||||
userId: user?.id,
|
||||
timeoutId: "2022-03-15-19-16",
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
logger?.silly(`Removing timeout for user ${user?.id}`);
|
||||
|
||||
// When timeout is out, remove it from the database
|
||||
await timeoutSchema?.deleteOne({
|
||||
guildId: guild?.id,
|
||||
userId: user?.id,
|
||||
timeoutId: "2022-03-15-19-16",
|
||||
});
|
||||
}, guildDB?.credits?.workTimeout);
|
||||
},
|
||||
};
|
22
src/plugins/commands/dns/index.ts
Normal file
22
src/plugins/commands/dns/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("dns")
|
||||
.setDescription("DNS commands.")
|
||||
|
||||
.addSubcommand(modules.lookup.builder);
|
||||
|
||||
export const execute = async (interaction: CommandInteraction) => {
|
||||
switch (interaction.options.getSubcommand()) {
|
||||
case "lookup":
|
||||
return modules.lookup.execute(interaction);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown subcommand: ${interaction.options.getSubcommand()}`
|
||||
);
|
||||
}
|
||||
};
|
3
src/plugins/commands/dns/modules/index.ts
Normal file
3
src/plugins/commands/dns/modules/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import lookup from "./lookup";
|
||||
|
||||
export default { lookup };
|
131
src/plugins/commands/dns/modules/lookup/index.ts
Normal file
131
src/plugins/commands/dns/modules/lookup/index.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import axios from "axios";
|
||||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
|
||||
export default {
|
||||
metadata: { guildOnly: false, ephemeral: false },
|
||||
|
||||
builder: (command: SlashCommandSubcommandBuilder) => {
|
||||
return command
|
||||
.setName("lookup")
|
||||
.setDescription(
|
||||
"Lookup a domain or ip. (Request sent over HTTP, proceed with caution!)"
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("query")
|
||||
.setDescription("The query you want to look up.")
|
||||
.setRequired(true)
|
||||
);
|
||||
},
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
const { errorColor, successColor, footerText, footerIcon } =
|
||||
await getEmbedConfig(interaction.guild);
|
||||
const embedTitle = "[:hammer:] Utility (Lookup)";
|
||||
|
||||
const { options } = interaction;
|
||||
const query = options.getString("query");
|
||||
|
||||
await axios
|
||||
.get(`http://ip-api.com/json/${query}`)
|
||||
.then(async (response) => {
|
||||
if (response.data.status !== "success") {
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setTitle(embedTitle)
|
||||
.setFooter({
|
||||
text: footerText,
|
||||
iconURL: footerIcon,
|
||||
})
|
||||
.setTimestamp(new Date())
|
||||
.setColor(errorColor)
|
||||
.setFooter({ text: footerText, iconURL: footerIcon })
|
||||
.setDescription(
|
||||
`${response?.data?.message}: ${response?.data?.query}`
|
||||
),
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setTitle(embedTitle)
|
||||
.setFooter({
|
||||
text: footerText,
|
||||
iconURL: footerIcon,
|
||||
})
|
||||
.setTimestamp(new Date())
|
||||
.setColor(successColor)
|
||||
.setFields([
|
||||
{
|
||||
name: ":classical_building: AS",
|
||||
value: `${response.data.as || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":classical_building: ISP",
|
||||
value: `${response.data.isp || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":classical_building: Organization",
|
||||
value: `${response.data.org || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":compass: Latitude",
|
||||
value: `${response.data.lat || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":compass: Longitude",
|
||||
value: `${response.data.lon || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":clock4: Timezone",
|
||||
value: `${response.data.timezone || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":globe_with_meridians: Country",
|
||||
value: `${response.data.country || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":globe_with_meridians: Region",
|
||||
value: `${response.data.regionName || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":globe_with_meridians: City",
|
||||
value: `${response.data.city || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":globe_with_meridians: Country Code",
|
||||
value: `${response.data.countryCode || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":globe_with_meridians: Region Code",
|
||||
value: `${response.data.region || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: ":globe_with_meridians: ZIP",
|
||||
value: `${response.data.zip || "Unknown"}`,
|
||||
inline: true,
|
||||
},
|
||||
]),
|
||||
],
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
23
src/plugins/commands/fun/index.ts
Normal file
23
src/plugins/commands/fun/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
import { CommandInteraction } from "discord.js";
|
||||
import logger from "../../../logger";
|
||||
|
||||
import modules from "../../commands/fun/modules";
|
||||
|
||||
export const builder = new SlashCommandBuilder()
|
||||
.setName("fun")
|
||||
.setDescription("Fun commands.")
|
||||
|
||||
.addSubcommand(modules.meme.builder);
|
||||
|
||||
export const moduleData = modules;
|
||||
|
||||
export const execute = async (interaction: CommandInteraction) => {
|
||||
const { options } = interaction;
|
||||
|
||||
if (options.getSubcommand() === "meme") {
|
||||
await modules.meme.execute(interaction);
|
||||
} else {
|
||||
logger.silly(`Unknown subcommand ${options.getSubcommand()}`);
|
||||
}
|
||||
};
|
5
src/plugins/commands/fun/modules/index.ts
Normal file
5
src/plugins/commands/fun/modules/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import meme from "./meme";
|
||||
|
||||
export default {
|
||||
meme,
|
||||
};
|
59
src/plugins/commands/fun/modules/meme/index.ts
Normal file
59
src/plugins/commands/fun/modules/meme/index.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
import axios from "axios";
|
||||
import { CommandInteraction, MessageEmbed } from "discord.js";
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
|
||||
export default {
|
||||
metadata: { guildOnly: false, ephemeral: false, cooldown: 15 },
|
||||
|
||||
builder: (command: SlashCommandSubcommandBuilder) => {
|
||||
return command.setName("meme").setDescription("Get a meme from r/memes)");
|
||||
},
|
||||
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
const { guild } = interaction;
|
||||
|
||||
const embedConfig = await getEmbedConfig(guild);
|
||||
|
||||
await axios
|
||||
.get("https://www.reddit.com/r/memes/random/.json")
|
||||
.then(async (res) => {
|
||||
const response = res.data[0].data.children;
|
||||
const content = response[0].data;
|
||||
|
||||
const embed = new MessageEmbed()
|
||||
.setAuthor({
|
||||
name: content.title,
|
||||
iconURL:
|
||||
"https://www.redditinc.com/assets/images/site/reddit-logo.png",
|
||||
url: `https://reddit.com${content.permalink}`,
|
||||
})
|
||||
.setTitle("[:sweat_smile:] Meme")
|
||||
.addFields([
|
||||
{
|
||||
name: "Author",
|
||||
value: `[${content.author}](https://reddit.com/user/${content.author})`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Votes",
|
||||
value: `${content.ups}/${content.downs}`,
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
.setTimestamp(new Date())
|
||||
.setImage(content.url)
|
||||
.setFooter({
|
||||
text: embedConfig.footerText,
|
||||
iconURL: embedConfig.footerIcon,
|
||||
})
|
||||
.setColor(embedConfig.successColor);
|
||||
|
||||
return interaction.editReply({ embeds: [embed] });
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new Error(error.message);
|
||||
});
|
||||
},
|
||||
};
|
29
src/plugins/commands/manage/index.ts
Normal file
29
src/plugins/commands/manage/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
//Dependencies
|
||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
import { CommandInteraction } from "discord.js";
|
||||
|
||||
// Groups
|
||||
import modules from "../../commands/manage/modules";
|
||||
import logger from "../../../logger";
|
||||
|
||||
export const moduleData = modules;
|
||||
|
||||
// Function
|
||||
export const builder = new SlashCommandBuilder()
|
||||
.setName("manage")
|
||||
.setDescription("Manage the bot.")
|
||||
.addSubcommandGroup(modules.counters.builder)
|
||||
.addSubcommandGroup(modules.credits.builder);
|
||||
|
||||
export const execute = async (interaction: CommandInteraction) => {
|
||||
// Destructure
|
||||
const { options } = interaction;
|
||||
|
||||
if (options?.getSubcommandGroup() === "credits") {
|
||||
return modules.credits.execute(interaction);
|
||||
}
|
||||
|
||||
if (options?.getSubcommandGroup() === "counters") {
|
||||
return modules.counters.execute(interaction);
|
||||
}
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue