diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index db1a7c3..0000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: "🐛 Bug Report" -about: Report unexpected behavior to improve the bot -title: 'bug: ' ---- - -## Description of Bug - - - - - -## Steps To Reproduce - -1. -2. - -## The current behavior - - - - -## The expected behavior diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..199ee24 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,42 @@ +name: Bug report +description: File a bug report for us to fix +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report. Please be a descriptive as possible so we can improve. + + - type: textarea + attributes: + label: Describe the bug + description: A clear description of what the bug is. + validations: + required: true + + - type: textarea + attributes: + label: To Reproduce + description: Steps to reproduce this behaviour + placeholder: | + 1. Do that + 2. Then this + 3. Error there + validations: + required: true + + - type: textarea + attributes: + label: Expected behaviour + description: Describe what should be happening. + validations: + required: true + + - type: textarea + attributes: + label: Screenshots / Videos + description: If applicable, add screenshots to help explain your problem. + + - type: textarea + attributes: + label: Additional Context + description: Add any other context about the problem here diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..2b26387 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Discord + url: https://discord.gg/calm + about: Join our discord. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md deleted file mode 100644 index 37cc293..0000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: 'Feature Request' -about: 'Request a feature for the bot' -title: 'feat: ' ---- - -## Intended behavior - -## Implementation method (optional) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..a97ee01 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,29 @@ +name: Feature request +description: Suggest an idea for this project +labels: "Feature Request" +body: + - type: markdown + attributes: + value: | + Thanks for taking this time to fill out a feature request. Please be as descriptive or as non descriptive as possible. + + - type: textarea + attributes: + label: What feature do you want to see added? + description: What would you like to add? + validations: + required: true + + - type: textarea + attributes: + label: Why would this improve Calmbot? + description: What about this feature would improve the bot overall? + validations: + required: true + + - type: textarea + attributes: + label: Implementation method (optional) + description: How would someone go about implementing this? + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index f563bb1..b7603b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .env node_modules -build \ No newline at end of file +build +logs +settings.json \ No newline at end of file diff --git a/README.md b/README.md index 495014e..7bbcd80 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CalmBot - +## The project is now *archived* and will no longer recieve support or updates. Please visit [the rewritten calmbot](https://github.com/calmguild/calmbot) > CalmBot is an open-source Discord bot, built with discord.js diff --git a/package.json b/package.json index 4df0cc5..764a6d0 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,13 @@ "start:dev": "ts-node src/bot.ts" }, "dependencies": { + "@types/fs-extra": "^9.0.6", "axios": "^0.21.1", "discord.js": "^12.5.1", "dotenv": "^8.2.0", - "mongoose": "^5.11.6" + "fs-extra": "^9.1.0", + "mongoose": "^5.11.6", + "winston": "^3.3.3" }, "devDependencies": { "rimraf": "^3.0.2", diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..120bb82 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,2 @@ +# temp files +members.json \ No newline at end of file diff --git a/scripts/getMembersFromDiscord.js b/scripts/getMembersFromDiscord.js new file mode 100644 index 0000000..26d7b60 --- /dev/null +++ b/scripts/getMembersFromDiscord.js @@ -0,0 +1,98 @@ +require("dotenv").config(); + +const { Client, Intents } = require("discord.js"); +const axios = require("axios"); +const fs = require("fs-extra"); +const path = require("path"); +const roles = require("../src/data/calm/roles.json"); + +const args = process.argv.slice(2); +const calmiesRole = roles.GENERAL.CALMIES; +const guildId = "5af718d40cf2cbe7a9eeb063"; + +const intents = new Intents([Intents.NON_PRIVILEGED, "GUILD_MEMBERS"]); +const bot = new Client({ ws: { intents } }); + +async function run() { + console.log("Bot ready!"); + + if (!args[0]) { + console.error("Provide a discord guild as the first argument"); + return; + } + const res = await axios.get(`https://api.hypixel.net/guild?key=${process.env.HYPIXEL_API_KEY}&id=${guildId}`); + const guild = res?.data?.guild; + if (!guild) { + console.log("Invalid guild id!"); + bot.destroy(); + return; + } + + console.log("Successfuly requested guild"); + + const members = []; + for (const [i, member] of Object.entries(guild.members)) { + const uuid = member.uuid; + + const res = await axios.get(`https://api.mojang.com/user/profiles/${uuid}/names`); + const names = res?.data; + if (!names) { + console.log("Failed to request name of " + uuid); + members.push({ uuid, ign: "", id: "", nick: "", tag: "" }); + return; + } + const ign = names[names.length - 1].name; + + members.push({ uuid, ign, id: "", nick: "", tag: "" }); + console.log(`${uuid}:${ign}`); + } + + const discordGuild = bot.guilds.cache.get(args[0]); + if (!discordGuild) { + console.error("The bot isn't in the guild " + args[0]); + return; + } + + const discordMembers = await discordGuild.members.fetch(); + for (const [id, member] of discordMembers) { + if (!member.roles.cache.some((r) => r.id === calmiesRole.id)) continue; + + let name = member.nickname; + if (!name) { + console.log("Couldn't find ign for " + member.user.tag); + continue; + } + + name = name.slice(name.indexOf("]") + 2); + const j = name.indexOf(" "); + if (j !== -1) name = name.slice(0, j); + + if (!name) { + console.log("Couldn't find ign for " + member.nickname); + continue; + } + const i = members.findIndex((m) => m.ign.toLowerCase() === name.toLowerCase()); + if (i === -1) { + console.log(`${member.nickname} isn't in the guild`); + continue; + } + + members[i].id = member.user.id; + members[i].nick = member.nickname; + members[i].tag = member.user.tag; + } + + bot.destroy(); + + for (const member of members) { + if (!member.id) { + console.log(`NOT_IN_DISCORD:${member.ign}`); + } + } + + console.log("WRITING", members); + fs.writeFileSync(path.join(__dirname, "members.json"), JSON.stringify(members, null, "\t")); +} + +bot.on("ready", run); +bot.login(process.env.BOT_TOKEN); diff --git a/src/bot.ts b/src/bot.ts index aea8e67..e38bd4d 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -5,7 +5,7 @@ import Client from "./structures/Client"; const bot = new Client(); -bot.loadCommands(path.join(__dirname, "commands")); +bot.registerCommands(path.join(__dirname, "commands"), true); bot.loadEvents(path.join(__dirname, "events")); bot.login(process.env.BOT_TOKEN); diff --git a/src/commands/8ball.ts b/src/commands/8ball.ts deleted file mode 100644 index bdf5ca7..0000000 --- a/src/commands/8ball.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Message, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; - -const responses = [ - "It is certain", - "It is decidedly so", - "Without a doubt", - "Yes definitely", - "You may rely on it", - "As I see it, yes", - "Most likely", - "Outlook good", - "Yes", - "Signs point to yes", - "Reply hazy try again", - "Ask again later", - "Better not tell you now", - "Cannot predict now", - "Concentrate and ask again", - "Don't count on it", - "My reply is no", - "My sources say no", - "Outlook not so good", - "Very doubtful", -]; - -module.exports = { - name: "8ball", - description: "Ask the Magic 8Ball a question", - category: "Fun", - usage: "8ball ", - run: async function run(client: Client, message: Message, args: Array) { - if (args.length === 0) { - message.channel.send("Please ask a question!"); - return; - } - const _8ball = new MessageEmbed().setTitle(`🎱 ${responses[Math.floor(Math.random() * responses.length + 0)]}`).setColor("#007FFF"); - message.channel.send(_8ball); - }, -}; diff --git a/src/commands/admin.ts b/src/commands/admin.ts deleted file mode 100644 index 9a1eff1..0000000 --- a/src/commands/admin.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Message, Role } from "discord.js"; -import Client from "../structures/Client"; -import adminmanualchallenge from "../handlers/admin/adminmanualchallenge"; -import admindisablecommand from "../handlers/admin/admindisablecommand"; -import adminenablecommand from "../handlers/admin/adminenablecommand"; -import adminsleep from "../handlers/admin/adminsleep"; -import admincommand from "../handlers/admin/admincommand"; -import adminfindsuggestor from "../handlers/admin/adminfindsuggestor"; -import adminsay from "../handlers/admin/adminsay"; - -import Roles from "../data/calm/roles.json"; -module.exports = { - name: "admin", - aliases: ["administrator"], - description: "For admin use only", - category: "Administration", - usage: "admin ", - run: async function run(client: Client, message: Message, args: Array) { - let srOfficerRole: Role; - if (message.guild.id === "501501905508237312") { - srOfficerRole = message.guild.roles.cache.find((r) => r.id === Roles.GENERAL.SR_OFFICER.id); - } else { - srOfficerRole = message.guild.roles.cache.find((r) => r.name === Roles.GENERAL.SR_OFFICER.name); - } - - // I know this ugly af but i was lazy - if (!srOfficerRole) { - if (message.guild.id !== "501501905508237312") { - if (!message.member.hasPermission(["ADMINISTRATOR"]) && message.member.roles.cache.find((r) => r.id === srOfficerRole.id) === undefined) { - return message.channel.send("Missing Permissions.\nRequired: **ADMINISTRATOR**"); - } - } else { - if (!message.member.hasPermission(["ADMINISTRATOR"]) && message.member.roles.cache.find((r) => r.id === srOfficerRole.id) === undefined) { - return message.channel.send("Missing Permissions.\nRequired: **ADMINISTRATOR**"); - } - } - } else { - if (!message.member.hasPermission("ADMINISTRATOR")) { - message.channel.send("Missing Permissions.\nRequired: **ADMINISTRATOR**"); - return; - } - } - - if (args.length === 0) { - message.channel.send("Invalid Arguments."); - return; - } - - args[0] = args[0].toLowerCase(); - if (args[0] === "manualchallenge") { - adminmanualchallenge.run(client, message, args); - return; - } else if (args[0] === "disablecommand") { - admindisablecommand.run(client, message, args); - return; - } else if (args[0] === "enablecommand") { - adminenablecommand.run(client, message, args); - return; - } else if (args[0] === "sleep") { - adminsleep.run(client, message, args); - } else if (args[0] === "command") { - admincommand.run(client, message, args); - } else if (args[0] === "findsuggestor") { - adminfindsuggestor.run(client, message, args); - } else if (args[0] === "say") { - adminsay.run(client, message, args); - } - }, -}; diff --git a/src/commands/admin/admin/command.ts b/src/commands/admin/admin/command.ts new file mode 100644 index 0000000..bf27c58 --- /dev/null +++ b/src/commands/admin/admin/command.ts @@ -0,0 +1,45 @@ +import axios, { AxiosError, AxiosResponse } from "axios"; +import { Message } from "discord.js" +import Client from "../../../structures/Client" +import { ICommand, RunCallback } from "../../../structures/Interfaces" +import Logger from "../../../utils/logger/Logger"; + +function CommandCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const command = args.join(" "); + console.log(command); + console.log(args); + + const ip = process.env.CALM_BOT_IP; + const port = process.env.CALM_BOT_PORT; + const key = process.env.CALM_BOT_KEY; + + axios + .post("http://" + ip + ":" + port + "/chat", { + message: command, + key: key, + }) + .then( + (response: AxiosResponse) => { + message.channel.send(`${response.status}: ${response.statusText}`); + }, + (error: AxiosError) => { + message.channel.send("There was an error making that request!"); + Logger.error(`Error making request to ${"http://" + ip + ":" + port + "/chat"}!!`); + Logger.error(error.code); + Logger.error(error.message) + } + ); + } + + return { + run: run, + settings: { + description: "Executes a command as calmbot", + usage: "admin command ", + minimumArgs: 1 + } + } +} + +export default CommandCommand(); \ No newline at end of file diff --git a/src/handlers/admin/admindisablecommand.ts b/src/commands/admin/admin/disablecommand.ts similarity index 51% rename from src/handlers/admin/admindisablecommand.ts rename to src/commands/admin/admin/disablecommand.ts index db1bcec..f706380 100644 --- a/src/handlers/admin/admindisablecommand.ts +++ b/src/commands/admin/admin/disablecommand.ts @@ -1,14 +1,14 @@ -import { Message } from "discord.js"; -import GuildSettings from "../../schemas/GuildSettings"; -import Client from "../../structures/Client"; -export default { - run: async function run(client: Client, message: Message, args: Array) { - if (args.length < 2) { - message.channel.send("Error! Invalid Arguments. Exmaple c!admin disablecommand (command-name)"); - return; - } +import { Message } from "discord.js" +import GuildSettings from "../../../schemas/GuildSettings"; +import Client from "../../../structures/Client" +import { ICommand, RunCallback } from "../../../structures/Interfaces" +import Logger from "../../../utils/logger/Logger"; - const cmdname = args[1].toLowerCase(); +function DisableCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const cmdname = args[0]?.toLowerCase(); + if (!cmdname || !message.guild) return; + if (cmdname === "admin") { message.channel.send("You can not disable this command!"); return; @@ -33,5 +33,17 @@ export default { guildSettings.disabledCommands.push(cmdname as string); await guildSettings.save(); message.channel.send(`Disabled \`${cmdname}\``); - }, -}; + Logger.info(`Disabled \`${cmdname}\``); + } + + return { + run: run, + settings: { + description: "Disables a command", + usage: "admin disablecommand ", + minimumArgs: 1 + } + } +} + +export default DisableCommand(); \ No newline at end of file diff --git a/src/handlers/admin/adminenablecommand.ts b/src/commands/admin/admin/enablecommand.ts similarity index 51% rename from src/handlers/admin/adminenablecommand.ts rename to src/commands/admin/admin/enablecommand.ts index f4af9a5..d14420f 100644 --- a/src/handlers/admin/adminenablecommand.ts +++ b/src/commands/admin/admin/enablecommand.ts @@ -1,14 +1,13 @@ -import { Message } from "discord.js"; -import GuildSettings from "../../schemas/GuildSettings"; -import Client from "../../structures/Client"; -export default { - run: async function run(client: Client, message: Message, args: Array) { - if (args.length < 2) { - message.channel.send("Error! Invalid Arguments. Exmaple c!admin enablecommand (command-name)"); - return; - } +import { Message } from "discord.js" +import GuildSettings from "../../../schemas/GuildSettings"; +import Client from "../../../structures/Client" +import { ICommand, RunCallback } from "../../../structures/Interfaces" +import Logger from "../../../utils/logger/Logger"; - const cmdname = args[1].toLowerCase(); +function EnableCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const cmdname = args[0]?.toLowerCase(); + if (!cmdname || !message.guild) return; if (!client.commands.has(cmdname)) { message.channel.send("Invalid Command: " + cmdname); @@ -33,5 +32,17 @@ export default { } await guildSettings.save(); message.channel.send(`Enabled \`${cmdname}\``); - }, -}; + Logger.info(`Enabled \`${cmdname}\``); + } + + return { + run: run, + settings: { + description: "Enables a command", + usage: "admin enablecommand ", + minimumArgs: 1 + } + } +} + +export default EnableCommand(); \ No newline at end of file diff --git a/src/commands/admin/admin/findsuggestor.ts b/src/commands/admin/admin/findsuggestor.ts new file mode 100644 index 0000000..814359d --- /dev/null +++ b/src/commands/admin/admin/findsuggestor.ts @@ -0,0 +1,44 @@ +import { Message, MessageEmbed } from "discord.js"; +import GuildSettings from "../../../schemas/GuildSettings"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; + +function FindSuggestorCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; + + const id = args[0]; + let guildSettings = await GuildSettings.findOne({ guildID: message.guild.id }); + if (guildSettings === null) { + const doc = new GuildSettings({ guildID: message.guild.id }); + await doc.save(); + guildSettings = doc; + } + + const suggestor = guildSettings.suggestions.find((element) => element.msgID === id); + if (!suggestor) { + message.channel.send("Could not find a suggestion with that msg id in our database."); + return; + } + + let embed = new MessageEmbed() + .setTitle("Suggestion id: " + id) + .addField("User", suggestor.suggestorTag) + .addField("Suggestion", suggestor.suggestion) + .addField("User ID", suggestor.suggestorID) + .setColor("#4287f5"); + + message.channel.send(embed); + }; + + return { + run: run, + settings: { + description: "Get data about a suggestion", + usage: "admin findsuggestor ", + minimumArgs: 1, + }, + }; +} + +export default FindSuggestorCommand(); diff --git a/src/commands/admin/admin/manualchallenge.ts b/src/commands/admin/admin/manualchallenge.ts new file mode 100644 index 0000000..8ca88fc --- /dev/null +++ b/src/commands/admin/admin/manualchallenge.ts @@ -0,0 +1,80 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import Challenges from "../../../data/calm/challenges/DecemberChallenges.json"; +import ChallengeParticipant from "../../../schemas/ChallengeParticipant"; +import Logger from "../../../utils/logger/Logger"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; + +function SayCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!args[0] || !args[1] || !args[2] || !message.guild) return; + + if (args[2].toLowerCase() !== "true" && args[2].toLowerCase() !== "false") { + message.channel.send("Invalid Arguments. ex c!manualchallenge (userid) (challengeid) (true/false) <-- HERE"); + return; + } + + let participant = await ChallengeParticipant.findOne({ discordID: args[0] }); + + if (participant === null) { + if (message.guild.members.cache.find((m) => m.id === args[0]) === undefined) { + message.channel.send("Could not find user in discord by id " + args[0]); + return; + } + const doc = new ChallengeParticipant({ discordID: args[0] }); + await doc.save(); + participant = await ChallengeParticipant.findOne({ discordID: args[0] }); + } + + if (getChallenge(args[1]) === undefined) { + message.channel.send(`Invalid Challenge ID: ${args[1]}`); + return true; + } + + if (args[2].toLowerCase() === "false") { + let participant = await ChallengeParticipant.findOne({ discordID: args[0] }); + if (!participant) return; + participant.completedChallenges.delete(args[1]); + await participant.save(); + message.channel.send(`Sucsess! Set ${args[0]}'s challenge #${args[1]} to __FALSE__`); + Logger.verbose(`Set ${args[0]}'s challenge #${args[1]} to FALSE`); + if (participant.completedChallenges.size === 0) { + let participant = await ChallengeParticipant.findOne({ discordID: args[0] }); + if (!participant) return; + await participant.delete(); + message.channel.send("Document in database for that user has been marked for deletion due to that challenge being the user's last TRUE challenge"); + Logger.verbose(`Deleting ${args[0]}'s challenge participant document as the last entry was manually deleted by ${message.author.id}`); + return; + } + return; + } else { + let participant = await ChallengeParticipant.findOne({ discordID: args[0] }); + if (!participant) return; + participant.completedChallenges.set(args[1], "true"); + await participant.save(); + message.channel.send(`Sucsess! Set ${args[0]}'s challenge #${args[1]} to __TRUE__`); + Logger.verbose(`Set ${args[0]}'s challenge #${args[1]} to TRUE`); + return; + } + }; + + return { + run: run, + settings: { + description: "Manually set a challenge", + usage: "admin challenge ", + minimumArgs: 3, + }, + }; +} + +export default SayCommand(); + +function getChallenge(id: string) { + for (const [, v] of Object.entries(Challenges)) { + if (v.id === id) { + return v; + } + } + return undefined; +} diff --git a/src/commands/admin/admin/say.ts b/src/commands/admin/admin/say.ts new file mode 100644 index 0000000..759257d --- /dev/null +++ b/src/commands/admin/admin/say.ts @@ -0,0 +1,38 @@ +import { Message, TextChannel, VoiceChannel } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import logger from "../../../utils/logger/Logger"; + +function SayCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!args[0]) return; + const id = args[0]; + const msg = args.slice(1, args.length).join(" "); + + const channel = message.guild?.channels.cache.get(id); + if (!channel) { + message.channel.send("Couldn't find that channel!"); + return; + } + + if (channel instanceof VoiceChannel) { + message.channel.send("Only can send messages in text channels!"); + } + + (channel as TextChannel).send(msg).catch((err) => { + message.channel.send("Error sending message in that channel!"); + logger.error(err); + }); + }; + + return { + run: run, + settings: { + description: "Say a command in a channel as calmbot", + usage: "admin say ", + minimumArgs: 2, + }, + }; +} + +export default SayCommand(); diff --git a/src/commands/admin/admin/sleep.ts b/src/commands/admin/admin/sleep.ts new file mode 100644 index 0000000..0f5c393 --- /dev/null +++ b/src/commands/admin/admin/sleep.ts @@ -0,0 +1,32 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; +import Logger from "../../../utils/logger/Logger"; + +function SleepCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; + let settings = await Database.getGuildSettings(message.guild.id); + settings.sleep = !settings.sleep; + await settings.save(); + + if (!settings.sleep) { + message.channel.send("Turned off sleep mode."); + Logger.info("Turned off sleep mode"); + } else { + message.channel.send("Turned on sleep mode. Run c!admin sleep to turn it off"); + Logger.info("Turned on sleep mode."); + } + }; + + return { + run: run, + settings: { + description: "Toggles sleepmode", + usage: "admin sleep", + }, + }; +} + +export default SleepCommand(); diff --git a/src/commands/admin/admin/subcommandSettings.ts b/src/commands/admin/admin/subcommandSettings.ts new file mode 100644 index 0000000..218ed76 --- /dev/null +++ b/src/commands/admin/admin/subcommandSettings.ts @@ -0,0 +1,8 @@ +import { ISubCommandSettings, PermissionsEnum } from "../../../structures/Interfaces"; + +const settings: ISubCommandSettings = { + guildOnly: true, + permissions: PermissionsEnum.ADMIN, +}; + +export default settings; diff --git a/src/commands/admin/approvesuggestion.ts b/src/commands/admin/approvesuggestion.ts new file mode 100644 index 0000000..02c1fe5 --- /dev/null +++ b/src/commands/admin/approvesuggestion.ts @@ -0,0 +1,54 @@ +import { Message, MessageEmbed, TextChannel } from "discord.js"; +import Client from "../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../structures/Interfaces"; +import Channels from "../../data/calm/channels.json"; +import logger from "../../utils/logger/Logger"; + +function ApproveSuggestionCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild || !args[0]) return; + + let suggestionChannel: TextChannel; + if (message.guild.id === "501501905508237312") { + suggestionChannel = message.guild.channels.cache.find((chan) => chan.id === Channels.SUGGESTIONS.SUGGESTIONS.id) as TextChannel; + } else { + suggestionChannel = message.guild.channels.cache.find((chan) => chan.name === Channels.SUGGESTIONS.SUGGESTIONS.name) as TextChannel; + } + + suggestionChannel.messages + .fetch(args[0]) + .then((approvedSuggestion) => { + let suggestion = approvedSuggestion.embeds[0]?.description; + let approvedEmbed = new MessageEmbed() + .setFooter(approvedSuggestion.embeds[0]?.footer) + .setColor("#57ff73") + .setTitle("Approved Suggestion:") + .setDescription(suggestion) + .addField("Approved By: ", message.author.username + "#" + message.author.discriminator) + .setTimestamp(); + + // Edits suggestion to indicate approval, and removes all reactions ("✅", "❎") + approvedSuggestion.edit(approvedEmbed).catch((err) => { + logger.error(err); + }); + approvedSuggestion.reactions.removeAll(); + + message.reply("Approved the suggestion."); + }) + .catch(() => { + message.channel.send("Could not find the suggestion! Did you use the right ID?"); + }); + }; + return { + run: run, + settings: { + description: "Approve a suggestion", + usage: "approvesuggestion ", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN, + guildOnly: true, + }, + }; +} + +export default ApproveSuggestionCommand(); diff --git a/src/commands/admin/denysuggestion.ts b/src/commands/admin/denysuggestion.ts new file mode 100644 index 0000000..f9fd040 --- /dev/null +++ b/src/commands/admin/denysuggestion.ts @@ -0,0 +1,56 @@ +import { Message, MessageEmbed, TextChannel } from "discord.js"; +import Client from "../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../structures/Interfaces"; +import Channels from "../../data/calm/channels.json"; +import logger from "../../utils/logger/Logger"; + +function ApproveSuggestionCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild || !args[0]) return; + + let suggestionChannel: TextChannel; + if (message.guild.id === "501501905508237312") { + suggestionChannel = message.guild.channels.cache.find((chan) => chan.id === Channels.SUGGESTIONS.SUGGESTIONS.id) as TextChannel; + } else { + suggestionChannel = message.guild.channels.cache.find((chan) => chan.name === Channels.SUGGESTIONS.SUGGESTIONS.name) as TextChannel; + } + + if (!suggestionChannel) return; + suggestionChannel.messages + .fetch(args[0]) + .then((deniedSuggestion) => { + let suggestion = deniedSuggestion.embeds[0]?.description; + + let deniedEmbed = new MessageEmbed() + .setFooter(deniedSuggestion.embeds[0]?.footer) + .setColor("#f74a4a") + .setTitle("Denied Suggestion:") + .setDescription(suggestion) + .addField("Denied By: ", message.author.username + "#" + message.author.discriminator) + .setTimestamp(); + + // Edits suggestion to indicate denial, and removes all reactions ("✅", "❎") + deniedSuggestion.edit(deniedEmbed).catch((err) => { + logger.error(err); + }); + deniedSuggestion.reactions.removeAll(); + + message.reply("Denied the suggestion."); + }) + .catch(() => { + message.channel.send("Could not find the suggestion! Did you use the right ID?"); + }); + }; + return { + run: run, + settings: { + description: "Denies a suggestion", + usage: "denysuggestion ", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN, + guildOnly: true, + }, + }; +} + +export default ApproveSuggestionCommand(); diff --git a/src/commands/lockdown.ts b/src/commands/admin/lockdown.ts similarity index 62% rename from src/commands/lockdown.ts rename to src/commands/admin/lockdown.ts index fdd075c..847cf99 100644 --- a/src/commands/lockdown.ts +++ b/src/commands/admin/lockdown.ts @@ -1,15 +1,13 @@ import { Message, Role, TextChannel } from "discord.js"; -import Client from "../structures/Client"; -import channels from "../data/calm/channels.json"; -import roles from "../data/calm/roles.json"; +import Client from "../../structures/Client"; +import channels from "../../data/calm/channels.json"; +import roles from "../../data/calm/roles.json"; +import logger from "../../utils/logger/Logger"; +import { ICommand, PermissionsEnum, RunCallback } from "../../structures/Interfaces"; -module.exports = { - name: "lockdown", - description: "Locks down the server", - category: "Administration", - permissions: ["ADMINISTRATOR"], - usage: "lockdown [full]", - run: async function run(client: Client, message: Message, args: Array) { +function LockdownCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; // Check if full lockdown, or just a normal one. let fullLock: boolean; if (args[0] == "full") { @@ -21,7 +19,7 @@ module.exports = { } // Gets the Guild Member Role, Nitro booster role, and the news channel. - let guildMemberRole: Role, nitroBoosterRole: Role, newsChannel: TextChannel; + let guildMemberRole: Role | undefined, nitroBoosterRole: Role | undefined, newsChannel: TextChannel; if (message.guild.id === "501501905508237312") { guildMemberRole = message.guild.roles.cache.get(roles.GENERAL.CALMIES.id); nitroBoosterRole = message.guild.roles.cache.get(roles.GENERAL.NITRO_BOOSTER.id); @@ -32,19 +30,18 @@ module.exports = { newsChannel = message.guild.channels.cache.find((chan) => chan.name === channels.IMPORTANT.NEWS.name) as TextChannel; } - if(guildMemberRole === undefined || nitroBoosterRole === undefined) { - return message.channel.send("Unable to find one or more roles. Please make sure the Guild Member role exists and the Nitro Booster role exists") + if (!guildMemberRole || !nitroBoosterRole) { + message.channel.send("Unable to find one or more roles. Please make sure the Guild Member role exists and the Nitro Booster role exists"); + return; } if (newsChannel) { - await newsChannel.send( - `**Attention,** \n<@${message.author.id}> has **initiated** a _server lockdown_. \nYou are **not muted**, but will not be able to talk until a server admin does \`c!unlock\`` - ); + await newsChannel.send(`**Attention,** \n<@${message.author.id}> has **initiated** a _server lockdown_. \nYou are **not muted**, but will not be able to talk until a server admin does \`c!unlock\``); } - for (const categoryName in channels) { - for (const channelName in channels[categoryName]) { - const channelProperties = channels[categoryName][channelName]; + for (const [k, v] of Object.entries(channels)) { + for (const [, channelProps] of Object.entries(v)) { + const channelProperties: any = channelProps; let channel: TextChannel; if (message.guild.id === "501501905508237312") { @@ -54,11 +51,11 @@ module.exports = { } if (!channel) { - console.log(`Channel ${channelProperties.name} wasn't found`); + logger.verbose(`Channel ${channelProperties.name} wasn't found`); } else if (channelProperties.membersOnly && fullLock) { channel.updateOverwrite(guildMemberRole, { SEND_MESSAGES: false, ADD_REACTIONS: false }); } else if (channelProperties.public && fullLock) { - switch (categoryName) { + switch (k) { case "UPON_JOINING": fullLockOverwrite(channel, guildMemberRole, nitroBoosterRole); break; @@ -82,7 +79,7 @@ module.exports = { break; } // End Switch } else if (channelProperties.public && !fullLock) { - switch (categoryName) { + switch (k) { case "UPON_JOINING": normalLockOverwrite(channel, guildMemberRole, nitroBoosterRole); break; @@ -108,18 +105,30 @@ module.exports = { } } } - }, -}; -// Functions to update channel permissions (overwrites) -function normalLockOverwrite(channel: TextChannel, guildMemberRole: Role, nitroBoosterRole: Role) { - channel.updateOverwrite(channel.guild.roles.everyone, { SEND_MESSAGES: false, ADD_REACTIONS: false }); - channel.updateOverwrite(guildMemberRole, { SEND_MESSAGES: true }); - channel.updateOverwrite(nitroBoosterRole, { SEND_MESSAGES: true }); -} + // Functions to update channel permissions (overwrites) + function normalLockOverwrite(channel: TextChannel, guildMemberRole: Role, nitroBoosterRole: Role) { + channel.updateOverwrite(channel.guild.roles.everyone, { SEND_MESSAGES: false, ADD_REACTIONS: false }); + channel.updateOverwrite(guildMemberRole, { SEND_MESSAGES: true }); + channel.updateOverwrite(nitroBoosterRole, { SEND_MESSAGES: true }); + } -function fullLockOverwrite(channel: TextChannel, guildMemberRole: Role, nitroBoosterRole: Role) { - channel.updateOverwrite(channel.guild.roles.everyone, { SEND_MESSAGES: false, ADD_REACTIONS: false }); - channel.updateOverwrite(guildMemberRole, { SEND_MESSAGES: false, ADD_REACTIONS: false }); - channel.updateOverwrite(nitroBoosterRole, { SEND_MESSAGES: false, ADD_REACTIONS: false }); + function fullLockOverwrite(channel: TextChannel, guildMemberRole: Role, nitroBoosterRole: Role) { + channel.updateOverwrite(channel.guild.roles.everyone, { SEND_MESSAGES: false, ADD_REACTIONS: false }); + channel.updateOverwrite(guildMemberRole, { SEND_MESSAGES: false, ADD_REACTIONS: false }); + channel.updateOverwrite(nitroBoosterRole, { SEND_MESSAGES: false, ADD_REACTIONS: false }); + } + }; + + return { + run: run, + settings: { + description: "Locksdown the server", + usage: "lockdown [full]", + guildOnly: true, + permissions: PermissionsEnum.ADMIN, + }, + }; } + +export default LockdownCommand(); diff --git a/src/commands/admin/tags/create.ts b/src/commands/admin/tags/create.ts new file mode 100644 index 0000000..cde1def --- /dev/null +++ b/src/commands/admin/tags/create.ts @@ -0,0 +1,44 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function CreateCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!!.id); + + const tagname = args[0]!!.toLowerCase(); + + if (client.commands.get(tagname)) { + message.channel.send("There is a command with that name!"); + return; + } + + if (settings.tags.find((tag) => tag.name === tagname)) { + message.channel.send(`A tag by that name already exists! Please do c!tags edit ${tagname} or c!tags delete ${tagname}`); + return; + } + + client.addPromptListener(`Please enter the text for ${tagname}`, message.channel, message.author.id, 15, (response) => { + settings.tags.push({ + name: tagname, + response: response.content, + }); + settings.save(); + + response.channel.send(`Added ${tagname}, do ${client.prefix + tagname} to see it!`); + }); + }; + + return { + run: run, + settings: { + description: "Creates a new tag", + usage: "tags create ", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN, + }, + }; +} + +export default CreateCommand(); diff --git a/src/commands/admin/tags/delete.ts b/src/commands/admin/tags/delete.ts new file mode 100644 index 0000000..2ce0823 --- /dev/null +++ b/src/commands/admin/tags/delete.ts @@ -0,0 +1,33 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function DeleteCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!!.id); + + const tagname = args[0]!!.toLowerCase(); + if (!settings.tags.find((tag) => tag.name === tagname)) { + message.channel.send(`A tag by that name doesn't exists! Please do c!tags create ${tagname}`); + return; + } + + const tag = settings.tags.find(t => t.name === tagname); + settings.tags = settings.tags.filter((ele) => ele !== tag); + settings.save(); + message.channel.send(`Deleted ${tagname}`); + }; + + return { + run: run, + settings: { + description: "Deletes a tag", + usage: "tags delete ", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN, + }, + }; +} + +export default DeleteCommand(); diff --git a/src/commands/admin/tags/edit.ts b/src/commands/admin/tags/edit.ts new file mode 100644 index 0000000..f40fff3 --- /dev/null +++ b/src/commands/admin/tags/edit.ts @@ -0,0 +1,38 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function EditCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!!.id); + + const tagname = args[0]!!.toLowerCase(); + if (!settings.tags.find((tag) => tag.name === tagname)) { + message.channel.send(`A tag by that name doesn't exists! Please do c!tags create ${tagname}`); + return; + } + + client.addPromptListener(`Please enter the new text for ${tagname}`, message.channel, message.author.id, 15, (response) => { + settings.tags = settings.tags.filter((ele) => ele.name !== tagname); + settings.tags.push({ + name: tagname, + response: response.content, + }); + settings.save(); + message.channel.send(`Edited ${tagname}`); + }); + }; + + return { + run: run, + settings: { + description: "Edits an already existing tag", + usage: "tags edit ", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN, + }, + }; +} + +export default EditCommand(); diff --git a/src/commands/admin/tags/list.ts b/src/commands/admin/tags/list.ts new file mode 100644 index 0000000..a0f3b1e --- /dev/null +++ b/src/commands/admin/tags/list.ts @@ -0,0 +1,31 @@ +import { Message, MessageEmbed } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function ListCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!!.id); + + if (settings.tags.length === 0) { + message.channel.send(`No tags exist. Do ${client.prefix}tags create to create one!`); + return; + } + + let desc = "```\n"; + settings.tags.forEach((tag) => (desc += client.prefix + tag.name + "\n")); + desc += "```"; + const embed = new MessageEmbed().setTitle("Tag list").setDescription(desc); + message.channel.send(embed); + }; + + return { + run: run, + settings: { + description: "Lists all tags", + usage: "tags list", + }, + }; +} + +export default ListCommand(); diff --git a/src/commands/admin/tags/subcommandSettings.ts b/src/commands/admin/tags/subcommandSettings.ts new file mode 100644 index 0000000..fc41b9a --- /dev/null +++ b/src/commands/admin/tags/subcommandSettings.ts @@ -0,0 +1,7 @@ +import { ISubCommandSettings } from "../../../structures/Interfaces"; + +const settings: ISubCommandSettings = { + guildOnly: true, +}; + +export default settings; diff --git a/src/commands/unlock.ts b/src/commands/admin/unlock.ts similarity index 80% rename from src/commands/unlock.ts rename to src/commands/admin/unlock.ts index a06bdd9..7dacc93 100644 --- a/src/commands/unlock.ts +++ b/src/commands/admin/unlock.ts @@ -1,15 +1,12 @@ import { Message, Role, TextChannel } from "discord.js"; -import Client from "../structures/Client"; -import channels from "../data/calm/channels.json"; +import Client from "../../structures/Client"; +import channels from "../../data/calm/channels.json"; +import logger from "../../utils/logger/Logger"; +import { ICommand, PermissionsEnum, RunCallback } from "../../structures/Interfaces"; -module.exports = { - name: "unlock", - description: "Unlocks the server", - category: "Administration", - permissions: ["ADMINISTRATOR"], - usage: "unlock [full]", - run: async function run(client: Client, message: Message, args: Array) { - // Check if full lockdown, or just a normal one. +function UnlockCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; let fullLock: boolean; if (args[0] == "full") { message.channel.send("Unlocking Public + Guild Channels!"); @@ -20,7 +17,7 @@ module.exports = { } // Gets the Guild Member Role, Nitro booster role, and the news channel. - let guildMemberRole: Role, nitroBoosterRole: Role, newsChannel: TextChannel; + let guildMemberRole: Role | undefined, nitroBoosterRole: Role | undefined, newsChannel: TextChannel; if (message.guild.id === "501501905508237312") { guildMemberRole = message.guild.roles.cache.get("501504002853306388"); nitroBoosterRole = message.guild.roles.cache.get("501504002853306388"); @@ -31,13 +28,19 @@ module.exports = { newsChannel = message.guild.channels.cache.find((chan) => chan.name === channels.IMPORTANT.NEWS.name) as TextChannel; } + if (!guildMemberRole || !nitroBoosterRole) { + message.channel.send("Unable to find one or more roles. Please make sure the Guild Member role exists and the Nitro Booster role exists"); + return; + } + + if (newsChannel) { newsChannel.send(`**Attention,** \n<@${message.author.id}> has **unlocked** the server! \nYou are **now free to chat**!`); } - for (const categoryName in channels) { - for (const channelName in channels[categoryName]) { - const channelProperties = channels[categoryName][channelName]; + for (const [k, v] of Object.entries(channels)) { + for (const [, channelProps] of Object.entries(v)) { + const channelProperties: any = channelProps; let channel: TextChannel; if (message.guild.id === "501501905508237312") { @@ -47,11 +50,11 @@ module.exports = { } if (!channel) { - console.log(`Channel ${channelProperties.name} wasn't found`); + logger.verbose(`Channel ${channelProperties.name} wasn't found`); } else if (channelProperties.membersOnly && fullLock) { channel.updateOverwrite(guildMemberRole, { SEND_MESSAGES: true, ADD_REACTIONS: null }); } else if (channelProperties.public && fullLock) { - switch (categoryName) { + switch (k) { case "UPON_JOINING": fullUnlockOverwrite(channel, guildMemberRole, nitroBoosterRole); break; @@ -77,7 +80,7 @@ module.exports = { break; } } else if (channelProperties.public && !fullLock) { - switch (categoryName) { + switch (k) { case "UPON_JOINING": normalUnlockOverwrite(channel, guildMemberRole, nitroBoosterRole); break; @@ -105,8 +108,19 @@ module.exports = { } } } - }, -}; + }; + + return { + run: run, + settings: { + description: "Unlocks the server", + usage: "unlock [full]", + guildOnly: true, + permissions: PermissionsEnum.ADMIN, + }, + }; +} + // Functions to update channel permissions (overwrites) function normalUnlockOverwrite(channel: TextChannel, guildMemberRole: Role, nitroBoosterRole: Role) { channel.updateOverwrite(channel.guild.roles.everyone, { SEND_MESSAGES: null, ADD_REACTIONS: null }); @@ -119,3 +133,6 @@ function fullUnlockOverwrite(channel: TextChannel, guildMemberRole: Role, nitroB channel.updateOverwrite(guildMemberRole, { SEND_MESSAGES: true, ADD_REACTIONS: true }); channel.updateOverwrite(nitroBoosterRole, { SEND_MESSAGES: true, ADD_REACTIONS: true }); } + + +export default UnlockCommand(); diff --git a/src/commands/application.ts b/src/commands/application.ts deleted file mode 100644 index f78b899..0000000 --- a/src/commands/application.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Message, TextChannel } from "discord.js"; -import Client from "../structures/Client"; -import channels from "../data/calm/channels.json"; - -module.exports = { - name: "application", - aliases: ["app", "joinguild"], - description: "Sends the link to join CalmGuild!", - category: "Information", - usage: "application", - run: async function run(client: Client, message: Message) { - let infoChannel: TextChannel; - if (message.guild.id === "501501905508237312") { - infoChannel = message.guild.channels.cache.find((chan) => chan.id === channels.UPON_JOINING.INFO.id) as TextChannel; - } else { - infoChannel = message.guild.channels.cache.find((chan) => chan.name === channels.UPON_JOINING.INFO.name) as TextChannel; - } - if (!infoChannel) { - return message.reply("we could not find the info channel!"); - } - const send = - ":green_circle: STATUS: OPEN :green_circle: \n" + - `If you need the requirements, please head to ${infoChannel} as they are stated there\n\n` + - "However, they are also on our application below :)\n\n" + - "**APPLICATION:** "; - - message.channel.send(send); - }, -}; diff --git a/src/commands/approvesuggestion.ts b/src/commands/approvesuggestion.ts deleted file mode 100644 index f43529f..0000000 --- a/src/commands/approvesuggestion.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Message, TextChannel, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; -import Channels from "../data/calm/channels.json"; - -module.exports = { - name: "approvesuggestion", - description: "Approve a suggestion!", - category: "Administration", - permissions: ["ADMINISTRATOR"], - usage: "approvesuggestion ", - run: async function run(client: Client, message: Message, args: Array) { - // Basic checks: no args provided; suggestions channel - - if (args.length === 0) return message.channel.send("Missing Arguments.\n**Usage:** `c!approvesuggestion [message id]`"); - - let suggestionChannel: TextChannel; - if (message.guild.id === "501501905508237312") { - suggestionChannel = message.guild.channels.cache.find((chan) => chan.id === Channels.SUGGESTIONS.SUGGESTIONS.id) as TextChannel; - } else { - suggestionChannel = message.guild.channels.cache.find((chan) => chan.name === Channels.SUGGESTIONS.SUGGESTIONS.name) as TextChannel; - } - - // Fetches suggestion from ID provided by user, then grabs suggestion from embed description - var suggestion: string, approvedSuggestion: Message; - await suggestionChannel.messages.fetch({ around: args[0], limit: 1 }).then((msg) => { - approvedSuggestion = msg.first(); - - // Checks if its in the correct channel / if it was made by the bot / if its actually an embed - if (!approvedSuggestion || approvedSuggestion.author !== client.user || !approvedSuggestion.embeds[0]) { - return message.channel.send("Please use the `Message ID` from the suggestion in <#" + suggestionChannel.id + ">"); - } - - suggestion = approvedSuggestion.embeds[0].description; - let suggestionAuthor = approvedSuggestion.embeds[0].footer.text.split(` • CalmBot v${client.version}`, 1); - let suggestorAvatar = approvedSuggestion.embeds[0].footer.iconURL; - let approvedEmbed = new MessageEmbed() - .setFooter(`${suggestionAuthor} • CalmBot v${client.version}`, suggestorAvatar) - .setColor("#57ff73") - .setTitle("Approved Suggestion:") - .setDescription(suggestion) - .addField("Approved By: ", message.author.username + "#" + message.author.discriminator) - .setTimestamp(); - - // Edits suggestion to indicate approval, and removes all reactions ("✅", "❎") - approvedSuggestion.edit(approvedEmbed); - approvedSuggestion.reactions.removeAll(); - - message.reply("Approved the suggestion."); - }); - }, -}; diff --git a/src/commands/arlo.ts b/src/commands/arlo.ts deleted file mode 100644 index 351b083..0000000 --- a/src/commands/arlo.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -import urls from "../data/img/arlo.json"; - -module.exports = { - name: "arlo", - description: "Sends a picture of ARLO!!", - category: "Images", - usage: "arlo", - run: async function run(client: Client, message: Message, args: Array) { - const img = urls[Math.floor(Math.random() * urls.length)]; - message.channel.send(img); - }, -}; diff --git a/src/commands/birthday.ts b/src/commands/birthday.ts deleted file mode 100644 index fe7ebfe..0000000 --- a/src/commands/birthday.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Message, TextChannel } from "discord.js"; -import Client from "../structures/Client"; -import channels from "../data/calm/channels.json"; - -module.exports = { - name: "birthday", - aliases: ["bb", "birthdays"], - description: "Explains how to set your birthday", - category: "Information", - usage: "birthday", - run: async function run(client: Client, message: Message) { - let commandsChannel: TextChannel; - - if (message.guild.id === "501501905508237312") { - commandsChannel = message.guild.channels.cache.find((chan) => chan.id === channels.COMMUNITY.COMMANDS.id) as TextChannel; - } else { - commandsChannel = message.guild.channels.cache.find((chan) => chan.name === channels.COMMUNITY.COMMANDS.name) as TextChannel; - } - if (!commandsChannel) { - return message.reply("we could not find the commands channel!"); - } - - message.channel.send( - `Want a special **Birthday Nerd** role when it's your birthday?????? AWESOME! FOLLOW THE INSTRUCTIONS BELOW!\n\nGo to ${commandsChannel} and execute the command below with your birthday:\n\nCommand: \`bb.set (date) [timezone]\`\n- Example: *bb.set oct-21 es*\n\nFor the timezone, click this link and copy the timezone given EXACTLY how it is: ` - ); - }, -}; diff --git a/src/commands/botinfo.ts b/src/commands/botinfo.ts deleted file mode 100644 index c8a2992..0000000 --- a/src/commands/botinfo.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Message, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; - -module.exports = { - name: "botinfo", - aliases: ["credits", "github", "source"], - description: "Information about CalmBot", - category: "Information", - usage: "botinfo", - run: async function run(client: Client, message: Message) { - // Uptime Calculations: - let daysUp = Math.floor(client.uptime / 86400000); - let hoursUp = Math.floor(client.uptime / 3600000) % 24; - let minutesUp = Math.floor(client.uptime / 60000) % 60; - let secondsUp = Math.floor(client.uptime / 1000) % 60; - - const botInfoEmbed = new MessageEmbed() - .setDescription("CalmBot, an *open-source* Discord bot built for the [Calm Community](https://discord.gg/calm)") - .setColor("#0083dd") - .setThumbnail(client.user.avatarURL()) - .setAuthor("CalmBot Info and Credits", client.user.avatarURL(), "https://github.com/CalmGuild/CalmBot") - .addField("ℹ Version:", `v${client.version}`, false) - .addField( - "📝 Credits:", - "Bot by **[Miqhtiedev](https://github.com/Miqhtiedev)** with contributions from **[SkillBeatsAll](https://github.com/SkillBeatsAll)** and [others](https://github.com/CalmGuild/CalmBot/graphs/contributors)", - false - ) - .addField("💻 Source Code:", "[CalmGuild GitHub Repo](https://github.com/CalmGuild/CalmBot)", false) - .addField("Uptime:", `${daysUp} days, ${hoursUp} hours, ${minutesUp} minutes and ${secondsUp} seconds`, true); - - message.channel.send(botInfoEmbed); - }, -}; diff --git a/src/commands/bunny.ts b/src/commands/bunny.ts deleted file mode 100644 index e6abbea..0000000 --- a/src/commands/bunny.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -import urls from "../data/img/bunny.json"; - -module.exports = { - name: "bunny", - description: "Sends a bunny picture!", - category: "Images", - usage: "bunny", - run: async function run(client: Client, message: Message, args: Array) { - const img = urls[Math.floor(Math.random() * urls.length)]; - message.channel.send(img); - }, -}; diff --git a/src/commands/cat.ts b/src/commands/cat.ts deleted file mode 100644 index 47848d5..0000000 --- a/src/commands/cat.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -import urls from "../data/img/cat.json"; - -module.exports = { - name: "cat", - description: "Sends a cat picture!", - category: "Images", - usage: "cat", - run: async function run(client: Client, message: Message, args: Array) { - const img = urls[Math.floor(Math.random() * urls.length)]; - message.channel.send(img); - }, -}; diff --git a/src/commands/denysuggestion.ts b/src/commands/denysuggestion.ts deleted file mode 100644 index d82e057..0000000 --- a/src/commands/denysuggestion.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Message, TextChannel, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; -import Channels from "../data/calm/channels.json"; - -module.exports = { - name: "denysuggestion", - description: "Denies a suggestion!", - category: "Administration", - permissions: ["ADMINISTRATOR"], - usage: "denysuggestion ", - run: async function run(client: Client, message: Message, args: Array) { - // Basic checks: Adminstrator Permission; no args provided; suggestions channel - if (!message.member.hasPermission("ADMINISTRATOR")) { - message.channel.send("Missing Permissions.\nRequired: **ADMINISTRATOR**"); - return; - } - - if (args.length === 0) return message.channel.send("Missing Arguments.\n**Usage:** `c!denysuggestion [message id]`"); - - let suggestionChannel: TextChannel; - if (message.guild.id === "501501905508237312") { - suggestionChannel = message.guild.channels.cache.find((chan) => chan.id === Channels.SUGGESTIONS.SUGGESTIONS.id) as TextChannel; - } else { - suggestionChannel = message.guild.channels.cache.find((chan) => chan.name === Channels.SUGGESTIONS.SUGGESTIONS.name) as TextChannel; - } - - // Fetches suggestion from ID provided by user, then grabs suggestion from embed description - var suggestion: string, deniedSuggestion: Message; - await suggestionChannel.messages.fetch({ around: args[0], limit: 1 }).then((msg) => { - deniedSuggestion = msg.first(); - - // Checks if its in the correct channel / if it was made by the bot / if its actually an embed - if (!deniedSuggestion || deniedSuggestion.author !== client.user || !deniedSuggestion.embeds[0]) { - return message.channel.send("Please use the `Message ID` from the suggestion in <#" + suggestionChannel.id + ">"); - } - - suggestion = deniedSuggestion.embeds[0].description; - - let suggestionAuthor = deniedSuggestion.embeds[0].footer.text.split(` • CalmBot v${client.version}`, 1); - let suggestorAvatar = deniedSuggestion.embeds[0].footer.iconURL; - let deniedEmbed = new MessageEmbed() - .setFooter(`${suggestionAuthor} • CalmBot v${client.version}`, suggestorAvatar) - .setColor("#f74a4a") - .setTitle("Denied Suggestion:") - .setDescription(suggestion) - .addField("Denied By: ", message.author.username + "#" + message.author.discriminator) - .setTimestamp(); - - // Edits suggestion to indicate denial, and removes all reactions ("✅", "❎") - deniedSuggestion.edit(deniedEmbed); - deniedSuggestion.reactions.removeAll(); - - message.reply("Denied the suggestion."); - }); - }, -}; diff --git a/src/commands/dog.ts b/src/commands/dog.ts deleted file mode 100644 index a6d9e8f..0000000 --- a/src/commands/dog.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -import urls from "../data/img/dog.json"; - -module.exports = { - name: "dog", - description: "Sends a dog picture!", - category: "Images", - usage: "dog", - run: async function run(client: Client, message: Message, args: Array) { - const img = urls[Math.floor(Math.random() * urls.length)]; - message.channel.send(img); - }, -}; diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts new file mode 100644 index 0000000..ef308c7 --- /dev/null +++ b/src/commands/fun/8ball.ts @@ -0,0 +1,52 @@ +import { Message, MessageEmbed } from "discord.js"; +import Client from "../../structures/Client"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +const responses = [ + "It is certain", + "Processing... I don't care", + "It is decidedly so", + "Without a doubt", + "Yes definitely", + "Error: 400. Question too stupid", + "You may rely on it", + "Im not answering that", + "As I see it, yes", + "Most likely", + "Outlook good", + "Yes", + "No", + "Signs point to yes", + "Reply hazy try again", + "Ask again later", + "Better not tell you now", + "Cannot predict now", + "This is a perfect discord bot and I can not waste my time with that question", + "Concentrate and ask again", + "Don't count on it", + "My reply is no", + "My sources say no", + "Outlook not so good", + "what??", + "Very doubtful", + "I don't feel like answering that, continue on with your day", +]; + +function EightBallCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const _8ball = new MessageEmbed().setTitle(`🎱 ${responses[Math.floor(Math.random() * responses.length + 0)]}`).setColor("#007FFF"); + message.channel.send(_8ball); + }; + + return { + run: run, + settings: { + description: "Ask the magical 8ball a question", + usage: "8ball ", + guildOnly: false, + minimumArgs: 1, + }, + }; +} + +export default EightBallCommand(); diff --git a/src/commands/fun/atsomeone.ts b/src/commands/fun/atsomeone.ts new file mode 100644 index 0000000..e7ddc63 --- /dev/null +++ b/src/commands/fun/atsomeone.ts @@ -0,0 +1,39 @@ +import { Message } from "discord.js"; +import Client from "../../structures/Client"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; +import logger from "../../utils/logger/Logger"; + +let cooldowns: string[] = [] + +function AtSomeoneCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + + if (cooldowns.includes(message.author.id)) { + message.channel.send("This command is on cooldown for one minute!"); + return; + } + + cooldowns.push(message.author.id); + setTimeout(() => { + cooldowns = cooldowns.filter((ele) => ele !== message.author.id); + }, 60000); + + message.guild?.members + .fetch() + .then((members) => { + message.channel.send(`Ping! ${members.random().user}`); + }) + .catch((err) => logger.error(err)); + }; + + return { + run: run, + settings: { + description: "Tag a random user", + usage: "atsomeone", + guildOnly: true, + }, + }; +} + +export default AtSomeoneCommand(); diff --git a/src/commands/help.ts b/src/commands/help.ts deleted file mode 100644 index bbe0bfc..0000000 --- a/src/commands/help.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Message, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; - -module.exports = { - name: "help", - aliases: ["h", "commands"], - category: "Information", - description: "Displays all the commands available", - usage: "help [command]", - run: async (client: Client, message: Message, args: Array) => { - try { - if (args[0]) { - return getCommand(client, message, args[0]); - } - if (!args[0]) { - return getAllCommands(client, message); - } - - function getAllCommands(client: Client, message: Message) { - const embed = new MessageEmbed().setAuthor(`Commands in ${message.guild.name}`, message.guild.iconURL()).setColor("#007FFF").setTimestamp(); - let categories = [...new Set(client.commands.map((cmd: any) => cmd.category))]; - for (const categoryID of categories) { - const category = client.commands.filter((cmd: any) => cmd.category === categoryID); - if (categoryID == "Administration") { - var categoryIcon = ":gear:"; - } - if (categoryID == "Moderation") { - var categoryIcon = ":hammer:"; - } - if (categoryID == "Fun") { - var categoryIcon = ":game_die:"; - } - if (categoryID == "Utility") { - var categoryIcon = ":toolbox:"; - } - if (categoryID == "Information") { - var categoryIcon = ":information_source:"; - } - if (categoryID == "Images") { - var categoryIcon = ":camera:"; - } - if (!categoryID) { - var categoryIcon = ":grey_question:"; - } - embed.addField(`${categoryIcon} ${categoryID} (${category.size})`, category.map((cmd: any) => `${cmd.name}`).join(" **|** ")); - } - embed.addField(":question: Command Information", `${client.prefix}help `); - embed.setFooter(`CalmBot v${client.version}` + " • " + `${client.commands.size}` + " Commands", message.author.displayAvatarURL()); - return message.channel.send(embed); - } - - function getCommand(client: Client, message: Message, input) { - const cmd: any = client.commands.get(input.toLowerCase()) || client.aliases.get(input.toLowerCase()); - if (!cmd) { - return message.channel.send(`No information found for command \`${client.prefix}${input.toLowerCase()}\`!`); - } else { - let aliasList: Object; - if (!cmd.aliases) { - aliasList = "None"; - } else { - aliasList = cmd.aliases.toString().split(",").join(", "); - } - let permissions = cmd.permissions ? `Permissions: \`${cmd.permissions.join(", ")}\`` : ""; - const helpEmbed = new MessageEmbed() - .setTitle(`:question: Help - ${cmd.name} command`) - .setColor("#007FFF") - .setTimestamp() - .setDescription("Category: `" + cmd.category + "`\n Description: `" + cmd.description + "`\n Usage: `" + client.prefix + cmd.usage + "`\n Aliases: `" + aliasList + `\`\n ${permissions}`) - .setFooter(`Syntax: <> = required, [] = optional • CalmBot v${client.version}`, message.author.displayAvatarURL()); - message.channel.send(helpEmbed); - } - } - } catch (err) { - message.channel.send("Sorry, we have encountered an error :sob:"); - console.log(err); - } - }, -}; diff --git a/src/commands/images/arlo.ts b/src/commands/images/arlo.ts new file mode 100644 index 0000000..7089a9e --- /dev/null +++ b/src/commands/images/arlo.ts @@ -0,0 +1,25 @@ +import { Message } from "discord.js"; +import Client from "../../structures/Client"; +import urls from "../../data/img/arlo.json"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +function ArloCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const img = urls[Math.floor(Math.random() * urls.length)]; + if (!img) { + message.channel.send("We could not find a picture. Please report this to a developer."); + return; + } + message.channel.send(img); + }; + + return { + run: run, + settings: { + description: "Sends a picture of arlo", + usage: "arlo", + }, + }; +} + +export default ArloCommand(); diff --git a/src/commands/images/cat.ts b/src/commands/images/cat.ts new file mode 100644 index 0000000..fdf1e7f --- /dev/null +++ b/src/commands/images/cat.ts @@ -0,0 +1,27 @@ +import { Message } from "discord.js"; +import Client from "../../structures/Client"; + +import urls from "../../data/img/cat.json"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +function CatCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const img = urls[Math.floor(Math.random() * urls.length)]; + if (!img) { + message.channel.send("We could not find a picture. Please report this to a developer."); + return; + } + message.channel.send(img); + } + + return { + run: run, + settings: { + description: "Send a cat picture", + usage: "cat", + } + } +} + + +export default CatCommand() \ No newline at end of file diff --git a/src/commands/images/dog.ts b/src/commands/images/dog.ts new file mode 100644 index 0000000..b9746ba --- /dev/null +++ b/src/commands/images/dog.ts @@ -0,0 +1,26 @@ +import { Message } from "discord.js"; +import Client from "../../structures/Client"; + +import urls from "../../data/img/dog.json"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +function DogCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const img = urls[Math.floor(Math.random() * urls.length)]; + if (!img) { + message.channel.send("We could not find a picture. Please report this to a developer."); + return; + } + message.channel.send(img); + }; + + return { + run: run, + settings: { + description: "Send a dog picture.", + usage: "dog", + }, + }; +} + +export default DogCommand(); diff --git a/src/commands/images/monkey.ts b/src/commands/images/monkey.ts new file mode 100644 index 0000000..f1ae396 --- /dev/null +++ b/src/commands/images/monkey.ts @@ -0,0 +1,27 @@ +import { Message } from "discord.js"; +import Client from "../../structures/Client"; + +import urls from "../../data/img/monkey.json"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +function MonkeyCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const img = urls[Math.floor(Math.random() * urls.length)]; + if (!img) { + message.channel.send("We could not find a picture. Please report this to a developer."); + return; + } + message.channel.send(img); + } + + return { + run: run, + settings: { + description: "Send a monkey picture", + usage: "monkey" + } + } +} + + +export default MonkeyCommand() \ No newline at end of file diff --git a/src/commands/moderation/format.ts b/src/commands/moderation/format.ts new file mode 100644 index 0000000..fe166b9 --- /dev/null +++ b/src/commands/moderation/format.ts @@ -0,0 +1,52 @@ +import { Message, MessageAttachment } from "discord.js"; +import Client from "../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../structures/Interfaces"; + +const format = "**Type of Punishment:** PUNISHMENT_TYPE\n**Discord name & #:** DISCORD_NAME\n**Discord ID:** DISCORD_ID\n**Evidence:** REASON"; +const genFormat = (punishmentType: string, name: string, id: string, reason: string): string => format.replace("PUNISHMENT_TYPE", punishmentType).replace("DISCORD_NAME", name).replace("DISCORD_ID", id).replace("REASON", reason); + +function FormatCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (args.length < 3) { + message.channel.send(`Invalid arguments!\n${client.prefix}format `); + return; + } + + let image: string | undefined = undefined; + if (message.attachments.size > 0 && attachIsImage(message.attachments.array()[0]!!)) image = message.attachments.array()[0]!!.url; + + const userid = args[1]; + + client.users + .fetch(userid as string) + .then((user) => { + const msg = genFormat(args[0]!!, user.tag, user.id, args.slice(2, args.length).join(" ") as string); + + if (image) message.channel.send(msg, { files: [image] }); + else message.channel.send(msg); + }) + .catch(() => { + const msg = genFormat(args[0] as string, "Invalid user ID", args[1] as string, args.slice(2, args.length).join(" ") as string); + if (image) message.channel.send(msg, { files: [image] }); + else message.channel.send(msg); + }); + }; + + return { + run: run, + settings: { + description: "Generate a punishment format", + usage: "format [attach image]", + guildOnly: true, + permissions: PermissionsEnum.STAFF, + }, + }; +} + +function attachIsImage(msgAttach: MessageAttachment) { + var url = msgAttach.url; + if (url === undefined) return false; + return url.indexOf("png", url.length - "png".length /*or 3*/) !== -1 || url.indexOf("jpeg", url.length - "jpeg".length /*or 3*/) !== -1 || url.indexOf("jpg", url.length - "jpg".length /*or 3*/) !== -1; +} + +export default FormatCommand(); diff --git a/src/commands/moderation/verbal/add.ts b/src/commands/moderation/verbal/add.ts new file mode 100644 index 0000000..ab1f267 --- /dev/null +++ b/src/commands/moderation/verbal/add.ts @@ -0,0 +1,102 @@ +import { Guild, GuildChannel, GuildMember, Message, MessageEmbed, TextChannel } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Channels from "../../../data/calm/channels.json"; +import Database from "../../../utils/database/Database"; +import logger from "../../../utils/logger/Logger"; + +function AddCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; + let guildSettings = await Database.getGuildSettings(message.guild.id); + + if (!attachIsImage(message.attachments.array()[0]?.url)) { + message.channel.send("Invalid arguments. Please provide reasoning as text and evidence as an attached image."); + return; + } + + const id = args[0]; + + let reason: string = ""; + let imgurl = message.attachments.array()[0]?.url; + if (!imgurl) { + message.channel.send("Unable to get image url?"); + return; + } + + for (let i = 1; i < args.length; i++) { + reason += args[i] + " "; + } + // Remove extra space + reason.substring(0, reason.length - 1); + + let member: GuildMember | undefined = await getMember(id as string, message.guild); + if (!member) { + message.channel.send(`Could not find user with id: ${id}`); + return; + } + + if (reason.length > 1020) { + message.channel.send("Your reason is too long. Attach an image and give text that is < 1020 characters (due to discord embed limitations)"); + return; + } + + const casenumber = guildSettings.punishmentcases; + guildSettings.verbals.push({ moderator: message.author.id, user: member.id, reasonText: reason, reasonImage: imgurl, casenumber: casenumber, timestamp: new Date().toDateString() }); + guildSettings.punishmentcases = guildSettings.punishmentcases + 1; + await guildSettings.save(); + + const embed = new MessageEmbed().setTitle(`${member.user.tag} has been verbal warned! Case: ${casenumber}`).setColor("#48db8f"); + message.channel.send(embed); + message.channel.send(`**Type of Punishment**: Verbal Warning\n**Discord Name & #**: ${member.user.tag}\n**Discord ID**: ${member.id}\n**Evidence**: ${reason}`, { files: [imgurl] }); + + let modlogs: GuildChannel | undefined = undefined; + if (message.guild.id === "501501905508237312") { + modlogs = message.guild.channels.cache.get(Channels.STAFF.MOD_LOG.id); + } else { + modlogs = message.guild.channels.cache.find((c) => c.name === Channels.STAFF.MOD_LOG.name); + } + + if (modlogs && modlogs instanceof TextChannel) { + const modlog = new MessageEmbed(); + modlog.setAuthor(`Case ${casenumber} | Verbal Warning | ${member.user.tag}`, member.user.displayAvatarURL()); + modlog.addField("User", member.user.toString(), true); + modlog.addField("Moderator", message.author.toString(), true); + modlog.addField("Reason", reason, true); + modlog.setImage(imgurl); + modlog.setFooter(`ID: ${member.id}`).setTimestamp(); + + modlogs.send(modlog).catch((err) => { + logger.error(err); + }); + } else { + logger.warn(`Could not find modlogs channel. GUILD: ${message.guild.id}`) + } + }; + + return { + run: run, + settings: { + description: "Add a verbal warning to someone", + usage: "verbal add ", + minimumArgs: 2, + }, + }; +} + +export default AddCommand(); + +function attachIsImage(url: string | undefined) { + if (url === undefined) return false; + return url.indexOf("png", url.length - "png".length /*or 3*/) !== -1 || url.indexOf("jpeg", url.length - "jpeg".length /*or 3*/) !== -1 || url.indexOf("jpg", url.length - "jpg".length /*or 3*/) !== -1; +} + +async function getMember(id: string, guild: Guild): Promise { + let member: GuildMember; + try { + member = await guild.members.fetch(id as string); + } catch { + return undefined; + } + return member; +} diff --git a/src/commands/moderation/verbal/case.ts b/src/commands/moderation/verbal/case.ts new file mode 100644 index 0000000..9cafa0d --- /dev/null +++ b/src/commands/moderation/verbal/case.ts @@ -0,0 +1,68 @@ +import { Guild, GuildMember, Message, MessageEmbed } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function CastCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; + const warning = await getWarningFromCase(message.guild.id, args[0] as string); + + if (!warning) { + message.channel.send("Could not find an active or inactive verbal with caseid " + args[0]); + return; + } + + let embed = new MessageEmbed(); + embed.setTitle("Case Number: " + args[0]); + embed.setColor("#eb1717"); + + let membertext = warning.user; + const member = await getMember(warning.user as string, message.guild); + if (member) membertext = member.user.tag; + + let modtext = warning.moderator; + const mod = await getMember(warning.moderator, message.guild); + if (mod) modtext = mod.user.tag; + + embed.addField("User:", membertext); + embed.addField("Moderator:", modtext); + embed.addField("Time:", warning.timestamp ? warning.timestamp : "No timestamp found!"); + if (warning.reasonText) embed.addField("Reason: ", warning.reasonText); + if (warning.reasonImage) embed.setImage(warning.reasonImage); + + message.channel.send(embed); + }; + + return { + run: run, + settings: { + description: "Gets info about a verbal case", + usage: "verbal case ", + minimumArgs: 1, + }, + }; +} + +export default CastCommand(); + +async function getWarningFromCase(guildID: string, caseID: string) { + let guildSettings = await Database.getGuildSettings(guildID); + let warning: any; + guildSettings.verbals.forEach((element) => { + if ((element.casenumber.toString() as string) === caseID) { + warning = element; + } + }); + return warning; +} + +async function getMember(id: string, guild: Guild) { + let member: GuildMember; + try { + member = await guild.members.fetch(id as string); + } catch { + return undefined; + } + return member; +} diff --git a/src/commands/moderation/verbal/help.ts b/src/commands/moderation/verbal/help.ts new file mode 100644 index 0000000..da42db3 --- /dev/null +++ b/src/commands/moderation/verbal/help.ts @@ -0,0 +1,32 @@ +import { Message, MessageEmbed } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import logger from "../../../utils/logger/Logger"; + +function HelpCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const verbalcommand = client.commands.get("verbal"); + if (!verbalcommand) { + message.channel.send("Error getting verbal subcommand list!"); + return; + } + + const embed = new MessageEmbed().setTitle("Verbal commands menu").setColor("#17c1eb"); + for (const [, command] of verbalcommand.subCommands!) { + embed.addField(client.prefix + command.settings?.usage, command.settings?.description); + } + message.channel.send(embed).catch((e) => { + logger.warn(e); + }); + }; + + return { + run: run, + settings: { + description: "Shows all verbal commands", + usage: "verbal help", + }, + }; +} + +export default HelpCommand(); diff --git a/src/commands/moderation/verbal/info.ts b/src/commands/moderation/verbal/info.ts new file mode 100644 index 0000000..a00cedf --- /dev/null +++ b/src/commands/moderation/verbal/info.ts @@ -0,0 +1,72 @@ +import { Guild, GuildMember, Message, MessageEmbed } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; +import { IVerbals } from "../../../schemas/GuildSettings"; + +function InfoCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; + + const warnings = await getUserWarnings(message.guild.id, args[0] as string); + if (warnings.length === 0) { + message.channel.send("No warnings found for user: " + args[0]); + return; + } + + let membertext = args[1]; + const member = await getMember(args[0] as string, message.guild); + if (member !== undefined) membertext = member.user.tag; + + warnings.forEach(async (warning) => { + let embed = new MessageEmbed(); + embed.setTitle(membertext + "'s verbal warnings"); + embed.setColor("#eb1717"); + + let modtext = warning.moderator; + if (!message.guild) return; + const mod = await getMember(warning.moderator, message.guild); + if (mod !== undefined) modtext = mod.user.tag; + + embed.addField("Case number: ", warning.casenumber, true); + embed.addField("Moderator: ", modtext); + embed.addField("Time:", warning.timestamp ? warning.timestamp : "No timestamp found!"); + if (warning.reasonText !== undefined) embed.addField("Reason: ", warning.reasonText); + if (warning.reasonImage !== undefined) embed.setImage(warning.reasonImage); + + await message.channel.send(embed); + }); + }; + + return { + run: run, + settings: { + description: "Get all verbal warnings for a specific user.", + usage: "verbal info ", + minimumArgs: 1, + }, + }; +} + +export default InfoCommand(); + +async function getUserWarnings(guildID: string, userID: string) { + let arr: IVerbals[] = []; + let guildSettings = await Database.getGuildSettings(guildID); + guildSettings.verbals.forEach((element) => { + if (element.user === userID) { + arr.push(element); + } + }); + return arr; +} + +async function getMember(id: string, guild: Guild) { + let member: GuildMember; + try { + member = await guild.members.fetch(id as string); + } catch { + return undefined; + } + return member; +} diff --git a/src/commands/moderation/verbal/remove.ts b/src/commands/moderation/verbal/remove.ts new file mode 100644 index 0000000..3ec0fac --- /dev/null +++ b/src/commands/moderation/verbal/remove.ts @@ -0,0 +1,40 @@ +import { Message, MessageEmbed } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function RemoveCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; + let guildSettings = await Database.getGuildSettings(message.guild.id); + + const verbal = guildSettings.verbals.find((element) => element.casenumber === Number.parseInt(args[0] as string)); + if (!verbal) { + message.channel.send(`Could not find verbal warning with case id ${args[0]}`); + return; + } + + guildSettings.verbals = arrayRemove(guildSettings.verbals, verbal); + guildSettings.save(); + + const embed = new MessageEmbed().setTitle(`Removed verbal case ${args[0]}`).setColor("#48db8f"); + message.channel.send(embed); + }; + + return { + run: run, + settings: { + description: "Remove a verbal warning from someone", + usage: "verbal remove ", + minimumArgs: 1, + }, + }; +} + +export default RemoveCommand(); + +function arrayRemove(arr: any[], value: any) { + return arr.filter(function (ele) { + return ele != value; + }); +} diff --git a/src/commands/moderation/verbal/subcommandSettings.ts b/src/commands/moderation/verbal/subcommandSettings.ts new file mode 100644 index 0000000..eadf8a0 --- /dev/null +++ b/src/commands/moderation/verbal/subcommandSettings.ts @@ -0,0 +1,9 @@ +import { ISubCommandSettings, PermissionsEnum } from "../../../structures/Interfaces"; + +const settings: ISubCommandSettings = { + defaultSubCommand: "add", + guildOnly: true, + permissions: PermissionsEnum.STAFF, +}; + +export default settings; \ No newline at end of file diff --git a/src/commands/monkey.ts b/src/commands/monkey.ts deleted file mode 100644 index cfbbd31..0000000 --- a/src/commands/monkey.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -import urls from "../data/img/monkey.json"; - -module.exports = { - name: "monkey", - description: "Sends a monkey picture!", - category: "Images", - usage: "monkey", - run: async function run(client: Client, message: Message, args: Array) { - const img = urls[Math.floor(Math.random() * urls.length)]; - message.channel.send(img); - }, -}; diff --git a/src/commands/ping.ts b/src/commands/ping.ts deleted file mode 100644 index 8762d36..0000000 --- a/src/commands/ping.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -module.exports = { - name: "ping", - description: "Pong!", - category: "Utility", - usage: "ping", - run: async function run(client: Client, message: Message) { - message.channel.send(`Latency is \`${Date.now() - message.createdTimestamp}ms\`, Pong!`); - }, -}; diff --git a/src/commands/reqs.ts b/src/commands/reqs.ts deleted file mode 100644 index a1a6d7a..0000000 --- a/src/commands/reqs.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Message, TextChannel } from "discord.js"; -import Client from "../structures/Client"; -import channels from "../data/calm/channels.json"; - -module.exports = { - name: "reqs", - aliases: ["requirements"], - description: "Displays current guild requirements", - category: "Information", - usage: "reqs", - run: async function run(client: Client, message: Message) { - let infoChannel: TextChannel; - if (message.guild.id === "501501905508237312") { - infoChannel = message.guild.channels.cache.find((chan) => chan.id === channels.UPON_JOINING.INFO.id) as TextChannel; - } else { - infoChannel = message.guild.channels.cache.find((chan) => chan.name === channels.UPON_JOINING.INFO.name) as TextChannel; - } - if (!infoChannel) { - return message.reply("we could not find the info channel!"); - } - const send = - `Our requirements can be found in ${infoChannel}!\n\n` + - "They are also stated on our *forums thread* & on our *application*!\n" + - "**Forums Thread**: \n" + - "**Application**: "; - - message.channel.send(send); - }, -}; diff --git a/src/commands/roles.ts b/src/commands/roles.ts deleted file mode 100644 index 316570f..0000000 --- a/src/commands/roles.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Message, TextChannel } from "discord.js"; -import Client from "../structures/Client"; -import channels from "../data/calm/channels.json"; - -module.exports = { - name: "roles", - description: "General information about roles", - category: "Information", - usage: "roles", - run: async function run(client: Client, message: Message) { - let selfRolesChannel: TextChannel, roleInfo: TextChannel; - if (message.guild.id === "501501905508237312") { - selfRolesChannel = message.guild.channels.cache.find((chan) => chan.id === channels.UPON_JOINING.SELF_ASSIGN_ROLES.id) as TextChannel; - roleInfo = message.guild.channels.cache.find((chan) => chan.id === channels.UPON_JOINING.ROLE_INFO.id) as TextChannel; - } else { - selfRolesChannel = message.guild.channels.cache.find((chan) => chan.name === channels.UPON_JOINING.SELF_ASSIGN_ROLES.name) as TextChannel; - roleInfo = message.guild.channels.cache.find((chan) => chan.name === channels.UPON_JOINING.ROLE_INFO.name) as TextChannel; - } - if (!selfRolesChannel || !roleInfo) { - return message.reply("we could not find the self-assign-roles / role-info channel!"); - } - - message.channel.send(`Information on all roles can be found in ${roleInfo}\nYou want some of these roles? Go give yourself some in ${selfRolesChannel}!! <3`); - }, -}; diff --git a/src/commands/socials.ts b/src/commands/socials.ts deleted file mode 100644 index aeb677e..0000000 --- a/src/commands/socials.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -module.exports = { - name: "socials", - aliases: ["social", "socialmedia"], - description: "Sends Guild social media, as well as the Guild Master's", - category: "Information", - usage: "socials", - run: async function run(client: Client, message: Message) { - const social = - "**Calm Social Media**\n" + - "Twitter: \n" + - "Plancke: \n" + - "Forums Thread: \n" + - "YouTube: \n\n" + - "**Guild Master's Media**\n" + - "Twitter: \n" + - "Plancke: \n" + - "Forums: \n" + - "YouTube: \n" + - "Twitch: "; - message.channel.send(social); - }, -}; diff --git a/src/commands/staff.ts b/src/commands/staff.ts deleted file mode 100644 index 759c7b1..0000000 --- a/src/commands/staff.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { GuildEmoji, Message, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; -import HypixelApi from "../utils/api/HypixelApi"; -import { IPlayer } from "../utils/api/Interfaces" - -module.exports = { - name: "staff", - description: "Displays all online Calm Guild Staff", - category: "Utility", - usage: "staff", - run: async function run(client: Client, message: Message) { - const guildStaff = await HypixelApi.getGuildStaff(); - if (!guildStaff) { - return message.channel.send("Error: To prevent ratelimiting this command is on cooldown. Please wait a little bit and then try again."); - } - - let onlineEmote: GuildEmoji | string, offlineEmote: GuildEmoji | string; - if(message.guild.id === "501501905508237312") { - onlineEmote = client.emojis.cache.find(e => e.id === "805656441704546324") - offlineEmote = client.emojis.cache.find(e => e.id === "805656538127401000"); - } else { - onlineEmote = "🟢"; - offlineEmote = "🔴"; - } - - const tmp = await message.channel.send("Please wait while we fetch data from the api. This could take some time."); - - let online = Array(); - let offline = Array(); - - for (const member in guildStaff) { - const player = await HypixelApi.getPlayerFromUUID(guildStaff[member].uuid); - - if (player.lastLogin > player.lastLogout) { - online.push(player); - } else { - offline.push(player); - } - } - - let embedDesc = ""; - online.forEach((player) => { - embedDesc += "`" + player.displayname + "` " + onlineEmote + "\n"; - }) - offline.forEach((player) => { - embedDesc += "`" + player.displayname + "` " + offlineEmote + "\n"; - }) - - - const embed = new MessageEmbed(); - embed.setTitle("Guild Staff"); - embed.setDescription(embedDesc); - await tmp.delete(); - await message.channel.send(embed) - }, -}; - diff --git a/src/commands/suggest.ts b/src/commands/suggest.ts deleted file mode 100644 index 3134d9c..0000000 --- a/src/commands/suggest.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Message, TextChannel, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; -import Database from "../utils/database/Database"; - -import Channels from "../data/calm/channels.json"; -module.exports = { - name: "suggest", - aliases: ["suggestion"], - description: "Make a suggestion for the server!", - category: "Utility", - usage: "suggest ", - run: async function run(client: Client, message: Message, args: Array) { - if (args.length === 0) { - message.channel.send("Invalid arguments! Please do c!suggest (suggestion)"); - return; - } - - let suggestion = ""; - for (let i = 0; i < args.length; i++) { - suggestion += args[i] + " "; - } - let suggestionChannel: TextChannel, firstReaction: string, secondReaction: string; - if (message.guild.id === "501501905508237312") { - suggestionChannel = message.guild.channels.cache.find((chan) => chan.id === Channels.SUGGESTIONS.SUGGESTIONS.id) as TextChannel; - firstReaction = "615239771723137026"; // https://cdn.discordapp.com/emojis/615239771723137026.png?v=1 - secondReaction = "615239802127777817"; // https://cdn.discordapp.com/emojis/615239802127777817.png?v=1 - } else { - suggestionChannel = message.guild.channels.cache.find((chan) => chan.name === Channels.SUGGESTIONS.SUGGESTIONS.name) as TextChannel; - firstReaction = "✅"; - secondReaction = "❎"; - } - - if (suggestionChannel === undefined) { - message.channel.send("Uh oh! We could not find a channel to put the suggestion in!"); - return; - } - - const suggestionEmbed = new MessageEmbed().setFooter(`${message.member.displayName} • CalmBot v${client.version}`, message.author.displayAvatarURL()).setColor("#007FFF").setTitle("Suggestion:").setDescription(suggestion).setTimestamp(); - - message.channel.send("Thanks for the suggestion! \n**Check it out: <#" + suggestionChannel.id + ">**"); - - let guildSettings = await Database.getGuildSettings(message.guild.id); - - suggestionChannel.send(suggestionEmbed).then((m) => { - m.react(firstReaction); - m.react(secondReaction); - - guildSettings.suggestions.push({ msgID: m.id, suggestorID: message.author.id, suggestorTag: message.author.tag, suggestion: suggestion }); - guildSettings.save(); - }); - }, -}; diff --git a/src/commands/trial.ts b/src/commands/trial.ts deleted file mode 100644 index 4092060..0000000 --- a/src/commands/trial.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Message } from "discord.js"; -import Client from "../structures/Client"; - -module.exports = { - name: "trial", - description: "Information about becoming a trial officer", - category: "Information", - usage: "trial", - run: async function run(client: Client, message: Message) { - const trialInfo = - "So you want to become Calm staff?? Awesome!!!\n\n" + - "Currently, our only requirements for Trial Officer are: Mee6 level 5+, have 2fa enabled on your account, a shareable email (for staff documents), age of 14+, and be in Calm Guild for two weeks or more!\n\n" + - "Application: "; - message.channel.send(trialInfo); - }, -}; diff --git a/src/commands/annonsuggestion.ts b/src/commands/utility/annonsuggestion.ts similarity index 56% rename from src/commands/annonsuggestion.ts rename to src/commands/utility/annonsuggestion.ts index 960f89e..5dcd980 100644 --- a/src/commands/annonsuggestion.ts +++ b/src/commands/utility/annonsuggestion.ts @@ -1,23 +1,14 @@ import { Message, TextChannel, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; -import Database from "../utils/database/Database"; -import Channels from "../data/calm/channels.json"; -module.exports = { - name: "annonsuggestion", - aliases: ["anonsuggestion", "anonsuggest"], - description: "Make a completely anonymous suggestion for the server!", - category: "Utility", - usage: "annonsuggestion ", - run: async function run(client: Client, message: Message, args: Array) { - if (args.length === 0) { - message.channel.send("Invalid arguments! Please do c!annonsuggestion (suggestion)"); - return; - } +import Client from "../../structures/Client"; +import Database from "../../utils/database/Database"; +import Channels from "../../data/calm/channels.json"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +function AnnonSuggestionCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild) return; + let suggestion = args.join(" "); - let suggestion = ""; - for (let i = 0; i < args.length; i++) { - suggestion += args[i] + " "; - } let suggestionChannel: TextChannel, firstReaction: string, secondReaction: string; if (message.guild.id === "501501905508237312") { suggestionChannel = message.guild.channels.cache.find((chan) => chan.id === Channels.SUGGESTIONS.SUGGESTIONS.id) as TextChannel; @@ -29,23 +20,39 @@ module.exports = { secondReaction = "❎"; } - if (suggestionChannel === undefined) { - message.channel.send("Uh oh! We could not find a channel to put the suggestion in!"); + if (!suggestionChannel) { + message.channel.send("We couldn't find the suggestion channel!"); return; } - const suggestionEmbed = new MessageEmbed().setFooter(`Anonymous Suggestion • CalmBot v${client.version}`).setColor("#007FFF").setTitle("Suggestion:").setDescription(suggestion).setTimestamp(); + const suggestionEmbed = new MessageEmbed().setFooter(`Anonymous Suggestion`).setColor("#007FFF").setTitle("Suggestion:").setDescription(suggestion).setTimestamp(); // Delete message so people can not see who made suggestion message.delete(); let guildSettings = await Database.getGuildSettings(message.guild.id); + if (!(suggestionChannel instanceof TextChannel)) { + message.channel.send("Suggestion channel not a text channel!"); + } + suggestionChannel.send(suggestionEmbed).then((m) => { m.react(firstReaction); m.react(secondReaction); guildSettings.suggestions.push({ msgID: m.id, suggestorID: message.author.id, suggestorTag: message.author.tag, suggestion: suggestion }); guildSettings.save(); }); - }, -}; + }; + + return { + run: run, + settings: { + description: "Send a fully anonymous suggestion for the server", + usage: "annonsuggestion ", + guildOnly: true, + minimumArgs: 1, + }, + }; +} + +export default AnnonSuggestionCommand(); diff --git a/src/commands/challenge.ts b/src/commands/utility/challenge.ts similarity index 55% rename from src/commands/challenge.ts rename to src/commands/utility/challenge.ts index 74b9253..7dacf75 100644 --- a/src/commands/challenge.ts +++ b/src/commands/utility/challenge.ts @@ -1,37 +1,17 @@ -import Channels from "../data/calm/channels.json"; -import Challenges from "../data/calm/challenges/DecemberChallenges.json"; -import Client from "../structures/Client"; -import Database from "../utils/database/Database"; -import { MessageEmbed, Message, MessageAttachment } from "discord.js"; - -import challengecheck from "../handlers/challenges/challengecheck"; -import challengeleaderboard from "../handlers/challenges/challengeleaderboard"; -import challengedenyrequest from "../handlers/challenges/challengedenyrequest"; -module.exports = { - name: "challenge", - description: "Make a challenge request doing c!challenge (challenge-id) (challenge-proof in form of picture or gamelink) ", - category: "Utility", - usage: "challenge / ", - run: async function challenge(client: Client, message: Message, args: Array) { - //ex c!challenge (id) (proof-link) / attached image - if (args.length === 0) { - message.channel.send("Invalid Arguments. \n Example: **c!challenge (challenge-id) (proof (game link or image attachment))** \n or **c!challenge check** to check a users progress**"); - return; - } +import { Message, MessageEmbed } from "discord.js"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; +import Database from "../../utils/database/Database"; +import Challenges from "../../data/calm/challenges/DecemberChallenges.json"; +import Channels from "../../data/calm/channels.json"; +import Client from "../../structures/Client"; - if (args[0] === "check") { - challengecheck.run(client, message, args); - return; - } else if (args[0] === "leaderboard") { - challengeleaderboard.run(client, message, args); - return; - } else if (args[0] === "denyrequest") { - challengedenyrequest.run(client, message, args); - return; - } +function ChallengeCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + console.log("a"); + if (!args[0] || !message.guild) return; if (args.length < 2) { - if (message.attachments.size === 0 || !attachIsImage(message.attachments.array()[0])) { + if (message.attachments.size === 0 || !attachIsImage(message.attachments.array()[0]?.url)) { message.channel.send("Invalid arguments. Please attach an image or send a hypixel gamelink."); return; } @@ -49,6 +29,9 @@ module.exports = { } let participant = await Database.getChallengeParticipant(message.author.id); + console.log(participant); + + if (participant === null) return; if (participant.completedChallenges.get(id) === "true") { message.channel.send(`You have already completed challenge ${id}.`); @@ -67,17 +50,20 @@ module.exports = { return; } + const url: string | undefined = message.attachments.array()[0]?.url; + if (!url && proofIsImage) return; + const embed = new MessageEmbed(); embed.setTitle("Challenge Request from " + message.author.username); embed.addField("Challenge ID:", id); - embed.addField("Challenge Name:", getChallenge(id).name, true); + embed.addField("Challenge Name:", getChallenge(id)?.name, true); if (proofIsImage) { - embed.setImage(message.attachments.array()[0].url); + embed.setImage(url!); } else { embed.addField("Proof", args[1]); } embed.addField("User ID:", message.author.id); - embed.setFooter("CalmBot | v" + client.version); + embed.setFooter("CalmBot"); if (id.startsWith("e")) { embed.setColor("#34e07e"); } else if (id.startsWith("m")) { @@ -94,19 +80,30 @@ module.exports = { message.channel.send( `<@${message.author.id}>, Your challenge has been submited by review from staff. Please be patient as this can take some time.\nContact any staff with the **Monthly Challenges Team** role if you have any questions!` ); - }, -}; + }; + + return { + run: run, + settings: { + description: "Make a challenge request", + usage: "challenge / ", + guildOnly: true, + minimumArgs: 1, + }, + }; +} + +export default ChallengeCommand(); -function attachIsImage(msgAttach: MessageAttachment) { - var url = msgAttach.url; +function attachIsImage(url: string | undefined) { if (url === undefined) return false; return url.indexOf("png", url.length - "png".length /*or 3*/) !== -1 || url.indexOf("jpeg", url.length - "jpeg".length /*or 3*/) !== -1 || url.indexOf("jpg", url.length - "jpg".length /*or 3*/) !== -1; } -function getChallenge(id: String) { - for (let challenge in Challenges) { - if (Challenges[challenge].id.toLowerCase() === id.toLowerCase()) { - return Challenges[challenge]; +function getChallenge(id: string) { + for (const [, v] of Object.entries(Challenges)) { + if (v.id === id) { + return v; } } return undefined; diff --git a/src/handlers/challenges/challengecheck.ts b/src/commands/utility/challenge/check.ts similarity index 67% rename from src/handlers/challenges/challengecheck.ts rename to src/commands/utility/challenge/check.ts index e037e66..ad5c992 100644 --- a/src/handlers/challenges/challengecheck.ts +++ b/src/commands/utility/challenge/check.ts @@ -1,33 +1,37 @@ -import { Message, Role } from "discord.js"; -import Client from "../../structures/Client"; -import Database from "../../utils/database/Database"; -import Challenges from "../../data/calm/challenges/DecemberChallenges.json"; -import Roles from "../../data/calm/roles.json"; - -export default { - run: async function run(client: Client, message: Message, args: Array) { - let discordID: string = null; - - let monthlyTeamRole: Role; +import { Message, Role } from "discord.js" +import Client from "../../../structures/Client" +import { ICommand, RunCallback } from "../../../structures/Interfaces" +import Roles from "../../../data/calm/roles.json"; +import Database from "../../../utils/database/Database"; +import Challenges from "../../../data/calm/challenges/DecemberChallenges.json"; + +function CheckCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild || !message.member) return; + let discordID: string | undefined = undefined; + + let monthlyTeamRole: Role | undefined; if (message.guild.id === "501501905508237312") { monthlyTeamRole = message.guild.roles.cache.find((r) => r.id === Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.id); } else { monthlyTeamRole = message.guild.roles.cache.find((r) => r.name === Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.name); } - if (monthlyTeamRole === undefined || message.member.roles.cache.find((r) => r.id === monthlyTeamRole.id) === undefined) { + if (monthlyTeamRole === undefined || message.member.roles.cache.find((r) => r.id === monthlyTeamRole?.id) === undefined) { discordID = message.author.id; - } else if (args.length > 1) { - const user = client.users.cache.find((u) => u.id === args[1]); + } else if (args.length > 0) { + const user: any = client.users.cache.find((u) => u.id === args[0]); if (message.guild.members.fetch(user) === undefined) { - message.channel.send(`Could not find user with ID: ${args[1]}`); + message.channel.send(`Could not find user with ID: ${args[0]}`); return; } //Converts string --> String - discordID = args[1].toString(); + discordID = args[0]?.toString(); } - if (discordID === null) discordID = message.author.id; + if (!discordID) discordID = message.author.id; const participant = await Database.getChallengeParticipant(discordID); + console.log(participant); + if (participant === null) { message.channel.send("You/They have not done any challenges yet!"); return; @@ -36,17 +40,19 @@ export default { const ogMsg = await message.channel.send( "**NOTE:** Due to issues with sending a message above 2K characters and crashing the bot multiple messages will be sent. Due to discord limitations this might take a little bit. Please be patient" ); - let points = 0; + let easyCompleted: Array = []; let mediumCompleted: Array = []; let hardCompleted: Array = []; let impossibleCompleted: Array = []; - for (const [challenge, value] of (await participant).completedChallenges) { + for (const [challenge, value] of participant.completedChallenges) { if (value == "true") { - points += getChallenge(challenge).points; - const challengeObject = getChallenge(challenge); - + console.log(challenge); + console.log(value); + + const challengeObject = getChallenge(challenge)!; + if (challengeObject.id.startsWith("e")) { easyCompleted.push(`id:${challengeObject.id} | ${challengeObject.name}\n`); } else if (challengeObject.id.startsWith("m")) { @@ -64,30 +70,28 @@ export default { let hardMissing: Array = []; let impossibleMissing: Array = []; - let msg2 = `\n\n**Missing Challenges**`; - msg2 += "```"; - for (const challenge in Challenges) { - if (!(await participant).completedChallenges.has(Challenges[challenge].id)) { - if (Challenges[challenge].id.startsWith("e")) { - easyMissing.push(`id:${Challenges[challenge].id} | ${Challenges[challenge].name}\n`); - } else if (Challenges[challenge].id.startsWith("m")) { - mediumMissing.push(`id:${Challenges[challenge].id} | ${Challenges[challenge].name}\n`); - } else if (Challenges[challenge].id.startsWith("h")) { - hardMissing.push(`id:${Challenges[challenge].id} | ${Challenges[challenge].name}\n`); - } else if (Challenges[challenge].id.startsWith("i")) { - impossibleMissing.push(`id:${Challenges[challenge].id} | ${Challenges[challenge].name}\n`); + for (const [, challenge] of Object.entries(Challenges)) { + if (!participant.completedChallenges.has(challenge.id)) { + if (challenge.id.startsWith("e")) { + easyMissing.push(`id:${challenge.id} | ${challenge.name}\n`); + } else if (challenge.id.startsWith("m")) { + mediumMissing.push(`id:${challenge.id} | ${challenge.name}\n`); + } else if (challenge.id.startsWith("h")) { + hardMissing.push(`id:${challenge.id} | ${challenge.name}\n`); + } else if (challenge.id.startsWith("i")) { + impossibleMissing.push(`id:${challenge.id} | ${challenge.name}\n`); } } } - let easyCompletedMsg: string = null; - let hardCompletedMsg: string = null; - let mediumCompletedMsg: string = null; - let impossibleCompletedMsg: string = null; - let easyMissingMsg: string = null; - let mediumMissingMsg: string = null; - let hardMissingMsg: string = null; - let impossibleMissingMsg: string = null; + let easyCompletedMsg: string | null = null; + let hardCompletedMsg: string | null = null; + let mediumCompletedMsg: string | null = null; + let impossibleCompletedMsg: string | null = null; + let easyMissingMsg: string | null = null; + let mediumMissingMsg: string | null = null; + let hardMissingMsg: string | null = null; + let impossibleMissingMsg: string | null = null; // Format and save all messages for completed challenges. if (easyCompleted.length !== 0) { @@ -179,13 +183,25 @@ export default { ogMsg.delete(); return; - }, -}; + } + + return { + run: run, + settings: { + description: "desc", + usage: "usage", + guildOnly: true + } + } +} + + +export default CheckCommand() -function getChallenge(id: String) { - for (let challenge in Challenges) { - if (Challenges[challenge].id.toLowerCase() === id.toLowerCase()) { - return Challenges[challenge]; +function getChallenge(id: string) { + for (const [, v] of Object.entries(Challenges)) { + if (v.id === id) { + return v; } } return undefined; diff --git a/src/handlers/challenges/challengedenyrequest.ts b/src/commands/utility/challenge/deny.ts similarity index 65% rename from src/handlers/challenges/challengedenyrequest.ts rename to src/commands/utility/challenge/deny.ts index ae906c5..faea952 100644 --- a/src/handlers/challenges/challengedenyrequest.ts +++ b/src/commands/utility/challenge/deny.ts @@ -1,24 +1,23 @@ -import Channels from "../../data/calm/channels.json"; -import Roles from "../../data/calm/roles.json"; -import Client from "../../structures/Client"; import { Message, MessageEmbed, Role, TextChannel } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Channels from "../../../data/calm/channels.json"; +import Roles from "../../../data/calm/roles.json"; -export default { - run: async function run(client: Client, message: Message, args: Array) { - //ex c!challenge denyrequest (msg-id) (reason) - if (args.length < 3) { - message.channel.send("Error: Invalid Arguments. Ex c!challenge denyrequest (msg-id) (reason)"); - return; - } - - const requestmessage = await message.channel.messages.fetch(args[1] as string); - if (requestmessage === undefined) { - message.channel.send("Unable to find request message with id " + args[1]); +function DenyCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.member || !message.guild) return; + let requestmessage; + + try { + requestmessage = await message.channel.messages.fetch(args[0] as string); + } catch (e) { + message.channel.send("Unable to find request message with id " + args[0]); return; } let reason = ""; - for (let i = 2; i < args.length + 1; i++) { + for (let i = 1; i < args.length; i++) { reason += args[i] + " "; } if (reason === "") { @@ -32,7 +31,7 @@ export default { if (message.guild.id === "501501905508237312" && message.channel.id !== Channels.STAFF.CHALLENGES.id) return; else if (message.guild.id !== "501501905508237312" && channel.name !== Channels.STAFF.CHALLENGES.name) return; - let monthlyTeamRole: Role; + let monthlyTeamRole: Role | undefined; if (message.guild.id === "501501905508237312") { monthlyTeamRole = message.guild.roles.cache.find((r) => r.id === Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.id); } else { @@ -46,41 +45,54 @@ export default { const member = message.member; - if (member.roles.cache.find((r) => r.id === monthlyTeamRole.id) === undefined) { + if (member.roles.cache.find((r) => r.id === monthlyTeamRole?.id) === undefined) { message.channel.send(`<@${message.author.id}> Error! As you do not have the Monthly Challenges Team role you are unable to approve this challenge!`); return; } + if (!requestmessage.embeds[0]) return; if (requestmessage.embeds[0].hexColor === "#ff0000" || requestmessage.embeds[0].hexColor === "#3cff00") return; const fields = requestmessage.embeds[0].fields; - const userID = fields.find((f) => f.name.toLowerCase() === "user id:").value; - const challengeID = fields.find((f) => f.name.toLowerCase() === "challenge id:").value; + if (!fields) return; + const userID = fields.find((f) => f.name.toLowerCase() === "user id:")?.value; + const challengeID = fields.find((f) => f.name.toLowerCase() === "challenge id:")?.value; const embed = new MessageEmbed(); - embed.setTitle(requestmessage.embeds[0].title.replace("Challenge Request", "Denied Challenge Request")); + embed.setTitle(requestmessage.embeds[0].title?.replace("Challenge Request", "Denied Challenge Request")); embed.setColor("#ff0000"); embed.addField("Challenge ID:", challengeID); - embed.addField("Challenge Name:", requestmessage.embeds[0].fields.find((f) => f.name === "Challenge Name:").value); + embed.addField("Challenge Name:", requestmessage.embeds[0].fields.find((f) => f.name === "Challenge Name:")?.value); if (requestmessage.embeds[0].image !== null) { embed.setImage(requestmessage.embeds[0].image.url); } else { - embed.addField("Proof", requestmessage.embeds[0].fields.find((f) => f.name === "Proof").value); + embed.addField("Proof", requestmessage.embeds[0].fields.find((f) => f.name === "Proof")?.value); } embed.addField("Denied by:", message.author.username + "#" + message.author.discriminator); requestmessage.edit(embed); let commandChannel: TextChannel; if (message.guild.id === "501501905508237312") { - commandChannel = message.guild.channels.cache.find((c) => c.id === Channels.COMMUNITY.COMMANDS.id) as TextChannel; + commandChannel = message.guild.channels.cache.find((c) => c.id === Channels.WEEKLY_MONTHLY.CHALLENGE_PROOF.id) as TextChannel; } else { - commandChannel = message.guild.channels.cache.find((c) => c.name === Channels.COMMUNITY.COMMANDS.name) as TextChannel; + commandChannel = message.guild.channels.cache.find((c) => c.name === Channels.WEEKLY_MONTHLY.CHALLENGE_PROOF.name) as TextChannel; } - if (commandChannel !== undefined) { + if (commandChannel) { commandChannel.send(`Sorry, <@${userID}>. your challenge request for challenge #${challengeID} has been denied.\n**REASON:** ${reason}`); } requestmessage.reactions.removeAll(); - }, -}; + }; + + return { + run: run, + settings: { + description: "desc", + usage: "usage", + guildOnly: true, + }, + }; +} + +export default DenyCommand(); diff --git a/src/handlers/challenges/challengeleaderboard.ts b/src/commands/utility/challenge/leaderboard.ts similarity index 51% rename from src/handlers/challenges/challengeleaderboard.ts rename to src/commands/utility/challenge/leaderboard.ts index bc1bb04..9407e2b 100644 --- a/src/handlers/challenges/challengeleaderboard.ts +++ b/src/commands/utility/challenge/leaderboard.ts @@ -1,19 +1,21 @@ import { Message, Role } from "discord.js"; -import Client from "../../structures/Client"; -import ChallengeParticipant from "../../schemas/ChallengeParticipant"; -import Roles from "../../data/calm/roles.json"; -import Challenges from "../../data/calm/challenges/DecemberChallenges.json"; - -export default { - run: async function run(client: Client, message: Message, args: Array) { - let monthlyTeamRole: Role; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Roles from "../../../data/calm/roles.json"; +import ChallengeParticipant from "../../../schemas/ChallengeParticipant"; +import Challenges from "../../../data/calm/challenges/DecemberChallenges.json"; + +function LeaderboardCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild || !message.member) return; + let monthlyTeamRole: Role | undefined; if (message.guild.id === "501501905508237312") { monthlyTeamRole = message.guild.roles.cache.find((r) => r.id === Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.id); } else { monthlyTeamRole = message.guild.roles.cache.find((r) => r.name === Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.name); } - if (monthlyTeamRole === undefined || message.member.roles.cache.find((r) => r.id === monthlyTeamRole.id) === undefined) { + if (monthlyTeamRole === undefined || message.member.roles.cache.find((r) => r.id === monthlyTeamRole?.id) === undefined) { message.channel.send(`Error: You do not have ${Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.name} role!`); return; } @@ -23,21 +25,22 @@ export default { (await ChallengeParticipant.find()).forEach(async (doc) => { let points = 0; for (const [challenge, value] of doc.completedChallenges) { - if (value === "true") points += getChallenge(challenge).points; + console.log(getChallenge(challenge)?.points!); + if (value === "true") points += getChallenge(challenge)?.points!; } lbMap.set(doc.discordID, points); }); //Sort map Greatest --> Least lbMap[Symbol.iterator] = function* () { - yield* [...this.entries()].sort((a, b) => b[1] - a[1]); + yield* [...this.entries()].sort((a: any, b: any) => b[1] - a[1]); }; let lb = [...lbMap]; let msg = ""; msg += `**Challenge Leaderboard**\n\n`; for (let i = 1; i < lb.length + 1; i++) { - msg += `\`#${i}\` | <@${lb[i - 1][0]}> with ${lb[i - 1][1]} points.\n`; + msg += `\`#${i}\` | <@${lb[i - 1]! [0]}> with ${lb[i - 1]! [1]} points.\n`; } if (lb.length === 0) { msg += "**No Entries Yet** ):"; @@ -46,13 +49,24 @@ export default { message.channel.send("Loading Message...").then((m) => { m.edit(msg); }); - }, -}; + }; + + return { + run: run, + settings: { + description: "desc", + usage: "usage", + guildOnly: true, + }, + }; +} + +export default LeaderboardCommand(); -function getChallenge(id: String) { - for (let challenge in Challenges) { - if (Challenges[challenge].id.toLowerCase() === id.toLowerCase()) { - return Challenges[challenge]; +function getChallenge(id: string) { + for (const [, v] of Object.entries(Challenges)) { + if (v.id === id) { + return v; } } return undefined; diff --git a/src/commands/utility/challenge/subcommandSettings.ts b/src/commands/utility/challenge/subcommandSettings.ts new file mode 100644 index 0000000..17c5a17 --- /dev/null +++ b/src/commands/utility/challenge/subcommandSettings.ts @@ -0,0 +1,7 @@ +import { ISubCommandSettings } from "../../../structures/Interfaces"; + +const settings: ISubCommandSettings = { + guildOnly: true, +} + +export default settings; \ No newline at end of file diff --git a/src/commands/utility/help.ts b/src/commands/utility/help.ts new file mode 100644 index 0000000..9aa33f2 --- /dev/null +++ b/src/commands/utility/help.ts @@ -0,0 +1,116 @@ +import { Collection, MessageEmbed } from "discord.js"; +import { ICommand, Permission, PermissionsEnum, RunCallback } from "../../structures/Interfaces"; + +function HelpCommand(): ICommand { + const run: RunCallback = async (client, message, args) => { + let categories: string[] = []; + client.commands.forEach((cmd) => { + if (cmd.category && !categories.includes(cmd.category)) categories.push(cmd.category); + }); + + const embed = new MessageEmbed(); + embed.setColor("#069420"); + + if (args.length === 0) { + embed.setTitle("Help | " + client.user?.username); + categories.forEach((category) => { + embed.addField(reFormat(category), getFormattedCommandsInCategory(category, client.commands), true); + }); + embed.setFooter(`Do ${client.prefix}help for help with a command!`); + message.channel.send(embed); + return; + } else if (args[0]) { + const commandName = args[0]?.toLowerCase(); + + let command = client.commands.get(commandName); + if (command === undefined) { + message.channel.send("Invalid command!"); + return; + } + + if (!command.settings && command.subCommandSettings) { + if (command.subCommandSettings.defaultSubCommand) { + command = command.subCommands?.get(command.subCommandSettings.defaultSubCommand); + } + if (!command || (!command.subCommandSettings?.defaultSubCommand && !command.settings)) { + embed.setTitle(`Help | ${commandName}`); + embed.addField("Subcommands: ", getFormattedSubcommands(command)); + message.channel.send(embed); + return; + } + } + + embed.setTitle(`Help | ${commandName} command`); + embed.addField("Description:", command.settings?.description, true); + if (command.settings?.permissions) embed.addField("Permission(s) Required:", `\`${formatPermission(command.settings.permissions)}\``); + embed.addField("Usage:", `\`${client.prefix + command.settings?.usage}\``, true); + embed.setFooter("<> = Required | [] = Optional"); + + message.channel.send(embed); + } + }; + + return { + run: run, + settings: { + description: "Shows this message!", + usage: "help [command]", + maximumArgs: 1, + }, + }; +} + +function getFormattedCommandsInCategory(name: string, commands: Collection): string { + let msg = "```\n"; + for (const cmd of commands) { + if (cmd[1].category === name) msg += `${cmd[0]}\n`; + } + msg += "```"; + return msg; +} + +function getFormattedSubcommands(command: ICommand | undefined): string { + let msg = "```\n"; + for (const cmd of command?.subCommands!) { + msg += `${cmd[0]} | `; + if (cmd[1].settings) msg += cmd[1].settings.description; + msg += "\n"; + } + msg += "```"; + return msg; +} + +function formatPermission(permission: Permission): string { + switch (permission) { + case PermissionsEnum.DEVELOPER: + return "Developer"; + break; + case PermissionsEnum.ADMIN: + return "Admin"; + break; + case PermissionsEnum.STAFF: + return "Staff"; + default: + return permission.toString(); + break; + } +} + +function reFormat(str: string): string { + let newStr = ""; + + for (let i = 0; i < str.length; i++) { + let char = str.charAt(i); + + if (i === 0) { + newStr += char.toUpperCase(); + } else if (str.charAt(i - 1) === " ") { + newStr += char.toUpperCase(); + } else { + newStr += char.toLowerCase(); + } + } + return newStr; +} + +export default HelpCommand(); diff --git a/src/commands/utility/open.ts b/src/commands/utility/open.ts new file mode 100644 index 0000000..f09b5f5 --- /dev/null +++ b/src/commands/utility/open.ts @@ -0,0 +1,99 @@ +import { Message, MessageEmbed } from "discord.js"; +import Client from "../../structures/Client"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; +import Database from "../../utils/database/Database"; +import logger from "../../utils/logger/Logger"; +import Ticket, { TicketType } from "../../utils/ticket/Ticket"; + +let cooldown: string[] = []; + +function OpenCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!.id); + const openedTicket = settings.tickets.find((t) => t.member === message.author.id); + + if (openedTicket) { + message.reply(`You already have an open ticket! <#${openedTicket.channel}>`); + return; + } + + if (cooldown.includes(message.author.id)) { + message.channel.send("This command is on cooldown!"); + return; + } + + cooldown.push(message.author.id); + setTimeout(() => { + cooldown = cooldown.filter((ele) => { + ele != message.author.id; + }); + }, 61000); // 10m cooldown + + const embed = new MessageEmbed() + .setTitle("Create a ticket!") + .setDescription("React to which type of ticket you wish to create.") + .addField("Support", "🔧", true) + .addField("Bugs", "🐞", true) + .addField("Report", "⚠️", true) + .addField("Redeem", "💰", true); + message.channel.send(embed).then((msg) => { + msg.react("🔧").catch(() => {}); + msg.react("🐞").catch(() => {}); + msg.react("⚠️").catch(() => {}); + msg.react("💰").catch(() => {}); + + client.addReactionListener( + msg, + (client, reaction, user) => { + const ticket = new Ticket(message.member!, TicketType.SUPPORT, settings, client); + let invalid = false; + switch (reaction.emoji.name) { + case "🐞": + ticket.setType(TicketType.BUGS); + break; + case "⚠️": + ticket.setType(TicketType.REPORT); + break; + case "💰": + ticket.setType(TicketType.REDEEM); + break; + case "🔧": + ticket.setType(TicketType.SUPPORT); + break; + default: + invalid = true; + break; + } + if (!invalid) { + ticket + .create() + .then((channel) => { + message.reply(`Opened a ticket for you! ${channel?.toString()}`); + msg.delete(); + }) + .catch((err) => { + logger.error(err); + message.reply("Unable to create that ticket! Please contact a developer!"); + }); + } + }, + [message.author.id] + ); + + setTimeout(() => { + msg.delete().catch(() => {}); + }, 60000); + }); + }; + + return { + run: run, + settings: { + description: "Open a ticket", + usage: "open", + guildOnly: true + }, + }; +} + +export default OpenCommand(); diff --git a/src/commands/utility/ping.ts b/src/commands/utility/ping.ts new file mode 100644 index 0000000..7363265 --- /dev/null +++ b/src/commands/utility/ping.ts @@ -0,0 +1,19 @@ +import { Message } from "discord.js"; +import Client from "../../structures/Client"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +function PingCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + message.channel.send(`Bot latency is \`${Date.now() - message.createdTimestamp}ms\`, Pong!`); + }; + + return { + run: run, + settings: { + description: "Pings the bot!", + usage: "ping", + }, + }; +} + +export default PingCommand(); diff --git a/src/commands/utility/suggest.ts b/src/commands/utility/suggest.ts new file mode 100644 index 0000000..edb54b4 --- /dev/null +++ b/src/commands/utility/suggest.ts @@ -0,0 +1,67 @@ +import { Message, TextChannel, MessageEmbed } from "discord.js"; +import Client from "../../structures/Client"; +import Database from "../../utils/database/Database"; +import Channels from "../../data/calm/channels.json"; +import { ICommand, RunCallback } from "../../structures/Interfaces"; + +function SuggestCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + if (!message.guild || !message.member) return; + + let suggestionChannel: TextChannel, firstReaction: string, secondReaction: string; + if (message.guild.id === "501501905508237312") { + suggestionChannel = message.guild.channels.cache.find((chan) => chan.id === Channels.SUGGESTIONS.SUGGESTIONS.id) as TextChannel; + firstReaction = "615239771723137026"; // https://cdn.discordapp.com/emojis/615239771723137026.png?v=1 + secondReaction = "615239802127777817"; // https://cdn.discordapp.com/emojis/615239802127777817.png?v=1 + } else { + suggestionChannel = message.guild.channels.cache.find((chan) => chan.name === Channels.SUGGESTIONS.SUGGESTIONS.name) as TextChannel; + firstReaction = "✅"; + secondReaction = "❎"; + } + + if (!suggestionChannel) { + message.channel.send("We could not find the suggestion channel!"); + return; + } + + const suggestionEmbed = new MessageEmbed() + .setFooter(`${message.member.displayName}`, message.author.displayAvatarURL()) + .setColor("#007FFF") + .setTitle("Suggestion:") + .setDescription(args.join(" ")) + .setTimestamp(); + + message.channel.send("Thanks for the suggestion! \n**Check it out: <#" + suggestionChannel.id + ">**"); + + let guildSettings = await Database.getGuildSettings(message.guild.id); + + if (!(suggestionChannel instanceof TextChannel)) { + message.channel.send("Suggestion channel not a text channel!"); + } + + suggestionChannel + .send(suggestionEmbed) + .then((m) => { + m.react(firstReaction); + m.react(secondReaction); + + guildSettings.suggestions.push({ msgID: m.id, suggestorID: message.author.id, suggestorTag: message.author.tag, suggestion: args.join(" ") }); + guildSettings.save(); + }) + .catch((e) => { + message.channel.send("Unable to send message. Is the suggestions channel a voice channel or am I missing permission?"); + }); + }; + + return { + run: run, + settings: { + description: "desc", + usage: "usage", + guildOnly: true, + minimumArgs: 1, + }, + }; +} + +export default SuggestCommand(); diff --git a/src/commands/utility/ticket/addrole.ts b/src/commands/utility/ticket/addrole.ts new file mode 100644 index 0000000..3dbd126 --- /dev/null +++ b/src/commands/utility/ticket/addrole.ts @@ -0,0 +1,38 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function AddRoleCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!.id); + const roleid = args[0]!; + + const role = message.guild?.roles.cache.get(roleid); + if (!role) { + message.channel.send(`Could not find that role! Did you copy the id right?`); + return; + } + + if (settings.ticketRoles.includes(roleid)) { + message.channel.send(`Role is already added! Do ${client.prefix}ticket removerole to remove it`); + return; + } + + settings.ticketRoles.push(roleid); + settings.save(); + message.reply(`Added ${role.name}`); + }; + + return { + run: run, + settings: { + description: "Add a role to the list of roles to gain access to tickets", + usage: "ticket addrole ", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN, + }, + }; +} + +export default AddRoleCommand(); diff --git a/src/commands/utility/ticket/close.ts b/src/commands/utility/ticket/close.ts new file mode 100644 index 0000000..623b866 --- /dev/null +++ b/src/commands/utility/ticket/close.ts @@ -0,0 +1,27 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; +import { deleteTicket } from "../../../utils/ticket/Ticket"; + +function CloseCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!.id); + if (!settings.tickets.find((t) => t.channel === message.channel.id)) { + message.channel.send("You must send this command in a ticket!"); + return; + } + + deleteTicket(message.channel, settings); + }; + + return { + run: run, + settings: { + description: "Close a ticket", + usage: "ticket close", + }, + }; +} + +export default CloseCommand(); diff --git a/src/commands/utility/ticket/removerole.ts b/src/commands/utility/ticket/removerole.ts new file mode 100644 index 0000000..7ef3027 --- /dev/null +++ b/src/commands/utility/ticket/removerole.ts @@ -0,0 +1,38 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function RemoveRoleCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!.id); + const roleid = args[0]!; + + const role = message.guild?.roles.cache.get(roleid); + if (!role) { + message.channel.send(`Could not find that role! Did you copy the id right?`); + return; + } + + if (!settings.ticketRoles.includes(roleid)) { + message.channel.send(`Role is not added! Do ${client.prefix}ticket addrole to add it`); + return; + } + + settings.ticketRoles = settings.ticketRoles.filter((ele) => ele != roleid); + settings.save(); + message.reply(`Removed ${role.name}`); + }; + + return { + run: run, + settings: { + description: "Removes a role from the list of roles to gain access to tickets", + usage: "ticket removerole ", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN, + }, + }; +} + +export default RemoveRoleCommand(); diff --git a/src/commands/utility/ticket/setsupportrole.ts b/src/commands/utility/ticket/setsupportrole.ts new file mode 100644 index 0000000..2b75578 --- /dev/null +++ b/src/commands/utility/ticket/setsupportrole.ts @@ -0,0 +1,39 @@ +import { Message } from "discord.js"; +import Client from "../../../structures/Client"; +import { ICommand, PermissionsEnum, RunCallback } from "../../../structures/Interfaces"; +import Database from "../../../utils/database/Database"; + +function SetSupportRoleCommand(): ICommand { + const run: RunCallback = async (client: Client, message: Message, args: string[]) => { + const settings = await Database.getGuildSettings(message.guild!.id); + const roleid = args[0]!; + if (roleid?.toLowerCase() === "remove") { + settings.ticketSupportedRole = undefined; + settings.save(); + message.reply("Removed the supported role!"); + return; + } + + const role = message.guild?.roles.cache.get(roleid); + if (!role) { + message.channel.send(`Could not find that role! Do ${client.prefix}ticket settings setsupportrole remove to remove it.`); + return; + } + + settings.ticketSupportedRole = roleid; + settings.save(); + message.reply("Set the role to " + role.name); + }; + + return { + run: run, + settings: { + description: "Set the role to be tagged every time a ticket is created", + usage: "ticket setsupportrole / remove", + minimumArgs: 1, + permissions: PermissionsEnum.ADMIN + }, + }; +} + +export default SetSupportRoleCommand(); diff --git a/src/commands/utility/ticket/subcommandSettings.ts b/src/commands/utility/ticket/subcommandSettings.ts new file mode 100644 index 0000000..fc41b9a --- /dev/null +++ b/src/commands/utility/ticket/subcommandSettings.ts @@ -0,0 +1,7 @@ +import { ISubCommandSettings } from "../../../structures/Interfaces"; + +const settings: ISubCommandSettings = { + guildOnly: true, +}; + +export default settings; diff --git a/src/commands/verbal.ts b/src/commands/verbal.ts deleted file mode 100644 index a90d26a..0000000 --- a/src/commands/verbal.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { Guild, GuildMember, Message, MessageAttachment, MessageEmbed } from "discord.js"; -import Client from "../structures/Client"; -import Database from "../utils/database/Database"; -import Roles from "../data/calm/roles.json"; - -const staffRoles = [Roles.GENERAL.STAFF_TEAM, Roles.GENERAL.DISCORD_STAFF, Roles.GENERAL.MANAGEMENT_TEAM]; - -module.exports = { - name: "verbal", - aliases: ["verbalwarn"], - description: "Issue a verbal warning", - category: "Moderation", - usage: "verbal ", - run: async function run(client: Client, message: Message, args: Array) { - // c!verbal (add, remove, info) (userid, casenumber) (reason) (attached image) - if (!(await isMod(message.member))) return message.channel.send("Missing permissions."); - - let guildSettings = await Database.getGuildSettings(message.guild.id); - - if (args.length === 0) { - const embed = new MessageEmbed() - .setTitle("Verbal Warnings") - .setDescription("Do c!verbal help for help on how to use this command.") - .addField("Total Cases:", guildSettings.punishmentcases - 1) - .addField("Active Cases:", guildSettings.verbals.length) - .setColor("#17c1eb"); - return message.channel.send(embed); - } - - args[0] = args[0].toLowerCase(); - if (args[0] === "add") { - // c!verbal add (userid) [reason] - if (args.length < 2) return message.channel.send("Invalid Arguments. Example: c!verbal add (userid) (reason) (attached image)"); - if (!attachIsImage(message.attachments.array()[0])) return message.channel.send("Invalid arguments. Please provide reasoning as text and evidence as an attached image."); - if (args.length < 3) return message.channel.send("Invalid arguments. Please provide reasoning as text and evidence as an attached image."); - - const id = args[1]; - - let reason: string = ""; - let imgurl = message.attachments.array()[0].url; - for (let i = 2; i < args.length; i++) { - reason += args[i] + " "; - } - // Remove extra space - reason.substring(0, reason.length - 1); - - let member: GuildMember = await getMember(id as string, message.guild); - if (member === undefined) return message.channel.send(`Could not find user with id: ${id}`); - - if (reason.length > 1020) return message.channel.send("Your reason is too long. Attach an image and give text that is < 1020 characters (due to discord embed limitations)"); - - const casenumber = guildSettings.punishmentcases; - guildSettings.verbals.push({ moderator: message.author.id, user: member.id, reasonText: reason, reasonImage: imgurl, casenumber: casenumber }); - guildSettings.punishmentcases = guildSettings.punishmentcases + 1; - await guildSettings.save(); - - const embed = new MessageEmbed().setTitle(`${member.user.tag} has been verbal warned! Case: ${casenumber}`).setColor("#48db8f"); - message.channel.send(embed); - } else if (args[0] === "remove") { - // c!verbal remove (casenumber) - if (args.length < 2) { - return message.channel.send("Invalid Arguments. Example: c!verbal remove (casenumber)"); - } - - const verbal = guildSettings.verbals.find((element) => element.casenumber === Number.parseInt(args[1] as string)); - if (verbal === undefined) { - return message.channel.send(`Could not find verbal warning with case id ${args[1]}`); - } - - guildSettings.verbals = arrayRemove(guildSettings.verbals, verbal); - guildSettings.save(); - - const embed = new MessageEmbed().setTitle(`Removed verbal case ${args[1]}`).setColor("#48db8f"); - message.channel.send(embed); - } else if (args[0] === "info") { - // c!verbal info (userid) - if (args.length < 2) return message.channel.send("Invalid Arguments. Example: c!verbal info (userid)"); - - const warnings = await getUserWarnings(message.guild.id, args[1] as string); - if (warnings.length === 0) return message.channel.send("No warnings found for user: " + args[1]); - - let membertext = args[1]; - const member = await getMember(args[1] as string, message.guild); - if (member !== undefined) membertext = member.user.tag; - - warnings.forEach(async (warning) => { - let embed = new MessageEmbed(); - embed.setTitle(membertext + "'s verbal warnings"); - embed.setColor("#eb1717"); - - let modtext = warning.moderator; - const mod = await getMember(warning.moderator, message.guild); - if (mod !== undefined) modtext = mod.user.tag; - - embed.addField("Case number: ", warning.casenumber, true); - embed.addField("Moderator: ", modtext); - if (warning.reasonText !== undefined) embed.addField("Reason: ", warning.reasonText); - if (warning.reasonImage !== undefined) embed.setImage(warning.reasonImage); - - await message.channel.send(embed); - }); - } else if (args[0] === "case") { - // c!verbal info (caseid) - if (args.length < 2) return message.channel.send("Invalid Arguments. Example: c!verbal case (caseid)"); - const warning = await getWarningFromCase(message.guild.id, args[1] as string); - - if (warning === undefined) return message.channel.send("Could not find an active or inactive verbal with caseid " + args[1]); - - let embed = new MessageEmbed(); - embed.setTitle("Case Number: " + args[1]); - embed.setColor("#eb1717"); - - let membertext = warning.user; - const member = await getMember(warning.user as string, message.guild); - if (member !== undefined) membertext = member.user.tag; - - let modtext = warning.moderator; - const mod = await getMember(warning.moderator, message.guild); - if (mod !== undefined) modtext = mod.user.tag; - - embed.addField("User:", membertext); - embed.addField("Moderator:", modtext); - if (warning.reasonText !== undefined) embed.addField("Reason: ", warning.reasonText); - if (warning.reasonImage !== undefined) embed.setImage(warning.reasonImage); - - return message.channel.send(embed); - } else if (args[0] === "help") { - const embed = new MessageEmbed() - .setTitle("Verbal Warning Command:") - .addField("c!verbal", "Shows some verbal warning information.") - .addField("c!verbal add (discord-id) (reason) (attached-image)", "Adds a verbal warning to a user and assigns it a case number.") - .addField("c!verbal remove (case-id)", "Removes a verbal warnings.") - .addField("c!verbal info (user-id)", "Get all verbal warnings for a specific user.") - .addField("c!verbal help", "Shows this message.") - .addField("c!verbal flush", "Admin only command (mainly used in development) used to delete all verbal warns and all cases.") - .setColor("#17c1eb"); - return message.channel.send(embed); - } else if (args[0] === "flush") { - if (!message.member.hasPermission(["ADMINISTRATOR"])) { - return message.channel.send("Missing Permissions.\nREQUIRED: **ADMINISTRATOR**"); - } - guildSettings.verbals = []; - guildSettings.punishmentcases = 1; - await guildSettings.save(); - return message.channel.send("Deleted all verbal warning data."); - } - }, -}; - -async function isMod(member: GuildMember) { - if (member.hasPermission(["ADMINISTRATOR"])) return true; - let hasModRole = false; - for (let i = 0; i < staffRoles.length; i++) { - if (member.guild.id === "501501905508237312") { - if (member.roles.cache.find((r) => r.id === staffRoles[i].id)) { - return true; - } - } else { - if (member.roles.cache.find((r) => r.name.toLowerCase() === staffRoles[i].name.toLowerCase())) { - return true; - } - } - } - return false; -} - -function attachIsImage(msgAttach: MessageAttachment) { - if (msgAttach === undefined) return false; - var url = msgAttach.url; - if (url === undefined) return false; - return url.indexOf("png", url.length - "png".length /*or 3*/) !== -1 || url.indexOf("jpeg", url.length - "jpeg".length /*or 3*/) !== -1 || url.indexOf("jpg", url.length - "jpg".length /*or 3*/) !== -1; -} - -async function getUserWarnings(guildID: string, userID: string) { - let arr = []; - let guildSettings = await Database.getGuildSettings(guildID); - guildSettings.verbals.forEach((element) => { - if (element.user === userID) { - arr.push(element); - } - }); - return arr; -} - -function arrayRemove(arr: Array, value: any) { - return arr.filter(function (ele) { - return ele != value; - }); -} - -async function getMember(id: string, guild: Guild) { - let member: GuildMember; - try { - member = await guild.members.fetch(id as string); - } catch { - return undefined; - } - return member; -} - -async function getWarningFromCase(guildID: string, caseID: string) { - let guildSettings = await Database.getGuildSettings(guildID); - let warning: any; - guildSettings.verbals.forEach((element) => { - if ((element.casenumber.toString() as string) === caseID) { - warning = element; - } - }); - return warning; -} diff --git a/src/commands/vouch.ts b/src/commands/vouch.ts deleted file mode 100644 index ae2a754..0000000 --- a/src/commands/vouch.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Message, TextChannel } from "discord.js"; -import Client from "../structures/Client"; -import channels from "../data/calm/channels.json"; - -module.exports = { - name: "vouch", - aliases: ["vouching", "vouchsystem"], - description: "Explains our vouching system!", - category: "Information", - usage: "vouch", - run: async function run(client: Client, message: Message) { - let commandsChannel: TextChannel, infoChannel: TextChannel; - - if (message.guild.id === "501501905508237312") { - commandsChannel = message.guild.channels.cache.find((chan) => chan.id === channels.COMMUNITY.COMMANDS.id) as TextChannel; - infoChannel = message.guild.channels.cache.find((chan) => chan.id === channels.UPON_JOINING.INFO.id) as TextChannel; - } else { - commandsChannel = message.guild.channels.cache.find((chan) => chan.name === channels.COMMUNITY.COMMANDS.name) as TextChannel; - infoChannel = message.guild.channels.cache.find((chan) => chan.name === channels.UPON_JOINING.INFO.name) as TextChannel; - } - if (!commandsChannel || !infoChannel) { - return message.reply("we could not find the commands / info channel!"); - } - - const vouchSystem = - "**Vouch System**\n" + - "To be eligible to vouch a member in Calm, you must be Noble+.\n" + - `To be vouched into Calm, you must meet the vouch requirements as stated in ${infoChannel}\n\n` + - "Each rank below gets the number of vouches listed PER MONTH.\n\n" + - "Noble: 3\nLoyal: 4\nElites: 5\nOG: 6\nGuild Staff: 7\n\n" + - `*If you have any questions, please do \`t!open\` in ${commandsChannel} to open a ticket and talk to staff*`; - - message.channel.send(vouchSystem); - }, -}; diff --git a/src/data/calm/Interfaces.ts b/src/data/calm/Interfaces.ts new file mode 100644 index 0000000..b7489da --- /dev/null +++ b/src/data/calm/Interfaces.ts @@ -0,0 +1,5 @@ +export interface IChallenge { + name: string; + points: number; + id: string; +} diff --git a/src/data/calm/challenges/Challenges.json b/src/data/calm/challenges/Challenges.json new file mode 100644 index 0000000..7284b01 --- /dev/null +++ b/src/data/calm/challenges/Challenges.json @@ -0,0 +1,229 @@ +{ + "1": { + "name": "Get 50k GEXP in a day", + "points": 1, + "id": "e1" + }, + "2": { + "name": "Click the dragon egg in the pit 15 times", + "points": 1, + "id": "e2" + }, + "3": { + "name": "Have someone give you cookies in Housing.", + "points": 1, + "id": "e3" + }, + "4": { + "name": "Break 3 beds in 3s/4s Bedwars.", + "points": 1, + "id": "e4" + }, + "5": { + "name": "Win Quake.", + "points": 1, + "id": "e5" + }, + "6": { + "name": "Win Farm Hunt as an animal.", + "points": 1, + "id": "e6" + }, + "7": { + "name": "Obtain a strength pot in Speed UHC.", + "points": 1, + "id": "e7" + }, + "8": { + "name": "TNT or Fireball at least 40 blocks in Bedwars Practice mode.", + "points": 1, + "id": "e8" + }, + "9": { + "name": "Win Easter Simulator", + "points": 1, + "id": "e9" + }, + "10": { + "name": "Get a Duels winstreak of 25", + "points": 1, + "id": "e10" + }, + "11": { + "name": "Find 30 eggs in a game of Easter Simulator", + "points": 1, + "id": "e11" + }, + "12": { + "name": "Get a screenshot with 5 or more Calm Members", + "points": 1, + "id": "e12" + }, + "13": { + "name": "Finish a Bedwars quest", + "points": 1, + "id": "e13" + }, + "14": { + "name": "Match and chat with a Calm member on bwtinder", + "points": 1, + "id": "e14" + }, + "15": { + "name": "Wear the Chicken Head in Skyblock", + "points": 1, + "id": "e15" + }, + + "16": { + "name": "Get 35xp in a Skywars game", + "points": 3, + "id": "m1" + }, + "17": { + "name": "Get 150k GEXP in a day", + "points": 3, + "id": "m2" + }, + "18": { + "name": "Get a heavenly head in Skywars", + "points": 3, + "id": "m3" + }, + "19": { + "name": "Beat a dungeon with a score of S+ in under 5mins", + "points": 3, + "id": "m4" + }, + "20": { + "name": "Collect 250 raffle tickets in a Skyblock event", + "points": 3, + "id": "m5" + }, + "21": { + "name": "Win PVP Run without double jumps", + "points": 3, + "id": "m6" + }, + "22": { + "name": "Get 10 kills in doubles Skywars", + "points": 3, + "id": "m7" + }, + "23": { + "name": "Win Quake with 2nd place having <25", + "points": 3, + "id": "m8" + }, + "24": { + "name": "Win Prop Hunt as a hider", + "points": 3, + "id": "m9" + }, + "25": { + "name": "Place an extra bed in Bedwars Lucky Block", + "points": 3, + "id": "m10" + }, + "26": { + "name": "Win Easter Simulator with 50 eggs", + "points": 3, + "id": "m11" + }, + "27": { + "name": "Get a Duels winstreak of 50", + "points": 3, + "id": "m12" + }, + "28": { + "name": "Win Skywars with 2 challenges on", + "points": 3, + "id": "m13" + }, + "29": { + "name": "Find the Bunny in the Easter Simulator game", + "points": 3, + "id": "m14" + }, + "30": { + "name": "Win a game of Duels with 50% accuracy", + "points": 3, + "id": "m15" + }, + + "31": { + "name": "Get 300k GEXP in a day", + "points": 5, + "id": "h1" + }, + "32": { + "name": "Get 6 beds in a Solo Bedwars Game", + "points": 5, + "id": "h2" + }, + "33": { + "name": "Win Quake with 2nd place having <20", + "points": 5, + "id": "h3" + }, + "34": { + "name": "Complete the Bedwars parkour in under 43 seconds", + "points": 5, + "id": "h4" + }, + "35": { + "name": "Win Easter Simulator with 70 eggs", + "points": 5, + "id": "h5" + }, + "36": { + "name": "Get a Duels winstreak of 75", + "points": 5, + "id": "h6" + }, + "37": { + "name": "Win Skywars with 6 or more challenges on", + "points": 5, + "id": "h7" + }, + "38": { + "name": "Kill 7 or 8 people in Solo Skywars", + "points": 5, + "id": "h8" + }, + "39": { + "name": "Win a game of Duels with 85% accuracy", + "points": 5, + "id": "h9" + }, + "40": { + "name": "Win a game of Vampirez as last survivor", + "points": 5, + "id": "h10" + }, + "41": { + "name": "Kill all players as murderer in Classic MM", + "points": 5, + "id": "h11" + }, + "42": { + "name": "Win a game of Bedwars in under 2 mins", + "points": 5, + "id": "h12" + }, + "43": { + "name": "Get 45xp or more in a Skywars game", + "points": 5, + "id": "h13" + }, + "44": { + "name": "Get 150 kills or more in a Paintball game", + "points": 5, + "id": "h14" + }, + "45": { + "name": "Win a game of Smash Heroes without losing a life", + "points": 5, + "id": "h15" + } +} diff --git a/src/data/calm/challenges/FeburaryChallenges.json b/src/data/calm/challenges/FeburaryChallenges.json new file mode 100644 index 0000000..b8dee74 --- /dev/null +++ b/src/data/calm/challenges/FeburaryChallenges.json @@ -0,0 +1,230 @@ +{ + "1":{ + "name": "Win a game of SOLO Skywars with 60 shards or more.", + "points": 1, + "id": "e1" + }, + "2":{ + "name": "Reach a killstreak of 5 in the duels lobby arena.", + "points": 1, + "id": "e2" + }, + "3":{ + "name": "Place top 3 in a game of party games.", + "points": 1, + "id": "e3" + }, + "4":{ + "name": "Win a game of TNT wizards.", + "points": 1, + "id": "e4" + }, + "5":{ + "name": "Get a duels winstreak of 5.", + "points": 1, + "id": "e5" + }, + "6":{ + "name": "Get a bedwars winstreak of 5.", + "points": 1, + "id": "e6" + }, + "7":{ + "name": "Win a game of build battle in any mode.", + "points": 1, + "id": "e7" + }, + "8":{ + "name": "Win a game of bedwars with 4 final kills.", + "points": 1, + "id": "e8" + }, + "9":{ + "name": "Witness a rank gifting (in lobby)", + "points": 1, + "id": "e9" + }, + "10":{ + "name": "Find a blue + loser in lobby/in a game. (hopez do not count)", + "points": 1, + "id": "e10" + }, + "11":{ + "name": "Win a game of ranked skywars", + "points": 1, + "id": "e11" + }, + "12":{ + "name": "Score 5-0 against someone in solo bridge", + "points": 1, + "id": "e12" + }, + "13":{ + "name": "Win a game of bedwars dream mode", + "points": 1, + "id": "e13" + }, + "14":{ + "name": "Punch a staff member", + "points": 1, + "id": "e14" + }, + "15":{ + "name": "Complete any lobby’s parkour", + "points": 1, + "id": "e15" + }, + + "16":{ + "name": "Get a killstreak of 50 in the pit", + "points": 3, + "id": "m1" + }, + "17":{ + "name": "Claim a bounty of 500g or more in the pit", + "points": 3, + "id": "m2" + }, + "18":{ + "name": "Win a game of SOLO skywars with 200 shards or more", + "points": 3, + "id": "m3" + }, + "19":{ + "name": "Win a game of TNT run", + "points": 3, + "id": "m4" + }, + "20":{ + "name": "Reach a duels winstreak of 15", + "points": 3, + "id": "m5" + }, + "21":{ + "name": "Reach a bedwars winstreak of 25", + "points": 3, + "id": "m6" + }, + "22":{ + "name": "Win a game of teams build battle solo", + "points": 3, + "id": "m7" + }, + "23":{ + "name": "Win a game of bedwars with 7 finals", + "points": 3, + "id": "m8" + }, + "24":{ + "name": "Wear full dia armor in solo normal skywars", + "points": 3, + "id": "m9" + }, + "25":{ + "name": "Have 16 or more pearls in your inv at once in skywars", + "points": 3, + "id": "m10" + }, + "26":{ + "name": "Win a game of TNT tag", + "points": 3, + "id": "m11" + }, + "27":{ + "name": "Win a game of ender spleef", + "points": 3, + "id": "m12" + }, + "28":{ + "name": "Obtain a notch apple in SOLO UHC", + "points": 3, + "id": "m13" + }, + "29":{ + "name": "Place top 3 in a TKR race", + "points": 3, + "id": "m14" + }, + "30":{ + "name": "Win a solo quake game", + "points": 3, + "id": "m15" + }, + + "31":{ + "name": "Claim a bounty of 1500g or more in the pit", + "points": 5, + "id": "h1" + }, + "32":{ + "name": "Win a game of SOLO skywars with 350 shards or more", + "points": 5, + "id": "h2" + }, + "33":{ + "name": "Reach a killstreak of 25 in the duels lobby arena", + "points": 5, + "id": "h3" + }, + "34":{ + "name": "Reach a duels winstreak of 30", + "points": 5, + "id": "h4" + }, + "35":{ + "name": "Reach a bedwars winstreak of 50", + "points": 5, + "id": "h5" + }, + "36":{ + "name": "Win a game of speed uhc", + "points": 5, + "id": "h6" + }, + "37":{ + "name": "Get 20 dias before pvp in a game of SOLO uhc", + "points": 5, + "id": "h7" + }, + "38":{ + "name": "Capture both wools at the same time in a game of ctw", + "points": 5, + "id": "h8" + }, + "39":{ + "name": "Win a game of bedwars with 10 finals", + "points": 5, + "id": "h9" + }, + "40":{ + "name": "Get 24 stars in a party games game", + "points": 5, + "id": "h10" + }, + "41":{ + "name": "Get the legendary dia rain in a SOLO build battle game", + "points": 5, + "id": "h11" + }, + "42":{ + "name": "Win a corrupt SOLO skywars game with 35xp or more", + "points": 5, + "id": "h12" + }, + "43":{ + "name": "Place top 3 in a major event in the pit", + "points": 5, + "id": "h13" + }, + "44":{ + "name": "Get a winstreak of 50 in one dreams mode for bedwar", + "points": 5, + "id": "h14" + }, + "45":{ + "name": "Get gifts from a daily reward", + "points": 5, + "id": "h15" + } + +} \ No newline at end of file diff --git a/src/data/calm/channels.json b/src/data/calm/channels.json index 31b6826..e6020cf 100644 --- a/src/data/calm/channels.json +++ b/src/data/calm/channels.json @@ -256,10 +256,20 @@ } }, "STAFF": { + "STAFF_CHAT": { + "name": "staff-chat", + "id": "547923467522015263", + "public": false + }, "CHALLENGES": { "name": "challenge-requests", "id": "787202572032278539", "public": false + }, + "MOD_LOG": { + "name": "mod-log", + "id": "777620674520416276", + "public": false } } } diff --git a/src/data/calm/roles.json b/src/data/calm/roles.json index b262cfe..a4b2758 100644 --- a/src/data/calm/roles.json +++ b/src/data/calm/roles.json @@ -16,6 +16,10 @@ "name": "Sr Officer", "id": "644368780515868703" }, + "DISCORD_MANAGER": { + "name": "Discord Manager", + "id": "750203201302692000" + }, "INACTIVE": { "name": "Inactive", "id": "591346583501668358" @@ -55,6 +59,10 @@ "NITRO_BOOSTER": { "name": "Nitro Booster", "id": "585574765444595781" + }, + "BOTS": { + "name": "Bots", + "id": "501507387765555200" } }, "RANK": { diff --git a/src/data/calm/staffuuids.ts b/src/data/calm/staffuuids.ts deleted file mode 100644 index 0d33994..0000000 --- a/src/data/calm/staffuuids.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const staffUUID = [ - "3a3c9d90-ab98-47b4-bc5d-73196a65e1d9", // hopez - "5791af76-b724-4f9b-90e7-e7869f1e1aa0", // ohLaike - "6984b889-5ff1-4e10-b549-76751169c91a", // Guitarking257 - "78d0f4bb-658e-4e9c-96ad-be5742a65c94", // Partaken - "3c36c369-c7fc-4305-8fd2-3055c9024d9c", // kiyeo - "c1f69253-3be0-4aae-b37d-ab100e5d3af6", // minik_ - "433b142d-bf96-4db0-9f16-10f7f7912565", // Nitwu - "3028e941-3ecc-4d2b-afed-bbdadc8aaa75", // Purplest - "79990cdf-6357-48f7-8437-565102010d74", // SkillBeatsAll - "a919ee94-adfc-4f0a-af98-20d79409b86d", // InstinctChan - "bf412996-c1e4-424b-880d-1d5788628e95", // Dayfruits - "5ef697d9-35e1-4fe8-a3bb-5a2715cfb02b", // myopic -]; \ No newline at end of file diff --git a/src/data/img/bunny.json b/src/data/img/bunny.json deleted file mode 100644 index 6d4b519..0000000 --- a/src/data/img/bunny.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/1.jpeg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/10.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/11.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/12.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/13.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/14.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/15.png", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/16.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/17.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/18.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/19.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/20.png", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/21.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/22.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/23.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/24.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/25.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/26.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/3.jpg", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/5.png", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/6.png", - "https://calm-bot-media.s3.us-east-2.amazonaws.com/bunny/7.jpg" -] diff --git a/src/events/message.ts b/src/events/message.ts index 70fb16a..a667e65 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,59 +1,41 @@ -import { Message, TextChannel } from "discord.js"; +import { Message } from "discord.js"; import DiscordClient from "../structures/Client"; import Database from "../utils/database/Database"; -module.exports = async function message(client: DiscordClient, message: Message) { - if (message.author.bot || message.channel.type === "dm") return; - - // #count-to-x channel code so invalid numbers are deleted and channel name is updated - let currentChannel = message.channel as TextChannel; - if (currentChannel.name.startsWith("count-to-")) { - const messageList = await message.channel.messages.fetch({ limit: 2 }); - const previousMessage = messageList.last(); - const previousCount = parseInt(previousMessage.content, 10); - const currentCount = parseInt(message.content, 10); - - // Makes sure user does not send message twice in a row - if (message.author.tag === previousMessage.author.tag) { - return message.delete(); - } - // Checks if it is correct number OR if the message is not a number at all - if (currentCount != previousCount + 1) { - return message.delete(); - } +export default async function message(client: DiscordClient, message: Message) { + if (message.author.bot) return; - // Checks if count is divisible by 1000, if so changes the channel name to #count-to-(current count + 1000) - if (currentCount % 1000 === 0) { - return currentChannel.setName(`count-to-${Math.floor((currentCount + 1000) / 1000)}k`); - } - } // End count-to code. + const prompt = client.promptListeners.find((p) => p.channel === message.channel.id && p.user === message.author.id); + if (prompt) { + client.promptListeners = client.promptListeners.filter((ele) => { + ele !== prompt; + }); - if (!message.content.toLowerCase().startsWith(client.prefix)) return; - - let guildSettings = await Database.getGuildSettings(message.guild.id); + if (message.content.toLowerCase() === "exit") { + message.channel.send("Exited."); + return; + } - const args = message.content.slice(client.prefix.length).trim().split(/ +/g); + prompt.callback(message); + return; + } - const command = args.shift().toLowerCase(); + if (!message.content.toLowerCase().startsWith(client.prefix)) return; - let cmd: any = client.commands.get(command); - if (!cmd) { - cmd = client.aliases.get(command); - } - if (!cmd) return; - if (cmd) { - if (guildSettings.disabledCommands.includes(cmd.name)) return; - if (cmd.name !== "admin" && guildSettings.sleep) return message.channel.send("Bot is in sleep mode! Do c!admin sleep to turn it back on!"); + const args: string[] = message.content.slice(client.prefix.length).trim().split(/ +/g); + const commandName = args.shift()!.toLowerCase(); - // Temporary disable of all commands in Calm #general Channel - if (message.guild.id === "501501905508237312" && message.channel.id === "501501905508237315") { + if (message.guild) { + const settings = await Database.getGuildSettings(message.guild.id); // gen guild settings + const tag = settings.tags.find((t) => t.name === commandName); + if (tag) { + message.channel.send(tag.response); return; } - if (cmd.permissions) { - let missingPerms = []; - missingPerms = cmd.permissions.filter((permission) => !message.member.hasPermission(permission)); - if (missingPerms.length) return message.channel.send(`You are missing the following permissions required to run this command: ${missingPerms.map((x) => `\`${x}\``).join(", ")}`); - } - cmd.run(client, message, args); } -}; + + let command: any = client.commands.get(commandName); + if (command) { + client.executeCommand(command, message, args); + } +} diff --git a/src/events/messageReactionAdd.ts b/src/events/messageReactionAdd.ts index e0e4ee1..2148e35 100644 --- a/src/events/messageReactionAdd.ts +++ b/src/events/messageReactionAdd.ts @@ -3,73 +3,83 @@ import Roles from "../data/calm/roles.json"; import Client from "../structures/Client"; import Database from "../utils/database/Database"; -import { MessageReaction, MessageEmbed, User, Message, TextChannel, Role, GuildChannel } from "discord.js"; +import { MessageReaction, MessageEmbed, User, Message, TextChannel, Role } from "discord.js"; -module.exports = async function messageReactionAdd(client: Client, reaction: MessageReaction, user: User) { - if (user.bot) return; +export default async function messageReactionAdd(client: Client, reaction: MessageReaction, user: User) { + const reactionListener = client.reactionListeners.find((r) => r.messageid === reaction.message.id); + if (reactionListener && !user.bot) { + if (reactionListener.userwhitelist && !reactionListener.userwhitelist.includes(user.id)) return; + reactionListener.callback(client, reaction, user); + return; + } + + if (user.bot || !reaction.message.guild) return; const message: Message = await reaction.message.channel.messages.fetch(reaction.message.id); - if (!message.author.bot) return; + if (!message.author.bot || !message.guild) return; const channel = reaction.message.guild.channels.cache.find((c) => c.id === reaction.message.channel.id); if (channel === undefined) return; if (message.guild.id === "501501905508237312" && message.channel.id !== Channels.STAFF.CHALLENGES.id) return; else if (message.guild.id !== "501501905508237312" && channel.name !== Channels.STAFF.CHALLENGES.name) return; - let monthlyTeamRole: Role; + let monthlyTeamRole: Role | undefined; if (message.guild.id === "501501905508237312") { monthlyTeamRole = message.guild.roles.cache.find((r) => r.id === Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.id); } else { monthlyTeamRole = message.guild.roles.cache.find((r) => r.name === Roles.GENERAL.MONTHLY_CHALLENGES_TEAM.name); } - if (monthlyTeamRole === undefined) { + if (!monthlyTeamRole) { message.channel.send(`<@${user.id}> Error! Unable to find role Monthly Challenges Team in this server! Please ask a server admin to make one and then assign it to you!`); return; } const member = message.guild.members.fetch(user); - if ((await member).roles.cache.find((r) => r.id === monthlyTeamRole.id) === undefined) { + if ((await member).roles.cache.find((r) => r.id === monthlyTeamRole?.id) === undefined) { message.channel.send(`<@${user.id}> Error! As you do not have the Monthly Challenges Team role you are unable to approve this challenge!`); return; } - if (message.embeds[0].hexColor === "#ff0000" || message.embeds[0].hexColor === "#3cff00") return; + if (message.embeds[0]?.hexColor === "#ff0000" || message.embeds[0]?.hexColor === "#3cff00") return; if (reaction.emoji.toString() === "✅") { - const fields = message.embeds[0].fields; - const userID = fields.find((f) => f.name.toLowerCase() === "user id:").value; - const challengeID = fields.find((f) => f.name.toLowerCase() === "challenge id:").value; + const fields = message.embeds[0]?.fields; + const userID = fields?.find((f) => f.name.toLowerCase() === "user id:")?.value; + const challengeID = fields?.find((f) => f.name.toLowerCase() === "challenge id:")?.value; + + if (!userID || !challengeID) return; let participant = await Database.getChallengeParticipant(userID); + if (!participant) return; participant.completedChallenges.set(challengeID, "true"); await participant.save(); const embed = new MessageEmbed(); - embed.setTitle(message.embeds[0].title.replace("Challenge Request", "Accepted Challenge Request")); + embed.setTitle(message.embeds[0]?.title?.replace("Challenge Request", "Accepted Challenge Request")); embed.setColor("#3cff00"); embed.addField("Challenge ID:", challengeID); - embed.addField("Challenge Name:", message.embeds[0].fields.find((f) => f.name === "Challenge Name:").value); + embed.addField("Challenge Name:", message.embeds[0]?.fields?.find((f) => f.name === "Challenge Name:")?.value); - if (message.embeds[0].image !== null) { + if (message.embeds[0]?.image?.url) { embed.setImage(message.embeds[0].image.url); } else { - embed.addField("Proof", message.embeds[0].fields.find((f) => f.name === "Proof").value); + embed.addField("Proof", message.embeds[0]?.fields?.find((f) => f.name === "Proof")?.value); } embed.addField("Accepted by:", user.username + "#" + user.discriminator); message.edit(embed); - let commandChannel; + let commandChannel: TextChannel; if (message.guild.id === "501501905508237312") { - commandChannel = message.guild.channels.cache.find((c) => c.id === Channels.COMMUNITY.COMMANDS.id); + commandChannel = message.guild.channels.cache.find((c) => c.id === Channels.WEEKLY_MONTHLY.CHALLENGE_PROOF.id) as TextChannel; } else { - commandChannel = message.guild.channels.cache.find((c) => c.name === Channels.COMMUNITY.COMMANDS.name); + commandChannel = message.guild.channels.cache.find((c) => c.name === Channels.WEEKLY_MONTHLY.CHALLENGE_PROOF.name) as TextChannel; } - if (commandChannel !== undefined) { - commandChannel.send(`Congratulations, <@${userID}>. Your challenge request for challenge #${challengeID} has been accepted. Do ${client.prefix}challenge check, to check your progress.`); - } + commandChannel = commandChannel as TextChannel; + commandChannel?.send(`Congratulations, <@${userID}>. Your challenge request for challenge #${challengeID} has been accepted. Do ${client.prefix}challenge check, to check your progress.`); + message.reactions.removeAll(); } -}; +} diff --git a/src/events/messageUpdate.ts b/src/events/messageUpdate.ts index 7ae7f9e..2ac634f 100644 --- a/src/events/messageUpdate.ts +++ b/src/events/messageUpdate.ts @@ -1,7 +1,7 @@ import { Message, TextChannel } from "discord.js"; import Client from "../structures/Client"; -module.exports = async function messageUpdate(client: Client, oldMessage: Message, newMessage: Message) { +export default async function messageUpdate(client: Client, oldMessage: Message, newMessage: Message) { let currentChannel = oldMessage.channel as TextChannel; // compromise that prevents one person from breaking the entire channel without it being possible for the bot to ruin anything if (currentChannel.name.startsWith("count-to-")) { diff --git a/src/events/ready.ts b/src/events/ready.ts index 8fdd7b4..f5bc9d0 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,7 +1,7 @@ import Client from "../structures/Client"; +import Logger from "../utils/logger/Logger"; -module.exports = async function ready(client: Client) { - console.log(" "); - console.log(`${client.user.tag} is now serving in ${client.guilds.cache.size} guilds!`); - console.log(" "); -}; +export default function ready(client: Client) { + Logger.info(`${client.user?.tag} is now serving in ${client.guilds.cache.size} guilds!`); + client.user?.setActivity("a server full of idiots", { type: "WATCHING" }); +} diff --git a/src/handlers/admin/admincommand.ts b/src/handlers/admin/admincommand.ts deleted file mode 100644 index e2ca9a5..0000000 --- a/src/handlers/admin/admincommand.ts +++ /dev/null @@ -1,38 +0,0 @@ -if (process.env.NODE_ENV !== "production") require("dotenv").config(); - -import { Message } from "discord.js"; -import Client from "../../structures/Client"; -import axios from "axios"; - -export default { - run: async function run(client: Client, message: Message, args: Array) { - if (args.length < 2) { - message.channel.send("Invalid Command Arguments. Ex c!admin command /gc test"); - return; - } - - let command = ""; - for (let i = 1; i < args.length; i++) { - command += args[i] + " "; - } - - const ip = process.env.CALM_BOT_IP; - const port = process.env.CALM_BOT_PORT; - const key = process.env.CALM_BOT_KEY; - - axios - .post("http://" + ip + ":" + port + "/chat", { - message: command, - key: key, - }) - .then( - (response) => { - message.channel.send(`StatusCode: ${response.status} | StatusMessage: ${response.statusText}`); - }, - (error) => { - console.log(error); - message.channel.send("There was an error making that request!"); - } - ); - }, -}; diff --git a/src/handlers/admin/adminfindsuggestor.ts b/src/handlers/admin/adminfindsuggestor.ts deleted file mode 100644 index 677669d..0000000 --- a/src/handlers/admin/adminfindsuggestor.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Message } from "discord.js"; -import GuildSettings from "../../schemas/GuildSettings"; -import Client from "../../structures/Client"; -import { MessageEmbed } from "discord.js"; - -export default { - run: async function run(client: Client, message: Message, args: Array) { - // c!admin findsuggestor (id) - if (args.length < 2) { - return message.channel.send("Invalid Arguments. Ex c!admin findsuggestor (id)"); - } - - const id = args[1]; - let guildSettings = await GuildSettings.findOne({ guildID: message.guild.id }); - if (guildSettings === null) { - const doc = new GuildSettings({ guildID: message.guild.id }); - await doc.save(); - guildSettings = doc; - } - - const suggestor = guildSettings.suggestions.find((element) => element.msgID === id); - if (suggestor === undefined) { - return message.channel.send("Could not find a suggestion with that msg id in our database."); - } - - let embed = new MessageEmbed() - .setTitle("Suggestion id: " + id) - .addField("User", suggestor.suggestorTag) - .addField("Suggestion", suggestor.suggestion) - .addField("User ID", suggestor.suggestorID) - .setColor("#4287f5"); - - message.channel.send(embed); - }, -}; diff --git a/src/handlers/admin/adminmanualchallenge.ts b/src/handlers/admin/adminmanualchallenge.ts deleted file mode 100644 index b13fc8a..0000000 --- a/src/handlers/admin/adminmanualchallenge.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Message, User } from "discord.js"; -import Client from "../../structures/Client"; -import Challenges from "../../data/calm/challenges/DecemberChallenges.json"; -import ChallengeParticipant from "../../schemas/ChallengeParticipant"; - -export default { - run: async function run(client: Client, message: Message, args: Array) { - // ex c!manualchallenge (userid) (challengeid) (true/false) - - if (args.length < 4) { - message.channel.send("Invalid Arguments. ex c!admin manualchallenge (userid) (challengeid) (true/false)"); - return; - } - - if (args[3].toLowerCase() !== "true" && args[3].toLowerCase() !== "false") { - message.channel.send("Invalid Arguments. ex c!manualchallenge (userid) (challengeid) (true/false) <-- HERE"); - return; - } - - let participant = await ChallengeParticipant.findOne({ discordID: args[1] as string }); - - if (participant === null) { - const user = await message.guild.members.fetch(await client.users.fetch(args[1] as string)); - if (message.guild.members.cache.find((m) => m.id === args[1]) === undefined) { - message.channel.send("Could not find user in discord by id " + args[1]); - return; - } - const doc = new ChallengeParticipant({ discordID: args[1] }); - await doc.save(); - participant = await ChallengeParticipant.findOne({ discordID: args[1] as string }); - } - - if (getChallenge(args[2]) === undefined) { - message.channel.send(`Invalid Challenge ID: ${args[2]}`); - return true; - } - - if (args[3].toLowerCase() === "false") { - let participant = await ChallengeParticipant.findOne({ discordID: args[1] as string }); - participant.completedChallenges.delete(args[2]); - await participant.save(); - message.channel.send(`Sucsess! Set ${args[1]}'s challenge #${args[2]} to __FALSE__`); - if (participant.completedChallenges.size === 0) { - let participant = await ChallengeParticipant.findOne({ discordID: args[1] as string }); - await participant.delete(); - message.channel.send("Document in database for that user has been marked for deletion due to that challenge being the user's last TRUE challenge"); - return; - } - return; - } else { - let participant = await ChallengeParticipant.findOne({ discordID: args[1] as string }); - participant.completedChallenges.set(args[2], "true"); - await participant.save(); - message.channel.send(`Sucsess! Set ${args[1]}'s challenge #${args[2]} to __TRUE__`); - return; - } - }, -}; - -function getChallenge(id: String) { - for (let challenge in Challenges) { - if (Challenges[challenge].id.toLowerCase() === id.toLowerCase()) { - return Challenges[challenge]; - } - } - return undefined; -} diff --git a/src/handlers/admin/adminsay.ts b/src/handlers/admin/adminsay.ts deleted file mode 100644 index 1914df1..0000000 --- a/src/handlers/admin/adminsay.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Message, TextChannel } from "discord.js"; -import Client from "../../structures/Client"; - -export default { - run: async function run(client: Client, message: Message, args: Array) { - //c!admin say (id) (message) - - if (args.length < 3) return message.channel.send("Invalid arguments"); - const channel = message.guild.channels.cache.find((c) => c.id === args[1]); - if (channel === undefined || !(channel instanceof TextChannel)) return message.channel.send("Could not find text channel with ID " + args[1]); - let text = ""; - for (let i = 2; i < args.length; i++) { - text += args[i] + " "; - } - - const txtchannel = channel as TextChannel; - txtchannel.send(text); - }, -}; diff --git a/src/handlers/admin/adminsleep.ts b/src/handlers/admin/adminsleep.ts deleted file mode 100644 index 788157f..0000000 --- a/src/handlers/admin/adminsleep.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Message } from "discord.js"; -import Database from "../../utils/database/Database"; -import Client from "../../structures/Client"; - -export default { - run: async function run(client: Client, message: Message, args: Array) { - let settings = await Database.getGuildSettings(message.guild.id); - if (settings.sleep) { - settings.sleep = false; - settings.save(); - message.channel.send("Turned off sleep mode"); - } else { - settings.sleep = true; - settings.save(); - message.channel.send("Turned on sleep mode. Run c!admin sleep to turn it off"); - } - }, -}; diff --git a/src/schemas/ChallengeParticipant.ts b/src/schemas/ChallengeParticipant.ts index 3e512ab..0a3fd9a 100644 --- a/src/schemas/ChallengeParticipant.ts +++ b/src/schemas/ChallengeParticipant.ts @@ -1,7 +1,7 @@ import { Schema, model, Document } from "mongoose"; export interface IChallengeParticipant extends Document { discordID: string; - completedChallenges: Map; + completedChallenges: Map; } const ChallengeParticipantSchema = new Schema({ diff --git a/src/schemas/GuildSettings.ts b/src/schemas/GuildSettings.ts index 627f0c9..c9bd2ee 100644 --- a/src/schemas/GuildSettings.ts +++ b/src/schemas/GuildSettings.ts @@ -1,11 +1,12 @@ import { Schema, model, Document } from "mongoose"; -interface IVerbals { +export interface IVerbals { moderator: string; user: string; reasonText: string; reasonImage: string; casenumber: number; + timestamp: string; } interface ISuggestions { @@ -15,6 +16,15 @@ interface ISuggestions { suggestion: string; } +interface ITicket { + member: string; + channel: string; +} + +interface ITag { + name: string; + response: string; +} export interface IGuildSettings extends Document { guildID: string; disabledCommands: Array; @@ -22,6 +32,11 @@ export interface IGuildSettings extends Document { punishmentcases: number; suggestions: Array; sleep: Boolean; + tickets: ITicket[]; + totalTickets: number; + ticketRoles: string[]; + ticketSupportedRole: string | undefined; + tags: ITag[]; } const GuildSettingsScema = new Schema({ @@ -31,6 +46,11 @@ const GuildSettingsScema = new Schema({ punishmentcases: { type: Number, default: 1 }, sleep: { type: Boolean, default: false }, suggestions: { type: Array(), default: new Array() }, + tickets: { type: Array(), default: new Array() }, + totalTickets: { type: Number, default: 0 }, + ticketRoles: { type: Array(), default: new Array() }, + ticketSupportedRole: { type: String, default: undefined }, + tags: { type: Array(), default: new Array() }, }); export default model("GuildSettings", GuildSettingsScema); diff --git a/src/structures/Client.ts b/src/structures/Client.ts index 226e974..ddf1fe8 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -1,16 +1,23 @@ import fs from "fs"; import path from "path"; -import { promisify } from "util"; -import Discord from "discord.js"; +import Discord, { Collection, DMChannel, Message, NewsChannel, TextChannel } from "discord.js"; import Database from "../utils/database/Database"; +import logger from "../utils/logger/Logger"; +import { ICommand, IPromptListener, IReactionListener, ISubCommandSettings, PromptCallback, ReactionCallback } from "./Interfaces"; -const readdir = promisify(fs.readdir); +import PermissionHandler from "../utils/Permissions/Permission"; +import Permission from "../utils/Permissions/Permission"; + +const defaultSettings: ISubCommandSettings = { + guildOnly: false, +}; export default class Client extends Discord.Client { prefix = "c!"; - version = "2.12.1"; - commands = new Discord.Collection(); - aliases = new Discord.Collection(); + commands: Collection = new Collection(); + developers = ["438057670042320896" /*Miqhtie*/, "234576713005137920" /*Joel*/]; + reactionListeners: IReactionListener[] = []; + promptListeners: IPromptListener[] = []; constructor() { super({ @@ -18,35 +25,243 @@ export default class Client extends Discord.Client { partials: ["GUILD_MEMBER", "USER", "MESSAGE", "REACTION", "CHANNEL"], }); - Database.initialize(`mongodb+srv://${process.env.MONGO_USERNAME}:${process.env.MONGO_PASSWORD}@${process.env.MONGO_HOST}/${process.env.MONGO_DBNAME}?retryWrites=true&w=majority`); + Database.initialize(`mongodb+srv://${process.env.MONGO_USERNAME}:${process.env.MONGO_PASSWORD}@${process.env.MONGO_HOST}/${process.env.MONGO_DBNAME}?retryWrites=true&w=majority`, this); } - async loadEvents(eventsDir: string) { - const eventFiles = await readdir(eventsDir); + loadEvents(eventsDir: string) { + // Get files in event directory + const files = fs.readdirSync(eventsDir); + + // counter + let e = 0; + + // time started + let timestarted = Date.now(); - eventFiles.forEach((file: string, i: number) => { - const eventName = file.split(".")[0]; - const event = require(path.join(eventsDir, file)); - this.on(eventName, event.bind(null, this)); + // Loop through files + files.forEach((file: string) => { + // Make sure file isn't a directory + const stats = fs.statSync(path.join(eventsDir, file)); + if (!stats.isDirectory()) { + // Get event name + const eventName = file.split(".")[0] as string; - console.log(`Loaded event: ${eventName}`); + // Get event function + const event: () => void = require(path.join(eventsDir, file)).default; + + // Bind event + this.on(eventName, event.bind(null, this)); + e++; + } }); + logger.info(`Done registering ${e} events. Took ${Date.now() - timestarted}ms`); } - async loadCommands(commandsDir: string) { - const commandFiles = await readdir(commandsDir); + async executeCommand(command: ICommand, message: Message, args: string[]) { + if (command.settings?.guildOnly && !message.guild) { + message.channel.send("This command can only be executed inside a guild!"); + return; + } - commandFiles.forEach((file: string, i: number) => { - const commandName = file.split(".")[0]; - const command = require(path.join(commandsDir, file)); - this.commands.set(commandName, command); - if (command.aliases) { - command.aliases.forEach((alias: any) => { - this.aliases.set(alias, command); - }); + try { + if (message.guild) { + const guildSettings = await Database.getGuildSettings(message.guild!!.id); + + const commandName = message.content.substring(this.prefix.length, message.content.length).trim().split(" ")[0]?.toLowerCase(); + + if (guildSettings.sleep && commandName !== "admin") { + message.channel.send(`Bot is in sleep mode! Do ${this.prefix}admin sleep to turn it back on!`); + return; + } + + if (guildSettings.disabledCommands.includes(commandName!!) && !(await Permission.isAdmin(message.member!!))) { + message.channel.send("This command is disabled!"); + return; + } + } + + if (command.settings?.permissions && message.member) { + if (!(await PermissionHandler.hasPermission(this, message.member, command.settings.permissions))) { + message.channel.send(`Missing permissions! Required: \`${command.settings.permissions}\``); + return; + } + } + + if (command.settings?.maximumArgs && command.settings?.maximumArgs < args.length) { + message.channel.send(`Too many arguments!\nMax: ${command.settings.maximumArgs}\nProvided: ${args.length}\nUsage: \`${command.settings.usage}\``); + return; + } + + if (command.settings?.minimumArgs && command.settings?.minimumArgs > args.length) { + message.channel.send(`Too little arguments!\nMin: ${command.settings.minimumArgs}\nProvided: ${args.length}\nUsage: \`${command.settings.usage}\``); + return; } - console.log(`Loaded command: ${commandName} ${command.aliases ? `with aliases: ${command.aliases.join(", ")}` : ""}`); + command.run(this, message, args); + } catch (e) { + message.channel.send("An unexpected error occured while trying to run that command!"); + logger.error(e); + return; + } + } + + registerCommands(commandsDir: string, inheritCategoryFromDirectory?: boolean) { + let timestarted = Date.now(); + if (!inheritCategoryFromDirectory) inheritCategoryFromDirectory = false; + + let parentName: string | undefined = undefined; + + if (inheritCategoryFromDirectory) { + fs.readdirSync(commandsDir).forEach((directory) => { + const commands = walk(path.join(commandsDir, directory), directory); + commands.forEach((v, k) => { + this.commands.set(k, v); + }); + }); + } else this.commands = walk(commandsDir); + + function walk(dir: string, category?: string): Collection { + const files = fs.readdirSync(dir); + const commands: Collection = new Collection(); + + files.forEach((file) => { + const stats = fs.statSync(path.join(dir, file)); + const name = file.split(".")[0]?.toLowerCase() as string; + if (stats.isFile()) { + if (name.toLowerCase() == "subcommandsettings") return; + + const cmd: ICommand = require(path.join(dir, file)).default; + let commandCategory: string | undefined = undefined; + if (cmd.category) commandCategory = cmd.category; + else if (category) commandCategory = category; + + const command: ICommand = { + run: cmd.run, + settings: cmd.settings, + category: commandCategory, + }; + + if (!files.includes(name)) commands.set(name, command); + } else if (stats.isDirectory()) { + parentName = name; + + const subcommands = walk(path.join(dir, file)); + + let options: ISubCommandSettings = defaultSettings; + const parentFiles = fs.readdirSync(path.join(dir, parentName)); + const subcommandSettings = parentFiles.find((f) => f.toLowerCase().startsWith("subcommandsettings")); + if (subcommandSettings) { + options = require(path.join(dir, parentName, subcommandSettings)).default; + } else { + logger.warn(`No subcommandSettings.ts file found for subcommand: ${name}, using default settings!`); + } + + let defaultSubCommand: string | undefined; + if (files.includes(name + ".ts") || files.includes(name + ".js")) { + defaultSubCommand = name; + subcommands.set(name, require(path.join(dir, name)).default); + } else { + defaultSubCommand = options.defaultSubCommand; + } + + const command: ICommand = { + settings: undefined, + category: category, + subCommandSettings: { + guildOnly: options.guildOnly, + maximumArgs: options.maximumArgs, + minimumArgs: options.minimumArgs, + permissions: options.permissions, + defaultSubCommand: defaultSubCommand, + }, + subCommands: subcommands, + run: async (client, message, args) => { + let subcommandName: string | undefined = undefined; + const newArgs = args.slice(0); // prevent args from being shifted + if (command.subCommands?.has(newArgs.shift()?.toLowerCase() as string)) subcommandName = args.shift()?.toLowerCase(); + let subcommand: ICommand | undefined = undefined; + + let options = command.subCommandSettings; + + try { + if (options?.guildOnly && !message.guild) { + message.channel.send("You must be in a guild to run this command!"); + return; + } + + if (options?.permissions && message.member) { + if (!(await PermissionHandler.hasPermission(client, message.member, options?.permissions))) { + message.channel.send(`Missing permissions! Required: \`${options?.permissions}\``); + return; + } + } + + if (options?.maximumArgs && options?.maximumArgs < args.length) { + message.channel.send(`Too many arguments!\nMax: ${options.maximumArgs}\nProvided: ${args.length}`); + return; + } + + if (options?.minimumArgs && options?.minimumArgs > args.length) { + message.channel.send(`Too little arguments!\nMin: ${options.minimumArgs}\nProvided: ${args.length}`); + return; + } + } catch (e) { + logger.error(e); + } + + if (subcommandName) { + subcommand = subcommands.get(subcommandName); + } else if (options?.defaultSubCommand) { + subcommand = subcommands.get(options.defaultSubCommand); + } + + if (subcommand) { + client.executeCommand(subcommand, message, args); + } else { + message.channel.send(`Invalid args.`); + } + }, + }; + + commands.set(name, command); + } + }); + return commands; + } + logger.info(`Done registering ${this.commands.size} commands. Took ${Date.now() - timestarted}ms`); + } + + addReactionListener(message: Message, callback: ReactionCallback, userwhitelist?: string[]) { + this.reactionListeners.push({ + messageid: message.id, + callback: callback, + userwhitelist: userwhitelist, }); } + + addPromptListener(prompt: string, channel: TextChannel | DMChannel | NewsChannel, user: string, timeout: number, callback: PromptCallback) { + // Override any existing prompts + this.promptListeners = this.promptListeners.filter((ele) => { + ele.user !== user && ele.channel !== channel.id; + }); + + channel + .send(prompt + `\n***Type "exit" to exit. Expres in \`${timeout}\` seconds***`) + .then((message) => { + this.promptListeners.push({ + user: user, + channel: message.channel.id, + callback: callback, + }); + + this.setTimeout(() => { + this.promptListeners = this.promptListeners.filter((ele) => { + ele.user !== user && ele.channel !== channel.id; + }); + }, timeout * 1000); + }) + .catch((err) => { + logger.error(err); + }); + } } diff --git a/src/structures/Interfaces.ts b/src/structures/Interfaces.ts new file mode 100644 index 0000000..d16eac4 --- /dev/null +++ b/src/structures/Interfaces.ts @@ -0,0 +1,49 @@ +import { Message, MessageReaction, PermissionResolvable, User } from "discord.js"; +import Client from "./Client"; + +export type RunCallback = (client: Client, message: Message, args: string[]) => void; +export type ReactionCallback = (client: Client, reaction: MessageReaction, user: User) => void; +export type PromptCallback = (response: Message) => void; +export type Permission = PermissionResolvable | PermissionsEnum; + +export interface IReactionListener { + messageid: string; + callback: ReactionCallback; + userwhitelist?: string[]; +} + +export interface IPromptListener { + user: string; + channel: string; + callback: PromptCallback; +} +export interface ICommand { + settings: ICommandSettings | undefined; + run: RunCallback; + category?: string; + subCommandSettings?: ISubCommandSettings; + subCommands?: Map; +} + +export interface ICommandSettings { + description: string; + usage: string; + guildOnly?: boolean; + permissions?: Permission; + minimumArgs?: number; + maximumArgs?: number; +} + +export interface ISubCommandSettings { + defaultSubCommand?: string; + guildOnly?: boolean; + minimumArgs?: number; + maximumArgs?: number; + permissions?: Permission; +} + +export enum PermissionsEnum { + DEVELOPER = "Developer", + ADMIN = "Admin", + STAFF = "Staff", +} diff --git a/src/utils/Permissions/Permission.ts b/src/utils/Permissions/Permission.ts new file mode 100644 index 0000000..8903aac --- /dev/null +++ b/src/utils/Permissions/Permission.ts @@ -0,0 +1,74 @@ +import { Guild, GuildMember, Role } from "discord.js"; +import Roles from "../../data/calm/roles.json"; +import Client from "../../structures/Client"; +import { Permission, PermissionsEnum } from "../../structures/Interfaces"; + +const isAdmin = async function (member: GuildMember): Promise { + const srOfficerRole = await getRole(member.guild, Roles.GENERAL.SR_OFFICER.id, Roles.GENERAL.SR_OFFICER.name); + const discordManagerRole = await getRole(member.guild, Roles.GENERAL.DISCORD_MANAGER.id, Roles.GENERAL.DISCORD_MANAGER.name); + + if (srOfficerRole) { + if (hasRole(srOfficerRole, member)) return true; + } + + if (discordManagerRole) { + if (hasRole(discordManagerRole, member)) return true; + } + + return member.hasPermission(["ADMINISTRATOR"]); +}; + +const isStaff = async function (member: GuildMember): Promise { + if (await isAdmin(member)) return true; + + const discordStaffRole = await getRole(member.guild, Roles.GENERAL.DISCORD_STAFF.id, Roles.GENERAL.DISCORD_STAFF.name); + const guildStaffRole = await getRole(member.guild, Roles.GENERAL.STAFF_TEAM.id, Roles.GENERAL.STAFF_TEAM.name); + + if (discordStaffRole) { + if (hasRole(discordStaffRole, member)) return true; + } + + if (guildStaffRole) { + if (hasRole(guildStaffRole, member)) return true; + } + + return false; +}; + +const hasPermission = async (client: Client, member: GuildMember, permission: Permission): Promise => { + return new Promise(async (resolve) => { + switch (permission) { + case PermissionsEnum.DEVELOPER: + if (client.developers.includes(member.id)) resolve(true); + break; + case PermissionsEnum.ADMIN: + resolve(await isAdmin(member)); + break; + case PermissionsEnum.STAFF: + resolve(await isStaff(member)); + break; + default: + resolve(member.hasPermission(permission)); + break; + } + }); +}; + +export default { + isAdmin: isAdmin, + isStaff: isStaff, + hasPermission: hasPermission, +}; + +async function getRole(guild: Guild, roleid?: string, rolename?: string): Promise { + if (guild.id == "501501905508237312" && roleid) { + return guild.roles.cache.find((r) => r.id === roleid); + } else if (rolename) { + return guild.roles.cache.find((r) => r.name === rolename); + } + return undefined; +} + +function hasRole(role: Role, member: GuildMember) { + return member.roles.cache.find((r) => r === role); +} diff --git a/src/utils/api/HypixelApi.ts b/src/utils/api/HypixelApi.ts index 8bbf0ce..91199f2 100644 --- a/src/utils/api/HypixelApi.ts +++ b/src/utils/api/HypixelApi.ts @@ -1,6 +1,7 @@ if (process.env.NODE_ENV !== "production") require("dotenv").config(); import axios from "axios"; -import * as Interface from "../api/Interfaces"; +import { IGuildMember, IPlayer } from "../api/Interfaces"; +import logger from "../logger/Logger"; const KEY = process.env.HYPIXEL_API_KEY; const CALM_GUILD_ID = "5af718d40cf2cbe7a9eeb063"; @@ -8,40 +9,55 @@ let APIUsage = 0; let APILastUsed = 0; export default { - getGuildStaff: async function (): Promise { - if (!canUseKey()) return undefined; - const res = await axios.get(`https://api.hypixel.net/guild?key=${KEY}&id=${CALM_GUILD_ID}`); - if (res.status !== 200) return undefined; - - const guild = res.data.guild; - let guildStaffRanks = []; - - guild.ranks.forEach((rank) => { - if (rank.priority >= 4) { - guildStaffRanks.push(rank.name); - } - }); + getGuildStaff: async function (): Promise { + return new Promise((resolve, reject) => { + if (!canUseKey()) reject("Key overuse."); + axios + .get(`https://api.hypixel.net/guild?key=${KEY}&id=${CALM_GUILD_ID}`) + .then((res) => { + const guild = res.data?.guild || null; - let staffMembers = []; - (guild.members as Interface.IGuildMember[]).forEach((member) => { - if (guildStaffRanks.includes(member.rank) || member.rank === "Guild Master") staffMembers.push(member); - }); + let guildStaffRanks: string[] = []; + + guild.ranks.forEach((rank: any) => { + if (rank.priority >= 4) { + guildStaffRanks.push(rank.name); + } + }); - return staffMembers as Interface.IGuildMember[]; + let staffMembers: IGuildMember[] = []; + (guild.members as IGuildMember[]).forEach((member) => { + if (guildStaffRanks.includes(member.rank) || member.rank === "Guild Master") staffMembers.push(member); + }); + + resolve(staffMembers); + }) + .catch((err) => { + reject(err); + logger.warn(err); + }); + }); }, - getPlayerFromUUID: async function (uuid: string): Promise { - if(!canUseKey()) return undefined; - const res = await axios.get(`https://api.hypixel.net/player?key=${KEY}&uuid=${uuid}`); - if(res.status !== 200) return undefined; - return res.data.player; + getPlayerFromUUID: async function (uuid: string): Promise { + return new Promise((resolve, reject) => { + if (!canUseKey()) reject("Key overuse."); + axios + .get(`https://api.hypixel.net/player?key=${KEY}&uuid=${uuid}`) + .then((res) => { + resolve(res.data.player); + }) + .catch((error) => { + reject(error); + logger.warn(error); + }); + }); }, }; - - // Make sure we don't get ratelimited because we love rate limits function canUseKey(): boolean { + logger.http(`New request for API key. Key has been used ${APIUsage} in the last minute.`); const date = new Date(); if (APILastUsed < date.getMinutes()) { APIUsage = 1; @@ -56,4 +72,3 @@ function canUseKey(): boolean { } } } - diff --git a/src/utils/api/Interfaces.ts b/src/utils/api/Interfaces.ts index d8e148a..d7cdf4c 100644 --- a/src/utils/api/Interfaces.ts +++ b/src/utils/api/Interfaces.ts @@ -1,14 +1,14 @@ -export interface IGuildMember{ - uuid: string, - rank: string, - joined: number, - questParticipation: number, - expHistory: Record +export interface IGuildMember { + uuid: string; + rank: string; + joined: number; + questParticipation: number; + expHistory: Record; } export interface IPlayer { - displayname: string, - uuid: string, - lastLogin: number, - lastLogout: number -} \ No newline at end of file + displayname: string; + uuid: string; + lastLogin: number; + lastLogout: number; +} diff --git a/src/utils/api/MojangApi.ts b/src/utils/api/MojangApi.ts new file mode 100644 index 0000000..f18b58f --- /dev/null +++ b/src/utils/api/MojangApi.ts @@ -0,0 +1,20 @@ +import axios from "axios"; +import logger from "../logger/Logger"; + +const MOJANG_API_URL = "https://api.mojang.com"; + +export default { + getUUID(name: string): Promise { + return new Promise((resolve, reject) => { + axios + .get(`${MOJANG_API_URL}/users/profiles/minecraft/${name}`) + .then((response) => { + resolve(response.data?.id); + }) + .catch((err) => { + logger.error(err); + reject(err); + }); + }); + }, +}; diff --git a/src/utils/database/Database.ts b/src/utils/database/Database.ts index 76e9cb9..f1efc6b 100644 --- a/src/utils/database/Database.ts +++ b/src/utils/database/Database.ts @@ -2,21 +2,25 @@ import mongoose from "mongoose"; import GuildSettings from "../../schemas/GuildSettings"; import ChallengeParticipant from "../../schemas/ChallengeParticipant"; +import Client from "../../structures/Client"; +import logger from "../logger/Logger"; export default { - initialize: async (url: string) => { + initialize: async (url: string, client: Client) => { await mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, }); - console.log("Connected to database!"); + logger.info("Connected to database!"); }, getGuildSettings: async (guildid: string) => { let guildSettings = await GuildSettings.findOne({ guildID: guildid }); if (guildSettings === null) { + logger.verbose(`Guild settings did not exist for ${guildid}... creating new document.`); const doc = new GuildSettings({ guildID: guildid }); await doc.save(); + logger.verbose(`Guild settings document for ${guildid} saved.`); guildSettings = doc; } return guildSettings; @@ -25,8 +29,10 @@ export default { getChallengeParticipant: async (discordid: string) => { let participant = await ChallengeParticipant.findOne({ discordID: discordid }); if (participant === null) { + logger.verbose(`Challenge Participant document did not exist for ${discordid}... creating new document.`); const doc = new ChallengeParticipant({ discordID: discordid }); await doc.save(); + logger.verbose(`Challenge Participant document for ${discordid} saved.`); participant = await ChallengeParticipant.findOne({ discordID: discordid }); } return participant; diff --git a/src/utils/logger/Logger.ts b/src/utils/logger/Logger.ts new file mode 100644 index 0000000..a37614b --- /dev/null +++ b/src/utils/logger/Logger.ts @@ -0,0 +1,29 @@ +import winston from "winston"; + +const logger = winston.createLogger({ + format: winston.format.combine(winston.format.timestamp(), winston.format.json()), + transports: [ + new winston.transports.File({ + filename: "./logs/combined.log", + level: "verbose", + }), + new winston.transports.File({ + level: "error", + filename: "./logs/error.log", + }), + ], +}); + +if (process.env.NODE_ENV !== "production") { + const consoleLogFormat = winston.format.printf((info) => { + return `${new Date().toISOString()} [${info.level}] ${info.message}`; + }); + + logger.add( + new winston.transports.Console({ + format: winston.format.combine(winston.format.colorize(), consoleLogFormat), + }) + ); +} + +export default logger; diff --git a/src/utils/ticket/Ticket.ts b/src/utils/ticket/Ticket.ts new file mode 100644 index 0000000..4ac7a5e --- /dev/null +++ b/src/utils/ticket/Ticket.ts @@ -0,0 +1,84 @@ +import { Channel, GuildMember, MessageEmbed, OverwriteResolvable, PermissionString } from "discord.js"; +import Client from "../../structures/Client"; +import Roles from "../../data/calm/roles.json"; +import { IGuildSettings } from "../../schemas/GuildSettings"; +import logger from "../logger/Logger"; + +const allow: PermissionString[] = ["VIEW_CHANNEL", "READ_MESSAGE_HISTORY", "SEND_MESSAGES", "USE_EXTERNAL_EMOJIS", "ATTACH_FILES", "EMBED_LINKS"]; + +export default class Ticket { + private owner: GuildMember | undefined; + private type: TicketType | undefined; + private settings: IGuildSettings | undefined; + private client: Client | undefined; + + constructor(owner: GuildMember, type: TicketType, settings: IGuildSettings, client: Client) { + this.owner = owner; + this.type = type; + this.settings = settings; + this.client = client; + } + + create(): Promise { + return new Promise(async (resolve, reject) => { + const id = this.settings!.totalTickets + 1; + const guild = this.owner?.guild!; + + this.settings!.totalTickets = id; + await this.settings?.save(); + + const channelOverwrites: OverwriteResolvable[] = [ + { deny: ["VIEW_CHANNEL"], id: guild.roles.everyone.id }, + { allow: allow, id: this.owner!!.id }, + ]; + + const botRoleData = Roles.GENERAL.BOTS; + const botsRole = guild.id === "501501905508237312" ? guild.roles.cache.get(botRoleData.id) : guild.roles.cache.find((r) => r.name === botRoleData.name); + if (botsRole) channelOverwrites.push({ allow: allow, id: botsRole.id }); + + this.settings!!.ticketRoles = this.settings!!.ticketRoles.filter((ele) => guild.roles.cache.has(ele)); + await this.settings?.save(); // Remove all roles in database that aren't in the server (that have been deleted) + + this.settings?.ticketRoles.forEach((roleid) => channelOverwrites.push({ allow: allow, id: roleid })); + + guild.channels + .create(`ticket-${id}`, { permissionOverwrites: channelOverwrites, topic: `Ticket created by ${this.owner ? this.owner.user.tag : "Couldn't get user!"}` }) + .then(async (channel) => { + let tag = this.settings?.ticketSupportedRole ? `<@&${this.settings.ticketSupportedRole}>` : ""; + + let ticketEmbed = new MessageEmbed() + .setTitle("Welcome to the ticket") + .setDescription(`Staff will be with you shortly, please be patient. When you are ready to close this do **${this.client?.prefix}ticket close**`) + .addField("Ticket Type:", this.type); + + channel.send(tag, ticketEmbed); + this.settings?.tickets.push({ channel: channel.id, member: this.owner!.id }); + this.settings?.save(); + resolve(channel); + }) + .catch((err) => { + logger.error(err); + reject(undefined); + }); + }); + } + + setType(type: TicketType) { + this.type = type; + } +} + +export enum TicketType { + SUPPORT = "Support 🔧", // Support ticket for questions + BUGS = "Bugs 🐞", // CalmBot bug reports + REPORT = "Report ⚠️", // Report a member + REDEEM = "Redeem 💰", // Redeem a tatsu reward +} + +export function deleteTicket(channel: Channel, settings: IGuildSettings) { + settings.tickets = settings.tickets.filter((ele) => { + ele.channel != channel.id; + }); + settings.save(); + channel.delete(); +} diff --git a/tsconfig.json b/tsconfig.json index af36d28..a2d7e2e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,19 @@ { "compilerOptions": { - "target": "esnext", + "target": "es6", "module": "commonjs", + "outDir": "./build", + "rootDir": "./src", "lib": ["esnext"], - "allowJs": true, - "outDir": "build", - "rootDir": "src", - "alwaysStrict": true, + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "typeRoots": ["./src/typings", "./node_modules/@types"], "esModuleInterop": true, - "resolveJsonModule": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true } } diff --git a/yarn.lock b/yarn.lock index abd876b..59bc4c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,15 @@ # yarn lockfile v1 +"@dabh/diagnostics@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" + integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + "@discordjs/collection@^0.1.6": version "0.1.6" resolved "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz" @@ -23,6 +32,13 @@ dependencies: "@types/node" "*" +"@types/fs-extra@^9.0.6": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.6.tgz#488e56b77299899a608b8269719c1d133027a6ab" + integrity sha512-ecNRHw4clCkowNOBJH1e77nvbPxHYnWIXMv1IAoG/9+MYGkgoyr3Ppxr7XYFNL41V422EDhyV4/4SSK8L2mlig== + dependencies: + "@types/node" "*" + "@types/mongodb@^3.5.27": version "3.6.3" resolved "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.3.tgz" @@ -48,11 +64,21 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +async@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + axios@^0.21.1: version "0.21.1" resolved "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz" @@ -96,6 +122,52 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" + integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colors@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +colorspace@1.1.x: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" + integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== + dependencies: + color "3.0.x" + text-hex "1.0.x" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -159,16 +231,46 @@ dotenv@^8.2.0: resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +fast-safe-stringify@^2.0.4: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + +fecha@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" + integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + follow-redirects@^1.10.0: version "1.13.1" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz" integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -186,6 +288,11 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -194,21 +301,56 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + kareem@2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz" integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ== +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +logform@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" + integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^4.2.0" + ms "^2.1.1" + triple-beam "^1.3.0" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -300,6 +442,11 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz" @@ -312,6 +459,13 @@ once@^1.3.0: dependencies: wrappy "1" +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -327,7 +481,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -readable-stream@^2.3.5: +readable-stream@^2.3.5, readable-stream@^2.3.7: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -340,6 +494,15 @@ readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + regexp-clone@1.0.0, regexp-clone@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz" @@ -370,7 +533,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: +safe-buffer@5.2.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -397,6 +560,13 @@ sift@7.0.1: resolved "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz" integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + sliced@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz" @@ -422,6 +592,18 @@ sparse-bitfield@^3.0.3: dependencies: memory-pager "^1.0.2" +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" @@ -429,6 +611,16 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + ts-node@^9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" @@ -451,20 +643,48 @@ typescript@^4.1.2: resolved "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz" integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== -util-deprecate@~1.0.1: +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +winston-transport@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" + integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== + dependencies: + readable-stream "^2.3.7" + triple-beam "^1.2.0" + +winston@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" + integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== + dependencies: + "@dabh/diagnostics" "^2.0.2" + async "^3.1.0" + is-stream "^2.0.0" + logform "^2.2.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.4.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= ws@^7.3.1: - version "7.4.2" - resolved "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz" - integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA== + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== yn@3.1.1: version "3.1.1"