diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..70e02a9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ + +# Descriptive title + +A person with no idea what task you were working on before should at least +have an idea as to what you did. + +# Link to github card +Type `#` to see list of autolinks to pull requests or issues + +# Changes +The body should contain a high-level overview of the individual changes you made, what parts of the application you changed, etc. +This may be in list form. + + +* +* +* + + +You may also wish to include links to related pull requests, issues, or branches which may affect your changes. +You can mention people by using `@` diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 14304da..7d8cdfd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,5 +8,5 @@ updates: - package-ecosystem: "npm" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "weekly" + interval: "daily" target-branch: "staging" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f51a845..56bdca6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -11,12 +11,10 @@ # name: "CodeQL" -on: - push: - branches: [ "main, staging" ] +on: + push: pull_request: - # The branches below must be a subset of the branches above - branches: [ "main, staging" ] + branches: [ main, staging ] schedule: - cron: '19 8 * * 2' diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fe461b4..2ff1a16 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -5,7 +5,9 @@ # Source repository: https://github.com/actions/dependency-review-action # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement name: 'Dependency Review' -on: [pull_request] +on: + pull_request: + branches: [main, staging] permissions: contents: read diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 6091a86..b129292 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,10 +1,9 @@ name: Docker Image CI -on: - push: - branches: [ "main, staging" ] +on: + push: pull_request: - branches: [ "main, staging" ] + branches: [ main, staging ] jobs: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index dec7807..633af27 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,6 +1,7 @@ name: Format on: pull_request: + branches: ['*', '!main'] jobs: format: runs-on: ubuntu-latest diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index 585312f..08af35f 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -1,7 +1,7 @@ name: Publish Docker image to GitHub Package Registry on: push: - branches: [ "main" ] + branches: [ main ] jobs: build: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 2358922..6ca9203 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ **/__pycache__ .vscode -.env +*.env node_modules unused-commands d.py/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2e0c22e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# CSSC-bot Contributing guidelines + +## Developing New Features + +First create a new branch with it's base set to the [staging](https://github.com/Antares-Network/CSSC-Bot/tree/staging) branch. +For naming conventions use `feature/GithubUsername/FeatureName`. Example: `feature/nathen418/mongodb-support` +Next when your feature is ready to be merged, submit a pull request to the [staging](https://github.com/Antares-Network/CSSC-Bot/tree/staging) branch. +This will be used to test your changes along side the rest of the code and any other pending new features. + +## Fixing Bugs + +Do the same as the instructions except when naming your branch use the naming convention: `bugfix/GithubUsername/BugName`. Example: `bugfix/nathen418/fix-api-connection-err` + +## Editing/Merging Pull Requests + +Only merge pull requests you authored or have been approved by the author to merge. + +## Pushing to branches + +Only push to branches you have been given permission to push to. This means that you should only commit to branches that have your name in the branch name unless the branch owner has given you permission to push to it. diff --git a/commands/owner/createRoles.ts b/commands/owner/createRoles.ts index 992c297..2b1efd4 100644 --- a/commands/owner/createRoles.ts +++ b/commands/owner/createRoles.ts @@ -1,6 +1,9 @@ import { ICommand } from "wokcommands"; import { createRoles } from "../../rolesOps"; import chalk from "chalk"; +import { classModel } from "../../models/classModel"; +import { staffModel } from "../../models/staffModel"; +import { yearModel } from "../../models/yearModel"; export default { name: "createRoles", @@ -13,18 +16,17 @@ export default { ownerOnly: true, callback: async ({ interaction }) => { - console.log( - chalk.green( - "Creating roles...\nCopy and paste the following output into your .env file" - ) - ); + console.log(chalk.green("Creating roles...")); console.log( chalk.red("------------------------------------------------------") ); + if (interaction.guild === null) { + return; + } // Create the roles - createRoles(interaction.guild!, "class"); - createRoles(interaction.guild!, "staff"); - createRoles(interaction.guild!, "year"); + await createRoles(interaction.guild, classModel); + await createRoles(interaction.guild, staffModel); + await createRoles(interaction.guild, yearModel); interaction.reply({ content: "Roles created!", ephemeral: true, diff --git a/commands/owner/csClassPoll.ts b/commands/owner/csClassPoll.ts index 6bee095..37457e6 100644 --- a/commands/owner/csClassPoll.ts +++ b/commands/owner/csClassPoll.ts @@ -6,18 +6,27 @@ import { } from "discord.js"; import chalk from "chalk"; import { ICommand } from "wokcommands"; -import classModel from "../../models/classModel"; +import { classModel, IClass } from "../../models/classModel"; import { checkForRoles } from "../../rolesOps"; +import { sleep } from "../../util"; -export interface Class { - CODE: string; - TITLE: string; - INFO: string; - ROLE_NAME: string; - ROLE_ID: string; - UUID: string; +// Splits any size list into lists of at most `max_list_len` +function split_list(list: T[], max_list_len: number): T[][] { + const class_chunks: T[][] = []; + for (let i = 0; i < list.length; i += max_list_len) { + class_chunks.push(list.slice(i, i + max_list_len)); + } + return class_chunks; } +// consumes a Class and returns Message Selec tOption data +function create_option_from_class(_class: IClass): MessageSelectOptionData { + return { + label: _class.CODE, + value: _class.CODE, + description: _class.TITLE, + }; +} export default { name: "csClassPoll", category: "owner", @@ -29,17 +38,21 @@ export default { ownerOnly: true, callback: async ({ client, interaction: msgInt }) => { - if (!checkForRoles(msgInt.guild!)) { + if (msgInt.guild === null) { + console.log(chalk.red("No guild")); + return; + } + if (!(await checkForRoles(msgInt.guild))) { msgInt.reply( "Please run the `/ createRoles` command in this server to create the necessary roles for this poll!" ); return; } - let classes: Class[] = await classModel.find({}).sort({ CODE: 1 }); + const classes = await classModel.find({}).sort({ CODE: 1 }); const class_chunks = split_list(classes, 25); - let rows: MessageActionRow[] = []; + const rows: MessageActionRow[] = []; for (let index = 0; index < class_chunks.length; index++) { const menu = new MessageSelectMenu(); menu.setCustomId(`csClassPoll+${index}`); @@ -72,12 +85,10 @@ export default { msgInt.reply({ embeds: [infoEmbed], components: row_chunks[index] }); } else { - msgInt.channel!.send({ components: row_chunks[index] }); + msgInt.reply({ components: row_chunks[index] }); } - // await on a new promise that resolves itself after a delay of 200 ms - await new Promise((resolve) => { - setTimeout(resolve, 200); - }); + + await sleep(200); } // Log the command usage @@ -92,21 +103,3 @@ export default { ); }, } as ICommand; - -// Splits any size list into lists of at most `max_list_len` -function split_list(list: Array, max_list_len: number) { - let class_chunks = []; - for (let i = 0; i < list.length; i += max_list_len) { - class_chunks.push(list.slice(i, i + max_list_len)); - } - return class_chunks; -} - -// consumes a Class and returns Message Selec tOption data -function create_option_from_class(_class: Class): MessageSelectOptionData { - return { - label: _class.CODE, - value: _class.CODE, - description: _class.TITLE, - }; -} diff --git a/commands/owner/staffPoll.ts b/commands/owner/staffPoll.ts index c35d75c..8b6cc06 100644 --- a/commands/owner/staffPoll.ts +++ b/commands/owner/staffPoll.ts @@ -58,8 +58,12 @@ export default { ) ); + if (msgInt.guild === null) { + console.log(chalk.red("No guild")); + return; + } // Send the embed and message component rows - if (!checkForRoles(msgInt.guild!)) { + if (!(await checkForRoles(msgInt.guild))) { msgInt.reply( "Please run the `/createRoles` command in this server to create the necessary roles for this poll!" ); diff --git a/commands/owner/yearPoll.ts b/commands/owner/yearPoll.ts index 17f6e62..2042765 100644 --- a/commands/owner/yearPoll.ts +++ b/commands/owner/yearPoll.ts @@ -66,15 +66,19 @@ export default { ) ); + if (msgInt.guild === null) { + console.log(chalk.red("No guild")); + return; + } // Send the embed and message component rows - if (!checkForRoles(msgInt.guild!)) { - msgInt.reply({ + if (!(await checkForRoles(msgInt.guild))) { + await msgInt.reply({ content: "Please run the /createRoles command in this server to create the necessary roles for this poll!", ephemeral: true, }); } else { - msgInt.reply({ embeds: [infoEmbed], components: [row] }); + await msgInt.reply({ embeds: [infoEmbed], components: [row] }); } // Log the command usage diff --git a/commands/user/clear.ts b/commands/user/clear.ts index 207b792..d3520a9 100644 --- a/commands/user/clear.ts +++ b/commands/user/clear.ts @@ -1,6 +1,9 @@ import chalk from "chalk"; import { GuildMember } from "discord.js"; import { ICommand } from "wokcommands"; +import { classModel } from "../../models/classModel"; +import { staffModel } from "../../models/staffModel"; +import { yearModel } from "../../models/yearModel"; import { removeRole } from "../../rolesOps"; export default { @@ -16,9 +19,9 @@ export default { // Remove roles from user if (!interaction.member) return; const member = interaction.member as GuildMember; - removeRole(member, "class"); - removeRole(member, "year"); - removeRole(member, "staff"); + await removeRole(member, classModel); + await removeRole(member, yearModel); + await removeRole(member, staffModel); // Reply to the user interaction.reply({ content: "Cleared all roles that I have assigned to you.", diff --git a/commands/user/github.ts b/commands/user/github.ts index 9ca20fe..f94f6d0 100644 --- a/commands/user/github.ts +++ b/commands/user/github.ts @@ -30,7 +30,7 @@ export default { .setDescription(description) .setFooter({ text: footer, iconURL: footerIcon }); - interaction.reply({ embeds: [Embed] }); + await interaction.reply({ embeds: [Embed] }); // Log the command usage console.log( diff --git a/commands/user/help.ts b/commands/user/help.ts index 3c3030e..9bd3086 100644 --- a/commands/user/help.ts +++ b/commands/user/help.ts @@ -53,7 +53,7 @@ export default { .setDescription(description) .addFields(fields) .setFooter({ text: footer, iconURL: footerIcon }); - interaction.reply({ embeds: [Embed] }); + await interaction.reply({ embeds: [Embed] }); // Log the command usage console.log( diff --git a/commands/user/ping.ts b/commands/user/ping.ts index 672d6b5..e9d5fec 100644 --- a/commands/user/ping.ts +++ b/commands/user/ping.ts @@ -26,7 +26,7 @@ export default { .setFooter({ text: footer, iconURL: footerIcon }); // Return the embed - interaction.reply({ embeds: [Embed] }); + await interaction.reply({ embeds: [Embed] }); // Log the command usage console.log( diff --git a/commands/user/status.ts b/commands/user/status.ts index 22742c2..bd7a48d 100644 --- a/commands/user/status.ts +++ b/commands/user/status.ts @@ -30,7 +30,7 @@ export default { .setDescription(description) .setFooter({ text: footer, iconURL: footerIcon }); - interaction.reply({ embeds: [Embed] }); + await interaction.reply({ embeds: [Embed] }); // Log the command usage console.log( diff --git a/commands/user/uptime.ts b/commands/user/uptime.ts index c2688cc..17adffe 100644 --- a/commands/user/uptime.ts +++ b/commands/user/uptime.ts @@ -1,5 +1,5 @@ import chalk from "chalk"; -import { MessageEmbed, TextChannel } from "discord.js"; +import { MessageEmbed } from "discord.js"; import { ICommand } from "wokcommands"; export default { @@ -12,12 +12,8 @@ export default { requiredPermissions: ["SEND_MESSAGES"], callback: async ({ client, interaction }) => { - // Command information - const id = interaction.user.id; - const chan = interaction.channel as TextChannel; - // Computed values - const time = client.uptime!; + const time = client.uptime !== null ? client.uptime : 0; const days = Math.floor(time / 86400000); const hours = Math.floor(time / 3600000) % 24; const minutes = Math.floor(time / 60000) % 60; @@ -38,7 +34,7 @@ export default { .setFooter({ text: footer, iconURL: footerIcon }); // Return the embed - interaction.reply({ embeds: [Embed] }); + await interaction.reply({ embeds: [Embed] }); // Log the command usage console.log( diff --git a/commands/user/version.ts b/commands/user/version.ts index 5a5d42f..c039b87 100644 --- a/commands/user/version.ts +++ b/commands/user/version.ts @@ -26,7 +26,7 @@ export default { .setFooter({ text: footer, iconURL: footerIcon }); // Return the embed - interaction.reply({ embeds: [Embed] }); + await interaction.reply({ embeds: [Embed] }); // Log the command usage console.log( diff --git a/features/interactionCreate.ts b/features/interactionCreate.ts index 2956a80..7f25607 100644 --- a/features/interactionCreate.ts +++ b/features/interactionCreate.ts @@ -1,5 +1,8 @@ import { Client, MessageEmbed, GuildMember } from "discord.js"; +import { yearModel } from "../models/yearModel"; import { removeRole, addNewRole } from "../rolesOps"; +import { staffModel } from "../models/staffModel"; +import { classModel } from "../models/classModel"; // Listen interactionCreate events from the client export default (client: Client): void => { @@ -30,14 +33,15 @@ export default (client: Client): void => { ephemeral: true, }); // Remove any previous roles in the dictionary from the user + await removeRole(member, yearModel); + + // No role to add if (interaction.values[0] === "None") { - removeRole(member, "year"); return; } - removeRole(member, "year"); // Assign the new role to the user - addNewRole(member, "year", interaction.values[0]); + await addNewRole(member, yearModel, interaction.values[0]); } else if (interaction.customId === "collegeStaffPoll") { // Set the embed title const title = "College Staff Poll"; @@ -52,15 +56,17 @@ export default (client: Client): void => { ], ephemeral: true, }); + // Remove any previous roles in the dictionary from the user + await removeRole(member, staffModel); + + // No role to add if (interaction.values[0] === "None") { - removeRole(member, "staff"); return; } - removeRole(member, "staff"); // Assign the new role to the user - addNewRole(member, "staff", interaction.values[0]); + await addNewRole(member, staffModel, interaction.values[0]); } else if (interaction.customId.startsWith("csClassPoll+")) { // Set the embed title const title = "CS Class Poll"; @@ -77,7 +83,7 @@ export default (client: Client): void => { }); // Assign the new role to the user - addNewRole(member, "class", interaction.values[0]); + await addNewRole(member, classModel, interaction.values[0]); } }); }; diff --git a/features/status-changer.ts b/features/status-changer.ts index c232230..0d46db6 100644 --- a/features/status-changer.ts +++ b/features/status-changer.ts @@ -1,6 +1,6 @@ import { Client } from "discord.js"; -export default async (client: Client) => { +export default (client: Client) => { const statusOptions = [ `/help | Ping: ${client.ws.ping}ms`, `V.${process.env.VERSION}`, diff --git a/features/statuspage.ts b/features/statuspage.ts index 2c09c0d..a555560 100644 --- a/features/statuspage.ts +++ b/features/statuspage.ts @@ -4,7 +4,7 @@ import { Client } from "discord.js"; export default (client: Client): void => { const updateStatus = async () => { // This function is called every 1 minutes and pings the network status page for uptime monitoring - axios.get( + await axios.get( `https://${process.env.UPTIME_KUMA_MONITOR_DOMAIN}/api/push/${process.env.UPTIME_KUMA_MONITOR_ID}?msg=OK&ping=${client.ws.ping}` ); setTimeout(updateStatus, 1000 * 60); diff --git a/index.ts b/index.ts index 3bea332..ee5ec5e 100644 --- a/index.ts +++ b/index.ts @@ -6,7 +6,10 @@ import chalk from "chalk"; import dotenv from "dotenv"; // import custom modules -import { checkForRoles } from "./rolesOps"; +import { checkForRoles, checkIfCollectionsExist } from "./rolesOps"; +import { classModel } from "./models/classModel"; +import { staffModel } from "./models/staffModel"; +import { yearModel } from "./models/yearModel"; // import all environment variables from .env file dotenv.config(); @@ -21,7 +24,7 @@ const client = new DiscordJs.Client({ ], }); -client.on("ready", () => { +client.on("ready", async () => { if (client.user) { console.log(chalk.green(`Logged in as ${client.user.tag}!`)); console.log( @@ -29,9 +32,12 @@ client.on("ready", () => { ); // Check to make sure the roles exist in all servers console.log("Checking if all roles exist in servers."); - client.guilds.cache.forEach(async (guild) => { - checkForRoles(guild); - }); + + await Promise.all( + client.guilds.cache.map((guild) => { + checkForRoles(guild); + }) + ); } const dbOptions = { @@ -58,6 +64,9 @@ client.on("ready", () => { // Set up the connection to the database wok.on("databaseConnected", async () => { + await checkIfCollectionsExist(classModel); + await checkIfCollectionsExist(staffModel); + await checkIfCollectionsExist(yearModel); console.log(chalk.green("Connected to the database")); }); }); diff --git a/models/classModel.ts b/models/classModel.ts index 6413cb9..b0d3c07 100644 --- a/models/classModel.ts +++ b/models/classModel.ts @@ -1,17 +1,28 @@ import mongoose, { Schema } from "mongoose"; +import { IRole } from "../rolesOps"; -const CLASS = new Schema({ - id: String, - CODE: String, - TITLE: String, - INFO: String, - ROLE_NAME: String, - ROLE_ID: String, - CHANNEL_NAME: String, - CHANNEL_ID: String, - UUID: String, +export interface IClass extends IRole { + id: string; + CODE: string; + TITLE: string; + INFO: string; + CHANNEL_NAME: string; + CHANNEL_ID: string; + UUID: string; +} + +const ClassSchema = new Schema({ + id: { type: String }, + CODE: { type: String, required: true }, + TITLE: { type: String, required: true }, + INFO: { type: String, required: true }, + ROLE_NAME: { type: String, required: true }, + ROLE_ID: { type: String, required: true }, + CHANNEL_NAME: { type: String }, + CHANNEL_ID: { type: String }, + UUID: { type: String, default: "remove" }, }); const name = "class"; -export = mongoose.models[name] || mongoose.model(name, CLASS, name); +export const classModel = mongoose.model(name, ClassSchema, name); diff --git a/models/staffModel.ts b/models/staffModel.ts index d92ace9..3d6f3c2 100644 --- a/models/staffModel.ts +++ b/models/staffModel.ts @@ -1,12 +1,18 @@ import mongoose, { Schema } from "mongoose"; +import { IRole } from "../rolesOps"; -const STAFF = new Schema({ - id: String, - NAME: String, - ROLE_NAME: String, - ROLE_ID: String, +export interface IStaff extends IRole { + id: string; + NAME: string; +} + +const StaffSchema = new Schema({ + id: { type: String }, + NAME: { type: String, required: true }, + ROLE_NAME: { type: String, required: true }, + ROLE_ID: { type: String, required: true }, }); const name = "staff"; -export = mongoose.models[name] || mongoose.model(name, STAFF, name); +export const staffModel = mongoose.model(name, StaffSchema, name); diff --git a/models/yearModel.ts b/models/yearModel.ts index f8bd9f0..dcded9e 100644 --- a/models/yearModel.ts +++ b/models/yearModel.ts @@ -1,12 +1,17 @@ import mongoose, { Schema } from "mongoose"; +import { IRole } from "../rolesOps"; -const YEAR = new Schema({ - id: String, - NAME: String, - ROLE_NAME: String, - ROLE_ID: String, +export interface IYear extends IRole { + id: string; + NAME: string; +} + +const YearSchema = new Schema({ + id: { type: String }, + NAME: { type: String, required: true }, + ROLE_NAME: { type: String, required: true }, + ROLE_ID: { type: String, required: true }, }); const name = "year"; - -export = mongoose.models[name] || mongoose.model(name, YEAR, name); +export const yearModel = mongoose.model(name, YearSchema, name); diff --git a/package-lock.json b/package-lock.json index 059f41a..860b625 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,9 @@ "license": "ISC", "dependencies": { "axios": "^0.27.2", - "chalk": "^4.1.2", + "chalk": "4.1.2", "discord.js": "^13.9.2", - "mongoose": "^6.2.2", + "mongoose": "^6.5.4", "path": "^0.12.7", "prettier": "^2.7.1", "ts-node": "^10.5.0", @@ -96,9 +96,9 @@ } }, "node_modules/@sapphire/shapeshift": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.5.1.tgz", - "integrity": "sha512-7JFsW5IglyOIUQI1eE0g6h06D/Far6HqpcowRScgCiLSqTf3hhkPWCWotVTtVycnDCMYIwPeaw6IEPBomKC8pA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.6.0.tgz", + "integrity": "sha512-tu2WLRdo5wotHRvsCkspg3qMiP6ETC3Q1dns1Q5V6zKUki+1itq6AbhMwohF9ZcLoYqg+Y8LkgRRtVxxTQVTBQ==", "dependencies": { "fast-deep-equal": "^3.1.3", "lodash.uniqwith": "^4.5.0" @@ -129,9 +129,9 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, "node_modules/@types/node": { - "version": "18.7.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz", - "integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==" + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" }, "node_modules/@types/node-fetch": { "version": "2.6.2", @@ -156,9 +156,9 @@ } }, "node_modules/@types/webidl-conversions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", - "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" }, "node_modules/@types/whatwg-url": { "version": "8.2.2", @@ -548,9 +548,9 @@ } }, "node_modules/mongoose": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.3.tgz", - "integrity": "sha512-0L2ZOPzNQ7kcIgpdfpmVXc+/SypdhzcTlaHXYa983u1lrVp7/i3ekwHpPiTXxYBvV6FwBAsFoHI7+Ovf8tp3Mg==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.4.tgz", + "integrity": "sha512-8hKV+9baDa7fyWRADQcSN/c0/QQbnewA2D0xOqdFb7f1UGYAHk4YSMNu9Hu2bdRUfQbK/daFuqlcmI17j6/8eg==", "dependencies": { "bson": "^4.6.5", "kareem": "2.4.1", @@ -933,9 +933,9 @@ "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==" }, "@sapphire/shapeshift": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.5.1.tgz", - "integrity": "sha512-7JFsW5IglyOIUQI1eE0g6h06D/Far6HqpcowRScgCiLSqTf3hhkPWCWotVTtVycnDCMYIwPeaw6IEPBomKC8pA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.6.0.tgz", + "integrity": "sha512-tu2WLRdo5wotHRvsCkspg3qMiP6ETC3Q1dns1Q5V6zKUki+1itq6AbhMwohF9ZcLoYqg+Y8LkgRRtVxxTQVTBQ==", "requires": { "fast-deep-equal": "^3.1.3", "lodash.uniqwith": "^4.5.0" @@ -962,9 +962,9 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, "@types/node": { - "version": "18.7.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz", - "integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==" + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" }, "@types/node-fetch": { "version": "2.6.2", @@ -988,9 +988,9 @@ } }, "@types/webidl-conversions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", - "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" }, "@types/whatwg-url": { "version": "8.2.2", @@ -1255,9 +1255,9 @@ } }, "mongoose": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.3.tgz", - "integrity": "sha512-0L2ZOPzNQ7kcIgpdfpmVXc+/SypdhzcTlaHXYa983u1lrVp7/i3ekwHpPiTXxYBvV6FwBAsFoHI7+Ovf8tp3Mg==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.4.tgz", + "integrity": "sha512-8hKV+9baDa7fyWRADQcSN/c0/QQbnewA2D0xOqdFb7f1UGYAHk4YSMNu9Hu2bdRUfQbK/daFuqlcmI17j6/8eg==", "requires": { "bson": "^4.6.5", "kareem": "2.4.1", @@ -1476,4 +1476,4 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index e04d259..b135bcb 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,13 @@ "homepage": "https://github.com/Antares-Network/CSSC-Bot#readme", "dependencies": { "axios": "^0.27.2", - "chalk": "^4.1.2", + "chalk": "4.1.2", "discord.js": "^13.9.2", - "mongoose": "^6.2.2", + "mongoose": "^6.5.4", "path": "^0.12.7", "prettier": "^2.7.1", "ts-node": "^10.5.0", "typescript": "^4.5.5", "wokcommands": "^1.5.3" } -} +} \ No newline at end of file diff --git a/rolesOps.ts b/rolesOps.ts index ca6ead1..4b5767b 100644 --- a/rolesOps.ts +++ b/rolesOps.ts @@ -1,14 +1,26 @@ import { GuildMember, Guild } from "discord.js"; import chalk from "chalk"; -import classModel from "./models/classModel"; -import staffModel from "./models/staffModel"; -import yearModel from "./models/yearModel"; +import { classModel } from "./models/classModel"; +import { staffModel } from "./models/staffModel"; +import { yearModel } from "./models/yearModel"; +import { Model } from "mongoose"; +export interface IRole { + ROLE_NAME: string; + ROLE_ID: string; +} + +export async function checkIfCollectionsExist(model: Model) { + if (!(await model.exists({}))) { + throw new Error(`${model.name} collection does not exist`); + } +} async function dbQuery() { - const classes = await classModel.find({}); - const staff = await staffModel.find({}); - const years = await yearModel.find({}); - return [classes, staff, years]; + return await Promise.all([ + classModel.find({}), + staffModel.find({}), + yearModel.find({}), + ]); } export async function getUsersRoles(member: GuildMember): Promise { @@ -23,20 +35,13 @@ export async function getUsersRoles(member: GuildMember): Promise { return list; } -export async function removeRole( +export async function removeRole( member: GuildMember, - type: string + model: Model ): Promise { // This function is triggered when a user changes their role, // it removes the previous role from the user - let list = []; - if (type === "class") { - list = await classModel.find({}); - } else if (type === "staff") { - list = await staffModel.find({}); - } else if (type === "year") { - list = await yearModel.find({}); - } + const list = await model.find({}); for (const role of list) { if (member.roles.cache.has(role.ROLE_ID)) { @@ -52,22 +57,28 @@ export async function removeRole( } } -export async function addNewRole( +export async function addNewRole( member: GuildMember, - type: string, + model: Model, id: string ) { // This function is triggered when a user changes their role, it adds the new role to the user let role; - if (type === "class") { - role = await classModel.findOne({ CODE: id }); - } else if (type === "staff") { - role = await staffModel.findOne({ NAME: id }); - } else if (type === "year") { - role = await yearModel.findOne({ NAME: id }); + switch (model.modelName) { + case "class": + //TODO: Rewrite IRole to include name, and replace CODE with name + role = await model.findOne({ CODE: id }); + break; + default: + role = await model.findOne({ NAME: id }); + } + + if (role === null) { + throw new Error(`No roll found with id: ${id}`); } - if (!member.roles.cache.has(role?.ROLE_ID)) { - await member.roles.add(role?.ROLE_ID); + + if (!member.roles.cache.has(role.ROLE_ID)) { + await member.roles.add(role.ROLE_ID); console.log( chalk.green( `Added role ${chalk.green(role.ROLE_NAME)} to ${chalk.yellow( @@ -78,15 +89,11 @@ export async function addNewRole( } } -export async function createRoles(guild: Guild, type: string): Promise { - let list; - if (type === "class") { - list = await classModel.find({}); - } else if (type === "staff") { - list = await staffModel.find({}); - } else if (type === "year") { - list = await yearModel.find({}); - } +export async function createRoles( + guild: Guild, + model: Model +): Promise { + const list = await model.find({}); list?.forEach(async (element) => { if (!guild.roles.cache.find((r) => r.name === element.ROLE_NAME)) { @@ -98,10 +105,14 @@ export async function createRoles(guild: Guild, type: string): Promise { console.log(chalk.yellow(`${element}: ${role?.id}`)); } else { // If the role already exists, print the id to the console - const role = guild.roles.cache.find((r) => r.name === element.ROLE_NAME); - element.ROLE_ID = role?.id; + const id = guild.roles.cache.find( + (r) => r.name === element.ROLE_NAME + )?.id; + if (id !== undefined) { + element.ROLE_ID = id; + } element.save(); - console.log(chalk.yellow(`${element}: ${role?.id}`)); + console.log(chalk.yellow(`${element}: ${id}`)); } }); } @@ -110,7 +121,7 @@ export async function checkForRoles(guild: Guild): Promise { // Check if all roles exist in a guild // Return true if they do, false if they don't - let collection: boolean[] = []; + const collection: boolean[] = []; for (const group of await dbQuery()) { for (const element of group) { @@ -120,7 +131,7 @@ export async function checkForRoles(guild: Guild): Promise { if (name == undefined && id == undefined) { console.log( chalk.red.bold( - `Role ${name} does not exist in ${guild.name}, Please run the /createRoles command in that server.` + `Role ${element.ROLE_NAME} does not exist in ${guild.name}, Please run the /createRoles command in that server.` ) ); collection.push(false); diff --git a/util.ts b/util.ts new file mode 100644 index 0000000..6d492e6 --- /dev/null +++ b/util.ts @@ -0,0 +1,6 @@ +export function sleep(ms: number) { + // Create new promise that resolves itself after a delay of + return new Promise((resolve: (args: void) => void) => + setTimeout(resolve, ms) + ); +}