├── .babelrc ├── .github └── workflows │ ├── deploy_docs.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── assets └── logo.svg ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── contributing │ │ ├── _category_.json │ │ ├── docs.md │ │ ├── general.md │ │ └── sdk.md │ ├── example_projects.md │ ├── getting-started │ │ ├── _category_.json │ │ ├── installation.md │ │ └── usage.md │ ├── methods │ │ ├── _category_.json │ │ ├── get_attendance.md │ │ ├── get_exams.md │ │ ├── get_grades.md │ │ ├── get_homework.md │ │ ├── get_lessons.md │ │ ├── get_lucky_number.md │ │ ├── get_message_boxes.md │ │ ├── get_messages.md │ │ ├── get_students.md │ │ └── select_student.md │ └── models │ │ ├── _category_.json │ │ ├── address.md │ │ ├── attendance.md │ │ ├── date_time.md │ │ ├── exam.md │ │ ├── grade.md │ │ ├── grade_category.md │ │ ├── grade_column.md │ │ ├── homework.md │ │ ├── lesson.md │ │ ├── lesson_room.md │ │ ├── lucky_number.md │ │ ├── message.md │ │ ├── message_box.md │ │ ├── period.md │ │ ├── presence_type.md │ │ ├── pupil.md │ │ ├── room.md │ │ ├── school.md │ │ ├── student.md │ │ ├── subject.md │ │ ├── teacher.md │ │ ├── team_class.md │ │ ├── team_virtual.md │ │ ├── time_slot.md │ │ └── unit.md ├── docusaurus.config.ts ├── package.json ├── sidebars.ts ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ └── img │ │ ├── docusaurus-social-card.jpg │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg └── tsconfig.json ├── package.json ├── rollup.config.mjs ├── src ├── api.ts ├── apiHelper.ts ├── endpoints.ts ├── index.ts ├── keystore.ts ├── models │ ├── account.ts │ ├── address.ts │ ├── attachment.ts │ ├── attendance.ts │ ├── changedLesson.ts │ ├── dateTime.ts │ ├── exam.ts │ ├── grade.ts │ ├── gradeCategory.ts │ ├── gradeColumn.ts │ ├── homework.ts │ ├── index.ts │ ├── lesson.ts │ ├── lessonChanges.ts │ ├── lessonRoom.ts │ ├── luckyNumber.ts │ ├── message.ts │ ├── messageBox.ts │ ├── period.ts │ ├── presenceType.ts │ ├── pupil.ts │ ├── school.ts │ ├── student.ts │ ├── subject.ts │ ├── teacher.ts │ ├── teamClass.ts │ ├── teamVirtual.ts │ ├── timeSlot.ts │ └── unit.ts ├── serialize.ts ├── signer.ts └── utils.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { "targets": { "node": "12" } }], 4 | ["@babel/typescript"] 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-transform-runtime", 8 | "@babel/proposal-object-rest-spread", 9 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 10 | ["@babel/proposal-class-properties", { "loose": true }], 11 | "babel-plugin-transform-typescript-metadata", 12 | ["@babel/plugin-transform-private-property-in-object", { "loose": true }], 13 | ["@babel/plugin-transform-private-methods", { "loose": true }] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/deploy_docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [19.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - name: Install dependencies 23 | run: yarn install 24 | working-directory: ./docs 25 | - name: Build 26 | run: yarn build 27 | working-directory: ./docs 28 | - name: Deploy to GitHub Pages 29 | uses: JamesIves/github-pages-deploy-action@v4.6.0 30 | with: 31 | branch: gh-pages 32 | folder: ./docs/build -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [18.x] 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | token: ${{ secrets.AVORTYBOT_TOKEN }} 16 | - name: Setup Node 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | registry-url: 'https://registry.npmjs.org' 21 | - name: Install dependencies and build 22 | run: yarn && yarn prepublish 23 | - name: Extract tag version 24 | id: extract_tag 25 | run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} 26 | - name: Update version in package.json 27 | run: | 28 | npm --no-git-tag-version version ${{ steps.extract_tag.outputs.VERSION }} 29 | git config --global user.email "contact.avorty@gmail.com" 30 | git config --global user.name "AvortyBot" 31 | git commit -am "chore: bump version to ${{ steps.extract_tag.outputs.VERSION }}" 32 | git push origin HEAD:main 33 | - name: Publish to npm 34 | run: npm publish --access public 35 | env: 36 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | push: 5 | branches: ["main"] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [18.x] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Setup Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install dependencies and run checks 20 | run: yarn 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Babel output 107 | lib/ 108 | 109 | #Auto-gen files for development 110 | package-lock.json 111 | yarn.lock 112 | 113 | # File for testing in development 114 | dev.js 115 | dev.ts 116 | account.json 117 | keystore.json 118 | 119 | # Other package managers 120 | pnpm-lock.yaml 121 | 122 | # IDE's 123 | .idea/ 124 | .vscode/ 125 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | /docs/ 3 | /src/ 4 | /test/ 5 | .env 6 | dev.js 7 | tsconfig.json 8 | /assets 9 | keystore.json 10 | account.json 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Capure 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # Vulcan Api JS 4 | 5 | Unoffical Vulcan UONET+ SDK for JavaScript / TypeScript 6 | 7 | [![Latest Stable Version](https://img.shields.io/npm/v/vulcan-api-js.svg?style=for-the-badge)](https://www.npmjs.com/package/vulcan-api-js) 8 | 9 | ## Description 10 | 11 | This project is loosely based on [Vulcan API](https://github.com/kapi2289/vulcan-api). 12 | 13 | ## Docs 14 | 15 | You can find the docs [here](https://vulcan-api.github.io/vulcan-api-js) 16 | 17 | ## License 18 | 19 | [MIT](https://github.com/Capure/vulcan-api-js/blob/master/LICENSE) 20 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # Other package managers 23 | pnpm-lock.yaml 24 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/contributing/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Contributing", 3 | "position": 5, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Some details on how to contribute to the project." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/contributing/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Contributing to the docs 6 | 7 | On every page you can find a link to the source file of the page. You can edit the file and create a pull request. 8 | 9 | ## Docusaurus 10 | 11 | Our documentation uses Docusaurus. If you want to learn about Markdown or special features of Docusaurus you can look at the [Docusaurus documentation](https://docusaurus.io/docs). -------------------------------------------------------------------------------- /docs/docs/contributing/general.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # General information 6 | 7 | Contributions are welcome! We are happy to accept contributions in the form of pull requests, issues, or just general feedback and discussion. 8 | 9 | ## Contributing to the SDK 10 | 11 | See [Contributing to the SDK](./sdk). 12 | 13 | ## Contributing to the documentation 14 | 15 | See [Contributing to the documentation](./docs). 16 | 17 | If you have any questions feel free to contact us. -------------------------------------------------------------------------------- /docs/docs/contributing/sdk.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Contributing to the SDK 6 | 7 | You can look on the [list of issues](https://github.com/avorty/vulcan-api-js/issues) and pick one to work on. If you have an idea for a new feature, you can create an issue and discuss it with us. 8 | 9 | ## Running SDK in the development mode 10 | 11 | We use yarn as a package manager. 12 | 13 | 1. First install the dependencies 14 | 15 | ```bash 16 | yarn install 17 | ``` 18 | 19 | 2. Then run the SDK in development mode 20 | 21 | ```bash 22 | yarn dev 23 | ``` -------------------------------------------------------------------------------- /docs/docs/example_projects.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Example projects 6 | 7 | Here is a list of example projects that use our SDK. 8 | 9 | ## BasedBook 10 | 11 | BasedBook is a web application created by [Avorty](https://github.com/avorty) and it's a great example of how to use our SDK with Nest.js. See [backend repository](https://github.com/avorty/basedbook-backend). -------------------------------------------------------------------------------- /docs/docs/getting-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting started", 3 | "position": 1, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "How to get started with the SDK" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Installation 6 | 7 | NPM 8 | ```bash 9 | npm install vulcan-api-js 10 | ``` 11 | 12 | YARN 13 | ```bash 14 | yarn add vulcan-api-js 15 | ``` 16 | 17 | PNPM 18 | ```bash 19 | pnpm add vulcan-api-js 20 | ``` 21 | 22 | :::info 23 | TypeScript typings are included. 24 | ::: 25 | 26 | :::info 27 | React Native is now supported, but you must install the react-native-get-random-values npm module and import it before the vulcan-api-js module. 28 | ::: 29 | -------------------------------------------------------------------------------- /docs/docs/getting-started/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Usage 6 | 7 | First, you need to create a keystore, this is where your certificate is stored. 8 | 9 | You will have to save it somewhere as that's what gets registered in Vulcan. 10 | 11 | ```js 12 | const {Keystore} = require('vulcan-api-js'); 13 | const fs = require('fs'); 14 | 15 | const main = async () => { 16 | const keystore = new Keystore(); 17 | await keystore.init(); 18 | 19 | fs.writeFileSync("keystore.json", keystore.dumpToJsonString(), { encoding: 'utf-8' }); 20 | }; 21 | 22 | main(); 23 | ``` 24 | 25 | Then you will have to register the account. 26 | 27 | ```js 28 | const {Keystore, AccountTools, registerAccount} = require('vulcan-api-js'); 29 | const fs = require('fs'); 30 | 31 | const main = async () => { 32 | const keystore = new Keystore(); 33 | keystore.loadFromJsonString(fs.readFileSync("keystore.json", { encoding: 'utf-8' })); 34 | 35 | const account = await registerAccount(keystore, {TOKEN}, {SYMBOL}, {PIN}); 36 | fs.writeFileSync("account.json", AccountTools.dumpToJsonString(account), { encoding: 'utf-8' }); 37 | }; 38 | 39 | main(); 40 | ``` 41 | 42 | When you have your keystore and account generated, you can load them and use the SDK. 43 | 44 | ```js 45 | const {Keystore, AccountTools, VulcanHebe} = require('vulcan-api-js'); 46 | 47 | const main = async () => { 48 | const keystore = new Keystore(); 49 | keystore.loadFromJsonString(fs.readFileSync("keystore.json", { encoding: 'utf-8' })); 50 | 51 | const client = new VulcanHebe(keystore, AccountTools.loadFromJsonString(fs.readFileSync("account.json", { encoding: 'utf-8' }))); 52 | 53 | // Pick your student (defaults to the first one) 54 | await client.selectStudent(); 55 | 56 | // You can use the SDK here 57 | }; 58 | 59 | main(); 60 | ``` 61 | 62 | :::info 63 | All the methods in the Vulcan API JS are async. 64 | ::: -------------------------------------------------------------------------------- /docs/docs/methods/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Methods", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "All methods" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/methods/get_attendance.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 9 3 | --- 4 | 5 | # getAttendance 6 | 7 | Gets attendace from a date range. 8 | 9 | ## Arguments 10 | 11 | - (from) - [DateTime](../models/date_time) 12 | - (to) - [DateTime](../models/date_time) 13 | 14 | ## Returns 15 | 16 | This method will return an array of [Attendance](../models/attendance) objects with all attendance from the date range. 17 | 18 | ## Example 19 | 20 | ```js 21 | client.getAttendance({from, to}).then(attendance => { 22 | attendance.forEach(attendance => { 23 | // attendance 24 | }) 25 | }) 26 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_exams.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | --- 4 | 5 | # getExams 6 | 7 | Gets all exams since the last sync. 8 | 9 | ## Arguments 10 | 11 | - (lastSync?) - Date 12 | 13 | ## Returns 14 | 15 | This method will return an array of [Exam](../models/exam) objects with all exams since the last sync. 16 | 17 | ## Example 18 | 19 | ```js 20 | client.getExams({lastSync}).then(exams => { 21 | exams.forEach(exam => { 22 | // exams 23 | }) 24 | }) 25 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_grades.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # getGrades 6 | 7 | Gets all grades since the last sync. 8 | 9 | ## Arguments 10 | - (lastSync?) - Date 11 | 12 | ## Returns 13 | 14 | This method will return an array of [Grade](../models/grade) objects with all grades since the last sync. 15 | 16 | ## Example 17 | 18 | ```js 19 | client.getGrades({lastSync}).then(grades => { 20 | grades.forEach(grade => { 21 | // grades 22 | }) 23 | }) 24 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_homework.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | --- 4 | 5 | # getHomework 6 | 7 | Gets all homework. 8 | 9 | ## Returns 10 | 11 | This method will return an array of [Homework](../models/homework) objects with all homework. 12 | 13 | ## Example 14 | 15 | ```js 16 | client.getHomework().then(homework => { 17 | homework.forEach(homework => { 18 | // homework 19 | }) 20 | }) 21 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_lessons.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # getLessons 6 | 7 | Gets a list of lessons. 8 | 9 | ## Arguments 10 | - (dateFrom?) - Date 11 | - (dateTo?) - Date 12 | 13 | If the dates aren't provided defaults to today. 14 | 15 | ## Returns 16 | 17 | An array of [Lesson](../models/Lesson) objects 18 | 19 | ## Example 20 | 21 | ```js 22 | client.getLessons({dateFrom?}, {dateTo?}).then(lessons => { 23 | lessons.forEach(lesson => { 24 | // lessons 25 | }) 26 | }) 27 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_lucky_number.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # getLuckyNumber 6 | 7 | Gets the lucky number for today. 8 | 9 | ## Returns 10 | 11 | An [LuckyNumber](../models/lucky_number) object. 12 | 13 | ## Example 14 | 15 | ```js 16 | client.getLuckyNumber().then(luckyNumber => { 17 | // luckyNumber 18 | }) 19 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_message_boxes.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # getMessageBoxes 6 | 7 | Gets all message boxes. 8 | 9 | ## Returns 10 | 11 | This method will return an array of [MessageBox](../models/message_box) objects with all message boxes. 12 | 13 | ## Example 14 | 15 | ```js 16 | client.getMessageBoxes().then(messageBoxes => { 17 | messageBoxes.forEach(messageBox => { 18 | // messageBoxes 19 | }) 20 | }) 21 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_messages.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # getMessages 6 | 7 | Gets all messages. 8 | 9 | ## Arguments 10 | - (messageBox) - [MessageBox](../models/message_box) 11 | 12 | ## Returns 13 | 14 | This method will return an array of [Message](../models/message) objects with all messages in the message box. 15 | 16 | ## Example 17 | 18 | ```js 19 | client.getMessages({messageBox}).then(messages => { 20 | messages.forEach(message => { 21 | // messages 22 | }) 23 | }) 24 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/get_students.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # getStudents 6 | 7 | Gets a list of students registered in the account. 8 | 9 | ## Returns 10 | A list of [Student](../models/student) objects 11 | 12 | ## Example 13 | 14 | ```js 15 | client.getStudents().then(students => { 16 | students.forEach(student => { 17 | // students 18 | }) 19 | }) 20 | ``` -------------------------------------------------------------------------------- /docs/docs/methods/select_student.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # selectStudent 6 | 7 | Sets the student that you want to use to access the API. 8 | 9 | ## Arguments 10 | - (student?) - [Student](../models/student) 11 | 12 | If a student is not provided the first one gets selected. 13 | 14 | ## Example 15 | 16 | ```js 17 | client.selectStudent({student?}).then(() => { 18 | // Do something 19 | }) 20 | ``` -------------------------------------------------------------------------------- /docs/docs/models/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Models", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "How to get started with the SDK" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/models/address.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 18 3 | --- 4 | 5 | # Address 6 | 7 | An object represents receiver of message. 8 | 9 | ## Properties 10 | - (globalKey) - string 11 | - (name) - string 12 | - (hasRead) - boolean -------------------------------------------------------------------------------- /docs/docs/models/attendance.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 19 3 | --- 4 | 5 | # Attendance 6 | 7 | An object representing attendance details. 8 | 9 | ## Properties 10 | - (lessonId) - number 11 | - (id) - number 12 | - (lessonNumber) - number 13 | - (globalKey) - string 14 | - (lessonClassId) - number 15 | - (lessonClassGlobalKey) - string 16 | - (calculcatePresence) - boolean 17 | - (replacement) - boolean 18 | - (subject) - [Subject](./subject) 19 | - (topic) - string 20 | - (teacher) - [Teacher](./teacher) 21 | - (secondTeacher?) - [Teacher](./teacher) 22 | - (mainTeacher?) - [Teacher](./teacher) 23 | - (teamClass?) - [TeamClass](./team_class) 24 | - (classAlias?) - string 25 | - (date) - [DateTime](./date_time) 26 | - (time?) - [TimeSlot](./time_slot) 27 | - (dateModified?) - [DateTime](./date_time) 28 | - (auxPresenceId?) - number 29 | - (justificationStatus?) - string 30 | - (presenceType?) - [PresenceType](./presence_type) 31 | - (note?) - string 32 | - (publicResources?) - string 33 | - (remoteResources?) - string 34 | - (group?) - [TeamVirtual](./team_virtual) 35 | - (visible?) - boolean 36 | -------------------------------------------------------------------------------- /docs/docs/models/date_time.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # DateTime 6 | 7 | An object representing date and time. 8 | 9 | ## Properties 10 | - (timestamp) - number 11 | - (date) - string 12 | - (time) - string 13 | - (dateDisplay?) - string -------------------------------------------------------------------------------- /docs/docs/models/exam.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 20 3 | --- 4 | 5 | # Exam 6 | 7 | An object representing exam. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (key) - string 12 | - (type) - string 13 | - (topic) - string 14 | - (dateCreated) - [DateTime](./date_time) 15 | - (dateModified) - [DateTime](./date_time) 16 | - (deadline) - [DateTime](./date_time) 17 | - (creator) - [Teacher](./teacher) 18 | - (subject) - [Subject](./subject) 19 | - (pupilId) - number 20 | -------------------------------------------------------------------------------- /docs/docs/models/grade.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 14 3 | --- 4 | 5 | # Grade 6 | 7 | An object representing a grade. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (key) - string 12 | - (pupilId) - number 13 | - (contentRaw) - string 14 | - (content) - string 15 | - (dateCreated) - [DateTime](./date_time) 16 | - (dateModify) - [DateTime](./date_time) 17 | - (creator) - [Teacher](./teacher) 18 | - (modifier) - [Teacher](./teacher) 19 | - (column) - [GradeColumn](./grade_column) 20 | - (value?) - number 21 | - (comment?) - string 22 | - (numerator?) - string 23 | - (denominator?) - number -------------------------------------------------------------------------------- /docs/docs/models/grade_category.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 16 3 | --- 4 | 5 | # GradeCategory 6 | 7 | An object representing a grade category. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (name) - string 12 | - (code) - string 13 | -------------------------------------------------------------------------------- /docs/docs/models/grade_column.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 15 3 | --- 4 | 5 | # GradeColumn 6 | 7 | An object representing a type of grade. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (key) - string 12 | - (periodId) - number 13 | - (name) - string 14 | - (code) - string 15 | - (group) - string 16 | - (number) - number 17 | - (weight) - number 18 | - (subject) - [Subject](./subject) 19 | - (category?) - [GradeCategory](./grade_category) 20 | - (period?) - [Period](./period) -------------------------------------------------------------------------------- /docs/docs/models/homework.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 21 3 | --- 4 | 5 | # Homework 6 | 7 | An object representing homework. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (key) - string 12 | - (homeworkId) - number 13 | - (content) - string 14 | - (dateCreated) - Date 15 | - (creator) - [Teacher](./teacher) 16 | - (subject) - [Subject](./subject) 17 | - (attachments) - Attachment[] 18 | - (isAnswerRequired) - boolean 19 | - (deadline) - Date 20 | - (answerDeadline) - Date 21 | - (answerDate) - Date 22 | -------------------------------------------------------------------------------- /docs/docs/models/lesson.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 17 3 | --- 4 | 5 | # Lesson 6 | 7 | An object representing a lesson. 8 | 9 | ## Properties 10 | - (id?) - number 11 | - (date?) - [DateTime](./date_time) 12 | - (timeSlot?) - [TimeSlot](./time_slot) 13 | - (room?) - [Room](./room) 14 | - (teacherPrimary?) - [Teacher](./teacher) 15 | - (teacherSecondary?) - [Teacher](./teacher) 16 | - (subject?) - [Subject](./subject) 17 | - (event?) - string 18 | - (change?) - string 19 | - (class?) - [TeamClass](./team_class) 20 | - (pupilAlias?) - string 21 | - (distribution?) - [TeamVirtual](./team_virtual.md) 22 | - (visible?) - boolean -------------------------------------------------------------------------------- /docs/docs/models/lesson_room.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | --- 4 | 5 | # LessonRoom 6 | 7 | An object representing a classroom. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (code) - string -------------------------------------------------------------------------------- /docs/docs/models/lucky_number.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 13 3 | --- 4 | 5 | # LuckyNumber 6 | 7 | An object representing a lucky number. 8 | 9 | ## Properties 10 | - (day) - number 11 | - (number) - number -------------------------------------------------------------------------------- /docs/docs/models/message.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 22 3 | --- 4 | 5 | # Message 6 | 7 | An object representing message. 8 | 9 | ## Properties 10 | - (id) - string 11 | - (globalKey) - string 12 | - (threadKey) - string 13 | - (subject) - string 14 | - (content) - string 15 | - (sentDate) - Date 16 | - (status) - number 17 | - (sender) - string 18 | - (receivers) - [Address](./address)[] 19 | - (attachments) - Attachment[] 20 | - (readDate?) - Date 21 | -------------------------------------------------------------------------------- /docs/docs/models/message_box.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 23 3 | --- 4 | 5 | # MessageBox 6 | 7 | An object representing message box. 8 | 9 | ## Properties 10 | - (id) - string 11 | - (globalKey) - string 12 | - (name) - string -------------------------------------------------------------------------------- /docs/docs/models/period.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Period 6 | 7 | An object representing a school year period. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (level) - number 12 | - (number) - number 13 | - (current) - boolean 14 | - (last) - boolean 15 | - (start) - any 16 | - (end) - any -------------------------------------------------------------------------------- /docs/docs/models/presence_type.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 24 3 | --- 4 | 5 | # Presence type 6 | 7 | An object representing presence type. 8 | 9 | ## Properties 10 | - (id) - string 11 | - (name) - string 12 | - (symbol) - string 13 | - (category_id) - number 14 | - (category_name) - string 15 | - (position) - number 16 | - (presence) - boolean 17 | - (absence) - boolean 18 | - (exemption) - boolean 19 | - (late) - boolean 20 | - (justified) - boolean 21 | - (deleted) - boolean -------------------------------------------------------------------------------- /docs/docs/models/pupil.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Pupil 6 | 7 | An object representing a person. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (loginId) - number 12 | - (loginValue) - string 13 | - (firstName) - string 14 | - (secondName) - string 15 | - (surname) - string 16 | - (sex) - boolean: 17 | 18 | ### Sex 19 | 20 | - Male - true 21 | - Female - false 22 | -------------------------------------------------------------------------------- /docs/docs/models/room.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 25 3 | --- 4 | 5 | # Room 6 | 7 | An object representing room. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (code) - string -------------------------------------------------------------------------------- /docs/docs/models/school.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # School 6 | 7 | An object representing a single school. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (name) - string 12 | - (short) - string 13 | - (address) - string -------------------------------------------------------------------------------- /docs/docs/models/student.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Student 6 | 7 | A student object returned by the SDK. 8 | 9 | ## Properties 10 | - (symbol) - string 11 | - (symbol_code) - string 12 | - (pupil) - [Pupil](./pupil) 13 | - (unit) - [Unit](./unit) 14 | - (school) - [School](./school) 15 | - (periods) - [Period](./period)[] 16 | -------------------------------------------------------------------------------- /docs/docs/models/subject.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | --- 4 | 5 | # Subject 6 | 7 | An object representing a school subject. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (key) - string 12 | - (name) - string 13 | - (code) - string 14 | - (position) - number -------------------------------------------------------------------------------- /docs/docs/models/teacher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 9 3 | --- 4 | 5 | # Teacher 6 | 7 | An object representing a teacher. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (name) - string 12 | - (surname) - string 13 | - (displayName) - string -------------------------------------------------------------------------------- /docs/docs/models/team_class.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 11 3 | --- 4 | 5 | # Subject 6 | 7 | An object representing a school class. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (key) - string 12 | - (displayName) - string 13 | - (symbol) - string -------------------------------------------------------------------------------- /docs/docs/models/team_virtual.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 12 3 | --- 4 | 5 | # TeamVirtual 6 | 7 | An object representing a part of school class. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (key) - string 12 | - (shortcut) - string 13 | - (name) - string 14 | - (partType) - string -------------------------------------------------------------------------------- /docs/docs/models/time_slot.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # Period 6 | 7 | An object representing a lesson time. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (start) - any 12 | - (end) - any 13 | - (display) - string 14 | - (position) - number -------------------------------------------------------------------------------- /docs/docs/models/unit.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Unit 6 | 7 | An object representing a school or a group of schools. 8 | 9 | ## Properties 10 | - (id) - number 11 | - (symbol) - string 12 | - (name) - string 13 | - (short) - string 14 | - (displayName) - string 15 | - (address) - string 16 | - (restUrl) - string -------------------------------------------------------------------------------- /docs/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import {themes as prismThemes} from 'prism-react-renderer'; 2 | import type {Config} from '@docusaurus/types'; 3 | import type * as Preset from '@docusaurus/preset-classic'; 4 | 5 | const config: Config = { 6 | title: 'vulcan-api-js', 7 | tagline: 'Unoffical Vulcan UONET+ SDK for JavaScript / TypeScript', 8 | favicon: 'img/favicon.ico', 9 | url: 'https://avorty.github.io', 10 | baseUrl: '/vulcan-api-js/', 11 | 12 | // GitHub pages deployment config. 13 | // If you aren't using GitHub pages, you don't need these. 14 | organizationName: 'avorty', // Usually your GitHub org/user name. 15 | projectName: 'vulcan-api-js', // Usually your repo name. 16 | 17 | onBrokenLinks: 'throw', 18 | onBrokenMarkdownLinks: 'warn', 19 | 20 | // Even if you don't use internationalization, you can use this field to set 21 | // useful metadata like html lang. For example, if your site is Chinese, you 22 | // may want to replace "en" with "zh-Hans". 23 | i18n: { 24 | defaultLocale: 'en', 25 | locales: ['en'], 26 | }, 27 | plugins: [require.resolve('docusaurus-lunr-search')], 28 | presets: [ 29 | [ 30 | 'classic', 31 | { 32 | docs: { 33 | sidebarPath: './sidebars.ts', 34 | // Please change this to your repo. 35 | // Remove this to remove the "edit this page" links. 36 | editUrl: 37 | 'https://github.com/avorty/vulcan-api-js/tree/main/docs/', 38 | }, 39 | theme: { 40 | customCss: './src/css/custom.css', 41 | }, 42 | } satisfies Preset.Options, 43 | ], 44 | ], 45 | 46 | themeConfig: { 47 | image: 'img/docusaurus-social-card.jpg', 48 | navbar: { 49 | title: 'vulcan-api-js', 50 | logo: { 51 | alt: 'Logo', 52 | src: 'img/logo.svg', 53 | }, 54 | items: [ 55 | { 56 | type: 'docSidebar', 57 | sidebarId: 'tutorialSidebar', 58 | position: 'left', 59 | label: 'Documentation', 60 | }, 61 | { 62 | href: 'https://github.com/avorty/vulcan-api-js', 63 | label: 'GitHub', 64 | position: 'right', 65 | }, 66 | ], 67 | }, 68 | footer: { 69 | style: 'dark', 70 | copyright: `Copyright © ${new Date().getFullYear()} Avorty
Built with Docusaurus.`, 71 | }, 72 | prism: { 73 | theme: prismThemes.github, 74 | darkTheme: prismThemes.dracula, 75 | }, 76 | } satisfies Preset.ThemeConfig, 77 | }; 78 | 79 | export default config; 80 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "3.0.1", 19 | "@docusaurus/preset-classic": "3.0.1", 20 | "@mdx-js/react": "^3.0.0", 21 | "clsx": "^2.0.0", 22 | "docusaurus-lunr-search": "^3.3.1", 23 | "lunr": "^2.3.9", 24 | "prism-react-renderer": "^2.3.0", 25 | "react": "^18.0.0", 26 | "react-dom": "^18.0.0" 27 | }, 28 | "devDependencies": { 29 | "@docusaurus/module-type-aliases": "3.0.1", 30 | "@docusaurus/tsconfig": "3.0.1", 31 | "@docusaurus/types": "3.0.1", 32 | "typescript": "~5.2.2" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.5%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 3 chrome version", 42 | "last 3 firefox version", 43 | "last 5 safari version" 44 | ] 45 | }, 46 | "engines": { 47 | "node": ">=18.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | // By default, Docusaurus generates a sidebar from the docs folder structure 15 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 16 | 17 | // But you can create a sidebar manually 18 | /* 19 | tutorialSidebar: [ 20 | 'intro', 21 | 'hello', 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['tutorial-basics/create-a-document'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | export default sidebars; 32 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Heading from '@theme/Heading'; 3 | import styles from './styles.module.css'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'Easy to Use', 14 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 15 | description: ( 16 | <> 17 | Our API is simple and easy to use. You can get started in minutes. 18 | 19 | ), 20 | }, 21 | { 22 | title: 'Clean and simple documentation', 23 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 24 | description: ( 25 | <> 26 | Our documentation is clean and simple. We describe every methods and properties available in our SDK in detail. 27 | 28 | ), 29 | }, 30 | { 31 | title: 'TypeScript support', 32 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 33 | description: ( 34 | <> 35 | We support TypeScript out of the box. No need to install any additional packages. 36 | 37 | ), 38 | }, 39 | ]; 40 | 41 | function Feature({ title, Svg, description }: FeatureItem) { 42 | return ( 43 |
44 |
45 | 46 |
47 |
48 | {title} 49 |

{description}

50 |
51 |
52 | ); 53 | } 54 | 55 | export default function HomepageFeatures(): JSX.Element { 56 | return ( 57 |
58 |
59 |
60 | {FeatureList.map((props, idx) => ( 61 | 62 | ))} 63 |
64 |
65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Link from '@docusaurus/Link'; 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 4 | import Layout from '@theme/Layout'; 5 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 6 | import Heading from '@theme/Heading'; 7 | 8 | import styles from './index.module.css'; 9 | 10 | function HomepageHeader() { 11 | const { siteConfig } = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 | 16 | {siteConfig.title} 17 | 18 |

{siteConfig.tagline}

19 |
20 | 23 | Get started 24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | 31 | export default function Home(): JSX.Element { 32 | const { siteConfig } = useDocusaurusContext(); 33 | return ( 34 | 37 | 38 |
39 | 40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulcan-api/vulcan-api-js/c6659a29ead7c6adf92ccc7138d2a3fa7e455add/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulcan-api/vulcan-api-js/c6659a29ead7c6adf92ccc7138d2a3fa7e455add/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulcan-api/vulcan-api-js/c6659a29ead7c6adf92ccc7138d2a3fa7e455add/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulcan-api/vulcan-api-js/c6659a29ead7c6adf92ccc7138d2a3fa7e455add/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_mountain.svg: -------------------------------------------------------------------------------- 1 | 2 | Easy to Use 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_react.svg: -------------------------------------------------------------------------------- 1 | 2 | Powered by React 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_tree.svg: -------------------------------------------------------------------------------- 1 | 2 | Focus on What Matters 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "lib/index.js", 3 | "name": "vulcan-api-js", 4 | "version": "3.5.4", 5 | "description": "Unofficial API for UONET+ e-register from Vulcan.", 6 | "scripts": { 7 | "prepublish": "rollup -c rollup.config.mjs & yarn tsc", 8 | "type-check": "tsc" 9 | }, 10 | "repository": "https://github.com/avorty/vulcan-api-js.git", 11 | "homepage": "https://avorty.github.io/vulcan-api-js", 12 | "author": "Avorty", 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | "require": "./lib/index.cjs", 17 | "import": "./lib/index.js" 18 | }, 19 | "dependencies": { 20 | "@babel/runtime": "^7.23.6", 21 | "axios": "^1.6.2", 22 | "buffer": "^6.0.3", 23 | "cross-fetch": "^4.0.0", 24 | "moment": "^2.29.4", 25 | "node-forge": "^1.3.1", 26 | "querystring": "^0.2.1", 27 | "reflect-metadata": "^0.2.1", 28 | "uuid": "^9.0.1" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.23.4", 32 | "@babel/core": "^7.23.6", 33 | "@babel/plugin-proposal-class-properties": "^7.18.6", 34 | "@babel/plugin-proposal-decorators": "^7.23.6", 35 | "@babel/plugin-proposal-object-rest-spread": "^7.20.7", 36 | "@babel/plugin-transform-runtime": "^7.23.6", 37 | "@babel/preset-env": "^7.23.6", 38 | "@babel/preset-typescript": "^7.23.3", 39 | "@rollup/plugin-babel": "^6.0.4", 40 | "@rollup/plugin-commonjs": "^25.0.7", 41 | "@rollup/plugin-json": "^6.1.0", 42 | "@rollup/plugin-node-resolve": "^15.2.3", 43 | "@types/dateformat": "^5.0.2", 44 | "@types/node": "^20.10.5", 45 | "@types/node-fetch": "^2.6.9", 46 | "@types/node-forge": "^1.3.10", 47 | "@types/uuid": "^9.0.7", 48 | "babel-plugin-transform-typescript-metadata": "^0.3.2", 49 | "dotenv": "^16.3.1", 50 | "rollup": "^4.9.1", 51 | "typescript": "^5.3.3", 52 | "yarn": "^1.22.21" 53 | }, 54 | "optionalDependencies": { 55 | "node-webcrypto-ossl": "^1.0.49" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import babel from '@rollup/plugin-babel'; 4 | import json from '@rollup/plugin-json'; 5 | import fs from 'fs'; 6 | 7 | const extensions = [ 8 | '.js', '.jsx', '.ts', '.tsx', 9 | ]; 10 | 11 | const packageRaw = fs.readFileSync("./package.json"); 12 | const pkg = JSON.parse(packageRaw); 13 | 14 | const name = 'RollupTypeScriptBabel'; 15 | 16 | export default { 17 | input: './src/index.ts', 18 | 19 | // Specify here external modules which you don't want to include in your bundle (for instance: 'lodash', 'moment' etc.) 20 | // https://rollupjs.org/guide/en#external-e-external 21 | external: [...Object.keys(pkg.dependencies || {})], 22 | 23 | plugins: [ 24 | // Allows node_modules resolution 25 | resolve({ extensions }), 26 | 27 | // Allow bundling cjs modules. Rollup doesn't understand cjs 28 | commonjs(), 29 | 30 | json(), 31 | // Compile TypeScript/JavaScript files 32 | babel({ extensions, include: ['src/**/*'], babelHelpers: 'runtime' }), 33 | ], 34 | 35 | output: [ 36 | { 37 | file: pkg.exports.import, 38 | format: 'es', 39 | }, 40 | { 41 | file: pkg.exports.require, 42 | format: 'cjs', 43 | }, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { Keystore } from "./keystore"; 2 | import "cross-fetch/polyfill"; 3 | import { getSignatureValues } from "./signer"; 4 | import { ApiHelper } from "./apiHelper"; 5 | import { 6 | APP_NAME, 7 | APP_OS, 8 | APP_USER_AGENT, 9 | APP_VERSION, 10 | millis, 11 | nowIso, 12 | uuid, 13 | } from "./utils"; 14 | import { Student, Account } from "./models"; 15 | 16 | export class Api { 17 | private keystore: Keystore; 18 | public account?: any; 19 | private restUrl?: any; 20 | public student?: any; 21 | public period?: any; 22 | public helper: ApiHelper; 23 | 24 | constructor(keystore: Keystore, account?: Account) { 25 | this.keystore = keystore; 26 | if (account) { 27 | this.account = account; 28 | this.restUrl = account.restUrl; 29 | } 30 | this.helper = new ApiHelper(this); 31 | } 32 | 33 | private buildPayload = (envelope: any) => ({ 34 | AppName: APP_NAME, 35 | AppVersion: APP_VERSION, 36 | CertificateId: this.keystore.fingerprint, 37 | Envelope: envelope, 38 | FirebaseToken: this.keystore.firebaseToken, 39 | API: 1, 40 | RequestId: uuid(), 41 | Timestamp: millis(), 42 | TimestampFormatted: nowIso(), 43 | }); 44 | 45 | private buildHeaders = (fullUrl: string, payload: string) => { 46 | if (!this.keystore.fingerprint || !this.keystore.privateKey) { 47 | throw Error("Keystore is not initialized!"); 48 | } 49 | const dt = new Date(); 50 | const { digest, canonicalUrl, signature } = getSignatureValues( 51 | this.keystore.fingerprint, 52 | this.keystore.privateKey, 53 | payload, 54 | fullUrl, 55 | dt.toUTCString() 56 | ); 57 | 58 | const headers: any = { 59 | "User-Agent": APP_USER_AGENT, 60 | vOS: APP_OS, 61 | vDeviceModel: this.keystore.deviceModel, 62 | vAPI: "1", 63 | vDate: dt.toUTCString(), 64 | vCanonicalUrl: canonicalUrl, 65 | Signature: signature, 66 | }; 67 | 68 | if (digest) { 69 | headers["Digest"] = digest; 70 | headers["Content-Type"] = "application/json"; 71 | } 72 | 73 | return headers; 74 | }; 75 | 76 | private request = async (method: "GET" | "POST", url: string, body?: any) => { 77 | const fullUrl = url.startsWith("http") 78 | ? url 79 | : this.restUrl 80 | ? this.restUrl + url 81 | : undefined; 82 | if (!fullUrl) { 83 | throw Error("Relative URL specified but no account loaded!"); 84 | } 85 | const payload = 86 | body && method === "POST" 87 | ? JSON.stringify(this.buildPayload(body)) 88 | : null; 89 | const headers = this.buildHeaders(fullUrl, payload === null ? "" : payload); 90 | const options: any = { 91 | headers: headers, 92 | method: method, 93 | }; 94 | if (payload !== null) { 95 | options["body"] = payload; 96 | } 97 | try { 98 | const rawRes = await fetch(fullUrl, options); 99 | const jsonRes: any = await rawRes.json(); 100 | const status = jsonRes["Status"]; 101 | const envelope = jsonRes["Envelope"]; 102 | if (status.Code !== 0) { 103 | throw Error(status["Message"]); 104 | } 105 | return envelope; 106 | } catch (e) { 107 | throw e; 108 | } 109 | }; 110 | 111 | public get = async (url: string, inQuery?: any) => { 112 | const query = inQuery 113 | ? `${(() => { 114 | let queryToReturn = ""; 115 | for (let item in inQuery) { 116 | queryToReturn += `&${item}=${encodeURIComponent(inQuery[item])}`; 117 | } 118 | return queryToReturn.substr(1); 119 | })()}` 120 | : undefined; 121 | 122 | if (query) { 123 | url += `?${query}`; 124 | } 125 | 126 | return await this.request("GET", url); 127 | }; 128 | 129 | public post = async (url: string, body: any) => 130 | await this.request("POST", url, body); 131 | 132 | public setStudent(student: Student) { 133 | this.student = student; 134 | this.restUrl = `${this.restUrl}${student.unit.symbol}/`; 135 | this.period = student.periods.filter((item) => item.current)[0]; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/apiHelper.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { Api } from "./api"; 3 | import { 4 | DATA_BY_MESSAGEBOX, 5 | DATA_BY_PERIOD, 6 | DATA_BY_PERSON, 7 | DATA_BY_PUPIL, 8 | DATA_ROOT 9 | } from "./endpoints"; 10 | import { Account } from "./models"; 11 | import { Period } from "./models"; 12 | import { Student } from "./models"; 13 | 14 | export enum FilterType { 15 | BY_PUPIL = 0, 16 | BY_PERSON = 1, 17 | BY_PERIOD = 2, 18 | BY_MESSAGEBOX = 3, 19 | } 20 | 21 | export const getEndpoint = (type: FilterType) => { 22 | switch (type) { 23 | case FilterType.BY_PUPIL: 24 | return DATA_BY_PUPIL; 25 | case FilterType.BY_PERSON: 26 | return DATA_BY_PERSON; 27 | case FilterType.BY_PERIOD: 28 | return DATA_BY_PERIOD; 29 | case FilterType.BY_MESSAGEBOX: 30 | return DATA_BY_MESSAGEBOX; 31 | default: 32 | return null; 33 | } 34 | }; 35 | 36 | export class ApiHelper { 37 | private api: Api; 38 | 39 | constructor(api: Api) { 40 | this.api = api; 41 | } 42 | 43 | public async getList( 44 | endpoint: string, 45 | deleted: boolean = false, 46 | lastSync?: Date, 47 | dateFrom?: Date, 48 | dateTo?: Date, 49 | filterType?: FilterType, 50 | messageBox?: string, 51 | folder?: number, 52 | params?: any 53 | ) { 54 | let url = ""; 55 | if (!this.api) { 56 | throw Error("You must select a student!"); 57 | } 58 | if (deleted) { 59 | throw Error("Getting deleted data IDs is not implemented yet."); 60 | } 61 | if (filterType !== undefined) { 62 | url = `${DATA_ROOT}/${endpoint}/${getEndpoint(filterType)}`; 63 | } else { 64 | url = `${DATA_ROOT}/${endpoint}`; 65 | } 66 | let query: any = {}; 67 | const account: Account = this.api.account; 68 | const student: Student = this.api.student; 69 | const period: Period = this.api.period; 70 | switch (filterType) { 71 | case FilterType.BY_PUPIL: 72 | query["unitId"] = student.unit.id; 73 | query["pupilId"] = student.pupil.id; 74 | query["periodId"] = period.id; 75 | break; 76 | case FilterType.BY_PERSON: 77 | query["loginId"] = account.loginId; 78 | break; 79 | case FilterType.BY_PERIOD: 80 | query["periodId"] = period.id; 81 | query["pupilId"] = student.pupil.id; 82 | break; 83 | case FilterType.BY_MESSAGEBOX: 84 | if(!messageBox) 85 | throw Error('No messageBox specified!'); 86 | query['box'] = messageBox; 87 | break; 88 | default: 89 | break; 90 | } 91 | if (dateFrom) { 92 | query["dateFrom"] = moment(dateFrom).format("YYYY-MM-DD"); 93 | } 94 | if (dateTo) { 95 | query["dateTo"] = moment(dateTo).format("YYYY-MM-DD"); 96 | } 97 | if (folder) { 98 | query['folder'] = folder; 99 | } 100 | query["lastId"] = "-2147483648"; // Comment from vulcan-api for python: don't ask, it's just Vulcan 101 | query["pageSize"] = 500; 102 | query["lastSyncDate"] = moment( 103 | lastSync || new Date("1970"), 104 | ).format("yyyy-mm-dd HH:MM:ss"); 105 | 106 | if (params) { 107 | query = { ...query, ...params }; 108 | } 109 | 110 | return await this.api.get(url, query); 111 | } 112 | 113 | public async getData(endpoint: string, query?: any) { 114 | const url = `${DATA_ROOT}/${endpoint}`; 115 | return await this.api.get(url, query); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/endpoints.ts: -------------------------------------------------------------------------------- 1 | export const DEVICE_REGISTER = "api/mobile/register/new"; 2 | export const STUDENT_LIST = "api/mobile/register/hebe"; 3 | 4 | export const DATA_ROOT = "api/mobile"; 5 | export const DATA_BY_PUPIL = "byPupil"; 6 | export const DATA_BY_PERSON = "byPerson"; 7 | export const DATA_BY_PERIOD = "byPeriod"; 8 | export const DATA_BY_MESSAGEBOX = "byBox"; 9 | export const DATA_DELETED = "deleted"; 10 | 11 | export const DATA_INTERNAL_TIME = "internal/time"; 12 | export const DATA_LUCKY_NUMBER = "school/lucky"; 13 | 14 | export const DATA_EXAM = "exam"; 15 | export const DATA_GRADE = "grade"; 16 | export const DATA_GRADE_SUMMARY = "grade/summary"; 17 | export const DATA_GRADE_AVERAGE = "grade/average"; 18 | export const DATA_HOMEWORK = "homework"; 19 | export const DATA_TIMETABLE = "schedule"; 20 | export const DATA_TIMETABLE_CHANGES = "schedule/changes"; 21 | 22 | export const DATA_MESSAGEBOX = "messagebox" 23 | export const DATA_MESSAGE = "messagebox/message" 24 | export const DATA_ATTENDANCE = "lesson"; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Api } from "./api"; 2 | import { 3 | DATA_ATTENDANCE, DATA_EXAM, 4 | DATA_GRADE, 5 | DATA_HOMEWORK, 6 | DATA_LUCKY_NUMBER, 7 | DATA_MESSAGE, 8 | DATA_MESSAGEBOX, 9 | DATA_TIMETABLE, 10 | DATA_TIMETABLE_CHANGES, 11 | DEVICE_REGISTER, 12 | STUDENT_LIST 13 | } from "./endpoints"; 14 | import { Keystore } from "./keystore"; 15 | import { APP_OS, getBaseUrl, uuid } from "./utils"; 16 | import { FilterType } from "./apiHelper"; 17 | import dateFormat from "dateformat"; 18 | import moment from "moment"; 19 | import { Account, ChangedLesson, Grade, Lesson, LuckyNumber, Student, MessageBox, Message, Homework, Attendance, Exam } from "./models"; 20 | 21 | export { AccountTools } from "./utils"; 22 | export * from "./keystore"; 23 | export * from "./models"; 24 | 25 | export const registerAccount = async ( 26 | keystore: Keystore, 27 | token: string, 28 | symbol: string, 29 | pin: string 30 | ): Promise => { 31 | token = token.toUpperCase(); 32 | symbol = symbol.toLowerCase(); 33 | 34 | const body = { 35 | OS: APP_OS, 36 | DeviceModel: keystore.deviceModel, 37 | Certificate: keystore.certificate, 38 | CertificateType: "X509", 39 | CertificateThumbprint: keystore.fingerprint, 40 | PIN: pin, 41 | SecurityToken: token, 42 | SelfIdentifier: uuid(keystore.fingerprint), 43 | }; 44 | 45 | const baseUrl = await getBaseUrl(token); 46 | const fullUrl = [baseUrl, symbol, DEVICE_REGISTER].join("/"); 47 | const api = new Api(keystore); 48 | const response = await api.post(fullUrl, body); 49 | if (!response.RestUrl) { 50 | response.RestUrl = `${await getBaseUrl(token)}/${symbol}/`; 51 | } 52 | 53 | return new Account().serialize(response); 54 | }; 55 | 56 | export class VulcanHebe { 57 | private readonly account: Account; 58 | private readonly api: Api; 59 | private student?: Student; 60 | constructor(keystore: Keystore, account: Account) { 61 | this.account = account; 62 | this.api = new Api(keystore, this.account); 63 | } 64 | 65 | public async getStudents() { 66 | const data: any = await this.api.get(STUDENT_LIST); 67 | const studentsToReturn: Student[] = data.map( 68 | (item: any): Student => new Student().serialize(item) 69 | ); 70 | return studentsToReturn; 71 | } 72 | 73 | public async selectStudent(studentToSet?: Student) { 74 | if (studentToSet) { 75 | this.student = studentToSet; 76 | this.api.setStudent(studentToSet); 77 | } else { 78 | const students = await this.getStudents(); 79 | if (students.length === 0) { 80 | throw new Error("Student not found!"); 81 | } else { 82 | const studentToSet = students[0]; 83 | this.student = studentToSet; 84 | this.api.setStudent(studentToSet); 85 | } 86 | } 87 | } 88 | 89 | public async getLessons(dateFrom?: Date, dateTo?: Date) { 90 | const dFrom = dateFrom ? dateFrom : new Date(); 91 | const dTo = dateTo ? dateTo : new Date(); 92 | const data = await this.api.helper.getList( 93 | DATA_TIMETABLE, 94 | false, 95 | undefined, 96 | dFrom, 97 | dTo, 98 | FilterType.BY_PUPIL 99 | ); 100 | return data.map( 101 | (item: any): Lesson => new Lesson().serialize(item) 102 | ) as Lesson[]; 103 | } 104 | 105 | public async getChangedLessons(dateFrom?: Date, dateTo?: Date) { 106 | const dFrom = dateFrom ? dateFrom : new Date(); 107 | const dTo = dateTo ? dateTo : new Date(); 108 | const data = await this.api.helper.getList( 109 | DATA_TIMETABLE_CHANGES, 110 | false, 111 | undefined, 112 | dFrom, 113 | dTo, 114 | FilterType.BY_PUPIL 115 | ); 116 | return data.map( 117 | (item: any): ChangedLesson => new ChangedLesson().serialize(item) 118 | ) as ChangedLesson[]; 119 | } 120 | 121 | public async getLuckyNumber(): Promise { 122 | const data = await this.api.helper.getData(DATA_LUCKY_NUMBER, { 123 | constituentId: this.api.student.school.id, 124 | day: moment().format("yyyy-mm-dd"), 125 | }); 126 | return new LuckyNumber().serialize(data); 127 | } 128 | 129 | private getPeriodById(periodId: number) { 130 | if (!this.student) { 131 | throw Error("Student was undefined!"); 132 | } 133 | const arr = this.student.periods.filter((period) => { 134 | return period.id === periodId; 135 | }); 136 | return arr?.length === 0 ? undefined : arr[0]; 137 | } 138 | 139 | public async getGrades(lastSync: Date) { 140 | const data = await this.api.helper.getList( 141 | DATA_GRADE, 142 | false, 143 | lastSync, 144 | undefined, 145 | undefined, 146 | FilterType.BY_PUPIL 147 | ); 148 | return (await Promise.all( 149 | data.map(async (grade: any) => 150 | new Grade( 151 | (periodId: number) => this.getPeriodById(periodId) as any 152 | ).serialize(grade) 153 | ) 154 | )) as Grade[]; 155 | } 156 | public async getMessageBoxes() { 157 | const data = await this.api.helper.getList( 158 | DATA_MESSAGEBOX, 159 | ); 160 | return (Promise.all( 161 | data.map(async (messageBox: MessageBox) => 162 | new MessageBox().serialize(messageBox) 163 | ) 164 | )); 165 | } 166 | public async getMessages(messageBox: string) { 167 | const data = await this.api.helper.getList( 168 | DATA_MESSAGE, 169 | undefined, 170 | undefined, 171 | undefined, 172 | undefined, 173 | FilterType.BY_MESSAGEBOX, 174 | messageBox, 175 | 1, 176 | ); 177 | return (Promise.all( 178 | data.map(async (message: Message) => 179 | new Message().serialize(message) 180 | ) 181 | )); 182 | } 183 | public async getHomework() { 184 | const data = await this.api.helper.getList( 185 | DATA_HOMEWORK, 186 | undefined, 187 | undefined, 188 | undefined, 189 | undefined, 190 | FilterType.BY_PUPIL 191 | ); 192 | return (Promise.all( 193 | data.map(async (homework: Homework) => 194 | new Homework().serialize(homework) 195 | ) 196 | )); 197 | } 198 | public async getAttendance(from: Date, to: Date) { 199 | const millisInOneDay = 86400000; 200 | to.setTime(to.getTime() + millisInOneDay); 201 | const data = await this.api.helper.getList( 202 | DATA_ATTENDANCE, 203 | false, 204 | undefined, 205 | from, 206 | to, 207 | FilterType.BY_PUPIL 208 | ); 209 | return (Promise.all( 210 | data.map(async (attendance: Attendance) => 211 | new Attendance().serialize(attendance) 212 | ) 213 | )); 214 | } 215 | public async getExams(lastSync?: Date): Promise { 216 | const data = await this.api.helper.getList( 217 | DATA_EXAM, 218 | false, 219 | lastSync, 220 | undefined, 221 | undefined, 222 | FilterType.BY_PUPIL 223 | ); 224 | return (Promise.all( 225 | data.map(async (exam: any) => 226 | new Exam().serialize(exam) 227 | ) 228 | )); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/keystore.ts: -------------------------------------------------------------------------------- 1 | import { defaultDeviceModel, getFirebaseToken, generateKeyPair } from "./utils"; 2 | export class Keystore { 3 | public certificate: string | undefined; 4 | public fingerprint: string | undefined; 5 | public privateKey: string | undefined; 6 | public firebaseToken: string | undefined; 7 | public deviceModel: string | undefined; 8 | 9 | public async init( 10 | deviceModel: string = defaultDeviceModel(), 11 | firebaseToken?: string 12 | ) { 13 | if (!firebaseToken) { 14 | firebaseToken = await getFirebaseToken(); 15 | } 16 | const { privateKey, certificate, fingerprint } = await generateKeyPair(); 17 | this.certificate = certificate; 18 | this.fingerprint = fingerprint; 19 | this.privateKey = privateKey; 20 | this.firebaseToken = firebaseToken; 21 | this.deviceModel = deviceModel; 22 | } 23 | 24 | /** 25 | * @deprecated since version 3.0 26 | */ 27 | public load( 28 | certificate: string, 29 | fingerprint: string, 30 | privateKey: string, 31 | firebaseToken: string, 32 | deviceModel: string 33 | ) { 34 | this.certificate = certificate; 35 | this.fingerprint = fingerprint; 36 | this.privateKey = privateKey; 37 | this.firebaseToken = firebaseToken; 38 | this.deviceModel = deviceModel; 39 | } 40 | 41 | public loadFromObject({ 42 | certificate, 43 | fingerprint, 44 | privateKey, 45 | firebaseToken, 46 | deviceModel, 47 | }: { 48 | certificate: string; 49 | fingerprint: string; 50 | privateKey: string; 51 | firebaseToken: string; 52 | deviceModel: string; 53 | }) { 54 | this.certificate = certificate; 55 | this.fingerprint = fingerprint; 56 | this.privateKey = privateKey; 57 | this.firebaseToken = firebaseToken; 58 | this.deviceModel = deviceModel; 59 | } 60 | 61 | public loadFromJsonString(jsonString: string) { 62 | this.loadFromObject(JSON.parse(jsonString)); 63 | } 64 | 65 | /** 66 | * @deprecated since version 3.2 - use `loadFromJsonString()` instead 67 | */ 68 | public async loadFromJsonFile(path: string) { 69 | throw new Error("Deprecated method. Use loadFromJsonString instead."); 70 | } 71 | 72 | public dumpToObject() { 73 | return { 74 | certificate: this.certificate, 75 | fingerprint: this.fingerprint, 76 | privateKey: this.privateKey, 77 | firebaseToken: this.firebaseToken, 78 | deviceModel: this.deviceModel, 79 | }; 80 | } 81 | 82 | public dumpToJsonString() { 83 | return JSON.stringify(this.dumpToObject()); 84 | } 85 | 86 | /** 87 | * @deprecated since version 3.2 - use `dumpToJsonString()` instead 88 | */ 89 | public async dumpToJsonFile(path: string) { 90 | throw new Error("Deprecated method. Use dumpToJsonString instead."); 91 | } 92 | 93 | /** 94 | * @deprecated since version 3.0 - use `dumpToObject()` instead 95 | */ 96 | public dump() { 97 | return this.dumpToObject(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/models/account.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Account extends Serializable { 4 | @bind("LoginId") loginId!: number; 5 | @bind("UserLogin") userLogin!: string; 6 | @bind("UserName") userName!: string; 7 | @bind("RestUrl") restUrl!: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/address.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Address extends Serializable { 4 | @bind('GlobalKey') globalKey: string = ''; 5 | @bind('Name') name: string = ''; 6 | @bind('HasRead') hasRead?: string; 7 | } -------------------------------------------------------------------------------- /src/models/attachment.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Attachment extends Serializable { 4 | @bind("Name") name!: string; 5 | @bind("Link") link!: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/attendance.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | import { Subject } from "./subject"; 3 | import { Teacher } from "./teacher"; 4 | import { TeamClass } from "./teamClass"; 5 | import { DateTime } from "./dateTime"; 6 | import { TimeSlot } from "./timeSlot"; 7 | import { PresenceType } from "./presenceType"; 8 | import { TeamVirtual } from "./teamVirtual"; 9 | 10 | export class Attendance extends Serializable { 11 | @bind('LessonId') lessonId: number = 0; 12 | @bind('Id') id: number = 0; 13 | @bind('LessonNumber') lessonNumber: number = 0; 14 | @bind('GlobalKey') globalKey: string = ''; 15 | @bind('LessonClassId') lessonClassId: number = 0; 16 | @bind('LessonClassGlobalKey') lessonClassGlobalKey: string = ''; 17 | @bind('CalculatePresence') calculcatePresence: boolean = false; 18 | @bind('Replacement') replacement: boolean = false; 19 | @bind('Subject') subject: Subject = new Subject(); 20 | @bind('Topic') topic: string = ''; 21 | @bind('TeacherPrimary') teacher: Teacher = new Teacher(); 22 | @bind('TeacherSecondary') secondTeacher?: Teacher = new Teacher(); 23 | @bind('TeacherMod') mainTeacher?: Teacher = new Teacher(); 24 | @bind('Clazz') teamClass?: TeamClass = new TeamClass(); 25 | @bind('GroupDefinition') classAlias?: string = ''; 26 | @bind('Day') date: DateTime = new DateTime(); 27 | @bind('TimeSlot') time?: TimeSlot = new TimeSlot(); 28 | @bind('DateModify') dateModified?: DateTime = new DateTime(); 29 | @bind('AuxPresenceId') auxPresenceId?: number = 0; 30 | @bind('JustificationStatus') justificationStatus?: string = ''; 31 | @bind('PresenceType') presenceType?: PresenceType; 32 | @bind('Note') note?: string; 33 | @bind('PublicResources') publicResources?: string; 34 | @bind('RemoteResources') remoteResources?: string; 35 | @bind('Distribution') group?: TeamVirtual; 36 | @bind('Visible') visible?: boolean; 37 | } -------------------------------------------------------------------------------- /src/models/changedLesson.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | import { DateTime } from "./dateTime"; 3 | import { LessonChanges } from "./lessonChanges"; 4 | import { LessonRoom } from "./lessonRoom"; 5 | import { Subject } from "./subject"; 6 | import { Teacher } from "./teacher"; 7 | import { TeamClass } from "./teamClass"; 8 | import { TeamVirtual } from "./teamVirtual"; 9 | import { TimeSlot } from "./timeSlot"; 10 | 11 | export class ChangedLesson extends Serializable { 12 | @bind("Id") id?: number; 13 | @bind("UnitId") unitId?: number; 14 | @bind("ScheduleId") scheduleId?: number; 15 | @bind("LessonDate") lessonDate?: DateTime; 16 | @bind("Note") note?: string; 17 | @bind("Reason") reason?: string; 18 | @bind("TimeSlot") time?: TimeSlot; 19 | @bind("Room") room?: LessonRoom; 20 | @bind("TeacherPrimary") teacher?: Teacher; 21 | @bind("TeacherSecondary") secondTeacher?: Teacher; 22 | @bind("Subject") subject?: Subject; 23 | @bind("Event") event?: string; 24 | @bind("Change") change?: LessonChanges; 25 | @bind("ChangeDate") changeDate?: DateTime; 26 | @bind("Clazz") class?: TeamClass; 27 | @bind("Distribution") distribution?: TeamVirtual; 28 | } 29 | -------------------------------------------------------------------------------- /src/models/dateTime.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class DateTime extends Serializable { 4 | @bind("Timestamp") timestamp!: number; 5 | @bind("Date") date!: string; 6 | @bind("DateDisplay") dateDisplay?: string; 7 | @bind("Time") time!: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/exam.ts: -------------------------------------------------------------------------------- 1 | import {bind, Serializable} from "../serialize"; 2 | import {DateTime} from "./dateTime"; 3 | import {Teacher} from "./teacher"; 4 | import {Subject} from "./subject"; 5 | 6 | export class Exam extends Serializable { 7 | @bind("Id") id: number = 0; 8 | @bind("Key") key: string = ''; 9 | @bind("Type") type: string = ''; 10 | @bind("Content") topic: string = ''; 11 | @bind("DateCreated") dateCreated: DateTime = new DateTime(); 12 | @bind("DateModify") dateModified: DateTime = new DateTime(); 13 | @bind("Deadline") deadline: DateTime = new DateTime(); 14 | @bind("Creator") creator: Teacher = new Teacher(); 15 | @bind("Subject") subject: Subject = new Subject(); 16 | @bind("PupilId") pupilId: number = 0; 17 | } -------------------------------------------------------------------------------- /src/models/grade.ts: -------------------------------------------------------------------------------- 1 | import { bind, customBind, Serializable } from "../serialize"; 2 | import { DateTime } from "./dateTime"; 3 | import { GradeColumn } from "./gradeColumn"; 4 | import { Period } from "./period"; 5 | import { Teacher } from "./teacher"; 6 | 7 | export class Grade extends Serializable { 8 | readonly periodBinder: (data: any) => Period; 9 | constructor(getPeriodById: (periodId: number) => Period) { 10 | super(); 11 | this.periodBinder = getPeriodById; 12 | } 13 | 14 | @bind("Id") id!: number; 15 | @bind("Key") key!: string; 16 | @bind("PupilId") pupilId!: number; 17 | @bind("ContentRaw") contentRaw!: string; 18 | @bind("Content") content!: string; 19 | @bind("DateCreated") dateCreated!: DateTime; 20 | @bind("DateModify") dateModify!: DateTime; 21 | @bind("Creator") creator!: Teacher; 22 | @bind("Modifier") modifier!: Teacher; 23 | @customBind("Column", (data: any, thisPass: any) => 24 | new GradeColumn(thisPass.periodBinder).serialize(data) 25 | ) 26 | column!: GradeColumn; 27 | @bind("Value") value?: number; 28 | @bind("Comment") comment?: string; 29 | @bind("Numerator") numerator?: number; 30 | @bind("Denominator") denominator?: number; 31 | } 32 | -------------------------------------------------------------------------------- /src/models/gradeCategory.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class GradeCategory extends Serializable { 4 | @bind("Id") id!: string; 5 | @bind("Name") name!: string; 6 | @bind("Code") code!: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/models/gradeColumn.ts: -------------------------------------------------------------------------------- 1 | import { bind, customBind, Serializable } from "../serialize"; 2 | import { GradeCategory } from "./gradeCategory"; 3 | import { Period } from "./period"; 4 | import { Subject } from "./subject"; 5 | 6 | export class GradeColumn extends Serializable { 7 | readonly periodBinder: (data: any) => Period; 8 | constructor(getPeriodById: (periodId: number) => Period) { 9 | super(); 10 | this.periodBinder = getPeriodById; 11 | } 12 | 13 | @bind("Id") id!: number; 14 | @bind("Key") key!: string; 15 | @bind("PeriodId") periodId!: number; 16 | @bind("Name") name!: string; 17 | @bind("Code") code!: string; 18 | @bind("Group") group!: string; 19 | @bind("Number") number!: number; 20 | @bind("Weight") weight!: number; 21 | @bind("Subject") subject!: Subject; 22 | @bind("Category") category?: GradeCategory; 23 | @customBind("PeriodId", (data: any, thisPass: any) => { 24 | return thisPass.periodBinder(data); 25 | }) 26 | period?: Period; 27 | } 28 | -------------------------------------------------------------------------------- /src/models/homework.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | import { Teacher } from "./teacher"; 3 | import { Subject } from "./subject"; 4 | import { Attachment } from "./attachment"; 5 | export class Homework extends Serializable { 6 | @bind('Id') id: number = 0; 7 | @bind('Key') key: string = ''; 8 | @bind('IdHomework') homeworkId: number = 0; 9 | @bind('Content') content: string = ''; 10 | @bind('DateCreated') dateCreated: Date = new Date(); 11 | @bind('Creator') creator: Teacher = new Teacher(); 12 | @bind('Subject') subject: Subject = new Subject(); 13 | @bind('Attachments') attachments: Attachment[] = []; 14 | @bind('IsAnswerRequired') isAnswerRequired: Subject = new Subject(); 15 | @bind('Deadline') deadline: Date = new Date(); 16 | @bind('AnswerDeadline') answerDeadline: Date = new Date(); 17 | @bind('AnswerDate') answerDate: Date = new Date(); 18 | } 19 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./account"; 2 | export * from "./address"; 3 | export * from "./attendance"; 4 | export * from "./luckyNumber"; 5 | export * from "./attachment"; 6 | export * from "./dateTime"; 7 | export * from "./exam"; 8 | export * from "./homework"; 9 | export * from "./message"; 10 | export * from "./messageBox"; 11 | export * from "./presenceType"; 12 | export * from "./timeSlot"; 13 | export * from "./teamVirtual"; 14 | export * from "./teamClass"; 15 | export * from "./unit"; 16 | export * from "./teacher"; 17 | export * from "./subject"; 18 | export * from "./pupil"; 19 | export * from "./period"; 20 | export * from "./school"; 21 | export * from "./student"; 22 | export * from "./lessonRoom"; 23 | export * from "./lessonChanges"; 24 | export * from "./lesson"; 25 | export * from "./gradeCategory"; 26 | export * from "./gradeColumn"; 27 | export * from "./grade"; 28 | export * from "./changedLesson"; 29 | -------------------------------------------------------------------------------- /src/models/lesson.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | import { DateTime } from "./dateTime"; 3 | import { LessonChanges } from "./lessonChanges"; 4 | import { LessonRoom } from "./lessonRoom"; 5 | import { Subject } from "./subject"; 6 | import { Teacher } from "./teacher"; 7 | import { TeamClass } from "./teamClass"; 8 | import { TeamVirtual } from "./teamVirtual"; 9 | import { TimeSlot } from "./timeSlot"; 10 | 11 | export class Lesson extends Serializable { 12 | @bind("Id") id?: number; 13 | @bind("Date") date?: DateTime; 14 | @bind("TimeSlot") timeSlot?: TimeSlot; 15 | @bind("Room") room?: LessonRoom; 16 | @bind("TeacherPrimary") teacherPrimary?: Teacher; 17 | @bind("TeacherSecondary") teacherSecondary?: Teacher; 18 | @bind("Subject") subject?: Subject; 19 | @bind("Event") event?: string; 20 | @bind("Change") change?: LessonChanges; 21 | @bind("Clazz") class?: TeamClass; 22 | @bind("PupilAlias") pupilAlias?: string; 23 | @bind("Distribution") distribution?: TeamVirtual; 24 | @bind("Visible") visible?: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /src/models/lessonChanges.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class LessonChanges extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Type") type!: number; 6 | @bind("Separation") separation!: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/models/lessonRoom.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class LessonRoom extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Code") code!: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/luckyNumber.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class LuckyNumber extends Serializable { 4 | @bind("Day") day!: string; 5 | @bind("Number") number!: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/message.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | import { Address } from "./address"; 3 | import { Attachment } from "./attachment"; 4 | 5 | export class Message extends Serializable { 6 | constructor() { 7 | super(); 8 | } 9 | @bind('Id') id: string = ''; 10 | @bind('GlobalKey') globalKey: string = ''; 11 | @bind('ThreadKey') threadKey: string = ''; 12 | @bind('Subject') subject: string = ''; 13 | @bind('Content') content: string = ''; 14 | @bind('DateSent') sentDate: Date = new Date(); 15 | @bind('Status') status: number = 0; 16 | @bind('Sender') sender: string = ''; 17 | @bind('Receiver') receivers: Address[] = []; 18 | @bind('Attachments') attachments: Attachment[] = []; 19 | @bind('DateRead') readDate?: Date = new Date(); 20 | } -------------------------------------------------------------------------------- /src/models/messageBox.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | export class MessageBox extends Serializable { 3 | constructor() { 4 | super(); 5 | } 6 | @bind("Id") id?: number; 7 | @bind('GlobalKey') globalKey: string = ''; 8 | @bind('Name') name: string = ''; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/period.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Period extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Level") level!: number; 6 | @bind("Number") number!: number; 7 | @bind("Current") current!: boolean; 8 | @bind("Last") last!: boolean; 9 | @bind("Start") start!: any; 10 | @bind("End") end!: any; 11 | } 12 | -------------------------------------------------------------------------------- /src/models/presenceType.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class PresenceType extends Serializable { 4 | 5 | @bind('Id') id: number = 0; 6 | @bind('Name') name: string = ''; 7 | @bind('Symbol') symbol: string = ''; 8 | @bind('CategoryId') category_id: number = 0; 9 | @bind('CategoryName') category_name: string = ''; 10 | @bind('Position') position: number = 0; 11 | @bind('Presence') presence: boolean = false; 12 | @bind('Absence') absence: boolean = false; 13 | @bind('LegalAbsence') exemption: boolean = false; 14 | @bind('Late') late: boolean = false; 15 | @bind('AbsenceJustified') justified: boolean = false; 16 | @bind('Removed') deleted: boolean = false; 17 | } -------------------------------------------------------------------------------- /src/models/pupil.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Pupil extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("LoginId") loginId!: number; 6 | @bind("LoginValue") loginValue!: string; 7 | @bind("FirstName") firstName!: string; 8 | @bind("Surname") surname!: string; 9 | @bind("Sex") sex!: boolean; 10 | @bind("SecondName") secondName?: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/models/school.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class School extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Name") name!: string; 6 | @bind("Short") short!: string; 7 | @bind("Address") address!: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/student.ts: -------------------------------------------------------------------------------- 1 | import { bind, customBind, Serializable } from "../serialize"; 2 | import { Period } from "./period"; 3 | import { Pupil } from "./pupil"; 4 | import { School } from "./school"; 5 | import { Unit } from "./unit"; 6 | 7 | const bindPeriods = (data: any[]) => { 8 | return data.map((period: any) => new Period().serialize(period)); 9 | }; 10 | 11 | export class Student extends Serializable { 12 | @bind("TopLevelPartition") symbol!: string; 13 | @bind("Partition") symbol_code!: string; 14 | @bind("Pupil") pupil!: Pupil; 15 | @bind("Unit") unit!: Unit; 16 | @bind("ConstituentUnit") school!: School; 17 | @customBind("Periods", bindPeriods) periods!: Period[]; 18 | } 19 | -------------------------------------------------------------------------------- /src/models/subject.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Subject extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Key") key!: string; 6 | @bind("Name") name!: string; 7 | @bind("Kod") code!: string; 8 | @bind("Position") position!: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/teacher.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Teacher extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Name") name!: string; 6 | @bind("Surname") surname!: string; 7 | @bind("DisplayName") displayName!: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/teamClass.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class TeamClass extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Key") key!: string; 6 | @bind("DisplayName") displayName!: string; 7 | @bind("Symbol") symbol!: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/teamVirtual.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class TeamVirtual extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Key") key!: string; 6 | @bind("Shortcut") shortcut!: string; 7 | @bind("Name") name!: string; 8 | @bind("PartType") partType!: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/timeSlot.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class TimeSlot extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Start") start!: string; 6 | @bind("End") end!: string; 7 | @bind("Display") display!: string; 8 | @bind("Position") position!: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/unit.ts: -------------------------------------------------------------------------------- 1 | import { bind, Serializable } from "../serialize"; 2 | 3 | export class Unit extends Serializable { 4 | @bind("Id") id!: number; 5 | @bind("Symbol") symbol!: string; 6 | @bind("Name") name!: string; 7 | @bind("Short") short!: string; 8 | @bind("DisplayName") displayName!: string; 9 | @bind("Address") address!: string; 10 | @bind("RestURL") restUrl!: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/serialize.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | const bindMetadataKey = Symbol("bind"); 4 | const skipBindingMetadataKey = Symbol("bindSkip"); 5 | const customBindMetadataKey = Symbol("type"); 6 | 7 | export const bind = function (bindName: string) { 8 | return Reflect.metadata(bindMetadataKey, bindName); 9 | }; 10 | 11 | export const customBind = function ( 12 | bindName: string, 13 | binder: ((data: any) => any) | ((data: any, thisPass: any) => any) 14 | ) { 15 | return Reflect.metadata(customBindMetadataKey, { bindName, binder }); 16 | }; 17 | 18 | export abstract class Serializable { 19 | public serialize(source: any) { 20 | if (source === null) { 21 | return null as any; 22 | } 23 | Object.keys(this).forEach((srcKey) => { 24 | const destKey = Reflect.getMetadata(bindMetadataKey, this, srcKey); 25 | const typeData = Reflect.getMetadata("design:type", this, srcKey); 26 | const customBind = Reflect.getMetadata( 27 | customBindMetadataKey, 28 | this, 29 | srcKey 30 | ); 31 | if (!destKey && !customBind) return; 32 | if (customBind) { 33 | this[srcKey as keyof this] = customBind.binder( 34 | source[customBind.bindName as keyof typeof source], 35 | this 36 | ); 37 | return; 38 | } 39 | const srcObj = source[destKey as keyof typeof source]; 40 | this[srcKey as keyof this] = 41 | typeData.prototype instanceof Serializable 42 | ? new (typeData as any)().serialize(srcObj) 43 | : srcObj; 44 | }); 45 | return this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/signer.ts: -------------------------------------------------------------------------------- 1 | import forge from "node-forge"; 2 | import { Buffer } from "buffer/"; 3 | 4 | function getDigest(body: any) { 5 | if (body == null) return ""; 6 | return forge.util.encode64( 7 | forge.md.sha256.create().update(body).digest().bytes() 8 | ); // base64 9 | } 10 | 11 | function getSignatureValue(values: any, pkey: any) { 12 | const messageDigest = forge.md.sha256.create(); 13 | messageDigest.update(values); 14 | const key = forge.pki 15 | .privateKeyFromPem( 16 | "-----BEGIN PRIVATE KEY-----\n" + pkey + "\n-----END PRIVATE KEY-----" 17 | ) 18 | .sign(messageDigest); 19 | return Buffer.from(forge.util.binary.raw.decode(key)).toString("base64"); 20 | } 21 | 22 | function getEncodedPath(path: any) { 23 | const url = path.match("(api/mobile/.+)"); 24 | if (url == null) 25 | throw new Error( 26 | "The URL does not seem correct (does not match `(api/mobile/.+)` regex)" 27 | ); 28 | 29 | return encodeURIComponent(url[0]).toLowerCase(); 30 | } 31 | 32 | function getHeadersList( 33 | body: any, 34 | digest: any, 35 | canonicalUrl: any, 36 | timestamp: any 37 | ) { 38 | const signData = [ 39 | ["vCanonicalUrl", canonicalUrl], 40 | body == null ? null : ["Digest", digest], 41 | ["vDate", new Date(timestamp).toUTCString()], 42 | ].filter((item) => !!item); 43 | 44 | return { 45 | headers: signData.map((item) => item![0]).join(" "), 46 | values: signData.map((item) => item![1]).join(""), 47 | }; 48 | } 49 | 50 | export interface SignatureValues { 51 | digest: string; 52 | canonicalUrl: string; 53 | signature: string; 54 | } 55 | 56 | function getSignatureValues( 57 | fingerprint: string, 58 | privateKey: string, 59 | body: string, 60 | requestPath: string, 61 | timestamp: number | string 62 | ): SignatureValues { 63 | const canonicalUrl = getEncodedPath(requestPath); 64 | const digest = getDigest(body); 65 | const { headers, values } = getHeadersList( 66 | body, 67 | digest, 68 | canonicalUrl, 69 | timestamp 70 | ); 71 | const signatureValue = getSignatureValue(values, privateKey); 72 | 73 | return { 74 | digest: `SHA-256=${digest}`, 75 | canonicalUrl: canonicalUrl, 76 | signature: `keyId="${fingerprint}",headers="${headers}",algorithm="sha256withrsa",signature=Base64(SHA256withRSA(${signatureValue}))`, 77 | }; 78 | } 79 | 80 | export { getSignatureValues }; 81 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | import forge from "node-forge"; 4 | 5 | import { v4 as uuidv4, v5 as uuidv5 } from "uuid"; 6 | import axios from "axios"; 7 | import qs from "querystring"; 8 | import { Account } from "./models"; 9 | 10 | export const now = () => { 11 | return Math.floor(Date.now() / 1000); 12 | }; 13 | export const uuid = (seed?: string) => { 14 | if (seed) { 15 | return uuidv5(seed, "6ba7b814-9dad-11d1-80b4-00c04fd430c8"); 16 | } 17 | return uuidv4(); 18 | }; 19 | export const getComponents = async () => { 20 | let r = await axios.get( 21 | "http://komponenty.vulcan.net.pl/UonetPlusMobile/RoutingRules.txt" 22 | ); 23 | const components: Array = r.data.split("\r\n"); 24 | let objToReturn: any = {}; 25 | for (let i = 0; i < components.length; i++) { 26 | objToReturn[components[i].split(",")[0]] = components[i].split(",")[1]; 27 | } 28 | return objToReturn; 29 | }; 30 | export const getFirebaseToken = async () => { 31 | const aid = "4609707972546570896:3626695765779152704"; 32 | const device = aid.split(":")[0]; 33 | const app = "pl.vulcan.uonetmobile"; 34 | const data = { 35 | sender: "987828170337", 36 | "X-scope": "*", 37 | "X-gmp_app_id": "1:987828170337:android:ac97431a0a4578c3", 38 | app: app, 39 | device: device, 40 | }; 41 | 42 | const headers = { 43 | Authorization: `AidLogin ${aid}`, 44 | "User-Agent": "Android-GCM/1.5", 45 | app: app, 46 | "Content-Type": "application/x-www-form-urlencoded", 47 | }; 48 | 49 | let r = await axios.post( 50 | "https://android.clients.google.com/c2dm/register3", 51 | qs.stringify(data), 52 | { headers: headers } 53 | ); 54 | 55 | return r.data.split("=")[1]; 56 | }; 57 | 58 | export async function getBaseUrl(token: string) { 59 | let code = token.substr(0, 3); 60 | let components = await getComponents(); 61 | if (components[code] !== undefined) { 62 | return components[code]; 63 | } else { 64 | throw "Niepoprawny token!"; 65 | } 66 | } 67 | export const APP_NAME = "DzienniczekPlus 2.0"; 68 | export const APP_VERSION = "1.4.2"; 69 | export const APP_OS = "Android"; 70 | export const APP_USER_AGENT = "Dart/2.10 (dart:io)"; 71 | 72 | export const defaultDeviceModel = () => `Vulcan API (Node ${process.version})`; 73 | 74 | export const millis = () => Date.now(); 75 | 76 | export const generateKeyPair = async () => { 77 | const addYears = (dt: Date, n: number) => 78 | new Date(dt.setFullYear(dt.getFullYear() + n)); 79 | 80 | const pki = forge.pki; 81 | 82 | const keys: any = await new Promise((resolve, reject) => { 83 | forge.pki.rsa.generateKeyPair( 84 | { bits: 2048, workers: 2 }, 85 | (err, keypair) => { 86 | if (err) { 87 | reject(err); 88 | } else { 89 | resolve(keypair); 90 | } 91 | } 92 | ); 93 | }); 94 | const cert = pki.createCertificate(); 95 | cert.publicKey = keys.publicKey; 96 | cert.privateKey = keys.privateKey; 97 | cert.serialNumber = "1"; 98 | cert.validity.notBefore = new Date(); 99 | cert.validity.notAfter = addYears(new Date(), 20); 100 | const attrs = [ 101 | { 102 | shortName: "CN", 103 | value: "APP_CERTIFICATE CA Certificate", 104 | }, 105 | ]; 106 | cert.setSubject(attrs); 107 | cert.setIssuer(attrs); 108 | cert.sign(cert.privateKey, forge.md.sha256.create()); 109 | 110 | const fHash = forge.md.sha1.create(); 111 | fHash.update(forge.asn1.toDer(pki.certificateToAsn1(cert)).getBytes()); 112 | const fingerprint = fHash.digest().toHex(); 113 | 114 | const privateKey = pki.privateKeyToAsn1(keys.privateKey); 115 | const privateKeyInfo = pki.wrapRsaPrivateKey(privateKey); 116 | const privateKeyPem = pki.privateKeyInfoToPem(privateKeyInfo); 117 | const certificate = pki 118 | .certificateToPem(cert) 119 | .replace("-----BEGIN CERTIFICATE-----", "") 120 | .replace("-----END CERTIFICATE-----", "") 121 | .replace(/\r?\n|\r/g, "") 122 | .trim(); 123 | const privateKeyToReturn = privateKeyPem 124 | .replace("-----BEGIN PRIVATE KEY-----", "") 125 | .replace("-----END PRIVATE KEY-----", "") 126 | .replace(/\r?\n|\r/g, "") 127 | .trim(); 128 | return { certificate, fingerprint, privateKey: privateKeyToReturn }; 129 | }; 130 | 131 | export const nowIso = () => moment().format('YYYY-MM-DD HH:mm:ss'); 132 | 133 | export class AccountTools { 134 | public static loadFromObject(account: { 135 | loginId: number; 136 | userLogin: string; 137 | userName: string; 138 | restUrl: string; 139 | }): Account { 140 | const accountToReturn = new Account(); 141 | accountToReturn.loginId = account.loginId; 142 | accountToReturn.userLogin = account.userLogin; 143 | accountToReturn.userName = account.userName; 144 | accountToReturn.restUrl = account.restUrl; 145 | return accountToReturn; 146 | } 147 | 148 | public static loadFromJsonString(jsonString: string) { 149 | return this.loadFromObject(JSON.parse(jsonString)); 150 | } 151 | 152 | /** 153 | * @deprecated since version 3.2 154 | */ 155 | public static async loadFromJsonFile(path: string) { 156 | throw new Error("Deprecated method. Use loadFromJsonString instead."); 157 | } 158 | 159 | public static dumpToObject(account: Account) { 160 | return { 161 | loginId: account.loginId, 162 | userLogin: account.userLogin, 163 | userName: account.userName, 164 | restUrl: account.restUrl, 165 | }; 166 | } 167 | 168 | public static dumpToJsonString(account: Account) { 169 | return JSON.stringify(this.dumpToObject(account)); 170 | } 171 | 172 | /** 173 | * @deprecated since version 3.2 174 | */ 175 | public static async dumpToJsonFile(account: Account, path: string) { 176 | throw new Error("Deprecated method. Use dumpToJsonString instead."); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true /* Generates corresponding '.d.ts' file. */, 11 | "emitDeclarationOnly": true, 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "lib" /* Redirect output structure to the directory. */, 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | /* Strict Type-Checking Options */ 24 | "strict": true /* Enable all strict type-checking options. */, 25 | "resolveJsonModule": true, 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | /* Module Resolution Options */ 38 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 39 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 40 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 41 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 42 | // "typeRoots": [], /* List of folders to include type definitions from. */ 43 | // "types": [], /* Type declaration files to be included in compilation. */ 44 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 45 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 46 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 47 | /* Source Map Options */ 48 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 49 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 50 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 51 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 52 | /* Experimental Options */ 53 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 54 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ 55 | /* Advanced Options */ 56 | // "declarationDir": "lib" /* Output directory for generated declaration files. */ 57 | }, 58 | "include": ["src"] 59 | } 60 | --------------------------------------------------------------------------------