Compare commits
4 Commits
e40b8fd0fe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a2ae277a0f | |||
| 82832117a3 | |||
| 4ee5f17566 | |||
| ff5bd9574e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
|
||||||
8
bun.lock
8
bun.lock
@@ -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=="],
|
||||||
|
|||||||
67
index.ts
67
index.ts
@@ -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}`);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user