@@ -14,6 +14,7 @@ import {
1414 underline ,
1515} from "discord.js" ;
1616import { FYW_MINIS , SCOTTYLABS_URL } from "../constants.js" ;
17+ import SyllabiData from "../data/course-api.syllabi.json" with { type : "json" } ;
1718import CoursesData from "../data/finalCourseJSON.json" with { type : "json" } ;
1819import { parseAndEvaluate } from "../modules/operator-parser.ts" ;
1920import type { SlashCommand } from "../types.d.ts" ;
@@ -44,6 +45,17 @@ type FCERecord = {
4445 responseRate : number ;
4546} ;
4647
48+ type Syllabus = {
49+ _id : {
50+ $oid : string ;
51+ } ;
52+ season : string ;
53+ year : number ;
54+ number : string ;
55+ section : string ;
56+ url : string ;
57+ } ;
58+
4759function loadCoursesData ( ) : Record < string , Course > {
4860 return CoursesData as Record < string , Course > ;
4961}
@@ -167,6 +179,40 @@ function loadFCEData(): Record<string, FCEData> {
167179 return fceMap ;
168180}
169181
182+ function loadSyllabiData ( ) : Record < string , Syllabus [ ] > {
183+ const syllabiData = SyllabiData as Syllabus [ ] ;
184+ const syllabi : Record < string , Syllabus [ ] > = { } ;
185+
186+ const seasonOrder : Record < string , number > = {
187+ F : 0 ,
188+ S : 1 ,
189+ M : 2 ,
190+ N : 3 ,
191+ } ;
192+
193+ for ( const entry of syllabiData ) {
194+ const courseid = formatCourseNumber ( entry . number ) ?? "" ;
195+ if ( ! syllabi [ courseid ] ) {
196+ syllabi [ courseid ] = [ ] ;
197+ }
198+ syllabi [ courseid ] . push ( entry ) ;
199+ }
200+
201+ for ( const courseid in syllabi ) {
202+ syllabi [ courseid ] ! . sort ( ( a , b ) => {
203+ if ( a . year !== b . year ) {
204+ return b . year - a . year ;
205+ }
206+
207+ return (
208+ ( seasonOrder [ a . season ] ?? 99 ) - ( seasonOrder [ b . season ] ?? 99 )
209+ ) ;
210+ } ) ;
211+ }
212+
213+ return syllabi ;
214+ }
215+
170216const command : SlashCommand = {
171217 data : new SlashCommandBuilder ( )
172218 . setName ( "courses" )
@@ -209,6 +255,19 @@ const command: SlashCommand = {
209255 )
210256 . setRequired ( true ) ,
211257 ) ,
258+ )
259+ . addSubcommand ( ( subcommand ) =>
260+ subcommand
261+ . setName ( "syllabus" )
262+ . setDescription ( "Get pdfs of syllabi for a course" )
263+ . addStringOption ( ( option ) =>
264+ option
265+ . setName ( "course_id" )
266+ . setDescription (
267+ "Course code in XX-XXX or XXXXX format (e.g., 15-112)" ,
268+ )
269+ . setRequired ( true ) ,
270+ ) ,
212271 ) ,
213272 async execute ( interaction ) {
214273 const coursesData = loadCoursesData ( ) ;
@@ -581,6 +640,99 @@ const command: SlashCommand = {
581640 return interaction . reply ( { embeds : [ embed ] } ) ;
582641 }
583642 }
643+ if ( interaction . options . getSubcommand ( ) === "syllabus" ) {
644+ const convertSem : Record < string , string > = {
645+ F : "Fall" ,
646+ S : "Spring" ,
647+ M : "Summer" ,
648+ N : "Summer" ,
649+ } ;
650+
651+ const syllabi = loadSyllabiData ( ) ;
652+ const fceData = loadFCEData ( ) ;
653+
654+ const courseid = formatCourseNumber (
655+ interaction . options . getString ( "course_id" , true ) ,
656+ ) ;
657+
658+ if ( ! courseid ) {
659+ return interaction . reply ( {
660+ content :
661+ "Please provide a valid course code in the format XX-XXX or XXXXX." ,
662+ flags : MessageFlags . Ephemeral ,
663+ } ) ;
664+ }
665+
666+ const course = coursesData [ courseid ] ;
667+
668+ if ( ! syllabi [ courseid ] || ! course ) {
669+ return interaction . reply ( {
670+ content : `Course ${ courseid } not found.` ,
671+ flags : MessageFlags . Ephemeral ,
672+ } ) ;
673+ }
674+ let foundSyllabi = syllabi [ courseid ] ;
675+
676+ const uniqueSyllabi = [
677+ ...new Map ( foundSyllabi . map ( ( s ) => [ s . url , s ] ) ) . values ( ) ,
678+ ] ;
679+
680+ const embeds : EmbedBuilder [ ] = [ ] ;
681+ let currentDesc = "" ;
682+
683+ let links = 0 ;
684+
685+ for ( const syllabus of uniqueSyllabi ) {
686+ /*
687+ Currently this uses fce data to find instructor data
688+ However, syllabi data should be updated to include instructor,
689+ and courses data should be changed to correctly include
690+ syllabi and session as intended
691+ */
692+
693+ let fceRec = fceData [ courseid ] ?. records ?? [ ] ;
694+ let fceEntry = undefined ;
695+
696+ for ( const rec of fceRec ) {
697+ // Rec.year and Syllabus.year will both be 2 digits
698+ if (
699+ rec . semester == convertSem [ syllabus . season ] &&
700+ rec . year == syllabus . year + 2000 &&
701+ rec . section == syllabus . section
702+ ) {
703+ fceEntry = rec ;
704+ }
705+ }
706+ const line : string = hyperlink (
707+ `${ syllabus . season } ${ syllabus . year } : ${ syllabus . number } -${ syllabus . section } ${ fceEntry ?. instructor ? `(${ fceEntry ?. instructor } )` : "" } \n` ,
708+ `${ syllabus . url } ` ,
709+ ) ;
710+ if ( links >= 20 ) {
711+ embeds . push (
712+ new EmbedBuilder ( )
713+ . setTitle ( `Syllabi for ${ courseid } : ${ course . name } ` )
714+ . setURL ( `${ SCOTTYLABS_URL } /course/${ courseid } ` )
715+ . setDescription ( currentDesc ) ,
716+ ) ;
717+ currentDesc = line ;
718+ links = 1 ;
719+ } else {
720+ currentDesc += line ;
721+ links ++ ;
722+ }
723+ }
724+
725+ if ( currentDesc ) {
726+ embeds . push (
727+ new EmbedBuilder ( )
728+ . setTitle ( `Syllabi for ${ courseid } : ${ course . name } ` )
729+ . setURL ( `${ SCOTTYLABS_URL } /course/${ courseid } ` )
730+ . setDescription ( currentDesc ) ,
731+ ) ;
732+ }
733+
734+ return new EmbedPaginator ( embeds ) . send ( interaction ) ;
735+ }
584736 } ,
585737} ;
586738
0 commit comments