From a883ae836947108f70f6c1b6a36d022fde7ddbd9 Mon Sep 17 00:00:00 2001 From: probro27 Date: Tue, 19 Mar 2024 18:00:34 -0400 Subject: [PATCH 01/13] created new uwflow command --- docs/COMMAND-WIKI.md | 15 +++++++++++++++ src/commandDetails/uwflow/info.ts | 30 ++++++++++++++++++++++++++++++ src/commands/uwflow/uwflow.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/commandDetails/uwflow/info.ts create mode 100644 src/commands/uwflow/uwflow.ts diff --git a/docs/COMMAND-WIKI.md b/docs/COMMAND-WIKI.md index c2cc7eca..5f344bae 100644 --- a/docs/COMMAND-WIKI.md +++ b/docs/COMMAND-WIKI.md @@ -304,6 +304,21 @@ - ``description``: The description of the customization to be set for the user. - **Subcommands:** None +# UWFLOW +## uwflow +- **Aliases:** None +- **Description:** Handle UWFlow commands. +- **Examples:** +- **Options:** None +- **Subcommands:** `info` + +## uwflow info +- **Aliases:** `information`, `i` +- **Description:** Get info about courses using UWFlow. +- **Examples:**
`.uwflow info`
`.uwflow information`
`.uwflow i` +- **Options:** None +- **Subcommands:** None + # SUGGESTION ## suggestion - **Aliases:** ``suggest`` diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts new file mode 100644 index 00000000..784e532c --- /dev/null +++ b/src/commandDetails/uwflow/info.ts @@ -0,0 +1,30 @@ +import { container } from '@sapphire/framework'; +import { + CodeyCommandDetails, + SapphireMessageExecuteType, + SapphireMessageResponse, +} from '../../codeyCommand'; + +const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( + _client, + _messageFromUser, + _args, +): Promise => { + return 'UWFlow is a website where students can view course reviews and ratings.'; +}; + +export const uwflowInfoCommandDetails: CodeyCommandDetails = { + name: 'info', + aliases: ['information', 'i'], + description: 'Get info about courses using UWFlow.', + detailedDescription: `**Examples:** +\`${container.botPrefix}uwflow info\` +\`${container.botPrefix}uwflow information\` +\`${container.botPrefix}uwflow i\``, + + isCommandResponseEphemeral: false, + messageWhenExecutingCommand: 'Getting information about UWFlow:', + executeCommand: uwflowInfoExecuteCommand, + options: [], + subcommandDetails: {}, +}; diff --git a/src/commands/uwflow/uwflow.ts b/src/commands/uwflow/uwflow.ts new file mode 100644 index 00000000..824468e7 --- /dev/null +++ b/src/commands/uwflow/uwflow.ts @@ -0,0 +1,28 @@ +import { Command } from '@sapphire/framework'; +import { uwflowInfoCommandDetails } from '../../commandDetails/uwflow/info'; +import { CodeyCommand, CodeyCommandDetails } from '../../codeyCommand'; + +const uwflowCommandDetails: CodeyCommandDetails = { + name: 'uwflow', + aliases: [], + description: 'Handle UWFlow commands.', + detailedDescription: `**Examples:**`, + options: [], + subcommandDetails: { + info: uwflowInfoCommandDetails, + }, + defaultSubcommandDetails: uwflowInfoCommandDetails, +}; + +export class UWFlowCommand extends CodeyCommand { + details = uwflowCommandDetails; + + public constructor(context: Command.Context, options: Command.Options) { + super(context, { + ...options, + aliases: uwflowCommandDetails.aliases, + description: uwflowCommandDetails.description, + detailedDescription: uwflowCommandDetails.detailedDescription, + }); + } +} From 6eb00de9ae7ebf1aed9e52754fbd96b545fb8b6c Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Thu, 21 Mar 2024 20:13:19 +0000 Subject: [PATCH 02/13] Added components for uwflow command --- src/components/uwflow.ts | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/components/uwflow.ts diff --git a/src/components/uwflow.ts b/src/components/uwflow.ts new file mode 100644 index 00000000..2c3bcd44 --- /dev/null +++ b/src/components/uwflow.ts @@ -0,0 +1,62 @@ +import axios from 'axios'; + +const uwflowApiUrl = 'https://uwflow.com/graphql'; + +interface courseInfoFromUrl { + data: { + course: [ + { + code: string; + name: string; + description: string; + rating: { + liked: number; + easy: number; + useful: number; + filled_count: number; + comment_count: number; + }; + }, + ]; + }; +} + +export interface courseInfo { + code: string; + name: string; + description: string; + liked: number; + easy: number; + useful: number; + filled_count: number; + comment_count: number; +} + +export const getCourseInfo = async (courseCode: string): Promise => { + const resultFromUWFLow: courseInfoFromUrl = ( + await axios.post(uwflowApiUrl, { + operationName: 'getCourse', + variables: { + code: courseCode, + }, + query: 'query getCourse($code: String) {\n course(where: { code: { _eq: $code } }) {\n code\n name\n description\n rating {\n liked\n easy\n useful\n filled_count\n comment_count\n}\n}\n}\n', + }) + ).data; + + if (resultFromUWFLow.data.course.length < 1) { + return "Oops, course does not exist!"; + } + + const result: courseInfo = { + code: resultFromUWFLow.data.course[0].code, + name: resultFromUWFLow.data.course[0].name, + description: resultFromUWFLow.data.course[0].description, + liked: resultFromUWFLow.data.course[0].rating.liked, + easy: resultFromUWFLow.data.course[0].rating.easy, + useful: resultFromUWFLow.data.course[0].rating.useful, + filled_count: resultFromUWFLow.data.course[0].rating.filled_count, + comment_count: resultFromUWFLow.data.course[0].rating.comment_count, + } + + return result; +} From b3171ece45942a6a42e91db9a5278ced7d94755f Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Thu, 21 Mar 2024 20:13:58 +0000 Subject: [PATCH 03/13] Updated info subcommand functionalities --- src/commandDetails/uwflow/info.ts | 47 +++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts index 784e532c..1e375c64 100644 --- a/src/commandDetails/uwflow/info.ts +++ b/src/commandDetails/uwflow/info.ts @@ -1,16 +1,44 @@ import { container } from '@sapphire/framework'; import { CodeyCommandDetails, + CodeyCommandOptionType, SapphireMessageExecuteType, SapphireMessageResponse, } from '../../codeyCommand'; +import { courseInfo, getCourseInfo } from '../../components/uwflow'; +import { EmbedBuilder } from 'discord.js'; const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( _client, _messageFromUser, - _args, + args, ): Promise => { - return 'UWFlow is a website where students can view course reviews and ratings.'; + const courseCode = args['course_code']; + const courseInfo: courseInfo | string = await getCourseInfo(courseCode); + + if (courseInfo === "Oops, course does not exist!") { + const courseEmbed = new EmbedBuilder().setColor('Red') + .setTitle(`Information for ${courseCode.toUpperCase()}`) + .setDescription(courseInfo); + return { embeds: [courseEmbed] }; + } + + const actualCourseInfo = courseInfo; + + const code = actualCourseInfo.code.toUpperCase(); + const name = actualCourseInfo.name; + const description = actualCourseInfo.description; + const liked = (actualCourseInfo.liked * 100).toFixed(2); + const easy = (actualCourseInfo.easy * 100).toFixed(2); + const useful = (actualCourseInfo.useful * 100).toFixed(2); + + const embedDescription = `Course code: ${code} \n\n Course name: ${name} \n\n Course description: \n ${description} \n\n Like rate: ${liked}% \n\n Easy rate: ${easy}% \n\n Useful rate: ${useful}%`; + + const courseEmbed = new EmbedBuilder().setColor('Blue') + .setTitle(`Information for ${code}`) + .setDescription(embedDescription); + + return { embeds: [courseEmbed] }; }; export const uwflowInfoCommandDetails: CodeyCommandDetails = { @@ -18,13 +46,20 @@ export const uwflowInfoCommandDetails: CodeyCommandDetails = { aliases: ['information', 'i'], description: 'Get info about courses using UWFlow.', detailedDescription: `**Examples:** -\`${container.botPrefix}uwflow info\` -\`${container.botPrefix}uwflow information\` -\`${container.botPrefix}uwflow i\``, +\`${container.botPrefix}uwflow info cs135\` +\`${container.botPrefix}uwflow information cs246\` +\`${container.botPrefix}uwflow i cs240\``, isCommandResponseEphemeral: false, messageWhenExecutingCommand: 'Getting information about UWFlow:', executeCommand: uwflowInfoExecuteCommand, - options: [], + options: [ + { + name: 'course_code', + description: 'The code of the course, all lowercase, e.g. cs135', + type: CodeyCommandOptionType.STRING, + required: true, + } + ], subcommandDetails: {}, }; From 411d041c5d06159dcf166c366c0bdbcae60888ce Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Thu, 21 Mar 2024 20:14:33 +0000 Subject: [PATCH 04/13] Updated documentation for uwflow command --- docs/COMMAND-WIKI.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/COMMAND-WIKI.md b/docs/COMMAND-WIKI.md index 5f344bae..ea4a2d2f 100644 --- a/docs/COMMAND-WIKI.md +++ b/docs/COMMAND-WIKI.md @@ -315,8 +315,8 @@ ## uwflow info - **Aliases:** `information`, `i` - **Description:** Get info about courses using UWFlow. -- **Examples:**
`.uwflow info`
`.uwflow information`
`.uwflow i` -- **Options:** None +- **Examples:**
`.uwflow info cs135`
`.uwflow information cs246`
`.uwflow i cs240` +- **Options:** - **Subcommands:** None # SUGGESTION From 907792aa6833402292f8678bc98120688f9d13a7 Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Thu, 21 Mar 2024 20:18:03 +0000 Subject: [PATCH 05/13] Fixed linting issues --- src/commandDetails/uwflow/info.ts | 18 +++--- src/components/uwflow.ts | 97 ++++++++++++++++--------------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts index 1e375c64..6bd9ca7f 100644 --- a/src/commandDetails/uwflow/info.ts +++ b/src/commandDetails/uwflow/info.ts @@ -16,10 +16,11 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( const courseCode = args['course_code']; const courseInfo: courseInfo | string = await getCourseInfo(courseCode); - if (courseInfo === "Oops, course does not exist!") { - const courseEmbed = new EmbedBuilder().setColor('Red') - .setTitle(`Information for ${courseCode.toUpperCase()}`) - .setDescription(courseInfo); + if (courseInfo === 'Oops, course does not exist!') { + const courseEmbed = new EmbedBuilder() + .setColor('Red') + .setTitle(`Information for ${courseCode.toUpperCase()}`) + .setDescription(courseInfo); return { embeds: [courseEmbed] }; } @@ -34,9 +35,10 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( const embedDescription = `Course code: ${code} \n\n Course name: ${name} \n\n Course description: \n ${description} \n\n Like rate: ${liked}% \n\n Easy rate: ${easy}% \n\n Useful rate: ${useful}%`; - const courseEmbed = new EmbedBuilder().setColor('Blue') - .setTitle(`Information for ${code}`) - .setDescription(embedDescription); + const courseEmbed = new EmbedBuilder() + .setColor('Blue') + .setTitle(`Information for ${code}`) + .setDescription(embedDescription); return { embeds: [courseEmbed] }; }; @@ -59,7 +61,7 @@ export const uwflowInfoCommandDetails: CodeyCommandDetails = { description: 'The code of the course, all lowercase, e.g. cs135', type: CodeyCommandOptionType.STRING, required: true, - } + }, ], subcommandDetails: {}, }; diff --git a/src/components/uwflow.ts b/src/components/uwflow.ts index 2c3bcd44..fe426083 100644 --- a/src/components/uwflow.ts +++ b/src/components/uwflow.ts @@ -3,60 +3,61 @@ import axios from 'axios'; const uwflowApiUrl = 'https://uwflow.com/graphql'; interface courseInfoFromUrl { - data: { - course: [ - { - code: string; - name: string; - description: string; - rating: { - liked: number; - easy: number; - useful: number; - filled_count: number; - comment_count: number; - }; - }, - ]; - }; + data: { + course: [ + { + code: string; + name: string; + description: string; + rating: { + liked: number; + easy: number; + useful: number; + filled_count: number; + comment_count: number; + }; + }, + ]; + }; } export interface courseInfo { - code: string; - name: string; - description: string; - liked: number; - easy: number; - useful: number; - filled_count: number; - comment_count: number; + code: string; + name: string; + description: string; + liked: number; + easy: number; + useful: number; + filled_count: number; + comment_count: number; } export const getCourseInfo = async (courseCode: string): Promise => { - const resultFromUWFLow: courseInfoFromUrl = ( - await axios.post(uwflowApiUrl, { - operationName: 'getCourse', - variables: { - code: courseCode, - }, - query: 'query getCourse($code: String) {\n course(where: { code: { _eq: $code } }) {\n code\n name\n description\n rating {\n liked\n easy\n useful\n filled_count\n comment_count\n}\n}\n}\n', - }) - ).data; + const resultFromUWFLow: courseInfoFromUrl = ( + await axios.post(uwflowApiUrl, { + operationName: 'getCourse', + variables: { + code: courseCode, + }, + query: + 'query getCourse($code: String) {\n course(where: { code: { _eq: $code } }) {\n code\n name\n description\n rating {\n liked\n easy\n useful\n filled_count\n comment_count\n}\n}\n}\n', + }) + ).data; - if (resultFromUWFLow.data.course.length < 1) { - return "Oops, course does not exist!"; - } + if (resultFromUWFLow.data.course.length < 1) { + return 'Oops, course does not exist!'; + } - const result: courseInfo = { - code: resultFromUWFLow.data.course[0].code, - name: resultFromUWFLow.data.course[0].name, - description: resultFromUWFLow.data.course[0].description, - liked: resultFromUWFLow.data.course[0].rating.liked, - easy: resultFromUWFLow.data.course[0].rating.easy, - useful: resultFromUWFLow.data.course[0].rating.useful, - filled_count: resultFromUWFLow.data.course[0].rating.filled_count, - comment_count: resultFromUWFLow.data.course[0].rating.comment_count, - } + const result: courseInfo = { + code: resultFromUWFLow.data.course[0].code, + name: resultFromUWFLow.data.course[0].name, + description: resultFromUWFLow.data.course[0].description, + liked: resultFromUWFLow.data.course[0].rating.liked, + easy: resultFromUWFLow.data.course[0].rating.easy, + useful: resultFromUWFLow.data.course[0].rating.useful, + filled_count: resultFromUWFLow.data.course[0].rating.filled_count, + comment_count: resultFromUWFLow.data.course[0].rating.comment_count, + }; - return result; -} + return result; +}; From 3b1a6a8c979f6ced1106e0e3c950c8ee8e904447 Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Mon, 25 Mar 2024 15:38:50 +0000 Subject: [PATCH 06/13] Added default behavior and modified error message --- src/commandDetails/uwflow/info.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts index 6bd9ca7f..e8cb890f 100644 --- a/src/commandDetails/uwflow/info.ts +++ b/src/commandDetails/uwflow/info.ts @@ -13,14 +13,28 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( _messageFromUser, args, ): Promise => { - const courseCode = args['course_code']; + const courseCodeArg = args['course_code']; + + // If no argument is passed, return default information + if (courseCodeArg === undefined) { + const defaultEmbed = new EmbedBuilder() + .setColor('Blue') + .setTitle('General Information') + .setDescription('UWFlow is a website where students can view course reviews and ratings.'); + return { embeds: [defaultEmbed] }; + } + + const courseCode = courseCodeArg; const courseInfo: courseInfo | string = await getCourseInfo(courseCode); + // If mistyped course code or course doesn't exist if (courseInfo === 'Oops, course does not exist!') { + const errorDesc = + 'Either the course does not exist or you did not type the course code in the correct format'; const courseEmbed = new EmbedBuilder() .setColor('Red') .setTitle(`Information for ${courseCode.toUpperCase()}`) - .setDescription(courseInfo); + .setDescription(errorDesc); return { embeds: [courseEmbed] }; } @@ -36,7 +50,7 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( const embedDescription = `Course code: ${code} \n\n Course name: ${name} \n\n Course description: \n ${description} \n\n Like rate: ${liked}% \n\n Easy rate: ${easy}% \n\n Useful rate: ${useful}%`; const courseEmbed = new EmbedBuilder() - .setColor('Blue') + .setColor('Green') .setTitle(`Information for ${code}`) .setDescription(embedDescription); @@ -58,9 +72,9 @@ export const uwflowInfoCommandDetails: CodeyCommandDetails = { options: [ { name: 'course_code', - description: 'The code of the course, all lowercase, e.g. cs135', + description: 'The course code, all lowercase, no spaces. Examples: cs135, amath351', type: CodeyCommandOptionType.STRING, - required: true, + required: false, }, ], subcommandDetails: {}, From 1e2d293ebe1b46777b77779b0cc366cb6e4b6c61 Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Mon, 25 Mar 2024 18:27:12 +0000 Subject: [PATCH 07/13] Redesigned embed --- src/commandDetails/uwflow/info.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts index e8cb890f..8d05de05 100644 --- a/src/commandDetails/uwflow/info.ts +++ b/src/commandDetails/uwflow/info.ts @@ -47,12 +47,17 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( const easy = (actualCourseInfo.easy * 100).toFixed(2); const useful = (actualCourseInfo.useful * 100).toFixed(2); - const embedDescription = `Course code: ${code} \n\n Course name: ${name} \n\n Course description: \n ${description} \n\n Like rate: ${liked}% \n\n Easy rate: ${easy}% \n\n Useful rate: ${useful}%`; - const courseEmbed = new EmbedBuilder() .setColor('Green') .setTitle(`Information for ${code}`) - .setDescription(embedDescription); + .addFields( + { name: 'Course code', value: code, inline: false }, + { name: 'Course name', value: name, inline: false }, + { name: 'Course description', value: description, inline: false }, + { name: 'Like ratings', value: `${liked}%`, inline: true }, + { name: 'Easy ratings', value: `${easy}%`, inline: true }, + { name: 'Useful ratings', value: `${useful}%`, inline: true }, + ); return { embeds: [courseEmbed] }; }; From 36bf2e4cdbec7dde7dcd8c79eb681b7d06dd2423 Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Mon, 25 Mar 2024 19:02:11 +0000 Subject: [PATCH 08/13] Accounted for different input formats --- src/commandDetails/uwflow/info.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts index 8d05de05..67ff2627 100644 --- a/src/commandDetails/uwflow/info.ts +++ b/src/commandDetails/uwflow/info.ts @@ -24,13 +24,14 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( return { embeds: [defaultEmbed] }; } - const courseCode = courseCodeArg; + // Standardize the course code (i.e. cs 135, CS135, CS 135 becomes cs135 for the GraphQL query) + const courseCode = courseCodeArg.split(' ').join('').toLowerCase(); + const courseInfo: courseInfo | string = await getCourseInfo(courseCode); // If mistyped course code or course doesn't exist if (courseInfo === 'Oops, course does not exist!') { - const errorDesc = - 'Either the course does not exist or you did not type the course code in the correct format'; + const errorDesc = 'Oops, that course does not exist!'; const courseEmbed = new EmbedBuilder() .setColor('Red') .setTitle(`Information for ${courseCode.toUpperCase()}`) @@ -77,7 +78,7 @@ export const uwflowInfoCommandDetails: CodeyCommandDetails = { options: [ { name: 'course_code', - description: 'The course code, all lowercase, no spaces. Examples: cs135, amath351', + description: 'The course code. Examples: cs135, cs 135, CS135, CS 135', type: CodeyCommandOptionType.STRING, required: false, }, From 9d381af7925137c4193b7a9fc03706b7901922f7 Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Mon, 24 Jun 2024 03:04:58 +0000 Subject: [PATCH 09/13] Refactored some code for more elegance --- src/commandDetails/uwflow/info.ts | 4 ++-- src/components/uwflow.ts | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts index 67ff2627..67a446a6 100644 --- a/src/commandDetails/uwflow/info.ts +++ b/src/commandDetails/uwflow/info.ts @@ -27,10 +27,10 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( // Standardize the course code (i.e. cs 135, CS135, CS 135 becomes cs135 for the GraphQL query) const courseCode = courseCodeArg.split(' ').join('').toLowerCase(); - const courseInfo: courseInfo | string = await getCourseInfo(courseCode); + const courseInfo: courseInfo | number = await getCourseInfo(courseCode); // If mistyped course code or course doesn't exist - if (courseInfo === 'Oops, course does not exist!') { + if (courseInfo === -1) { const errorDesc = 'Oops, that course does not exist!'; const courseEmbed = new EmbedBuilder() .setColor('Red') diff --git a/src/components/uwflow.ts b/src/components/uwflow.ts index fe426083..50652bff 100644 --- a/src/components/uwflow.ts +++ b/src/components/uwflow.ts @@ -32,7 +32,7 @@ export interface courseInfo { comment_count: number; } -export const getCourseInfo = async (courseCode: string): Promise => { +export const getCourseInfo = async (courseCode: string): Promise => { const resultFromUWFLow: courseInfoFromUrl = ( await axios.post(uwflowApiUrl, { operationName: 'getCourse', @@ -40,12 +40,25 @@ export const getCourseInfo = async (courseCode: string): Promise Date: Mon, 24 Jun 2024 03:48:05 +0000 Subject: [PATCH 10/13] Implemented req subcommand to retrieve requisites --- docs/COMMAND-WIKI.md | 9 +++- src/commandDetails/uwflow/req.ts | 73 ++++++++++++++++++++++++++++++++ src/commands/uwflow/uwflow.ts | 2 + src/components/uwflow.ts | 73 ++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/commandDetails/uwflow/req.ts diff --git a/docs/COMMAND-WIKI.md b/docs/COMMAND-WIKI.md index ea4a2d2f..308c0e15 100644 --- a/docs/COMMAND-WIKI.md +++ b/docs/COMMAND-WIKI.md @@ -310,7 +310,7 @@ - **Description:** Handle UWFlow commands. - **Examples:** - **Options:** None -- **Subcommands:** `info` +- **Subcommands:** `info`, `req` ## uwflow info - **Aliases:** `information`, `i` @@ -319,6 +319,13 @@ - **Options:** - **Subcommands:** None +## uwflow req +- **Aliases:** `requisite` +- **Description:** Get course requisites +- **Examples:**
`.uwflow req cs135`
`.uwflow requisite cs246` +- **Options:** +- **Subcommands:** None + # SUGGESTION ## suggestion - **Aliases:** ``suggest`` diff --git a/src/commandDetails/uwflow/req.ts b/src/commandDetails/uwflow/req.ts new file mode 100644 index 00000000..87fc3fa2 --- /dev/null +++ b/src/commandDetails/uwflow/req.ts @@ -0,0 +1,73 @@ +import { container } from '@sapphire/framework'; +import { + CodeyCommandDetails, + CodeyCommandOptionType, + SapphireMessageExecuteType, + SapphireMessageResponse, +} from '../../codeyCommand'; +import { courseReqs, getCourseReqs } from '../../components/uwflow'; +import { EmbedBuilder } from 'discord.js'; + +const uwflowReqExecuteCommand: SapphireMessageExecuteType = async ( + _client, + _messageFromUser, + args, +): Promise => { + const courseCodeArg = args['course_code']; + + // Standardize the course code (i.e. cs 135, CS135, CS 135 becomes cs135 for the GraphQL query) + const courseCode = courseCodeArg.split(' ').join('').toLowerCase(); + + const result: courseReqs | number = await getCourseReqs(courseCode); + + // If mistyped course code or course doesn't exist + if (result === -1) { + const errorDesc = 'Oops, that course does not exist!'; + const courseEmbed = new EmbedBuilder() + .setColor('Red') + .setTitle(`Information for ${courseCode.toUpperCase()}`) + .setDescription(errorDesc); + return { embeds: [courseEmbed] }; + } + + const requisites = result; + + const code = requisites.code.toUpperCase(); + const antireqs = requisites.antireqs; + const prereqs = requisites.prereqs; + const coreqs = requisites.coreqs; + + const courseEmbed = new EmbedBuilder() + .setColor('Green') + .setTitle(`Requisites for ${code}`) + .addFields( + { name: 'Course code', value: code, inline: false }, + { name: 'Antirequisites', value: antireqs, inline: false }, + { name: 'Prerequisites', value: prereqs, inline: false }, + { name: 'Corequisites', value: coreqs, inline: false }, + ); + + return { embeds: [courseEmbed] }; +}; + +export const uwflowReqCommandDetails: CodeyCommandDetails = { + name: 'req', + aliases: ['requisite'], + description: 'Get course requisites', + detailedDescription: `**Examples:** +\`${container.botPrefix}uwflow req cs135\` +\`${container.botPrefix}uwflow requisite cs246\``, + + isCommandResponseEphemeral: false, + messageWhenExecutingCommand: 'Getting information from UWFlow:', + executeCommand: uwflowReqExecuteCommand, + options: [ + { + name: 'course_code', + description: 'The course code. Examples: cs135, cs 135, CS135, CS 135', + type: CodeyCommandOptionType.STRING, + required: true, + }, + ], + subcommandDetails: {}, +}; diff --git a/src/commands/uwflow/uwflow.ts b/src/commands/uwflow/uwflow.ts index 824468e7..4f5168b3 100644 --- a/src/commands/uwflow/uwflow.ts +++ b/src/commands/uwflow/uwflow.ts @@ -1,5 +1,6 @@ import { Command } from '@sapphire/framework'; import { uwflowInfoCommandDetails } from '../../commandDetails/uwflow/info'; +import { uwflowReqCommandDetails } from '../../commandDetails/uwflow/req'; import { CodeyCommand, CodeyCommandDetails } from '../../codeyCommand'; const uwflowCommandDetails: CodeyCommandDetails = { @@ -10,6 +11,7 @@ const uwflowCommandDetails: CodeyCommandDetails = { options: [], subcommandDetails: { info: uwflowInfoCommandDetails, + req: uwflowReqCommandDetails, }, defaultSubcommandDetails: uwflowInfoCommandDetails, }; diff --git a/src/components/uwflow.ts b/src/components/uwflow.ts index 50652bff..433608d2 100644 --- a/src/components/uwflow.ts +++ b/src/components/uwflow.ts @@ -1,7 +1,9 @@ import axios from 'axios'; +// UWFlow API URL const uwflowApiUrl = 'https://uwflow.com/graphql'; +// Course info interface for UWFlow interface courseInfoFromUrl { data: { course: [ @@ -21,6 +23,7 @@ interface courseInfoFromUrl { }; } +// Course info interface for CodeyBot export interface courseInfo { code: string; name: string; @@ -32,6 +35,39 @@ export interface courseInfo { comment_count: number; } +// Course requisites interface for UWFlow +interface courseReqsFromUrl { + data: { + course: [ + { + code: string; + id: number; + antireqs: string | null; + prereqs: string | null; + coreqs: string | null; + }, + ]; + }; +} + +// Course requisites interface for CodeyBot +export interface courseReqs { + code: string; + antireqs: string; + prereqs: string; + coreqs: string; +} + +// Format string +const formatInput = (input: string | null): string => { + if (input === null) { + return "None"; + } + + return input; +} + +// Retrieve course info export const getCourseInfo = async (courseCode: string): Promise => { const resultFromUWFLow: courseInfoFromUrl = ( await axios.post(uwflowApiUrl, { @@ -57,6 +93,7 @@ export const getCourseInfo = async (courseCode: string): Promise => { + const resultFromUWFLow: courseReqsFromUrl = ( + await axios.post(uwflowApiUrl, { + operationName: 'getCourse', + variables: { + code: courseCode, + }, + query: + `query getCourse($code: String) { + course(where: { code: { _eq: $code }}) { + code + id + antireqs + prereqs + coreqs + } + }`, + }) + ).data; + + // If no data is found, return -1 to signal error + if (resultFromUWFLow.data.course.length < 1) { + return -1; + } + + const result: courseReqs = { + code: resultFromUWFLow.data.course[0].code, + antireqs: formatInput(resultFromUWFLow.data.course[0].antireqs), + prereqs: formatInput(resultFromUWFLow.data.course[0].prereqs), + coreqs: formatInput(resultFromUWFLow.data.course[0].coreqs), + }; + + return result; +} From b72c16c63fcb58f5e224baafd2df548fea8b6934 Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Mon, 24 Jun 2024 03:52:41 +0000 Subject: [PATCH 11/13] Reformatted some code --- docs/COMMAND-WIKI.md | 2 +- src/commandDetails/uwflow/info.ts | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/COMMAND-WIKI.md b/docs/COMMAND-WIKI.md index 308c0e15..7467f18d 100644 --- a/docs/COMMAND-WIKI.md +++ b/docs/COMMAND-WIKI.md @@ -314,7 +314,7 @@ ## uwflow info - **Aliases:** `information`, `i` -- **Description:** Get info about courses using UWFlow. +- **Description:** Get course information - **Examples:**
`.uwflow info cs135`
`.uwflow information cs246`
`.uwflow i cs240` - **Options:** - **Subcommands:** None diff --git a/src/commandDetails/uwflow/info.ts b/src/commandDetails/uwflow/info.ts index 67a446a6..7b4d9fda 100644 --- a/src/commandDetails/uwflow/info.ts +++ b/src/commandDetails/uwflow/info.ts @@ -27,10 +27,10 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( // Standardize the course code (i.e. cs 135, CS135, CS 135 becomes cs135 for the GraphQL query) const courseCode = courseCodeArg.split(' ').join('').toLowerCase(); - const courseInfo: courseInfo | number = await getCourseInfo(courseCode); + const result: courseInfo | number = await getCourseInfo(courseCode); // If mistyped course code or course doesn't exist - if (courseInfo === -1) { + if (result === -1) { const errorDesc = 'Oops, that course does not exist!'; const courseEmbed = new EmbedBuilder() .setColor('Red') @@ -39,14 +39,14 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( return { embeds: [courseEmbed] }; } - const actualCourseInfo = courseInfo; + const info = result; - const code = actualCourseInfo.code.toUpperCase(); - const name = actualCourseInfo.name; - const description = actualCourseInfo.description; - const liked = (actualCourseInfo.liked * 100).toFixed(2); - const easy = (actualCourseInfo.easy * 100).toFixed(2); - const useful = (actualCourseInfo.useful * 100).toFixed(2); + const code = info.code.toUpperCase(); + const name = info.name; + const description = info.description; + const liked = (info.liked * 100).toFixed(2); + const easy = (info.easy * 100).toFixed(2); + const useful = (info.useful * 100).toFixed(2); const courseEmbed = new EmbedBuilder() .setColor('Green') @@ -66,7 +66,7 @@ const uwflowInfoExecuteCommand: SapphireMessageExecuteType = async ( export const uwflowInfoCommandDetails: CodeyCommandDetails = { name: 'info', aliases: ['information', 'i'], - description: 'Get info about courses using UWFlow.', + description: 'Get course information', detailedDescription: `**Examples:** \`${container.botPrefix}uwflow info cs135\` \`${container.botPrefix}uwflow information cs246\` From b9b2b55acbe86e29d159c1b646fb769ef0e490f8 Mon Sep 17 00:00:00 2001 From: Di Nguyen Date: Mon, 24 Jun 2024 03:54:10 +0000 Subject: [PATCH 12/13] Fixed linter errors --- src/components/uwflow.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/uwflow.ts b/src/components/uwflow.ts index 433608d2..d25e53b2 100644 --- a/src/components/uwflow.ts +++ b/src/components/uwflow.ts @@ -61,11 +61,11 @@ export interface courseReqs { // Format string const formatInput = (input: string | null): string => { if (input === null) { - return "None"; + return 'None'; } return input; -} +}; // Retrieve course info export const getCourseInfo = async (courseCode: string): Promise => { @@ -75,8 +75,7 @@ export const getCourseInfo = async (courseCode: string): Promise Date: Mon, 1 Jul 2024 04:12:18 +0000 Subject: [PATCH 13/13] Added search subcommand --- docs/COMMAND-WIKI.md | 9 ++- src/commandDetails/uwflow/search.ts | 93 +++++++++++++++++++++++++++++ src/commands/uwflow/uwflow.ts | 2 + src/components/uwflow.ts | 67 +++++++++++++++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/commandDetails/uwflow/search.ts diff --git a/docs/COMMAND-WIKI.md b/docs/COMMAND-WIKI.md index 7467f18d..67d5943f 100644 --- a/docs/COMMAND-WIKI.md +++ b/docs/COMMAND-WIKI.md @@ -310,7 +310,7 @@ - **Description:** Handle UWFlow commands. - **Examples:** - **Options:** None -- **Subcommands:** `info`, `req` +- **Subcommands:** `info`, `req`, `search` ## uwflow info - **Aliases:** `information`, `i` @@ -326,6 +326,13 @@ - **Options:** - **Subcommands:** None +## uwflow search +- **Aliases:** None +- **Description:** Search for courses in specified range +- **Examples:**
`.uwflow search CS 100 200`
`.uwflow search cs 100 200` +- **Options:** +- **Subcommands:** None + # SUGGESTION ## suggestion - **Aliases:** ``suggest`` diff --git a/src/commandDetails/uwflow/search.ts b/src/commandDetails/uwflow/search.ts new file mode 100644 index 00000000..81700f2c --- /dev/null +++ b/src/commandDetails/uwflow/search.ts @@ -0,0 +1,93 @@ +import { container } from '@sapphire/framework'; +import { + CodeyCommandDetails, + CodeyCommandOptionType, + SapphireMessageExecuteType, + SapphireMessageResponse, +} from '../../codeyCommand'; +import { searchResults, getSearchResults } from '../../components/uwflow'; +import { EmbedBuilder } from 'discord.js'; + +const uwflowSearchExecuteCommand: SapphireMessageExecuteType = async ( + _client, + _messageFromUser, + args, +): Promise => { + const courseArg = args['course']; + const min = args['min']; + const max = args['max']; + + // Standardize the course initials (i.e. CS becomes cs for the GraphQL query) + const course = courseArg.toLowerCase(); + + const results: searchResults[] | number = await getSearchResults(course, min, max); + + // If mistyped course initials or course doesn't exist + if (results === -1) { + const errorDesc = 'UWFlow returned no data'; + const courseEmbed = new EmbedBuilder() + .setColor('Red') + .setTitle(`Information for query of ${course.toUpperCase()} courses in range ${min} - ${max}`) + .setDescription(errorDesc); + return { embeds: [courseEmbed] }; + } + + const resultArray = results; + // If no courses fit the range + if (resultArray.length < 1) { + const desc = 'No courses suit the query'; + const embed = new EmbedBuilder() + .setColor('Orange') + .setTitle(`Information for query of ${course} courses in range ${min} - ${max}`) + .setDescription(desc); + return { embeds: [embed] }; + } + + const courseArray: string[] = []; + for (const result of resultArray) { + courseArray.push(result.code); + } + + const desc = courseArray.join(', '); + + const resultEmbed = new EmbedBuilder() + .setColor('Green') + .setTitle(`Information for query of ${course} courses in range ${min} - ${max}`) + .setDescription(desc); + + return { embeds: [resultEmbed] }; +}; + +export const uwflowSearchCommandDetails: CodeyCommandDetails = { + name: 'search', + aliases: [], + description: 'Search for courses in specified range', + detailedDescription: `**Examples:** +\`${container.botPrefix}uwflow search CS 100 200\` +\`${container.botPrefix}uwflow search cs 100 200\``, + + isCommandResponseEphemeral: false, + messageWhenExecutingCommand: 'Getting information from UWFlow:', + executeCommand: uwflowSearchExecuteCommand, + options: [ + { + name: 'course', + description: 'The initials of the course. Examples: CS, cs, MATH, math', + type: CodeyCommandOptionType.STRING, + required: true, + }, + { + name: 'min', + description: 'The minimum code of the course', + type: CodeyCommandOptionType.INTEGER, + required: true, + }, + { + name: 'max', + description: 'The maximum code of the course', + type: CodeyCommandOptionType.INTEGER, + required: true, + }, + ], + subcommandDetails: {}, +}; diff --git a/src/commands/uwflow/uwflow.ts b/src/commands/uwflow/uwflow.ts index 4f5168b3..c4c7e142 100644 --- a/src/commands/uwflow/uwflow.ts +++ b/src/commands/uwflow/uwflow.ts @@ -1,6 +1,7 @@ import { Command } from '@sapphire/framework'; import { uwflowInfoCommandDetails } from '../../commandDetails/uwflow/info'; import { uwflowReqCommandDetails } from '../../commandDetails/uwflow/req'; +import { uwflowSearchCommandDetails } from '../../commandDetails/uwflow/search'; import { CodeyCommand, CodeyCommandDetails } from '../../codeyCommand'; const uwflowCommandDetails: CodeyCommandDetails = { @@ -12,6 +13,7 @@ const uwflowCommandDetails: CodeyCommandDetails = { subcommandDetails: { info: uwflowInfoCommandDetails, req: uwflowReqCommandDetails, + search: uwflowSearchCommandDetails, }, defaultSubcommandDetails: uwflowInfoCommandDetails, }; diff --git a/src/components/uwflow.ts b/src/components/uwflow.ts index d25e53b2..9e871cb4 100644 --- a/src/components/uwflow.ts +++ b/src/components/uwflow.ts @@ -58,6 +58,26 @@ export interface courseReqs { coreqs: string; } +// Search results interface for UWflow +interface searchResultsFromUrl { + data: { + search_courses: [ + { + name: string; + code: string; + has_prereqs: boolean; + }, + ]; + }; +} + +// Search results interface for CodeyBot +export interface searchResults { + name: string; + code: string; + has_prereqs: boolean; +} + // Format string const formatInput = (input: string | null): string => { if (input === null) { @@ -145,3 +165,50 @@ export const getCourseReqs = async (courseCode: string): Promise => { + const resultFromUWFLow: searchResultsFromUrl = ( + await axios.post(uwflowApiUrl, { + operationName: 'explore', + variables: { + query: course, + code_only: true, + }, + query: `query explore($query: String, $code_only: Boolean) { + search_courses(args: { query: $query, code_only: $code_only }) { + name + code + has_prereqs + } + }`, + }) + ).data; + + // If no data is found, return -1 to signal error + if (resultFromUWFLow.data.search_courses.length < 1) { + return -1; + } + + // Search results + const results = resultFromUWFLow.data.search_courses; + + // Array of courses in range [min, max] + const resultArray: searchResults[] = results.filter((result) => { + const code = result.code; + let numString = ''; + for (const char of code) { + if (!isNaN(parseInt(char))) { + numString += char; + } + } + const num = parseInt(numString, 10); + return min <= num && num <= max; + }); + + return resultArray; +};