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.
Files changed (67) hide show
  1. maxapi_python-1.2.2/.github/workflows/tests.yml +153 -0
  2. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.gitignore +1 -1
  3. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/PKG-INFO +43 -6
  4. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/README.md +35 -5
  5. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/test.py +2 -2
  6. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/pyproject.toml +53 -1
  7. maxapi_python-1.2.2/pytest.ini +26 -0
  8. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/clients.rst +22 -0
  9. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/core.py +1 -1
  10. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/websocket.py +7 -16
  11. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/payloads.py +29 -14
  12. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/static/constant.py +1 -1
  13. maxapi_python-1.2.1/pytest.ini +0 -6
  14. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.coderabbit.yaml +0 -0
  15. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/FUNDING.yml +0 -0
  16. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  17. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  18. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
  19. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/pull_request_template.md +0 -0
  20. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.github/workflows/publish.yml +0 -0
  21. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/.pre-commit-config.yaml +0 -0
  22. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/LICENSE +0 -0
  23. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/assets/icon.svg +0 -0
  24. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/assets/logo.svg +0 -0
  25. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/example.py +0 -0
  26. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/flt_test.py +0 -0
  27. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/large_file_upload.py +0 -0
  28. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/reg.py +0 -0
  29. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/examples/telegram_bridge.py +0 -0
  30. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/mkdocs.yml +0 -0
  31. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/Makefile +0 -0
  32. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/build.sh +0 -0
  33. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/make.bat +0 -0
  34. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/_static/logo.svg +0 -0
  35. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/conf.py +0 -0
  36. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/decorators.rst +0 -0
  37. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/examples.rst +0 -0
  38. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/guides.rst +0 -0
  39. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/index.rst +0 -0
  40. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/installation.rst +0 -0
  41. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/quickstart.rst +0 -0
  42. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/redocs/source/types.rst +0 -0
  43. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/ruff.toml +0 -0
  44. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/__init__.py +0 -0
  45. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/crud.py +0 -0
  46. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/exceptions.py +0 -0
  47. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/files.py +0 -0
  48. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/filters.py +0 -0
  49. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/formatter.py +0 -0
  50. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/formatting.py +0 -0
  51. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/interfaces.py +0 -0
  52. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/__init__.py +0 -0
  53. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/auth.py +0 -0
  54. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/channel.py +0 -0
  55. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/group.py +0 -0
  56. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/handler.py +0 -0
  57. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/message.py +0 -0
  58. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/scheduler.py +0 -0
  59. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/self.py +0 -0
  60. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/socket.py +0 -0
  61. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/telemetry.py +0 -0
  62. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/user.py +0 -0
  63. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/mixins/utils.py +0 -0
  64. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/models.py +0 -0
  65. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/navigation.py +0 -0
  66. {maxapi_python-1.2.1 → maxapi_python-1.2.2}/src/pymax/static/enum.py +0 -0
  67. {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.1
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="WEB")
6
+ ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
7
7
 
8
8
  client = MaxClient(
9
- phone="+79911111111",
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.1"
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
@@ -295,7 +295,7 @@ class MaxClient(ApiMixin, WebSocketMixin, BaseClient):
295
295
  if self._token is None:
296
296
  await self._login()
297
297
 
298
- await self._sync()
298
+ await self._sync(self.user_agent)
299
299
 
300
300
  await self._post_login_tasks(sync=False)
301
301
 
@@ -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] = "WEB"
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"
@@ -1,6 +0,0 @@
1
- [pytest]
2
- testpaths = tests
3
- python_files = test_*.py
4
- python_classes = Test*
5
- python_functions = test_*
6
- addopts = -v --tb=short
File without changes
File without changes
File without changes