feat: injected button - add all courses from MyUT AND passing URL to handler (#291)

* feat: first button attempt

* feat: fetching each course code

* feat: adding courses function from there but idk where to get the active schedule from

* docs: todo

* feat: retrieved active schedule

* feat: button tactics

* feat: add support for my.utexas.edu

* feat: inject button into MyUT

* feat: refactor code to render components dynamically based on site

* feat: scrape course ids from MyUT and remove duplicates

* feat: site support links for classlist

* feat: add utility function to add course by URL

* feat: support additional case for course cal

* feat: duplicates

* chore: cleanup

* feat: temporary checkpoint

* feat: reroute to use new add course by url

* feat: linking to new function, cleaning up, adding messaging for course url add

* chore: unused import

* feat: relinking addCourse function to the button fingers crossed

* feat: we did it!

* chore: remove comment

* chore: cleanup cleanup

* feat: tried to handle the async stuff because of that small bug but nothing fixed. doesnt hurt tho

* feat: i have fixed it holy kevinnn

* chore: delete unused file and organization

* chore: removed unused log

* feat: better log for course add

* chore: refactor via data destructuring

* chore: pass component as prop via React.ComponentType

---------

Co-authored-by: Ethan Lanting <ethanlanting@gmail.com>
Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
2024-11-15 19:07:37 -06:00
committed by GitHub
parent 9ad32390d1
commit c41467c617
9 changed files with 179 additions and 49 deletions

View File

@@ -31,6 +31,15 @@ const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
renameSchedule({ data, sendResponse }) {
renameSchedule(data.scheduleId, data.newName).then(sendResponse);
},
// proxy so we can add courses
addCourseByURL({ data: { url, method, body, response }, sendResponse }) {
fetch(url, {
method,
body,
})
.then(res => (response === 'json' ? res.json() : res.text()))
.then(sendResponse);
},
};
export default userScheduleHandler;

View File

@@ -19,6 +19,6 @@ export default async function addCourse(scheduleId: string, course: Course): Pro
course.colors = getUnusedColor(activeSchedule, course);
activeSchedule.courses.push(course);
activeSchedule.updatedAt = Date.now();
await UserScheduleStore.set('schedules', schedules);
console.log(`Course added: ${course.courseName} (ID: ${course.uniqueId})`);
}

View File

@@ -0,0 +1,64 @@
import addCourse from '@pages/background/lib/addCourse';
import { background } from '@shared/messages';
import type { UserSchedule } from '@shared/types/UserSchedule';
import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
import getCourseTableRows from '@views/lib/getCourseTableRows';
import { SiteSupport } from '@views/lib/getSiteSupport';
/**
* Adds a course to the active schedule by fetching course details from a provided URL.
* If no URL is provided, prompts the user to enter one.
* Sriram and Elie made this
*
* @param activeSchedule - The user's active schedule to which the course will be added.
* @param link - The URL from which to fetch the course details. If not provided, a prompt will ask for it.
*
* @returns A promise that resolves when the course has been added or the operation is cancelled.
*
* @throws an error if there is an issue with scraping the course details.
*/
export async function addCourseByURL(activeSchedule: UserSchedule, link?: string): Promise<void> {
// todo: Use a proper modal instead of a prompt
// eslint-disable-next-line no-param-reassign, no-alert
if (!link) link = prompt('Enter course link') || undefined;
// Exit if the user cancels the prompt
if (!link) return;
try {
let htmlText: string;
try {
htmlText = await background.addCourseByURL({
url: link,
method: 'GET',
response: 'text',
});
} catch (e) {
// eslint-disable-next-line no-alert
alert(`Failed to fetch url '${link}'`);
return;
}
const doc = new DOMParser().parseFromString(htmlText, 'text/html');
const scraper = new CourseCatalogScraper(SiteSupport.COURSE_CATALOG_DETAILS, doc, link);
const tableRows = getCourseTableRows(doc);
const scrapedCourses = scraper.scrape(tableRows, false);
if (scrapedCourses.length !== 1) return;
const description = scraper.getDescription(doc);
const row = scrapedCourses[0]!;
const course = row.course!;
course.description = description;
if (activeSchedule.courses.every(c => c.uniqueId !== course.uniqueId)) {
console.log('adding course');
await addCourse(activeSchedule.id, course);
} else {
console.log('course already exists');
}
} catch (error) {
console.error('Error scraping course:', error);
}
}