@lime1/esprit-ts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -0
- package/dist/index.cjs +696 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +243 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.js +659 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils.ts"],"sourcesContent":["import axios, { AxiosInstance } from 'axios';\r\nimport { wrapper } from 'axios-cookiejar-support';\r\nimport { CookieJar } from 'tough-cookie';\r\nimport * as cheerio from 'cheerio';\r\nimport qs from 'qs';\r\n\r\nimport type {\r\n Cookie,\r\n LoginResult,\r\n EspritClientConfig,\r\n Grade,\r\n GradeSummary,\r\n Absence,\r\n Credit,\r\n Schedule,\r\n StudentInfo,\r\n} from './types';\r\n\r\nimport {\r\n extractASPNetFormData,\r\n findInputByPatterns,\r\n getInputName,\r\n parseTable,\r\n parseGrades,\r\n calculateGradeSummary,\r\n isLoginPage,\r\n isHomePage,\r\n} from './utils';\r\n\r\n// Re-export types for consumers\r\nexport * from './types';\r\nexport { calculateGradeSummary, calculateModuleAverage } from './utils';\r\n\r\n/**\r\n * ESPRIT Student Portal Client\r\n * \r\n * A TypeScript client for scraping student data from the ESPRIT university portal.\r\n * Handles ASP.NET session management, login flow, and data extraction.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { EspritClient } from '@lime1/esprit-ts';\r\n * \r\n * const client = new EspritClient();\r\n * const result = await client.login('your-id', 'your-password');\r\n * \r\n * if (result.success) {\r\n * const grades = await client.getGrades();\r\n * console.log(grades);\r\n * }\r\n * ```\r\n */\r\nexport class EspritClient {\r\n private client: AxiosInstance;\r\n private cookieJar: CookieJar;\r\n private debug: boolean;\r\n\r\n // URL constants\r\n private readonly LOGIN_URL = 'https://esprit-tn.com/esponline/online/default.aspx';\r\n private readonly HOME_URL = 'https://esprit-tn.com/esponline/Etudiants/Accueil.aspx';\r\n private readonly GRADES_URL = 'https://esprit-tn.com/ESPOnline/Etudiants/Resultat2021.aspx';\r\n private readonly ABSENCES_URL = 'https://esprit-tn.com/ESPOnline/Etudiants/absenceetud.aspx';\r\n private readonly CREDITS_URL = 'https://esprit-tn.com/ESPOnline/Etudiants/Historique_Cr%C3%A9dit.aspx';\r\n private readonly SCHEDULES_URL = 'https://esprit-tn.com/ESPOnline/Etudiants/Emplois.aspx';\r\n private readonly LOGOUT_URLS = [\r\n 'https://esprit-tn.com/esponline/Etudiants/Deconnexion.aspx',\r\n 'https://esprit-tn.com/esponline/online/Deconnexion.aspx',\r\n ];\r\n\r\n /**\r\n * Create a new EspritClient instance.\r\n * \r\n * @param config - Optional configuration options\r\n */\r\n constructor(config: EspritClientConfig = {}) {\r\n this.debug = config.debug ?? false;\r\n this.cookieJar = new CookieJar();\r\n\r\n // Create axios instance with cookie jar support\r\n this.client = wrapper(axios.create({\r\n jar: this.cookieJar,\r\n withCredentials: true,\r\n timeout: config.timeout ?? 30000,\r\n headers: {\r\n 'User-Agent': config.userAgent ??\r\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\r\n 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',\r\n 'Accept-Language': 'en-US,en;q=0.5',\r\n 'Connection': 'keep-alive',\r\n },\r\n maxRedirects: 5,\r\n }));\r\n }\r\n\r\n /**\r\n * Log a debug message if debug mode is enabled.\r\n */\r\n private log(message: string, ...args: unknown[]): void {\r\n if (this.debug) {\r\n console.log(`[EspritClient] ${message}`, ...args);\r\n }\r\n }\r\n\r\n /**\r\n * Login to the ESPRIT student portal.\r\n * \r\n * This handles the multi-step ASP.NET login flow:\r\n * 1. Load login page and extract ViewState\r\n * 2. Submit student ID with checkbox\r\n * 3. Submit password\r\n * \r\n * @param identifier - Student ID\r\n * @param password - Student password\r\n * @returns LoginResult with success status and cookies\r\n */\r\n async login(identifier: string, password: string): Promise<LoginResult> {\r\n try {\r\n // Step 1: Get the initial login page\r\n this.log('Loading login page...');\r\n const initialResponse = await this.client.get(this.LOGIN_URL);\r\n\r\n if (initialResponse.status !== 200) {\r\n return { success: false, cookies: [], message: 'Failed to load login page' };\r\n }\r\n\r\n let $ = cheerio.load(initialResponse.data);\r\n let formData = extractASPNetFormData(initialResponse.data);\r\n\r\n // Find the ID input field\r\n const idInput = findInputByPatterns($, ['textbox3', 'textbox1']);\r\n const idFieldName = idInput ? getInputName(idInput, $) : 'ctl00$ContentPlaceHolder1$TextBox3';\r\n this.log('Found ID field:', idFieldName);\r\n\r\n // Find the checkbox\r\n const checkbox = $('input[type=\"checkbox\"]').first();\r\n const checkboxName = checkbox.length > 0 ? getInputName(checkbox, $) : '';\r\n this.log('Found checkbox:', checkboxName);\r\n\r\n // Step 2: Check the checkbox to reveal continue button\r\n if (checkboxName) {\r\n const checkboxFormData = {\r\n ...formData,\r\n [idFieldName]: identifier,\r\n [checkboxName]: 'on',\r\n __EVENTTARGET: checkboxName,\r\n __EVENTARGUMENT: '',\r\n };\r\n\r\n this.log('Checking checkbox...');\r\n const checkboxResponse = await this.client.post(\r\n this.LOGIN_URL,\r\n qs.stringify(checkboxFormData),\r\n { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }\r\n );\r\n\r\n if (checkboxResponse.status === 200) {\r\n $ = cheerio.load(checkboxResponse.data);\r\n formData = extractASPNetFormData(checkboxResponse.data);\r\n\r\n // Look for continue button and click it if present\r\n const continueButton = findInputByPatterns($, ['continuer', 'continue']);\r\n if (continueButton) {\r\n const continueButtonName = getInputName(continueButton, $);\r\n this.log('Clicking continue button:', continueButtonName);\r\n\r\n const continueFormData = {\r\n ...formData,\r\n [idFieldName]: identifier,\r\n [checkboxName]: 'on',\r\n __EVENTTARGET: continueButtonName,\r\n __EVENTARGUMENT: '',\r\n };\r\n\r\n const continueResponse = await this.client.post(\r\n this.LOGIN_URL,\r\n qs.stringify(continueFormData),\r\n { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }\r\n );\r\n\r\n if (continueResponse.status === 200) {\r\n $ = cheerio.load(continueResponse.data);\r\n formData = extractASPNetFormData(continueResponse.data);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Step 3: Find and click the \"Suivant\" button to move to password page\r\n const suivantButton = findInputByPatterns($, ['button3', 'button1', 'suivant', 'next']);\r\n const suivantButtonName = suivantButton\r\n ? getInputName(suivantButton, $)\r\n : 'ctl00$ContentPlaceHolder1$Button3';\r\n\r\n this.log('Clicking Suivant button:', suivantButtonName);\r\n\r\n const suivantFormData = {\r\n ...formData,\r\n [idFieldName]: identifier,\r\n ...(checkboxName ? { [checkboxName]: 'on' } : {}),\r\n __EVENTTARGET: suivantButtonName,\r\n __EVENTARGUMENT: '',\r\n };\r\n\r\n const suivantResponse = await this.client.post(\r\n this.LOGIN_URL,\r\n qs.stringify(suivantFormData),\r\n { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }\r\n );\r\n\r\n if (suivantResponse.status !== 200) {\r\n return { success: false, cookies: [], message: 'Failed to submit ID' };\r\n }\r\n\r\n $ = cheerio.load(suivantResponse.data);\r\n\r\n // Check if we're still on the ID page (incorrect ID)\r\n const passwordField = findInputByPatterns($, ['textbox7']);\r\n const idFieldStillPresent = findInputByPatterns($, ['textbox3']);\r\n\r\n if (idFieldStillPresent && !passwordField) {\r\n return { success: false, cookies: [], message: 'Identifiant incorrect !' };\r\n }\r\n\r\n this.log('Now on password page...');\r\n\r\n // Step 4: Submit password\r\n formData = extractASPNetFormData(suivantResponse.data);\r\n\r\n const passwordFieldName = passwordField\r\n ? getInputName(passwordField, $)\r\n : 'ctl00$ContentPlaceHolder1$TextBox7';\r\n\r\n const connexionButton = findInputByPatterns($, ['buttonetudiant', 'button2', 'connexion', 'connect']);\r\n const connexionButtonName = connexionButton\r\n ? getInputName(connexionButton, $)\r\n : 'ctl00$ContentPlaceHolder1$ButtonEtudiant';\r\n\r\n this.log('Submitting password with button:', connexionButtonName);\r\n\r\n const passwordFormData = {\r\n ...formData,\r\n [passwordFieldName]: password,\r\n __EVENTTARGET: connexionButtonName,\r\n __EVENTARGUMENT: '',\r\n };\r\n\r\n const loginResponse = await this.client.post(\r\n suivantResponse.request?.res?.responseUrl ?? this.LOGIN_URL,\r\n qs.stringify(passwordFormData),\r\n {\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n maxRedirects: 5,\r\n }\r\n );\r\n\r\n const finalUrl = loginResponse.request?.res?.responseUrl ?? '';\r\n this.log('Final URL:', finalUrl);\r\n\r\n // Check for incorrect password (still on login page)\r\n $ = cheerio.load(loginResponse.data);\r\n const passwordStillPresent = findInputByPatterns($, ['textbox7']);\r\n const stillOnLoginPage = isLoginPage(finalUrl);\r\n\r\n if (stillOnLoginPage && passwordStillPresent) {\r\n return { success: false, cookies: [], message: 'Mot de passe incorrect !' };\r\n }\r\n\r\n // Check for success indicators\r\n const successIndicators = [\r\n 'Vous pouvez consulter dans cet espace',\r\n 'Espace Etudiant',\r\n 'Accueil.aspx',\r\n 'Label2',\r\n 'Label3',\r\n ];\r\n\r\n const onHomePage = isHomePage(finalUrl);\r\n const hasSuccessIndicator = successIndicators.some(\r\n indicator => loginResponse.data.includes(indicator)\r\n );\r\n\r\n if (!stillOnLoginPage && (onHomePage || hasSuccessIndicator)) {\r\n this.log('Login successful!');\r\n\r\n // Extract cookies\r\n const cookies: Cookie[] = await this.getCookies();\r\n\r\n return { success: true, cookies, message: 'Login successful!' };\r\n }\r\n\r\n return { success: false, cookies: [], message: 'Login failed - unknown error' };\r\n\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : 'Unknown error';\r\n this.log('Login error:', message);\r\n return { success: false, cookies: [], message };\r\n }\r\n }\r\n\r\n /**\r\n * Logout from the ESPRIT portal.\r\n * \r\n * @returns True if logout was successful\r\n */\r\n async logout(): Promise<boolean> {\r\n try {\r\n // Method 1: Try ASP.NET postback mechanism\r\n this.log('Attempting logout via postback...');\r\n\r\n const homeResponse = await this.client.get(this.HOME_URL, { maxRedirects: 5 });\r\n\r\n if (homeResponse.status === 200 && !isLoginPage(homeResponse.request?.res?.responseUrl ?? '')) {\r\n const formData = extractASPNetFormData(homeResponse.data);\r\n\r\n const logoutFormData = {\r\n ...formData,\r\n __EVENTTARGET: 'ctl00$LinkButton1',\r\n __EVENTARGUMENT: '',\r\n };\r\n\r\n await this.client.post(\r\n this.HOME_URL,\r\n qs.stringify(logoutFormData),\r\n { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }\r\n );\r\n\r\n this.log('Logout successful via postback');\r\n return true;\r\n }\r\n\r\n // Method 2: Try direct logout URLs\r\n for (const logoutUrl of this.LOGOUT_URLS) {\r\n try {\r\n this.log('Trying logout URL:', logoutUrl);\r\n await this.client.get(logoutUrl, { maxRedirects: 5 });\r\n this.log('Logout successful via URL');\r\n return true;\r\n } catch {\r\n continue;\r\n }\r\n }\r\n\r\n // Method 3: Clear cookies as fallback\r\n this.log('Clearing cookies...');\r\n this.cookieJar.removeAllCookiesSync();\r\n return true;\r\n\r\n } catch (error) {\r\n this.log('Logout error:', error);\r\n this.cookieJar.removeAllCookiesSync();\r\n return true;\r\n }\r\n }\r\n\r\n /**\r\n * Get current session cookies.\r\n * \r\n * @returns Array of Cookie objects\r\n */\r\n async getCookies(): Promise<Cookie[]> {\r\n const cookies = await this.cookieJar.getCookies(this.LOGIN_URL);\r\n return cookies.map(cookie => ({\r\n name: cookie.key,\r\n value: cookie.value,\r\n domain: cookie.domain ?? '',\r\n path: cookie.path ?? '/',\r\n }));\r\n }\r\n\r\n /**\r\n * Get student grades.\r\n * \r\n * @returns Array of Grade objects or null if page cannot be loaded\r\n */\r\n async getGrades(): Promise<Grade[] | null> {\r\n try {\r\n const response = await this.client.get(this.GRADES_URL, { maxRedirects: 5 });\r\n\r\n if (isLoginPage(response.request?.res?.responseUrl ?? '')) {\r\n this.log('Session expired - redirected to login');\r\n return null;\r\n }\r\n\r\n const $ = cheerio.load(response.data);\r\n\r\n // Check for expected page content\r\n const h1Tag = $('h1').filter((_, el) =>\r\n $(el).text().includes('Notes Des Modules')\r\n );\r\n\r\n if (h1Tag.length === 0) {\r\n this.log('Page does not contain expected content');\r\n return null;\r\n }\r\n\r\n // Find the grades table\r\n const table = $('#ContentPlaceHolder1_GridView1');\r\n if (table.length === 0) {\r\n this.log('Grades table not found');\r\n return null;\r\n }\r\n\r\n const { headers, rows } = parseTable($, table);\r\n return parseGrades(headers, rows);\r\n\r\n } catch (error) {\r\n this.log('Error getting grades:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get grades with calculated averages.\r\n * \r\n * @returns GradeSummary with grades and total average, or null if page cannot be loaded\r\n */\r\n async getGradesWithAverage(): Promise<GradeSummary | null> {\r\n const grades = await this.getGrades();\r\n if (!grades) return null;\r\n return calculateGradeSummary(grades);\r\n }\r\n\r\n /**\r\n * Get student absences.\r\n * \r\n * @returns Array of Absence records (as key-value objects) or null\r\n */\r\n async getAbsences(): Promise<Absence[] | null> {\r\n try {\r\n const response = await this.client.get(this.ABSENCES_URL, { maxRedirects: 5 });\r\n\r\n if (isLoginPage(response.request?.res?.responseUrl ?? '')) {\r\n this.log('Session expired - redirected to login');\r\n return null;\r\n }\r\n\r\n const $ = cheerio.load(response.data);\r\n\r\n // Check for expected page content\r\n const strongTag = $('strong').filter((_, el) =>\r\n $(el).text().includes('Absence')\r\n );\r\n\r\n if (strongTag.length === 0) {\r\n this.log('Page does not contain expected content');\r\n return null;\r\n }\r\n\r\n // Find the absences table\r\n const table = $('#ContentPlaceHolder1_GridView2');\r\n if (table.length === 0) {\r\n this.log('Absences table not found');\r\n return null;\r\n }\r\n\r\n const { headers, rows } = parseTable($, table);\r\n\r\n // Convert to objects with header keys\r\n return rows.map(row => {\r\n const absence: Absence = {};\r\n headers.forEach((header, index) => {\r\n absence[header] = row[index] ?? '';\r\n });\r\n return absence;\r\n });\r\n\r\n } catch (error) {\r\n this.log('Error getting absences:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get student credit history.\r\n * \r\n * @returns Array of Credit records (as key-value objects) or null\r\n */\r\n async getCredits(): Promise<Credit[] | null> {\r\n try {\r\n const response = await this.client.get(this.CREDITS_URL, { maxRedirects: 5 });\r\n\r\n if (isLoginPage(response.request?.res?.responseUrl ?? '')) {\r\n this.log('Session expired - redirected to login');\r\n return null;\r\n }\r\n\r\n const $ = cheerio.load(response.data);\r\n\r\n // Find the credits table\r\n let table = $('#ContentPlaceHolder1_GridView1');\r\n\r\n if (table.length === 0) {\r\n // Try to find table by content\r\n $('table').each((_, el) => {\r\n const firstRow = $(el).find('tr').first();\r\n const headers = firstRow.find('th').map((_, th) => $(th).text().trim()).get();\r\n if (headers.some(h => h.includes('Année') || h.includes('enseignement'))) {\r\n table = $(el);\r\n return false; // break\r\n }\r\n });\r\n }\r\n\r\n if (table.length === 0) {\r\n this.log('Credits table not found');\r\n return null;\r\n }\r\n\r\n const { headers, rows } = parseTable($, table);\r\n\r\n // Convert to objects with header keys\r\n return rows.map(row => {\r\n const credit: Credit = {};\r\n headers.forEach((header, index) => {\r\n credit[header] = row[index] ?? '';\r\n });\r\n return credit;\r\n });\r\n\r\n } catch (error) {\r\n this.log('Error getting credits:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get available schedules.\r\n * \r\n * @returns Array of Schedule objects or null\r\n */\r\n async getSchedules(): Promise<Schedule[] | null> {\r\n try {\r\n const response = await this.client.get(this.SCHEDULES_URL, { maxRedirects: 5 });\r\n\r\n if (isLoginPage(response.request?.res?.responseUrl ?? '')) {\r\n this.log('Session expired - redirected to login');\r\n return null;\r\n }\r\n\r\n const $ = cheerio.load(response.data);\r\n\r\n // Check for expected page content\r\n const strongTag = $('strong').filter((_, el) =>\r\n $(el).text().includes('Emploi du temps')\r\n );\r\n\r\n if (strongTag.length === 0) {\r\n this.log('Page does not contain expected content');\r\n return null;\r\n }\r\n\r\n // Find the schedules table\r\n const table = $('#ContentPlaceHolder1_GridView1');\r\n if (table.length === 0) {\r\n this.log('Schedules table not found');\r\n return null;\r\n }\r\n\r\n const schedules: Schedule[] = [];\r\n\r\n table.find('tr').each((index, row) => {\r\n if (index === 0) return; // Skip header\r\n\r\n const cells = $(row).find('td');\r\n const rawData: string[] = [];\r\n let name = '';\r\n let link: string | null = null;\r\n\r\n cells.each((i, cell) => {\r\n const cellText = $(cell).text().trim();\r\n const cellLink = $(cell).find('a');\r\n\r\n if (i === 0) {\r\n name = cellText;\r\n }\r\n\r\n if (cellLink.length > 0) {\r\n const href = cellLink.attr('href') ?? '';\r\n // Extract postback target from javascript call\r\n const match = href.match(/'([^']+)'/);\r\n link = match ? match[1] : href;\r\n }\r\n\r\n rawData.push(cellText);\r\n });\r\n\r\n if (name) {\r\n schedules.push({ name, link, rawData });\r\n }\r\n });\r\n\r\n return schedules;\r\n\r\n } catch (error) {\r\n this.log('Error getting schedules:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get student name from the home page.\r\n * \r\n * @returns Student name or null\r\n */\r\n async getStudentName(): Promise<string | null> {\r\n try {\r\n const response = await this.client.get(this.HOME_URL, { maxRedirects: 5 });\r\n\r\n if (isLoginPage(response.request?.res?.responseUrl ?? '')) {\r\n this.log('Session expired - redirected to login');\r\n return null;\r\n }\r\n\r\n const $ = cheerio.load(response.data);\r\n\r\n // Try multiple selectors for the name label\r\n const selectors = [\r\n '#Label2',\r\n '#ContentPlaceHolder1_Label2',\r\n 'span.h4.text-info',\r\n ];\r\n\r\n for (const selector of selectors) {\r\n const element = $(selector);\r\n if (element.length > 0) {\r\n const text = element.text().trim();\r\n if (text) return text;\r\n }\r\n }\r\n\r\n this.log('Student name not found');\r\n return null;\r\n\r\n } catch (error) {\r\n this.log('Error getting student name:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get student class from the home page.\r\n * \r\n * @returns Student class name or null\r\n */\r\n async getStudentClass(): Promise<string | null> {\r\n try {\r\n const response = await this.client.get(this.HOME_URL, { maxRedirects: 5 });\r\n\r\n if (isLoginPage(response.request?.res?.responseUrl ?? '')) {\r\n this.log('Session expired - redirected to login');\r\n return null;\r\n }\r\n\r\n const $ = cheerio.load(response.data);\r\n\r\n // Try multiple selectors for the class label\r\n const selectors = [\r\n '#Label3',\r\n '#ContentPlaceHolder1_Label3',\r\n ];\r\n\r\n for (const selector of selectors) {\r\n const element = $(selector);\r\n if (element.length > 0) {\r\n const text = element.text().trim();\r\n if (text) return text;\r\n }\r\n }\r\n\r\n this.log('Student class not found');\r\n return null;\r\n\r\n } catch (error) {\r\n this.log('Error getting student class:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get both student name and class in one call.\r\n * \r\n * @returns StudentInfo object with name and className\r\n */\r\n async getStudentInfo(): Promise<StudentInfo> {\r\n try {\r\n const response = await this.client.get(this.HOME_URL, { maxRedirects: 5 });\r\n\r\n if (isLoginPage(response.request?.res?.responseUrl ?? '')) {\r\n this.log('Session expired - redirected to login');\r\n return { name: null, className: null };\r\n }\r\n\r\n const $ = cheerio.load(response.data);\r\n\r\n // Get name\r\n let name: string | null = null;\r\n for (const selector of ['#Label2', '#ContentPlaceHolder1_Label2']) {\r\n const element = $(selector);\r\n if (element.length > 0) {\r\n const text = element.text().trim();\r\n if (text) {\r\n name = text;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // Get class\r\n let className: string | null = null;\r\n for (const selector of ['#Label3', '#ContentPlaceHolder1_Label3']) {\r\n const element = $(selector);\r\n if (element.length > 0) {\r\n const text = element.text().trim();\r\n if (text) {\r\n className = text;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n return { name, className };\r\n\r\n } catch (error) {\r\n this.log('Error getting student info:', error);\r\n return { name: null, className: null };\r\n }\r\n }\r\n}\r\n\r\n// Default export\r\nexport default EspritClient;\r\n","import * as cheerio from 'cheerio';\r\nimport type { CheerioAPI, Cheerio } from 'cheerio';\r\nimport type { Element, AnyNode } from 'domhandler';\r\nimport type { ASPNetFormData, Grade, GradeWithAverage, GradeSummary } from './types';\r\n\r\n/**\r\n * Extract ASP.NET hidden form fields from HTML content.\r\n * These fields are required for successful form submissions (postbacks).\r\n * \r\n * @param html - Raw HTML content of the page\r\n * @returns Object containing __VIEWSTATE, __VIEWSTATEGENERATOR, and __EVENTVALIDATION\r\n */\r\nexport function extractASPNetFormData(html: string): ASPNetFormData {\r\n const $ = cheerio.load(html);\r\n\r\n return {\r\n __VIEWSTATE: $('input#__VIEWSTATE').val() as string ?? '',\r\n __VIEWSTATEGENERATOR: $('input#__VIEWSTATEGENERATOR').val() as string ?? '',\r\n __EVENTVALIDATION: $('input#__EVENTVALIDATION').val() as string ?? '',\r\n };\r\n}\r\n\r\n/**\r\n * Find an input field by various ID patterns (case-insensitive).\r\n * ASP.NET often uses different naming conventions.\r\n * \r\n * @param $ - Cheerio instance\r\n * @param patterns - Array of ID patterns to search for (case-insensitive)\r\n * @returns The input element or null if not found\r\n */\r\nexport function findInputByPatterns(\r\n $: CheerioAPI,\r\n patterns: string[]\r\n): Cheerio<Element> | null {\r\n for (const pattern of patterns) {\r\n const lowerPattern = pattern.toLowerCase();\r\n\r\n // Try by ID\r\n const byId = $(`input`).filter((_, el) => {\r\n const id = $(el).attr('id')?.toLowerCase() ?? '';\r\n return id.includes(lowerPattern);\r\n }).first();\r\n\r\n if (byId.length > 0) return byId;\r\n\r\n // Try by name\r\n const byName = $(`input`).filter((_, el) => {\r\n const name = $(el).attr('name')?.toLowerCase() ?? '';\r\n return name.includes(lowerPattern);\r\n }).first();\r\n\r\n if (byName.length > 0) return byName;\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Get the name or id attribute of an input element.\r\n * Prefers 'name' attribute as it's used in form submissions.\r\n * \r\n * @param $input - Cheerio element for the input\r\n * @param $ - Cheerio instance\r\n * @returns The name or id attribute value\r\n */\r\nexport function getInputName(\r\n $input: Cheerio<Element>,\r\n $: CheerioAPI\r\n): string {\r\n return $input.attr('name') ?? $input.attr('id') ?? '';\r\n}\r\n\r\n/**\r\n * Parse a European-style number string (comma as decimal separator).\r\n * \r\n * @param value - String value to parse (e.g., \"14,5\")\r\n * @returns Parsed number or null if invalid/empty\r\n */\r\nexport function parseEuropeanNumber(value: string | undefined): number | null {\r\n if (!value || value.trim() === '') return null;\r\n\r\n // Replace comma with dot for parsing\r\n const normalized = value.trim().replace(',', '.');\r\n const parsed = parseFloat(normalized);\r\n\r\n return isNaN(parsed) ? null : parsed;\r\n}\r\n\r\n/**\r\n * Parse raw grade data from table rows into typed Grade objects.\r\n * \r\n * @param headers - Array of column headers\r\n * @param rows - Array of row data (each row is array of cell values)\r\n * @returns Array of Grade objects\r\n */\r\nexport function parseGrades(headers: string[], rows: string[][]): Grade[] {\r\n const grades: Grade[] = [];\r\n\r\n // Find column indices (case-insensitive matching)\r\n const headerLower = headers.map(h => h.toLowerCase());\r\n const designationIdx = headerLower.findIndex(h => h.includes('designation') || h.includes('module'));\r\n const coefIdx = headerLower.findIndex(h => h.includes('coef'));\r\n const ccIdx = headerLower.findIndex(h => h.includes('cc') || h.includes('note_cc'));\r\n const tpIdx = headerLower.findIndex(h => h.includes('tp') || h.includes('note_tp'));\r\n const examIdx = headerLower.findIndex(h => h.includes('exam') || h.includes('note_exam'));\r\n\r\n for (const row of rows) {\r\n if (row.length === 0) continue;\r\n\r\n grades.push({\r\n designation: designationIdx >= 0 ? row[designationIdx] ?? '' : row[0] ?? '',\r\n coefficient: coefIdx >= 0 ? parseEuropeanNumber(row[coefIdx]) : null,\r\n noteCC: ccIdx >= 0 ? parseEuropeanNumber(row[ccIdx]) : null,\r\n noteTP: tpIdx >= 0 ? parseEuropeanNumber(row[tpIdx]) : null,\r\n noteExam: examIdx >= 0 ? parseEuropeanNumber(row[examIdx]) : null,\r\n });\r\n }\r\n\r\n return grades;\r\n}\r\n\r\n/**\r\n * Calculate module average based on available grade components.\r\n * Uses the same formula as the Python version:\r\n * - Only exam: 100% exam\r\n * - Exam + CC: 60% exam + 40% CC\r\n * - Exam + TP: 80% exam + 20% TP\r\n * - All three: 50% exam + 30% CC + 20% TP\r\n * \r\n * @param grade - Grade object with available components\r\n * @returns Calculated average or null if exam grade is missing\r\n */\r\nexport function calculateModuleAverage(grade: Grade): number | null {\r\n const { noteExam, noteCC, noteTP } = grade;\r\n\r\n // Exam grade is required for calculation\r\n if (noteExam === null) return null;\r\n\r\n if (noteTP === null) {\r\n if (noteCC === null) {\r\n // Only exam grade available\r\n return noteExam;\r\n } else {\r\n // Exam + CC\r\n return noteExam * 0.6 + noteCC * 0.4;\r\n }\r\n } else if (noteCC === null) {\r\n // Exam + TP\r\n return noteExam * 0.8 + noteTP * 0.2;\r\n } else {\r\n // All three components\r\n return noteExam * 0.5 + noteCC * 0.3 + noteTP * 0.2;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate the weighted average for all grades.\r\n * \r\n * @param grades - Array of Grade objects\r\n * @returns GradeSummary with individual averages and total weighted average\r\n */\r\nexport function calculateGradeSummary(grades: Grade[]): GradeSummary {\r\n const gradesWithAverage: GradeWithAverage[] = grades.map(grade => ({\r\n ...grade,\r\n moyenne: calculateModuleAverage(grade),\r\n }));\r\n\r\n // Calculate weighted average (only for grades with valid moyenne and coefficient)\r\n let totalWeightedSum = 0;\r\n let totalCoefficient = 0;\r\n\r\n for (const grade of gradesWithAverage) {\r\n if (grade.moyenne !== null && grade.coefficient !== null) {\r\n totalWeightedSum += grade.moyenne * grade.coefficient;\r\n totalCoefficient += grade.coefficient;\r\n }\r\n }\r\n\r\n const totalAverage = totalCoefficient > 0\r\n ? totalWeightedSum / totalCoefficient\r\n : null;\r\n\r\n return {\r\n grades: gradesWithAverage,\r\n totalAverage,\r\n totalCoefficient,\r\n };\r\n}\r\n\r\n/**\r\n * Parse a table element into headers and rows.\r\n * \r\n * @param $ - Cheerio instance\r\n * @param table - Cheerio table element\r\n * @returns Object with headers and rows arrays\r\n */\r\nexport function parseTable(\r\n $: CheerioAPI,\r\n table: Cheerio<AnyNode>\r\n): { headers: string[]; rows: string[][] } {\r\n const rowElements = table.find('tr');\r\n const headers: string[] = [];\r\n const rows: string[][] = [];\r\n\r\n rowElements.each((index, row) => {\r\n const cells: string[] = [];\r\n\r\n if (index === 0) {\r\n // First row - extract headers from th elements\r\n $(row).find('th').each((_, cell) => {\r\n headers.push($(cell).text().trim());\r\n });\r\n } else {\r\n // Data rows - extract from td elements\r\n $(row).find('td').each((_, cell) => {\r\n cells.push($(cell).text().trim());\r\n });\r\n if (cells.length > 0) {\r\n rows.push(cells);\r\n }\r\n }\r\n });\r\n\r\n return { headers, rows };\r\n}\r\n\r\n/**\r\n * Check if a URL indicates the user has been redirected to the login page.\r\n * \r\n * @param url - Current page URL\r\n * @returns True if on login page\r\n */\r\nexport function isLoginPage(url: string): boolean {\r\n const lower = url.toLowerCase();\r\n return lower.includes('default.aspx') || lower.includes('login');\r\n}\r\n\r\n/**\r\n * Check if a URL indicates the user is on the home/dashboard page.\r\n * \r\n * @param url - Current page URL\r\n * @returns True if on home page\r\n */\r\nexport function isHomePage(url: string): boolean {\r\n return url.toLowerCase().includes('accueil.aspx');\r\n}\r\n"],"mappings":"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","names":["cheerio","headers","_"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lime1/esprit-ts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeScript client for ESPRIT student portal - scrape grades, absences, credits, and schedules",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"clean": "rimraf dist",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"esprit",
|
|
28
|
+
"student",
|
|
29
|
+
"portal",
|
|
30
|
+
"scraper",
|
|
31
|
+
"grades",
|
|
32
|
+
"absences",
|
|
33
|
+
"credits",
|
|
34
|
+
"schedule",
|
|
35
|
+
"tunisia",
|
|
36
|
+
"typescript"
|
|
37
|
+
],
|
|
38
|
+
"author": "TheLime1",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/TheLime1/esprit-ts.git"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/TheLime1/esprit-ts/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/TheLime1/esprit-ts#readme",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"axios": "^1.6.7",
|
|
50
|
+
"axios-cookiejar-support": "^5.0.0",
|
|
51
|
+
"cheerio": "^1.0.0-rc.12",
|
|
52
|
+
"qs": "^6.11.2",
|
|
53
|
+
"tough-cookie": "^4.1.3"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.11.16",
|
|
57
|
+
"@types/qs": "^6.9.11",
|
|
58
|
+
"@types/tough-cookie": "^4.0.5",
|
|
59
|
+
"dotenv": "^16.4.1",
|
|
60
|
+
"rimraf": "^5.0.5",
|
|
61
|
+
"tsup": "^8.0.1",
|
|
62
|
+
"typescript": "^5.3.3"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=18.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|