diff --git a/404.html b/404.html new file mode 100644 index 0000000..e519c5d --- /dev/null +++ b/404.html @@ -0,0 +1 @@ +
# These are supported funding model platforms | |
custom: ["https://pay.cloudtips.ru/p/55f818b7", "https://www.tinkoff.ru/rm/ivchenko.viktor6/ylUQO36949/"] |
name: Отчет об ошибке (Bug report) about: Создайте отчет, чтобы помочь нам исправить ошибку title: '' labels: bug assignees: lentryd
Описание проблемы Кратко опишите, в чем заключается ошибка.
Ожидаемое поведение Четко укажите, какое поведение вы ожидали бы увидеть вместо ошибки.
Шаги для воспроизведения
Скриншоты/Логи Если есть возможность, приложите скриншоты или фрагменты логов, которые помогут нам разобраться в проблеме.
(пожалуйста, заполните следующую информацию):
Дополнительная информация Укажите любой другой контекст, который может быть полезен для понимания проблемы.
",11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"отчет-об-ошибке--bug-report-.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},1749:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return i}});var l=n(6252);const t=["innerHTML"];function d(s,a,n,d,e,r){return(0,l.wg)(),(0,l.iD)("div",{innerHTML:s.html},null,8,t)}var e={name:"codeql-analysis.yml",data:()=>({html:'# For most projects, this workflow file will not need changing; you simply need | |
# to commit it to your repository. | |
# | |
# You may wish to alter this file to override the set of languages analyzed, | |
# or to provide custom queries or build logic. | |
# | |
# ******** NOTE ******** | |
# We have attempted to detect the languages in your repository. Please check | |
# the `language` matrix defined below to confirm you have the correct set of | |
# supported CodeQL languages. | |
# | |
name: "CodeQL" | |
on: | |
push: | |
branches: [ main ] | |
pull_request: | |
# The branches below must be a subset of the branches above | |
branches: [ main ] | |
schedule: | |
- cron: '34 13 * * 6' | |
jobs: | |
analyze: | |
name: Analyze | |
runs-on: ubuntu-latest | |
strategy: | |
fail-fast: false | |
matrix: | |
language: [ 'javascript' ] | |
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] | |
# Learn more: | |
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v2 | |
# Initializes the CodeQL tools for scanning. | |
- name: Initialize CodeQL | |
uses: github/codeql-action/init@v1 | |
with: | |
languages: ${{ matrix.language }} | |
# If you wish to specify custom queries, you can do so here or in a config file. | |
# By default, queries listed here will override any specified in a config file. | |
# Prefix the list here with "+" to use these queries and those in the config file. | |
# queries: ./path/to/local/query, your-org/your-repo/queries@main | |
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). | |
# If this step fails, then you should remove it and run the build manually (see below) | |
- name: Autobuild | |
uses: github/codeql-action/autobuild@v1 | |
# ℹ️ Command-line programs to run using the OS shell. | |
# 📚 https://git.io/JvXDl | |
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines | |
# and modify them (or add more) to build your code if your project | |
# uses a compiled language | |
#- run: | | |
# make bootstrap | |
# make release | |
- name: Perform CodeQL Analysis | |
uses: github/codeql-action/analyze@v1 | |
name: Автоматическая публикация релизов | |
on: | |
release: | |
types: [published] | |
jobs: | |
publish: | |
name: Публикация релиза | |
runs-on: ubuntu-latest | |
steps: | |
- name: Качаем репозиторий | |
uses: actions/checkout@v2.3.4 | |
- name: Установка Node.js | |
uses: actions/setup-node@v2.4.1 | |
with: | |
node-version: 16.13.0 | |
- name: Установка зависимостей и компиляция | |
run: | | |
npm install | |
npm run build | |
- name: Публикация релиза | |
run: | | |
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} | |
npm publish --ignore-scripts | |
env: | |
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} |
name: Build Github Pages | |
on: [push] | |
jobs: | |
build-and-deploy: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Build-Vue | |
uses: lentryd/VDocs@1.0.0 | |
with: | |
username: ${{ github.repository_owner }} | |
reponame: ${{ github.event.repository.name }} | |
branch: ${{ github.ref_name }} | |
token: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged | |
gtag: G-2HEF2QY5SL |
.* | |
dist | |
!.npmignore | |
node_modules |
.* | |
src | |
docs | |
*.tgz | |
node_modules | |
tsconfig.json | |
CODE_OF_CONDUCT.md |
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our community include:
Examples of unacceptable behavior include:
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at lentryd. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
',35),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"CODE_OF_CONDUCT.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},2664:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return i}});var l=n(6252);const t=["innerHTML"];function d(s,a,n,d,e,r){return(0,l.wg)(),(0,l.iD)("div",{innerHTML:s.html},null,8,t)}var e={name:"LICENSE",data:()=>({html:'MIT License | |
Copyright (c) 2023 lentryd | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. |
NetSchoolApi - это враппер для продукта "Сетевой город. Образование", предоставляющий доступ к информации о пользователе, такой как дневник, расписание и другие данные.
Следующие инструкции помогут вам запустить проект на вашем локальном компьютере для разработки и тестирования.
Перед установкой и использованием этой библиотеки убедитесь, что у вас установлены следующие компоненты:
Вы можете установить библиотеку с помощью следующей команды:
npm i netschoolapi\n
test.js
в корне проекта.test.js
, заменив данные на ваши:const NS = require("netschoolapi").default;\nconst user = new NS({\n origin: "https://example.com/", // Origin вашего сайта\n login: "Иванов", // Ваш логин\n password: "******", // Ваш пароль\n school: "МБОУ ....", // Название вашей школы (как на сайте)\n});\n\n(async function () {\n const info = await user.info();\n console.log(info);\n})();\n
node test.js\n
Для работы этой библиотеки используются следующие зависимости:
Версии этой библиотеки управляются согласно SemVer. Список доступных версий можно найти в разделе теги.
Также посмотрите список участников, которые внесли свой вклад в проект.
Этот проект распространяется под лицензией MIT. Подробную информацию смотрите в файле LICENSE.
Для получения помощи и общения присоединяйтесь к Telegram-чату
',30),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"README.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},5883:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return i}});var l=n(6252);const t=["innerHTML"];function d(s,a,n,d,e,r){return(0,l.wg)(),(0,l.iD)("div",{innerHTML:s.html},null,8,t)}var e={name:"bun.lockb",data:()=>({html:'#!/usr/bin/env bun | |
bun-lockfile-format-v0 | |
\0\0\0ғbr�̺�O������V�%�w�22wWe�Qk\0\0\0\0\0\0J\0\0\0\0\0\0\0\b\0\0\0\0\0\0\0\b\0\0\0\0\0\0\0�\0\0\0\0\0\0\0�I\0\0\0\0\0\0\0\0\0\0\0\0\f\0\0�h\0\0\0\0\0�he\0\0\0\0\0\0�\0\0\0 | |
\0\0�=\0\0\t\0\0�boolbasedomutils3\0\0 | |
\0\0��\0\0\0\0��\0\0\0\0�entitiescss-what^\0\0\0 | |
\0\0��\0\0\f\0\0�M\0\0\0\0\0�debug\0\0\0ms\0\0\0\0\0\0"\0\0 | |
\0\0�=\0\0\0\0\0�*\0\0\0\0\0� \0\0\0 | |
\0\0�ws\0\0\0\0\0\0m\0\0 | |
\0\0�@\0\0 | |
\0\0��\0\0\0\0�tr46\0\0\0\0d\0\0\t\0\0�\f\0\0\0\v\0\0��\0\0\f\0\0�S\0\0\0\0�\b\0\0\t\0\0�c\b\0\0 | |
\0\0�mime-db\0T\b\0\0\0\0�'\t\0\0\0\0�asynckit\0\0\0\t\0\0� | |
\0\0 | |
\0\0�Z | |
\0\0\t\0\0� | |
\0\0\0\0�mylas\0\0\0globby\0\0slash\0\0\0merge2\0\0ignore\0\0T\v\0\0\t\0\0�]\f\0\0 | |
\0\0��\f\0\0\t\0\0�braces\0\0 | |
\0\0 | |
\0\0�` | |
\0\0\0\0�� | |
\0\0\t\0\0�R\f\0\0\v\0\0�is-glob\0i\0\0 | |
\0\0�B\f\0\0\0\0�fastq\0\0\0reusify\0�\0\0\0\0��\0\0\f\0\0��\0\0\0\0�2\f\0\0\0\0�dir-glob�\0\0\t\0\0�I\v\0\0\v\0\0��\t\0\0\t\0\0�chokidarfseventsreaddirp�\0\0\0\0�|\0\0\0\0�anymatch\f\0\0\0\v\0\0��\0\0\f\0\0�\f�Y�\b4�$�a�O��#�� | |
�y�&뗾0s��S�U�0a��~]��N���"����/S�>I^g胣ʇ�X�##<�\vh�i4�Т��QW���@ɯ>�dT< � | |
"�@��d_}�LoS��\x004��Ƥ��յHe��:9����m*\0�2��p�M�%�,+�ebI}��<ː�����ŗ�8֤*ދuI)J:f�ؙ��C�˘y�j�i�/Z�&�=9\to<��Q���S��lj} | |
��Gh��//��^�a��Ť��ƥnOw*�5ѯ�x~$���έe2�-��7�&�I3R���B���+&��@�J|]�=��nG�#3�`NZ��`n���\t)e������f��R���e�j1?�%|��měX�Q�U`����a������=����5hX2T���*/H��[C���w�f�.Ou5 | |
��ڄ�3�,��;UJv�J�0N�]$ET?�,�\fM�Z��)�)�M���;H^z�JK�d������s���˕�h�ܻ�!s,��p�����9nKIg��]?/�������"�"R�D��t�Њd��Viܞ��u��U뒵�/Z�&�=9\to<��Q��\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0x\0\0\0I\0\0�\0\0\0\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0\0,\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0F\0\0:\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\f\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0H\0\0D\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0D\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\b\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0@\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0@\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0J\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0,\0\x002\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0^\0\0,\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0H\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0E\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0-\0\0�\b\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0J\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0L\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x000\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x004\0\0�\b\0\0\0\0\0\0\f\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0H\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0A\0\0�\0\0\0\0\0\0\b\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0D\0\0�\0\0\0\0\0\0\v\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\b\0\0:\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0m\b\0\0=\0\0�\0\0\0\0\0\0#\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\b\0\x007\0\0�\0\0\x004\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\b\0\0F\0\0�\0\0\0\0\0\0\0\b\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x005\t\0\0D\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0y\t\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\t\0\0;\0\0�\0\0\0\b\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 | |
\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0c | |
\0\0:\0\0�\0\0\0\0\0\0\b\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0� | |
\0\0D\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0� | |
\0\x003\0\0�\0\0\0\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\v\0\x005\0\0�\v\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0]\v\0\x002\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\v\0\x004\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\v\0\x004\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\v\0\0;\0\0�\0\0\0\0\0\0\v\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0g\f\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\f\0\0:\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\f\0\x004\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0$ | |
\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0n | |
\0\0D\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0� | |
\0\0:\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0� | |
\0\0>\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x003\0\x006\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0s\0\0<\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0?\0\0�\0\0\0\0\0\0\b\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x003\0\0�\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x004\0\x006\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0j\0\0E\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0@\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 | |
\0\0F\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0P\0\0?\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0:\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 | |
\0\0>\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0H\0\0:\0\0�\t\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x008\0\0D\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\0J\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�\0\x008\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x009\0\0�\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0H\0\0A\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\b\0\0\0\b\0\0\0\0\0\0 | |
\0\0\0\0\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"\0\0\0\0\0\0\0"\0\0\0\0\0\0$\0\0\0\0\0\0&\0\0\0\0\0\0(\0\0\0\0\0\0\0(\0\0\0\0\0\0\0(\0\0\0\0\0\0)\0\0\0\0\0\0*\0\0\0\0\0\0\0*\0\0\0\0\0\0,\0\0\0\0\0\0/\0\0\0\0\0\x000\0\0\0\0\0\0\x000\0\0\0\0\0\x001\0\0\0\0\0\0\x001\0\0\0\0\0\0\x001\0\0\0\0\0\x007\0\0\0\0\0\x008\0\0\0\0\0\0\x008\0\0\0\0\0\0\x008\0\0\0\0\0\0\x008\0\0\0\0\0\0>\0\0\0\0\0\0\0>\0\0\0\0\0\0\0>\0\0\0\0\0\0\0>\0\0\0\0\0\0C\0\0\0\0\0\0E\0\0\0\0\0\0\0E\0\0\0\0\0\0F\0\0\0\0\0\0G\0\0\0\0\0\0H\0\0\0\0\0\0\0H\0\0\0\0\0\0I\0\0\0\0\0\0J\0\0\0\0\0\0\0J\0\0\0\0\0\0L\0\0\0\0\0\0M\0\0\0\0\0\0\0M\0\0\0\0\0\0O\0\0\0\0\0\0P\0\0\0\0\0\0\0P\0\0\0\0\0\0\0P\0\0\0\0\0\0Q\0\0\0\0\0\0\0Q\0\0\0\0\0\0\0Q\0\0\0\0\0\0\0Q\0\0\0\b\0\0\0Y\0\0\0\0\0\0\0Y\0\0\0\0\0\0Z\0\0\0\0\0\0[\0\0\0\0\0\0\0[\0\0\0\0\0\0]\0\0\0\0\0\0^\0\0\0\0\0\0\0\0\0\0\0\b\0\0\0\b\0\0\0\0\0\0 | |
\0\0\0\0\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"\0\0\0\0\0\0\0"\0\0\0\0\0\0$\0\0\0\0\0\0&\0\0\0\0\0\0(\0\0\0\0\0\0\0(\0\0\0\0\0\0\0(\0\0\0\0\0\0)\0\0\0\0\0\0*\0\0\0\0\0\0\0*\0\0\0\0\0\0,\0\0\0\0\0\0/\0\0\0\0\0\x000\0\0\0\0\0\0\x000\0\0\0\0\0\x001\0\0\0\0\0\0\x001\0\0\0\0\0\0\x001\0\0\0\0\0\x007\0\0\0\0\0\x008\0\0\0\0\0\0\x008\0\0\0\0\0\0\x008\0\0\0\0\0\0\x008\0\0\0\0\0\0>\0\0\0\0\0\0\0>\0\0\0\0\0\0\0>\0\0\0\0\0\0\0>\0\0\0\0\0\0C\0\0\0\0\0\0E\0\0\0\0\0\0\0E\0\0\0\0\0\0F\0\0\0\0\0\0G\0\0\0\0\0\0H\0\0\0\0\0\0\0H\0\0\0\0\0\0I\0\0\0\0\0\0J\0\0\0\0\0\0\0J\0\0\0\0\0\0L\0\0\0\0\0\0M\0\0\0\0\0\0\0M\0\0\0\0\0\0O\0\0\0\0\0\0P\0\0\0\0\0\0\0P\0\0\0\0\0\0\0P\0\0\0\0\0\0Q\0\0\0\0\0\0\0Q\0\0\0\0\0\0\0Q\0\0\0\0\0\0\0Q\0\0\0\b\0\0\0Y\0\0\0\0\0\0\0Y\0\0\0\0\0\0Z\0\0\0\0\0\0[\0\0\0\0\0\0\0[\0\0\0\0\0\0]\0\0\0\0\0\0^\0\0\0\0\0\0\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0��0�8\v�@k�9��F������z�EV��d�)�?�'�M�u�ɛ;@�tw�P�o�f�+W7J\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0�C�A����f-'�௬(C�r�- )������v2�����\t���6d�b��!�wRNX��\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0� | |
Z�E� | |
����]9캢���v��1�L2��߿�1.�f���/�=��E���f4�61\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0���f��\t�|Ɂ<��6|�f��\fb_u�K8�A��o�²�Q����e��A�U�/\0��ū#�����\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0%���(��Fϔ�(3t�h�`$����7Y�m���w\0ِ��Q�-����e��-rK��������\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0�O�^��"�\f[_�8\v�t�K��s��e�E��^���6�4�r����緿*�7��c�`�����\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0r\f%��b\b��Oz]x:1M筲rb���6,W}������O���Rw�H��M�M�Z�����2A�\0\0\0��\0\0\b\0\0\0\0\0\0\0\0\0\0\08�>����^s�7G'1�}��z��>��|�v��4#�k)��u7���">~H�&���&\0\0\0��\0\0\t\0\0\0\0\0\0\0\0\0\0\0��\0�(�����D | |
� | |
h�$�H�}'@�l��+���:��l��xS.���[P�[|Ք�\0�i��]��&\0\0\0��\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0��{מ�?��x`�_9i�W�\b��kQ���Ě�����Q^��q | |
��\fm��&2$�?��/����X\0\0\0��\0\0\v\0\0\0\0\0\0\0\0\0\0\05+�{��ld�:9�}�;����|1��Ah��i�W��:� | |
�o��#NJp��\0��Dh�G\0\0\0��\0\0\f\0\0\0\0\0\0\0\0\0\0\0����,��s2��N�Z�D�*%��BP��� S�O���C/�Z���_�nP���Kq�o":��3\0\0\0��\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0a�7+͒�b�-�D�2��J^�t�#�2=�ǃU�]����H��r7`�glq��̑Mo | |
�B���\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0�����z���"��0h�Ym]"6N�̇Y%&ֹ���avw�P����:�ófX��,\f�\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0��s��F�dB)�L)��M������ԡ� | |
h����D��v��b"�%��c�j�J�0�x<Ҁ��\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0�Ys���dm�ٯ�T %xHcήh��h%=Ʌ�p�/�V�&�������?�s�q\0�G�#��{E�dS�\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0D��SC2��4���W��g��U�\v�u�S�1?� | |
��_q�@�\v�$�?ZG�-l1d`��8\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0��T�Hyɥ��By�z��RE����D����µ����r˽~�Y�^o�l�`���b�I��M\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0W��=K_��{�X�D�GEj&��y�.��0t�ROW� T�R�8%B��)s,�0�Jh��X�\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0��z���M�A� | |
�O�D���0#�3��}�Dکg�6��i˧[#tذ=��i� L��B��~\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0�V�Y��m�.��z8�2�]O�<0���dٲz�nse����� | |
��M� | |
��>��㽠��̝8?\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0s�Q}I�u�g�>�j�\0����ݛ | |
9��R��֥k"�\t������["�ׯx��2���D��2��\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\07u����S�@��ď\th�{���&\t�������ֹ �Wc<�Xw���WW_��5|y��g`�\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0��ђ�D�@��X2+"<�kd��@Ώ��ےZ�V��]`v� | |
��m���^X�uC*�C$�1�\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0^;8˕;��w:`G����q߬�r8{�퍔�d�@��:_�Y{�K:ʃ���6������ \0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0��?��&v��la�z���QX��}KY< | |
�!3w��<A�W�Kf�����Ҭ�d�$F�Ǐ\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0یE��y���$�r3����'U&!�p��J��X�O������Ǝ���y�C�-к�G�PL\t%B�^�\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\01$�!���1��^�?C�ܟ\v@�ov���2���-�������/�\t�ԧvߕ�����I�\0\0\0��\0\0\0\0\0\0\0\0\0\0\0\0\0d6>l���4���B�=�����==�=Meao�ϾL��V�����#�#B<�pu��/�!ͧ��\0\0\0��\0\0 \0\0\0\0\0\0\0\0\0\0\0��8�^�b[�X�p������Sb���9IlC\bfQ��;@�'��lO�5e:��P-�Sx�\0\0\0��\0\0!\0\0\0\0\0\0\0\0\0\0\0x1�%����[�E^����\0+Y��/NAQ�){R���s�3��|�7g�c{��q�v\0\0\0��\0\0"\0\0\0\0\0\0\0\0\0\0\0g$����բ��K(pW���P��G�V�^r�� ��-��6h\t ���K�c���xUѧ0y71\0\0\0��\0\0#\0\0\0\0\0\0\0\0\0\0\09�8~-F�M�����?�`��Ii#ZFO��#�.|w a�+x������\bu�����\0\0\0��\0\0$\0\0\0\0\0\0\0\0\0\0\0!��(�A](�gY�mt�ј�D�qq�ƈ ���Jɩ[+�m�C"9bJ[\fE<L�~�av�*,m�?\0\0\0��\0\0%\0\0\0\0\0\0\0\0\0\0\0q��|!�'k\b+R.Ju�Th1�0�}'�c\fg�X����lW0�;t� | |
��ǶE����q�@ | |
�F�\0\0\0��\0\0&\0\0\0\0\0\0\0\0\0\0\0\t?�v��8���y&��υg�k�ŀ���8~vn���Z��KP%����~�\v�дC��\0\0\0��\0\0'\0\0\0\0\0\0\0\0\0\0\0��l�7ZТ�u��-�S/�JF��UK=�j�����oNu�D��u0P�肛BF6>v��>b��G0,\0\0\0��\0\0(\0\0\0\0\0\0\0\0\0\0\0n,� | |
R}��\v��U��(�Xa��xj\b�Ӵ"Š5e� | |
^p{Q��\t�"�'�����_0Bb�\0\0\0��\0\0)\0\0\0\0\0\0\0\0\0\0\0�h�����#*j��J}S�i�V�\0�2�z���|�k�( #�)�c�%¹�sH��EoT�O��R�\0\0\0��\0\0*\0\0\0\0\0\0\0\0\0\0\0��5����~�I+po��\ba�h�U(����Ƅ��y}-o��||�x��[k���Eu�@�̀t3"�\0\0\0��\0\0+\0\0\0\0\0\0\0\0\0\0\0��\t[�|���?/ ��C]oD��Y�(#8��t�\vN\t�W7;�OΗ3\b ���?��ђN:\0\0\0��\0\0,\0\0\0\0\0\0\0\0\0\0\0 | |
l``h�<"�|��>�L��\b:\b�|N;���zs�I�?�5�fr-Շ�Aq2>��a)���Q�\0\0\0��\0\0-\0\0\0\0\0\0\0\0\0\0\0Ƴ��V�J��Tp'�<�{��,�c\0�x�A��+�tH]H� | |
���"����F�`1�p�9\0\0\0��\0\0.\0\0\0\0\0\0\0\0\0\0\0\f̾[j�gc��cW� | |
�冔HX�;��&�ė�#��.��f��p�]���R67[`�3Ht��O�t\0\0\0��\0\0/\0\0\0\0\0\0\0\0\0\0\0%M�xt͎a6T!���<|� | |
����\b���x@X�U�h�\0��%:��D]t�m+./c� V�T\0\0\0��\0\x000\0\0\0\0\0\0\0\0\0\0\0�׀��I�C؋".Z����� | |
|��HK���x�r��?(-Ðt�ƺ��?� | |
� ;�qsPs|�q�\0\0\0��\0\x001\0\0\0\0\0\0\0\0\0\0\0b��{u�,�b.-X86'-�m��z0�w��@4q��g@Zp����\f����P��=)��*�>���\0\0\0��\0\x002\0\0\0\0\0\0\0\0\0\0\0���>��!+����1m�0�I7��t�mg�ý�ꆷI?���|z�"�4F��`����&!��\0�H�<�\0\0\0��\0\x003\0\0\0\0\0\0\0\0\0\0\0�P�~H:{��*]�:^-S*� DW4��{�D>���gg� | |
�M4��K�t | |
<9J���6��efTV�]�q�\0\0\0��\0\x004\0\0\0\0\0\0\0\0\0\0\0\0� I\0��"X���Fq���gN��slc���������d��y�l\v�gmÜ���3#9D�+�l�i/Y�\0\0\0��\0\x005\0\0\0\0\0\0\0\0\0\0\0��Rk!�ߦ`�V�X��m���͗ã����BC�]{a�7�����>�u�fd���k��RJ\0\0\0��\0\x006\0\0\0\0\0\0\0\0\0\0\0I��\0� | |
�MՋ��f�=-�I2~2\v͜(�J2+����%�[;~�~����H@IG��x�\0\0\0��\0\x007\0\0\0\0\0\0\0\0\0\0\0�`~S`Y��\f(�g��1m�R���l�Ƞ��d�ғ���˖(5!�We��Yg�zJ\0\0\0��\0\x008\0\0\0\0\0\0\0\0\0\0\0b�)Rr���(��I��R����=bX��$�����Y�\07��x6$����D=/���+&bg�7\0\0\0��\0\x009\0\0\0\0\0\0\0\0\0\0\0S���Ʒ}�ސ!u�O�?R(�+��$��o]Sb|k���ׂ��P��x�Ai���2h�S���\v��\0\0\0��\0\0:\0\0\0\0\0\0\0\0\0\0\0������Bm�(*��p����N��璝�+�u����1�o�9:4(n�`u;z�Jf=�ʦ.0��\0\0\0��\0\0;\0\0\0\0\0\0\0\0\0\0\0�^ɔ|��<YM���\0M�ЁK0����-P�wa�4K�-�Q������P���h���1�\0\0\0��\0\0<\0\0\0\0\0\0\0\0\0\0\06�I�����%��C2h�N\t�2�\b�������*�6 | |
YBp�"�d/��\f��!z=��\0\0\0��\0\0=\0\0\0\0\0\0\0\0\0\0\0FHO>�����@���g��[��wa"���x�/#�n�-�����\0�|��7)=M+�uU�'���8�\0\0\0��\0\0>\0\0\0\0\0\0\0\0\0\0\0ZJ֧ё��(f38���eb�E�\v7��q�y�����C��q,���c������p��Ѧ3s \0\0\0��\0\0?\0\0\0\0\0\0\0\0\0\0\0�2��L | |
���������B}3\t��LW��c���IӦ�2\tR���_:�,=�[Z�U=�%�3��\0\0\0��\0\0@\0\0\0\0\0\0\0\0\0\0\0l����S ���T{�\t�wx��\0��1��;��yY����y��+��xZ�����Љ�A?�Z��\0\0\0��\0\0A\0\0\0\0\0\0\0\0\0\0\0��y�Nn���y����g!L��@�%^F�.I{�e��k� | |
\bL�9Z�U��h�:6Yf�{{!�a]2$�\0\0\0��\0\0B\0\0\0\0\0\0\0\0\0\0\0��|�O�܍���I�%��2��u�1H�C���]t�RT\b�8�uP2U�1��8ӂ?d-kN�:�\0\0\0�\0\0\0C\0\0\0\0\0\0\0\0\0\0\0�*�A1�ӸnZo�\b:�2#�?痋$��� | |
�[���M(*0����i��*�5��Zo��(\0\0\0��\0\0D\0\0\0\0\0\0\0\0\0\0\0����'�n���Y߰4(�>��~�ߏy�ݩ)���I��o �\fA'j~C>�m�:=UL�\0\0\0��\0\0E\0\0\0\0\0\0\0\0\0\0\0d�a�:�<���vǻ:��;YW���G�;��1R)�6[�DO���N5��nH}�KyW3���F_\0\0\0��\0\0F\0\0\0\0\0\0\0\0\0\0\0�7-'�A�h')(xv�^��4�5�$�h����p�SI��ء�=�x^C��m>��4\b��(\0\0\0��\0\0G\0\0\0\0\0\0\0\0\0\0\0?��=�"�d�������}�?�㔊6�O�L{tc�+��i�+c�z�h��]��1�U2���oZ\0\0\0��\0\0H\0\0\0\0\0\0\0\0\0\0\0��,S�e]�\tb��S�&���&�����ڰ�;W�~NOV�� | |
��m�j���T�,��\b" \0\0\0��\0\0I\0\0\0\0\0\0\0\0\0\0\07.�gcc�m/�T����5n�~���0�j��j���kJW�ᐘ�T]/&�_I��@����Uˊ6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0bin/he\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\t\0\0��\t\0\0\0\0�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0�I\0\0\0\0\0\0LJ\0\0\0\0\0\0 | |
<src.install.lockfile.Tree> 20 sizeof, 4 alignof | |
\0\0\0\0\0\0\0\0��������\0\0\0\0G\0\0\0\0\0\0\0\0\0\0\0\0\0G\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0H\0\0\0\0\0\0\0\0\0+\0\0\0\0\0\0I\0\0\0\0\0\0\0\0\0(\0\0\0\0\0\0J\0\0\0\0\0\0xJ\0\0\0\0\0\0�K\0\0\0\0\0\0 | |
<u32> 4 sizeof, 4 alignof | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0]\0\0\x001\0\0\x002\0\0\x003\0\0\x004\0\0\x005\0\0\x006\0\0\0\0\0\0\0\0\0\0\0\0 \0\0\0\0\0\0\0\0\0\0\0\0\b\0\0\0\t\0\0\0Q\0\0\0R\0\0\0S\0\0\0T\0\0\0U\0\0\0W\0\0\0X\0\0\x008\0\0\x009\0\0\0:\0\0\0;\0\0\0<\0\0\0=\0\0\x007\0\0\0*\0\0\0$\0\0\0\0\0\0 | |
\0\0\0\v\0\0\0\f\0\0\0 | |
\0\0\0\0\0\0\0\0\0E\0\0\0Z\0\0\0I\0\0\0P\0\0\0>\0\0\0?\0\0\0B\0\0\0,\0\0\0-\0\0\0.\0\0\0&\0\0\0'\0\0\0\0\0\0\0\0\0F\0\0\0J\0\0\0K\0\0\x000\0\0\0/\0\0\0\0\0\0G\0\0\0N\0\0\0L\0\0\0O\0\0\0+\0\0\0(\0\0\0)\0\0\0)\0\0\0�K\0\0\0\0\0\0HM\0\0\0\0\0\0 | |
<u32> 4 sizeof, 4 alignof | |
\0H\0\0\0$\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\f\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\v\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\t\0\0\0\b\0\0\0\0\0\0\b\0\0\0\b\0\0\0\0\0\0 | |
\0\0\0 | |
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0��������\0\0\0����\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0#\0\0\0!\0\0\0\0\0\0 \0\0\0"\0\0\0B\0\0\0A\0\0\0)\0\0\0(\0\0\0'\0\0\0%\0\0\0&\0\0\0@\0\0\0>\0\0\0-\0\0\0,\0\0\0+\0\0\0*\0\0\0=\0\0\x007\0\0\x004\0\0\0+\0\0\0.\0\0\x000\0\0\0/\0\0\x001\0\0\x002\0\0\x003\0\0\x005\0\0\x006\0\0\0:\0\0\x008\0\0\x009\0\0\0=\0\0\0;\0\0\0<\0\0\0?\0\0\0G\0\0\x000\0\0\x004\0\0\0E\0\0\x005\0\0\0'\0\0\0D\0\0\0C\0\0\0/\0\0\0F\0\0\0'\0\0\0/\0\0\0I\0\0\0xM\0\0\0\0\0\0W\0\0\0\0\0\0 | |
<[26]u8> 26 sizeof, 1 alignof | |
\0\f\0\0\0\v\0\0��/Z�&�=9\b^22.10.1\0\0\0\t\0\0�7�&�I3R\b^1.8.10\0 \0\0\0 | |
\0\0��%�,+�eb\b^5.7.2\0\0*\0\0\0\0\0�\0�2��p�M^0.1.0\0\0=\0\0\0\0\0����m*^6.1.1\0\0M\0\0\0\0\0��d_}�LoS^7.0.5\0\0^\0\0\0 | |
\0\0�ɯ>�dT< \0\0\0\0\0\0\0\0h\0\0\0\0\0��$�a�O�^6.1.13\0�\0\0\0 | |
\0\0��&뗾^5.1.0\0\0he\0\0\0\0\0\0�#�� | |
�y1.2.0\0\0\0boolbase0a��~]��^1.0.0\0\0css-what��QW���@^6.1.0\0\x003\0\0 | |
\0\0���/S�>^5.0.2\0\0domutilsN���"��^3.0.1\0\0=\0\0\t\0\0�0s��S�U�^2.0.1\0\0boolbase0a��~]��^1.0.0\0\0�\0\0\0\0���X�##<�^2.0.0\0\0�\0\0\0\0�I^g胣�^2.3.0\0\x003\0\0 | |
\0\0���/S�>^5.0.1\0\0�\0\0\0\0�I^g胣�^2.3.0\0\0�\0\0\0\0�I^g胣�^2.3.0\0\x003\0\0 | |
\0\0���/S�>^5.0.2\0\0entities\vh�i4�Т^4.2.0\0\0�\0\0\f\0\0�� | |
"�@��|\0\0\0\0�"\0\0 | |
\0\0�e��:9�^7.0.2\0\0debug\0\0\0��\x004���4\0\0\0\0\0\0\0ms\0\0\0\0\0\0���յH^2.1.3\0\0debug\0\0\0��\x004���^4.3.4\0\0S\0\0\0\0��S��lj} | |
^2.6.11\0d\0\0\t\0\0��˘y�j�i^8.5.12\0M\0\0\0\0\0��d_}�LoS^7.0.5\0\0m\0\0 | |
\0\0�������2.x\0\0\0\0\0ws\0\0\0\0\0\0I}��<�^8.18.0\0 \0\0\0 | |
\0\0��%�,+�eb^5.0.0\0\0�\0\0 | |
\0\0�hs����i^4.0.1\0\0�\0\0\0\0��5TY��>=5.0.2\0@\0\0 | |
\0\0�ŗ�8�^5.0.0\0\0encoding���T����^0.1.0\0\0tr46\0\0\0\0:f�ؙ��C~0.0.3\0\0�\0\0\0\0��*ދuI)J^3.0.0\0\0\f\0\0\0\v\0\0��/Z�&�=9*\0\0\0\0\0\0\0�\0\0\f\0\0�\to<��Q��~6.19.2\0\b\0\0\t\0\0���Gh��/^4.0.0\0\0\f\0\0\0\v\0\0��/Z�&�=9*\0\0\0\0\0\0\0asynckitέe2�-��^0.4.0\0\0T\b\0\0\0\0�Ow*�5ѯ�^1.0.8\0\0c\b\0\0 | |
\0\0�/��^�a�^2.1.12\0mime-db\0�Ť��ƥn1.52.0\0\0'\t\0\0\0\0�x~$���~1.0.0\0\0chokidarg��]?/�^3.5.3\0\0�\t\0\0\t\0\0��9nKI^9.0.0\0\0globby\0\0��`n��^11.0.4\0mylas\0\0\0G�#3�`NZ^2.1.9\0\0 | |
\0\0\0\0�|]�=��n^3.0.0\0\0 | |
\0\0 | |
\0\0����B���+^1.2.6\0\0Z | |
\0\0\t\0\0�&��@�J^1.2.8\0\0I\v\0\0\v\0\0���p����^2.1.0\0\0dir-globs���˕�h^3.0.1\0\0T\v\0\0\t\0\0�1?�%|�^3.2.9\0\0ignore\0\0�R���e�j^5.2.0\0\0merge2\0\0����f�^1.4.1\0\0slash\0\0\0�\t)e��^3.0.0\0\x002\f\0\0\0\0��d������^2.0.2\0\0B\f\0\0\0\0��,��;UJ^1.2.3\0\0R\f\0\0\v\0\0����C���^5.1.2\0\0merge2\0\0����f�^1.3.0\0\0]\f\0\0 | |
\0\0��měX�Q�^4.0.4\0\0braces\0\0�����^3.0.2\0\0�\f\0\0\t\0\0�U`����a�^2.3.1\0\0 | |
\0\0 | |
\0\0�=����5h^7.1.1\0\0` | |
\0\0\0\0�X2T���^5.0.1\0\0� | |
\0\0\t\0\0�*/H��[�^7.0.0\0\0is-glob\0w�f�.Ou5^4.0.1\0\0i\0\0 | |
\0\0� | |
��ڄ�3^2.1.1\0\0�\0\0\0\0��\fM�Z��)2.1.5\0\0\0fastq\0\0\0v�J�0N�]^1.6.0\0\0reusify\0$ET?�,^1.0.4\0\x002\f\0\0\0\0��d������2.0.5\0\0\0�\0\0\f\0\0��)�M���^1.1.9\0\0�\0\0\0\0�;H^z�JK^1.2.2\0\0�\0\0\t\0\0��ܻ�!s,^4.0.0\0\0anymatch�u��U뒵~3.1.2\0\0braces\0\0�����~3.0.2\0\0R\f\0\0\v\0\0����C���~5.1.2\0\0�\0\0\0\0�D��t�Њ~2.1.0\0\0is-glob\0w�f�.Ou5~4.0.1\0\0 | |
\0\0\0\0�|]�=��n~3.0.0\0\0readdirp"�"R�~3.6.0\0\0fsevents������~2.3.2\0\0�\f\0\0\t\0\0�U`����a�^2.2.1\0\0|\0\0\0\0�d��Viܞ�^2.0.0\0\0 | |
\0\0\0\0�|]�=��n^3.0.0\0\0�\f\0\0\t\0\0�U`����a�^2.0.4\0\0�\0\0\f\0\0�\to<��Q��~6.20.0\0PW\0\0\0\0\0\0�W\0\0\0\0\0\0 | |
<src.install.semver.ExternalString> 16 sizeof, 8 alignof | |
\0\0tsc\0\0\0\0\0���r��bin/tsc\0ޣ<�q�tsserver]� �RfK�w\0\0\f\0\0��85�x��W\0\0\0\0\0\0Ik\0\0\0\0\0\0 | |
<u8> 1 sizeof, 1 alignof | |
\0\0\0\0\0\0netschoolapi@types/nodetsc-aliastypescript@lentryd/web-clientcompare-versionshttps-proxy-agenticonv-litenode-html-parserhttps://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgzcss-selecthttps://registry.npmjs.org/he/-/he-1.2.0.tgzhttps://registry.npmjs.org/css-select/-/css-select-5.1.0.tgzdomhandlernth-checkhttps://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgzhttps://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgzhttps://registry.npmjs.org/domutils/-/domutils-3.0.1.tgzdom-serializerdomelementtypehttps://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgzhttps://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgzhttps://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgzhttps://registry.npmjs.org/entities/-/entities-4.4.0.tgzhttps://registry.npmjs.org/css-what/-/css-what-6.1.0.tgzhttps://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz>= 2.1.2 < 3.0.0safer-bufferhttps://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgzhttps://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgzagent-basehttps://registry.npmjs.org/debug/-/debug-4.3.7.tgzhttps://registry.npmjs.org/ms/-/ms-2.1.3.tgzhttps://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgzhttps://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgzhttps://registry.npmjs.org/@lentryd/web-client/-/web-client-0.1.0.tgz@types/node-fetch@types/wsnode-fetchbin/tsserverhttps://registry.npmjs.org/typescript/-/typescript-5.7.2.tgzhttps://registry.npmjs.org/ws/-/ws-8.18.0.tgzbufferutilutf-8-validatehttps://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgzwhatwg-urlhttps://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgzwebidl-conversionshttps://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgzhttps://registry.npmjs.org/tr46/-/tr46-0.0.3.tgzhttps://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgzhttps://registry.npmjs.org/@types/node/-/node-22.5.5.tgzundici-typeshttps://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgzhttps://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgzform-datahttps://registry.npmjs.org/form-data/-/form-data-4.0.0.tgzcombined-streammime-typeshttps://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgzhttps://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgzhttps://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgzdelayed-streamhttps://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgzhttps://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgzdist/bin/index.jshttps://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.10.tgzcommandernormalize-pathplimit-lithttps://registry.npmjs.org/plimit-lit/-/plimit-lit-1.2.7.tgzqueue-lithttps://registry.npmjs.org/queue-lit/-/queue-lit-1.2.8.tgzhttps://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgzhttps://registry.npmjs.org/mylas/-/mylas-2.1.10.tgzhttps://registry.npmjs.org/globby/-/globby-11.1.0.tgzarray-unionfast-globhttps://registry.npmjs.org/slash/-/slash-3.0.0.tgzhttps://registry.npmjs.org/merge2/-/merge2-1.4.1.tgzhttps://registry.npmjs.org/ignore/-/ignore-5.2.0.tgzhttps://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz@nodelib/fs.stat@nodelib/fs.walkglob-parentmicromatchhttps://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgzpicomatchhttps://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgzhttps://registry.npmjs.org/braces/-/braces-3.0.3.tgzfill-rangehttps://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgzto-regex-rangehttps://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgzis-numberhttps://registry.npmjs.org/is-number/-/is-number-7.0.0.tgzhttps://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgzhttps://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgzis-extglobhttps://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgzhttps://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz@nodelib/fs.scandirhttps://registry.npmjs.org/fastq/-/fastq-1.13.0.tgzhttps://registry.npmjs.org/reusify/-/reusify-1.0.4.tgzhttps://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgzrun-parallelhttps://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgzqueue-microtaskhttps://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgzhttps://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgzhttps://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgzpath-typehttps://registry.npmjs.org/path-type/-/path-type-4.0.0.tgzhttps://registry.npmjs.org/array-union/-/array-union-2.1.0.tgzhttps://registry.npmjs.org/commander/-/commander-9.3.0.tgzhttps://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgzis-binary-pathhttps://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgzhttps://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgzhttps://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgzbinary-extensionshttps://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgzhttps://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgzhttps://registry.npmjs.org/@types/node/-/node-22.10.1.tgzhttps://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 |
.assignment()
Этот метод возвращает информацию об оценке. Именно благодаря этому методу можете узнать вес оценки и имя человека, который ее поставил.
Для использования этого метода нужно передать id
оценки (получить его можно при помощи метода .diary()). Если у вас есть доступ к нескольким ученикам, то также нужно передать id
нужного ученика.
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем id оценки\n //....\n\n // Получаем информацию\n const result = await user.assignment({ id });\n console.log(result);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем id оценки\n //....\n\n // Получаем информацию\n const result = await user.assignment({ id });\n console.log(result);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"assignment.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},2035:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.assignmentTypes()
Этот метод возвращает массив со всеми типами заданий, которые могут встретиться в сетевом.
Для использования этого метода не нужно передавать данные.
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем информацию\n const types = await user.assignmentTypes();\n console.log(types.findById(3));\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем информацию\n const types = await user.assignmentTypes();\n console.log(types.findById(3));\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"assignmentTypes.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},8931:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.contextAsync
Это значение позволяет получить доступ к контексту без предварительной авторизации.
Не работает при импорте класса
Safe
Представим, что мы хотим получить оценки первого предмета из доступных и чтобы не вызывать авторизацию, мы можем воспользоваться этим значением.
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем массив предметов\n const { subjects } = await user.contextAsync;\n\n // Получаем отчет "Отчет об успеваемости ученика"\n const report = await user.grades({ subjectId: subjects[0].id });\n console.log(report);\n})();\n
Без этого значения код выглядел бы следующим образом:
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Авторизуемся\n await user.logIn();\n\n // Получаем массив предметов\n const { subjects } = await user.context;\n\n // Получаем отчет "Отчет об успеваемости ученика"\n const report = await user.grades({ subjectId: subjects[0].id });\n console.log(report);\n})();\n
',10),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"contextAsync.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},8506:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.logIn()
и .logOut()
Эти методы необходимы для открытия/закрытия сессии. В основном они должен использоваться в классе Safe
, так как по умолчанию эти методы применяются автоматически.
Мы хотим получить дневник (как в базовом примере). Однако мы не доверяем автоматической авторизации и хотим все делать сами.
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем дневник\n const diary = await user.diary();\n console.log(diary);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
Этот пример работает не только с классом
Safe
Для безопасного хранения пользовательских данных рекомендуется сохранить хэш пароля (md5) в вашей базе данных. Библиотека может работать как с "чистым" паролем, так и с его хэшем.
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n school: "МБОУ ...", // Название школы (полностью) или её id\n password: {\n hash: "e10adc3949ba59abbe56e057f20f883e", // MD5 хэш пароля\n length: 6, // Длина оригинального пароля\n },\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем дневник\n const diary = await user.diary();\n console.log(diary);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"controlSession.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},4349:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.diary()
Этот метод возвращает дневник (предметы, оценки, дз и т.д.)
Для использования этого метода нужно передать промежуток (начало и конец недели). Если у вас есть доступ к нескольким ученикам, то также нужно передать id
нужного ученика.
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем дневник\n const diary = await user.diary();\n console.log(diary.days[0].lessons[2]);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем дневник\n const diary = await user.diary();\n console.log(diary.days[0].lessons[2]);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"diary.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},5834:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.downloadFile()
Этот метод позволяет получить файл из дневника
import fs from "fs";\nimport NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем файл\n const buffer = await user.downloadFile({ id: 5863936, assignId: 354142125 });\n\n // Сохраняем файл\n fs.writeFileSync("./тест.docx", buffer);\n})();\n
Не рекомендуется
import fs from "fs";\nimport { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем файл\n const buffer = await user.downloadFile({ id: 5863936, assignId: 354142125 });\n\n // Сохраняем файл\n fs.writeFileSync("./тест.docx", buffer);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',10),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"downloadFile.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},653:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.fetch()
Этот метод позволяет самостоятельно делать запросы на сервер сетевого (куки и прочии данные будут добавлены)
Этот метод не обновляет сессию, поэтому вам придется делать это самостоятельно
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем данные\n const res = await user.fetch("/webapi/sysInfo");\n console.log(await res.text());\n})();\n
',7),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"fetch.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},1887:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.grades()
Этот метод возвращает отчет об успеваемости по выбранному предмету.
Для использования этот метода необходимо передать id предмета (вся информация о доступных предметах есть в классе Context)
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем отчет\n const result = await user.grades({ subjectId: 7960494 });\n console.log(result.assignments);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем отчет\n const result = await user.grades({ subjectId: 7960494 });\n console.log(result.assignments);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"grades.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},7948:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.info()
Этот метод позволяет получить системную информацию о пользователе (например email, телефон и т.д.)
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем информацию\n const result = await user.info();\n console.log(result);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем информацию\n const result = await user.info();\n console.log(result);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',10),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"info.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},9584:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.journal()
Этот метод возвращает отчет об успеваемости и посещаемости учащегося.
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем отчет\n const result = await user.journal();\n console.log(result.subjects);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем отчет\n const result = await user.journal();\n console.log(result.subjects);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',10),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"journal.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},5661:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.photo()
Этот метод позволяет получить фото пользователя
import fs from "fs";\nimport NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем фото\n const buffer = await user.photo();\n\n // Сохраняем фото\n fs.writeFileSync("./img.png", buffer);\n})();\n
Не рекомендуется
import fs from "fs";\nimport { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем фото\n const buffer = await user.photo();\n\n // Сохраняем фото\n fs.writeFileSync("./img.png", buffer);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',10),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"photo.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},1721:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.reportFile()
Этот метод возвращает отчеты
Вообще этот метод создан для более удобной работа с другими методами, которые возвращают отчеты. Однако если вы можете сделать это напрямую.
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем "Отчет об успеваемости и посещаемости ученика"\n const result = await user.reportFile({\n url: "reports/studenttotal/queue",\n filters: [\n {\n filterId: "SID",\n filterValue: "323259", // ID ученика\n },\n {\n filterId: "PCLID",\n filterValue: "3041290", // ID класса\n },\n {\n filterId: "period",\n filterValue: "2022-01-10T00:00:00 - 2022-05-24T00:00:00",\n },\n ],\n });\n console.log(result);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем "Отчет об успеваемости и посещаемости ученика"\n const result = await user.reportFile({\n url: "reports/studenttotal/queue",\n filters: [\n {\n filterId: "SID",\n filterValue: "323259", // ID ученика\n },\n {\n filterId: "PCLID",\n filterValue: "3041290", // ID класса\n },\n {\n filterId: "period",\n filterValue: "2022-01-10T00:00:00 - 2022-05-24T00:00:00",\n },\n ],\n });\n console.log(result);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"reportfile.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},2968:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.scheduleDay()
Этот метод возвращает расписание на день
Для использования этого метода нужно передать дату дня. Если у вас есть доступ к нескольким ученикам, то также нужно передать id
нужного ученика.
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем расписание\n const schedule = await user.scheduleDay({ date: new Date() });\n console.log(schedule);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем расписание\n const schedule = await user.scheduleDay({ date: new Date() });\n console.log(schedule);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',11),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"scheduleDay.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},9525:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.scheduleWeek()
Этот метод возвращает расписание на неделю
Для использования этого метода нужно передать дату дня. Если у вас есть доступ к нескольким ученикам, то также нужно передать id
нужного ученика.
Важно: Если ты посмотрел возвращаемые классы, то ты мог заметить, что названия уроков и классы хранятся в массивах. Сделано это из-за таких моментов.
Расписание в сетевом:
День | № урока | 11б |
---|---|---|
Вт | 1 | История [108] |
2 | Англ.яз./2 гр. [209], Англ.яз./1 гр. [108] | |
3 | Матем. [108] | |
4 | Матем. [108] | |
5 | Биол. [108] | |
6 | Физика [108] | |
7 | Экономика [108] | |
8 | - | |
9 | - | |
10 | - |
Проанализированный результат:
[\n {\n "names": ["История"],\n "number": 1,\n "classesName": ["108"]\n },\n {\n "names": ["Англ.яз./2 гр.", "Англ.яз./1 гр."],\n "number": 2,\n "classesName": ["209", "108"]\n },\n {\n "names": ["Матем."],\n "number": 3,\n "classesName": ["108"]\n },\n {\n "names": ["Матем."],\n "number": 4,\n "classesName": ["108"]\n },\n {\n "names": ["Биол."],\n "number": 5,\n "classesName": ["108"]\n },\n {\n "names": ["Физика"],\n "number": 6,\n "classesName": ["108"]\n },\n {\n "names": ["Экономика"],\n "number": 7,\n "classesName": ["108"]\n }\n]\n
import NS from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Получаем расписание\n const schedule = await user.scheduleWeek();\n console.log(schedule);\n})();\n
Не рекомендуется
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Открываем сессию\n await user.logIn();\n\n // Получаем расписание\n const schedule = await user.scheduleWeek();\n console.log(schedule);\n\n // Закрываем сессию\n await user.logOut();\n})();\n
',16),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"scheduleweek.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},9346:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('.sessionValid()
Этот метод проверяет валидность сессии через API "Сетевой город. Образование". В основном он должен использоваться в классе Safe
, так как по умолчанию этот метод обновляет сессию.
Представьте, что мы пишем бота, который позволяет вам взаимодействовать с сетевым. Однако мы решили сами следить за сессией, и нам нужно как-то ее проверить.
import { Safe as NS } from "netschoolapi";\n\nconst user = new NS({\n origin: "https://example.com",\n login: "Иванов",\n password: "123456",\n school: "МБОУ ...", // Название школы (полностью) или её id\n});\n\n(async function () {\n // Проверяем сессию\n const valid = await user.sessionValid();\n\n if (valid) console.log("Сессия открыта");\n else console.log("Сессия уже недействительна");\n})();\n
',7),d=[t];function e(s,a,n,t,e,r){return(0,l.wg)(),(0,l.iD)("div",null,d)}var r={name:"sessionValid.md"},c=n(3744);const i=(0,c.Z)(r,[["render",e]]);var b=i},7577:function(s,a,n){"use strict";n.r(a),n.d(a,{default:function(){return b}});var l=n(6252);const t=(0,l.uE)('По умолчанию эти методы применяются автоматически. Они доступны при импорте класса
Safe
.
void
при успешном выходе или ошибкуtrue
если сессия активнаstudentId?: number
- ID учащегося, можно не указывать.Buffer
Период дневника может составлять более 7 дней, главное, чтобы он не выходил за рамки учебного года.
studentId?: number
- ID учащегося, можно не указывать.start: Date
- Дата, с которой начинается дневникend: Date
- Последний день в дневникеstudentId?: number
- ID учащегося, можно не указывать.id: number
- ID заданияstudentId?: number
- ID учащегося, можно не указывать.assignId: number
- ID задания, с которым связан файлid: number
- ID файлаclassId?: number
- ID класса, можно не указыватьdate?: Date
- Дата дня, на который нужно получить расписание.classId?: number
- ID класса, можно не указыватьdate?: Date
- Дата дня, на который нужно получить расписание.url: string
- Ссылка на таскер (например для "Отчет об успеваемости и посещаемости ученика" это reports/studenttotal/queue
)filters: { filterId: string, filterValue: string }[]
- Массив с фильтрами (форма, которая находится на странице запроса)yearId?: number
- ID года, за который требуется отчетtimeout?: number
- Время в миллисекундах, через которое запрос будет закрыт (по умолчания 60000
, при значении -1
запрос не будет закрываться)transport?: 0 | 1
- 0 - Web Sockets, 1 - Long Polling (если отсутствует, то используется Web Sockets или Long Polling, в зависимости от версии сервера)subjectId: number
- ID предметаstart?: Date
- начало периодаend?: Date
- окончание периодаtermId?: number
- ID четвертиclassId?: number
- ID классаstudentId?: number
- ID учащегосяtransport?: 0 | 1
- 0 - Web Sockets, 1 - Long Polling (если отсутствует, то используется Web Sockets или Long Polling, в зависимости от версии сервера)start?: Date
- начало периодаend?: Date
- окончание периодаtermId?: number
- ID четвертиclassId?: number
- ID классаstudentId?: number
- ID учащегосяtransport?: 0 | 1
- 0 - Web Sockets, 1 - Long Polling (если отсутствует, то используется Web Sockets или Long Polling, в зависимости от версии сервера)url: string
- Ссылка на ресурсinit?: InitRequest
- Объект InitRequesturl: string
- Ссылка на прокси серверЭто значение, а не метод, и оно не доступно в классе
Safe
.
Интерфейс опциональных значений запроса
.params: object
- объект параметров запроса (?a=12&b=24)Данные, которые могут быть полезны при работе с "Сетевым Городом"
.user: object
- данные пользователя.id: number
- id пользователя.name: string
- имя пользователя в системе.terms: array
- массив доступных четвертей.id: number
- id четверти.name: string
- название четверти.value: string
- значение четверти (из фильтра).isCurrent: boolean
- является ли эта четверть текущей.start: Date
- начало четверти.end: Date
- конец четверти.classes: array
- массив доступных классов.id: number
- id класса.name: string
- название класса.value: string
- значение класса (из фильтра).students: array
- массив доступных учащихся.id: number
- id учащегося.name: string
- имя учащегося в системе.value: string
- значение учащегося (из фильтра).year: object
- данные выбранного года.id: number
- id года.gId: number
- последнии две цифры года.name: string
- название года ('2021/2022').start: Date
- дата начала года.end: Date
- дата окончания года.server: object
- данные сервера.id: string
- id сервера.version: string
- версия сервера.timeFormat: string
- формат времени.dateFormat: string
- формат даты.school: object
- данные школы.id: number
- id школы.name: string
- название школы.fullName: string
- полное название школы.subjects: array
- массив доступных предметов.id: number
- id предмета.name: string
- название предмета.checkDate(date: Date): boolean
- является ли дата частью года.termExists(id: number): boolean
- существует ли id четверти.defaultTerm(): number
- id текущей четверти.classExists(id: number): boolean
- существует ли id класса.defaultClass(): number
- id первого класса.studentExists(id: number): boolean
- существует ли id учащегося.defaultStudent(): number
- id первого учащегося.subjectExists(id: number): boolean
- существует ли id предмета.compareServerVersion(version: string): 0 | 1 | -1
- сравнивает версию сервера с указанной (1 если указанная версия больше, -1 если меньше, 0 если равны)Класс необходим для удобного хранения данных сеанса/пользователя.
.userId: number
- ID пользователя (не путать с id учащегося).yearId: number
- ID учебного года.schoolId: number
- ID школы.studentsId: number[]
- массив с id учащихся, к которым у пользователя есть доступ (для учащихся они совпадают сuserId
).expiryDate: number
- время окончания сессии (указано в Unix Time).accessToken: string
- токен доступа, отправляется в заголовках, как at.globalYearId: number
- ID года (чаще всего это последние 2 цифры года).isValid(): boolean
- возвращает true
, если сессия все еще активна.isExpired(): boolean
- возвращает "true", если сессия больше не активнаКласс необходим для удобной работы с данными пользователя.
.email: string
- почта пользователя.phone: string
- телефон пользователя.lastName: string
- фамилия пользователя.firstName: string
- имя пользователя.middleName: string
- отчество пользователя.birthDate: Date
- день рождения пользователя.existsPhoto: boolean
- возвращает true
если фото установлено.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с дневником.
.days: Day[]
- массив объектов Day.termName: string
- название учебного периода (например: 2 полугодие).className: string
- название класса (например: 10б).start: Date
- дата, с которой начинается дневник.end: Date
- дата последнего дня в дневнике.slice(): Day[]
- возвращает массив объектов Day. Принимает объект данных:start: Date
- дата, с которой дневник должен быть обрезанend: Date
- дата, до которой дневник должен быть обрезан.currentLesson(): Lesson
- возвращает объект Lesson. Принимает дату с учетом времени.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с днями в дневнике.
.date: Date
- дата этого дня.lessons: Lesson[]
- массив объектов Lesson.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с уроками в дневнике.
.id: number
- ID занятия.start: Date
- дата начала урока.end: Date
- дата окончания урока.subject: string
- название предмета.assignments: Assignment[]
- массив объектов Assignment.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с заданиями для уроков в дневнике.
.id: number
- ID задания.dot: boolean
- возвращает true
, если урок просрочен (точка в дневнике).date: Date
- дата сдачи задания.text: string
- текст задания.mark: number | null
- оценка за задание, если таковая имеется.answer?: object
- объект ответа на задание.date: Date
- дата ответа.text: string
- текст ответа.attachments: Attachment[]
- массив объектов Attachment.typeId: number
- ID типа задания.comment: string | null
- комментарий учителя, если таковой имеется.lessonId: number
- ID занятия.attachments: Attachment[]
- массив объектов Attachment.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы c файлами, которые прикреплены к заданиям.
.id: number
- ID файла.name: string
- название файла.date?: Date
- дата загрузки файла (видно только у файлов ответа).description?: string
- описание файла.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с доп. информацией о задании.
.id: number
- ID задания.date: Date
- дата сдачи задания.text: string
- текст задания.weight: number
- вес оценки.subject: string
- название предмета.teacher: string
- имя учителя.isDeleted: boolean
- хз, за что это отвечает (у меня всегда true
).description: string
- описание задания.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобного хранения типов заданий.
.id: number
- ID типа задания.name: string
- название типа задания.abbr: string
- короткое название типа задания.order: number
- какая-то странная вещь (если вы знаете, что это такое, то пишите).toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с типами задания (AssignmentType).
.types: AssignmentType[]
- массив объектов AssignmentType.findById(): AssignmentType
- возвращает объект класса AssignmentType. Принимает id типа задания в качестве аргумента.findByName(): AssignmentType
- возвращает объект класса AssignmentType. Принимает название типа задания в качестве аргумента.findByAbbr(): AssignmentType
- возвращает объект класса AssignmentType. Принимает аббревиатуру типа задания в качестве аргумента.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с расписанием на день.
.raw: string
- HTML код таблицы с расписанием.date: Date
- дата возвращаемого дня.lines: ScheduleDayLine[]
- массив объектов ScheduleDayLine..toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с "линиями" расписания.
.name: string
- название предмета/мероприятия.className?: string
- название кабинета.start: Date
- время начала предмета/мероприятия..end: Date
- время окончания предмета/мероприятия..toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с расписанием на неделю.
.raw: string
- HTML код таблицы с расписанием.date: Date
- дата требуемого дня.parsed: ScheduleWeekLine[]
- массив объектов ScheduleWeekLine..toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с "линиями" расписания.
.date: Date
- дата дня..lessons: object[]
- массив предметов.lessons.names: string[]
- названия предметов.lessons.number: number
- порядковый номер предмета.lessons.classesName: string[]
- названия кабинетов.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с отчетом успеваемости (по предмету)
.raw: string
- HTML код отчета.range: object
- период отчета.start: Date
- начало отчета.end: Date
- окончание отчета.teacher: string
- имя учителя, ведущего урок.averageMark: number
- средняя оценка.assignments: array
- массив оценок.type: AssignmentType
- объект класса AssignmentType.theme: string
- тема урока (например: 'Чтение произведений 20-го века').date: Date
- дата урока.issueDate: Date
- дата выставления оценки.mark: number
- полученная оценка.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
)Класс необходим для удобной работы с отчетом успеваемости и посещаемости
.raw: string
- HTML код отчета.range: object
- период отчета.start: Date
- начало отчета.end: Date
- окончание отчета.subjects: array
- массив предметов.id: number
- ID предмета в системе.name: string
- название предмета в системе.marks: array
- массив оценок.mark: number
- оценка.date: Date
- дата оценки.termId: number
- ID четверти.dotList: array
- массив точек (долгов).date: Date
- дата точки.termId: number
- ID четверти.missedList: array
- массив пропусков.type: string
- аббревиатура пропуска (например: УП, ОТ и т.д.).date: number
- дата пропуска.termId: number
- ID четверти.periodMiddleMark: number
- средняя оценка по предмету за выбранный промежуток.toJSON(): object
- возвращает объект класса (нужно для нормальной работы JSON.stringify()
){ | |
"name": "netschoolapi", | |
"main": "dist/index.js", | |
"types": "dist/index.d.ts", | |
"version": "1.12.1", | |
"description": "Полностью асинхронный API враппер для "Сетевой Город. Образование" написанный на Node.js", | |
"scripts": { | |
"build": "tsc && tsc-alias", | |
"test": "npm run build && node test" | |
}, | |
"keywords": [ | |
"sgo", | |
"nodejs", | |
"netschool", | |
"api-client", | |
"typescript" | |
], | |
"homepage": "https://lentryd.su/NetSchoolApi/", | |
"repository": { | |
"type": "git", | |
"url": "https://github.com/lentryd/NetSchoolApi.git" | |
}, | |
"author": "lentryd", | |
"license": "MIT", | |
"dependencies": { | |
"@lentryd/web-client": "^0.1.0", | |
"compare-versions": "^6.1.1", | |
"https-proxy-agent": "^7.0.5", | |
"iconv-lite": "^0.6.3", | |
"node-html-parser": "^6.1.13" | |
}, | |
"devDependencies": { | |
"@types/node": "^22.10.1", | |
"tsc-alias": "^1.8.10", | |
"typescript": "^5.7.2" | |
} | |
} |
import Client, { InitRequest, requestHook } from "@/classes/Client"; | |
import Session from "@/classes/Session"; | |
import Context from "@/classes/Context"; | |
import logIn from "@/methods/logIn"; | |
import logOut from "@/methods/logOut"; | |
import context from "@/methods/context"; | |
import sessionValid from "@/methods/sessionValid"; | |
import info from "@/methods/info"; | |
import photo from "@/methods/photo"; | |
import diary from "@/methods/diary"; | |
import assignment from "@/methods/assignment"; | |
import downloadFile from "@/methods/downloadFile"; | |
import assignmentTypes from "@/methods/assignmentTypes"; | |
import scheduleDay from "@/methods/scheduleDay"; | |
import scheduleWeek from "@/methods/scheduleWeek"; | |
import reportFile from "@/methods/reportFile"; | |
import grades from "@/methods/grades"; | |
import journal from "@/methods/journal"; | |
import fetch from "@/methods/fetch"; | |
import { Credentials as PhotoCredentials } from "@/methods/photo"; | |
import { Credentials as DiaryCredentials } from "@/methods/diary"; | |
import { Credentials as AssignmentCredentials } from "@/methods/assignment"; | |
import { Credentials as DownloadFileCredentials } from "@/methods/downloadFile"; | |
import { Credentials as ScheduleDayCredentials } from "@/methods/scheduleDay"; | |
import { Credentials as ScheduleWeekCredentials } from "@/methods/scheduleWeek"; | |
import { Credentials as ReportFileCredentials } from "@/methods/reportFile"; | |
import { Credentials as GradesCredentials } from "@/methods/grades"; | |
import { Credentials as JournalCredentials } from "@/methods/journal"; | |
export type PasswordType = string | { hash: string; length: number }; | |
export interface Credentials { | |
login: string; | |
origin: string; | |
school: number | string; | |
password: PasswordType; | |
} | |
export default class NetSchoolApiSafe { | |
public context: null | Context = null; | |
protected session: null | Session = null; | |
protected client: Client; | |
protected credentials: Credentials; | |
/** | |
* Создание пользователя | |
* @/param credentials Данные пользователя | |
*/ | |
constructor(credentials: Credentials) { | |
this.credentials = credentials; | |
this.client = new Client(credentials.origin); | |
this.client.onResponse(requestHook.bind(this.client)); | |
this.client.path.set("webapi"); | |
this.client.headers.set("at", () => | |
this.session?.isValid() ? this.session.accessToken : undefined | |
); | |
} | |
// ⭐️ Пусть будет | |
/** Произвольные запросы к сетевому */ | |
fetch(url: string, init?: InitRequest) { | |
return fetch.call(this, url, init); | |
} | |
/** | |
* Получение прокси | |
* @returns Прокси | |
*/ | |
getProxy() { | |
return this.client.getProxy(); | |
} | |
/** | |
* Установка прокси | |
* @param url Ссылка на прокси | |
*/ | |
setProxy(url: string) { | |
return this.client.setProxy(url); | |
} | |
// ⭐️ Сессия | |
/** Открытие сессии в "Сетевой город. Образование" */ | |
async logIn() { | |
await logIn.call(this); | |
if (!this.context) this.context = await context.call(this); | |
return this.session; | |
} | |
/** Закрытие сессии в "Сетевой город. Образование" */ | |
async logOut() { | |
await logOut.call(this); | |
return (this.session = null); | |
} | |
/** Проверка сессии через API "Сетевой город. Образование"*/ | |
sessionValid() { | |
return sessionValid.call(this); | |
} | |
// ⭐️ Пользователь | |
/** Информация пользователя */ | |
info() { | |
return info.call(this); | |
} | |
/** Фото пользователя */ | |
photo(credentials?: PhotoCredentials) { | |
return photo.call(this, credentials); | |
} | |
// ⭐️ Дневник | |
/** Дневник пользователя*/ | |
diary(credentials?: DiaryCredentials) { | |
return diary.call(this, credentials); | |
} | |
/** Информация о задание */ | |
assignment(credentials: AssignmentCredentials) { | |
return assignment.call(this, credentials); | |
} | |
/** Скачивание файла */ | |
downloadFile(credentials: DownloadFileCredentials) { | |
return downloadFile.call(this, credentials); | |
} | |
/** Типы заданий */ | |
assignmentTypes() { | |
return assignmentTypes.call(this); | |
} | |
// ⭐️ Расписание | |
/** Расписание на день */ | |
scheduleDay(credentials?: ScheduleDayCredentials) { | |
return scheduleDay.call(this, credentials); | |
} | |
/** Расписание на неделю */ | |
scheduleWeek(credentials?: ScheduleWeekCredentials) { | |
return scheduleWeek.call(this, credentials); | |
} | |
// ⭐️ Отчеты | |
reportFile(credentials: ReportFileCredentials) { | |
return reportFile.call(this, credentials); | |
} | |
/** Отчет об успеваемости (по предмету) */ | |
grades(credentials: GradesCredentials) { | |
return grades.call(this, credentials); | |
} | |
/** Отчет об успеваемости (полный) */ | |
journal(credentials: JournalCredentials) { | |
return journal.call(this, credentials); | |
} | |
} |
import NS, { Credentials } from "@NS"; | |
import Session from "@/classes/Session"; | |
let activeClasses = 0; | |
const errors: string[] = []; | |
export default class NetSchoolApi extends NS { | |
/** Уведомления */ | |
private console = { | |
info(title: string, ...optionalParams: any[]) { | |
console.info("[46m[30m INFO [0m", title); | |
if (!!optionalParams) console.info(...optionalParams); | |
}, | |
done(title: string, ...optionalParams: any[]) { | |
console.info("[42m[30m DONE [0m", title); | |
if (!!optionalParams) console.info(...optionalParams); | |
}, | |
error(title: string, ...optionalParams: any[]) { | |
console.error("[41m[30m ERROR [0m", title); | |
if (!!optionalParams) console.error(...optionalParams); | |
}, | |
}; | |
/** Начался ли процесс закрытия */ | |
private startClosing = false; | |
constructor(credentials: Credentials) { | |
super(credentials); | |
activeClasses++; | |
this.console.info(`Класс пользователя ${this.credentials.login} создан`); | |
// Если нажали Ctrl + C, то закрываем сессию | |
process.addListener("SIGINT", this.closeProcess.bind(this)); | |
// Прежде чем завершить процесс, мы закрываем сессию | |
process.addListener("beforeExit", this.closeProcess.bind(this)); | |
// Если произошла ошибка, мы закрываем сессию | |
process.addListener("uncaughtException", (err) => { | |
if (!errors.includes(err.name)) { | |
this.console.error("Ошибка в коде привела к закрытию программы", err); | |
errors.push(err.name); | |
} | |
this.closeProcess.bind(this); | |
}); | |
} | |
/** Контекст который доступен без авторизации */ | |
get contextAsync(): Promise<NonNullable<NS["context"]>> { | |
// Если нет контекста, то создаём (через авторизацию) | |
return new Promise((resolve, reject) => { | |
if (this.context) { | |
resolve(this.context); | |
} else { | |
this.logIn() | |
.then(() => { | |
resolve(this.context as NonNullable<NS["context"]>); | |
}) | |
.catch(reject); | |
} | |
}); | |
} | |
/** Открытие сессии (только если она закрыта) */ | |
async logIn(): Promise<Session> { | |
const valid = await super.sessionValid(); | |
if (valid) return this.session as any; | |
else return super.logIn() as any; | |
} | |
/** Закрытие сессии (только если она открыта) */ | |
async logOut() { | |
const valid = await super.sessionValid(); | |
if (valid) return super.logOut(); | |
else return null; | |
} | |
/** Повторное открытие сессии (всегда возвращает `true`) */ | |
async sessionValid(): Promise<true> { | |
if (!(await super.sessionValid())) await super.logIn(); | |
return true; | |
} | |
/** Экстренное закрытие сессии */ | |
private async closeProcess() { | |
if (this.startClosing) return; | |
this.startClosing = true; | |
// Закрываем сессию | |
await this.logOut() | |
.then(() => | |
this.console.done(`Сеанс ${this.credentials.login} успешно закрыт`) | |
) | |
.catch((err) => | |
this.console.error( | |
`Ошибка закрытия сессии ${this.credentials.login}`, | |
err | |
) | |
); | |
// Уменьшаем счетчик | |
activeClasses--; | |
// Если счетчик пуст, то закрываем процесс | |
if (activeClasses === 0) process.exit(0); | |
} | |
} |
import Attachment, { | |
AttachmentObject, | |
AnswerFilesObject, | |
} from "@/classes/Attachment"; | |
type Mark = { | |
assignmentId: number; | |
studentId: number; | |
mark: number; | |
dutyMark: boolean; | |
}; | |
type TextAnswer = { | |
answer: string; | |
answerDate: Date; | |
}; | |
export interface AssignmentObject { | |
id: number; | |
typeId: number; | |
dueDate: string; | |
mark?: Mark; | |
markComment?: { | |
id: number; | |
name: string; | |
teacher: string; | |
wasRead: boolean; | |
editTime: string; | |
}; | |
textAnswer?: TextAnswer; | |
attachments: AttachmentObject[]; | |
answerFiles: AnswerFilesObject[]; | |
assignmentName: string; | |
classMeetingId: number; | |
} | |
export default class Assignment { | |
id: number; | |
dot: boolean; | |
text: string; | |
mark?: number; | |
typeId: number; | |
comment?: string; | |
lessonId: number; | |
attachments: Attachment[]; | |
private _date: string; | |
private _answer?: TextAnswer; | |
private _answerAtt: Attachment[]; | |
constructor(assignment: AssignmentObject) { | |
this.id = assignment.id; | |
this.dot = assignment.mark?.dutyMark ?? false; | |
this.text = assignment.assignmentName; | |
this.mark = assignment.mark?.mark; | |
this.typeId = assignment.typeId; | |
this.comment = assignment.markComment?.name; | |
this.lessonId = assignment.classMeetingId; | |
this.attachments = assignment.attachments.map((a) => new Attachment(a)); | |
this._date = assignment.dueDate; | |
this._answer = assignment.textAnswer; | |
this._answerAtt = assignment.answerFiles.map((a) => new Attachment(a)); | |
} | |
get date() { | |
return new Date(this._date); | |
} | |
get answer() { | |
if (!this._answer) return undefined; | |
return { | |
date: new Date(this._answer.answerDate), | |
text: this._answer.answer, | |
attachments: this._answerAtt, | |
}; | |
} | |
toJSON() { | |
const answer = !this._answer | |
? undefined | |
: { | |
date: this._answer.answerDate, | |
text: this._answer.answer, | |
attachments: this._answerAtt.map((a) => a.toJSON()), | |
}; | |
return { | |
id: this.id, | |
dot: this.dot, | |
text: this.text, | |
mark: this.mark, | |
answer, | |
typeId: this.typeId, | |
lessonId: this.lessonId, | |
attachments: this.attachments.map((a) => a.toJSON()), | |
}; | |
} | |
} |
interface Teacher { | |
id: number; | |
name: string; | |
} | |
interface SubjectGroup { | |
id: number; | |
name: string; | |
} | |
export interface AssignmentInfoObject { | |
id: number; | |
date: string; | |
weight: number; | |
teacher: Teacher; | |
teachers?: Teacher[]; | |
isDeleted: boolean; | |
description: string; | |
subjectGroup: SubjectGroup; | |
assignmentName: string; | |
} | |
export default class { | |
id: number; | |
text: string; | |
weight: number; | |
subject: string; | |
teacher: string; | |
isDeleted: boolean; | |
description: string; | |
private _date: string; | |
constructor(assignment: AssignmentInfoObject) { | |
this.id = assignment.id; | |
this.text = assignment.assignmentName; | |
this.weight = assignment.weight; | |
this.subject = assignment.subjectGroup.name; | |
this.teacher = assignment.teachers | |
? assignment.teachers[0].name | |
: assignment.teacher.name; | |
this.isDeleted = assignment.isDeleted; | |
this.description = assignment.description; | |
this._date = assignment.date; | |
} | |
get date() { | |
return new Date(this._date); | |
} | |
toJSON() { | |
return { | |
id: this.id, | |
text: this.text, | |
date: this._date, | |
weight: this.weight, | |
subject: this.subject, | |
teacher: this.teacher, | |
isDeleted: this.isDeleted, | |
description: this.description, | |
}; | |
} | |
} |
export interface TypesObject { | |
id: number; | |
name: string; | |
abbr: string; | |
order: number; | |
} | |
export default class AssignmentType { | |
id: number; | |
name: string; | |
abbr: string; | |
order: number; | |
constructor(type: TypesObject) { | |
this.id = type.id; | |
this.name = type.name; | |
this.abbr = type.abbr; | |
this.order = type.order; | |
} | |
toJSON() { | |
return { | |
id: this.id, | |
name: this.name, | |
abbr: this.abbr, | |
order: this.order, | |
}; | |
} | |
} |
import AssignmentType, { TypesObject } from "./AssignmentType"; | |
export default class AssignmentTypes { | |
types: AssignmentType[]; | |
constructor(types: TypesObject[]) { | |
this.types = types.map((t) => new AssignmentType(t)); | |
} | |
findById(id: number) { | |
return this.types.find((t) => t.id === id) ?? null; | |
} | |
findByName(name: string) { | |
return this.types.find((t) => t.name === name) ?? null; | |
} | |
findByAbbr(abbr: string) { | |
return this.types.find((t) => t.abbr === abbr) ?? null; | |
} | |
toJSON() { | |
return this.types.map((t) => t.toJSON()); | |
} | |
} |
export type AttachmentObject = { | |
id: number; | |
name?: string; | |
description?: string; | |
originalFileName: string; | |
}; | |
export type AnswerFilesObject = { | |
studentId: number; | |
attachment: { | |
id: number; | |
aFile: any; | |
saved: number; | |
userId: any; | |
fileName: string; | |
description?: string; | |
}; | |
attachmentDate: string; | |
}; | |
export type AttachmentRaw = AttachmentObject | AnswerFilesObject; | |
export default class Attachment { | |
id: number; | |
name: string; | |
description?: string; | |
private _date?: string; | |
constructor(raw: AttachmentRaw) { | |
this.id = "attachment" in raw ? raw.attachment.id : raw.id; | |
this.name = | |
"attachment" in raw ? raw.attachment.fileName : raw.originalFileName; | |
this._date = "attachment" in raw ? raw.attachmentDate : undefined; | |
this.description = | |
"attachment" in raw ? raw.attachment.description : raw.description; | |
} | |
get date() { | |
if (!this._date) return undefined; | |
return new Date(this._date); | |
} | |
toJSON() { | |
return { | |
id: this.id, | |
name: this.name, | |
date: this._date, | |
description: this.description, | |
}; | |
} | |
} |
import { HttpsProxyAgent } from "https-proxy-agent"; | |
import WebClient, { Response, InitRequest } from "@lentryd/web-client"; | |
export async function isSecurityWarning(res: Response) { | |
return ( | |
res.headers.get("content-type")?.includes?.("text/html") && | |
+(res.headers.get("content-length") ?? "") < 1000 && | |
!res.headers.has("filename") && | |
(await res.clone().text()).includes("/asp/SecurityWarning.asp") | |
); | |
} | |
export async function requestHook( | |
this: Client, | |
res: Response, | |
url: string, | |
init?: InitRequest | |
) { | |
if (await isSecurityWarning(res)) { | |
await this.post( | |
"../asp/SecurityWarning.asp", | |
Client.formData({ | |
at: this.headers.get().at, | |
WarnType: 2, | |
}) | |
); | |
return this.request(url, init); | |
} | |
} | |
export * from "@lentryd/web-client"; | |
export default class Client extends WebClient { | |
/** | |
* Получает прокси для запросов | |
* @returns Прокси | |
*/ | |
getProxy() { | |
return this.agent.get(); | |
} | |
/** | |
* Задает прокси для запросов | |
* @param url Ссылка на прокси | |
* @returns | |
*/ | |
setProxy(url: string) { | |
return this.agent.set(new HttpsProxyAgent(url)); | |
} | |
} |
import { compareVersions } from "compare-versions"; | |
export interface Term { | |
// ID четверти | |
id: number; | |
// Название четверти | |
name: string; | |
// Значение фильтра | |
value: string; | |
// Является ли эта четверть текущей | |
isCurrent: boolean; | |
// Дата начала четверти | |
start: Date; | |
// Дата окончания четверти | |
end: Date; | |
} | |
export interface Class { | |
// ID класса | |
id: number; | |
// Название класса | |
name: string; | |
// Значение фильтра | |
value: string; | |
} | |
export interface Student { | |
// ID ученика | |
id: number; | |
// Имя ученика | |
name: string; | |
// Значение фильтра | |
value: string; | |
} | |
export interface User { | |
id: number; | |
name: string; | |
terms: Term[]; | |
classes: Class[]; | |
students: Student[]; | |
} | |
export interface Year { | |
id: number; | |
gId: number; | |
name: string; | |
start: Date; | |
end: Date; | |
} | |
export interface Server { | |
id: string; | |
version: string; | |
timeFormat: string; | |
dateFormat: string; | |
} | |
export interface Subject { | |
id: number; | |
name: string; | |
} | |
export interface School { | |
id: number; | |
name: string; | |
fullName: string; | |
} | |
export interface Credentials { | |
user: User; | |
year: Year; | |
server: Server; | |
school: School; | |
subjects: Subject[]; | |
} | |
export default class Context { | |
readonly user: User; | |
readonly year: Year; | |
readonly server: Server; | |
readonly school: School; | |
readonly subjects: Subject[]; | |
constructor(credentials: Credentials) { | |
this.user = credentials.user; | |
this.year = credentials.year; | |
this.server = credentials.server; | |
this.school = credentials.school; | |
this.subjects = credentials.subjects; | |
} | |
/** Проверяет является ли число частью года */ | |
checkDate(date: Date) { | |
const { start, end } = this.year; | |
return +start <= +date && +date <= +end; | |
} | |
/** Существует ли четверть */ | |
termExists(id: number) { | |
return !!this.user.terms.find((t) => t.id == id); | |
} | |
/** ID текущей четверти */ | |
defaultTerm(): Term | undefined { | |
return this.user.terms.find((t) => t.isCurrent); | |
} | |
/** Получить четверть по ID */ | |
getTermById(id: number) { | |
return this.user.terms.find((t) => t.id == id); | |
} | |
/** Существует ли класс */ | |
classExists(id: number) { | |
return !!this.getClassById(id); | |
} | |
/** ID первого класса */ | |
defaultClass(): Class | undefined { | |
return this.user.classes[0]; | |
} | |
/** Получить класс по ID */ | |
getClassById(id: number) { | |
return this.user.classes.find((c) => c.id == id); | |
} | |
/** Существует ли ученик */ | |
studentExists(id: number) { | |
return !!this.getStudentById(id); | |
} | |
/** ID первого ученика */ | |
defaultStudent(): Student | undefined { | |
return this.user.students[0]; | |
} | |
/** Получить ученика по ID */ | |
getStudentById(id: number) { | |
return this.user.students.find((s) => s.id == id); | |
} | |
/** Существует ли предмет */ | |
subjectExists(id: number) { | |
return !!this.subjects.find((s) => s.id == id); | |
} | |
/** | |
* Сравнивает версию сервера с указанной | |
* @param version Версия сервера с которой сравнивается | |
* @returns 1 если указанная версия больше, -1 если меньше, 0 если равны | |
*/ | |
compareServerVersion(version: string) { | |
return compareVersions(this.server.version, version); | |
} | |
} |
import Lesson, { LessonObject } from "./Lesson"; | |
export interface DayObject { | |
date: string; | |
lessons: LessonObject[]; | |
} | |
export default class Day { | |
lessons: Lesson[]; | |
private _date: string; | |
constructor(day: DayObject) { | |
this._date = day.date; | |
this.lessons = day.lessons.map((l) => new Lesson(l)); | |
} | |
get date() { | |
return new Date(this._date); | |
} | |
toJSON() { | |
return { | |
date: this._date, | |
lessons: this.lessons.map((l) => l.toJSON()), | |
}; | |
} | |
} |
import Day, { DayObject } from "./Day"; | |
import Lesson from "./Lesson"; | |
export interface DiaryObject { | |
weekEnd: string; | |
termName: string; | |
weekDays?: DayObject[]; | |
weekStart: string; | |
className: string; | |
} | |
export default class Diary { | |
days: Day[]; | |
termName: string; | |
className: string; | |
private _end: string; | |
private _start: string; | |
constructor(diary: DiaryObject) { | |
this.days = diary.weekDays?.map((d) => new Day(d)) ?? []; | |
this.termName = diary.termName; | |
this.className = diary.className; | |
this._end = diary.weekEnd; | |
this._start = diary.weekStart; | |
} | |
get start() { | |
return new Date(this._start); | |
} | |
get end() { | |
return new Date(this._end); | |
} | |
slice({ start, end }: { start: Date; end: Date }) { | |
return this.days.filter(({ date }) => date >= start && date < end); | |
} | |
currentLesson(date: Date) { | |
const lessons: Lesson[] = []; | |
this.days.forEach((d) => lessons.push(...d.lessons)); | |
return ( | |
lessons.find( | |
({ start: startDate, end: endDate }) => date >= startDate && date < endDate | |
) ?? null | |
); | |
} | |
toJSON() { | |
return { | |
days: this.days.map((d) => d.toJSON()), | |
endDate: this._end, | |
startDate: this._start, | |
}; | |
} | |
} |
import { str2date, date2JSON } from "@/utils/dateNum"; | |
import { query, table } from "@/utils/parseHtml"; | |
import AssignmentTypes from "./AssignmentTypes"; | |
interface Credentials { | |
types: AssignmentTypes; | |
htmlText: string; | |
hasTerms: boolean; | |
} | |
export default class Grades { | |
raw: string; | |
range: { start: Date; end: Date }; | |
teacher: string; | |
averageMark: number; | |
private _types: AssignmentTypes; | |
constructor(credentials: Credentials) { | |
this.raw = credentials.htmlText; | |
this._types = credentials.types; | |
const [start = "", end = ""] = | |
query( | |
this.raw, | |
`table td:nth-child(2) > span:nth-child(${ | |
credentials.hasTerms ? 5 : 3 | |
})` | |
)?.structuredText.match(/((d{1,2}.){2}d{2})/g) ?? []; | |
this.range = { start: str2date(start), end: str2date(end) }; | |
this.teacher = | |
query( | |
this.raw, | |
`table td:nth-child(2) > span:nth-child(${ | |
credentials.hasTerms ? 11 : 9 | |
})` | |
)?.childNodes[1].text.trim() ?? ""; | |
this.averageMark = +( | |
query(this.raw, ".table-print tr.totals td:nth-child(3)") | |
?.structuredText.replace(",", ".") | |
.replace?.(/^D+(?=d)/, "") ?? "" | |
); | |
} | |
get assignments() { | |
const trs = table({ html: this.raw, query: ".table-print" }); | |
trs.pop(); | |
return trs.map((tr) => { | |
const [typeTd, themeTd, dateTd, issueDateTd, markTd] = | |
tr.querySelectorAll("td") ?? []; | |
return { | |
type: this._types.findByName(typeTd?.structuredText), | |
theme: themeTd?.structuredText, | |
date: str2date(dateTd?.structuredText), | |
issueDate: str2date(issueDateTd?.structuredText), | |
mark: +markTd?.structuredText, | |
}; | |
}); | |
} | |
toJSON() { | |
return { | |
raw: this.raw, | |
range: { | |
start: date2JSON(this.range.start), | |
end: date2JSON(this.range.end), | |
}, | |
teacher: this.teacher, | |
assignments: this.assignments.map((a) => ({ | |
...a, | |
date: date2JSON(a.date), | |
issueDate: date2JSON(a.issueDate), | |
})), | |
}; | |
} | |
} |
export interface InfoObject { | |
firstName: string; | |
lastName: string; | |
middleName: string; | |
birthDate: string; | |
mobilePhone: string; | |
email: string; | |
existsPhoto: boolean; | |
} | |
export default class Info { | |
email: string; | |
phone: string; | |
lastName: string; | |
firstName: string; | |
middleName: string; | |
existsPhoto: boolean; | |
private _birthDate: string; | |
constructor(info: InfoObject) { | |
this.email = info.email; | |
this.phone = info.mobilePhone; | |
this.lastName = info.lastName; | |
this.firstName = info.firstName; | |
this.middleName = info.middleName; | |
this.existsPhoto = info.existsPhoto; | |
this._birthDate = info.birthDate; | |
} | |
get birthDate() { | |
return new Date(this._birthDate); | |
} | |
toJSON() { | |
return { | |
email: this.email, | |
phone: this.phone, | |
lastName: this.lastName, | |
firstName: this.firstName, | |
birthDate: this._birthDate, | |
middleName: this.middleName, | |
existsPhoto: this.existsPhoto, | |
}; | |
} | |
} |
import Context from "@/classes/Context"; | |
import { str2date, date2JSON } from "@/utils/dateNum"; | |
import { query, queryAll, table } from "@/utils/parseHtml"; | |
//? Константы | |
// Селекторы промежутков | |
const RANGE_SELECTOR = "table td:nth-child(2) > span:nth-child(5)"; | |
const RANGE_SELECTOR_V2 = "table td:nth-child(2) > span:nth-child(7)"; | |
// Селектор дней | |
const DAYS_SELECTOR = "table.table-print tr:nth-child(2) > th"; | |
// Селектор месяцев | |
const MONTHS_SELECTOR = "table.table-print tr:nth-child(1) > th[colspan]"; | |
// Расшифровка названия месяцев в числа (разные года) | |
const MONTH_NUMBERS = { | |
Сентябрь: 8, | |
Октябрь: 9, | |
Ноябрь: 10, | |
Декабрь: 11, | |
Январь: 12, | |
Февраль: 13, | |
Март: 14, | |
Апрель: 15, | |
Май: 16, | |
Июнь: 17, | |
Июль: 18, | |
Август: 19, | |
}; | |
// Расшифровка названия месяцев в числа (одинаковые года года) | |
const MONTH_NUMBERS_SY = { | |
Сентябрь: 8, | |
Октябрь: 9, | |
Ноябрь: 10, | |
Декабрь: 11, | |
Январь: 0, | |
Февраль: 1, | |
Март: 2, | |
Апрель: 3, | |
Май: 4, | |
Июнь: 5, | |
Июль: 6, | |
Август: 7, | |
}; | |
//? Типы данных | |
/** Параметры для инициализации */ | |
interface Credentials { | |
htmlText: string; | |
terms: Context["user"]["terms"]; | |
subjects: Context["subjects"]; | |
} | |
/** Тип данных распарсенных предметов */ | |
export interface Subject { | |
/** ID предмета */ | |
id: number; | |
/** Название предмета */ | |
name: string; | |
/** Массив оценок */ | |
marks: { mark: number; date: Date; termId: number }[]; | |
/** Массив точек (долгов) */ | |
dotList: { date: Date; termId: number }[]; | |
/** Массив пропусков */ | |
missedList: { type: string; date: Date; termId: number }[]; | |
/** Итоговые оценки */ | |
totalMarks: { mark: number; termId: number }[]; | |
/** Средние оценки */ | |
middleMarks: { mark: number; termId: number }[]; | |
/** Средняя оценка за выбранный период */ | |
periodMiddleMark: number; | |
/** Средняя оценка | |
* | |
* _В следующих версиях будет удален, используйте .periodMiddleMark_ | |
* @deprecated */ | |
middleMark: number; | |
} | |
/** Тип данных распарсенных предметов (JSON) */ | |
export interface SubjectData { | |
/** ID предмета */ | |
id: number; | |
/** Название предмета */ | |
name: string; | |
/** Массив оценок */ | |
marks: { mark: number; date: string; termId: number }[]; | |
/** Массив точек (долгов) */ | |
dotList: { date: string; termId: number }[]; | |
/** Массив пропусков */ | |
missedList: { type: string; date: string; termId: number }[]; | |
/** Итоговые оценки */ | |
totalMarks: { mark: number; termId: number }[]; | |
/** Средние оценки */ | |
middleMarks: { mark: number; termId: number }[]; | |
/** Средняя оценка за выбранный период */ | |
periodMiddleMark: number; | |
/** Средняя оценка | |
* | |
* _В следующих версиях будет удален, используйте .periodMiddleMark_ | |
* @deprecated */ | |
middleMark: number; | |
} | |
/** Данные журнала в формате JSON */ | |
export interface JournalData { | |
raw: string; | |
range: { start: string; end: string }; | |
subjects: SubjectData[]; | |
} | |
//? Функции помощники | |
/** | |
* Возвращает id четверти по дате | |
* @param terms массив четвертей | |
* @param date дата | |
* @returns id четверти | |
*/ | |
function termByDate(terms: Credentials["terms"], date: Date): number { | |
return ( | |
terms.find( | |
(term) => term.id != -1 && +term.start <= +date && +date <= +term.end | |
)?.id ?? -1 | |
); | |
} | |
/** | |
* Сопоставляем оценки с четвертями | |
* @param terms массив четвертей | |
* @param termTds массив с названиями четвертей | |
* @param marksTds массив с оценками четвертей | |
*/ | |
function totalMarksFormat( | |
terms: Credentials["terms"], | |
termTds: string[], | |
marksTds: number[] | |
): { | |
totalMarks: Subject["totalMarks"]; | |
middleMarks: Subject["middleMarks"]; | |
periodMiddleMark: Subject["periodMiddleMark"]; | |
// TODO: удалить в следующих версиях | |
middleMark: Subject["middleMark"]; | |
} { | |
// Сопоставляем оценки с четвертями | |
const totalMarks: Subject["totalMarks"] = []; | |
const middleMarks: Subject["middleMarks"] = []; | |
marksTds.forEach((mark, index) => { | |
// Получаем id четверти | |
const tdName = termTds[index]; | |
const termId = terms.find((t) => tdName.includes(t.name))?.id ?? -1; | |
// Если это итоговая оценка за выбранный период | |
if (tdName.includes("Итог") && mark) totalMarks.push({ mark, termId }); | |
// Если это средняя оценка за выбранный период | |
if (tdName.includes("Средн") && mark) middleMarks.push({ mark, termId }); | |
}); | |
// Получаем среднюю оценку за выбранный период | |
const periodMiddleMark = middleMarks[middleMarks.length - 1]?.mark ?? 0; | |
return { | |
totalMarks, | |
middleMarks, | |
periodMiddleMark, | |
// TODO: удалить в следующих версиях | |
middleMark: periodMiddleMark, | |
}; | |
} | |
/** | |
* Парсим данные из файла отчета | |
* @param html отчет в формате HTML | |
* @param start дата начала отчета | |
* @param end дата окончания отчета | |
* @returns массив дат | |
*/ | |
function parseDates(html: string, start: Date, end: Date) { | |
const isSameYear = start.getFullYear() === end.getFullYear(); | |
// Получаем дни | |
const days = Array.from( | |
queryAll(html, DAYS_SELECTOR), | |
(th) => +th.structuredText | |
); | |
// Получаем месяца и его длину | |
const months = Array.from(queryAll(html, MONTHS_SELECTOR), (th) => ({ | |
length: +(th.getAttribute("colspan") ?? ""), | |
number: !isSameYear | |
? MONTH_NUMBERS[th.structuredText as keyof typeof MONTH_NUMBERS] | |
: MONTH_NUMBERS_SY[th.structuredText as keyof typeof MONTH_NUMBERS_SY], | |
})); | |
// Форматируем даты | |
const result: Date[] = []; | |
months.forEach(({ number, length }) => { | |
const resultLength = result.length; | |
for (let i = resultLength; i < resultLength + length; i++) { | |
const date = new Date(start); | |
date.setDate(days[i]); | |
date.setMonth(number); | |
result.push(date); | |
} | |
}); | |
return result; | |
} | |
export default class Journal { | |
/** HTML код отчета */ | |
raw: string; | |
/** Промежуток отчета */ | |
range: { | |
/** Начало отчета */ | |
start: Date; | |
/** Конец отчета */ | |
end: Date; | |
}; | |
private _terms: Credentials["terms"]; | |
private _subjects: Credentials["subjects"]; | |
constructor(credentials: Credentials) { | |
this.raw = credentials.htmlText; | |
this._terms = credentials.terms; | |
this._subjects = credentials.subjects; | |
const [start = "", end = ""] = | |
query( | |
this.raw, | |
!this._hasTerms ? RANGE_SELECTOR : RANGE_SELECTOR_V2 | |
)?.structuredText.match(/((d{1,2}.){2}d{2})/g) ?? []; | |
this.range = { start: str2date(start), end: str2date(end) }; | |
} | |
/** Проверяет наличия деления на четверти */ | |
private get _hasTerms(): boolean { | |
return this._terms.length > 0; | |
} | |
/** Получаем распарсенные оценки по предметам */ | |
get subjects(): Subject[] { | |
// Парсим даты | |
const dates = parseDates(this.raw, this.range.start, this.range.end); | |
// Получаем доступ к таблице | |
const trs = table({ | |
html: this.raw, | |
query: ".table-print", | |
removeHeaders: false, | |
}); | |
// Получаем название четвертей | |
const termTds = Array.from(trs[0].querySelectorAll("th[rowspan]"), (th) => | |
th.text.trim() | |
); | |
// Удаляем лишнее | |
trs.splice(0, 2); | |
termTds.shift(); | |
return trs.map((tr) => { | |
// Получаем название предмета | |
const nameTd = tr.querySelector("td:nth-child(1)"); | |
const name = nameTd?.text.trim() ?? ""; | |
// Получаем средние оценки и итоговые | |
const marksTds = Array.from( | |
tr.querySelectorAll("td.cell-num-2"), | |
(td) => +td.text.trim().replace(",", ".") | |
); | |
// Получаем данные ячеек за период | |
const periodTds = Array.from(tr.querySelectorAll(":not([class])"), (td) => | |
td.text.trim() | |
); | |
// Парсим данные ячеек за период | |
const marks: Subject["marks"] = []; | |
const dotList: Subject["dotList"] = []; | |
const missedList: Subject["missedList"] = []; | |
periodTds.forEach((content, i) => { | |
const date = dates[i]; | |
if (!content) return; | |
content.match(/d/g)?.forEach((str) => | |
marks.push({ | |
mark: +str, | |
date, | |
termId: termByDate(this._terms, date), | |
}) | |
); | |
content.match(/./g)?.forEach(() => | |
dotList.push({ | |
date, | |
termId: termByDate(this._terms, date), | |
}) | |
); | |
content.match(/[А-Яа-я]+/g)?.forEach((type) => | |
missedList.push({ | |
type, | |
date, | |
termId: termByDate(this._terms, date), | |
}) | |
); | |
}); | |
// Возвращаем данные предмета | |
return { | |
id: this._subjects.find((s) => s.name === name)?.id ?? -1, | |
name, | |
marks, | |
dotList, | |
missedList, | |
...totalMarksFormat(this._terms, termTds, marksTds), | |
}; | |
}); | |
} | |
toJSON(): JournalData { | |
return { | |
raw: this.raw, | |
range: { | |
start: date2JSON(this.range.start), | |
end: date2JSON(this.range.end), | |
}, | |
subjects: this.subjects.map((s) => ({ | |
...s, | |
marks: s.marks.map((m) => ({ | |
...m, | |
date: date2JSON(m.date), | |
})), | |
dotList: s.dotList.map((d) => ({ | |
...d, | |
date: date2JSON(d.date), | |
})), | |
missedList: s.missedList.map((m) => ({ | |
...m, | |
date: date2JSON(m.date), | |
})), | |
})), | |
}; | |
} | |
} |
import Assignment, { AssignmentObject } from "./Assignment"; | |
export interface LessonObject { | |
day: string; | |
room: string; | |
number: number; | |
endTime: string; | |
startTime: string; | |
subjectName: string; | |
assignments: undefined | AssignmentObject[]; | |
classmeetingId: number; | |
} | |
export default class Lesson { | |
id: number; | |
subject: string; | |
assignments: Assignment[]; | |
private _endDate: string; | |
private _startDate: string; | |
constructor(lesson: LessonObject) { | |
this.id = lesson.classmeetingId; | |
this.subject = lesson.subjectName; | |
this._endDate = lesson.day.replace("00:00", lesson.endTime); | |
this._startDate = lesson.day.replace("00:00", lesson.startTime); | |
this.assignments = lesson.assignments?.map((a) => new Assignment(a)) ?? []; | |
} | |
get end() { | |
return new Date(this._endDate); | |
} | |
get start() { | |
return new Date(this._startDate); | |
} | |
toJSON() { | |
return { | |
id: this.id, | |
subject: this.subject, | |
endDate: this._endDate, | |
startDate: this._startDate, | |
assignments: this.assignments.map((a) => a.toJSON()), | |
}; | |
} | |
} |
import { outerHTML, table } from "@/utils/parseHtml"; | |
import { num2str } from "@/utils/dateNum"; | |
import ScheduleDayLine from "./ScheduleDayLine"; | |
interface Credentials { | |
date: string; | |
htmlText: string; | |
} | |
function timeFormat(date: string, strDate: string) { | |
if (strDate.includes(" ")) { | |
const [date1, time] = strDate.split(" "); | |
const [day, month] = date1.split("."); | |
return date | |
.replace(/-d{2}-/, "-" + num2str(month) + "-") | |
.replace(/-d{2}T/, "-" + num2str(day) + "T") | |
.replace(/T.+/, "T" + time); | |
} else { | |
return date.replace(/T.+/, "T" + strDate); | |
} | |
} | |
export default class ScheduleDay { | |
raw: string; | |
private _date: string; | |
constructor(credentials: Credentials) { | |
this.raw = outerHTML({ html: credentials.htmlText, query: ".table" }); | |
this._date = credentials.date; | |
} | |
get date() { | |
return new Date(this._date); | |
} | |
get lines(): ScheduleDayLine[] { | |
return table({ html: this.raw }).map((tr) => { | |
const [timeTd, nameTd] = tr?.querySelectorAll?.("td") ?? []; | |
let [start, end] = timeTd?.structuredText.split(" - "); | |
const startDate = timeFormat(this._date, start); | |
const endDate = timeFormat(this._date, end); | |
let name = nameTd?.structuredText; | |
const className = name.match(/[(.+)]/)?.[1]; | |
if (className) name = name.replace(/ [(.+)]/, ""); | |
return new ScheduleDayLine({ name, endDate, startDate, className }); | |
}); | |
} | |
toJSON() { | |
return { | |
raw: this.raw, | |
date: this._date, | |
lines: this.lines.map((line) => line.toJSON()), | |
}; | |
} | |
} |
interface Credentials { | |
name: string; | |
className?: string; | |
startDate: string; | |
endDate: string; | |
} | |
export default class ScheduleDayLine { | |
name: string; | |
className?: string; | |
private _startDate: string; | |
private _endDate: string; | |
constructor(credentials: Credentials) { | |
this.name = credentials.name; | |
this.className = credentials.className; | |
this._startDate = credentials.startDate; | |
this._endDate = credentials.endDate; | |
} | |
get start() { | |
return new Date(this._startDate); | |
} | |
get end() { | |
return new Date(this._endDate); | |
} | |
toJSON() { | |
return { | |
name: this.name, | |
className: this.className, | |
startDate: this._startDate, | |
endDate: this._endDate, | |
}; | |
} | |
} |
import { date2JSON } from "@/utils/dateNum"; | |
import { outerHTML, table } from "@/utils/parseHtml"; | |
import ScheduleWeekLine from "./ScheduleWeekLine"; | |
const DATE_REGEX = /Расписание.+?с (d{1,2}.d{1,2}.d{1,2})/; | |
interface Credentials { | |
htmlText: string; | |
} | |
export default class ScheduleWeek { | |
raw: string; | |
private _date: string; | |
constructor(credentials: Credentials) { | |
let date = credentials.htmlText.match(DATE_REGEX)?.[1] ?? "08.04.04"; | |
this.raw = outerHTML({ html: credentials.htmlText, query: ".table" }); | |
this._date = date2JSON(date); | |
} | |
get date() { | |
return new Date(this._date); | |
} | |
get parsed(): ScheduleWeekLine[] { | |
return table({ html: this.raw }).map((tr, i) => { | |
const [numberTd, nameTd] = tr?.querySelectorAll?.("td") ?? []; | |
const date = this.date; | |
date.setDate(date.getDate() + i); | |
const numbers = numberTd?.childNodes | |
?.filter((n) => n.nodeType == 3) | |
?.map((n) => parseInt(n.text)); | |
const names = nameTd?.childNodes | |
?.filter((n) => n.nodeType == 3) | |
?.map((n) => n.text); | |
const lessons: ScheduleWeekLine["lessons"] = []; | |
for (let i = 0; i < names.length; i++) { | |
const name = names[i]; | |
if (name == "-") continue; | |
lessons.push({ | |
names: name.replace(/ [.+?]/g, "").split(", "), | |
number: numbers[i], | |
classesName: name | |
.match(/[(d+?)]/g) | |
?.map((n) => n.replace(/[|]/g, "")), | |
}); | |
} | |
return new ScheduleWeekLine({ | |
date: date2JSON(date), | |
lessons, | |
}); | |
}); | |
} | |
toJSON() { | |
return { | |
raw: this.raw, | |
date: this._date, | |
parsed: this.parsed.map((i) => i.toJSON()), | |
}; | |
} | |
} |
interface Credentials { | |
date: string; | |
lessons: { | |
names: string[]; | |
number: number; | |
classesName?: string[]; | |
}[]; | |
} | |
export default class ScheduleWeekLine { | |
lessons: Credentials["lessons"]; | |
private _date: string; | |
constructor(credentials: Credentials) { | |
this._date = credentials.date; | |
this.lessons = credentials.lessons; | |
} | |
get date() { | |
return new Date(this._date); | |
} | |
toJSON() { | |
return { | |
date: this._date, | |
lessons: this.lessons, | |
}; | |
} | |
} |
interface Credentials { | |
ver: string; | |
expiryDate: number; | |
accessToken: string; | |
} | |
export default class Session { | |
ver: string; | |
expiryDate: number; | |
accessToken: string; | |
constructor(credentials: Credentials) { | |
this.ver = credentials.ver; | |
this.expiryDate = credentials.expiryDate; | |
this.accessToken = credentials.accessToken; | |
} | |
isValid() { | |
return this.expiryDate - Date.now() > 0; | |
} | |
isExpired() { | |
return !this.isValid(); | |
} | |
} |
import NetSchoolApi from "@/NetSchoolApi"; | |
import NetSchoolApiSafe from "@/NetSchoolApi-safe"; | |
export default NetSchoolApi; | |
export const Safe = NetSchoolApiSafe; |
import NS from "@NS"; | |
import { sessionValid, studentIdValid } from "@/utils/checks"; | |
import AssignmentInfo from "@/classes/AssignmentInfo"; | |
export interface Credentials { | |
studentId?: number; | |
id: number; | |
} | |
export default async function (this: NS, credentials: Credentials) { | |
const { client } = await sessionValid.call(this); | |
let { id, studentId } = credentials; | |
studentId = studentIdValid.call(this, credentials.studentId).id; | |
return client | |
.get(`student/diary/assigns/${id}`, { params: { studentId } }) | |
.then((res) => res.json() as any) | |
.then((data) => new AssignmentInfo(data)); | |
} |
import NS from "@NS"; | |
import { sessionValid } from "@/utils/checks"; | |
import AssignmentTypes from "@/classes/AssignmentTypes"; | |
export default async function (this: NS) { | |
await sessionValid.call(this); | |
return this.client | |
.get("grade/assignment/types", { params: { all: false } }) | |
.then((res) => res.json() as any) | |
.then((data) => new AssignmentTypes(data)); | |
} |
import NS from "@NS"; | |
import Context from "@/classes/Context"; | |
import sysInfo from "./methods/sysInfo"; | |
import context from "./methods/context"; | |
import schoolInfo from "./methods/schoolInfo"; | |
import studentGrades from "./methods/studentGrades"; | |
export default async function (this: NS) { | |
const { client } = this; | |
const [ | |
{ server }, | |
{ year, user, server: server1, schoolId }, | |
{ user: user1, subjects }, | |
] = await Promise.all([ | |
sysInfo(client), | |
context(client), | |
studentGrades(client), | |
]); | |
return new Context({ | |
year, | |
user: { ...user, ...user1 }, | |
server: { ...server, ...server1 }, | |
school: { ...(await schoolInfo(client, schoolId)), id: schoolId }, | |
subjects, | |
}); | |
} |
import Client from "@/classes/Client"; | |
interface GlobalYear { | |
id: number; | |
name: string; | |
endDate: Date; | |
startDate: Date; | |
} | |
interface SchoolYear { | |
id: number; | |
name: string; | |
closed: string; | |
endDate: Date; | |
schoolId: number; | |
startDate: Date; | |
globalYear: GlobalYear; | |
} | |
interface User { | |
id: number; | |
name: string; | |
} | |
interface Organization { | |
id: number; | |
name: string; | |
} | |
interface ContextObject { | |
at: string; | |
user: User; | |
emId?: any; | |
roles: string[]; | |
rights: string[]; | |
userId: number; | |
version: string; | |
schoolId: number; | |
funcType: string; | |
dateFormat: string; | |
timeFormat: string; | |
schoolyear: SchoolYear; | |
productName: string; | |
versionDate?: any; | |
schoolYearId: number; | |
globalYearId: number; | |
userLanguage: string; | |
organization: Organization; | |
organizationName: string; | |
} | |
export default async function (client: Client) { | |
const data: ContextObject = await client | |
.get("context") | |
.then((res) => res.json() as any); | |
return { | |
year: { | |
id: data.schoolyear.id, | |
gId: data.schoolyear.globalYear.id, | |
name: data.schoolyear.name, | |
start: new Date(data.schoolyear.startDate), | |
end: new Date(data.schoolyear.endDate), | |
}, | |
user: { id: data.user.id, name: data.user.name }, | |
server: { dateFormat: data.dateFormat, timeFormat: data.timeFormat }, | |
schoolId: data.schoolId, | |
}; | |
} |
import Client from "@/classes/Client"; | |
interface LocationType { | |
id: number; | |
key: string; | |
name: string; | |
} | |
interface LocationInfo { | |
locationType: LocationType; | |
inProvinceCenter: boolean; | |
isProvinceSchoolInCity: boolean; | |
} | |
interface CommonInfo { | |
legalFormName: string; | |
legalFormName83: string; | |
typeName: string; | |
showFormName: boolean; | |
formName: string; | |
schoolName: string; | |
fullSchoolName: string; | |
schoolNumber: string; | |
foundingDate: Date; | |
independ: string; | |
additionalName: string; | |
mainSchool: string; | |
founders: string[]; | |
ownEducManagements: string[]; | |
status: string; | |
smallOrganization: string; | |
locationInfo: LocationInfo; | |
about: string; | |
emId?: any; | |
} | |
interface ManagementInfo { | |
director: string; | |
principalUVR: string; | |
principalAHC: string; | |
principalIT: string; | |
collegiateManagement: string; | |
} | |
interface ContactInfo { | |
stateProvinceName: string; | |
cityName: string; | |
districtName?: any; | |
postAddress: string; | |
phones: string; | |
fax: string; | |
email: string; | |
web: string; | |
addressesAdditionalBuildings: string; | |
juridicalAddress: string; | |
} | |
interface OtherInfo { | |
inn: string; | |
kpp: string; | |
ogrn: string; | |
okpo: string; | |
okato: string; | |
okogu: string; | |
okopf: string; | |
okfs: string; | |
okved: string; | |
specialization: string; | |
maxOccupancy: string; | |
maxOccupancyOnShift: string; | |
numberShifts: string; | |
referenceToCharter: string; | |
presenceOfPool: string; | |
barrierFreeEnvironment: string; | |
videoSurveillance: string; | |
linkToScanCopyLicenseEducation?: any; | |
socialPartnerShip: string; | |
timetable: string; | |
conditionsEducation: string; | |
projectTypeForSchool?: any; | |
} | |
interface BankDetails { | |
bankScore: string; | |
corrScore: string; | |
personalAccount: string; | |
bik: string; | |
note: string; | |
bankName: string; | |
bankKpp: string; | |
} | |
interface FoodPayDetails { | |
foodPayOrgName: string; | |
foodPayInn: string; | |
foodPayKpp: string; | |
foodPayBankName: string; | |
foodPayBankScore: string; | |
foodPayBankCorrScore: string; | |
foodPayBankBik: string; | |
foodPayBankKpp: string; | |
} | |
interface InternetConnectionInfo { | |
computersCount: string; | |
contentFilteringName: string; | |
internetSpeedUnderContract: string; | |
internetSpeedInFact: string; | |
internetProviderName: string; | |
internetAccessTechnology: string; | |
} | |
export interface CardObject { | |
commonInfo: CommonInfo; | |
managementInfo: ManagementInfo; | |
contactInfo: ContactInfo; | |
otherInfo: OtherInfo; | |
bankDetails: BankDetails; | |
foodPayDetails: FoodPayDetails; | |
internetConnectionInfo: InternetConnectionInfo; | |
} | |
export default async function (client: Client, id: number) { | |
const { commonInfo }: CardObject = await client | |
.get(`schools/${id}/card`) | |
.then((res) => res.json() as any); | |
return { name: commonInfo.schoolName, fullName: commonInfo.fullSchoolName }; | |
} |
import Client from "@/classes/Client"; | |
interface RelatedObject { | |
type: string; | |
ref: string; | |
} | |
interface Dependency { | |
relatedObject: RelatedObject; | |
relatedValue?: any; | |
condition: string; | |
} | |
interface Filter { | |
id: string; | |
title: string; | |
order: number; | |
filterType: string; | |
optionalFlag: boolean; | |
hideSingleOption: boolean; | |
hasSureCheckedFlag: boolean; | |
hideTitleFlag: boolean; | |
existStateProvider: boolean; | |
emptyText: string; | |
dependencies: Dependency[]; | |
} | |
interface FilterPanel { | |
filters: Filter[]; | |
} | |
interface Report { | |
id: string; | |
name: string; | |
group: string; | |
level: string; | |
order: number; | |
filterPanel: FilterPanel; | |
presentTypes: any[]; | |
} | |
interface Item { | |
title: string; | |
value: string; | |
} | |
interface Range { | |
start: Date; | |
end: Date; | |
} | |
interface DefaultRange { | |
start: Date; | |
end: Date; | |
} | |
export interface FilterSource { | |
items: Item[]; | |
defaultValue: string; | |
filterId: string; | |
nullText?: any; | |
minValue?: Date; | |
maxValue?: Date; | |
range: Range; | |
defaultRange: DefaultRange; | |
} | |
interface StudentGradesObject { | |
report: Report; | |
filterSources: FilterSource[]; | |
} | |
export default async function (client: Client) { | |
const { filterSources: data }: StudentGradesObject = await client | |
.get("reports/studentGrades") | |
.then((res) => res.json() as any); | |
const classes = | |
data | |
.find((f) => f.filterId == "PCLID_IUP") | |
?.items.map((c) => ({ | |
id: parseInt(c.value), | |
name: c.title, | |
value: c.value, | |
})) ?? []; | |
const subjects = | |
data | |
.find((f) => f.filterId == "SGID") | |
?.items.map((s) => ({ | |
id: parseInt(s.value), | |
name: s.title, | |
value: s.value, | |
})) ?? []; | |
const students = | |
data | |
.find((f) => f.filterId == "SID") | |
?.items.map((s) => ({ | |
id: parseInt(s.value), | |
name: s.title, | |
value: s.value, | |
})) ?? []; | |
const termFilters = data.find((f) => f.filterId == "TERMID"); | |
const terms = !termFilters | |
? [] | |
: await Promise.all( | |
termFilters.items.map(async (t) => { | |
const filters = await client | |
.post("v2/reports/studentgrades/initfilters", { | |
body: JSON.stringify({ | |
params: null, | |
selectedData: [{ filterId: "TERMID", filterValue: t.value }], | |
}), | |
headers: { | |
"Content-Type": "application/json", | |
}, | |
}) | |
.then((res) => res.json() as Promise<FilterSource[]>); | |
const termDates = filters.find((f) => f.filterId == "period")?.range; | |
if (!termDates) throw new Error("Не удалось получить даты четверти"); | |
return { | |
id: parseInt(t.value), | |
name: t.title, | |
value: t.value, | |
isCurrent: termFilters.defaultValue == t.value, | |
start: new Date(termDates?.start), | |
end: new Date(termDates?.end), | |
}; | |
}) | |
); | |
return { | |
user: { | |
terms, | |
classes, | |
students, | |
}, | |
subjects, | |
}; | |
} |
import Client from "@/classes/Client"; | |
export default async function (client: Client) { | |
const text = await client.get("sysInfo").then((res) => res.text()); | |
const id = text.match(/Id: (.+)/)?.[1] ?? ""; | |
const version = text.match(/Версия системы: (.+)/)?.[1] ?? ""; | |
return { server: { id, version } }; | |
} |
import NS from "@NS"; | |
import Diary, { DiaryObject } from "@/classes/Diary"; | |
import { sessionValid, dateValid, studentIdValid } from "@/utils/checks"; | |
import { AttachmentObject, AnswerFilesObject } from "@/classes/Attachment"; | |
type AttachmentRaw = { | |
assignmentId: number; | |
attachments: AttachmentObject[]; | |
answerFiles: AnswerFilesObject[]; | |
}; | |
export interface Credentials { | |
studentId?: number; | |
start?: Date; | |
end?: Date; | |
} | |
export default async function (this: NS, credentials: Credentials = {}) { | |
const { client, context } = await sessionValid.call(this); | |
let { studentId, start, end } = credentials; | |
studentId = studentIdValid.call(this, studentId).id; | |
if (start && end) dateValid.call(this, start, end); | |
else { | |
const { weekStart } = await client | |
.get("student/diary/init") | |
.then((res) => res.json() as any); | |
start = new Date(weekStart); | |
end = new Date(weekStart); | |
end.setDate(end.getDate() + 7); | |
} | |
const diaryRaw = await client | |
.get("student/diary", { | |
params: { | |
yearId: context.year.id, | |
studentId, | |
weekEnd: end.toJSON().replace(/T.+/, ""), | |
weekStart: start.toJSON().replace(/T.+/, ""), | |
}, | |
}) | |
.then((res) => res.json() as unknown as DiaryObject); | |
const assignments: number[] = []; | |
diaryRaw.weekDays?.forEach((day) => { | |
day.lessons.forEach((lesson) => { | |
lesson.assignments?.forEach((assignment) => { | |
assignments.push(assignment.id); | |
}); | |
}); | |
}); | |
const attachmentsRaw = await client | |
.post("student/diary/get-attachments", { | |
params: { studentId }, | |
headers: { | |
"Content-Type": "application/json", | |
}, | |
body: JSON.stringify({ | |
assignId: assignments, | |
}), | |
}) | |
.then((res) => res.json() as unknown as AttachmentRaw[]); | |
const diaryObject = diaryRaw; | |
if (diaryObject.weekDays) | |
diaryObject.weekDays = diaryObject.weekDays.map((day) => ({ | |
...day, | |
lessons: day.lessons.map((lesson) => ({ | |
...lesson, | |
assignments: lesson.assignments?.map((assignment) => { | |
const item = attachmentsRaw.find( | |
({ assignmentId }) => assignment.id === assignmentId | |
); | |
return { | |
...assignment, | |
attachments: item?.attachments ?? [], | |
answerFiles: item?.answerFiles ?? [], | |
}; | |
}), | |
})), | |
})); | |
return new Diary(diaryObject); | |
} |
import NS from "@NS"; | |
import { sessionValid, studentIdValid } from "@/utils/checks"; | |
export interface Credentials { | |
studentId?: number; | |
assignId: number; | |
id: number; | |
} | |
export default async function (this: NS, credentials: Credentials) { | |
const { client } = await sessionValid.call(this); | |
let { id, assignId, studentId } = credentials; | |
studentId = studentIdValid.call(this, studentId).id; | |
const response = await client | |
.post("student/diary/get-attachments", { | |
params: { studentId }, | |
headers: { | |
Referer: client.join("../angular/school/studentdiary/"), | |
"Content-Type": "application/json", | |
}, | |
body: JSON.stringify({ | |
assignId: [assignId], | |
}), | |
}) | |
.then((res) => res.text()); | |
if (!response.includes(id.toString())) | |
throw new Error(`Нет файла ${id} для задания ${assignId}`); | |
return client.get(`attachments/${id}`).then((res) => res.buffer()); | |
} |
import NS from "@NS"; | |
import { InitRequest } from "@/classes/Client"; | |
import { sessionValid } from "@/utils/checks"; | |
export default async function (this: NS, url: string, init?: InitRequest) { | |
const { client } = await sessionValid.call(this); | |
return client.request("../" + url, init); | |
} |
import NS from "@NS"; | |
import Grades from "@/classes/Grades"; | |
import { date2JSON } from "@/utils/dateNum"; | |
import { | |
sessionValid, | |
dateValid, | |
termIdValid, | |
classIdValid, | |
studentIdValid, | |
termDateValid, | |
} from "@/utils/checks"; | |
export interface Credentials { | |
/** ID предмета */ | |
subjectId: number; | |
/** Дата начала отчета (если `termId` не указан, либо равен `-1`, то должна быть в пределах учебного года, иначе в пределах выбранной четверти) */ | |
start?: Date; | |
/** Дата окончания отчета (если `termId` не указан, либо равен `-1`, то должна быть в пределах учебного года, иначе в пределах выбранной четверти) */ | |
end?: Date; | |
/** ID четверти (берется текущая четверть) */ | |
termId?: number; | |
/** ID класса (обычно берется первый из массива) */ | |
classId?: number; | |
/** ID студента (обычно берется первый из массива) */ | |
studentId?: number; | |
/** Какой протокол использовать | |
* | |
* 0 - Web Sockets, 1 - Long Polling | |
* | |
* если отсутствует, то используется Web Sockets или Long Polling (в зависимости от версии сервера) | |
*/ | |
transport?: 0 | 1; | |
} | |
export default async function grades(this: NS, credentials: Credentials) { | |
const { context } = await sessionValid.call(this); | |
let { subjectId, start, end, termId, classId, studentId, transport } = | |
credentials; | |
// Проверяем существует ли предмет | |
if (!context.subjectExists(subjectId)) | |
throw new Error(`Предмета ${subjectId} не существует`); | |
// Проверяем валидность данных | |
const termData = termIdValid.call(this, termId); | |
const classData = classIdValid.call(this, classId); | |
const studentData = studentIdValid.call(this, studentId); | |
// Если не указаны даты, то берем текущий учебный год | |
const termDates = await termDateValid.call(this, termData.id, start, end); | |
start = termDates.start; | |
end = termDates.end; | |
const [types, htmlText] = await Promise.all([ | |
this.assignmentTypes(), | |
this.reportFile({ | |
url: "reports/studentgrades/queue", | |
filters: [ | |
{ | |
filterId: "SID", | |
filterValue: studentData.value, | |
}, | |
{ | |
filterId: "PCLID_IUP", | |
filterValue: classData.value, | |
}, | |
{ | |
filterId: "SGID", | |
filterValue: subjectId, | |
}, | |
{ | |
filterId: "TERMID", | |
filterValue: termData.value, | |
}, | |
{ | |
filterId: "period", | |
filterValue: date2JSON(start) + " - " + date2JSON(end), | |
}, | |
], | |
transport, | |
}), | |
]); | |
return new Grades({ | |
types, | |
htmlText, | |
hasTerms: context.user.terms.length > 0, | |
}); | |
} |
import NS from "@NS"; | |
import Info from "@/classes/Info"; | |
import { sessionValid } from "@/utils/checks"; | |
export default async function (this: NS) { | |
const { client } = await sessionValid.call(this); | |
return client | |
.get("mysettings") | |
.then((res) => res.json()) | |
.then((data) => new Info(data)); | |
} |
import NS from "@NS"; | |
import Journal from "@/classes/Journal"; | |
import { date2JSON } from "@/utils/dateNum"; | |
import { | |
classIdValid, | |
sessionValid, | |
studentIdValid, | |
termDateValid, | |
termIdValid, | |
} from "@/utils/checks"; | |
export interface Credentials { | |
/** Дата начала отчета (если `termId` не указан, либо равен `-1`, то должна быть в пределах учебного года, иначе в пределах выбранной четверти) */ | |
start?: Date; | |
/** Дата окончания отчета (если `termId` не указан, либо равен `-1`, то должна быть в пределах учебного года, иначе в пределах выбранной четверти) */ | |
end?: Date; | |
/** ID четверти (берется текущая четверть) */ | |
termId?: number; | |
/** ID класса (обычно берется первый из массива) */ | |
classId?: number; | |
/** ID студента (обычно берется первый из массива) */ | |
studentId?: number; | |
/** Какой протокол использовать | |
* | |
* 0 - Web Sockets, 1 - Long Polling | |
* | |
* если отсутствует, то используется Web Sockets или Long Polling (в зависимости от версии сервера) | |
*/ | |
transport?: 0 | 1; | |
} | |
export default async function journal(this: NS, credentials: Credentials = {}) { | |
const { context } = await sessionValid.call(this); | |
let { start, end, termId, classId, studentId, transport } = credentials; | |
// Проверяем валидность данных | |
const termData = termIdValid.call(this, termId); | |
const classData = classIdValid.call(this, classId); | |
const studentData = studentIdValid.call(this, studentId); | |
// Если не указаны даты, то берем текущий учебный год | |
const termDates = await termDateValid.call(this, termData.id, start, end); | |
start = termDates.start; | |
end = termDates.end; | |
// Получаем текст отчета | |
const htmlText = await this.reportFile({ | |
url: | |
context.compareServerVersion("5.24.0.0") == -1 | |
? "reports/studenttotal/queue" | |
: "v2/reports/studenttotal/queue", | |
filters: [ | |
{ | |
filterId: "SID", | |
filterValue: studentData.value, | |
}, | |
{ | |
filterId: "PCLID", | |
filterValue: classData.id.toString(), | |
}, | |
{ | |
filterId: "TERMID", | |
filterValue: termData.value, | |
}, | |
{ | |
filterId: "period", | |
filterValue: date2JSON(start) + " - " + date2JSON(end), | |
}, | |
], | |
transport, | |
}); | |
return new Journal({ | |
htmlText, | |
terms: context.user.terms, | |
subjects: context.subjects, | |
}); | |
} |
import NS from "@NS"; | |
import Session from "@/classes/Session"; | |
import signIn from "./methods/signIn"; | |
export default async function (this: NS) { | |
const { client, credentials } = this; | |
const { at: accessToken, ver, timeOut } = await signIn(client, credentials); | |
this.session = new Session({ | |
ver, | |
accessToken, | |
expiryDate: Date.now() + timeOut, | |
}); | |
} |
import Client from "@/classes/Client"; | |
export interface AuthData { | |
lt: string; | |
ver: string; | |
salt: string; | |
} | |
export default async function (client: Client) { | |
const data: AuthData = await client | |
.post("/auth/getData") | |
.then((res) => res.json() as any) | |
.catch(() => ({})); | |
if (!data.lt || !data.ver || !data.salt) { | |
throw new Error("Сетевой не вернул данные для авторизации."); | |
} else { | |
return data; | |
} | |
} |
import { encode } from "iconv-lite"; | |
import { createHash } from "crypto"; | |
import { PasswordType } from "@NS"; | |
function md5(str: string): string { | |
const buf = encode(str, "windows-1251"); | |
return createHash("md5").update(buf).digest("hex"); | |
} | |
export default function (salt: string, password: PasswordType) { | |
const hash = typeof password === "string" ? md5(password) : password.hash; | |
const pw2 = md5(salt + hash); | |
const pw = pw2.substring(0, password.length); | |
return { pw, pw2 }; | |
} |
import Client from "@/classes/Client"; | |
interface SchoolSearchData { | |
id: number; | |
inn: string; | |
ogrn: string; | |
name: string; | |
cityId: number; | |
address: null; | |
shortName: string; | |
provinceId: number; | |
} | |
declare module SchoolPreLoginData { | |
export interface Country { | |
id: number; | |
name: string; | |
} | |
export interface State { | |
id: number; | |
name: string; | |
} | |
export interface Province { | |
kladr: string; | |
id: number; | |
name: string; | |
} | |
export interface City { | |
atoTypeName: string; | |
id: number; | |
name: string; | |
} | |
export interface Func { | |
id: number; | |
name: string; | |
} | |
export interface School { | |
id: number; | |
name: string; | |
} | |
export interface RootObject { | |
countries: Country[]; | |
cid: number; | |
states: State[]; | |
sid: number; | |
provinces: Province[]; | |
pid: number; | |
cities: City[]; | |
cn: number; | |
funcs: Func[]; | |
sft: number; | |
schools: School[]; | |
scid: number; | |
hlevels?: any; | |
ems?: any; | |
} | |
} | |
export interface SchoolInfo { | |
cid: number; | |
sid: number; | |
pid: number; | |
cn: number; | |
sft: number; | |
scid: number; | |
} | |
export default async function (client: Client, school: string | number) { | |
const data = await Promise.all([ | |
client | |
.get("schools/search") | |
.catch(() => client.get("schools/search?name=У")) | |
.then((res) => res.json() as Promise<SchoolSearchData[]>), | |
client | |
.get("prepareloginform") | |
.then((res) => res.json() as Promise<SchoolPreLoginData.RootObject>), | |
]).catch((e) => { | |
console.error(e); | |
throw new Error("Не удалось получить список школ"); | |
}); | |
if (typeof school === "string") school = school.toLowerCase().trim(); | |
const result = data[0].find(({ id, name, shortName }) => | |
typeof school !== "string" | |
? id == school | |
: name.toLowerCase().trim() == school || | |
shortName.toLowerCase().trim() == school | |
); | |
if (!result) throw new Error("Не удалось найти школу"); | |
if (typeof school === "string") { | |
console.info( | |
"[46m[30m INFO [0m", | |
`ID школы: ${result.id}. Советуем в будущем использовать ID школы вместо названия.` | |
); | |
} | |
return { | |
cid: data[1].cid, | |
sid: data[1].sid, | |
pid: result.provinceId, | |
cn: result.cityId, | |
sft: 2, | |
scid: result.id, | |
}; | |
} |
import { Credentials } from "@NS"; | |
import Client from "@/classes/Client"; | |
import authData from "./authData"; | |
import schoolInfo from "./schoolInfo"; | |
import passwordHash from "./passwordHash"; | |
interface SignInObject { | |
at: string; | |
timeOut: number; | |
} | |
export interface SignIn { | |
at: string; | |
ver: string; | |
timeOut: number; | |
} | |
export default async function ( | |
client: Client, | |
credentials: Credentials | |
): Promise<SignIn> { | |
const { login: un, password, school: schoolCr } = credentials; | |
// Сохранение куки | |
await client.get("logindata"); | |
const [{ lt, ver, salt }, school] = await Promise.all([ | |
authData(client), | |
schoolInfo(client, schoolCr), | |
]); | |
const { at, timeOut }: SignInObject = await client | |
.post( | |
"/login", | |
Client.formData({ | |
un, | |
lt, | |
ver, | |
loginType: 1, | |
...school, | |
...passwordHash(salt, password), | |
}) | |
) | |
.then((res) => res.json() as any) | |
.catch((e) => { | |
console.error(e); | |
throw new Error("Не удалось войти. Проверьте введение данные."); | |
}); | |
return { at, ver, timeOut }; | |
} |
import NS from "@NS"; | |
import signOut from "./signOut"; | |
export default async function (this: NS) { | |
const { session } = this; | |
if (session) await signOut.call(this, session); | |
this.session = null; | |
} |
import NS from "@NS"; | |
import Client from "@/classes/Client"; | |
import Session from "@/classes/Session"; | |
export default function (this: NS, session: Session) { | |
const { accessToken: at, ver } = session; | |
const url = | |
this.context?.compareServerVersion("5.29.0.0") !== -1 | |
? "auth/logout" | |
: "../asp/logout.asp"; | |
return this.client.post(url, Client.formData({ at, ver })); | |
} |
import NS from "@NS"; | |
import Client from "@/classes/Client"; | |
import { sessionValid, studentIdValid } from "@/utils/checks"; | |
export interface Credentials { | |
studentId?: number; | |
} | |
export default async function (this: NS, credentials: Credentials = {}) { | |
const { client, session } = await sessionValid.call(this); | |
const { accessToken: at, ver } = session; | |
const userId = studentIdValid.call(this, credentials.studentId); | |
// Просто получаем куки | |
await client.post( | |
"../asp/MySettings/MySettings.asp", | |
Client.formData({ at, ver }, { params: { at } }) | |
); | |
return client | |
.get("users/photo", { params: { at, ver, userId } }) | |
.then((res) => res.buffer()); | |
} |
import NS from "@NS"; | |
import Client from "@/classes/Client"; | |
import { sessionValid } from "@/utils/checks"; | |
import legacy from "./legacy"; | |
//? Типы данных | |
/** Фильтры отчета */ | |
export type Filter = { | |
filterId: string; | |
filterValue: string | number; | |
}; | |
/** Параметры отчета */ | |
export type Param = { | |
name: string; | |
value: string | number; | |
}; | |
export type Message = | |
| { | |
type: 1; | |
target: "progress"; | |
arguments: Array<{ | |
taskId: number; | |
status: string; | |
}>; | |
} | |
| { | |
type: 1; | |
target: "complete"; | |
arguments: Array<{ | |
taskId: number; | |
data: string; | |
componentId: string; | |
}>; | |
} | |
| { | |
type: 3; | |
invocationId: string; | |
result: { | |
success: boolean; | |
message: any; | |
}; | |
} | |
| { error: string }; | |
/** Параметры для инициализации */ | |
export interface Credentials { | |
/** Ссылка для запроса */ | |
url: string; | |
/** Фильтры запроса */ | |
filters: Filter[]; | |
/** Идентификатор года отчета */ | |
yearId?: number; | |
/** Время ожидания ответа */ | |
timeout?: number; | |
/** Какой протокол использовать | |
* | |
* 0 - Web Sockets, 1 - Long Polling | |
* | |
* если отсутствует, то используется Web Sockets или Long Polling (в зависимости от версии сервера) | |
* @deprecated в новых версиях сервера не используется longPolling | |
*/ | |
transport?: 0 | 1; | |
} | |
//? Функции помощники | |
/** | |
* Формируем задачу | |
* @param client клиент | |
* @param url ссылка для запроса | |
* @param params параметры запроса | |
* @param filters фильтры запроса | |
* @returns номер задачи | |
*/ | |
export async function getTaskId( | |
client: Client, | |
url: string, | |
params: Param[], | |
filters: Filter[] | |
): Promise<number> { | |
return await client | |
.post(url, { | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify({ params, selectedData: filters }), | |
}) | |
.then((res) => res.json()) | |
.then(({ taskId }) => taskId); | |
} | |
/** | |
* Форматируем сообщение для WebSocket (с блядскими символами) | |
* @param message сообщение (объект) | |
* @param send нужно ли преобразовать в JSON | |
* @returns сообщение (строка или буфер) | |
*/ | |
function formatMsg(message: any, send: boolean = false): Buffer | any { | |
if (send) return Buffer.from(JSON.stringify(message) + ""); | |
else return JSON.parse(message.toString().replace("", "")); | |
} | |
/** | |
* Форматируем сообщение | |
* @param msg сообщение от сервера | |
*/ | |
function messageHandler(msg: Message) { | |
// Обработка сообщения | |
const data = { | |
error: undefined as string | undefined, | |
fileCode: undefined as string | undefined, | |
}; | |
// Если отчет готов | |
if ("type" in msg && msg.type === 1 && msg.target === "complete") { | |
data.fileCode = msg.arguments.find((arg) => { | |
const keys = Object.keys(arg); | |
return keys.includes("taskId") && keys.includes("data"); | |
})?.data; | |
} | |
// Если произошла ошибка | |
if ("error" in msg) data.error = msg.error; | |
return data; | |
} | |
/** | |
* Получение отчета с помощью Web Sockets | |
* @param client клиент | |
* @param context контекст | |
* @param params параметры запроса | |
* @param taskId номер задачи | |
* @param timeout время ожидания ответа | |
* @returns идентификатор отчета | |
*/ | |
async function webSocketsConnection( | |
client: Client, | |
at: string, | |
taskId: number, | |
timeout: number | |
) { | |
// Создаем WebSocket | |
const ws = client.ws("queueHub", { params: { at } }); | |
// Обрабатывает сообщения | |
return new Promise<string>((resolve, reject) => { | |
let fileCode: string | undefined; | |
let timeoutId = 0 as unknown as NodeJS.Timeout; | |
// Открываем соединение | |
ws.once("open", async () => { | |
// Устанавливаем тайм-аут | |
if (timeout > 0) timeoutId = setTimeout(() => ws.close(4010), timeout); | |
// Открываем соединение | |
ws.send(formatMsg({ protocol: "json", version: 1 }, true)); | |
// Уведомляем сервер о задаче | |
ws.send( | |
formatMsg( | |
{ | |
arguments: [taskId, "report-v2"], | |
invocationId: "0", | |
target: "startTask", | |
type: 1, | |
}, | |
true | |
) | |
); | |
}); | |
// Слушаем сообщения | |
ws.on("message", (msg: Buffer) => { | |
// Пытаемся преобразовать сообщение в JSON | |
let data: { error?: string; fileCode?: string }; | |
try { | |
data = messageHandler(formatMsg(msg)); | |
} catch (e) { | |
return; | |
} | |
// Обрабатываем ошибку | |
if (data.error) { | |
console.error(data.error); | |
ws.close(4003, data.error); | |
} | |
// Обрабатываем | |
if (data.fileCode) { | |
fileCode = data.fileCode; | |
ws.close(4000); | |
} | |
}); | |
// Обрабатываем ошибки соединения | |
ws.once("error", (err) => ws.close(4002, err.message)); | |
// Закрываем соединение | |
ws.once("close", async (code, message) => { | |
clearTimeout(timeoutId); | |
switch (code) { | |
// Если соединение закрыто успешно | |
case 1000: | |
case 4000: | |
if (!fileCode) return reject(new Error("Server didn't respond")); | |
resolve(fileCode); | |
break; | |
// Если произошла ошибка инициализации | |
case 4001: | |
reject(new Error("Error during initialization")); | |
break; | |
// Если произошла ошибка с соединением | |
case 4002: | |
reject(new Error("Error in socket.\nError: " + message)); | |
break; | |
// Если произошла ошибка с задачей | |
case 4003: | |
reject(new Error("Error in task.\nError: " + message)); | |
break; | |
// Если вышло время ожидания | |
case 4010: | |
reject(new Error("Response time expired")); | |
break; | |
// Неизвестная ошибка | |
default: | |
reject(new Error("Unknown error.\nError: " + message)); | |
} | |
}); | |
}); | |
} | |
export default async function reportFile( | |
this: NS, | |
credentials: Credentials | |
): Promise<string> { | |
const { url, filters, yearId, timeout = 6e4, transport } = credentials; | |
const { client, session, context } = await sessionValid.call(this); | |
// Параметры запроса | |
const params: Param[] = [ | |
{ name: "DATEFORMAT", value: context.server.dateFormat }, | |
{ | |
name: "SCHOOLYEARID", | |
value: (yearId ? yearId : context.year.id).toString(), | |
}, | |
{ name: "SERVERTIMEZONE", value: 3 }, | |
{ name: "FULLSCHOOLNAME", value: context.school.fullName }, | |
]; | |
// Получаем идентификатор отчета | |
let fileCode: string | undefined; | |
// Если версия сервера меньше | |
if (context.compareServerVersion("5.29.0.0") == -1) { | |
fileCode = await legacy.call(this, params, credentials); | |
} | |
// Если версия сервера больше или равна | |
else { | |
// Получаем идентификатор задачи | |
const taskId = await getTaskId(client, url, params, filters); | |
// Получаем идентификатор отчета | |
fileCode = await webSocketsConnection( | |
client, | |
session.accessToken, | |
taskId, | |
timeout | |
); | |
} | |
// Возвращаем отчет | |
if (!fileCode) throw new Error("Server didn't respond"); | |
return (await client.get("files/" + fileCode)).text(); | |
} |
import NS from "@NS"; | |
import Client from "@/classes/Client"; | |
import Context from "@/classes/Context"; | |
import { sessionValid } from "@/utils/checks"; | |
import { Credentials, Param, getTaskId } from "./index"; | |
//? Типы данных | |
/** Сообщения от сервера */ | |
type Message = { | |
/** Идентификатор сообщения */ | |
C: string; | |
/** Сообщения */ | |
M: { | |
/** Какая-то служебная фигня */ | |
H: "QueueHub"; | |
/** Тип сообщения */ | |
M: "error" | "progress" | "complete"; | |
/** Данные сообщения */ | |
A: { | |
/** Какая-то служебная фигня */ | |
ComponentId: string; | |
/** Сами данные */ | |
Data: string; | |
/** Номер задачи */ | |
TaskId: number; | |
/** Детали */ | |
Details?: string; | |
}[]; | |
}[]; | |
}; | |
/** Параметры для запуска формирования отчета */ | |
type Query = { | |
_: string; | |
at: string; | |
tid?: string; | |
transport: "webSockets" | "longPolling"; | |
clientProtocol: number; | |
connectionData: string; | |
connectionToken?: string; | |
}; | |
/** Ответ сервера на открытие соединения */ | |
interface NegotiateObject { | |
Url: string; | |
ConnectionId: string; | |
TryWebSockets: boolean; | |
ProtocolVersion: string; | |
ConnectionToken: string; | |
KeepAliveTimeout: number; | |
DisconnectTimeout: number; | |
ConnectionTimeout: number; | |
TransportConnectTimeout: number; | |
} | |
//? Функции помощники | |
/** | |
* Получаем токен соединения | |
* @param client клиент | |
* @param params параметры для запроса | |
*/ | |
async function negotiate(client: Client, params: Query) { | |
return client | |
.get("signalr/negotiate", { params }) | |
.then((res) => res.json() as Promise<NegotiateObject>); | |
} | |
/** | |
* Форматируем сообщение | |
* @param msg сообщение от сервера | |
*/ | |
function messageHandler(msg: Message) { | |
const { C: messageId, M } = msg; | |
let error = M.find((m) => m.M === "error")?.A[0].Details; | |
let fileCode = M.find((m) => m.M === "complete")?.A[0].Data; | |
return { error, fileCode, messageId }; | |
} | |
/** | |
* Уведомляем сервер о открытии соединения | |
* @param client клиент | |
* @param params параметры для запроса | |
*/ | |
async function startConnection(client: Client, params: Query) { | |
return client | |
.get("signalr/start", { params }) | |
.then((res) => res.json() as Promise<{ Response: string }>) | |
.then(({ Response }) => { | |
if (Response !== "started") | |
throw new Error( | |
"Соединение не удалось запустить, причина в том: " + Response | |
); | |
else return true; | |
}); | |
} | |
/** | |
* Получение отчета с помощью Web Sockets | |
* @param client клиент | |
* @param context контекст | |
* @param params параметры запроса | |
* @param taskId номер задачи | |
* @param timeout время ожидания ответа | |
* @returns идентификатор отчета | |
*/ | |
async function webSocketsConnection( | |
client: Client, | |
context: Context, | |
params: Query, | |
taskId: number, | |
timeout: number | |
) { | |
// Создаем WebSocket | |
const ws = client.ws("signalr/connect", { params }); | |
// Обрабатывает сообщения | |
return new Promise<string>((resolve, reject) => { | |
let fileCode: string | undefined; | |
let timeoutId = 0 as unknown as NodeJS.Timeout; | |
// Открываем соединение | |
ws.once("open", async () => { | |
// Открываем соединение | |
await startConnection(client, params).catch(() => ws.close(4001)); | |
// Устанавливаем тайм-аут | |
if (timeout > 0) timeoutId = setTimeout(() => ws.close(4010), timeout); | |
// Уведомляем сервер о задаче | |
const msg: (string | number)[] = [taskId]; | |
if (context.compareServerVersion("5.24.0.0") != -1) msg.push("report-v2"); | |
ws.send( | |
JSON.stringify({ | |
I: 0, | |
H: "queuehub", | |
M: "StartTask", | |
A: msg, | |
}) | |
); | |
}); | |
// Слушаем сообщения | |
ws.on("message", (msg: string) => { | |
// Пытаемся преобразовать сообщение в JSON | |
let data: { error?: string; fileCode?: string; messageId: string }; | |
try { | |
data = messageHandler(JSON.parse(msg)); | |
} catch (e) { | |
return; | |
} | |
// Обрабатываем ошибку | |
if (data.error) { | |
console.error(data.error); | |
ws.close(4003, data.error); | |
} | |
// Обрабатываем | |
if (data.fileCode) { | |
fileCode = data.fileCode; | |
ws.close(4000); | |
} | |
}); | |
// Обрабатываем ошибки соединения | |
ws.once("error", (err) => ws.close(4002, err.message)); | |
// Закрываем соединение | |
ws.once("close", async (code, message) => { | |
clearTimeout(timeoutId); | |
await client.post("signalr/abort", { params }); | |
switch (code) { | |
// Если соединение закрыто успешно | |
case 1000: | |
case 4000: | |
if (!fileCode) return reject(new Error("Server didn't respond")); | |
resolve(fileCode); | |
break; | |
// Если произошла ошибка инициализации | |
case 4001: | |
reject(new Error("Error during initialization")); | |
break; | |
// Если произошла ошибка с соединением | |
case 4002: | |
reject(new Error("Error in socket.\nError: " + message)); | |
break; | |
// Если произошла ошибка с задачей | |
case 4003: | |
reject(new Error("Error in task.\nError: " + message)); | |
break; | |
// Если вышло время ожидания | |
case 4010: | |
reject(new Error("Response time expired")); | |
break; | |
// Неизвестная ошибка | |
default: | |
reject(new Error("Unknown error.\nError: " + message)); | |
} | |
}); | |
}); | |
} | |
/** | |
* Получение отчета с помощью Long Polling | |
* @param client клиент | |
* @param context контекст | |
* @param params параметры запроса | |
* @param taskId номер задачи | |
* @param timeout время ожидания ответа | |
* @returns идентификатор отчета | |
*/ | |
async function longPollingConnection( | |
client: Client, | |
context: Context, | |
params: Query, | |
taskId: number, | |
timeout: number | |
) { | |
// Создаем соединение | |
let messageId = await client | |
.post("signalr/connect", { params }) | |
.then((res) => res.json()) | |
.then(({ C }) => C); | |
// Открываем соединение | |
let fileCode: string | undefined; | |
let timeoutId = 0 as unknown as NodeJS.Timeout; | |
let timeoutIsOut = false; | |
await startConnection(client, params); | |
// Устанавливаем тайм-аут | |
if (timeout > 0) timeoutId = setTimeout(() => (timeoutIsOut = true), timeout); | |
// Уведомляем сервер о задаче | |
const msg: (string | number)[] = [taskId]; | |
if (context.compareServerVersion("5.24.0.0") != -1) msg.push("report-v2"); | |
await client.post("signalr/send", { | |
params, | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", | |
}, | |
body: | |
"data=" + | |
JSON.stringify({ | |
I: 0, | |
H: "queuehub", | |
M: "StartTask", | |
A: msg, | |
}), | |
}); | |
// Получаем сообщения | |
while (!timeoutIsOut && !fileCode) { | |
// Получаем сообщение | |
let data = await client | |
.post("signalr/poll", { | |
params, | |
...Client.formData({ messageId }), | |
}) | |
.then((res) => res.json()) | |
.then(messageHandler); | |
messageId = data.messageId; | |
// Обрабатываем ошибку | |
if (data.error) { | |
console.error(data.error); | |
throw new Error("Error in task.\nError: " + data.error); | |
} | |
// Обрабатываем идентификатор отчета | |
if (data.fileCode) fileCode = data.fileCode; | |
} | |
if (timeoutIsOut) throw new Error("Response time expired"); | |
if (!fileCode) throw new Error("Server didn't respond"); | |
clearTimeout(timeoutId); | |
return fileCode; | |
} | |
export default async function getFileCode( | |
this: NS, | |
params: Param[], | |
credentials: Credentials | |
) { | |
const { url, filters, timeout = 6e4, transport } = credentials; | |
const { client, session, context } = await sessionValid.call(this); | |
// Данные для запроса | |
const query: Query = { | |
_: session.ver, | |
at: session.accessToken, | |
transport: "webSockets", | |
clientProtocol: 1.5, | |
connectionData: '[{"name":"queuehub"}]', | |
}; | |
// Получаем токен подключения | |
const { ConnectionId, ConnectionToken } = await negotiate(client, query); | |
query.tid = ConnectionId[0]; | |
query.connectionToken = ConnectionToken; | |
// Получаем номер задачи | |
const taskId = await getTaskId(client, url, params, filters); | |
// Получаем идентификатор отчета | |
let fileCode: string | undefined; | |
if ( | |
transport == 0 || | |
(!transport && context.compareServerVersion("5.24.0.0") == -1) | |
) { | |
query.transport = "webSockets"; | |
fileCode = await webSocketsConnection( | |
client, | |
context, | |
query, | |
taskId, | |
timeout | |
); | |
} else if ( | |
transport == 1 || | |
(!transport && context.compareServerVersion("5.24.0.0") > -1) | |
) { | |
query.transport = "longPolling"; | |
fileCode = await longPollingConnection( | |
client, | |
context, | |
query, | |
taskId, | |
timeout | |
); | |
} | |
return fileCode; | |
} |
import NS from "@NS"; | |
import Client from "@/classes/Client"; | |
import ScheduleDay from "@/classes/ScheduleDay"; | |
import { date2str } from "@/utils/dateNum"; | |
import { sessionValid, dateValid, classIdValid } from "@/utils/checks"; | |
export interface Credentials { | |
date?: Date; | |
classId?: number; | |
} | |
export default async function (this: NS, credentials: Credentials = {}) { | |
const { client, session } = await sessionValid.call(this); | |
const { accessToken: at, ver } = session; | |
let { date, classId } = credentials; | |
if (!date) date = new Date(); | |
else dateValid.call(this, date); | |
const htmlText = await client | |
.post( | |
"../asp/Calendar/DayViewS.asp", | |
Client.formData({ | |
at, | |
ver, | |
date: date2str(date), | |
PCLID_IUP: classIdValid.call(this, classId) + "_0", | |
LoginType: 0, | |
}) | |
) | |
.then((res) => res.text()); | |
return new ScheduleDay({ | |
date: date.toJSON().replace(/T.+/, "T00:00"), | |
htmlText, | |
}); | |
} |
import NS from "@NS"; | |
import Client from "@/classes/Client"; | |
import ScheduleWeek from "@/classes/ScheduleWeek"; | |
import { date2str } from "@/utils/dateNum"; | |
import { sessionValid, dateValid, classIdValid } from "@/utils/checks"; | |
export interface Credentials { | |
date?: Date; | |
classId?: number; | |
} | |
export default async function (this: NS, credentials: Credentials = {}) { | |
const { client, session } = await sessionValid.call(this); | |
const { accessToken: at, ver } = session; | |
let { date, classId } = credentials; | |
if (!date) date = new Date(); | |
else dateValid.call(this, date); | |
const htmlText = await client | |
.post( | |
"../asp/Calendar/WeekViewTimeS.asp", | |
Client.formData({ | |
at, | |
ver, | |
date: date2str(date), | |
PCLID_IUP: classIdValid.call(this, classId) + "_0", | |
LoginType: 0, | |
}) | |
) | |
.then((res) => res.text()); | |
return new ScheduleWeek({ | |
htmlText, | |
}); | |
} |
import NS from "@NS"; | |
export default async function (this: NS) { | |
const { client, session } = this; | |
if (!session) return false; | |
const { accessToken: token } = session; | |
return client | |
.get("context/expired", { params: { token } }) | |
.then((res) => res.json()) | |
.then((b) => (typeof b == "boolean" ? !b : false)); | |
} |
/** | |
* Этот файл содержит функции, необходимые для проверки данных. | |
* Они часто очень просты, но они слишком распространены в методах. | |
*/ | |
import NS from "@NS"; | |
import { FilterSource } from "@/methods/context/methods/studentGrades"; | |
/** | |
* Проверяет валидность сессии. | |
*/ | |
export async function sessionValid(this: NS) { | |
if (!(await this.sessionValid()) || !this.session || !this.context) | |
throw new Error("Сначала надо открыть сессию."); | |
return { ...this } as NS & { | |
session: NonNullable<NS["session"]>; | |
context: NonNullable<NS["context"]>; | |
}; | |
} | |
/** | |
* Проверяет валидность дат | |
*/ | |
export function dateValid(this: NS, ...dates: Date[]) { | |
for (let date of dates) | |
if (!this.context?.checkDate(date)) | |
throw new Error("Дата выходит за рамки учебного года"); | |
return dates; | |
} | |
/** | |
* Проверяет валидность дат четверти или возвращает дефолтное значение. | |
* @param this Класс библиотеки. | |
* @param termId ID четверти. | |
*/ | |
export async function termDateValid( | |
this: NS, | |
termId: number, | |
startDate?: Date, | |
endDate?: Date | |
) { | |
// Получаем четверть по id | |
const term = this.context?.user.terms.find((t) => t.id == termId); | |
if (!term) throw new Error("Четверть не существует"); | |
// Проверяем даты | |
const { start, end } = term; | |
if (!startDate) startDate = start; | |
if (!endDate) endDate = end; | |
for (let date of [startDate, endDate]) | |
if (+date < +start && +end < +date) | |
throw new Error(`Дата выходит за рамки четверти ${termId}`); | |
// Возвращаем даты | |
return { start: startDate, end: endDate }; | |
} | |
/** | |
* Проверяет id пользователя или возвращает дефолтное | |
*/ | |
export function studentIdValid(this: NS, id?: number) { | |
const context = this.context as NonNullable<NS["context"]>; | |
if (!id) id = context.defaultStudent()?.id; | |
const data = id && context.getStudentById(id); | |
if (data) return data; | |
else throw new Error(`Нет пользователя c id: ${id}`); | |
} | |
/** | |
* Проверяет id класса или возвращает дефолтное | |
*/ | |
export function classIdValid(this: NS, id?: number) { | |
const context = this.context as NonNullable<NS["context"]>; | |
if (!id) id = context.defaultClass()?.id; | |
const data = id && context.getClassById(id); | |
if (data) return data; | |
else throw new Error(`Нет класса c id: ${id}`); | |
} | |
/** | |
* Проверяет id четверти или возвращает дефолтное | |
*/ | |
export function termIdValid(this: NS, id?: number) { | |
const context = this.context as NonNullable<NS["context"]>; | |
if (!id) id = context.defaultTerm()?.id; | |
const data = id && context.getTermById(id); | |
if (data) return data; | |
else throw new Error(`Нет четверти c id: ${id}`); | |
} |
/** | |
* Переводит число `1` в строку "01" | |
*/ | |
export function num2str(num: string | number) { | |
if (typeof num == "string") num = parseInt(num); | |
if (num < 10) return "0" + num; | |
else return num.toString(); | |
} | |
/** | |
* Переводит объект `Date` в строку формата "dd.mm.yy" | |
*/ | |
export function date2str(date: Date) { | |
const day = date.getDate(); | |
const month = date.getMonth() + 1; | |
const year = date.getFullYear() % 100; | |
return day + "." + month + "." + year; | |
} | |
/** | |
* Переводит строку формата "dd.mm.yy" в объект `Date` | |
*/ | |
export function str2date(str: string) { | |
if (!/(d{1,2}.){2}d{2}/.test(str)) | |
throw new Error("Invalid date string: " + str); | |
const [day, month, year] = (str.match(/d+/g) as string[]).map((s) => | |
parseInt(s) | |
); | |
const date = new Date(); | |
date.setHours(0, 0, 0, 0); | |
date.setDate(day); | |
date.setMonth(month - 1); | |
date.setFullYear(~~(date.getFullYear() / 100) * 100 + year); | |
return date; | |
} | |
/** | |
* Переводит строку формата "dd.mm.yy" в строку формата "yyyy-mm-ddT00:00" | |
*/ | |
export function date2JSON(str: string): string; | |
/** | |
* Переводит объект `Date` в строку формата "yyyy-mm-ddT00:00" | |
*/ | |
export function date2JSON(date: Date): string; | |
export function date2JSON(arg: string | Date): string { | |
const date = typeof arg === "string" ? str2date(arg) : arg; | |
const day = date.getDate(); | |
const year = date.getFullYear(); | |
const month = date.getMonth() + 1; | |
return `${year}-${num2str(month)}-${num2str(day)}T00:00`; | |
} |
import { parse as p, Options } from "node-html-parser"; | |
function parse(data: string, options?: Partial<Options>) { | |
return p(data.replace(/ /g, " "), options); | |
} | |
export function outerHTML(args: { html: string; query: string }) { | |
const { html, query } = args; | |
return parse(html).querySelector(query)?.outerHTML ?? html; | |
} | |
export function query(html: string, query: string) { | |
return parse(html).querySelector(query); | |
} | |
export function queryAll(html: string, query: string) { | |
return parse(html).querySelectorAll(query); | |
} | |
export function table(args: { | |
html: string; | |
query?: string; | |
removeHeaders?: boolean; | |
}) { | |
const { html, query = ".table", removeHeaders = true } = args; | |
const trs = parse(html).querySelector(query)?.querySelectorAll?.("tr") ?? []; | |
if (removeHeaders) trs.shift(); | |
return trs; | |
} |
{ | |
"compilerOptions": { | |
"module": "commonjs", | |
"target": "es6", | |
"strict": true, | |
"pretty": true, | |
"baseUrl": "./", | |
"outDir": "dist", | |
"rootDir": "src", | |
"sourceMap": true, | |
"declaration": true, | |
"esModuleInterop": true, | |
"strictNullChecks": true, | |
"moduleResolution": "node", | |
"paths": { | |
"@/*": [ | |
"src/*" | |
], | |
"@NS": [ | |
"src/NetSchoolApi-safe.ts" | |
] | |
} | |
}, | |
"lib": [ | |
"es2015" | |
] | |
} |
f?Z(e,i,c,!0,!1,p):R(t,n,r,i,c,s,l,u,p)},W=(e,t,n,r,i,c,s,l,u)=>{let a=0;const f=t.length;let p=e.length-1,d=f-1;while(a<=p&&a<=d){const r=e[a],o=t[a]=u?un(t[a]):ln(t[a]);if(!zt(r,o))break;m(r,o,n,null,i,c,s,l,u),a++}while(a<=p&&a<=d){const r=e[p],o=t[d]=u?un(t[d]):ln(t[d]);if(!zt(r,o))break;m(r,o,n,null,i,c,s,l,u),p--,d--}if(a>p){if(a<=d){const e=d+1,o=e