Compare commits

...

4 Commits

Author SHA1 Message Date
a2ae277a0f Assignment Logs 2025-12-22 17:27:41 -06:00
82832117a3 working full end-to-end with encryption 2025-12-22 13:56:59 -06:00
4ee5f17566 removed JSON output 2025-12-22 12:27:15 -06:00
ff5bd9574e Final Grade added to JSON 2025-12-21 13:43:40 -06:00
4 changed files with 73 additions and 6 deletions

2
.gitignore vendored
View File

@@ -34,5 +34,7 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
.DS_Store .DS_Store
skyward_screenshots/** skyward_screenshots/**
output/**
skywardstate.json skywardstate.json

View File

@@ -5,6 +5,8 @@
"": { "": {
"name": "skyward-analysis", "name": "skyward-analysis",
"dependencies": { "dependencies": {
"@types/cors": "^2.8.19",
"cors": "^2.8.5",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"express": "^5.2.1", "express": "^5.2.1",
"pino": "^10.1.0", "pino": "^10.1.0",
@@ -29,6 +31,8 @@
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
"@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="], "@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="],
"@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="], "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="],
@@ -69,6 +73,8 @@
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
"dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@@ -151,6 +157,8 @@
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],

View File

@@ -3,8 +3,18 @@ import type { Browser, BrowserContext, Page } from 'playwright';
import { existsSync, mkdirSync, writeFileSync } from 'fs'; import { existsSync, mkdirSync, writeFileSync } from 'fs';
import pino from 'pino'; import pino from 'pino';
import express from 'express'; import express from 'express';
import cors from 'cors'; // ADD THIS
const app = express(); const app = express();
app.use(cors({
origin: ['http://localhost:5173', 'http://localhost:4000'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use(express.json()); app.use(express.json());
const logger = pino({ const logger = pino({
@@ -16,7 +26,7 @@ const logger = pino({
}, },
}); });
const SCREENSHOTS_DIR = 'skyward_screenshots'; const OUTPUT_DIR = 'output';
const TARGET_CATEGORIES = ['NW1', 'NW2', 'SE1', 'NW3', 'NW4', 'SE2']; const TARGET_CATEGORIES = ['NW1', 'NW2', 'SE1', 'NW3', 'NW4', 'SE2'];
// Browser pool to reuse browser instances // Browser pool to reuse browser instances
@@ -71,6 +81,9 @@ app.post('/fetch-grades', async (req, res) => {
try { try {
const grades = await runSkywardBot(username, password); const grades = await runSkywardBot(username, password);
// writeFileSync(`${OUTPUT_DIR}/grades_${username}_${Date.now()}.json`, JSON.stringify(grades, null, 2));
res.json({ res.json({
success: true, success: true,
@@ -174,8 +187,8 @@ const runSkywardBot = async (username: string, password: string) => {
logger.info('Starting Skyward bot with Firefox on Linux...'); logger.info('Starting Skyward bot with Firefox on Linux...');
// Create screenshots directory if it doesn't exist // Create screenshots directory if it doesn't exist
if (!existsSync(SCREENSHOTS_DIR)) { if (!existsSync(OUTPUT_DIR)) {
mkdirSync(SCREENSHOTS_DIR); mkdirSync(OUTPUT_DIR);
} }
const browser = await getSharedBrowser(); const browser = await getSharedBrowser();
@@ -640,7 +653,7 @@ async function openSkywardFromLaunchpad(page: Page, context: any, allClassGrades
async function navigateToGrading(page: Page, allClassGrades: any[]) { async function navigateToGrading(page: Page, allClassGrades: any[]) {
logger.info('Step 8: Navigating to Grading section...'); logger.info('Step 8: Navigating to Grading section...');
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
@@ -690,6 +703,34 @@ async function navigateToGrading(page: Page, allClassGrades: any[]) {
logger.info('Successfully navigated to Grading page'); logger.info('Successfully navigated to Grading page');
// ADD THIS SECTION HERE - Click "All Year" radio button
logger.info('Step 8.5: Clicking "All Year" radio button...');
try {
const allYearRadio = await page.$('input[type="radio"][name="DateRangeModechild"][value="AllYear"]');
if (!allYearRadio) {
logger.warn('Could not find "All Year" radio button');
} else {
const isVisible = await allYearRadio.isVisible();
logger.info(`All Year radio button found - visible: ${isVisible}`);
if (isVisible) {
await allYearRadio.scrollIntoViewIfNeeded();
await page.waitForTimeout(500);
await allYearRadio.click();
logger.info('✓ Clicked "All Year" radio button');
await page.waitForTimeout(2000); // Wait for page to update with all year data
} else {
logger.warn('All Year radio button not visible, skipping');
}
}
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
logger.warn(`Error clicking All Year radio button: ${errorMsg}`);
logger.info('Continuing anyway...');
}
// END OF NEW SECTION
logger.info('Step 9: Finding grades table...'); logger.info('Step 9: Finding grades table...');
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
@@ -753,7 +794,20 @@ async function navigateToGrading(page: Page, allClassGrades: any[]) {
logger.info(` ✓ Clicked grade cell`); logger.info(` ✓ Clicked grade cell`);
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
const gradePercentageLabel = await page.$('label.labelTag.visualLabel.header');
let overallGrade = '';
if (gradePercentageLabel) {
const percentageText = (await gradePercentageLabel.textContent())?.trim();
overallGrade = percentageText || '';
logger.info(` 📊 Grade Percentage: ${percentageText}`);
} else {
logger.info(` No grade percentage label found`);
}
const categoryContainer = await page.$('span.SegmentedAssignmentsInformationCell:not(.left) label.labelTag.visualLabel'); const categoryContainer = await page.$('span.SegmentedAssignmentsInformationCell:not(.left) label.labelTag.visualLabel');
if (!categoryContainer) { if (!categoryContainer) {
@@ -896,6 +950,7 @@ async function navigateToGrading(page: Page, allClassGrades: any[]) {
teacher: teacher, teacher: teacher,
period: period, period: period,
category: categoryText, category: categoryText,
overallGrade: overallGrade,
grades: grades, grades: grades,
}); });
@@ -961,7 +1016,7 @@ async function navigateToGrading(page: Page, allClassGrades: any[]) {
logger.info(`\n🎉 Finished processing all grade cells!`); logger.info(`\n🎉 Finished processing all grade cells!`);
try { try {
const gradesFilePath = `${SCREENSHOTS_DIR}/all_grades.json`; const gradesFilePath = `${OUTPUT_DIR}/all_grades.json`;
writeFileSync(gradesFilePath, JSON.stringify(allClassGrades, null, 2)); writeFileSync(gradesFilePath, JSON.stringify(allClassGrades, null, 2));
logger.info(`\n📄 Saved all grades to: ${gradesFilePath}`); logger.info(`\n📄 Saved all grades to: ${gradesFilePath}`);
logger.info(` Total classes processed: ${allClassGrades.length}`); logger.info(` Total classes processed: ${allClassGrades.length}`);

View File

@@ -11,6 +11,8 @@
"typescript": "^5" "typescript": "^5"
}, },
"dependencies": { "dependencies": {
"@types/cors": "^2.8.19",
"cors": "^2.8.5",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"express": "^5.2.1", "express": "^5.2.1",
"pino": "^10.1.0", "pino": "^10.1.0",