maxapi-python 1.2.1__tar.gz → 1.2.2__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.
- maxapi_python-1.2.2/.github/workflows/tests.yml +153 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.gitignore +1 -1
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/PKG-INFO +43 -6
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/README.md +35 -5
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/test.py +2 -2
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/pyproject.toml +53 -1
- maxapi_python-1.2.2/pytest.ini +26 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/clients.rst +22 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/core.py +1 -1
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/websocket.py +7 -16
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/payloads.py +29 -14
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/static/constant.py +1 -1
- maxapi_python-1.2.1/pytest.ini +0 -6
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.coderabbit.yaml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/FUNDING.yml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/pull_request_template.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/workflows/publish.yml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.pre-commit-config.yaml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/LICENSE +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/assets/icon.svg +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/assets/logo.svg +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/example.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/flt_test.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/large_file_upload.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/reg.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/telegram_bridge.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/mkdocs.yml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/Makefile +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/build.sh +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/make.bat +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/_static/logo.svg +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/conf.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/decorators.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/examples.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/guides.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/index.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/installation.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/quickstart.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/types.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/ruff.toml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/__init__.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/crud.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/exceptions.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/files.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/filters.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/formatter.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/formatting.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/interfaces.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/__init__.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/auth.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/channel.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/group.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/handler.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/message.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/scheduler.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/self.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/socket.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/telemetry.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/user.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/utils.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/models.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/navigation.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/static/enum.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/types.py +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
#on:
|
|
4
|
+
# push:
|
|
5
|
+
# branches: [ main, develop ]
|
|
6
|
+
# pull_request:
|
|
7
|
+
# branches: [ main, develop ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ['3.10', '3.11', '3.12', '3.13']
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Cache pip packages
|
|
27
|
+
uses: actions/cache@v4
|
|
28
|
+
with:
|
|
29
|
+
path: ~/.cache/pip
|
|
30
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
|
|
31
|
+
restore-keys: |
|
|
32
|
+
${{ runner.os }}-pip-
|
|
33
|
+
|
|
34
|
+
- name: Install runtime + test tools
|
|
35
|
+
run: |
|
|
36
|
+
python -m pip install --upgrade pip
|
|
37
|
+
# Устанавливаем package (runtime deps)
|
|
38
|
+
pip install -e "."
|
|
39
|
+
# Явно ставим инструменты для тестов/линта, чтобы они были доступны в runner
|
|
40
|
+
pip install pytest pytest-asyncio pytest-cov pytest-timeout flake8 mypy
|
|
41
|
+
|
|
42
|
+
- name: Lint with flake8
|
|
43
|
+
run: |
|
|
44
|
+
flake8 src/pymax tests \
|
|
45
|
+
--count \
|
|
46
|
+
--select=E9,F63,F7,F82 \
|
|
47
|
+
--show-source \
|
|
48
|
+
--statistics
|
|
49
|
+
flake8 src/pymax tests \
|
|
50
|
+
--count \
|
|
51
|
+
--exit-zero \
|
|
52
|
+
--max-complexity=10 \
|
|
53
|
+
--max-line-length=79 \
|
|
54
|
+
--statistics
|
|
55
|
+
continue-on-error: true
|
|
56
|
+
|
|
57
|
+
- name: Type check with mypy
|
|
58
|
+
run: |
|
|
59
|
+
mypy src/pymax \
|
|
60
|
+
--ignore-missing-imports \
|
|
61
|
+
--no-error-summary
|
|
62
|
+
continue-on-error: true
|
|
63
|
+
|
|
64
|
+
- name: Run unit tests
|
|
65
|
+
run: |
|
|
66
|
+
pytest -m "not mockserver" \
|
|
67
|
+
--cov=src/pymax \
|
|
68
|
+
--cov-report=xml \
|
|
69
|
+
--cov-report=term-missing
|
|
70
|
+
|
|
71
|
+
- name: Upload coverage to Codecov
|
|
72
|
+
uses: codecov/codecov-action@v4
|
|
73
|
+
with:
|
|
74
|
+
files: coverage.xml
|
|
75
|
+
flags: unittests
|
|
76
|
+
name: codecov-umbrella
|
|
77
|
+
fail_ci_if_error: false
|
|
78
|
+
|
|
79
|
+
- name: Archive pytest cache
|
|
80
|
+
if: always()
|
|
81
|
+
uses: actions/upload-artifact@v4
|
|
82
|
+
with:
|
|
83
|
+
name: pytest-cache-${{ matrix.python-version }}
|
|
84
|
+
path: .pytest_cache/
|
|
85
|
+
retention-days: 5
|
|
86
|
+
|
|
87
|
+
integration-tests:
|
|
88
|
+
runs-on: ubuntu-latest
|
|
89
|
+
|
|
90
|
+
steps:
|
|
91
|
+
- name: Checkout repository (with submodules)
|
|
92
|
+
uses: actions/checkout@v4
|
|
93
|
+
with:
|
|
94
|
+
submodules: recursive
|
|
95
|
+
|
|
96
|
+
- name: Set up Python
|
|
97
|
+
uses: actions/setup-python@v5
|
|
98
|
+
with:
|
|
99
|
+
python-version: '3.13'
|
|
100
|
+
|
|
101
|
+
- name: Install runtime + test tools (integration)
|
|
102
|
+
run: |
|
|
103
|
+
python -m pip install --upgrade pip
|
|
104
|
+
pip install -e "."
|
|
105
|
+
pip install pytest pytest-asyncio pytest-cov pytest-timeout flake8 mypy
|
|
106
|
+
|
|
107
|
+
- name: Set up Go
|
|
108
|
+
uses: actions/setup-go@v5
|
|
109
|
+
with:
|
|
110
|
+
go-version: '1.22'
|
|
111
|
+
|
|
112
|
+
- name: Start MockServer
|
|
113
|
+
run: |
|
|
114
|
+
git clone https://github.com/fresh-milkshake/gomax-prerelease.git
|
|
115
|
+
cd gomax-prerelease/mockserver
|
|
116
|
+
go mod download
|
|
117
|
+
go run cmd/server/main.go &
|
|
118
|
+
sleep 3
|
|
119
|
+
|
|
120
|
+
- name: Run integration tests
|
|
121
|
+
run: |
|
|
122
|
+
pytest -m mockserver -v --tb=short
|
|
123
|
+
continue-on-error: true
|
|
124
|
+
env:
|
|
125
|
+
MOCKSERVER_WS_URL: ws://localhost:8080/
|
|
126
|
+
MOCKSERVER_HTTP_URL: http://localhost:8080
|
|
127
|
+
|
|
128
|
+
code-quality:
|
|
129
|
+
runs-on: ubuntu-latest
|
|
130
|
+
|
|
131
|
+
steps:
|
|
132
|
+
- name: Checkout repository
|
|
133
|
+
uses: actions/checkout@v4
|
|
134
|
+
|
|
135
|
+
- name: Set up Python
|
|
136
|
+
uses: actions/setup-python@v5
|
|
137
|
+
with:
|
|
138
|
+
python-version: '3.13'
|
|
139
|
+
|
|
140
|
+
- name: Install dependencies + quality tools
|
|
141
|
+
run: |
|
|
142
|
+
python -m pip install --upgrade pip
|
|
143
|
+
pip install -e "."
|
|
144
|
+
# black/isort/pylint используются только в этом job
|
|
145
|
+
pip install black isort pylint
|
|
146
|
+
|
|
147
|
+
- name: Check code formatting with black
|
|
148
|
+
run: black --check src/pymax tests
|
|
149
|
+
continue-on-error: true
|
|
150
|
+
|
|
151
|
+
- name: Check import sorting with isort
|
|
152
|
+
run: isort --check-only src/pymax tests
|
|
153
|
+
continue-on-error: true
|
|
@@ -116,7 +116,7 @@ cache/
|
|
|
116
116
|
# Keep lockfiles and important configs tracked? If you want to track specific lockfiles,
|
|
117
117
|
# remove them from this .gitignore (for example: remove poetry.lock or uv.lock).
|
|
118
118
|
tests2/
|
|
119
|
-
tests
|
|
119
|
+
tests
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
# Bad dev's requirements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxapi-python
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Python wrapper для API мессенджера Max
|
|
5
5
|
Project-URL: Homepage, https://github.com/ink-developer/PyMax
|
|
6
6
|
Project-URL: Repository, https://github.com/ink-developer/PyMax
|
|
@@ -19,6 +19,13 @@ Requires-Dist: msgpack>=1.1.1
|
|
|
19
19
|
Requires-Dist: qrcode>=8.2
|
|
20
20
|
Requires-Dist: sqlmodel>=0.0.24
|
|
21
21
|
Requires-Dist: websockets>=15.0
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: flake8; extra == 'test'
|
|
24
|
+
Requires-Dist: mypy; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'test'
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest-timeout>=2.1.0; extra == 'test'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
22
29
|
Description-Content-Type: text/markdown
|
|
23
30
|
|
|
24
31
|
<p align="center">
|
|
@@ -36,11 +43,6 @@ Description-Content-Type: text/markdown
|
|
|
36
43
|
<img src="https://img.shields.io/badge/packaging-uv-D7FF64.svg" alt="Packaging">
|
|
37
44
|
</p>
|
|
38
45
|
|
|
39
|
-
> [!IMPORTANT]
|
|
40
|
-
> (20.12.25) Из за резкого изменения апи большая часть библиотеки не работает.
|
|
41
|
-
Смотрите [новость](https://t.me/pymax_news/111)
|
|
42
|
-
>
|
|
43
|
-
> P.s добавил логин по qr в dev/1.2.1
|
|
44
46
|
|
|
45
47
|
---
|
|
46
48
|
> ⚠️ **Дисклеймер**
|
|
@@ -82,6 +84,41 @@ uv add -U maxapi-python
|
|
|
82
84
|
|
|
83
85
|
## Быстрый старт
|
|
84
86
|
|
|
87
|
+
### Аутентификация (`device_type`)
|
|
88
|
+
|
|
89
|
+
> [!IMPORTANT]
|
|
90
|
+
> Параметр `device_type` в `UserAgentPayload` **критически важен** для выбора способа авторизации:
|
|
91
|
+
|
|
92
|
+
**Вход по номеру телефона (DESKTOP):**
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from pymax import MaxClient
|
|
96
|
+
from pymax.payloads import UserAgentPayload
|
|
97
|
+
|
|
98
|
+
ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
|
|
99
|
+
|
|
100
|
+
client = MaxClient(
|
|
101
|
+
phone="+79111111111",
|
|
102
|
+
work_dir="cache",
|
|
103
|
+
headers=ua,
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Вход через QR-код (WEB)** — токен совместим с веб-версией Max:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from pymax import MaxClient
|
|
111
|
+
from pymax.payloads import UserAgentPayload
|
|
112
|
+
|
|
113
|
+
ua = UserAgentPayload(device_type="WEB", app_version="25.12.13")
|
|
114
|
+
|
|
115
|
+
client = MaxClient(
|
|
116
|
+
phone="+7911111111",
|
|
117
|
+
work_dir="cache",
|
|
118
|
+
headers=ua,
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
85
122
|
### Базовый пример использования
|
|
86
123
|
|
|
87
124
|
```python
|
|
@@ -13,11 +13,6 @@
|
|
|
13
13
|
<img src="https://img.shields.io/badge/packaging-uv-D7FF64.svg" alt="Packaging">
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
-
> [!IMPORTANT]
|
|
17
|
-
> (20.12.25) Из за резкого изменения апи большая часть библиотеки не работает.
|
|
18
|
-
Смотрите [новость](https://t.me/pymax_news/111)
|
|
19
|
-
>
|
|
20
|
-
> P.s добавил логин по qr в dev/1.2.1
|
|
21
16
|
|
|
22
17
|
---
|
|
23
18
|
> ⚠️ **Дисклеймер**
|
|
@@ -59,6 +54,41 @@ uv add -U maxapi-python
|
|
|
59
54
|
|
|
60
55
|
## Быстрый старт
|
|
61
56
|
|
|
57
|
+
### Аутентификация (`device_type`)
|
|
58
|
+
|
|
59
|
+
> [!IMPORTANT]
|
|
60
|
+
> Параметр `device_type` в `UserAgentPayload` **критически важен** для выбора способа авторизации:
|
|
61
|
+
|
|
62
|
+
**Вход по номеру телефона (DESKTOP):**
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from pymax import MaxClient
|
|
66
|
+
from pymax.payloads import UserAgentPayload
|
|
67
|
+
|
|
68
|
+
ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
|
|
69
|
+
|
|
70
|
+
client = MaxClient(
|
|
71
|
+
phone="+79111111111",
|
|
72
|
+
work_dir="cache",
|
|
73
|
+
headers=ua,
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Вход через QR-код (WEB)** — токен совместим с веб-версией Max:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from pymax import MaxClient
|
|
81
|
+
from pymax.payloads import UserAgentPayload
|
|
82
|
+
|
|
83
|
+
ua = UserAgentPayload(device_type="WEB", app_version="25.12.13")
|
|
84
|
+
|
|
85
|
+
client = MaxClient(
|
|
86
|
+
phone="+7911111111",
|
|
87
|
+
work_dir="cache",
|
|
88
|
+
headers=ua,
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
62
92
|
### Базовый пример использования
|
|
63
93
|
|
|
64
94
|
```python
|
|
@@ -3,10 +3,10 @@ import asyncio
|
|
|
3
3
|
from pymax import MaxClient
|
|
4
4
|
from pymax.payloads import UserAgentPayload
|
|
5
5
|
|
|
6
|
-
ua = UserAgentPayload(device_type="
|
|
6
|
+
ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
|
|
7
7
|
|
|
8
8
|
client = MaxClient(
|
|
9
|
-
phone="+
|
|
9
|
+
phone="+79116290861",
|
|
10
10
|
work_dir="cache",
|
|
11
11
|
headers=ua,
|
|
12
12
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "maxapi-python"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.2"
|
|
4
4
|
description = "Python wrapper для API мессенджера Max"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -36,7 +36,25 @@ where = ["src"]
|
|
|
36
36
|
[tool.setuptools.package-dir]
|
|
37
37
|
"" = "src"
|
|
38
38
|
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
test = [
|
|
41
|
+
"pytest>=8.0.0",
|
|
42
|
+
"pytest-asyncio>=0.24.0",
|
|
43
|
+
"pytest-cov>=5.0.0",
|
|
44
|
+
"pytest-timeout>=2.1.0",
|
|
45
|
+
"flake8",
|
|
46
|
+
"mypy",
|
|
47
|
+
]
|
|
48
|
+
|
|
39
49
|
[dependency-groups]
|
|
50
|
+
test = [
|
|
51
|
+
"pytest>=8.0.0",
|
|
52
|
+
"pytest-asyncio>=0.24.0",
|
|
53
|
+
"pytest-cov>=5.0.0",
|
|
54
|
+
"pytest-timeout>=2.1.0",
|
|
55
|
+
"flake8",
|
|
56
|
+
"mypy",
|
|
57
|
+
]
|
|
40
58
|
dev = [
|
|
41
59
|
"furo>=2025.9.25",
|
|
42
60
|
"ghp-import>=2.1.0",
|
|
@@ -46,6 +64,10 @@ dev = [
|
|
|
46
64
|
"pre-commit>=4.3.0",
|
|
47
65
|
"pydocstring>=0.2.1",
|
|
48
66
|
"sphinx>=8.1.3",
|
|
67
|
+
"pytest>=8.0.0",
|
|
68
|
+
"pytest-asyncio>=0.24.0",
|
|
69
|
+
"pytest-cov>=5.0.0",
|
|
70
|
+
"pytest-timeout>=2.1.0",
|
|
49
71
|
]
|
|
50
72
|
|
|
51
73
|
[tool.hatch.build.targets.wheel]
|
|
@@ -74,3 +96,33 @@ profile = "black"
|
|
|
74
96
|
line_length = 79
|
|
75
97
|
multi_line_output = 3
|
|
76
98
|
include_trailing_comma = true
|
|
99
|
+
|
|
100
|
+
[tool.pytest.ini_options]
|
|
101
|
+
asyncio_mode = "auto"
|
|
102
|
+
testpaths = ["tests"]
|
|
103
|
+
python_files = ["test_*.py"]
|
|
104
|
+
python_classes = ["Test*"]
|
|
105
|
+
python_functions = ["test_*"]
|
|
106
|
+
addopts = "-v --tb=short --strict-markers"
|
|
107
|
+
markers = [
|
|
108
|
+
"asyncio: marker for asyncio tests",
|
|
109
|
+
"mockserver: marker for MockServer integration tests",
|
|
110
|
+
"integration: marker for integration tests",
|
|
111
|
+
"slow: marker for slow tests",
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
[tool.coverage.run]
|
|
115
|
+
source = ["src/pymax"]
|
|
116
|
+
branch = true
|
|
117
|
+
|
|
118
|
+
[tool.coverage.report]
|
|
119
|
+
exclude_lines = [
|
|
120
|
+
"pragma: no cover",
|
|
121
|
+
"def __repr__",
|
|
122
|
+
"raise AssertionError",
|
|
123
|
+
"raise NotImplementedError",
|
|
124
|
+
"if __name__ == .__main__.:",
|
|
125
|
+
"if TYPE_CHECKING:",
|
|
126
|
+
"class .*\\bProtocol\\):",
|
|
127
|
+
"@(abc\\.)?abstractmethod",
|
|
128
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[pytest]
|
|
2
|
+
|
|
3
|
+
asyncio_mode = auto
|
|
4
|
+
|
|
5
|
+
testpaths = tests
|
|
6
|
+
|
|
7
|
+
python_files = test_*.py
|
|
8
|
+
python_classes = Test*
|
|
9
|
+
python_functions = test_*
|
|
10
|
+
|
|
11
|
+
addopts =
|
|
12
|
+
-v
|
|
13
|
+
--tb=short
|
|
14
|
+
--strict-markers
|
|
15
|
+
-ra
|
|
16
|
+
--color=yes
|
|
17
|
+
|
|
18
|
+
markers =
|
|
19
|
+
asyncio: асинхронные тесты
|
|
20
|
+
mockserver: интеграционные тесты с MockServer
|
|
21
|
+
integration: интеграционные тесты
|
|
22
|
+
slow: медленные тесты
|
|
23
|
+
unit: модульные тесты
|
|
24
|
+
skip_ci: пропустить в CI
|
|
25
|
+
|
|
26
|
+
timeout = 30
|
|
@@ -20,6 +20,28 @@ MaxClient
|
|
|
20
20
|
logger=None, # Пользовательский логгер
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
+
.. warning::
|
|
24
|
+
|
|
25
|
+
Параметр ``device_type`` в ``UserAgentPayload`` **критически важен** для выбора способа авторизации:
|
|
26
|
+
|
|
27
|
+
**DESKTOP** — вход по номеру телефона:
|
|
28
|
+
|
|
29
|
+
.. code-block:: python
|
|
30
|
+
|
|
31
|
+
from pymax.payloads import UserAgentPayload
|
|
32
|
+
|
|
33
|
+
ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
|
|
34
|
+
client = MaxClient(phone="+79111111111", headers=ua)
|
|
35
|
+
|
|
36
|
+
**WEB** — вход через QR-код; токен совместим с веб-версией Max:
|
|
37
|
+
|
|
38
|
+
.. code-block:: python
|
|
39
|
+
|
|
40
|
+
from pymax.payloads import UserAgentPayload
|
|
41
|
+
|
|
42
|
+
ua = UserAgentPayload(device_type="WEB", app_version="25.12.13")
|
|
43
|
+
client = MaxClient(phone="+79111111111", headers=ua)
|
|
44
|
+
|
|
23
45
|
Основные методы:
|
|
24
46
|
|
|
25
47
|
.. code-block:: python
|
|
@@ -50,9 +50,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
50
50
|
payload=payload,
|
|
51
51
|
).model_dump(by_alias=True)
|
|
52
52
|
|
|
53
|
-
self.logger.debug(
|
|
54
|
-
"make_message opcode=%s cmd=%s seq=%s", opcode, cmd, self._seq
|
|
55
|
-
)
|
|
53
|
+
self.logger.debug("make_message opcode=%s cmd=%s seq=%s", opcode, cmd, self._seq)
|
|
56
54
|
return msg
|
|
57
55
|
|
|
58
56
|
async def _send_interactive_ping(self) -> None:
|
|
@@ -68,9 +66,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
68
66
|
self.logger.warning("Interactive ping failed", exc_info=True)
|
|
69
67
|
await asyncio.sleep(DEFAULT_PING_INTERVAL)
|
|
70
68
|
|
|
71
|
-
async def connect(
|
|
72
|
-
self, user_agent: UserAgentPayload | None = None
|
|
73
|
-
) -> dict[str, Any] | None:
|
|
69
|
+
async def connect(self, user_agent: UserAgentPayload | None = None) -> dict[str, Any] | None:
|
|
74
70
|
"""
|
|
75
71
|
Устанавливает соединение WebSocket с сервером и выполняет handshake.
|
|
76
72
|
|
|
@@ -173,9 +169,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
173
169
|
fut = self._file_upload_waiters.pop(id_, None)
|
|
174
170
|
if fut and not fut.done():
|
|
175
171
|
fut.set_result(data)
|
|
176
|
-
self.logger.debug(
|
|
177
|
-
"Fulfilled file upload waiter for %s=%s", key, id_
|
|
178
|
-
)
|
|
172
|
+
self.logger.debug("Fulfilled file upload waiter for %s=%s", key, id_)
|
|
179
173
|
|
|
180
174
|
async def _handle_message_notifications(self, data: dict) -> None:
|
|
181
175
|
if data.get("opcode") != Opcode.NOTIF_MESSAGE.value:
|
|
@@ -359,9 +353,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
359
353
|
)
|
|
360
354
|
return data
|
|
361
355
|
except Exception:
|
|
362
|
-
self.logger.exception(
|
|
363
|
-
"Send and wait failed (opcode=%s, seq=%s)", opcode, msg["seq"]
|
|
364
|
-
)
|
|
356
|
+
self.logger.exception("Send and wait failed (opcode=%s, seq=%s)", opcode, msg["seq"])
|
|
365
357
|
raise RuntimeError("Send and wait failed")
|
|
366
358
|
finally:
|
|
367
359
|
self._pending.pop(msg["seq"], None)
|
|
@@ -442,7 +434,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
442
434
|
else:
|
|
443
435
|
return float(2**retry_count)
|
|
444
436
|
|
|
445
|
-
async def _sync(self) -> None:
|
|
437
|
+
async def _sync(self, user_agent: UserAgentPayload) -> None:
|
|
446
438
|
self.logger.info("Starting initial sync")
|
|
447
439
|
|
|
448
440
|
payload = SyncPayload(
|
|
@@ -453,6 +445,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
453
445
|
presence_sync=0,
|
|
454
446
|
drafts_sync=0,
|
|
455
447
|
chats_count=40,
|
|
448
|
+
user_agent=user_agent,
|
|
456
449
|
).model_dump(by_alias=True)
|
|
457
450
|
try:
|
|
458
451
|
data = await self._send_and_wait(opcode=Opcode.LOGIN, payload=payload)
|
|
@@ -473,9 +466,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
473
466
|
self.logger.exception("Error parsing chat entry")
|
|
474
467
|
|
|
475
468
|
if raw_payload.get("profile", {}).get("contact"):
|
|
476
|
-
self.me = Me.from_dict(
|
|
477
|
-
raw_payload.get("profile", {}).get("contact", {})
|
|
478
|
-
)
|
|
469
|
+
self.me = Me.from_dict(raw_payload.get("profile", {}).get("contact", {}))
|
|
479
470
|
|
|
480
471
|
self.logger.info(
|
|
481
472
|
"Sync completed: dialogs=%d chats=%d channels=%d",
|
|
@@ -39,6 +39,20 @@ class BaseWebSocketMessage(BaseModel):
|
|
|
39
39
|
payload: dict[str, Any]
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class UserAgentPayload(CamelModel):
|
|
43
|
+
device_type: str = Field(default=DEFAULT_DEVICE_TYPE)
|
|
44
|
+
locale: str = Field(default=DEFAULT_LOCALE)
|
|
45
|
+
device_locale: str = Field(default=DEFAULT_DEVICE_LOCALE)
|
|
46
|
+
os_version: str = Field(default=DEFAULT_OS_VERSION)
|
|
47
|
+
device_name: str = Field(default=DEFAULT_DEVICE_NAME)
|
|
48
|
+
header_user_agent: str = Field(default=DEFAULT_USER_AGENT)
|
|
49
|
+
app_version: str = Field(default=DEFAULT_APP_VERSION)
|
|
50
|
+
screen: str = Field(default=DEFAULT_SCREEN)
|
|
51
|
+
timezone: str = Field(default=DEFAULT_TIMEZONE)
|
|
52
|
+
client_session_id: int = Field(default=DEFAULT_CLIENT_SESSION_ID)
|
|
53
|
+
build_number: int = Field(default=DEFAULT_BUILD_NUMBER)
|
|
54
|
+
|
|
55
|
+
|
|
42
56
|
class RequestCodePayload(CamelModel):
|
|
43
57
|
phone: str
|
|
44
58
|
type: AuthType = AuthType.START_AUTH
|
|
@@ -59,6 +73,21 @@ class SyncPayload(CamelModel):
|
|
|
59
73
|
presence_sync: int = 0
|
|
60
74
|
drafts_sync: int = 0
|
|
61
75
|
chats_count: int = 40
|
|
76
|
+
user_agent: UserAgentPayload = Field(
|
|
77
|
+
default_factory=lambda: UserAgentPayload(
|
|
78
|
+
device_type=DEFAULT_DEVICE_TYPE,
|
|
79
|
+
locale=DEFAULT_LOCALE,
|
|
80
|
+
device_locale=DEFAULT_DEVICE_LOCALE,
|
|
81
|
+
os_version=DEFAULT_OS_VERSION,
|
|
82
|
+
device_name=DEFAULT_DEVICE_NAME,
|
|
83
|
+
header_user_agent=DEFAULT_USER_AGENT,
|
|
84
|
+
app_version=DEFAULT_APP_VERSION,
|
|
85
|
+
screen=DEFAULT_SCREEN,
|
|
86
|
+
timezone=DEFAULT_TIMEZONE,
|
|
87
|
+
client_session_id=DEFAULT_CLIENT_SESSION_ID,
|
|
88
|
+
build_number=DEFAULT_BUILD_NUMBER,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
62
91
|
|
|
63
92
|
|
|
64
93
|
class ReplyLink(CamelModel):
|
|
@@ -276,20 +305,6 @@ class RemoveReactionPayload(CamelModel):
|
|
|
276
305
|
message_id: str
|
|
277
306
|
|
|
278
307
|
|
|
279
|
-
class UserAgentPayload(CamelModel):
|
|
280
|
-
device_type: str = Field(default=DEFAULT_DEVICE_TYPE)
|
|
281
|
-
locale: str = Field(default=DEFAULT_LOCALE)
|
|
282
|
-
device_locale: str = Field(default=DEFAULT_DEVICE_LOCALE)
|
|
283
|
-
os_version: str = Field(default=DEFAULT_OS_VERSION)
|
|
284
|
-
device_name: str = Field(default=DEFAULT_DEVICE_NAME)
|
|
285
|
-
header_user_agent: str = Field(default=DEFAULT_USER_AGENT)
|
|
286
|
-
app_version: str = Field(default=DEFAULT_APP_VERSION)
|
|
287
|
-
screen: str = Field(default=DEFAULT_SCREEN)
|
|
288
|
-
timezone: str = Field(default=DEFAULT_TIMEZONE)
|
|
289
|
-
client_session_id: int = Field(default=DEFAULT_CLIENT_SESSION_ID)
|
|
290
|
-
build_number: int = Field(default=DEFAULT_BUILD_NUMBER)
|
|
291
|
-
|
|
292
|
-
|
|
293
308
|
class ReworkInviteLinkPayload(CamelModel):
|
|
294
309
|
revoke_private_link: bool = True
|
|
295
310
|
chat_id: int
|
|
@@ -9,7 +9,7 @@ WEBSOCKET_ORIGIN: Final[Origin] = Origin("https://web.max.ru")
|
|
|
9
9
|
HOST: Final[str] = "api.oneme.ru"
|
|
10
10
|
PORT: Final[int] = 443
|
|
11
11
|
DEFAULT_TIMEOUT: Final[float] = 20.0
|
|
12
|
-
DEFAULT_DEVICE_TYPE: Final[str] = "
|
|
12
|
+
DEFAULT_DEVICE_TYPE: Final[str] = "DESKTOP"
|
|
13
13
|
DEFAULT_LOCALE: Final[str] = "ru"
|
|
14
14
|
DEFAULT_DEVICE_LOCALE: Final[str] = "ru"
|
|
15
15
|
DEFAULT_DEVICE_NAME: Final[str] = "Chrome"
|
maxapi_python-1.2.1/pytest.ini
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|