itstep-mystat-mcp 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- itstep_mystat_mcp-0.1.1/.env.example +14 -0
- itstep_mystat_mcp-0.1.1/.github/workflows/publish.yml +46 -0
- itstep_mystat_mcp-0.1.1/.gitignore +48 -0
- itstep_mystat_mcp-0.1.1/PKG-INFO +15 -0
- itstep_mystat_mcp-0.1.1/README.md +235 -0
- itstep_mystat_mcp-0.1.1/docs/api-findings.md +138 -0
- itstep_mystat_mcp-0.1.1/mystat/__init__.py +3 -0
- itstep_mystat_mcp-0.1.1/mystat/__main__.py +10 -0
- itstep_mystat_mcp-0.1.1/mystat/api.py +195 -0
- itstep_mystat_mcp-0.1.1/mystat/attachments.py +369 -0
- itstep_mystat_mcp-0.1.1/mystat/auth.py +233 -0
- itstep_mystat_mcp-0.1.1/mystat/cache.py +449 -0
- itstep_mystat_mcp-0.1.1/mystat/cli.py +454 -0
- itstep_mystat_mcp-0.1.1/mystat/clients.py +486 -0
- itstep_mystat_mcp-0.1.1/mystat/config.py +214 -0
- itstep_mystat_mcp-0.1.1/mystat/extraction.py +182 -0
- itstep_mystat_mcp-0.1.1/mystat/homework.py +579 -0
- itstep_mystat_mcp-0.1.1/mystat/search.py +47 -0
- itstep_mystat_mcp-0.1.1/mystat/server.py +224 -0
- itstep_mystat_mcp-0.1.1/pyproject.toml +41 -0
- itstep_mystat_mcp-0.1.1/scripts/discover.py +286 -0
- itstep_mystat_mcp-0.1.1/scripts/discover_details.py +181 -0
- itstep_mystat_mcp-0.1.1/scripts/discover_files.py +89 -0
- itstep_mystat_mcp-0.1.1/scripts/probe_auth_email.py +35 -0
- itstep_mystat_mcp-0.1.1/scripts/probe_auth_extra.py +52 -0
- itstep_mystat_mcp-0.1.1/scripts/probe_auth_shape.py +60 -0
- itstep_mystat_mcp-0.1.1/scripts/probe_bundle.py +42 -0
- itstep_mystat_mcp-0.1.1/scripts/probe_login.py +50 -0
- itstep_mystat_mcp-0.1.1/scripts/probe_web_client.py +53 -0
- itstep_mystat_mcp-0.1.1/server.py +10 -0
- itstep_mystat_mcp-0.1.1/uv.lock +1274 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
MYSTAT_LOGIN=your_login
|
|
2
|
+
MYSTAT_PASSWORD=your_password
|
|
3
|
+
|
|
4
|
+
MYSTAT_BASE_URL=https://mapi.itstep.org/v1/mystat
|
|
5
|
+
MYSTAT_LANGUAGE=uk
|
|
6
|
+
MYSTAT_STATUS_FILTER=3
|
|
7
|
+
MYSTAT_SORT=-hw.time
|
|
8
|
+
|
|
9
|
+
CACHE_DIR=./cache
|
|
10
|
+
DATABASE_PATH=./cache/mystat.db
|
|
11
|
+
|
|
12
|
+
SYNC_LIMIT=1000
|
|
13
|
+
HTTP_TIMEOUT=30
|
|
14
|
+
MAX_ATTACHMENT_SIZE_MB=100
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Trusted Publishing (OIDC): no API token is stored in the repository.
|
|
4
|
+
# PyPI must be configured with:
|
|
5
|
+
# owner = stealbe
|
|
6
|
+
# repository = itstep-mystat-mcp
|
|
7
|
+
# workflow = publish.yml
|
|
8
|
+
# environment= pypi
|
|
9
|
+
on:
|
|
10
|
+
release:
|
|
11
|
+
types: [published]
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
name: build sdist and wheel
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: astral-sh/setup-uv@v7
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.13"
|
|
23
|
+
- name: Build
|
|
24
|
+
run: uv build
|
|
25
|
+
- uses: actions/upload-artifact@v4
|
|
26
|
+
with:
|
|
27
|
+
name: dist
|
|
28
|
+
path: dist/
|
|
29
|
+
if-no-files-found: error
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
name: publish to PyPI
|
|
33
|
+
needs: build
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
# Must match the "Environment name" of the pending publisher on PyPI.
|
|
36
|
+
environment: pypi
|
|
37
|
+
permissions:
|
|
38
|
+
# Required for OIDC: without this the action cannot obtain a token.
|
|
39
|
+
id-token: write
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/download-artifact@v4
|
|
42
|
+
with:
|
|
43
|
+
name: dist
|
|
44
|
+
path: dist/
|
|
45
|
+
- name: Publish
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ---- secrets -------------------------------------------------------------
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
.token
|
|
6
|
+
.token.*
|
|
7
|
+
|
|
8
|
+
# ---- local state and caches ---------------------------------------------
|
|
9
|
+
cache/
|
|
10
|
+
logs/
|
|
11
|
+
*.log
|
|
12
|
+
*.db
|
|
13
|
+
*.db-wal
|
|
14
|
+
*.db-shm
|
|
15
|
+
*.part
|
|
16
|
+
|
|
17
|
+
# ---- python --------------------------------------------------------------
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[cod]
|
|
20
|
+
*.egg-info/
|
|
21
|
+
build/
|
|
22
|
+
dist/
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
coverage.xml
|
|
28
|
+
htmlcov/
|
|
29
|
+
|
|
30
|
+
# ---- virtual environments ------------------------------------------------
|
|
31
|
+
.venv/
|
|
32
|
+
.venv*/
|
|
33
|
+
venv/
|
|
34
|
+
|
|
35
|
+
# ---- editors and OS ------------------------------------------------------
|
|
36
|
+
.idea/
|
|
37
|
+
.vscode/
|
|
38
|
+
*.swp
|
|
39
|
+
.DS_Store
|
|
40
|
+
Thumbs.db
|
|
41
|
+
desktop.ini
|
|
42
|
+
|
|
43
|
+
# ---- leftovers -----------------------------------------------------------
|
|
44
|
+
uv.lock.bak
|
|
45
|
+
.tmp-*/
|
|
46
|
+
|
|
47
|
+
# ---- local test suite (kept on disk, not versioned) ----------------------
|
|
48
|
+
tests/
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: itstep-mystat-mcp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Local MCP server exposing ITStep MyStat homework (list, details, attachments, search)
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: aiosqlite>=0.20
|
|
7
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Requires-Dist: mcp>=1.2
|
|
10
|
+
Requires-Dist: openpyxl>=3.1
|
|
11
|
+
Requires-Dist: pillow>=10.0
|
|
12
|
+
Requires-Dist: pypdf>=5.0
|
|
13
|
+
Requires-Dist: python-docx>=1.1
|
|
14
|
+
Requires-Dist: python-dotenv>=1.0
|
|
15
|
+
Requires-Dist: python-pptx>=1.0
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# itstep-mystat-mcp
|
|
2
|
+
|
|
3
|
+
Локальный MCP-сервер (stdio) для доступа к домашним заданиям ITStep MyStat:
|
|
4
|
+
синхронизация списка заданий, загрузка вложений, извлечение текста, поиск.
|
|
5
|
+
|
|
6
|
+
Ставится одной командой `uv tool install`, настраивается мастером `setup`:
|
|
7
|
+
логин и пароль вводятся в терминале, сервер сам прописывается в локальные
|
|
8
|
+
клиенты (OpenCode, Claude Code, Claude Desktop, Codex).
|
|
9
|
+
|
|
10
|
+
## Быстрый старт
|
|
11
|
+
|
|
12
|
+
```powershell
|
|
13
|
+
uv tool install itstep-mystat-mcp # из индекса (когда опубликован)
|
|
14
|
+
# или из локальной папки проекта:
|
|
15
|
+
uv tool install .
|
|
16
|
+
|
|
17
|
+
itstep-mystat-mcp setup # логин/пароль + авто-регистрация в клиентах
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`setup` задаёт три вопроса (если значения не переданы флагами) и делает всё сам:
|
|
21
|
+
|
|
22
|
+
1. спрашивает логин MyStat (`MYSTAT_LOGIN`);
|
|
23
|
+
2. спрашивает пароль (ввод скрыт, просит повторить);
|
|
24
|
+
3. проверяет пару реальным `POST /auth/login`;
|
|
25
|
+
4. пишет конфиг в `%APPDATA%\itstep-mystat-mcp\.env`;
|
|
26
|
+
5. регистрирует MCP-сервер в найденных клиентах.
|
|
27
|
+
|
|
28
|
+
После этого достаточно перезапустить клиент.
|
|
29
|
+
|
|
30
|
+
## Что умеет
|
|
31
|
+
|
|
32
|
+
| Инструмент | Назначение |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `mystat_login` | принудительный login, замена сохранённого токена |
|
|
35
|
+
| `mystat_auth_status` | есть ли токен, когда получен, известен ли срок (токен не раскрывается) |
|
|
36
|
+
| `mystat_logout` | удалить токен из локального кеша |
|
|
37
|
+
| `homework_sync` | синхронизация списка + деталей + вложений |
|
|
38
|
+
| `homework_list` | список заданий из кеша (краткие сведения) |
|
|
39
|
+
| `homework_get` | одно задание: текст, вложения, статусы |
|
|
40
|
+
| `homework_search` | полнотекстовый поиск (FTS5) |
|
|
41
|
+
| `homework_read_file` | чтение вложения или извлечённого текста |
|
|
42
|
+
| `homework_cache_status` | статистика кеша |
|
|
43
|
+
|
|
44
|
+
## Команды CLI
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
itstep-mystat-mcp setup # мастер настройки (логин/пароль + клиенты)
|
|
48
|
+
itstep-mystat-mcp run # MCP-сервер по stdio (это вызывают клиенты)
|
|
49
|
+
itstep-mystat-mcp status # пути конфига/кеша, наличие кредов, токен, размер кеша
|
|
50
|
+
itstep-mystat-mcp sync # разовая синхронизация из терминала
|
|
51
|
+
itstep-mystat-mcp login # принудительный login, токен в кеш
|
|
52
|
+
itstep-mystat-mcp logout # удалить токен
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`itstep-mystat-mcp run` без аргументов — поведение по умолчанию (так его
|
|
56
|
+
запускают MCP-клиенты).
|
|
57
|
+
|
|
58
|
+
### Флаги `setup`
|
|
59
|
+
|
|
60
|
+
| Флаг | Смысл |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `--login <login>` | логин без интерактивного вопроса |
|
|
63
|
+
| `--env-file <path>` | куда писать конфиг (по умолчанию `%APPDATA%\itstep-mystat-mcp\.env`) |
|
|
64
|
+
| `--cache-dir <path>` | каталог кеша (по умолчанию рядом с конфигом) |
|
|
65
|
+
| `--client auto\|all\|none\|<name>` | целевые клиенты; можно повторять. `auto` — найденные на машине (по умолчанию) |
|
|
66
|
+
| `--verify` / `--no-verify` | проверять ли креды реальным login (по умолчанию проверяет) |
|
|
67
|
+
| `-y`, `--yes` | не задавать вопросов: брать значения из флагов, `.env` или окружения |
|
|
68
|
+
|
|
69
|
+
Пароль можно передать через переменную окружения `MYSTAT_PASSWORD` — тогда
|
|
70
|
+
мастер не спрашивает его в интерактиве (удобно для скриптов и CI):
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
$env:MYSTAT_PASSWORD = "..." # останется в истории шелла — учитывайте это
|
|
74
|
+
itstep-mystat-mcp setup --login my_login --client none --no-verify
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Куда ставится конфиг и клиенты
|
|
78
|
+
|
|
79
|
+
| Клиент | Путь / способ |
|
|
80
|
+
|---|---|
|
|
81
|
+
| конфиг сервера | `%APPDATA%\itstep-mystat-mcp\.env` (кеш — `%APPDATA%\itstep-mystat-mcp\cache`) |
|
|
82
|
+
| OpenCode | `~/.config/opencode/opencode.jsonc` (или `opencode.json`) |
|
|
83
|
+
| Claude Code | `claude mcp add --scope user` (используется CLI, если он в `PATH`) |
|
|
84
|
+
| Claude Desktop | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
85
|
+
| Codex | `~/.codex/config.toml` (`$CODEX_HOME` учитывается) |
|
|
86
|
+
|
|
87
|
+
Перед изменением существующего файла создаётся резервная копия
|
|
88
|
+
`<имя>.bak-YYYYMMDD-HHMMSS`. Если файл не парсится, он **не** перезаписывается —
|
|
89
|
+
вместо этого выводится сниппет для ручной вставки.
|
|
90
|
+
|
|
91
|
+
## Ручное подключение (если авто-регистрация не сработала)
|
|
92
|
+
|
|
93
|
+
OpenCode, `~/.config/opencode/opencode.jsonc`:
|
|
94
|
+
|
|
95
|
+
```jsonc
|
|
96
|
+
"mcp": {
|
|
97
|
+
"itstep-mystat": {
|
|
98
|
+
"type": "local",
|
|
99
|
+
"command": ["C:\\Users\\<you>\\AppData\\Local\\uv\\tools\\itstep-mystat-mcp\\Scripts\\python.exe", "-m", "mystat"],
|
|
100
|
+
"environment": { "PYTHONUTF8": "1" },
|
|
101
|
+
"enabled": true,
|
|
102
|
+
"timeout": 180000
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Claude Desktop / общий JSON-формат:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"mcpServers": {
|
|
112
|
+
"itstep-mystat": {
|
|
113
|
+
"command": "itstep-mystat-mcp",
|
|
114
|
+
"args": ["run"],
|
|
115
|
+
"env": { "PYTHONUTF8": "1" }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Codex, `~/.codex/config.toml`:
|
|
122
|
+
|
|
123
|
+
```toml
|
|
124
|
+
[mcp_servers.itstep-mystat]
|
|
125
|
+
command = "itstep-mystat-mcp"
|
|
126
|
+
args = ["run"]
|
|
127
|
+
env = { PYTHONUTF8 = "1" }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Таймаут увеличен: первая синхронизация скачивает десятки файлов.
|
|
131
|
+
|
|
132
|
+
## Разработка (из исходников)
|
|
133
|
+
|
|
134
|
+
```powershell
|
|
135
|
+
uv sync
|
|
136
|
+
uv run python -m mystat status
|
|
137
|
+
uv run python -m mystat sync --no-attachments
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`python server.py` оставлен как тонкая обёртка над `mystat.server:main`.
|
|
141
|
+
|
|
142
|
+
Сервер общается по stdio. `stdout` занят протоколом; все логи идут в `stderr`
|
|
143
|
+
и в `cache/logs/mystat.log`.
|
|
144
|
+
|
|
145
|
+
## Структура
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
server.py тонкая обёртка (совместимость)
|
|
149
|
+
mystat/__main__.py python -m mystat
|
|
150
|
+
mystat/cli.py setup / run / status / sync / login / logout
|
|
151
|
+
mystat/clients.py регистрация MCP в opencode, claude*, codex
|
|
152
|
+
mystat/server.py MCP-сервер (mcp 2.x MCPServer, stdio, 9 инструментов)
|
|
153
|
+
mystat/config.py .env, пути, лимиты
|
|
154
|
+
mystat/cache.py SQLite (WAL), миграции, FTS5
|
|
155
|
+
mystat/auth.py login / refresh / хранение токена
|
|
156
|
+
mystat/api.py единый HTTP-клиент, 401-recovery, ретраи
|
|
157
|
+
mystat/homework.py нормализация, sync, read model
|
|
158
|
+
mystat/attachments.py валидация URL, скачивание, имена, MIME
|
|
159
|
+
mystat/extraction.py PDF/DOCX/PPTX/XLSX/TXT/HTML
|
|
160
|
+
mystat/search.py безопасные FTS5-запросы
|
|
161
|
+
docs/api-findings.md подтверждённый контракт API
|
|
162
|
+
scripts/ разведочные скрипты (не входят в поставку)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Ключевые решения (по фактам API)
|
|
166
|
+
|
|
167
|
+
- Токен — JWT, живёт 30 дней; `expires_at` берётся из `exp`.
|
|
168
|
+
- `401` → `POST /auth/refresh` с текущим токеном; при неудаче — `POST /auth/login`.
|
|
169
|
+
Один повтор запроса, не более.
|
|
170
|
+
- Текст задания есть **только** в `GET /od/homework/{id}` (`comment` в списке пуст).
|
|
171
|
+
- Детали приходят объектом с ключом `"0"`, а не массивом — нормализатор
|
|
172
|
+
поддерживает оба варианта.
|
|
173
|
+
- Имя файла — из `Content-Disposition` (`filename*`, UTF-8); MIME проверяется
|
|
174
|
+
по magic bytes; `ETag` у файлового хранилища недостоверен, поэтому изменения
|
|
175
|
+
определяются по SHA-256.
|
|
176
|
+
- Скачиваются только `https://*.itstep.org/api/v1/files/...`; проверяется
|
|
177
|
+
разрешённый IP (private/loopback/link-local запрещены), размер ограничен,
|
|
178
|
+
редиректы проверяются.
|
|
179
|
+
- ZIP не распаковывается, OCR не выполняется (первая версия).
|
|
180
|
+
|
|
181
|
+
## Безопасность
|
|
182
|
+
|
|
183
|
+
- Логин и пароль хранятся только в локальном конфиге, который не попадает
|
|
184
|
+
в репозиторий (`.env` в `.gitignore`).
|
|
185
|
+
- Пароль никогда не выводится: `setup` читает его скрытым вводом, `status`
|
|
186
|
+
показывает только `configured` / `MISSING`.
|
|
187
|
+
- Токен никогда не возвращается MCP-инструментами и не пишется в логи;
|
|
188
|
+
заголовок `Authorization` в лог не попадает.
|
|
189
|
+
- Регистрация клиентов не удаляет чужие записи: изменяется только ключ
|
|
190
|
+
`itstep-mystat`.
|
|
191
|
+
|
|
192
|
+
## Тесты
|
|
193
|
+
|
|
194
|
+
Набор тестов есть локально, но не входит в репозиторий (см. `.gitignore`):
|
|
195
|
+
|
|
196
|
+
```powershell
|
|
197
|
+
.venv\Scripts\python.exe -m pytest -q
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
82 теста: авторизация и refresh, ретраи, нормализация, sync, пометка
|
|
201
|
+
исчезнувших заданий, безопасность URL и имён, скачивание, FTS5, CLI,
|
|
202
|
+
регистрация клиентов и кодирование значений `.env`. Тесты не ходят в сеть:
|
|
203
|
+
`httpx.MockTransport` и обезличенные фикстуры в `tests/fixtures/`.
|
|
204
|
+
|
|
205
|
+
## Формат `.env`
|
|
206
|
+
|
|
207
|
+
`setup` пишет значение без кавычек, когда это безопасно (логин, Windows-пути),
|
|
208
|
+
и в двойных кавычках с экранированием — когда внутри есть пробелы, `#`,
|
|
209
|
+
кавычки или обрамляющие пробелы:
|
|
210
|
+
|
|
211
|
+
```dotenv
|
|
212
|
+
MYSTAT_LOGIN=student_login
|
|
213
|
+
MYSTAT_PASSWORD="pa ss#1 \"x\""
|
|
214
|
+
CACHE_DIR=C:\Users\you\AppData\Roaming\itstep-mystat-mcp\cache
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Читается файл с `interpolate=False`: `python-dotenv` подставляет `${VAR}`
|
|
218
|
+
даже внутри кавычек, и без этого пароль вида `${...}` молча превращался бы в
|
|
219
|
+
пустую строку. После записи значения сверяются обратным чтением — при
|
|
220
|
+
расхождении `setup` сообщает об ошибке, не печатая сами значения.
|
|
221
|
+
|
|
222
|
+
`--cache-dir` всегда сохраняется абсолютным: MCP-клиент запускает сервер из
|
|
223
|
+
произвольного рабочего каталога, относительный путь разрешился бы не там.
|
|
224
|
+
|
|
225
|
+
## Ограничения
|
|
226
|
+
|
|
227
|
+
- Первый запуск `homework_sync` требует действующих учётных данных
|
|
228
|
+
(мастер `setup` или `.env`).
|
|
229
|
+
- Токен хранится в SQLite открытым текстом (как в ТЗ). Шифрование через
|
|
230
|
+
Windows DPAPI можно добавить отдельным этапом.
|
|
231
|
+
- Если пароль не принимается, API отвечает одинаковым `401` и для неверного
|
|
232
|
+
пароля, и для несуществующего логина — поэтому «неверный пароль» и
|
|
233
|
+
«неизвестный логин» неразличимы.
|
|
234
|
+
- Проектный `.env` в папке разработки имеет приоритет над
|
|
235
|
+
`%APPDATA%\itstep-mystat-mcp\.env`; `setup` предупреждает об этом.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# MyStat API — подтверждённые факты (discovery)
|
|
2
|
+
|
|
3
|
+
**Дата:** 12.11.2026
|
|
4
|
+
**Источник:** реальные запросы к `https://mapi.itstep.org/v1/mystat` + JS-бандл веб-клиента `https://mystat.itstep.org/main-PTSNAXV7.js`.
|
|
5
|
+
**Статус:** всё ниже — наблюдённые факты, кроме явно помеченных INFERENCE.
|
|
6
|
+
|
|
7
|
+
Закрывает неизвестные из раздела 18 ТЗ.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Авторизация
|
|
12
|
+
|
|
13
|
+
**`POST /auth/login`**
|
|
14
|
+
|
|
15
|
+
```http
|
|
16
|
+
Content-Type: application/json
|
|
17
|
+
X-Language: uk
|
|
18
|
+
|
|
19
|
+
{"login": "<login>", "password": "<password>"}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- `200` → тело **plain text JWT** (не JSON). `Content-Type: text/html; charset=UTF-8`, длина 604.
|
|
23
|
+
- `401` → `Unauthorized: error_unauthorized` (одинаково для неверного логина и пароля).
|
|
24
|
+
- `422` → `Validation error. The Password is required` (без пароля) / `validation_custom_email_required_without` (без логина).
|
|
25
|
+
- Имена полей: только `login` + `password`. `username`, `email` не принимаются (`500`/`422`).
|
|
26
|
+
- `X-Language`, `Origin`, `Referer`, `User-Agent`, `X-Userid` на результат **не влияют**.
|
|
27
|
+
|
|
28
|
+
**JWT claims (payload):** `iss, iat, exp, nbf, jti, sub, id, id_user_storage, email, username, name, branch_id, project_id, lang, profile, module, branch_alias`.
|
|
29
|
+
`exp - iat = 2 592 000 c` = **30 дней** → `expires_at` можно заполнять из токена без верификации подписи.
|
|
30
|
+
|
|
31
|
+
**`POST /auth/refresh`** (в ТЗ помечен как неизвестный — теперь известно)
|
|
32
|
+
|
|
33
|
+
```http
|
|
34
|
+
Authorization: Bearer <access_token>
|
|
35
|
+
Content-Type: application/json
|
|
36
|
+
|
|
37
|
+
{}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `200` → новый JWT тем же plain-text форматом. **Refresh token не нужен** — сам access token выступает основанием для refresh.
|
|
41
|
+
- `401` без заголовка → `Unauthorized: Token is empty`.
|
|
42
|
+
|
|
43
|
+
Веб-клиент после логина вызывает `/auth/me` (`accountService.getAccountForce$`), но для MCP-потока он не требуется: `list` и `homework/{id}` работают сразу с Bearer-токеном.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. Список заданий
|
|
48
|
+
|
|
49
|
+
**`GET /od/homework/list?sort=-hw.time&status=3&limit=1000`**
|
|
50
|
+
|
|
51
|
+
Заголовки: `Authorization: Bearer <token>`, `X-Language: uk`. **`X-Userid` не требуется** (проверено: без него `200`).
|
|
52
|
+
|
|
53
|
+
Форма ответа:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "data": [ ... 60 элементов ... ], "_meta": { "currentPage": 1, "totalPages": 1, "totalCount": 60 } }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Поля элемента списка:
|
|
60
|
+
|
|
61
|
+
| Поле | Тип | Пример / смысл |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `id` | int | `502719` — ID задания |
|
|
64
|
+
| `id_spec` | int | ID предмета |
|
|
65
|
+
| `id_teach` | int | ID преподавателя |
|
|
66
|
+
| `id_group` | int | ID группы |
|
|
67
|
+
| `fio_teach` | str | ФИО преподавателя |
|
|
68
|
+
| `theme` | str | тема задания |
|
|
69
|
+
| `name_spec` | str | название предмета |
|
|
70
|
+
| `comment` | str | **всегда пустой в списке** (см. п.3) |
|
|
71
|
+
| `creation_time` / `completion_time` / `overdue_time` | str | `YYYY-MM-DD` |
|
|
72
|
+
| `file_path` | str \| null | URL основного вложения |
|
|
73
|
+
| `cover_image` | str \| null | URL картинки-обложки |
|
|
74
|
+
| `status` | int | наблюдалось только `3` при фильтре `status=3` |
|
|
75
|
+
| `homework_stud` | obj | данные сдачи: `id, file_path, mark, creation_time, stud_answer, auto_mark, status, photo_pas, fio_stud, related_homeworks, related_materials, unlock_expire, communication_history, is_retake` |
|
|
76
|
+
| `homework_comment` | obj | `text_comment, attachment, attachment_path, date_updated` |
|
|
77
|
+
| `is_autotest`, `autotest_status`, `autotest_result`, `ospr`, `default_deadline_days`, `coins_to_close_deadline`, `completion_days`, `is_academic_holiday` | mixed | служебные |
|
|
78
|
+
|
|
79
|
+
Заголовков `X-Pagination-*` сервер **не отдаёт** — пагинация только через `_meta`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. Детали задания
|
|
84
|
+
|
|
85
|
+
**`GET /od/homework/{id}`**
|
|
86
|
+
|
|
87
|
+
Форма ответа — **не массив**, а объект с ключом-строкой `"0"`:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "0": { ...поля задания... }, "relatedHomeworks": [ ... ] }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Парсер обязан корректно принимать: объект с ключом `"0"`, массив из одного элемента и (на всякий случай) одиночный объект.
|
|
94
|
+
|
|
95
|
+
Поля внутри `"0"` совпадают с полями списка, **но `comment` здесь содержит реальный текст задания** (проверено на 10 заданиях: в списке пусто, в деталях 9–539 символов). Значит `homework_sync` обязан запрашивать детали — иначе текст задания теряется.
|
|
96
|
+
|
|
97
|
+
`relatedHomeworks[]`: `id_domzadstud, id_teach, id_stud, status, time, id_domzad, ospr, mark, nlenta, date_vizit, is_retake, auto_test_status, auto_test_response, unlock_expire, moved_from_inactive_teacher, created_at, updated_at, domZad{ id_domzad, material_link, id_teach, id_tgroups, id_spec, time, id_schedule, nlenta, date, ospr, dz_theme, deadline, is_autotest, public_material_uuid, spec{...} }`.
|
|
98
|
+
|
|
99
|
+
`updated_at` в `relatedHomeworks` — единственный кандидат на «время изменения», но относится к связанным сдачам, не к самому заданию. **Для самого задания поля изменения нет** → определение изменений по SHA-256 нормализованного JSON деталей (как в ТЗ).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 4. Вложения
|
|
104
|
+
|
|
105
|
+
В JSON **нет** имени, MIME и размера файла — только URL.
|
|
106
|
+
|
|
107
|
+
| Поле | Смысл |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `file_path` | основное вложение задания (есть у 53 из 60 заданий) |
|
|
110
|
+
| `cover_image` | картинка-обложка задания (не вложение) |
|
|
111
|
+
| `homework_stud.file_path` | файл, загруженный студентом (в наблюдённых данных `null`) |
|
|
112
|
+
| `homework_comment.attachment` / `attachment_path` | вложение к комментарию преподавателя |
|
|
113
|
+
|
|
114
|
+
Наблюдённые домены: `fs1.itstep.org`, `fs3.itstep.org`, `fsx1.itstep.org`.
|
|
115
|
+
Шаблон пути: `/api/v1/files/<opaque-token>`.
|
|
116
|
+
|
|
117
|
+
Скачивание **открытое, без авторизации**:
|
|
118
|
+
|
|
119
|
+
- `200`, `access-control-allow-origin: *`, `server: nginx`;
|
|
120
|
+
- `Content-Disposition: attachment; filename="DZ_2026_11_09.pdf"; filename*=utf-8''ДЗ_2026_11_09.pdf` → отсюда берём исходное имя (декодировать `filename*` по RFC 5987);
|
|
121
|
+
- `Content-Type` и `Content-Length` корректны (проверено: `application/pdf`, `...wordprocessingml.document`, `video/mp4`, `image/png`, `text/html`);
|
|
122
|
+
- `Last-Modified` присутствует не всегда;
|
|
123
|
+
- **`ETag` непригоден**: у разных файлов одинаковый sha1 пустой строки. Условные запросы по ETag использовать нельзя → только SHA-256 содержимого.
|
|
124
|
+
|
|
125
|
+
Вложение может быть **HTML-документом** (сгенерированная страница задания `Домашнє_завдання_...html`) — извлечение текста обязано это поддерживать.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 5. Следствия для реализации
|
|
130
|
+
|
|
131
|
+
1. Токен хранить как plain text JWT + `expires_at` из `exp` (30 дней).
|
|
132
|
+
2. На `401`: сначала `POST /auth/refresh` с Bearer, при неудаче — `POST /auth/login`; один повтор исходного запроса.
|
|
133
|
+
3. `homework_sync` всегда тянет `homework/{id}` (в списке нет текста задания).
|
|
134
|
+
4. Allowlist файлов: HTTPS + host заканчивается на `.itstep.org` + path начинается с `/api/v1/files/`.
|
|
135
|
+
5. Имя файла: `Content-Disposition` → `filename*` (UTF-8) → `filename` → имя из URL → `attachment_<n>`.
|
|
136
|
+
6. MIME: доверять `Content-Type`, но проверять magic bytes (PK\x03\x04, %PDF, \x89PNG, ftyp, <!DOCTYPE).
|
|
137
|
+
7. Change detection: SHA-256 нормализованного JSON деталей и SHA-256 содержимого файла; ETag игнорировать.
|
|
138
|
+
8. `status=3` — единственное наблюдённое значение; вынести в конфиг.
|