Merge pull request #232 from ZynerOrg/dev

2022.4
This commit is contained in:
Axel Olausson Holtenäs 2022-04-17 19:33:31 +02:00 committed by GitHub
commit d26402115f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 937 additions and 231 deletions

View file

@ -13,6 +13,7 @@ inställningar
inte
Krediter
multistream
nastyox
Nivå
omdöme
Omdöme
@ -20,11 +21,15 @@ Otillgänglig
pino
Poäng
Profil
rando
satta
senaste
Sifell
själv
Språk
Språkkod
upsert
uuidv
Vermium
xyter
Zyner

2
.eslintignore Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist

15
.eslintrc Normal file
View file

@ -0,0 +1,15 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "no-loops", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-console": 1,
"no-loops/no-loops": 2
}
}

View file

@ -1,7 +1,7 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier"],
"plugins": ["@typescript-eslint", "no-loops", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
@ -9,10 +9,7 @@
"prettier"
],
"rules": {
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-explicit-any": 0,
"no-async-promise-executor": 0,
"prettier/prettier": "error"
"no-console": 1,
"no-loops/no-loops": 2
}
}

1
.husky/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
_

4
.husky/pre-commit Normal file
View file

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

View file

@ -1,34 +0,0 @@
{
"bot": {
"token": "required",
"clientId": "required",
"guildId": "required"
},
"colors": {
"success": "0x22bb33",
"error": "0xbb2124",
"wait": "0xf0ad4e"
},
"mongodb": {
"url": "mongodb+srv://username:password@server/database?retryWrites=true&w=majority"
},
"footer": {
"icon": "https://avatars.githubusercontent.com/u/83163073",
"text": "https://github.com/ZynerOrg/xyter"
},
"disable": {
"redeem": false
},
"hoster": {
"name": "someone",
"url": "scheme://domain.tld"
},
"debug": false,
"reputation": {
"timeout": 86400000
},
"secretKey": "SET A LONG RANDOM PASSWORD HERE, ITS FOR USE OF ENCRYPTION WITH LENGTH OF 32",
"importToDB": false,
"clearUnused": false,
"devMode": false
}

View file

@ -4,14 +4,17 @@
"description": "Earn credits while chatting! And more",
"main": "src/index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon | pino-pretty -i pid,hostname -t yyyy-mm-dd HH:MM:s"
"test": "jest",
"start": "nodemon | pino-pretty -i pid,hostname -t yyyy-mm-dd HH:MM:s",
"prettier-format": "prettier \"src/**/*.ts\" --write",
"lint": "eslint ./src --ext .ts",
"prepare": "husky install"
},
"keywords": [
"zyner",
"Zyner",
"xyter",
"controlpanel",
"controlpanel.gg",
"vermium"
"controlpanel.gg"
],
"repository": {
"type": "git",
@ -26,25 +29,15 @@
"dependencies": {
"@discordjs/builders": "^0.12.0",
"@discordjs/rest": "^0.3.0",
"@nastyox/rando.js": "^2.0.5",
"axios": "^0.26.0",
"better-sqlite3": "^7.5.0",
"chance": "^1.1.8",
"common": "^0.2.5",
"crypto": "^1.0.1",
"dbd-dark-dashboard": "^1.6.45",
"discord-api-types": "^0.31.0",
"discord-dashboard": "^2.3.14",
"discord-easy-dashboard": "^1.2.1",
"discord.js": "^13.6.0",
"dotenv": "^16.0.0",
"i18next": "^21.6.13",
"module-alias": "^2.2.2",
"mongoose": "^6.2.3",
"node-schedule": "^2.1.0",
"pino": "^7.0.0-rc.9",
"pino-pretty": "^7.6.1",
"quick.db": "^7.1.3",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.1",
"typescript": "^4.6.3",
@ -61,7 +54,14 @@
"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",
"lint-staged": "^12.3.7",
"prettier": "^2.6.0"
},
"lint-staged": {
"*.ts": "eslint --cache --fix"
}
}

View file

@ -1,2 +0,0 @@
// Controlpanel.gg (Pterodactyl) API token
export const cpggToken = "";

View file

@ -1,5 +1,14 @@
import { Intents } from "discord.js"; // discord.js
// Discord API token
export const token = "";
// Discord API id
export const clientId = "";
// Discord API intents
export const intents = [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MEMBERS,
];

View file

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

View file

@ -17,6 +17,14 @@ interface IGuild {
minimumLength: number;
timeout: number;
};
welcome: {
status: boolean;
joinChannel: string;
leaveChannel: string;
joinChannelMessage: string;
leaveChannelMessage: string;
};
audits: { status: boolean; channelId: string };
}
const guildSchema = new Schema<IGuild>(
@ -83,6 +91,20 @@ const guildSchema = new Schema<IGuild>(
default: 5000,
},
},
welcome: {
status: {
type: Boolean,
default: false,
},
joinChannel: { type: String },
leaveChannel: { type: String },
joinChannelMessage: { type: String },
leaveChannelMessage: { type: String },
},
audits: {
status: { type: Boolean, default: false },
channelId: { type: String },
},
},
{ timestamps: true }
);

View file

@ -0,0 +1,43 @@
import logger from "@logger";
import { GuildMember, MessageEmbed, TextChannel } from "discord.js";
import guildSchema from "@schemas/guild";
import { footerText, footerIcon, successColor } from "@config/embed";
export default {
execute: async (member: GuildMember) => {
const guildData = await guildSchema.findOne({ guildId: member.guild.id });
const { client } = member;
if (guildData === null) return;
if (guildData.audits.status !== true) return;
if (!guildData.audits.channelId) return;
const channel = client.channels.cache.get(`${guildData.audits.channelId}`);
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setAuthor({
name: "Member Joined",
iconURL: member.user.displayAvatarURL(),
})
.setDescription(`${member.user} ${member.user.tag}`)
.addFields([
{ name: "Account Age", value: `${member.user.createdAt}` },
])
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
});
},
};

View file

@ -5,6 +5,8 @@ import { GuildMember } from "discord.js";
import updatePresence from "@helpers/updatePresence";
import fetchUser from "@helpers/fetchUser";
import logger from "@logger";
import joinMessage from "../guildMemberAdd/joinMessage";
import audits from "../guildMemberAdd/audits";
export default {
name: "guildMemberAdd",
@ -15,6 +17,8 @@ export default {
`New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})`
);
await audits.execute(member);
await joinMessage.execute(member);
await fetchUser(user, guild);
await updatePresence(client);
},

View file

@ -0,0 +1,45 @@
import logger from "@logger";
import { GuildMember, MessageEmbed, TextChannel } from "discord.js";
import guildSchema from "@schemas/guild";
import { footerText, footerIcon, successColor } from "@config/embed";
export default {
execute: async (member: GuildMember) => {
logger.info(member);
const guildData = await guildSchema.findOne({ guildId: member.guild.id });
const { client } = member;
if (guildData === null) return;
if (guildData.welcome.status !== true) return;
if (!guildData.welcome.joinChannel) return;
const channel = client.channels.cache.get(
`${guildData.welcome.joinChannel}`
);
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setTitle(`${member.user.username} has joined the server!`)
.setThumbnail(member.user.displayAvatarURL())
.setDescription(
guildData.welcome.joinChannelMessage ||
"Configure a join message in the `/settings guild welcome`."
)
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
});
},
};

View file

@ -0,0 +1,40 @@
import logger from "@logger";
import { GuildMember, MessageEmbed, TextChannel } from "discord.js";
import guildSchema from "@schemas/guild";
import { footerText, footerIcon, errorColor } from "@config/embed";
export default {
execute: async (member: GuildMember) => {
const guildData = await guildSchema.findOne({ guildId: member.guild.id });
const { client } = member;
if (guildData === null) return;
if (guildData.audits.status !== true) return;
if (!guildData.audits.channelId) return;
const channel = client.channels.cache.get(`${guildData.audits.channelId}`);
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(errorColor)
.setAuthor({
name: "Member Left",
iconURL: member.user.displayAvatarURL(),
})
.setDescription(`${member.user} ${member.user.tag}`)
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
});
},
};

View file

@ -5,6 +5,8 @@ import { GuildMember } from "discord.js";
import updatePresence from "@helpers/updatePresence";
import dropUser from "@helpers/dropUser";
import logger from "@logger";
import leaveMessage from "./leaveMessage";
import audits from "./audits";
export default {
name: "guildMemberRemove",
@ -15,6 +17,8 @@ export default {
`Removed member: ${user.tag} (${user.id}) from guild: ${guild.name} (${guild.id})`
);
await audits.execute(member);
await leaveMessage.execute(member);
await dropUser(user, guild);
await updatePresence(client);
},

View file

@ -0,0 +1,45 @@
import logger from "@logger";
import { GuildMember, MessageEmbed, TextChannel } from "discord.js";
import guildSchema from "@schemas/guild";
import { footerText, footerIcon, errorColor } from "@config/embed";
export default {
execute: async (member: GuildMember) => {
logger.info(member);
const guildData = await guildSchema.findOne({ guildId: member.guild.id });
const { client } = member;
if (guildData === null) return;
if (guildData.welcome.status !== true) return;
if (!guildData.welcome.leaveChannel) return;
const channel = client.channels.cache.get(
`${guildData.welcome.leaveChannel}`
);
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(errorColor)
.setTitle(`${member.user.username} has left the server!`)
.setThumbnail(member.user.displayAvatarURL())
.setDescription(
guildData.welcome.leaveChannelMessage ||
"Configure a leave message in the `/settings guild welcome`."
)
.setTimestamp()
.setFooter({
text: footerText,
iconURL: footerIcon,
}),
],
});
},
};

View file

@ -0,0 +1,48 @@
import logger from "@logger";
import { Interaction, MessageEmbed, TextChannel } from "discord.js";
import guildSchema from "@schemas/guild";
import { footerText, footerIcon, successColor } from "@config/embed";
export default {
execute: async (interaction: Interaction) => {
if (interaction === null) return;
if (interaction.guild === null) return;
const guildData = await guildSchema.findOne({
guildId: interaction.guild.id,
});
const { client } = interaction;
if (guildData === null) return;
if (guildData.audits.status !== true) return;
if (!guildData.audits.channelId) return;
const channel = client.channels.cache.get(`${guildData.audits.channelId}`);
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.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,
}),
],
});
},
};

View file

@ -4,6 +4,7 @@ import { CommandInteraction } from "discord.js";
// Dependencies
import isCommand from "@root/events/interactionCreate/components/isCommand";
import logger from "@logger";
import audits from "./audits";
export default {
name: "interactionCreate",
@ -14,6 +15,7 @@ export default {
`New interaction: ${id} in guild: ${guild?.name} (${guild?.id})`
);
await audits.execute(interaction);
await isCommand(interaction);
},
};

View file

@ -0,0 +1,51 @@
import logger from "@logger";
import { Message, MessageEmbed, TextChannel } from "discord.js";
import guildSchema from "@schemas/guild";
import { footerText, footerIcon, successColor } from "@config/embed";
export default {
execute: async (message: Message) => {
if (message === null) return;
if (message.guild === null) return;
const guildData = await guildSchema.findOne({
guildId: message.guild.id,
});
const { client } = message;
if (guildData === null) return;
if (guildData.audits.status !== true) return;
if (!guildData.audits.channelId) return;
const channel = client.channels.cache.get(`${guildData.audits.channelId}`);
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setAuthor({
name: 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,
}),
],
});
},
};

View file

@ -0,0 +1,9 @@
import { Message } from "discord.js";
import audits from "@events/messageDelete/audits";
export default {
name: "messageDelete",
async execute(message: Message) {
await audits.execute(message);
},
};

View file

@ -0,0 +1,53 @@
/* eslint-disable no-loops/no-loops */
import logger from "@logger";
import { Message, MessageEmbed, TextChannel } from "discord.js";
import guildSchema from "@schemas/guild";
import { footerText, footerIcon, successColor } from "@config/embed";
export default {
execute: async (oldMessage: Message, newMessage: Message) => {
if (oldMessage === null) return;
if (newMessage === null) return;
if (oldMessage.guild === null) return;
if (newMessage.guild === null) return;
const guildData = await guildSchema.findOne({
guildId: oldMessage.guild.id,
});
const { client } = oldMessage;
if (guildData === null) return;
if (guildData.audits.status !== true) return;
if (!guildData.audits.channelId) return;
const channel = client.channels.cache.get(`${guildData.audits.channelId}`);
if (channel === null) return;
(channel as TextChannel).send({
embeds: [
new MessageEmbed()
.setColor(successColor)
.setAuthor({
name: 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,
}),
],
});
},
};

View file

@ -5,11 +5,15 @@ import logger from "@logger";
// Modules
import counter from "./modules/counter";
import audits from "./audits";
export default {
name: "messageUpdate",
async execute(_oldMessage: Message, newMessage: Message) {
async execute(oldMessage: Message, newMessage: Message) {
const { author, guild } = newMessage;
await audits.execute(oldMessage, newMessage);
logger?.verbose(
`Message update event fired by ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`
);

View file

@ -1,25 +1,25 @@
// Dependencies
import { Client } from "discord.js";
import logger from "../../logger";
import logger from "@logger";
// Helpers
import deployCommands from "../../handlers/deployCommands";
import updatePresence from "../../helpers/updatePresence";
import devMode from "../../helpers/devMode";
import updatePresence from "@helpers/updatePresence";
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`);
logger.info(`${client.user?.tag} (${client.user?.id}) is ready`);
await updatePresence(client);
await devMode(client);
await deployCommands();
await deployCommands(client);
client?.guilds?.cache?.forEach((guild) => {
logger?.info(
`${client.user?.tag} (${client.user?.id}) is in guild: ${guild.name} (${guild.id})`
client.guilds?.cache.forEach((guild) => {
logger.verbose(
`${client.user?.tag} (${client.user?.id}) is in guild: ${guild.name} (${guild.id}) with member count of ${guild.memberCount}`
);
});
},

View file

@ -1,18 +1,18 @@
import fs from "fs"; // fs
import { Collection } from "discord.js"; // discord.js
import { Client } from "../types/common/discord";
import logger from "../logger";
import { Client } from "@root/types/common/discord";
import logger from "@logger";
export default async (client: Client) => {
client.commands = new Collection();
fs.readdir("./src/plugins", async (error: any, plugins: any) => {
fs.readdir("./src/plugins", async (error, plugins) => {
if (error) {
return logger?.error(`Error reading plugins: ${error}`);
return logger.error(`Error reading plugins: ${error}`);
}
await Promise.all(
plugins?.map(async (pluginName: any) => {
plugins.map(async (pluginName) => {
const plugin = await import(`../plugins/${pluginName}`);
await client?.commands?.set(
@ -20,8 +20,14 @@ export default async (client: Client) => {
plugin?.default
);
logger?.verbose(`Loaded plugin: ${pluginName}`);
logger.verbose(`Loaded plugin: ${pluginName}`);
})
);
)
.then(async () => {
logger.debug("Successfully loaded plugins.");
})
.catch(async (err) => {
logger.error(err);
});
});
};

View file

@ -3,52 +3,51 @@ import { token, clientId } from "@config/discord";
import { devMode, guildId } from "@config/other";
import logger from "../logger";
import fs from "fs";
import { Client } from "@root/types/common/discord";
import { REST } from "@discordjs/rest";
import { Routes } from "discord-api-types/v9";
export default async () => {
fs.readdir("./src/plugins", async (error: any, plugins: any) => {
if (error) {
return logger?.error(new Error(error));
}
export default async (client: Client) => {
const pluginList = [] as string[];
const pluginList = [] as any;
await Promise.all(
client.commands.map(async (pluginData: any) => {
pluginList.push(pluginData.data.toJSON());
logger.verbose(
`${pluginData.data.name} successfully pushed to plugin list.`
);
})
)
.then(async () => {
logger.debug("Successfully pushed all plugins to plugin list.");
})
.catch(async (error) => {
logger.error(error);
});
await Promise.all(
plugins?.map(async (pluginName: any) => {
const plugin = await import(`../plugins/${pluginName}`);
const rest = new REST({ version: "9" }).setToken(token);
pluginList.push(plugin.default.data.toJSON());
logger?.verbose(`Loaded plugin: ${pluginName} for deployment`);
})
);
const rest = new REST({ version: "9" }).setToken(token);
await rest
.put(Routes.applicationCommands(clientId), {
body: pluginList,
})
.then(async () => {
logger.debug(`Successfully deployed plugins to Discord`);
})
.catch(async (error) => {
logger.error(error);
});
if (devMode) {
await rest
.put(Routes.applicationCommands(clientId), {
.put(Routes.applicationGuildCommands(clientId, guildId), {
body: pluginList,
})
.then(async () => {
logger?.info(`Successfully deployed plugins to Discord`);
})
.catch(async (err: any) => {
logger.error(err);
.then(async () =>
logger.debug(`Successfully deployed guild plugins to Discord`)
)
.catch(async (error) => {
logger.error(error);
});
if (devMode) {
await rest
.put(Routes.applicationGuildCommands(clientId, guildId), {
body: pluginList,
})
.then(async () =>
logger?.info(`Successfully deployed guild plugins to Discord`)
)
.catch(async (err: any) => {
logger.error(err);
});
}
});
}
};

View file

@ -9,11 +9,11 @@ import { devMode, guildId } from "@config/other";
export default async (client: Client) => {
if (!devMode) {
return client?.application?.commands?.set([], guildId).then(async () => {
return logger?.verbose(
return logger.debug(
`Development commands disabled for guild: ${guildId}`
);
});
}
return logger?.verbose(`Development commands enabled for guild: ${guildId}`);
return logger.debug(`Development commands enabled for guild: ${guildId}`);
};

View file

@ -3,20 +3,33 @@ import { Client } from "discord.js"; // discord.js
import logger from "@logger";
export default async (client: Client) => {
const eventFiles = fs.readdirSync("./src/events");
for (const file of eventFiles) {
const event = require(`../events/${file}`);
if (event.once) {
client.once(event.default.name, (...args) =>
event.default.execute(...args)
);
logger?.verbose(`Loaded event: ${event.default.name}`);
} else {
client.on(event.default.name, (...args) =>
event.default.execute(...args)
);
logger?.verbose(`Loaded event: ${event.default.name}`);
fs.readdir("./src/events", async (error, events) => {
if (error) {
return logger.error(`Error reading plugins: ${error}`);
}
}
await Promise.all(
events.map(async (eventName) => {
const event = await import(`../events/${eventName}`);
logger.verbose(`Loaded event: ${eventName}`);
if (event.once) {
return client.once(event.default.name, async (...args) =>
event.default.execute(...args)
);
}
return client.on(event.default.name, async (...args) =>
event.default.execute(...args)
);
})
)
.then(async () => {
logger.debug("Successfully loaded events.");
})
.catch(async (err) => {
logger.error(err);
});
});
};

View file

@ -6,7 +6,7 @@ import { Guild, User } from "discord.js";
export default async (user: User, guild: Guild) => {
await userSchema
.deleteOne({ userId: user?.id, guildId: guild?.id })
.deleteOne({ userId: user.id, guildId: guild.id })
.then(async () => {
logger?.verbose(`Deleted user: ${user?.id} from guild: ${guild?.id}`);
})

View file

@ -1,5 +1,5 @@
import sleep from "./sleep";
import logger from "../logger";
import sleep from "@helpers/sleep";
import logger from "@logger";
import Chance from "chance";
export default async function saveUser(data: any, data2: any) {

View file

@ -1,30 +1,28 @@
// Dependencies
import "tsconfig-paths/register"; // Allows using tsconfig.json paths during runtime
import { Client, Intents } from "discord.js"; // discord.js
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";
// Configurations
import { token } from "@config/discord";
async function main() {
const client = new Client({
intents,
});
const client = new Client({
intents: [
Intents?.FLAGS?.GUILDS,
Intents?.FLAGS?.GUILD_MESSAGES,
Intents?.FLAGS?.GUILD_MEMBERS,
],
});
await locale();
await database();
await schedules(client);
locale();
database();
schedules(client);
await commands(client);
await events(client);
commands(client);
events(client);
await client.login(token);
}
client?.login(token);
main();

View file

@ -135,9 +135,9 @@ export default async () => {
},
})
.then(async () => {
logger?.verbose(`i18next initialized`);
logger.debug(`i18next initialized`);
})
.catch(async (error) => {
logger?.error(`i18next failed to initialize: ${error}`);
logger.error(`i18next failed to initialize: ${error}`);
});
};

View file

@ -3,10 +3,10 @@ import { SlashCommandBuilder } from "@discordjs/builders";
import { CommandInteraction } from "discord.js";
// Modules
import give from "./modules/give";
import give from "@plugins/reputation/modules/give";
// Handlers
import logger from "../../logger";
import logger from "@logger";
// Function
export default {

View file

@ -11,6 +11,8 @@ import logger from "@logger";
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 { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
// Function
@ -21,7 +23,9 @@ export default {
.setDescription("Guild settings.")
.addSubcommand(pterodactyl.data)
.addSubcommand(credits.data)
.addSubcommand(points.data);
.addSubcommand(points.data)
.addSubcommand(welcome.data)
.addSubcommand(audits.data);
},
execute: async (interaction: CommandInteraction) => {
// Destructure member
@ -51,16 +55,32 @@ export default {
logger?.verbose(`Executing pterodactyl subcommand`);
return pterodactyl.execute(interaction);
} else if (options?.getSubcommand() === "credits") {
}
if (options?.getSubcommand() === "credits") {
logger?.verbose(`Executing credits subcommand`);
return credits.execute(interaction);
} else if (options?.getSubcommand() === "points") {
}
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);
}
logger?.verbose(`No subcommand found`);
},
};

View file

@ -0,0 +1,85 @@
// Dependencies
import { CommandInteraction } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
// Handlers
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 {
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("audits")
.setDescription("Audits")
.addBooleanOption((option) =>
option.setName("status").setDescription("Should audits be enabled?")
)
.addChannelOption((option) =>
option
.setName("channel")
.setDescription("Channel for audit messages.")
.addChannelType(ChannelType.GuildText as number)
);
},
execute: async (interaction: CommandInteraction) => {
// Destructure member
const { options, guild } = interaction;
// Get options
const status = options?.getBoolean("status");
const channel = options?.getChannel("channel");
// Get guild object
const guildDB = await guildSchema?.findOne({
guildId: guild?.id,
});
if (guildDB === null) {
return logger?.verbose(`Guild not found in database.`);
}
// Modify values
guildDB.audits.status = status !== null ? status : guildDB?.audits?.status;
guildDB.audits.channelId =
channel !== null ? channel.id : guildDB?.audits?.channelId;
// Save guild
await guildDB?.save()?.then(async () => {
logger?.verbose(`Guild audits updated.`);
return interaction?.editReply({
embeds: [
{
title: ":hammer: Settings - Guild [Audits]",
description: `Audits settings updated.`,
color: successColor,
fields: [
{
name: "🤖 Status",
value: `${guildDB?.audits?.status}`,
inline: true,
},
{
name: "🌊 Channel",
value: `${guildDB?.audits?.channelId}`,
inline: true,
},
],
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
},
],
});
});
},
};

View file

@ -0,0 +1,131 @@
// Dependencies
import { CommandInteraction } from "discord.js";
// Configurations
import { successColor, footerText, footerIcon } from "@config/embed";
// Handlers
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 {
data: (command: SlashCommandSubcommandBuilder) => {
return command
.setName("welcome")
.setDescription("Welcome")
.addBooleanOption((option) =>
option.setName("status").setDescription("Should welcome be enabled?")
)
.addChannelOption((option) =>
option
.setName("join-channel")
.setDescription("Channel for join messages.")
.addChannelType(ChannelType.GuildText as number)
)
.addChannelOption((option) =>
option
.setName("leave-channel")
.setDescription("Channel for leave messages.")
.addChannelType(ChannelType.GuildText as number)
)
.addStringOption((option) =>
option
.setName("leave-message")
.setDescription("Message for leave messages.")
)
.addStringOption((option) =>
option
.setName("join-message")
.setDescription("Message for join messages.")
);
},
execute: async (interaction: CommandInteraction) => {
// Destructure member
const { options, guild } = interaction;
// Get options
const status = options?.getBoolean("status");
const joinChannel = options?.getChannel("join-channel");
const leaveChannel = options?.getChannel("leave-channel");
const joinChannelMessage = options?.getString("join-message");
const leaveChannelMessage = options?.getString("leave-message");
// Get guild object
const guildDB = await guildSchema?.findOne({
guildId: guild?.id,
});
if (guildDB === null) {
return logger?.verbose(`Guild not found in database.`);
}
// Modify values
guildDB.welcome.status =
status !== null ? status : guildDB?.welcome?.status;
guildDB.welcome.joinChannel =
joinChannel !== null ? joinChannel.id : guildDB?.welcome?.joinChannel;
guildDB.welcome.leaveChannel =
leaveChannel !== null ? leaveChannel.id : guildDB?.welcome?.leaveChannel;
guildDB.welcome.joinChannelMessage =
joinChannelMessage !== null
? joinChannelMessage
: guildDB?.welcome?.joinChannelMessage;
guildDB.welcome.leaveChannelMessage =
leaveChannelMessage !== null
? leaveChannelMessage
: guildDB?.welcome?.leaveChannelMessage;
// Save guild
await guildDB?.save()?.then(async () => {
logger?.verbose(`Guild welcome updated.`);
return interaction?.editReply({
embeds: [
{
title: ":hammer: Settings - Guild [Welcome]",
description: `Welcome settings updated.`,
color: successColor,
fields: [
{
name: "🤖 Status",
value: `${guildDB?.welcome?.status}`,
inline: true,
},
{
name: "🌊 Join Channel",
value: `${guildDB?.welcome?.joinChannel}`,
inline: true,
},
{
name: "🌊 Leave Channel",
value: `${guildDB?.welcome?.leaveChannel}`,
inline: true,
},
{
name: "📄 Join Channel Message",
value: `${guildDB?.welcome?.joinChannelMessage}`,
inline: true,
},
{
name: "📄 Leave Channel Message",
value: `${guildDB?.welcome?.leaveChannelMessage}`,
inline: true,
},
],
timestamp: new Date(),
footer: {
iconURL: footerIcon,
text: footerText,
},
},
],
});
});
},
};

View file

@ -11,7 +11,14 @@ export default async (client: Client) => {
const expression = "*/5 * * * *";
schedule.scheduleJob(expression, async () => {
logger?.verbose("Running shop roles job.");
await shopRoles(client);
logger.info("Running jobs.");
await shopRoles(client)
.then(() => {
logger.info("Shop roles job finished.");
})
.catch((err) => {
logger.error(`Shop roles job failed: ${err}`);
});
});
};

View file

@ -9,51 +9,131 @@ import shopRoleSchema from "@schemas/shopRole";
import guildSchema from "@schemas/guild";
export default async (client: Client) => {
await shopRoleSchema?.find()?.then(async (shopRoles: any) => {
shopRoles?.map(async (shopRole: any) => {
const payed = new Date(shopRole?.lastPayed);
const roles = await shopRoleSchema.find();
const oneHourAfterPayed = payed?.setHours(payed?.getHours() + 1);
await Promise.all(
roles.map(async (role) => {
const { guildId, userId, roleId } = role;
if (new Date() > new Date(oneHourAfterPayed)) {
logger?.verbose(`Shop role ${shopRole?.roleId} is expired.`);
const lastPayment = new Date(role.lastPayed);
// Get guild object
const guild = await guildSchema?.findOne({
guildId: shopRole?.guildId,
});
const nextPayment = new Date(
lastPayment.setHours(lastPayment.getHours() + 1)
);
if (guild === null) return;
const userDB = await userSchema?.findOne({
userId: shopRole?.userId,
guildId: shopRole?.guildId,
});
const { pricePerHour } = guild.shop.roles;
if (new Date() < nextPayment) {
logger.silly(`Shop role ${roleId} is not due for payment.`);
}
if (userDB === null) return;
const guildData = await guildSchema.findOne({ guildId });
if (userDB?.credits < pricePerHour) {
const rGuild = client?.guilds?.cache?.get(`${shopRole?.guildId}`);
const rMember = await rGuild?.members?.fetch(`${shopRole?.userId}`);
if (!guildData) {
logger.error(`Guild ${guildId} not found.`);
return;
}
shopRoleSchema
?.deleteOne({ _id: shopRole?._id })
?.then(async () =>
logger?.verbose(`Shop role ${shopRole?.roleId} was deleted.`)
)
.catch(async (error) => {
return logger?.error(error);
});
if (!userId) {
logger.error(`User ID not found for shop role ${roleId}.`);
return;
}
return rMember?.roles?.remove(`${shopRole?.roleId}`);
const userData = await userSchema.findOne({ guildId, userId });
if (!userData) {
logger.error(`User ${userId} not found for shop role ${roleId}.`);
return;
}
const rGuild = client?.guilds?.cache?.get(guildId);
const rMember = await rGuild?.members?.fetch(userId);
if (!rMember) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
return;
}
const rRole = rMember.roles.cache.get(roleId);
if (!rRole) {
logger.error(`Role ${roleId} not found for shop role ${roleId}.`);
return;
}
if (!rMember) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
return;
}
if (new Date() > nextPayment) {
logger.verbose(
`Shop role ${roleId} is due for payment. Withdrawing credits from user ${userId}.`
);
const { pricePerHour } = guildData.shop.roles;
if (userData.credits < pricePerHour) {
logger.error(
`User ${userId} does not have enough credits to pay for shop role ${roleId}.`
);
if (!rMember) {
logger.error(`Member ${userId} not found for shop role ${roleId}.`);
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;
}
rMember.roles.remove(roleId);
return;
}
shopRole.lastPayed = new Date();
shopRole?.save()?.then(async () => {
userDB.credits -= pricePerHour;
userDB?.save();
});
userData.credits -= pricePerHour;
await userData
.save()
.then(async () => {
role.lastPayed = new Date();
await role
.save()
.then(async () => {
logger.verbose(`Shop role ${roleId} has been paid for.`);
})
.catch(async (err) => {
logger.error(
`Error saving shop role ${roleId} last payed date.`,
err
);
});
logger.verbose(
`Shop role ${roleId} has been paid for. Keeping role ${roleId} for user ${userId}.`
);
})
.catch(async (err) => {
logger.error(
`Error saving user ${userId} credits for shop role ${roleId}.`,
err
);
});
}
});
});
})
);
};

View file

@ -1,34 +1,34 @@
{
"compilerOptions": {
"target": "es2019",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "commonjs",
"resolveJsonModule": true,
"isolatedModules": true,
"outDir": "./build",
"baseUrl": "./src",
"typeRoots": ["/types/common", "./node_modules/@types"],
"paths": {
"@interface/*": ["Interfaces/*"],
"@root/*": ["*"],
"@config/*": ["config/*"],
"@events/*": ["events/*"],
"@logger": ["logger"],
"@database": ["database"],
"@schedules": ["schedules"],
"@handlers/*": ["handlers/*"],
"@helpers/*": ["helpers/*"],
"@locale": ["locale"],
"@schemas/*": ["database/schemas/*"]
}
},
"include": ["./src"],
"exclude": ["./node_modules", "./test"]
}
{
"compilerOptions": {
"target": "es2022",
"module": "CommonJS",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"isolatedModules": true,
"outDir": "./build",
"baseUrl": "./src",
"typeRoots": ["/types/common", "./node_modules/@types"],
"paths": {
"@interface/*": ["Interfaces/*"],
"@root/*": ["*"],
"@config/*": ["config/*"],
"@events/*": ["events/*"],
"@logger": ["logger"],
"@database": ["database"],
"@schedules": ["schedules"],
"@handlers/*": ["handlers/*"],
"@helpers/*": ["helpers/*"],
"@locale": ["locale"],
"@plugins/*": ["plugins/*"],
"@schemas/*": ["database/schemas/*"]
}
},
"include": ["./src"],
"exclude": ["./node_modules", "./test"]
}