From c4f379ff1c3bfcc4be8e3948b4f33ed69357ea30 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 18 Aug 2026 15:47:15 +0800 Subject: [PATCH] feat: content script for auto-login, privacy, scraping --- content.js | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 content.js diff --git a/content.js b/content.js new file mode 100644 index 0000000..446a951 --- /dev/null +++ b/content.js @@ -0,0 +1,142 @@ +(function () { + 'use strict'; + + const MASTER_KEY = 'enabled'; + + async function isEnabled() { + const { enabled } = await browser.storage.local.get(MASTER_KEY); + return enabled !== false; + } + + function autoAcceptPrivacy() { + const modal = document.getElementById('myModal'); + if (!modal) return; + const checkbox = document.getElementById('Confirmation'); + const submit = document.getElementById('Submit'); + if (checkbox && !checkbox.checked) checkbox.click(); + if (submit) submit.click(); + } + + function watchPrivacyModal() { + autoAcceptPrivacy(); + const observer = new MutationObserver(() => autoAcceptPrivacy()); + observer.observe(document.body, { childList: true, subtree: true }); + setTimeout(() => observer.disconnect(), 30000); + } + + async function autoLogin() { + const { profiles } = await browser.storage.local.get('profiles'); + const active = (profiles || []).find((p) => p.enabled); + if (!active) return; + const userField = document.querySelector( + 'input[name="UserName"], input[name="username"], input[id="UserName"], input[id="username"]' + ); + const passField = document.querySelector('input[type="password"]'); + if (!userField || !passField) return; + userField.value = active.username; + passField.value = active.password; + userField.dispatchEvent(new Event('input', { bubbles: true })); + passField.dispatchEvent(new Event('input', { bubbles: true })); + const form = userField.closest('form') || passField.closest('form'); + if (!form) return; + setTimeout(() => { + const submitBtn = form.querySelector('input[type="submit"], button[type="submit"]'); + if (submitBtn) submitBtn.click(); + else form.submit(); + }, 300); + } + + async function fetchCourseDescription(courseCode, academicPeriod, academicYear, token) { + const body = new URLSearchParams(); + body.set('Courses', courseCode); + body.set('AcademicPeriod', academicPeriod); + body.set('AcademicYear', academicYear); + body.set('AjaxFormVM.Controller', 'CourseSchedule'); + body.set('AjaxFormVM.Action', 'CourseScheduleOffered'); + body.set('AjaxFormVM.ResultTargetId', 'CourseScheduleOfferedList'); + body.set('AjaxFormVM.LinkToReturn', ''); + body.set('AjaxFormVM.LinkToReturnTargetId', ''); + body.set('AjaxFormVM.FormHeader', 'Choose term to view courses offered'); + body.set('AjaxFormVM.FormHeaderIcon', ''); + body.set('AjaxFormVM.FormId', 'CourseScheduleCourseScheduleOffered'); + body.set('AjaxFormVM.ShowCloseButton', 'True'); + body.set('AjaxFormVM.PreventAutoUpdateTarget', 'True'); + body.set('AjaxFormVM.CloseModalAfterSubmit', 'False'); + body.set('AjaxFormVM.AjaxFormType', 'Search'); + body.set('AjaxFormVM.FormNoteLocation', ''); + body.set('AjaxFormVM.SubmitButton.FaIcon', ''); + body.set('AjaxFormVM.SubmitButton.ButtonText', ''); + body.set('AjaxFormVM.SubmitButton.ButtonSize', ''); + body.set('__RequestVerificationToken', token); + const resp = await fetch('/CourseSchedule/CourseScheduleOffered?Length=14', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + }, + body: body.toString(), + credentials: 'same-origin' + }); + const html = await resp.text(); + const courses = ISMIStools.parseOfferedCourses(html); + const match = courses.find((c) => c.courseCode === courseCode); + return match ? match.description : ''; + } + + function extractTotalUnits(text) { + const m = /Total Units:\s*([\d.]+)/i.exec(text); + return m ? m[1] : ''; + } + + async function scrapeStudyLoad() { + const table = document.querySelector('table.table-forum'); + if (!table) throw new Error('No study load table found on this page.'); + const courses = ISMIStools.parseStudyLoadTable(table.outerHTML); + if (!courses.length) throw new Error('No courses found in study load table.'); + + const periodEl = document.getElementById('AcademicPeriod'); + const yearEl = document.getElementById('AcademicYear'); + const tokenEl = document.querySelector('input[name="__RequestVerificationToken"]'); + const academicPeriod = periodEl ? periodEl.value : ''; + const academicYear = yearEl ? yearEl.value : ''; + const token = tokenEl ? tokenEl.value : ''; + + const uniqueCodes = [...new Set(courses.map((c) => c.courseCode))]; + const descMap = {}; + for (const code of uniqueCodes) { + try { + descMap[code] = await fetchCourseDescription(code, academicPeriod, academicYear, token); + } catch (e) { + descMap[code] = ''; + } + } + courses.forEach((c) => { c.description = descMap[c.courseCode] || ''; }); + + let header = {}; + try { + const resp = await fetch('/Manage/Student', { credentials: 'same-origin' }); + const html = await resp.text(); + header = ISMIStools.parseProfilePage(html); + } catch (e) { + header = {}; + } + + const totalUnits = extractTotalUnits(document.body.innerText); + return { header, courses, totalUnits }; + } + + browser.runtime.onMessage.addListener((msg, sender, sendResponse) => { + if (msg && msg.type === 'scrape-study-load') { + scrapeStudyLoad() + .then((data) => sendResponse({ ok: true, data })) + .catch((err) => sendResponse({ ok: false, error: String((err && err.message) || err) })); + return true; // async response + } + }); + + (async function init() { + if (!(await isEnabled())) return; + watchPrivacyModal(); + if (/\/Account\/Login/i.test(location.href)) autoLogin(); + })(); +})();