diff options
author | Minteck <contact@minteck.org> | 2023-01-10 14:54:04 +0100 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2023-01-10 14:54:04 +0100 |
commit | 99c1d9af689e5325f3cf535c4007b3aeb8325229 (patch) | |
tree | e663b3c2ebdbd67c818ac0c5147f0ce1d2463cda /school/node_modules/pronote-api/src/server | |
parent | 9871b03912fc28ad38b4037ebf26a78aa937baba (diff) | |
download | pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.tar.gz pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.tar.bz2 pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.zip |
Update - This is an automated commit
Diffstat (limited to 'school/node_modules/pronote-api/src/server')
9 files changed, 811 insertions, 0 deletions
diff --git a/school/node_modules/pronote-api/src/server/auth.js b/school/node_modules/pronote-api/src/server/auth.js new file mode 100644 index 0000000..54962a2 --- /dev/null +++ b/school/node_modules/pronote-api/src/server/auth.js @@ -0,0 +1,49 @@ +const { v4: uuid } = require('uuid'); + +const { loginStudent, loginParent } = require('../auth'); + +const sessions = {}; + +async function login({ url, username, password, cas, account = 'student' }) +{ + if (!url || !username || !password) { + throw { + http: 400, + message: 'Missing \'url\', or \'username\', or \'password\', or header \'Content-Type: application/json\'' + }; + } + + let func; + switch (account) { + case 'student': + func = loginStudent; + break; + case 'parent': + func = loginParent; + break; + default: + throw { + http: 400, + message: `Unknown account type '${account}'` + }; + } + + const token = uuid(); + sessions[token] = await func(url, username, password, cas); + + return { token }; +} + +// eslint-disable-next-line no-unused-vars +async function logout(_, token) +{ + delete sessions[token]; + return { success: true }; +} + +function getSession(token) +{ + return sessions[token]; +} + +module.exports = { login, logout, getSession }; diff --git a/school/node_modules/pronote-api/src/server/context.js b/school/node_modules/pronote-api/src/server/context.js new file mode 100644 index 0000000..9f34442 --- /dev/null +++ b/school/node_modules/pronote-api/src/server/context.js @@ -0,0 +1,73 @@ +function common(session) { + return { + params: () => session.params, + user: () => session.user, + + keepAlive: async () => { + await session.keepAlive(); + return true; + }, + logout: async () => { + await session.logout(); + return true; + }, + + setKeepAlive: async ({ enabled }) => { + await session.setKeepAlive(enabled); + return enabled; + } + }; +} + +function student(session) { + return { + timetable: ({ from, to }) => session.timetable(from, to), + marks: ({ period }) => session.marks(period), + evaluations: ({ period }) => session.evaluations(period), + absences: ({ period, from, to }) => session.absences(period, from, to), + infos: () => session.infos(), + contents: ({ from, to }) => session.contents(from, to), + homeworks: ({ from, to }) => session.homeworks(from, to), + menu: ({ from, to }) => session.menu(from, to), + files: () => session.files() + }; +} + +function parent(session) { + function getStudent(student) { + for (const s of session.user.students) { + if (s.id === student || s.name === student) { + return s; + } + } + + return null; + } + + return { + timetable: ({ student, from, to }) => session.timetable(getStudent(student), from, to), + marks: ({ student, period }) => session.marks(getStudent(student), period), + evaluations: ({ student, period }) => session.evaluations(getStudent(student), period), + absences: ({ student, period, from, to }) => session.absences(getStudent(student), period, from, to), + infos: ({ student }) => session.infos(getStudent(student)), + contents: ({ student, from, to }) => session.contents(getStudent(student), from, to), + homeworks: ({ student, from, to }) => session.homeworks(getStudent(student), from, to), + menu: ({ student, from, to }) => session.menu(getStudent(student), from, to), + files: ({ student }) => session.files(getStudent(student)) + }; +} + +function getContext(session) { + const result = common(session); + + switch (session.type.name) { + case 'student': + return { ...result, ...student(session) }; + case 'parent': + return { ...result, ...parent(session) }; + default: + return result; + } +} + +module.exports = getContext; diff --git a/school/node_modules/pronote-api/src/server/date.js b/school/node_modules/pronote-api/src/server/date.js new file mode 100644 index 0000000..a35cd5a --- /dev/null +++ b/school/node_modules/pronote-api/src/server/date.js @@ -0,0 +1,20 @@ +const { GraphQLScalarType, Kind } = require('graphql'); + +module.exports = new GraphQLScalarType({ + name: 'Date', + description: 'Equivalent of the JS Date type', + + parseValue(value) { + return new Date(value); + }, + serialize(value) { + return value.getTime(); + }, + parseLiteral(ast) { + if (ast.kind === Kind.INT) { + return new Date(parseInt(ast.value)); + } + + return new Date(ast.value); + } +}); diff --git a/school/node_modules/pronote-api/src/server/http.js b/school/node_modules/pronote-api/src/server/http.js new file mode 100644 index 0000000..66b07a5 --- /dev/null +++ b/school/node_modules/pronote-api/src/server/http.js @@ -0,0 +1,57 @@ +/* eslint no-console: off */ + +const polka = require('polka'); +const body = require('body-parser'); + +function start(host, port, handlers) +{ + const server = polka(); + server.use(body.json()); + + server.post('/auth/login', (req, res) => handle(req, res, handlers.login)); + server.post('/auth/logout', (req, res) => handle(req, res, handlers.logout)); + server.post('/graphql', (req, res) => handle(req, res, handlers.graphql)); + + return new Promise((resolve, reject) => { + server.listen(port, host, err => { + if (err) { + return reject(err); + } + + return resolve(); + }) + }); +} + +function handle(req, res, handler) +{ + handler(req.body, req.headers.token) + .then(result => respond(res, 200, result)) + .catch(err => { + console.error('Error during request handling :'); + console.error(err); + + if (err.message) { + delete err.http; + respond(res, err.http || 500, err); + } else { + respond(res, 500, { + message: 'Internal error : ' + err + }); + } + }); +} + +function respond(res, code, obj) +{ + const data = JSON.stringify(obj); + const headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Content-Length': Buffer.byteLength(data) + }; + + res.writeHead(code, headers); + res.end(data); +} + +module.exports = start; diff --git a/school/node_modules/pronote-api/src/server/index.js b/school/node_modules/pronote-api/src/server/index.js new file mode 100644 index 0000000..1693e1c --- /dev/null +++ b/school/node_modules/pronote-api/src/server/index.js @@ -0,0 +1,47 @@ +const { graphql } = require('graphql'); + +const http = require('./http'); +const context = require('./context'); +const getSchemas = require('./schemas'); +const { login, logout, getSession } = require('./auth'); + +async function start(host, port) +{ + const schemas = await getSchemas(); + + await http(host, port, { + graphql: ({ query, variables }, token) => handle(token, schemas, query, context, variables), + login: params => login(params), + logout: (_, token) => logout(token) + }); +} + +async function handle(token, schemas, query, context, variables) +{ + if (!token) { + throw { + http: 401, + message: 'Missing \'Token\' header' + }; + } + + if (!query) { + throw { + http: 400, + message: 'Missing \'query\' field or \'Content-Type: application/json\' header' + }; + } + + const session = getSession(token); + if (!session) { + throw { + http: 401, + message: 'Unknown session token' + }; + } + + const schema = schemas[session.type.name]; + return await graphql(schema, query, context(session), null, variables); +} + +module.exports = start; diff --git a/school/node_modules/pronote-api/src/server/schemas/common.graphql b/school/node_modules/pronote-api/src/server/schemas/common.graphql new file mode 100644 index 0000000..0d821ff --- /dev/null +++ b/school/node_modules/pronote-api/src/server/schemas/common.graphql @@ -0,0 +1,464 @@ +scalar Date + +type Query { + params: PronoteParams! + user: PronoteUser! +} + +type Mutation { + # Please remember to disable this when you don't need it, or else sessions will last forever ! + setKeepAlive(enabled: Boolean!): Boolean! + + # This is in a mutation, because this is a request that send a keep alive to Pronote, not a value + # It will always returns True + keepAlive: Boolean! + + # This is in a mutation, because this is a request that send a keep alive to Pronote, not a value + # It will always returns True + logout: Boolean! +} + +type Lesson { + id: String! + from: Date! + to: Date! + isDetention: Boolean! + hasDuplicate: Boolean! + subject: String + teacher: String + room: String + status: String + isAway: Boolean + isCancelled: Boolean + color: String + remoteLesson: Boolean +} + +type Marks { + subjects: [MarksSubject]! + averages: MarksAverages! +} + +type MarksSubject { + name: String! + averages: MarksSubjectAverages! + color: String! + marks: [Mark!]! +} + +type MarksAverages { + student: Float + studentClass: Float +} + +type MarksSubjectAverages { + student: Float! + studentClass: Float! + min: Float! + max: Float! +} + +type Mark { + id: String!, + title: String!, + value: Float, + scale: Float!, + average: Float, + coefficient: Float! + min: Float + max: Float + date: Date! + isAway: Boolean! +} + +type EvaluationSubject { + name: String! + teacher: String! + color: String! + evaluations: [Evaluation!]! +} + +type Evaluation { + id: String! + name: String! + date: Date! + coefficient: Float! + levels: [EvaluationLevel!]! +} + +type EvaluationLevel { + name: String! + value: EvaluationLevelValue! + prefixes: [String!]! +} + +type EvaluationLevelValue { + short: String! + long: String! +} + +type Absences { + absences: [Absence!]! + delays: [Delay!]! + punishments: [Punishment!]! + other: [OtherEvent!]! + totals: [SubjectAbsences!]! +} + +type Absence { + id: String! + from: Date! + to: Date! + justified: Boolean! + solved: Boolean! + hours: Float! + reason: String +} + +type Delay { + id: String! + date: Date! + justified: Boolean! + solved: Boolean! + justification: String! + minutesMissed: Int! + reason: String +} + +type Punishment { + id: String! + date: Date! + isExclusion: Boolean! + isDuringLesson: Boolean! + homework: String! + circumstances: String! + giver: String! + reason: String + detention: Detention +} + +type Detention { + id: String! + from: Date! + to: Date! +} + +type OtherEvent { + id: String! + kind: String! + date: Date! + giver: String! + comment: String! + subject: String +} + +type SubjectAbsences { + subject: String! + hoursAssisted: Float! + hoursMissed: Float! + subs: [SubjectAbsences!] +} + +type Info { + id: String! + date: Date! + title: String + author: String! + content: String! + htmlContent: String! + files: [File!]! +} + +type LessonContent { + id: String! + subject: String! + teachers: [String!]! + from: Date! + to: Date! + color: String! + title: String + description: String! + htmlDescription: String! + files: [File!]! + category: String! +} + +type Homework { + id: String! + description: String! + htmlDescription: String! + subject: String! + givenAt: Date! + for: Date! + done: Boolean! + color: String! + files: [File!]! +} + +type File { + id: String! + time: String! + subject: String! + name: String! + url: String! +} + +type MenuDay { + date: Date! + meals: [[[MenuMealEntry!]!]!]! +} + +type MenuMealEntry { + name: String! + labels: [MenuMealLabel!]! +} + +type MenuMealLabel { + name: String! + color: String! +} + +type PronoteParams { + navigatorId: String + fonts: [String!]! + withMember: Boolean! + forNewCaledonia: Boolean! + loginImageId: Float! + loginImageUrl: String! + cssLogo: String! + theme: Float! + serverTime: Date! + mobileURL: String! + mobileSupport: Boolean! + title: String! + indexEducationWebsite: String! + version: String! + versionFull: String! + year: Float! + language: PronoteLanguage + supportedLanguages: [PronoteLanguage]! + infoPage: String! + hasForum: Boolean! + helpURL: String! + videosURL: String! + twitterURL: String! + withLoginOptions: Boolean! + establishment: String! + displayWeeks: String! + schoolYear: String! + firstCycle: Date! + firstDay: Date! + firstMonday: Date! + lastDay: Date! + ticksPerDay: Float! + ticksPerHour: Float! + sequenceDuration: Float! + ticksForHalfDayAbsence: Float! + hasLunch: Boolean! + lunchStart: Float! + lunchEnd: Float! + withPlainAfternoonHours: Boolean! + firstOrLastWorkingDay: Date! + workingDays: [Float!]! + lunchDays: [Float!]! + parentsChat: Boolean! + workingDaysPerCycle: Float! + firstDayOfWeek: Float! + timetableGridsInCycle: Float! + workingDaysCycle: [Float!]! + halfWorkingDays: [[Float!]!]! + frequenciesRanges: [[Float!]!]! + frequenciesLabels: [String!]! + defaultMarkMax: Float! + allowedAnnotations: [Float!]! + acquisitionLevels: [PronoteAcquisitionLevel]! + displayAcquisitionShortLabel: Boolean! + withEvaluationHistory: Boolean! + withoutIntermediaryLevelAutoValidation: Boolean! + onlySchoolYearEvaluationsInAutoValidation: Boolean! + CECRLLevelsSupport: Boolean + langActivityColor: String + minMarkMCQ: Float! + maxMarkMCQ: Float! + maxPointsMCQ: Float! + skillsGridLabelSize: Float! + homeworkCommentSize: Float! + officeEnabled: Boolean! + officeFederatedMode: Boolean! + officeTutorial: String! + oneDriveTutorial: String! + connexionInfoRetrieval: Boolean! + font: String! + fontSize: Float! + attachedStudents: Boolean! + phoneMask: String! + maxECTS: Float! + maxAppreciationSizes: [Float!]! + publicHolidays: [PronoteHoliday]! + displaySequences: Boolean! + firstHour: Date! + hours: [PronoteHour]! + endHours: [PronoteHour]! + sequences: [String!]! + periods: [PronotePeriod]! + logo: Float! + breaks: [PronoteBreak]! + appCookieName: String! +} + +type PronoteUser { + name: String! + establishmentsInfo: [PronoteEstablishmentInfo!]! + userSettings: PronoteUserSettings! + sessionAuthorizations: PronoteSessionAuthorizations! + authorizations: PronoteUserAuthorizations + minPasswordSize: Int! + maxPasswordSize: Int! + passwordRules: [Int!]! + kioskAccess: Boolean! + tabs: [PronoteTab!]! + hiddenTabs: [Int!]! + notifiedTabs: [Int!]! +} + +type PronoteLanguage { + id: Int! + name: String! +} + +type PronoteAcquisitionLevel { + count: Int! + positions: [PronoteAcquisitionLevelPositions!]! + triggerPosition: Float! + activeFor: [Int!]! + shortName: String! + shortPath: String! + color: String + weighting: Float + brevetPoints: Float + acquired: Boolean + countsForSuccess: Boolean +} + +type PronoteAcquisitionLevelPositions { + name: String! + count: Int! + shortName: String! + shortNameWithPrefix: String +} + +type PronoteHoliday { + name: String! + from: Date! + to: Date! +} + +type PronoteHour { + name: String! + count: Int! + round: Boolean! +} + +type PronotePeriod { + name: String! + notationPeriod: Int! + from: Date! + to: Date! +} + +type PronoteBreak { + name: String! + position: Int! +} + +type PronoteNamedObject { + name: String! +} + +type PronoteClassHistoryElement { + name: String! + hadMarks: Boolean! + hadOptions: Boolean! +} + +type PronoteTabPillars { + tab: Int! + levels: [PronotePillarLevel!]! +} + +type PronotePillarLevel { + name: String! + pillars: [PronotePillar!]! +} + +type PronotePillar { + name: String! + isForeignLanguage: Boolean! + isCoreSkill: Boolean! + subject: PronoteNamedObject! +} + +type PronoteTabPeriods { + tab: Int! + periods: [PronoteTabPeriod!]! + defaultPeriod: PronoteNamedObject +} + +type PronoteTabPeriod { + name: String! + isCorePeriod: Boolean! +} + +type PronoteEstablishmentInfo { + name: String! + logoID: Int! + address: [String!]! + postalCode: String! + postalLabel: String! + city: String! + province: String! + country: String! + website: String! +} + +type PronoteUserSettings { + version: Float! + timetable: PronoteUserTimetableSettings! + theme: Int! + unreadDiscussions: Boolean! +} + +type PronoteUserTimetableSettings { + displayCanceledLessons: Boolean! + invertAxis: Boolean! + invertWeeklyPlanAxis: Boolean! + invertDayPlanAxis: Boolean! + invertDay2PlanAxis: Boolean! + dayCount: Int! + resourceCount: Int! + daysInTimetable: Int! + sequenceCount: Int! +} + +type PronoteUserAuthorizations { + discussions: Boolean! + teachersDiscussions: Boolean! + timetableVisibleWeeks: [Int!]! + canEditLessons: [Int!]! + hideClassParts: Boolean! + maxEstablishmentFileSize: Int! + maxUserWorkFileSize: Int! + hasPassword: Boolean! + hasPersonalInfo: Boolean! + canPrint: Boolean! +} + +type PronoteSessionAuthorizations { + twitterManagement: Boolean! + expandedAttestation: Boolean! +} + +type PronoteTab { + id: Int! + subs: [PronoteTab!]! +} diff --git a/school/node_modules/pronote-api/src/server/schemas/index.js b/school/node_modules/pronote-api/src/server/schemas/index.js new file mode 100644 index 0000000..099d358 --- /dev/null +++ b/school/node_modules/pronote-api/src/server/schemas/index.js @@ -0,0 +1,40 @@ +const path = require('path'); +const fs = require('fs').promises; + +const { buildSchema } = require('graphql'); + +const date = require('../date'); + +const SCHEMAS = ['student', 'parent']; + +async function readFile(name) +{ + const file = path.join(__dirname, name); + const content = await fs.readFile(file); + + return content.toString(); +} + +async function readSchema(common, name) +{ + const content = await readFile(name + '.graphql'); + const schema = buildSchema(common + '\n' + content); + + Object.assign(schema._typeMap.Date, date); + + return schema; +} + +async function getSchemas() +{ + const common = await readFile('common.graphql'); + const result = {}; + + for (const schema of SCHEMAS) { + result[schema] = await readSchema(common, schema); + } + + return result; +} + +module.exports = getSchemas; diff --git a/school/node_modules/pronote-api/src/server/schemas/parent.graphql b/school/node_modules/pronote-api/src/server/schemas/parent.graphql new file mode 100644 index 0000000..0f6bf86 --- /dev/null +++ b/school/node_modules/pronote-api/src/server/schemas/parent.graphql @@ -0,0 +1,40 @@ +extend type Query { + timetable(student: String!, from: Date, to: Date): [Lesson!] + marks(student: String!, period: String): Marks + evaluations(student: String!, period: String): [EvaluationSubject!] + absences(student: String!, period: String, from: Date, to: Date): Absences + infos(student: String!): [Info!] + contents(student: String!, from: Date, to: Date): [LessonContent!] + homeworks(student: String!, from: Date, to: Date): [Homework!] + menu(student: String!, from: Date, to: Date): [MenuDay!] + files(student: String!): [File!] +} + +extend type PronoteUser { + isDelegate: Boolean! + isBDMember: Boolean! + canDiscussWithManagers: Boolean! + absencesReasons: [PronoteNamedObject!]! + delaysReasons: [PronoteNamedObject!]! + classDelegates: [PronoteNamedObject!]! + students: [PronoteStudent!]! +} + +extend type PronoteUserAuthorizations { + staffDiscussion: Boolean! + parentsDiscussion: Boolean! + editStudentPassword: Boolean! + editCoordinates: Boolean! + editAuthorizations: Boolean! +} + +type PronoteStudent { + name: String! + establishment: PronoteNamedObject! + avatar: String + studentClass: PronoteNamedObject! + classHistory: [PronoteClassHistoryElement!]! + groups: [PronoteNamedObject!]! + tabsPillars: [PronoteTabPillars!]! + tabsPeriods: [PronoteTabPeriods!]! +} diff --git a/school/node_modules/pronote-api/src/server/schemas/student.graphql b/school/node_modules/pronote-api/src/server/schemas/student.graphql new file mode 100644 index 0000000..99dd73f --- /dev/null +++ b/school/node_modules/pronote-api/src/server/schemas/student.graphql @@ -0,0 +1,21 @@ +extend type Query { + timetable(from: Date, to: Date): [Lesson!] + marks(period: String): Marks + evaluations(period: String): [EvaluationSubject!] + absences(period: String, from: Date, to: Date): Absences + infos: [Info] + contents(from: Date, to: Date): [LessonContent!] + homeworks(from: Date, to: Date): [Homework!] + menu(from: Date, to: Date): [MenuDay!] + files: [File!] +} + +extend type PronoteUser { + establishment: PronoteNamedObject! + avatar: String + studentClass: PronoteNamedObject! + classHistory: [PronoteClassHistoryElement!]! + groups: [PronoteNamedObject!]! + tabsPillars: [PronoteTabPillars!]! + tabsPeriods: [PronoteTabPeriods!]! +} |