Giter VIP home page Giter VIP logo

pawnote's Introduction

Pawnote: A purrfect API wrapper for PRONOTE

This library is not affiliated with Index-Education or PRONOTE in any way.

What is "PRONOTE" ?

PRONOTE is a school management application used by educational establishments in France to centralize and facilitate communication between teachers, students and parents. It lets you manage and consult timetables, grades, absences and homework, as well as communicate via messages. PRONOTE aims to improve the transparency and efficiency of day-to-day school management.

Implementations

You're currently on the index branch.

Since we're implementing this library in different programming languages, we have a branch for each of them.

JS/TS Rust Python Swift
Kotlin C# Dart

License

This project is licensed under the GPL-3.0 License - see the LICENSE.md file for details.

pawnote's People

Contributors

vexcited avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

pawnote's Issues

refactor: rewrite everything to match new structure

We should rewrite the whole library after a restructure design we decided to adopt for all our JS/TS libraries.


Roadmap

Consider that everything not mentioned here is currently working.

  • getResourcesForWeek
  • getResourcesForInterval
  • patchHomeworkStatus
  • getGradesOverview
  • generateGradesReportPDF
  • getEvaluations
  • getPersonalInformation
  • getLessonResource
  • getLessonHomework
  • getNews
  • getDiscussionsOverview
  • getMessagesOverviewFromDiscussion
  • postDiscussionCommand
  • markDiscussionAsRead
  • getRecipientsForMessage
  • patchNewsState
  • getRecipientsForDiscussionCreation
  • createDiscussion
  • replyToDiscussionMessage
  • uploadHomeworkFile (+ add support for bun)
  • removeHomeworkFile
  • getFrequencyForWeek
  • Usage of Queue in the RequestFN#send
  • ARD partner for SSO

Support platforms outside of Node.js

For now, we only build for Node.js using wasm-pack.

We could do two builds and find a way to merge both results.

wasm-pack build -t nodejs -d pkg-node
wasm-pack build -t browser -d pkg
  • Is the .wasm output the same ? What changes ?
  • Is there a way to have in common the same JS file ?

feat(discussions): able to save and patch a draft

CREATION

Can only create a draft when content is not empty.

{
  "session": 9108278,
  "numeroOrdre": "90c0a5aae6e73e79b214df46301dcd33",
  "nom": "SaisieMessage",
  "donneesSec": {
    "_Signature_": {
      "onglet": 131 // DISCUSSIONS
    },
    "donnees": {
      "commande": "brouillon", // available in commands enum
      "brouillon": {
        "N": -1001, // `generateCreationID()` from Pawnote internals
        "E": 1 // 1 = CREATION
      },
      "messagePourReponse": {
        "N": "88#rfxo3tiY-0ZADCNfjFFApfH6ZEOUfNd-m9E8DsvVIrs", // ListeMessages : messagePourReponse.V.N
        "G": 0 // ListeMessages : messagePourReponse.V.G
      },
      "objet": "", // ???
      "contenu": "hello !", // content we want to draft
      "listeDestinataires": [], // only for teachers and authorized
      "listeFichiers": [] // only for teachers and authorized
    }
  }
}

ListeMessagerie after creation

{
  "N": "0",
  "estUneDiscussion": true,
  "nombreMessages": 2,
  "objet": "Hoho",
  "listePossessionsMessages": {
    "_T": 24,
    "V": [
      {
        "N": "115#QoEB1BDvI2etVLNmfbsHuevbV_4yWj2_gFu9TUxXArk"
      },
      {
        "N": "115#fxCTxav6EfNYSi_F8VtU5-26om1866rAriqe5kA1dlQ"
      }
    ]
  },
  "avecReponse": true,
  "messagePourParticipants": {
    "_T": 24,
    "V": {
      "N": "88#rfxo3tiY-0ZADCNfjFFApfH6ZEOUfNd-m9E8DsvVIrs"
    }
  },
  "nbPublic": 27,
  "initiateur": "M. LACAZE H.",
  "nbBrouillons": 1, // NEW !!
  "dernierPossessionMessage": {
    "_T": 24,
    "V": {
      "N": "115#fxCTxav6EfNYSi_F8VtU5-26om1866rAriqe5kA1dlQ"
    }
  },
  "messageFenetre": {
    "_T": 24,
    "V": {
      "N": "88#rfxo3tiY-0ZADCNfjFFApfH6ZEOUfNd-m9E8DsvVIrs",
      "G": 0
    }
  },
  "lu": true,
  "libelleDate": "23h16",
  "estNonPossede": false,
  "listeEtiquettes": {
    "_T": 24,
    "V": [
      {
        "N": "54#Vbjbo4bz-lJkQ038beAmhdNV-Pakrwxn5rA_aDfJJMk" // brouillon
      }
    ]
  },
  "profondeur": 0
}

ListeMessages after creation

{
  "nom": "ListeMessages",
  "session": 9108278,
  "numeroOrdre": "28260EC17FD7658CEA5FD17D26739C29",
  "donneesSec": {
    "donnees": {
      "listeMessages": {
        "_T": 24,
        "V": [
          {
            "N": "88#rfxo3tiY-0ZADCNfjFFApfH6ZEOUfNd-m9E8DsvVIrs",
            "messageSource": {
              "_T": 24,
              "V": {
                "N": "0"
              }
            },
            "possessionMessage": {
              "_T": 24,
              "V": {
                "N": "115#fxCTxav6EfNYSi_F8VtU5-26om1866rAriqe5kA1dlQ"
              }
            },
            // ...
          }
        ]
      },
      "messagePourReponse": {
        "_T": 24,
        "V": {
          "N": "88#rfxo3tiY-0ZADCNfjFFApfH6ZEOUfNd-m9E8DsvVIrs",
          "G": 0,
          "nbDestinataires": 26
        }
      },
      "listeBoutons": {
        "_T": 24,
        "V": [
          {
            "G": 4,
            "L": "Envoyer"
          }
        ]
      },
      "brouillon": {
        "_T": 24,
        "V": {
          "N": "115#QoEB1BDvI2etVLNmfbsHuevbV_4yWj2_gFu9TUxXArk",
          "contenu": {
            "_T": 21,
            "V": "ffffffffffff"
          },
          "estHTML": false
        }
      },
      "nbPossessionsMessage": 1
    },
    "nom": "ListeMessages"
  }
}

PATCH

{
  "session": 9108278,
  "numeroOrdre": "b92e4847eebef93a754c2b26a5e0c886",
  "nom": "SaisieMessage",
  "donneesSec": {
    "_Signature_": {
      "onglet": 131 // DISCUSSIONS
    },
    "donnees": {
      "commande": "brouillon", // available in commands enum
      "brouillon": {
        "N": "115#QoEB1BDvI2etVLNmfbsHuevbV_4yWj2_gFu9TUxXArk", // ID
        "E": 2 // 2 = MODIFICATION
      },
      "messagePourReponse": {
        "N": "88#rfxo3tiY-0ZADCNfjFFApfH6ZEOUfNd-m9E8DsvVIrs",
        "G": 0
      },
      "contenu": "ddddddddddddddddddddd",
      "listeFichiers": []
    }
  }
}

SENDING

{
  "session": 9108278,
  "numeroOrdre": "219c3f01a448322ac8fb59c9784d44c5",
  "nom": "SaisieMessage",
  "donneesSec": {
    "_Signature_": {
      "onglet": 131
    },
    "donnees": {
      "commande": "",
      "bouton": {
        "N": 0,
        "G": 2
      },
      "brouillon": {
        "N": "115#QoEB1BDvI2etVLNmfbsHuevbV_4yWj2_gFu9TUxXArk"
      },
      "messagePourReponse": {
        "N": "88#rfxo3tiY-0ZADCNfjFFApfH6ZEOUfNd-m9E8DsvVIrs",
        "G": 0
      },
      "contenu": "ffffffffffff",
      "listeDestinataires": [],
      "listeFichiers": []
    }
  }
}

feat(timetable): provide a builtin reducer for parsing lessons automatically

Default behavior of the lessons given by the TimetableOverview is to return the raw parsed lessons. Meaning that it just iterates through the data given by Pronote and gives you back an array of parsed objects.

Even though, it doesn't handle updated lessons, etc. so to fix this, we might add an extra reducer function that can be added on top to make sure data is well parsed.

Fixes this issue : https://github.com/PapillonApp/Papillon/issues/113

feat(parser): add handling for values concerning ARD partner

Here's what the homepage response looks like for ARD

{
  "partenaireARD": {
    // TODO: this is not handled yet in the parser !
    "porteMonnaie": {
      "_T": 24,
      "V": [
        {
          "libellePorteMonnaie": "RESTAURATION",
          "valeurSolde": "420,69 €",
          "avecWarning": false,
          "hintSolde": "valeur au 31/03/24 - 22h57",
          "hintPorteMonnaie": "Mode de fonctionnement : Ticket"
        }
      ]
    },

    // data passed to `partnerURL` API request
    "SSO": {
      "codePartenaire": "ARD",
      "intituleLien": "Services en ligne liés à la restauration",
      "description": "Permet d’accéder au site ARD-GECENLIGNE de l’établissement sans avoir à s’authentifier à nouveau."
    },

    // means we can refresh the widget (maybe TODO ?)
    "avecActualisation": true
  }
}

This is what it looks like in the homepage UI :

image

This is what the HTML looks like :

image

Refactor everything to Rust/C++/C

As the title says, we want to migrate the whole js branch codebase into a a single language codebase.

Why ?

Since we want to support multiple languages (and we're a bit lazy to rewrite the whole implementation in different languages on each update) we need to have a "core" library. We thought about using a low level language to do it.

What will be the structure ?

This core library WON'T make HTTP requests by itself. It'll be the parser and the builder of every requests made in other languages.

EDIT:

The library won't make HTTP requests for the WebAssembly build. It'll take a fetcher from JS to make the HTTP request and then return this value to the library.

That's why for each API call we'll have three functions : the base function, a function ONLY for WASM and a more general function. Both of these two functions will have the same name, it'll just build depending on the target. They'll both call the base function (but with a different fetcher)

How will we support multiple languages ?

Here's how we see it: we'll make a WebAssembly build for JS/TS and FFI for every other languages (such as C#, Kotlin, Swift, Python and more)

What about the js and dart branches ?

They'll be deleted once the rewrite is done with the bindings working for JS/TS.

Please understand that the work done in the js branch wasn't useless since the whole new implementation will be based on the hundred of hours spent on that branch.

What about next releases ?

Once the rewrite is done with JS/TS bindings working and maybe FFI PoC working, we'll do the v1.0.0 release.

All past releases for the js branch will be moved to "pre-releases".
They'll still be available on NPM, but might go deprecated after the release.

Roadmap

I'll try to keep this roadmap updated with time.

Setup

  • Add base files
  • Make a simple implementation of cleanPronoteUrl
  • Add an example to test the current implementation

Requests (and async)

  • Write a fetcher in JS that can be used to make requests (through WASM)
  • Write an implementation to make requests
  • Write a JS (WASM) example that works

Authentication

  • Add account types (enum)
  • Read HTML from PRONOTE page and extract session
  • Session model

feat(timetable): handle `absences` property

When we're in "Mesure conservatoire", we have those :

Image

{
  "listeAbsences": {
    "_T": 24,
    "V": []
  },
  "listeRetards": {
    "_T": 24,
    "V": []
  },
  "listePunitions": {
    "_T": 24,
    "V": []
  },
  "listeInfirmeries": {
    "_T": 24,
    "V": []
  },
  "joursCycle": {
    "_T": 24,
    "V": [
      {
        "jourCycle": 3,
        "exclusionsEtab": {
          "placeDebut": 60,
          "placeFin": 79,
          "MC": true
        }
      }
    ]
  }
}

When we're absent, it doesn't show in the UI but we can get them in the response :

{
  "listeAbsences": {
    "_T": 24,
    "V": [
      {
        "N": "1#oMFwaR64OBCud0Uho_2_nBFemERhb5tz3rJPrCbPwHA",
        "placeDebut": 60,
        "placeFin": 62,
        "motif": {
          "_T": 24,
          "V": {
            "L": "Motif non encore connu",
            "N": "92#ZMzqOMkFwUc1xfEKaOm4gvmAA4cvRlS7Oia9tH8OYNA",
            "couleur": "#FFFFFF"
          }
        }
      }
    ]
  },
  "listeRetards": {
    "_T": 24,
    "V": []
  },
  "listePunitions": {
    "_T": 24,
    "V": []
  },
  "listeInfirmeries": {
    "_T": 24,
    "V": []
  },
  "joursCycle": {
    "_T": 24,
    "V": [
      {
        "jourCycle": 3
      }
    ]
  }
}

refactor: find a lighter alternative to node-forge

Since node-forge is not tree-shakable, the whole library gets into the bundle of any user of the JS/TS library.

Sounds very sad and an ESM version of the node-forge library would be very useful there !

I'll take a look around to see if there's anything similar already done. If not, I'll try to rewrite it myself.

feat(onglets): add checks to know if onglet is enabled or not

For some people, news methods throw an error because the menu is disabled in the instance itself.

We need to implement a way to let developers check it themselves but also add checks in the methods to prevent developers calling those methods without properly checking before-hand.

add a note concerning toutatice URLs

when using the geolocation API, we might find URLs like these https://0350053t.index-education.net/ that are in fact invalid and are in reality https://0350053t.pronote.toutatice.fr/ (it's just an example, but know that there's a lot of those)

so in a real world app, you would probably need to check both URLs if the index-education.net one is failing : needs more digging though.

fix(timetable): add support for detention

Rendering in official app

image

Data from API

{
  "N": "117#sIIFtlWodcMSEoWEHNGeKY6rjkv65ywYOxrAMoy5Y7E", // id in 117 ???
  "estRetenue": "eleve", // apparently, it's not a boolean but a constant to "eleve"
  "numeroSemaine": 37, // just like the activities
  "ListeContenus": {
      "_T": 24,
      "V": [
          {
              "L": "Rattrapage de devoir de CC 12h55 - 13h55",
              "estHoraire": true // this is used to define the title
          },
          {
              "G": 34,
              "L": "Assistant d'éducation" // personal and/or teacher
          },
          {
              "G": 17,
              "L": "E 402bis" // room
          }
      ]
  },
  "place": 62,
  "duree": 2,
  "DateDuCours": {
      "_T": 7,
      "V": "15/05/2024 12:55:00"
  },
  "imgRealise": "RealiseProgramme", // random constant ??
  "hintRealise": "Programmée"       // another random constant ??
}

feat: add notifications handler

Create a handler in the client to access data from this menu.

image

Related PR on pronotepy : bain3/pronotepy#288


request

{
  "session": 1261072,
  "numeroOrdre": "c0169622e638d173cc1245a582ae5a4f",
  "nom": "CentraleNotifications",
  "donneesSec": {
    "_Signature_": {
      "onglet": 7 // current active onglet, could be any 
    }
  }
}

response data

{
  "donnees": {
    "liste": {
      "_T": 24,
      "V": [
        {
          "L": "Sécurité",
          "ident": "secSecurite",
          "liste": {
            "_T": 24,
            "V": [
              {
                "type": 1,
                "action": 0,
                "date": {
                  "_T": 7,
                  "V": "04/07/2024 22:41:03"
                },
                "id": "not_app_5_FA2949486DC7B572D796718B89343531",
                "titre": {
                  "_T": 21,
                  "V": "Opérations sur votre compte PRONOTE"
                },
                "message": {
                  "_T": 21,
                  "V": "Le 04/07/2024 à 22:41, suite à une modification de la politique de sécurité de l'établissement par votre administrateur, la politique de sécurité de votre compte a été modifiée.<br />  - personnalisation de votre mot de passe"
                },
                "compteur": 1,
                "modalites": {
                  "_T": 26,
                  "V": "[0]"
                },
                "dateExpiration": {
                  "_T": 7,
                  "V": "11/07/2024 22:41:03"
                },
                "onBtnClick": true
              }
            ]
          }
        }
      ]
    },
    "nbNotifs": 1
  },
  "nom": "CentraleNotifications"
}

javascript builder

_construireListeNotifications(aSection, aIndexSection, aAfficherHistorique) {
  const H = [];
  aSection.liste.parcourir((aNotification,aIndexNotif)=>{
    const lEstHistorique = aNotification.modalites.contains(TypeCentraleNotifications_1.TypeModaliteAffichage.affHistorique);
    if ((aAfficherHistorique && !lEstHistorique) || (!aAfficherHistorique && lEstHistorique)) {
      return;
    }
    let lBoutonCoin = '';
    let lAvecActionArticle = false;
    switch (aNotification.action) {
    case TypeCentraleNotifications_1.TypeActionBoutonNotif.abnLien:
      lBoutonCoin = '<i class="sc_btnCoin icon_affichage_widget"></i>';
      lAvecActionArticle = true;
      break;
    case TypeCentraleNotifications_1.TypeActionBoutonNotif.abnLu:
      if (!aAfficherHistorique) {
        lBoutonCoin = ['<ie-btnimage class="sc_btnCoin btnImageIcon icon_eye_close"', ' title="', ObjetTraduction_1.GTraductions.getValeur('CentraleNotifications.MarquerLu'), '"', ObjetHtml_1.GHtml.composeAttr('ie-model', 'btnNotifCoin', [aIndexSection, aIndexNotif]), '></ie-btnimage>'].join('');
      }
      break;
    case TypeCentraleNotifications_1.TypeActionBoutonNotif.abnFermer:
      lBoutonCoin = ['<ie-btnimage class="sc_btnCoin btnImageIcon icon_fermeture_widget"', ' title="', ObjetTraduction_1.GTraductions.getValeur('Fermer'), '"', ObjetHtml_1.GHtml.composeAttr('ie-model', 'btnNotifCoin', [aIndexSection, aIndexNotif]), '></ie-btnimage>'].join('');
      break;
    }
    const lClasses = [];
    if (lAvecActionArticle) {
      lClasses.push('sc_article_action', 'ie-ripple');
    }
    if (aAfficherHistorique) {
      lClasses.push('sc_article_histo');
    }
    H.push('<article tabindex="0"', (lClasses.length > 0 ? ' class="' + lClasses.join(' ') + '"' : ''), (lAvecActionArticle ? ObjetHtml_1.GHtml.composeAttr('ie-node', 'getNodeNotif', [aIndexSection, aIndexNotif, aNotification.id]) : ''), '>', lBoutonCoin, '<div class="sc_article_gauche">', _contruireEtiquetteArticle(aNotification), '</div>', '<div class="sc_article_contenu">', aNotification.titre ? '<div class="sc_article_contenu_titre' + (lBoutonCoin ? ' sc_article_contenu_titre_avecBtn' : '') + '">' + aNotification.titre + '</div>' : '', '<div ie-node="getNodeMessage">', aNotification.message, '</div>', aNotification.dateLue ? _construireDateLue(aNotification) : '', aNotification.dateExpiration && lEstHistorique ? _construireDateExpiration(aNotification) : '', '</div>', '</article>');
  }
  );
  return H.join('');
}

enums

;IE.fModule({
  f: function(exports, require, module, global) {
    "use strict";
    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    exports.TypeModaliteAffichage = exports.TypeActionBoutonNotif = exports.TypeNotification = void 0;
    var TypeNotification;
    (function(TypeNotification) {
      TypeNotification[TypeNotification["nOrpheline"] = 0] = "nOrpheline";
      TypeNotification[TypeNotification["nUnique"] = 1] = "nUnique";
      TypeNotification[TypeNotification["nCompteur"] = 2] = "nCompteur";
      TypeNotification[TypeNotification["nRouleau"] = 3] = "nRouleau";
    }
    )(TypeNotification || (exports.TypeNotification = TypeNotification = {}));
    var TypeActionBoutonNotif;
    (function(TypeActionBoutonNotif) {
      TypeActionBoutonNotif[TypeActionBoutonNotif["abnLu"] = 0] = "abnLu";
      TypeActionBoutonNotif[TypeActionBoutonNotif["abnFermer"] = 1] = "abnFermer";
      TypeActionBoutonNotif[TypeActionBoutonNotif["abnLien"] = 2] = "abnLien";
      TypeActionBoutonNotif[TypeActionBoutonNotif["abnNoAction"] = 3] = "abnNoAction";
    }
    )(TypeActionBoutonNotif || (exports.TypeActionBoutonNotif = TypeActionBoutonNotif = {}));
    var TypeModaliteAffichage;
    (function(TypeModaliteAffichage) {
      TypeModaliteAffichage[TypeModaliteAffichage["affZone"] = 0] = "affZone";
      TypeModaliteAffichage[TypeModaliteAffichage["affHistorique"] = 1] = "affHistorique";
      TypeModaliteAffichage[TypeModaliteAffichage["affModale"] = 2] = "affModale";
      TypeModaliteAffichage[TypeModaliteAffichage["affPopup"] = 3] = "affPopup";
    }
    )(TypeModaliteAffichage || (exports.TypeModaliteAffichage = TypeModaliteAffichage = {}));
  },
  fn: 'typecentralenotifications.js'
});

feat(discussions): able to delete definitively a draft

image

Only available when a draft is done and content from draft is not empty.

{
  "session": 9108278,
  "numeroOrdre": "2d3d7c0864732b551f3406d08a33f80e",
  "nom": "SaisieMessage",
  "donneesSec": {
    "_Signature_": {
      "onglet": 131
    },
    "donnees": {
      "commande": "suppression",
      "listePossessionsMessages": [
        {
          "N": "115#j6XPhjDE-z31zl-FSrGKDwLnhlnZrfj6YbwkHQpwJlQ",
          "E": 2 // 2 = MODIFICATION
        }
      ]
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.