summaryrefslogtreecommitdiff
path: root/school/node_modules/pronote-api/src/server
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2023-01-10 14:54:04 +0100
committerMinteck <contact@minteck.org>2023-01-10 14:54:04 +0100
commit99c1d9af689e5325f3cf535c4007b3aeb8325229 (patch)
treee663b3c2ebdbd67c818ac0c5147f0ce1d2463cda /school/node_modules/pronote-api/src/server
parent9871b03912fc28ad38b4037ebf26a78aa937baba (diff)
downloadpluralconnect-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')
-rw-r--r--school/node_modules/pronote-api/src/server/auth.js49
-rw-r--r--school/node_modules/pronote-api/src/server/context.js73
-rw-r--r--school/node_modules/pronote-api/src/server/date.js20
-rw-r--r--school/node_modules/pronote-api/src/server/http.js57
-rw-r--r--school/node_modules/pronote-api/src/server/index.js47
-rw-r--r--school/node_modules/pronote-api/src/server/schemas/common.graphql464
-rw-r--r--school/node_modules/pronote-api/src/server/schemas/index.js40
-rw-r--r--school/node_modules/pronote-api/src/server/schemas/parent.graphql40
-rw-r--r--school/node_modules/pronote-api/src/server/schemas/student.graphql21
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!]!
+}