maxapi-python 1.2.1__tar.gz → 1.2.3__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.3/.github/workflows/tests.yml +153 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.gitignore +1 -1
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/PKG-INFO +43 -6
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/README.md +35 -5
- maxapi_python-1.2.3/examples/example.py +74 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/pyproject.toml +53 -1
- maxapi_python-1.2.3/pytest.ini +26 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/clients.rst +22 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/core.py +2 -1
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/files.py +5 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/interfaces.py +1 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/group.py +31 -12
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/self.py +74 -9
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/socket.py +10 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/websocket.py +16 -16
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/payloads.py +32 -14
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/static/constant.py +1 -1
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/static/enum.py +1 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/types.py +57 -28
- maxapi_python-1.2.1/examples/example.py +0 -268
- maxapi_python-1.2.1/examples/flt_test.py +0 -51
- maxapi_python-1.2.1/examples/large_file_upload.py +0 -51
- maxapi_python-1.2.1/examples/reg.py +0 -34
- maxapi_python-1.2.1/examples/test.py +0 -20
- maxapi_python-1.2.1/pytest.ini +0 -6
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.coderabbit.yaml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.github/FUNDING.yml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.github/pull_request_template.md +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.github/workflows/publish.yml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/.pre-commit-config.yaml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/LICENSE +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/assets/icon.svg +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/assets/logo.svg +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/examples/telegram_bridge.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/mkdocs.yml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/Makefile +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/build.sh +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/make.bat +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/_static/logo.svg +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/conf.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/decorators.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/examples.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/guides.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/index.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/installation.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/quickstart.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/redocs/source/types.rst +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/ruff.toml +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/__init__.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/crud.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/exceptions.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/filters.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/formatter.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/formatting.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/__init__.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/auth.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/channel.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/handler.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/message.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/scheduler.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/telemetry.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/user.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/mixins/utils.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/models.py +0 -0
- {maxapi_python-1.2.1 → maxapi_python-1.2.3}/src/pymax/navigation.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.3
|
|
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
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import datetime
|
|
3
|
+
import logging
|
|
4
|
+
from time import time
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import pymax
|
|
8
|
+
from pymax import MaxClient, Message, ReactionInfo, SocketMaxClient, filters
|
|
9
|
+
from pymax.files import File, Photo, Video
|
|
10
|
+
from pymax.filters import Filters
|
|
11
|
+
from pymax.payloads import UserAgentPayload
|
|
12
|
+
from pymax.static.enum import AttachType, Opcode
|
|
13
|
+
from pymax.types import Chat
|
|
14
|
+
|
|
15
|
+
phone = "+79991234567"
|
|
16
|
+
headers = UserAgentPayload(device_type="WEB")
|
|
17
|
+
|
|
18
|
+
client = MaxClient(
|
|
19
|
+
phone=phone,
|
|
20
|
+
work_dir="cache",
|
|
21
|
+
reconnect=False,
|
|
22
|
+
logger=None,
|
|
23
|
+
headers=headers,
|
|
24
|
+
)
|
|
25
|
+
client.logger.setLevel(logging.INFO)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@client.on_start
|
|
29
|
+
async def handle_start() -> None:
|
|
30
|
+
print(f"Client started as {client.me.names[0].first_name}!")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@client.on_raw_receive
|
|
34
|
+
async def handle_raw_receive(data: dict[str, Any]) -> None:
|
|
35
|
+
print(f"Raw data received: {data}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@client.on_reaction_change
|
|
39
|
+
async def handle_reaction_change(
|
|
40
|
+
message_id: str, chat_id: int, reaction_info: ReactionInfo
|
|
41
|
+
) -> None:
|
|
42
|
+
print(
|
|
43
|
+
f"Reaction changed on message {message_id} in chat {chat_id}: "
|
|
44
|
+
f"Total count: {reaction_info.total_count}, "
|
|
45
|
+
f"Your reaction: {reaction_info.your_reaction}, "
|
|
46
|
+
f"Counters: {reaction_info.counters[0].reaction}={reaction_info.counters[0].count}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@client.on_chat_update
|
|
51
|
+
async def handle_chat_update(chat: Chat) -> None:
|
|
52
|
+
print(f"Chat updated: {chat.id}, new title: {chat.title}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@client.on_message(Filters.chat(0) & Filters.text("hello"))
|
|
56
|
+
async def handle_message(message: Message) -> None:
|
|
57
|
+
print(f"New message in chat {message.chat_id} from {message.sender}: {message.text}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@client.on_message_edit()
|
|
61
|
+
async def handle_edited_message(message: Message) -> None:
|
|
62
|
+
print(f"Edited message in chat {message.chat_id}: {message.text}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@client.on_message_delete()
|
|
66
|
+
async def handle_deleted_message(message: Message) -> None:
|
|
67
|
+
print(f"Deleted message in chat {message.chat_id}: {message.id}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
try:
|
|
72
|
+
asyncio.run(client.start())
|
|
73
|
+
except KeyboardInterrupt:
|
|
74
|
+
print("Client stopped by user")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "maxapi-python"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.3"
|
|
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
|
|
@@ -116,6 +116,7 @@ class MaxClient(ApiMixin, WebSocketMixin, BaseClient):
|
|
|
116
116
|
self.dialogs: list[Dialog] = []
|
|
117
117
|
self.channels: list[Channel] = []
|
|
118
118
|
self.me: Me | None = None
|
|
119
|
+
self.contacts: list[User] = []
|
|
119
120
|
self._users: dict[int, User] = {}
|
|
120
121
|
|
|
121
122
|
self._work_dir: str = work_dir
|
|
@@ -295,7 +296,7 @@ class MaxClient(ApiMixin, WebSocketMixin, BaseClient):
|
|
|
295
296
|
if self._token is None:
|
|
296
297
|
await self._login()
|
|
297
298
|
|
|
298
|
-
await self._sync()
|
|
299
|
+
await self._sync(self.user_agent)
|
|
299
300
|
|
|
300
301
|
await self._post_login_tasks(sync=False)
|
|
301
302
|
|
|
@@ -46,6 +46,11 @@ class Photo(BaseFile):
|
|
|
46
46
|
} # FIXME: костыль ✅
|
|
47
47
|
|
|
48
48
|
def __init__(self, url: str | None = None, path: str | None = None) -> None:
|
|
49
|
+
if path:
|
|
50
|
+
self.file_name = Path(path).name
|
|
51
|
+
elif url:
|
|
52
|
+
self.file_name = Path(url).name
|
|
53
|
+
|
|
49
54
|
super().__init__(url, path)
|
|
50
55
|
|
|
51
56
|
def validate_photo(self) -> tuple[str, str] | None:
|
|
@@ -95,9 +95,7 @@ class GroupMixin(ClientProtocol):
|
|
|
95
95
|
operation="add",
|
|
96
96
|
).model_dump(by_alias=True)
|
|
97
97
|
|
|
98
|
-
data = await self._send_and_wait(
|
|
99
|
-
opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload
|
|
100
|
-
)
|
|
98
|
+
data = await self._send_and_wait(opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload)
|
|
101
99
|
|
|
102
100
|
if data.get("payload", {}).get("error"):
|
|
103
101
|
MixinsUtils.handle_error(data)
|
|
@@ -155,9 +153,7 @@ class GroupMixin(ClientProtocol):
|
|
|
155
153
|
clean_msg_period=clean_msg_period,
|
|
156
154
|
).model_dump(by_alias=True)
|
|
157
155
|
|
|
158
|
-
data = await self._send_and_wait(
|
|
159
|
-
opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload
|
|
160
|
-
)
|
|
156
|
+
data = await self._send_and_wait(opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload)
|
|
161
157
|
|
|
162
158
|
if data.get("payload", {}).get("error"):
|
|
163
159
|
MixinsUtils.handle_error(data)
|
|
@@ -293,6 +289,33 @@ class GroupMixin(ClientProtocol):
|
|
|
293
289
|
|
|
294
290
|
return chat
|
|
295
291
|
|
|
292
|
+
async def resolve_group_by_link(self, link: str) -> Chat | None:
|
|
293
|
+
"""
|
|
294
|
+
Разрешает группу по ссылке
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
link (str): Ссылка на группу.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Chat | None: Объект чата группы или None, если не найдено.
|
|
301
|
+
"""
|
|
302
|
+
proceed_link = self._process_chat_join_link(link)
|
|
303
|
+
if proceed_link is None:
|
|
304
|
+
raise ValueError("Invalid group link")
|
|
305
|
+
|
|
306
|
+
data = await self._send_and_wait(
|
|
307
|
+
opcode=Opcode.LINK_INFO,
|
|
308
|
+
payload={
|
|
309
|
+
"link": proceed_link,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if data.get("payload", {}).get("error"):
|
|
314
|
+
MixinsUtils.handle_error(data)
|
|
315
|
+
|
|
316
|
+
chat = Chat.from_dict(data["payload"].get("chat", {}))
|
|
317
|
+
return chat
|
|
318
|
+
|
|
296
319
|
async def rework_invite_link(self, chat_id: int) -> Chat:
|
|
297
320
|
"""
|
|
298
321
|
Пересоздает ссылку для приглашения в группу
|
|
@@ -329,14 +352,10 @@ class GroupMixin(ClientProtocol):
|
|
|
329
352
|
chat_id for chat_id in chat_ids if await self._get_chat(chat_id) is None
|
|
330
353
|
]
|
|
331
354
|
if missed_chat_ids:
|
|
332
|
-
payload = GetChatInfoPayload(chat_ids=missed_chat_ids).model_dump(
|
|
333
|
-
by_alias=True
|
|
334
|
-
)
|
|
355
|
+
payload = GetChatInfoPayload(chat_ids=missed_chat_ids).model_dump(by_alias=True)
|
|
335
356
|
else:
|
|
336
357
|
chats: list[Chat] = [
|
|
337
|
-
chat
|
|
338
|
-
for chat_id in chat_ids
|
|
339
|
-
if (chat := await self._get_chat(chat_id)) is not None
|
|
358
|
+
chat for chat_id in chat_ids if (chat := await self._get_chat(chat_id)) is not None
|
|
340
359
|
]
|
|
341
360
|
return chats
|
|
342
361
|
|