Merge branch 'dev' into snyk-upgrade-8f2d20d6968ccc2e7c705d12f966b527

This commit is contained in:
Axel Olausson Holtenäs 2022-05-19 10:03:29 +02:00 committed by GitHub
commit ae3cd011e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
95 changed files with 1583 additions and 1358 deletions

View file

@ -1,5 +1,16 @@
version = 1
[[analyzers]]
name = "javascript"
enabled = true
[[analyzers]]
name = "shell"
enabled = true
enabled = true
[[transformers]]
name = "standardjs"
enabled = true
[[transformers]]
name = "prettier"

View file

@ -1,52 +1,52 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"abrahamwilliam007.es7-javascript-class-snippets",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense",
"dbaeumer.vscode-eslint",
"donjayamanne.githistory",
"eamodio.gitlens",
"esbenp.prettier-vscode",
"github.github-vscode-theme",
"irongeek.vscode-env",
"xabikos.javascriptsnippets",
"wix.vscode-import-cost",
"vscode-icons-team.vscode-icons",
"visualstudioexptteam.vscodeintellicode",
"teledemic.branch-warnings",
"tabnine.tabnine-vscode",
"streetsidesoftware.code-spell-checker",
"seatonjiang.gitmoji-vscode",
"sburg.vscode-javascript-booster",
"kisstkondoros.vscode-codemetrics",
"mgmcdermott.vscode-language-babel",
"mhutchie.git-graph",
"mikestead.dotenv",
"mongodb.mongodb-vscode",
"ms-vscode-remote.remote-wsl-recommender",
"ms-vscode.js-debug",
"ms-vscode.js-debug-companion",
"ms-vscode.references-view",
"ms-vscode.vscode-js-profile-table",
"pflannery.vscode-versionlens",
"adpyke.codesnap",
"anan.devdocstab",
"axosoft.gitkraken-glo",
"gruntfuggly.todo-tree",
"kiteco.kite",
"lkytal.pomodoro",
"wayou.vscode-todo-highlight",
"johnpapa.vscode-peacock",
"stepsize.stepsize",
"nicoespeon.abracadabra",
"sonarsource.sonarlint-vscode",
"nicoespeon.hocus-pocus",
"aaron-bond.better-comments",
"oouo-diogo-perdigao.docthis"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"abrahamwilliam007.es7-javascript-class-snippets",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense",
"dbaeumer.vscode-eslint",
"donjayamanne.githistory",
"eamodio.gitlens",
"esbenp.prettier-vscode",
"github.github-vscode-theme",
"irongeek.vscode-env",
"xabikos.javascriptsnippets",
"wix.vscode-import-cost",
"vscode-icons-team.vscode-icons",
"visualstudioexptteam.vscodeintellicode",
"teledemic.branch-warnings",
"tabnine.tabnine-vscode",
"streetsidesoftware.code-spell-checker",
"seatonjiang.gitmoji-vscode",
"sburg.vscode-javascript-booster",
"kisstkondoros.vscode-codemetrics",
"mgmcdermott.vscode-language-babel",
"mhutchie.git-graph",
"mikestead.dotenv",
"mongodb.mongodb-vscode",
"ms-vscode-remote.remote-wsl-recommender",
"ms-vscode.js-debug",
"ms-vscode.js-debug-companion",
"ms-vscode.references-view",
"ms-vscode.vscode-js-profile-table",
"pflannery.vscode-versionlens",
"adpyke.codesnap",
"anan.devdocstab",
"axosoft.gitkraken-glo",
"gruntfuggly.todo-tree",
"kiteco.kite",
"lkytal.pomodoro",
"wayou.vscode-todo-highlight",
"johnpapa.vscode-peacock",
"stepsize.stepsize",
"nicoespeon.abracadabra",
"sonarsource.sonarlint-vscode",
"nicoespeon.hocus-pocus",
"aaron-bond.better-comments",
"oouo-diogo-perdigao.docthis"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}

52
.vscode/settings.json vendored
View file

@ -1,26 +1,26 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"],
"editor.cursorBlinking": "phase",
"editor.cursorSmoothCaretAnimation": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.fontFamily": "Cascadia Code",
"editor.fontLigatures": true,
"editor.formatOnSave": true,
"editor.guides.bracketPairs": "active",
"editor.minimap.maxColumn": 200,
"editor.minimap.renderCharacters": false,
"editor.minimap.showSlider": "always",
"editor.tabSize": 2,
"editor.wordWrapColumn": 100,
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"cSpell.customDictionaries": {
"custom-dictionary-workspace": {
"name": "custom-dictionary-workspace",
"path": "${workspaceFolder:xyter}/.cspell/custom-dictionary-workspace.txt",
"addWords": true,
"scope": "workspace"
}
}
}
{
"editor.bracketPairColorization.enabled": true,
"editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"],
"editor.cursorBlinking": "phase",
"editor.cursorSmoothCaretAnimation": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.fontFamily": "Cascadia Code",
"editor.fontLigatures": true,
"editor.formatOnSave": true,
"editor.guides.bracketPairs": "active",
"editor.minimap.maxColumn": 200,
"editor.minimap.renderCharacters": false,
"editor.minimap.showSlider": "always",
"editor.tabSize": 2,
"editor.wordWrapColumn": 100,
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"cSpell.customDictionaries": {
"custom-dictionary-workspace": {
"name": "custom-dictionary-workspace",
"path": "${workspaceFolder:xyter}/.cspell/custom-dictionary-workspace.txt",
"addWords": true,
"scope": "workspace"
}
}
}

View file

@ -7,54 +7,50 @@ This document entails the privacy policy and agreement that you accept when addi
This privacy policy applies to **Xyter**#7721 (949998000401436673)
### Terminology
* **Server Manager** - Anyone who has the ability to add a bot to a server or configure the bot for the server. This is usually an administrator or moderator
* **Server Member** - Anyone who is a member of server to which one of the bots has been added
* **Service User** - Anyone who authorizes an application (logs in) for a scope that provides additional information
- **Server Manager** - Anyone who has the ability to add a bot to a server or configure the bot for the server. This is usually an administrator or moderator
- **Server Member** - Anyone who is a member of server to which one of the bots has been added
- **Service User** - Anyone who authorizes an application (logs in) for a scope that provides additional information
### Data Collected By Command
The following items may be collected and stored when intentionally provided by a user (usually by means of a command). This data will not be collected automatically. When providing data in this way, you forego any rights to the content of the data provided.
* Server configurations (/settings guild)
* Data and content for automated tasks (/shop roles)
* Locale (/settings user language)
* Reputation (/reputation give)
The following items may be collected and stored when intentionally provided by a user (usually by means of a command). This data will not be collected automatically. When providing data in this way, you forego any rights to the content of the data provided.
- Server configurations (/settings guild)
- Data and content for automated tasks (/shop roles)
- Locale (/settings user language)
- Reputation (/reputation give)
### Data Collected When Enabled
These items will be automatically collected if a bot is configured to perform certain actions by a server manager. These features are always opt-in, and thus this data will not be collected unless the corresponding feature is enabled.
* API Token & URL (/settings guild pterodactyl)
* Counters (/admin counter)
- API Token & URL (/settings guild pterodactyl)
- Counters (/admin counter)
### Data Collected Automatically
This data may be collected automatically. This data is used to provide statistics or history data. For any bots that collect this data, it is necessary for features of said bot.
* Username ID (Unique identifier)
* Guild ID (Unique identifier)
* Any data needed for standard operation of Discord bots, such as server permissions
* Credits (/profile view)
* Points (/profile view)
* Levels (/profile view)
* Timestamps (Audit logs and all your data that we store)
- Username ID (Unique identifier)
- Guild ID (Unique identifier)
- Any data needed for standard operation of Discord bots, such as server permissions
- Credits (/profile view)
- Points (/profile view)
- Levels (/profile view)
- Timestamps (Audit logs and all your data that we store)
### Data Storage
All stored data is kept on protected servers. While storage methods vary, most data is kept within password-protected databases such as [MongoDB Atlas](https://atlas.mongodb.com/). Please keep in mind that even with these protections, no data can ever be 100% secure. All efforts are taken to keep your data secure and private, but its absolute security cannot be guaranteed.
### Feedback
Feedback on Xyter bot and service is appreciated. When you submit comments, suggestions, bug reports, and any other forms of feedback, you forego any rights to the content, title, or intent of the provided feedback. Additionally, the feedback may be utilized in any way.
### Agreement
By adding Xyter bot to your server or using this bot or service in any way, you are consenting to the policies outlined in this document. In addition, you (the server manager) are agreeing to inform your members of the [Developer Terms of Service](https://discordapp.com/developers/docs/legal) and the contents of this document. If you, the server manager, do not agree to this document, you may remove the bot(s) from the server. If you, the server member, do not agree to this document, you may leave the server that contains the bot(s). If you, the service user, do not agree to this document, you may revoke authorization of the application(s) in your 'Authorized Apps' menu.
Changes to This Privacy Policy

View file

@ -2,10 +2,10 @@
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| Version | Supported |
| ---------- | ------------------ |
| 2022.4.x | :white_check_mark: |
| < 2022.4.x | :x: |
| < 2022.4.x | :x: |
## Reporting a Vulnerability

View file

@ -1,3 +0,0 @@
files:
- source: /lang/en/*.json
translation: /lang/%two_letters_code%/%original_file_name%

View file

@ -1,3 +0,0 @@
{
"test":"test"
}

View file

@ -5,7 +5,7 @@
"main": "src/index.ts",
"scripts": {
"test": "jest",
"start": "nodemon | pino-pretty -i pid,hostname -t yyyy-mm-dd HH:MM:s",
"start": "node .",
"prettier-format": "prettier \"src/**/*.ts\" --write",
"lint": "eslint ./src --ext .ts",
"prepare": "husky install"
@ -27,37 +27,41 @@
"email": "vermium@zyner.org"
},
"dependencies": {
"@discordjs/builders": "^0.12.0",
"@crowdin/ota-client": "^0.7.0",
"@discordjs/builders": "^0.13.0",
"@discordjs/rest": "^0.4.0",
"axios": "^0.26.0",
"axios": "^0.27.2",
"chance": "^1.1.8",
"common": "^0.2.5",
"crypto": "^1.0.1",
"discord-api-types": "^0.32.0",
"discord-api-types": "^0.33.0",
"discord.js": "^13.6.0",
"i18next": "^21.6.13",
"i18next-async-backend": "^2.0.0",
"i18next-http-backend": "^1.4.0",
"i18next-resources-to-backend": "^1.0.0",
"mongoose": "^6.2.3",
"node-schedule": "^2.1.0",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.1",
"tsconfig-paths": "^4.0.0",
"typescript": "^4.6.3",
"uuid": "^8.3.2",
"winston-daily-rotate-file": "^4.6.1"
},
"devDependencies": {
"@types/chance": "^1.1.3",
"@types/node-schedule": "^1.3.2",
"@types/node-schedule": "2.1.0",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"eslint": "8.13.0",
"eslint": "8.15.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": "^7.0.0",
"jest": "^27.5.1",
"husky": "8.0.1",
"jest": "28.0.0",
"lint-staged": "^12.3.7",
"prettier": "^2.6.0"
},

View file

@ -8,13 +8,15 @@ import logger from "@logger";
import { url } from "@config/database";
export default async () => {
mongoose.connect(url).then(async (connection) => {
logger?.info(`Connected to database: ${connection.connection.name}`);
await mongoose.connect(url).then(async (connection) => {
logger.info(`Connected to database: ${connection.connection.name}`);
});
mongoose.connection.on("error", (error) => {
logger?.error(error);
mongoose.connection.on("error", async (error) => {
logger.error(error);
});
mongoose.connection.on("warn", (warning) => {
logger?.warn(warning);
mongoose.connection.on("warn", async (warning) => {
logger.warn(warning);
});
};

View file

@ -7,7 +7,6 @@ import fetchGuild from "@helpers/fetchGuild";
import logger from "@logger";
export default {
name: "guildCreate",
async execute(guild: Guild) {
const { client } = guild;
@ -15,5 +14,7 @@ export default {
await fetchGuild(guild);
await updatePresence(client);
logger.silly(`guildCreate: ${guild}`);
},
};

View file

@ -7,7 +7,6 @@ import dropGuild from "@helpers/dropGuild";
import logger from "@logger";
export default {
name: "guildDelete",
async execute(guild: Guild) {
const { client } = guild;
@ -15,5 +14,7 @@ export default {
await dropGuild(guild);
await updatePresence(client);
logger.silly(`guildDelete: ${guild}`);
},
};

View file

@ -20,24 +20,35 @@ export default {
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,
}),
],
});
(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})`
);
});
},
};

View file

@ -9,7 +9,6 @@ import joinMessage from "../guildMemberAdd/joinMessage";
import audits from "../guildMemberAdd/audits";
export default {
name: "guildMemberAdd",
async execute(member: GuildMember) {
const { client, user, guild } = member;

View file

@ -20,21 +20,32 @@ export default {
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,
}),
],
});
(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})`
);
});
},
};

View file

@ -9,7 +9,6 @@ import leaveMessage from "./leaveMessage";
import audits from "./audits";
export default {
name: "guildMemberRemove",
async execute(member: GuildMember) {
const { client, user, guild } = member;

View file

@ -26,23 +26,34 @@ export default {
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setDescription(
`
(channel as TextChannel)
.send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setDescription(
`
**Interaction created by** ${interaction.user.username} **in** ${interaction.channel}
`
)
.setThumbnail(interaction.user.displayAvatarURL())
.addFields([{ name: "Event", value: "interactionCreate" }])
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
});
)
.setThumbnail(interaction.user.displayAvatarURL())
.addFields([{ name: "Event", value: "interactionCreate" }])
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
})
.then(async () => {
logger.info(
`Audit log sent for event interactionCreate in guild ${interaction?.guild?.name} (${interaction?.guild?.id})`
);
})
.catch(async () => {
logger.error(
`Audit log failed to send for event interactionCreate in guild ${interaction?.guild?.name} (${interaction?.guild?.id})`
);
});
},
};

View file

@ -4,16 +4,83 @@ import { CommandInteraction, MessageEmbed } from "discord.js";
import logger from "@logger";
import { errorColor, footerText, footerIcon } from "@config/embed";
import i18next from "i18next";
import deferReply from "@root/helpers/deferReply";
import getCommandMeta from "@root/helpers/getCommandMeta";
export default async (interaction: CommandInteraction) => {
if (!interaction.isCommand()) return;
const { client, guild, commandName, user } = interaction;
const { client, guild, commandName, user, memberPermissions } = interaction;
const currentCommand = client.commands.get(commandName);
if (!currentCommand) return;
await interaction.deferReply({ ephemeral: true });
if (currentCommand == null) {
logger.silly(`Command ${commandName} not found`);
}
const meta = await getCommandMeta(interaction, currentCommand);
await deferReply(interaction, meta.ephemeral || false);
if (
meta.permissions &&
meta.guildOnly &&
!memberPermissions?.has(meta.permissions)
) {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:toolbox:] Manage")
.setDescription(`You do not have the permission to manage the bot.`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
if (meta.guildOnly) {
if (!guild) {
logger.verbose(`Guild is null`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setDescription(
i18next.t("guildOnly", {
lng: interaction.locale,
ns: "errors",
})
)
.setColor(errorColor)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
}
if (meta.dmOnly) {
if (guild) {
logger.verbose(`Guild exist`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setDescription(
i18next.t("dmOnly", {
lng: interaction.locale,
ns: "errors",
})
)
.setColor(errorColor)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
}
await currentCommand
.execute(interaction)
@ -23,7 +90,7 @@ export default async (interaction: CommandInteraction) => {
);
})
.catch(async (error: any) => {
logger?.error(error);
logger?.error(`${error}`);
return interaction.editReply({
embeds: [

View file

@ -7,7 +7,6 @@ import logger from "@logger";
import audits from "./audits";
export default {
name: "interactionCreate",
async execute(interaction: CommandInteraction) {
const { guild, id } = interaction;

View file

@ -2,7 +2,6 @@ import { Message } from "discord.js";
import modules from "@events/messageCreate/modules";
export default {
name: "messageCreate",
async execute(message: Message) {
await modules.credits.execute(message);
await modules.points.execute(message);

View file

@ -26,26 +26,37 @@ export default {
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setAuthor({
name: message.author.username,
iconURL: message.author.displayAvatarURL(),
})
.setDescription(
`
(channel as TextChannel)
.send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setAuthor({
name: message.author.username,
iconURL: message.author.displayAvatarURL(),
})
.setDescription(
`
**Message sent by** ${message.author} **deleted in** ${message.channel}
${message.content}
`
)
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
});
)
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
})
.then(async () => {
logger.info(
`Audit log sent for event messageDelete in guild ${message?.guild?.name} (${message?.guild?.id})`
);
})
.catch(async () => {
logger.error(
`Audit log failed to send for event messageDelete in guild ${message?.guild?.name} (${message?.guild?.id})`
);
});
},
};

View file

@ -3,7 +3,6 @@ import audits from "@events/messageDelete/audits";
import counter from "./modules/counter";
export default {
name: "messageDelete",
async execute(message: Message) {
await audits.execute(message);
await counter(message);

View file

@ -29,25 +29,36 @@ export default {
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setAuthor({
name: newMessage.author.username,
iconURL: newMessage.author.displayAvatarURL(),
})
.setDescription(
`
(channel as TextChannel)
.send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setAuthor({
name: newMessage.author.username,
iconURL: newMessage.author.displayAvatarURL(),
})
.setDescription(
`
**Message edited in** ${newMessage.channel} [jump to message](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.id})
`
)
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
});
)
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
})
.then(async () => {
logger.info(
`Audit log sent for event messageUpdate in guild ${newMessage?.guild?.name} (${newMessage?.guild?.id})`
);
})
.catch(async () => {
logger.error(
`Audit log failed to send for event messageUpdate in guild ${newMessage?.guild?.name} (${newMessage?.guild?.id})`
);
});
},
};

View file

@ -8,7 +8,6 @@ import counter from "./modules/counter";
import audits from "./audits";
export default {
name: "messageUpdate",
async execute(oldMessage: Message, newMessage: Message) {
const { author, guild } = newMessage;

View file

@ -8,7 +8,6 @@ import deployCommands from "@handlers/deployCommands";
import devMode from "@handlers/devMode";
export default {
name: "ready",
once: true,
async execute(client: Client) {
logger.info(`${client.user?.tag} (${client.user?.id}) is ready`);

View file

@ -15,19 +15,18 @@ export default async (client: Client) => {
plugins.map(async (pluginName) => {
const plugin = await import(`../plugins/${pluginName}`);
await client?.commands?.set(
plugin?.default?.data?.name,
plugin?.default
await client.commands.set(
plugin.default.data.name,
plugin.default,
plugin.default.meta
);
logger.verbose(`Loaded plugin: ${pluginName}`);
})
)
.then(async () => {
logger.debug("Successfully loaded plugins.");
})
.catch(async (err) => {
logger.error(err);
logger.error(`${err}`);
});
});
};

View file

@ -4,7 +4,7 @@ import { secretKey, algorithm } from "@config/encryption";
const iv = crypto.randomBytes(16);
const encrypt = (text: any) => {
const encrypt = (text: any): { iv: error; content: error } => {
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);

View file

@ -15,12 +15,12 @@ export default async (client: Client) => {
logger.verbose(`Loaded event: ${eventName}`);
if (event.once) {
return client.once(event.default.name, async (...args) =>
return client.once(eventName, async (...args) =>
event.default.execute(...args)
);
}
return client.on(event.default.name, async (...args) =>
return client.on(eventName, async (...args) =>
event.default.execute(...args)
);
})

22
src/helpers/deferReply.ts Normal file
View file

@ -0,0 +1,22 @@
import { CommandInteraction, MessageEmbed } from "discord.js";
import { waitColor, footerText, footerIcon } from "@config/embed";
export default async (interaction: CommandInteraction, ephemeral: boolean) => {
await interaction.deferReply({
ephemeral,
});
await interaction.editReply({
embeds: [
new MessageEmbed()
.setFooter({
text: footerText,
iconURL: footerIcon,
})
.setTimestamp(new Date())
.setTitle("Processing your request")
.setColor(waitColor)
.setDescription("Please wait..."),
],
});
};

View file

@ -0,0 +1,9 @@
import { footerText, footerIcon } from "@config/embed";
import { MessageEmbed } from "discord.js";
export default new MessageEmbed()
.setFooter({
text: footerText,
iconURL: footerIcon,
})
.setTimestamp(new Date());

View file

@ -0,0 +1,12 @@
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].meta;
}
return currentCommand.groups[subcommandGroup].modules[subcommand].meta;
};

View file

@ -1,6 +1,6 @@
import logger from "@root/logger";
export default (count: number, noun: string, suffix?: string) => {
export default (count: number, noun: string, suffix?: string): string => {
const result = `${count} ${noun}${count !== 1 ? suffix || "s" : ""}`;
logger?.verbose(`Pluralized ${count} to ${result}`);
return result;

View file

@ -4,25 +4,62 @@ import { token, intents } from "@config/discord";
import { Client } from "discord.js"; // discord.js
import locale from "@locale";
import database from "@database";
import schedules from "@schedules";
import events from "@handlers/events";
import commands from "@handlers/commands";
async function main() {
// Main process that starts all other sub processes
const main = async () => {
// Initiate client object
const client = new Client({
intents,
});
await locale();
await database();
await schedules(client);
// Start database manager
await database()
.then(async () => {
await logger.silly("Database process started");
})
.catch(async (err) => {
await logger.error(err);
});
await commands(client);
await events(client);
// Start schedule manager
await schedules(client)
.then(async () => {
await logger.silly("Schedules process started");
})
.catch(async (err) => {
await logger.error(err);
});
// Start command handler
await commands(client)
.then(async () => {
await logger.silly("Commands process started");
})
.catch(async (err) => {
await logger.error(err);
});
// Start event handler
await events(client)
.then(async () => {
await logger.silly("Events process started");
})
.catch(async (err) => {
await logger.error(err);
});
// Authorize with Discord's API
await client.login(token);
}
};
main();
await main()
.then(async () => {
await logger.silly("Main process started");
})
.catch(async (err) => {
await logger.error(err);
});

View file

@ -1,205 +0,0 @@
import i18next from "i18next";
import logger from "@logger";
export default async () => {
await i18next
.init({
lng: "en", // if you're using a language detector, do not define the lng option
// debug: true,
fallbackLng: "en",
resources: {
en: {
general: { not_available: "Not Available" },
commands: {
credits: {
general: {
credits_one: "{{count}} credit",
credits_other: "{{count}} credits",
},
addons: {
balance: { embed: { title: "Credits" } },
gift: { embed: { title: "Gift" } },
},
},
reputation: {
addons: {
give: {
version01: {
embed: {
title: ":medal: Reputation",
description:
"You have given reputation within the last day, you can not repute now!",
},
},
version02: {
embed: {
title: ":medal: Reputation",
description:
"You have given {{user}} a {{type}} reputation!",
},
},
version03: {
embed: {
title: ":medal: Reputation",
description: "You can not repute yourself.",
},
},
},
},
},
profile: {
addons: {
view: {
embed: {
title: "Profile",
reputation: "Reputation (Global)",
level: "Level (Guild)",
points: "Points (Guild)",
credits: "Credits (Guild)",
language_code: "Language Code (Global)",
},
},
settings: {
embed: {
title: "Profile",
description: "Following settings is set",
fields: { language: "Language" },
},
},
},
},
},
},
sv: {
general: { not_available: "Otillgänglig" },
commands: {
credits: {
general: {
credits_one: "{{count}} krona",
credits_other: "{{count}} kronor",
},
addons: {
balance: { embed: { title: "Krediter" } },
gift: { embed: { title: "Gåva" } },
},
},
reputation: {
addons: {
give: {
version01: {
embed: {
title: ":medal: Omdöme",
description:
"Du har redan gett omdöme inom den senaste dagen, du kan inte ge ett omdöme just nu!",
},
},
version02: {
embed: {
title: ":medal: Omdöme",
description: "Du har gett {{user}} ett {{type}} omdöme!",
},
},
version03: {
embed: {
title: ":medal: Omdöme",
description: "Du kan inte ge dig själv ett omdöme.",
},
},
},
},
},
profile: {
addons: {
view: {
embed: {
title: "Profil",
reputation: "Omdöme (Globalt)",
level: "Nivå (Server)",
points: "Poäng (Server)",
credits: "Krediter (Server)",
language_code: "Språkkod (Globalt)",
},
},
settings: {
embed: {
title: "Profil",
description: "Följande inställningar är satta",
fields: { language: "Språk" },
},
},
},
},
},
},
de: {
general: { not_available: "Nicht verfügbar" },
commands: {
credits: {
general: {
credits_one: "{{count}} Guthaben",
credits_other: "{{count}} Guthaben",
},
addons: {
balance: { embed: { title: "Guthaben" } },
gift: { embed: { title: "Geschenk" } },
},
},
reputation: {
addons: {
give: {
version01: {
embed: {
title: ":medal: Ruf",
description:
"Du hast dir am letzten Tag einen Ruf verschafft, den du jetzt nicht rühmen kannst!",
},
},
version02: {
embed: {
title: ":medal: Ruf",
description:
"Du hast {{user}} einen {{type}} Ruf gegeben!",
},
},
version03: {
embed: {
title: ":medal: Ruf",
description: "Du kannst dich nicht selbst rühmen.",
},
},
},
},
},
profile: {
addons: {
view: {
embed: {
title: "Profil",
reputation: "Ruf (Weltweit)",
level: "Level (Gilde)",
points: "Punkte (Gilde)",
credits: "Guthaben (Gilde)",
language_code: "Sprachcode (Weltweit)",
},
},
settings: {
embed: {
title: "Profile",
description: "Folgende Einstellungen werden vorgenommen",
fields: { language: "Sprache" },
},
},
},
},
},
},
},
})
.then(async () => {
logger.debug(`i18next initialized`);
})
.catch(async (error) => {
logger.error(`i18next failed to initialize: ${error}`);
});
};

View file

@ -3,24 +3,31 @@ import "winston-daily-rotate-file";
const { combine, timestamp, printf, colorize, align, json } = winston.format;
export default winston.createLogger({
level: process.env.LOG_LEVEL || "silly",
transports: [
new winston.transports.DailyRotateFile({
filename: "logs/combined-%DATE%.log",
datePattern: "YYYY-MM-DD",
maxFiles: "14d",
format: combine(timestamp(), json()),
}),
new winston.transports.Console({
format: combine(
colorize({ all: true }),
timestamp({
format: "YYYY-MM-DD HH:MM:ss",
module.exports = {
// Logger initialized async-hronously
logger: async () => {
return winston.createLogger({
level: process.env.LOG_LEVEL || "silly",
transports: [
new winston.transports.DailyRotateFile({
filename: "logs/combined-%DATE%.log",
datePattern: "YYYY-MM-DD",
maxFiles: "14d",
format: combine(timestamp(), json()),
}),
align(),
printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`)
),
}),
],
});
new winston.transports.Console({
format: combine(
colorize({ all: true }),
timestamp({
format: "YYYY-MM-DD HH:MM:ss",
}),
align(),
printf(
(info) => `[${info.timestamp}] ${info.level}: ${info.message}`
)
),
}),
],
});
},
};

View file

@ -1,17 +1,18 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import logger from "@logger";
import modules from "@root/plugins/counters/modules";
import modules from "@plugins/counters/modules";
export default {
metadata: { author: "Zyner" },
modules,
data: new SlashCommandBuilder()
.setName("counters")
.setDescription("View guild counters")
.addSubcommand(modules.view.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;

View file

@ -1,3 +1,3 @@
import view from "./view";
import view from "@plugins/counters/modules/view";
export default { view };

View file

@ -1,11 +1,3 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { ChannelType } from "discord-api-types/v10";
import counterSchema from "@schemas/counter";
// Configuration
import {
errorColor,
successColor,
@ -13,7 +5,16 @@ import {
footerIcon,
} from "@config/embed";
import { CommandInteraction, MessageEmbed } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { ChannelType } from "discord-api-types/v10";
import counterSchema from "@schemas/counter";
import i18next from "i18next";
export default {
meta: { guildOnly: true, ephemeral: false },
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("view")
@ -25,14 +26,28 @@ export default {
`The channel that contains the counter you want to view`
)
.setRequired(true)
.addChannelType(ChannelType.GuildText as number)
.addChannelTypes(ChannelType.GuildText)
);
},
execute: async (interaction: CommandInteraction) => {
const { options, guild } = interaction;
const { options, guild, locale } = interaction;
const discordChannel = options?.getChannel("channel");
const embed = new MessageEmbed()
.setTitle(
i18next.t("counters:modules:view:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({
text: footerText,
iconURL: footerIcon,
});
const counter = await counterSchema?.findOne({
guildId: guild?.id,
channelId: discordChannel?.id,
@ -41,32 +56,31 @@ export default {
if (counter === null) {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:1234:] Counters (View)")
.setDescription(`No counter found for channel ${discordChannel}!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
embed
.setDescription(
i18next.t("counters:modules:view:error01:description", {
lng: locale,
ns: "plugins",
channel: discordChannel,
})
)
.setColor(errorColor),
],
});
}
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:1234:] Counters (View)")
embed
.setDescription(
`Viewing counter for channel ${discordChannel} with count ${counter.counter}.`
i18next.t("counters:modules:view:success01:description", {
lng: locale,
ns: "plugins",
channel: discordChannel,
amount: counter.counter,
})
)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
.setColor(successColor),
],
});
},

View file

@ -1,44 +1,39 @@
// Dependencies
import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
import logger from "@logger";
// Modules
import modules from "@root/plugins/credits/modules";
import modules from "@plugins/credits/modules";
export default {
metadata: { author: "Zyner" },
modules,
data: new SlashCommandBuilder()
.setName("credits")
.setDescription("Manage your credits.")
.addSubcommand(modules.balance.data)
.addSubcommand(modules.gift.data)
.addSubcommand(modules.top.data)
.addSubcommand(modules.work.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
if (options?.getSubcommand() === "balance") {
logger?.verbose(`Executing balance subcommand`);
return modules.balance.execute(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.verbose(`Unknown subcommand ${options.getSubcommand()}`);
}
if (options?.getSubcommand() === "gift") {
logger?.verbose(`Executing gift subcommand`);
return modules.gift.execute(interaction);
}
if (options?.getSubcommand() === "top") {
logger?.verbose(`Executing top command`);
return modules.top.execute(interaction);
}
if (options?.getSubcommand() === "work") {
logger?.verbose(`Executing work command`);
return modules.work.execute(interaction);
}
logger?.verbose(`Unknown subcommand ${options?.getSubcommand()}`);
},
};

View file

@ -1,10 +1,3 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import logger from "@logger";
// Configurations
import {
errorColor,
successColor,
@ -12,41 +5,53 @@ import {
footerIcon,
} from "@config/embed";
// Helpers
import pluralize from "@helpers/pluralize";
import i18next from "i18next";
import { CommandInteraction, MessageEmbed } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import logger from "@logger";
import fetchUser from "@helpers/fetchUser";
export default {
meta: { guildOnly: true, ephemeral: true },
data: (command: SlashCommandSubcommandBuilder) => {
return (
command
.setName("balance")
.setDescription(`View a user's balance`)
// User
.addUserOption((option) =>
option
.setName("user")
.setDescription(`The user whose balance you want to view`)
)
);
return command
.setName("balance")
.setDescription(`View a user's balance`)
.addUserOption((option) =>
option
.setName("user")
.setDescription(`The user whose balance you want to view`)
);
},
execute: async (interaction: CommandInteraction) => {
const { options, user, guild } = interaction;
const { options, user, guild, locale } = interaction;
const discordUser = options?.getUser("user");
const discordUser = options.getUser("user");
const embed = new MessageEmbed()
.setTitle(
i18next.t("credits:modules:balance:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon });
if (guild === null) {
logger?.verbose(`Guild is null`);
logger.verbose(`Guild is null`);
return interaction?.editReply({
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Balance)")
.setDescription(`You can only use this command in a guild!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("guildOnly", {
lng: locale,
ns: "errors",
})
)
.setColor(errorColor),
],
});
}
@ -54,50 +59,55 @@ export default {
const userObj = await fetchUser(discordUser || user, guild);
if (userObj === null) {
logger?.verbose(`User not found`);
logger.verbose(`User not found`);
return interaction?.editReply({
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Balance)")
.setDescription(`Could not find user ${discordUser || user}`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("userNotFound", {
lng: locale,
ns: "errors",
user: discordUser || user,
})
)
.setColor(errorColor),
],
});
}
if (userObj.credits === null) {
logger?.verbose(`User has no credits`);
logger.verbose(`User has no credits`);
return interaction?.editReply({
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Balance)")
.setDescription(`${discordUser || user} has no credits!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("credits:modules:balance:error01:description", {
lng: locale,
ns: "plugins",
user: discordUser || user,
})
)
.setColor(errorColor),
],
});
}
logger?.verbose(`Found user ${discordUser || user}`);
logger.verbose(`Found user ${discordUser || user}`);
return interaction?.editReply({
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Balance)")
embed
.setDescription(
`${discordUser || user} has ${pluralize(
userObj.credits,
`credit`
)}!`
i18next.t("credits:modules:balance:success01:description", {
lng: locale,
ns: "plugins",
user: discordUser || user,
amount: userObj.credits,
})
)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(successColor),
],
});
},

View file

@ -18,9 +18,12 @@ import saveUser from "@helpers/saveUser";
// Models
import fetchUser from "@helpers/fetchUser";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import i18next from "i18next";
// Function
export default {
meta: { guildOnly: true, ephemeral: true },
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("gift")
@ -42,38 +45,52 @@ export default {
);
},
execute: async (interaction: CommandInteraction) => {
const { options, user, guild, client } = interaction;
const { options, user, guild, client, locale } = interaction;
const optionUser = options?.getUser("user");
const optionAmount = options?.getInteger("amount");
const optionReason = options?.getString("reason");
const optionUser = options.getUser("user");
const optionAmount = options.getInteger("amount");
const optionReason = options.getString("reason");
const embed = new MessageEmbed()
.setTitle(
i18next.t("credits:modules:gift:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon });
if (guild === null) {
logger?.verbose(`Guild is null`);
logger.verbose(`Guild is null`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
.setDescription(`We can not find your guild!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("guildOnly", {
lng: locale,
ns: "errors",
})
)
.setColor(errorColor),
],
});
}
if (optionUser === null) {
logger?.verbose(`User not found`);
logger.verbose(`User not found`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
.setDescription(`We can not find your requested user!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("userNotFound", {
lng: locale,
ns: "errors",
})
)
.setColor(errorColor),
],
});
}
@ -85,119 +102,126 @@ export default {
const toUserDB = await fetchUser(optionUser, guild);
if (fromUserDB === null) {
logger?.verbose(`User not found`);
logger.verbose(`User not found`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
embed
.setDescription(
`We can not find your requested from user in our database!`
i18next.t("userNotFound", {
lng: locale,
ns: "errors",
})
)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(errorColor),
],
});
}
if (toUserDB === null) {
logger?.verbose(`User not found`);
logger.verbose(`User not found`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
embed
.setDescription(
`We can not find your requested to user in our database!`
i18next.t("userNotFound", {
lng: locale,
ns: "errors",
})
)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(errorColor),
],
});
}
// If receiver is same as sender
if (optionUser?.id === user?.id) {
logger?.verbose(`User is same as sender`);
if (optionUser.id === user.id) {
logger.verbose(`User is same as sender`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
.setDescription(`You can not pay yourself!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("credits:modules:gift:error01:description", {
lng: locale,
ns: "plugins",
})
)
.setColor(errorColor),
],
});
}
// If amount is null
if (optionAmount === null) {
logger?.verbose(`Amount is null`);
logger.verbose(`Amount is null`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
.setDescription(`We could not read your requested amount!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("amountNotFound", {
lng: locale,
ns: "errors",
})
)
.setColor(errorColor),
],
});
}
// If amount is zero or below
if (optionAmount <= 0) {
logger?.verbose(`Amount is zero or below`);
logger.verbose(`Amount is zero or below`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
.setDescription(`You can't gift zero or below!`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("credits:modules:gift:error02:description", {
lng: locale,
ns: "plugins",
})
)
.setColor(errorColor),
],
});
}
// If user has below gifting amount
if (fromUserDB?.credits < optionAmount) {
logger?.verbose(`User has below gifting amount`);
if (fromUserDB.credits < optionAmount) {
logger.verbose(`User has below gifting amount`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
embed
.setDescription(
`You have insufficient credits. Your balance is ${fromUserDB?.credits}!`
i18next.t("credits:modules:gift:error03:description", {
lng: locale,
ns: "plugins",
amount: fromUserDB.credits,
})
)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(errorColor),
],
});
}
// If toUserDB has no credits
if (toUserDB === null) {
logger?.verbose(`User has no credits`);
logger.verbose(`User has no credits`);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
embed
.setDescription(
`We can not find your requested to user in our database!`
i18next.t("userNotFound", {
lng: locale,
ns: "errors",
})
)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(errorColor),
],
});
}
@ -209,46 +233,50 @@ export default {
toUserDB.credits += optionAmount;
// Save users
await saveUser(fromUserDB, toUserDB)?.then(async () => {
await saveUser(fromUserDB, toUserDB).then(async () => {
// Get DM user object
const dmUser = client?.users?.cache?.get(optionUser?.id);
const dmUser = client.users.cache.get(optionUser.id);
if (dmUser == null) return;
// Send DM to user
await dmUser
?.send({
.send({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
embed
.setDescription(
`You have received ${optionAmount} credits from ${
user?.tag
} with reason ${
optionReason ? ` with reason: ${optionReason}` : ""
}!`
i18next.t("credits:modules:gift:error03:description", {
lng: locale,
ns: "plugins",
user: user.tag,
amount: optionAmount,
reason: optionReason || "unspecified",
})
)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(successColor),
],
})
.catch(async (error) =>
logger?.error(`[Gift] Error sending DM to user: ${error}`)
logger.error(`[Gift] Error sending DM to user: ${error}`)
);
logger?.verbose(
`[Gift] Successfully gifted ${optionAmount} credits to ${optionUser?.tag}`
logger.verbose(
`[Gift] Successfully gifted ${optionAmount} credits to ${optionUser.tag}`
);
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Gift)")
embed
.setDescription(
`Successfully gifted ${optionAmount} credits to ${optionUser?.tag}!`
i18next.t("credits:modules:gift:success02:description", {
lng: locale,
ns: "plugins",
user: user,
amount: optionAmount,
reason: optionReason || "unspecified",
})
)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(successColor),
],
});
});

View file

@ -1,6 +1,6 @@
import balance from "./balance";
import gift from "./gift";
import top from "./top";
import work from "./work";
import balance from "@plugins/credits/modules/balance";
import gift from "@plugins/credits/modules/gift";
import top from "@plugins/credits/modules/top";
import work from "@plugins/credits/modules/work";
export default { balance, gift, top, work };

View file

@ -1,48 +1,86 @@
// Dependencies
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import {
successColor,
errorColor,
footerText,
footerIcon,
} from "@config/embed";
import i18next from "i18next";
import { CommandInteraction, MessageEmbed } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import logger from "@logger";
import userSchema from "@schemas/user";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
// Helpers
import pluralize from "@helpers/pluralize";
import userSchema, { IUser } from "@schemas/user";
export default {
meta: { guildOnly: true, ephemeral: false },
data: (command: SlashCommandSubcommandBuilder) => {
return command.setName("top").setDescription(`View the top users`);
},
execute: async (interaction: CommandInteraction) => {
// Get all users in the guild
const { locale, guild } = interaction;
const usersDB = await userSchema.find({ guildId: interaction?.guild?.id });
const embed = new MessageEmbed()
.setTitle(
i18next.t("credits:modules:top:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon });
if (guild === null) {
logger.verbose(`Guild is null`);
return interaction.editReply({
embeds: [
embed
.setDescription(
i18next.t("guildOnly", {
lng: locale,
ns: "errors",
})
)
.setColor(errorColor),
],
});
}
const usersDB = await userSchema.find({ guildId: guild.id });
const topTen = usersDB
// Sort them after credits amount (ascending)
.sort((a, b) => (a?.credits > b?.credits ? -1 : 1))
.sort((a, b) => (a.credits > b.credits ? -1 : 1))
// Return the top 10
.slice(0, 10);
// Create entry object
const entry = (x: any, index: number) =>
`${index + 1}. <@${x?.userId}> - ${pluralize(x?.credits, "credit")}`;
const entry = (x: IUser, index: number) =>
i18next.t("credits:modules:top:entry", {
lng: locale,
ns: "plugins",
index: index + 1,
user: x.userId,
amount: x.credits,
});
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Top)")
embed
.setDescription(
`Top 10 users with the most credits.
` ${i18next.t("credits:modules:top:success01:description", {
lng: locale,
ns: "plugins",
})}
${topTen.map(entry).join("\n")}`
${topTen.map(entry).join("\n")}
`
)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(successColor),
],
});
},

View file

@ -4,7 +4,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import Chance from "chance";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
import {
successColor,
errorColor,
footerText,
footerIcon,
} from "@config/embed";
// Handlers
import logger from "@logger";
@ -15,14 +20,30 @@ import timeoutSchema from "@schemas/timeout";
// Helpers
import fetchUser from "@helpers/fetchUser";
import fetchGuild from "@helpers/fetchGuild";
import i18next from "i18next";
export default {
meta: { guildOnly: true, ephemeral: true },
data: (command: SlashCommandSubcommandBuilder) => {
return command.setName("work").setDescription(`Work to earn credits`);
},
execute: async (interaction: CommandInteraction) => {
// Destructure member
const { guild, user } = interaction;
const { guild, user, locale } = interaction;
const embed = new MessageEmbed()
.setTitle(
i18next.t("credits:modules:work:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({
text: footerText,
iconURL: footerIcon,
});
// Chance module
const chance = new Chance();
@ -46,14 +67,15 @@ export default {
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Work)")
embed
.setDescription(
`You can not work while on timeout, please wait ${guildDB?.credits.workTimeout} seconds.`
i18next.t("credits:modules:work:error01:description", {
lng: locale,
ns: "plugins",
time: guildDB?.credits.workTimeout,
})
)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
.setColor(errorColor),
],
});
}
@ -78,12 +100,16 @@ export default {
return interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:dollar:] Credits (Work)")
.setDescription(`You worked and earned ${creditsEarned} credits`)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t("credits:modules:work:success01:description", {
lng: locale,
ns: "plugins",
time: guildDB?.credits.workTimeout,
amount: creditsEarned,
})
)
.setColor(successColor),
],
});
});

27
src/plugins/fun/index.ts Normal file
View file

@ -0,0 +1,27 @@
import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
import logger from "@logger";
import modules from "@plugins/fun/modules";
export default {
modules,
data: new SlashCommandBuilder()
.setName("fun")
.setDescription("Fun commands.")
.addSubcommand(modules.meme.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
switch (options.getSubcommand()) {
case "meme":
await modules.meme.execute(interaction);
break;
default:
logger.verbose(`Unknown subcommand ${options.getSubcommand()}`);
}
},
};

View file

@ -0,0 +1,5 @@
import meme from "@plugins/fun/modules/meme";
export default {
meme,
};

View file

@ -0,0 +1,37 @@
import { successColor, footerText, footerIcon } from "@config/embed";
import axios from "axios";
import { CommandInteraction, MessageEmbed } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import logger from "@logger";
export default {
meta: { guildOnly: false, ephemeral: false },
data: (command: SlashCommandSubcommandBuilder) => {
return command.setName("meme").setDescription("Get a meme from r/memes)");
},
execute: async (interaction: CommandInteraction) => {
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()
.setTitle(content.title)
.setTimestamp(new Date())
.setImage(content.url)
.setFooter({
text: `👍 ${content.ups}︱👎 ${content.downs}\n${footerText}`,
iconURL: footerIcon,
})
.setColor(successColor);
return interaction.editReply({ embeds: [embed] });
})
.catch((error) => {
logger.error(`${error}`);
});
},
};

View file

@ -5,31 +5,33 @@ import { CommandInteraction } from "discord.js";
import logger from "@logger";
// Modules
import moduleCreate from "./modules/create";
import moduleDelete from "./modules/delete";
import modules from "./modules";
// Function
export default {
modules,
data: (group: SlashCommandSubcommandGroupBuilder) => {
return group
.setName("counters")
.setDescription("Manage guild counters.")
.addSubcommand(moduleCreate.data)
.addSubcommand(moduleDelete.data);
.addSubcommand(modules.add.data)
.addSubcommand(modules.remove.data);
},
execute: async (interaction: CommandInteraction) => {
const { options } = interaction;
if (options?.getSubcommand() === "create") {
if (options?.getSubcommand() === "add") {
logger?.verbose(`Executing create subcommand`);
return moduleCreate.execute(interaction);
return modules.add.execute(interaction);
}
if (options?.getSubcommand() === "delete") {
if (options?.getSubcommand() === "remove") {
logger?.verbose(`Executing delete subcommand`);
return moduleDelete.execute(interaction);
return modules.remove.execute(interaction);
}
logger?.verbose(`Unknown subcommand ${options?.getSubcommand()}`);

View file

@ -1,5 +1,5 @@
// Dependencies
import { MessageEmbed, CommandInteraction } from "discord.js";
import { MessageEmbed, CommandInteraction, Permissions } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { ChannelType } from "discord-api-types/v10";
@ -16,19 +16,26 @@ import logger from "@logger";
// Models
import counterSchema from "@schemas/counter";
import i18next from "i18next";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("create")
.setName("add")
.setDescription("Add a counter to your guild.")
.addChannelOption((option) =>
option
.setName("channel")
.setDescription("The channel to send the counter to.")
.setRequired(true)
.addChannelType(ChannelType.GuildText as number)
.addChannelTypes(ChannelType.GuildText)
)
.addStringOption((option) =>
option
@ -43,12 +50,22 @@ export default {
);
},
execute: async (interaction: CommandInteraction) => {
const { options, guild } = interaction;
const { options, guild, locale } = interaction;
const discordChannel = options?.getChannel("channel");
const countingWord = options?.getString("word");
const startValue = options?.getNumber("start");
const embed = new MessageEmbed()
.setTitle(
i18next.t("manage:groups:counters:modules:add:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon });
const counter = await counterSchema?.findOne({
guildId: guild?.id,
channelId: discordChannel?.id,
@ -57,12 +74,18 @@ export default {
if (counter) {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:toolbox:] Manage - Counters (Create)")
.setDescription(`A counter already exists for this channel.`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t(
"manage:groups:counters:modules:add:error01:description",
{
lng: locale,
ns: "plugins",
channel: discordChannel,
}
)
)
.setColor(errorColor),
],
});
}
@ -79,12 +102,18 @@ export default {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:toolbox:] Manage - Counters (Create)")
.setDescription(`Created counter for ${discordChannel}`)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t(
"manage:groups:counters:modules:create:success01:description",
{
lng: locale,
ns: "plugins",
channel: discordChannel,
}
)
)
.setColor(successColor),
],
});
});

View file

@ -0,0 +1,4 @@
import add from "@plugins/manage/groups/counters/modules/add";
import remove from "@plugins/manage/groups/counters/modules/remove";
export default { add, remove };

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import { CommandInteraction, MessageEmbed, Permissions } from "discord.js";
// Configurations
import {
@ -16,26 +16,43 @@ import logger from "@logger";
import counterSchema from "@schemas/counter";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { ChannelType } from "discord-api-types/v10";
import i18next from "i18next";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("delete")
.setName("remove")
.setDescription(`Delete a counter from your guild.`)
.addChannelOption((option) =>
option
.setName("channel")
.setDescription("The channel to delete the counter from.")
.setRequired(true)
.addChannelType(ChannelType.GuildText as number)
.addChannelTypes(ChannelType.GuildText)
);
},
execute: async (interaction: CommandInteraction) => {
const { options, guild } = interaction;
const { options, guild, locale } = interaction;
const discordChannel = options?.getChannel("channel");
const embed = new MessageEmbed()
.setTitle(
i18next.t("manage:groups:counters:modules:remove:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon });
const counter = await counterSchema?.findOne({
guildId: guild?.id,
channelId: discordChannel?.id,
@ -46,12 +63,17 @@ export default {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:toolbox:] Manage - Counters (Delete)")
.setDescription(`The counter for this channel does not exist.`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t(
"manage:groups:counters:modules:remove:error01:description",
{
lng: locale,
ns: "plugins",
}
)
)
.setColor(errorColor),
],
});
}
@ -66,12 +88,17 @@ export default {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:toolbox:] Manage - Counters (Delete)")
.setDescription(`The counter for this channel has been deleted.`)
.setTimestamp(new Date())
.setColor(successColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
embed
.setDescription(
i18next.t(
"manage:groups:counters:modules:remove:success01:description",
{
lng: locale,
ns: "plugins",
}
)
)
.setColor(successColor),
],
});
})

View file

@ -1,53 +1,43 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
import logger from "@logger";
// Modules
import moduleGive from "./modules/give";
import moduleSet from "./modules/set";
import moduleTake from "./modules/take";
import moduleTransfer from "./modules/transfer";
import modules from "./modules";
// Function
export default {
modules,
data: (group: SlashCommandSubcommandGroupBuilder) => {
return group
.setName("credits")
.setDescription("Manage the credits of a user.")
.addSubcommand(moduleGive.data)
.addSubcommand(moduleSet.data)
.addSubcommand(moduleTake.data)
.addSubcommand(moduleTransfer.data);
.addSubcommand(modules.give.data)
.addSubcommand(modules.set.data)
.addSubcommand(modules.take.data)
.addSubcommand(modules.transfer.data);
},
execute: async (interaction: CommandInteraction) => {
const { options } = interaction;
if (options?.getSubcommand() === "give") {
logger?.verbose(`Executing give subcommand`);
switch (options.getSubcommand()) {
case "give":
logger.verbose(`Executing give subcommand`);
return moduleGive.execute(interaction);
return modules.give.execute(interaction);
case "set":
logger.verbose(`Executing set subcommand`);
return modules.set.execute(interaction);
case "take":
logger.verbose(`Executing take subcommand`);
return modules.take.execute(interaction);
case "transfer":
logger.verbose(`Executing transfer subcommand`);
return modules.transfer.execute(interaction);
default:
logger.verbose(`Unknown subcommand ${options.getSubcommand()}`);
}
if (options?.getSubcommand() === "set") {
logger?.verbose(`Executing set subcommand`);
return moduleSet.execute(interaction);
}
if (options?.getSubcommand() === "take") {
logger?.verbose(`Executing take subcommand`);
return moduleTake.execute(interaction);
}
if (options?.getSubcommand() === "transfer") {
logger?.verbose(`Executing transfer subcommand`);
return moduleTransfer.execute(interaction);
}
logger?.verbose(`No subcommand found`);
},
};

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import { CommandInteraction, MessageEmbed, Permissions } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Configurations
@ -21,6 +21,12 @@ import fetchUser from "@helpers/fetchUser";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("give")

View file

@ -0,0 +1,6 @@
import give from "@plugins/manage/groups/credits/modules/give";
import set from "@plugins/manage/groups/credits/modules/set";
import take from "@plugins/manage/groups/credits/modules/take";
import transfer from "@plugins/manage/groups/credits/modules/transfer";
export default { give, set, take, transfer };

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import { CommandInteraction, MessageEmbed, Permissions } from "discord.js";
// Configurations
import {
@ -20,6 +20,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("set")

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import { CommandInteraction, MessageEmbed, Permissions } from "discord.js";
// Configurations
import {
@ -21,6 +21,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("take")

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction, MessageEmbed } from "discord.js";
import { CommandInteraction, MessageEmbed, Permissions } from "discord.js";
// Configurations
import {
@ -21,6 +21,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("transfer")

View file

@ -0,0 +1,4 @@
import counters from "@plugins/manage/groups/counters";
import credits from "@plugins/manage/groups/credits";
export default { counters, credits };

View file

@ -1,52 +1,35 @@
//Dependencies
import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction, Permissions, MessageEmbed } from "discord.js";
// Configurations
import { errorColor, footerText, footerIcon } from "@config/embed";
import { CommandInteraction } from "discord.js";
// Groups
import credits from "./groups/credits";
import counters from "./groups/counters";
import groups from "@plugins/manage/groups";
import logger from "@logger";
// Function
export default {
metadata: { author: "Zyner" },
groups,
data: new SlashCommandBuilder()
.setName("manage")
.setDescription("Manage the bot.")
.addSubcommandGroup(counters.data)
.addSubcommandGroup(credits.data),
.addSubcommandGroup(groups.counters.data)
.addSubcommandGroup(groups.credits.data),
async execute(interaction: CommandInteraction) {
// Destructure
const { memberPermissions, options } = interaction;
// Check permission
if (!memberPermissions?.has(Permissions?.FLAGS?.MANAGE_GUILD)) {
return interaction?.editReply({
embeds: [
new MessageEmbed()
.setTitle("[:toolbox:] Manage")
.setDescription(`You do not have the permission to manage the bot.`)
.setTimestamp(new Date())
.setColor(errorColor)
.setFooter({ text: footerText, iconURL: footerIcon }),
],
});
}
const { options } = interaction;
if (options?.getSubcommandGroup() === "credits") {
logger?.verbose(`Subcommand group is credits`);
return credits.execute(interaction);
return groups.credits.execute(interaction);
}
if (options?.getSubcommandGroup() === "counters") {
logger?.verbose(`Subcommand group is counters`);
return counters.execute(interaction);
return groups.counters.execute(interaction);
}
logger?.verbose(`Subcommand group is not credits or counters`);

View file

@ -3,34 +3,26 @@ import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Modules
import view from "./modules/view";
import modules from "@plugins/profile/modules";
// Handlers
import logger from "@logger";
// Function
export default {
metadata: { author: "Zyner" },
modules,
data: new SlashCommandBuilder()
.setName("profile")
.setDescription("Check a profile.")
.addSubcommand((subcommand) =>
subcommand
.setName("view")
.setDescription("View a profile.")
.addUserOption((option) =>
option
.setName("target")
.setDescription("The profile you wish to view")
)
),
.addSubcommand(modules.view.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
if (options?.getSubcommand() === "view") {
logger?.verbose(`Executing view subcommand`);
return view(interaction);
return modules.view.execute(interaction);
}
logger?.verbose(`No subcommand found`);

View file

@ -0,0 +1,3 @@
import view from "@plugins/profile/modules/view";
export default { view };

View file

@ -8,68 +8,82 @@ import { successColor, footerText, footerIcon } from "@config/embed";
import fetchUser from "@helpers/fetchUser";
import logger from "@logger";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default async (interaction: CommandInteraction) => {
// Destructure
const { client, options, user, guild } = interaction;
export default {
meta: { guildOnly: true, ephemeral: false },
// Target information
const target = options?.getUser("target");
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("view")
.setDescription("View a profile.")
.addUserOption((option) =>
option.setName("target").setDescription("The profile you wish to view")
);
},
// Discord User Information
const discordUser = await client?.users?.fetch(
`${target ? target?.id : user?.id}`
);
execute: async (interaction: CommandInteraction) => {
// Destructure
const { client, options, user, guild } = interaction;
if (guild === null) {
return logger?.verbose(`Guild is null`);
}
// Target information
const target = options?.getUser("target");
// User Information
const userObj = await fetchUser(discordUser, guild);
// Discord User Information
const discordUser = await client?.users?.fetch(
`${target ? target?.id : user?.id}`
);
// Embed object
const embed = {
author: {
name: `${discordUser?.username}#${discordUser?.discriminator}`,
icon_url: discordUser?.displayAvatarURL(),
},
color: successColor,
fields: [
{
name: `:dollar: Credits`,
value: `${userObj?.credits || "Not found"}`,
inline: true,
if (guild === null) {
return logger?.verbose(`Guild is null`);
}
// User Information
const userObj = await fetchUser(discordUser, guild);
// Embed object
const embed = {
author: {
name: `${discordUser?.username}#${discordUser?.discriminator}`,
icon_url: discordUser?.displayAvatarURL(),
},
{
name: `:squeeze_bottle: Level`,
value: `${userObj?.level || "Not found"}`,
inline: true,
color: successColor,
fields: [
{
name: `:dollar: Credits`,
value: `${userObj?.credits || "Not found"}`,
inline: true,
},
{
name: `:squeeze_bottle: Level`,
value: `${userObj?.level || "Not found"}`,
inline: true,
},
{
name: `:squeeze_bottle: Points`,
value: `${userObj?.points || "Not found"}`,
inline: true,
},
{
name: `:loudspeaker: Reputation`,
value: `${userObj?.reputation || "Not found"}`,
inline: true,
},
{
name: `:rainbow_flag: Language`,
value: `${userObj?.language || "Not found"}`,
inline: true,
},
],
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
{
name: `:squeeze_bottle: Points`,
value: `${userObj?.points || "Not found"}`,
inline: true,
},
{
name: `:loudspeaker: Reputation`,
value: `${userObj?.reputation || "Not found"}`,
inline: true,
},
{
name: `:rainbow_flag: Language`,
value: `${userObj?.language || "Not found"}`,
inline: true,
},
],
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
};
};
// Return interaction reply
return interaction?.editReply({ embeds: [embed] });
// Return interaction reply
return interaction?.editReply({ embeds: [embed] });
},
};

View file

@ -3,25 +3,25 @@ import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Modules
import give from "./modules/give";
import modules from "./modules";
// Handlers
import logger from "@logger";
// Function
export default {
metadata: { author: "Zyner" },
modules,
data: new SlashCommandBuilder()
.setName("reputation")
.setDescription("Manage reputation.")
.addSubcommand(give.data),
.addSubcommand(modules.give.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
if (options?.getSubcommand() === "give") {
logger?.verbose(`Executing give subcommand`);
await give.execute(interaction);
await modules.give.execute(interaction);
}
logger?.verbose(`No subcommand found`);

View file

@ -21,6 +21,8 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: { guildOnly: true, ephemeral: true },
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("give")
@ -36,8 +38,13 @@ export default {
.setName("type")
.setDescription("What type of reputation you want to repute")
.setRequired(true)
.addChoice("Positive", "positive")
.addChoice("Negative", "negative")
.addChoices(
{ name: "Positive", value: "positive" },
{
name: "Negative",
value: "negative",
}
)
);
},
execute: async (interaction: CommandInteraction) => {

View file

@ -0,0 +1,3 @@
import give from "@plugins/reputation/modules/give";
export default { give };

View file

@ -0,0 +1,60 @@
// Dependencies
import { CommandInteraction } from "discord.js";
// Handlers
import logger from "@logger";
// Modules
import modules from "./modules";
import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
// Function
export default {
modules,
data: (group: SlashCommandSubcommandGroupBuilder) => {
return group
.setName("guild")
.setDescription("Guild settings.")
.addSubcommand(modules.pterodactyl.data)
.addSubcommand(modules.credits.data)
.addSubcommand(modules.points.data)
.addSubcommand(modules.welcome.data)
.addSubcommand(modules.audits.data)
.addSubcommand(modules.shop.data);
},
execute: async (interaction: CommandInteraction) => {
// Destructure member
const { options } = interaction;
switch (options?.getSubcommand()) {
case "pterodactyl":
logger?.verbose(`Subcommand is pterodactyl`);
return modules.pterodactyl.execute(interaction);
case "credits":
logger?.verbose(`Subcommand is credits`);
return modules.credits.execute(interaction);
case "points":
logger?.verbose(`Subcommand is points`);
return modules.points.execute(interaction);
case "welcome":
logger?.verbose(`Subcommand is welcome`);
return modules.welcome.execute(interaction);
case "audits":
logger?.verbose(`Subcommand is audits`);
return modules.audits.execute(interaction);
case "shop":
logger?.verbose(`Subcommand is shop`);
return modules.shop.execute(interaction);
default:
logger?.verbose(`Subcommand is not found`);
}
},
};

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { CommandInteraction, Permissions } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
@ -14,6 +14,12 @@ import { ChannelType } from "discord-api-types/v10";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("audits")
@ -25,7 +31,7 @@ export default {
option
.setName("channel")
.setDescription("Channel for audit messages.")
.addChannelType(ChannelType.GuildText as number)
.addChannelTypes(ChannelType.GuildText)
);
},
execute: async (interaction: CommandInteraction) => {

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { CommandInteraction, Permissions } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
@ -13,6 +13,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("credits")

View file

@ -0,0 +1,8 @@
import audits from "@plugins/settings/groups/guild/modules/audits";
import credits from "@plugins/settings/groups/guild/modules/credits";
import points from "@plugins/settings/groups/guild/modules/points";
import pterodactyl from "@plugins/settings/groups/guild/modules/pterodactyl";
import shop from "@plugins/settings/groups/guild/modules/shop";
import welcome from "@plugins/settings/groups/guild/modules/welcome";
export default { audits, credits, points, pterodactyl, shop, welcome };

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { CommandInteraction, Permissions } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
@ -13,6 +13,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("points")

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { CommandInteraction, Permissions } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
@ -14,6 +14,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("pterodactyl")

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { CommandInteraction, Permissions } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
@ -10,10 +10,15 @@ import logger from "@logger";
// Models
import guildSchema from "@schemas/guild";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { ChannelType } from "discord-api-types/v10";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("shop")

View file

@ -1,5 +1,5 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { CommandInteraction, Permissions } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
@ -14,6 +14,12 @@ import { ChannelType } from "discord-api-types/v10";
// Function
export default {
meta: {
guildOnly: true,
ephemeral: true,
permissions: [Permissions.FLAGS.MANAGE_GUILD],
},
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("welcome")
@ -25,14 +31,16 @@ export default {
option
.setName("join-channel")
.setDescription("Channel for join messages.")
.addChannelType(ChannelType.GuildText as number)
.addChannelTypes(ChannelType.GuildText)
)
.addChannelOption((option) =>
option
.setName("leave-channel")
.setDescription("Channel for leave messages.")
.addChannelType(ChannelType.GuildText as number)
.addChannelTypes(ChannelType.GuildText)
)
.addStringOption((option) =>
option
.setName("leave-message")

View file

@ -0,0 +1,3 @@
import guild from "@plugins/settings/groups/guild";
export default { guild };

View file

@ -1,94 +0,0 @@
// Dependencies
import { Permissions, CommandInteraction } from "discord.js";
// Configurations
import { errorColor, footerText, footerIcon } from "@config/embed";
// Handlers
import logger from "@logger";
// Modules
import pterodactyl from "./modules/pterodactyl";
import credits from "./modules/credits";
import points from "./modules/points";
import welcome from "./modules/welcome";
import audits from "./modules/audits";
import shop from "./modules/shop";
import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
// Function
export default {
data: (group: SlashCommandSubcommandGroupBuilder) => {
return group
.setName("guild")
.setDescription("Guild settings.")
.addSubcommand(pterodactyl.data)
.addSubcommand(credits.data)
.addSubcommand(points.data)
.addSubcommand(welcome.data)
.addSubcommand(audits.data)
.addSubcommand(shop.data);
},
execute: async (interaction: CommandInteraction) => {
// Destructure member
const { memberPermissions, options } = interaction;
// Check permission
if (!memberPermissions?.has(Permissions?.FLAGS?.MANAGE_GUILD)) {
logger?.verbose(`User does not have permission to execute command.`);
return interaction?.editReply({
embeds: [
{
title: ":tools: Settings - Guild",
color: errorColor,
description: "You do not have permission to use this command.",
timestamp: new Date(),
footer: {
iconURL: footerIcon as string,
text: footerText as string,
},
},
],
});
}
if (options?.getSubcommand() === "pterodactyl") {
logger?.verbose(`Executing pterodactyl subcommand`);
return pterodactyl.execute(interaction);
}
if (options?.getSubcommand() === "credits") {
logger?.verbose(`Executing credits subcommand`);
return credits.execute(interaction);
}
if (options?.getSubcommand() === "points") {
logger?.verbose(`Executing points subcommand`);
return points.execute(interaction);
}
if (options?.getSubcommand() === "welcome") {
logger?.verbose(`Executing welcome subcommand`);
return welcome.execute(interaction);
}
if (options?.getSubcommand() === "audits") {
logger?.verbose(`Executing audit subcommand`);
return audits.execute(interaction);
}
if (options?.getSubcommand() === "shop") {
logger?.verbose(`Executing shop subcommand`);
return shop.execute(interaction);
}
logger?.verbose(`No subcommand found`);
},
};

View file

@ -3,20 +3,20 @@ import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Groups
import guildGroup from "./guild";
import userGroup from "./user";
import groups from "./groups";
// Handlers
import logger from "@logger";
// Function
export default {
metadata: { author: "Zyner" },
groups,
data: new SlashCommandBuilder()
.setName("settings")
.setDescription("Manage settings.")
.addSubcommandGroup(guildGroup.data)
.addSubcommandGroup(userGroup.data),
.addSubcommandGroup(groups.guild.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
@ -24,13 +24,7 @@ export default {
if (options.getSubcommandGroup() === "guild") {
logger.verbose(`Executing guild subcommand`);
return guildGroup.execute(interaction);
}
if (options.getSubcommandGroup() === "user") {
logger.verbose(`Executing user subcommand`);
return userGroup.execute(interaction);
return groups.guild.execute(interaction);
}
logger.verbose(`No subcommand group found`);

View file

@ -1,41 +0,0 @@
// Dependencies
import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Handlers
import logger from "@logger";
// Modules
import appearance from "./modules/appearance";
// Function
export default {
data: (group: SlashCommandSubcommandGroupBuilder) => {
return group
.setName("user")
.setDescription("User settings.")
.addSubcommand((command) =>
command
.setName("appearance")
.setDescription("User appearance settings.")
.addStringOption((option) =>
option
.setName("language")
.setDescription("Set the language.")
.addChoice("English", "en")
.addChoice("Swedish", "sv")
)
);
},
execute: async (interaction: CommandInteraction) => {
const { options } = interaction;
if (options?.getSubcommand() === "appearance") {
logger?.verbose(`Executing appearance subcommand`);
await appearance(interaction);
}
logger?.verbose(`No subcommand found`);
},
};

View file

@ -1,61 +0,0 @@
// Dependencies
import { CommandInteraction } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
// Handlers
import logger from "@logger";
// Models
import fetchUser from "@helpers/fetchUser";
// Function
export default async (interaction: CommandInteraction) => {
// Destructure member
const { options, user, guild } = interaction;
// Get options
const language = options?.getString("language");
if (guild === null) {
return logger?.verbose(`Guild is null`);
}
// Get user object
const userDB = await fetchUser(user, guild);
if (userDB === null) {
return logger?.verbose(`User is null`);
}
// Modify values
userDB.language = language !== null ? language : userDB?.language;
// Save guild
await userDB?.save()?.then(async () => {
logger?.verbose(`Updated user language.`);
return interaction?.editReply({
embeds: [
{
title: ":hammer: Settings - User [Appearance]",
description: "Successfully updated user settings.",
color: successColor,
fields: [
{
name: "🏳️‍🌈 Language",
value: `${userDB?.language}`,
inline: true,
},
],
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
},
],
});
});
};

View file

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

View file

@ -3,24 +3,25 @@ import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Handlers
import logger from "../../../logger";
import logger from "@logger";
import { errorColor, footerText, footerIcon } from "@config/embed";
// Modules
import buy from "./modules/buy";
import cancel from "./modules/cancel";
import modules from "./modules";
import guildSchema from "@schemas/guild";
// Function
export default {
modules,
data: (group: SlashCommandSubcommandGroupBuilder) => {
return group
.setName("roles")
.setDescription("Shop for custom roles.")
.addSubcommand(buy.data)
.addSubcommand(cancel.data);
.addSubcommand(modules.buy.data)
.addSubcommand(modules.cancel.data);
},
execute: async (interaction: CommandInteraction) => {
const { options, guild } = interaction;
@ -53,13 +54,13 @@ export default {
if (options?.getSubcommand() === "buy") {
logger.verbose(`Executing buy subcommand`);
await buy.execute(interaction);
await modules.buy.execute(interaction);
}
if (options?.getSubcommand() === "cancel") {
logger.verbose(`Executing cancel subcommand`);
await cancel.execute(interaction);
await modules.cancel.execute(interaction);
}
},
};

View file

@ -25,6 +25,8 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: { guildOnly: true, ephemeral: true },
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("buy")
@ -33,11 +35,13 @@ export default {
option
.setName("name")
.setDescription("Name of the role you wish to buy.")
.setRequired(true)
)
.addStringOption((option) =>
option
.setName("color")
.setDescription("Color of the role you wish to buy.")
.setRequired(true)
);
},
execute: async (interaction: CommandInteraction) => {

View file

@ -20,12 +20,17 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: { guildOnly: true, ephemeral: true },
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("cancel")
.setDescription("Cancel a purchase.")
.addRoleOption((option) =>
option.setName("role").setDescription("Role you wish to cancel.")
option
.setName("role")
.setDescription("Role you wish to cancel.")
.setRequired(true)
);
},
execute: async (interaction: CommandInteraction) => {

View file

@ -0,0 +1,7 @@
import buy from "./buy";
import cancel from "./cancel";
export default {
buy,
cancel,
};

View file

@ -3,35 +3,37 @@ import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Modules
import pterodactyl from "./modules/pterodactyl";
import modules from "./modules";
// Groups
import roles from "./roles";
import groups from "./groups";
// Handlers
import logger from "../../logger";
// Function
export default {
metadata: { author: "Zyner" },
modules,
groups,
data: new SlashCommandBuilder()
.setName("shop")
.setDescription("Shop for credits and custom roles.")
.addSubcommand(pterodactyl.data)
.addSubcommandGroup(roles.data),
.addSubcommand(modules.pterodactyl.data)
.addSubcommandGroup(groups.roles.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
if (options?.getSubcommand() === "pterodactyl") {
logger.verbose(`Executing pterodactyl subcommand`);
return pterodactyl.execute(interaction);
return modules.pterodactyl.execute(interaction);
}
if (options?.getSubcommandGroup() === "roles") {
logger?.verbose(`Subcommand group is roles`);
return roles.execute(interaction);
return groups.roles.execute(interaction);
}
logger?.verbose(`No subcommand found.`);

View file

@ -0,0 +1,3 @@
import pterodactyl from "@plugins/shop/modules/pterodactyl";
export default { pterodactyl };

View file

@ -1,9 +1,7 @@
// Dependencies
import { CommandInteraction } from "discord.js";
import { v4 as uuidv4 } from "uuid";
import axios from "axios";
// Configurations
import {
successColor,
errorColor,
@ -11,20 +9,18 @@ import {
footerIcon,
} from "@config/embed";
// Handlers
import logger from "@logger";
import encryption from "@handlers/encryption";
// Helpers
import pluralize from "@helpers/pluralize";
// Models
import apiSchema from "@schemas/api";
import fetchUser from "@helpers/fetchUser";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: { guildOnly: true, ephemeral: true },
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("pterodactyl")
@ -38,10 +34,8 @@ export default {
execute: async (interaction: CommandInteraction) => {
const { options, guild, user, client } = interaction;
// Get options
const optionAmount = options?.getInteger("amount");
// If amount is null
if (optionAmount === null) {
logger?.verbose(`Amount is null.`);
@ -65,17 +59,14 @@ export default {
return logger?.verbose(`Guild is null`);
}
// Get user object
const userDB = await fetchUser(user, guild);
if (userDB === null) {
return logger?.verbose(`User is null`);
}
// Get DM user object
const dmUser = client?.users?.cache?.get(user?.id);
// Stop if amount or user credits is below 100
if ((optionAmount || userDB?.credits) < 100) {
logger?.verbose(`Amount or user credits is below 100.`);
@ -101,7 +92,6 @@ export default {
});
}
// Stop if amount or user credits is above 1.000.000
if ((optionAmount || userDB?.credits) > 1000000) {
logger?.verbose(`Amount or user credits is above 1.000.000.`);
@ -128,7 +118,6 @@ export default {
});
}
// Stop if user credits is below amount
if (userDB?.credits < optionAmount) {
logger?.verbose(`User credits is below amount.`);
@ -154,15 +143,12 @@ export default {
});
}
// Generate a unique voucher for the user
const code = uuidv4();
// Get api object
const apiCredentials = await apiSchema?.findOne({
guildId: guild?.id,
});
// Create a api instance
const api = axios?.create({
baseURL: apiCredentials?.url,
headers: {
@ -170,13 +156,10 @@ export default {
},
});
// Get shop URL
const shopUrl = apiCredentials?.url?.replace("/api", "/store");
// Make API request
await api
// Make a post request to the API
?.post("vouchers", {
uses: 1,
code,
@ -184,17 +167,14 @@ export default {
memo: `${interaction?.createdTimestamp} - ${interaction?.user?.id}`,
})
// If successful
?.then(async () => {
logger?.verbose(`Successfully created voucher.`);
// Withdraw amount from user credits
userDB.credits -= optionAmount || userDB?.credits;
// Save new credits
await userDB
?.save()
// If successful
?.then(async () => {
logger?.verbose(`Successfully saved new credits.`);
@ -237,7 +217,6 @@ export default {
});
})
// If error occurs
.catch(async (error) => {
logger?.verbose(`Error saving new credits. - ${error}`);
@ -258,7 +237,6 @@ export default {
});
})
// If error occurs
.catch(async (error: any) => {
logger?.verbose(`Error creating voucher. - ${error}`);

View file

@ -1,45 +0,0 @@
// Dependencies
import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Modules
import lookup from "./modules/lookup";
import about from "./modules/about";
import stats from "./modules/stats";
// Handlers
import logger from "../../logger";
// Function
export default {
metadata: { author: "Zyner" },
data: new SlashCommandBuilder()
.setName("utilities")
.setDescription("Common utilities.")
.addSubcommand(lookup.data)
.addSubcommand(about.data)
.addSubcommand(stats.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
if (options?.getSubcommand() === "lookup") {
logger.verbose(`Executing lookup subcommand`);
return lookup.execute(interaction);
}
if (options?.getSubcommand() === "about") {
logger.verbose(`Executing about subcommand`);
return about.execute(interaction);
}
if (options?.getSubcommand() === "stats") {
logger.verbose(`Executing stats subcommand`);
return stats.execute(interaction);
}
logger.verbose(`No subcommand found.`);
},
};

View file

@ -1,134 +0,0 @@
// Dependencies
import axios from "axios";
import { CommandInteraction } from "discord.js";
// Configurations
import {
successColor,
errorColor,
footerText,
footerIcon,
} from "@config/embed";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Handlers
import logger from "@logger";
// Function
export default {
data: (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 { options } = interaction;
// Get lookup query
const query = options?.getString("query");
// Make API request
await axios
// Make a get request
?.get(`http://ip-api.com/json/${query}`)
// If successful
?.then(async (res) => {
// If query failed
if (res?.data?.status === "fail") {
// Create embed object
const embed = {
title: ":hammer: Utilities - Lookup",
description: `${res?.data?.message}: ${res?.data?.query}`,
color: errorColor,
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
};
// Send interaction reply
await interaction?.editReply({ embeds: [embed] });
}
// If query is successful
else if (res?.data?.status === "success") {
// Create embed object
const embed = {
title: ":hammer: Utilities - Lookup",
fields: [
{
name: "AS",
value: `${res?.data?.as || "Not available"}`,
},
{
name: "Country",
value: `${res?.data?.country || "Not available"}`,
},
{
name: "Country Code",
value: `${res?.data?.countryCode || "Not available"}`,
},
{
name: "Region",
value: `${res?.data?.region || "Not available"}`,
},
{
name: "Region Name",
value: `${res?.data?.regionName || "Not available"}`,
},
{
name: "City",
value: `${res?.data?.city || "Not available"}`,
},
{
name: "ZIP Code",
value: `${res?.data?.zip || "Not available"}`,
},
{
name: "Latitude",
value: `${res?.data?.lat || "Not available"}`,
},
{
name: "Longitude",
value: `${res?.data?.lon || "Not available"}`,
},
{
name: "Timezone",
value: `${res?.data?.timezone || "Not available"}`,
},
{
name: "ISP",
value: `${res?.data?.isp || "Not available"}`,
},
{
name: "Organization",
value: `${res?.data?.org || "Not available"}`,
},
],
color: successColor,
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
};
// Send interaction reply
await interaction?.editReply({ embeds: [embed] });
}
})
.catch(async (e) => {
logger?.error(e);
});
},
};

View file

@ -0,0 +1,40 @@
// Dependencies
import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Modules
import modules from "@plugins/utility/modules";
// Handlers
import logger from "../../logger";
// Function
export default {
modules,
data: new SlashCommandBuilder()
.setName("utility")
.setDescription("Common utility.")
.addSubcommand(modules.lookup.data)
.addSubcommand(modules.about.data)
.addSubcommand(modules.stats.data)
.addSubcommand(modules.avatar.data),
async execute(interaction: CommandInteraction) {
const { options } = interaction;
switch (options.getSubcommand()) {
case "lookup":
return modules.lookup.execute(interaction);
case "about":
return modules.about.execute(interaction);
case "stats":
return modules.stats.execute(interaction);
case "avatar":
return modules.avatar.execute(interaction);
default:
logger.error(`Unknown subcommand ${options.getSubcommand()}`);
}
},
};

View file

@ -9,6 +9,8 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
export default {
meta: { guildOnly: false, ephemeral: false },
data: (command: SlashCommandSubcommandBuilder) => {
return command.setName("about").setDescription("About this bot!)");
},

View file

@ -0,0 +1,52 @@
import { successColor, footerText, footerIcon } from "@config/embed";
import i18next from "i18next";
import { CommandInteraction, MessageEmbed } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
export default {
meta: { guildOnly: false, ephemeral: false },
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("avatar")
.setDescription("Check someones avatar!)")
.addUserOption((option) =>
option
.setName("user")
.setDescription("The user whose avatar you want to check")
);
},
execute: async (interaction: CommandInteraction) => {
const { locale } = interaction;
const userOption = interaction.options.getUser("user");
const targetUser = userOption || interaction.user;
const embed = new MessageEmbed()
.setTitle(
i18next.t("utility:modules:avatar:general:title", {
lng: locale,
ns: "plugins",
})
)
.setTimestamp(new Date())
.setFooter({ text: footerText, iconURL: footerIcon });
return interaction.editReply({
embeds: [
embed
.setDescription(
i18next.t("utility:modules:avatar:success01:description", {
lng: locale,
ns: "plugins",
user: targetUser,
})
)
.setThumbnail(targetUser.displayAvatarURL())
.setColor(successColor),
],
});
},
};

View file

@ -0,0 +1,11 @@
import avatar from "@plugins/utility/modules/avatar";
import about from "@plugins/utility/modules/about";
import lookup from "@plugins/utility/modules/lookup";
import stats from "@plugins/utility/modules/stats";
export default {
avatar,
about,
lookup,
stats,
};

View file

@ -0,0 +1,118 @@
import axios from "axios";
import { CommandInteraction } from "discord.js";
import { successColor, errorColor } from "@config/embed";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import embedBuilder from "@root/helpers/embedBuilder";
export default {
meta: { guildOnly: false, ephemeral: false },
data: (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 embedTitle = "[:hammer:] Utility (Lookup)";
embedBuilder.setTitle(embedTitle);
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: [
embedBuilder
.setColor(errorColor)
.setDescription(
`${response?.data?.message}: ${response?.data?.query}`
),
],
});
return;
}
await interaction.editReply({
embeds: [
embedBuilder.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,
},
]),
],
});
});
},
};

View file

@ -2,6 +2,8 @@ import { successColor, footerText, footerIcon } from "@config/embed";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
export default {
meta: { guildOnly: false, ephemeral: false },
data: (command: SlashCommandSubcommandBuilder) => {
return command.setName("stats").setDescription("Check bot statistics!)");
},

View file

@ -55,13 +55,25 @@ export default async (client: Client) => {
const rRole = rMember.roles.cache.get(roleId);
if (!rRole) {
logger.error(`Role ${roleId} not found for shop role ${roleId}.`);
return;
}
if (!rMember) {
if (!rMember || !rRole) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
await shopRoleSchema
.deleteOne({
userId,
roleId,
guildId,
})
.then(async () => {
logger.verbose(
`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;
}
@ -79,23 +91,6 @@ export default async (client: Client) => {
if (!rMember) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
await shopRoleSchema
.deleteOne({
userId,
roleId,
guildId,
})
.then(async () => {
logger.verbose(
`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;
}

View file

@ -12,6 +12,7 @@
"moduleResolution": "node",
"isolatedModules": true,
"outDir": "./build",
"resolveJsonModule": true,
"baseUrl": "./src",
"typeRoots": ["/types/common", "./node_modules/@types"],
"paths": {