vkx 0.2.2__tar.gz → 0.2.4__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.
- {vkx-0.2.2 → vkx-0.2.4}/PKG-INFO +59 -5
- {vkx-0.2.2 → vkx-0.2.4}/README.md +58 -4
- {vkx-0.2.2 → vkx-0.2.4}/pyproject.toml +1 -1
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/__init__.py +2 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/client/__init__.py +2 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/client/client.py +70 -13
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/client/http.py +15 -0
- vkx-0.2.4/src/vkx/client/storage.py +458 -0
- vkx-0.2.4/src/vkx/client/tokens.py +813 -0
- vkx-0.2.2/src/vkx/client/storage.py +0 -255
- vkx-0.2.2/src/vkx/client/tokens.py +0 -427
- {vkx-0.2.2 → vkx-0.2.4}/.gitignore +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/LICENSE +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/examples/README.md +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/_common.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/aiohttp.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/django.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/fastapi.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/flask.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/sanic.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/adapters/starlette.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/client/errors.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/client/pagination.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/client/resolve.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/client/upload.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/base_model.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/categories.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/bot_events.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/callback_events.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/enums/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/enums/callback_events.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/enums/group_events.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/enums/user_events.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/objects/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/objects/group_event_objects.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/objects/user_event_objects.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/events/user_events.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/formatting.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/account.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/ads.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/app_widgets.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/apps.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/auth.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/base_category.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/board.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/bugtracker.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/calls.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/database.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/docs.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/donut.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/downloaded_games.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/fave.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/friends.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/gifts.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/groups.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/lead_forms.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/likes.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/market.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/messages.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/newsfeed.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/notes.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/notifications.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/orders.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/pages.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/photos.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/podcasts.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/polls.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/pretty_cards.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/search.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/secure.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/stats.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/status.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/storage.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/store.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/stories.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/streaming.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/translations.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/users.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/utils.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/video.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/wall.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/methods/widgets.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/objects.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/account.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/ads.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/app_widgets.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/apps.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/auth.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/base.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/board.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/bugtracker.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/calls.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/database.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/docs.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/donut.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/downloaded_games.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/fave.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/friends.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/gifts.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/groups.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/lead_forms.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/likes.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/market.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/messages.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/newsfeed.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/notes.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/notifications.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/orders.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/pages.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/photos.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/podcasts.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/pretty_cards.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/search.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/store.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/stories.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/streaming.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/translations.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/users.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/utils.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/video.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/wall.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/models/responses/widgets.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/py.typed +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/server/__init__.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/server/_polling.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/server/bots_longpoll.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/server/callback.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/server/callback_server.py +0 -0
- {vkx-0.2.2 → vkx-0.2.4}/src/vkx/server/longpoll.py +0 -0
{vkx-0.2.2 → vkx-0.2.4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vkx
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Асинхронный клиент VK API: выбор токена по scope, батчинг через execute, типы API/событий и разбор Callback/Long Poll.
|
|
5
5
|
Project-URL: Homepage, https://github.com/matrixd0t/vkx
|
|
6
6
|
Project-URL: Repository, https://github.com/matrixd0t/vkx
|
|
@@ -100,18 +100,24 @@ asyncio.run(main())
|
|
|
100
100
|
|
|
101
101
|
### Создание
|
|
102
102
|
|
|
103
|
-
Передать можно статичный токен, веб-cookies, готовые источники или всё вместе:
|
|
103
|
+
Передать можно статичный токен, веб-cookies, логин/пароль, готовые источники или всё вместе:
|
|
104
104
|
|
|
105
105
|
```python
|
|
106
|
-
from vkx import VKClient
|
|
106
|
+
from vkx import VKClient, open_store
|
|
107
107
|
|
|
108
108
|
vk = VKClient(tokens="vk1.a....")
|
|
109
109
|
vk = VKClient(cookies={"p": "...", "remixsid": "..."})
|
|
110
110
|
vk = VKClient(tokens=["tok_a", "tok_b"], cookies=[{"p": ..., "remixsid": ...}])
|
|
111
|
+
vk = VKClient(cookies="cookies.json") # в том числе JSON-файл, созданный vkqr
|
|
112
|
+
vk = VKClient(login="user@example.com", password="...", store=open_store("auth.json"))
|
|
113
|
+
# переопределить User-Agent HTTP-клиента:
|
|
114
|
+
vk = VKClient(tokens="vk1.a....", user_agent="MyApp/1.0")
|
|
111
115
|
```
|
|
112
116
|
|
|
113
117
|
Конструктор не ходит в сеть; сеть трогается при первом `call()` или явном
|
|
114
118
|
`await vk.probe()`.
|
|
119
|
+
Если передан `user_agent`, он заменяет заголовок `User-Agent` HTTP-клиента;
|
|
120
|
+
переданный вручную `http_client` должен предоставлять изменяемый `headers`.
|
|
115
121
|
|
|
116
122
|
### Владелец и права
|
|
117
123
|
|
|
@@ -144,10 +150,58 @@ countries = await vk.database.get_countries(count=1500)
|
|
|
144
150
|
|
|
145
151
|
```python
|
|
146
152
|
vk = VKClient(cookies={"p": "...", "remixsid": "..."})
|
|
153
|
+
vk = VKClient(cookies="cookies.json")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Принимается словарь cookie, список таких словарей или JSON-файл. Поддерживаются
|
|
157
|
+
простой JSON-объект `{"p": "...", "remixsid": "..."}`, список объектов и формат
|
|
158
|
+
[`vkqr`](https://github.com/matrixd0t/vkqr):
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"cookies": [
|
|
163
|
+
{
|
|
164
|
+
"created_at": 1700000000,
|
|
165
|
+
"user_id": 580106999,
|
|
166
|
+
"p": "...",
|
|
167
|
+
"remixsid": "..."
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Для формата `vkqr` можно передать путь прямо в `VKClient` — файл сам читается,
|
|
174
|
+
метаданные `created_at` и `user_id` не отправляются как cookies. Веб-токен
|
|
175
|
+
получается и обновляется автоматически; при переданном `store` токен и владелец
|
|
176
|
+
сохраняются в нём. `open_store()` без пути создаёт SQLite в `.vkx/vkx.sqlite`.
|
|
177
|
+
Если `open_store("cookies.json")`
|
|
178
|
+
используется с JSON-файлом `vkqr`, VKX сохраняет cookie-записи в том же массиве
|
|
179
|
+
`cookies`, а кеш токенов и состояния обычных токенов — в служебном поле `_vkx`.
|
|
180
|
+
Старый формат JSONStore читается и преобразуется при следующем сохранении. Если
|
|
181
|
+
в файле несколько разных аккаунтов, один `VKClient` всё равно будет привязан к
|
|
182
|
+
первому успешно проверенному владельцу; для отдельных аккаунтов создавайте
|
|
183
|
+
отдельные клиенты.
|
|
184
|
+
|
|
185
|
+
### Авторизация по логину и паролю
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from vkx import VKClient, open_store
|
|
189
|
+
|
|
190
|
+
store = open_store("auth.json")
|
|
191
|
+
vk = VKClient(login="user@example.com", password="...", store=store)
|
|
147
192
|
```
|
|
148
193
|
|
|
149
|
-
|
|
150
|
-
|
|
194
|
+
Источник сначала пробует сохранённые cookies и обновляет по ним web-токен. Если
|
|
195
|
+
VK отклоняет cookie-сессию, он пробует получить новые cookies через VK ID по
|
|
196
|
+
логину/паролю, сохраняет их и повторяет получение web-токена. При следующем
|
|
197
|
+
запуске можно указать только `login` и тот же `store`: сохранённый пароль будет
|
|
198
|
+
восстановлен. Если для входа VK требует капчу или код подтверждения, авторизация
|
|
199
|
+
только по логину/паролю завершится понятной ошибкой — автоматического обхода
|
|
200
|
+
дополнительной проверки нет.
|
|
201
|
+
|
|
202
|
+
Пароль сохраняется в Store **без шифрования**. Ограничьте доступ к файлу/БД и не
|
|
203
|
+
коммитьте их в репозиторий; для JSON используйте права доступа, ограниченные
|
|
204
|
+
текущим пользователем.
|
|
151
205
|
|
|
152
206
|
### `resolve()`
|
|
153
207
|
|
|
@@ -72,18 +72,24 @@ asyncio.run(main())
|
|
|
72
72
|
|
|
73
73
|
### Создание
|
|
74
74
|
|
|
75
|
-
Передать можно статичный токен, веб-cookies, готовые источники или всё вместе:
|
|
75
|
+
Передать можно статичный токен, веб-cookies, логин/пароль, готовые источники или всё вместе:
|
|
76
76
|
|
|
77
77
|
```python
|
|
78
|
-
from vkx import VKClient
|
|
78
|
+
from vkx import VKClient, open_store
|
|
79
79
|
|
|
80
80
|
vk = VKClient(tokens="vk1.a....")
|
|
81
81
|
vk = VKClient(cookies={"p": "...", "remixsid": "..."})
|
|
82
82
|
vk = VKClient(tokens=["tok_a", "tok_b"], cookies=[{"p": ..., "remixsid": ...}])
|
|
83
|
+
vk = VKClient(cookies="cookies.json") # в том числе JSON-файл, созданный vkqr
|
|
84
|
+
vk = VKClient(login="user@example.com", password="...", store=open_store("auth.json"))
|
|
85
|
+
# переопределить User-Agent HTTP-клиента:
|
|
86
|
+
vk = VKClient(tokens="vk1.a....", user_agent="MyApp/1.0")
|
|
83
87
|
```
|
|
84
88
|
|
|
85
89
|
Конструктор не ходит в сеть; сеть трогается при первом `call()` или явном
|
|
86
90
|
`await vk.probe()`.
|
|
91
|
+
Если передан `user_agent`, он заменяет заголовок `User-Agent` HTTP-клиента;
|
|
92
|
+
переданный вручную `http_client` должен предоставлять изменяемый `headers`.
|
|
87
93
|
|
|
88
94
|
### Владелец и права
|
|
89
95
|
|
|
@@ -116,10 +122,58 @@ countries = await vk.database.get_countries(count=1500)
|
|
|
116
122
|
|
|
117
123
|
```python
|
|
118
124
|
vk = VKClient(cookies={"p": "...", "remixsid": "..."})
|
|
125
|
+
vk = VKClient(cookies="cookies.json")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Принимается словарь cookie, список таких словарей или JSON-файл. Поддерживаются
|
|
129
|
+
простой JSON-объект `{"p": "...", "remixsid": "..."}`, список объектов и формат
|
|
130
|
+
[`vkqr`](https://github.com/matrixd0t/vkqr):
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"cookies": [
|
|
135
|
+
{
|
|
136
|
+
"created_at": 1700000000,
|
|
137
|
+
"user_id": 580106999,
|
|
138
|
+
"p": "...",
|
|
139
|
+
"remixsid": "..."
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Для формата `vkqr` можно передать путь прямо в `VKClient` — файл сам читается,
|
|
146
|
+
метаданные `created_at` и `user_id` не отправляются как cookies. Веб-токен
|
|
147
|
+
получается и обновляется автоматически; при переданном `store` токен и владелец
|
|
148
|
+
сохраняются в нём. `open_store()` без пути создаёт SQLite в `.vkx/vkx.sqlite`.
|
|
149
|
+
Если `open_store("cookies.json")`
|
|
150
|
+
используется с JSON-файлом `vkqr`, VKX сохраняет cookie-записи в том же массиве
|
|
151
|
+
`cookies`, а кеш токенов и состояния обычных токенов — в служебном поле `_vkx`.
|
|
152
|
+
Старый формат JSONStore читается и преобразуется при следующем сохранении. Если
|
|
153
|
+
в файле несколько разных аккаунтов, один `VKClient` всё равно будет привязан к
|
|
154
|
+
первому успешно проверенному владельцу; для отдельных аккаунтов создавайте
|
|
155
|
+
отдельные клиенты.
|
|
156
|
+
|
|
157
|
+
### Авторизация по логину и паролю
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from vkx import VKClient, open_store
|
|
161
|
+
|
|
162
|
+
store = open_store("auth.json")
|
|
163
|
+
vk = VKClient(login="user@example.com", password="...", store=store)
|
|
119
164
|
```
|
|
120
165
|
|
|
121
|
-
|
|
122
|
-
|
|
166
|
+
Источник сначала пробует сохранённые cookies и обновляет по ним web-токен. Если
|
|
167
|
+
VK отклоняет cookie-сессию, он пробует получить новые cookies через VK ID по
|
|
168
|
+
логину/паролю, сохраняет их и повторяет получение web-токена. При следующем
|
|
169
|
+
запуске можно указать только `login` и тот же `store`: сохранённый пароль будет
|
|
170
|
+
восстановлен. Если для входа VK требует капчу или код подтверждения, авторизация
|
|
171
|
+
только по логину/паролю завершится понятной ошибкой — автоматического обхода
|
|
172
|
+
дополнительной проверки нет.
|
|
173
|
+
|
|
174
|
+
Пароль сохраняется в Store **без шифрования**. Ограничьте доступ к файлу/БД и не
|
|
175
|
+
коммитьте их в репозиторий; для JSON используйте права доступа, ограниченные
|
|
176
|
+
текущим пользователем.
|
|
123
177
|
|
|
124
178
|
### `resolve()`
|
|
125
179
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vkx"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Асинхронный клиент VK API: выбор токена по scope, батчинг через execute, типы API/событий и разбор Callback/Long Poll."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -25,6 +25,7 @@ from .client import (
|
|
|
25
25
|
HttpResponse,
|
|
26
26
|
JSONStore,
|
|
27
27
|
MemoryStore,
|
|
28
|
+
PasswordCookieSource,
|
|
28
29
|
PendingApiCall,
|
|
29
30
|
ResolveCache,
|
|
30
31
|
ResolvedMention,
|
|
@@ -102,6 +103,7 @@ __all__ = [
|
|
|
102
103
|
"JSONStore",
|
|
103
104
|
"LongPoll",
|
|
104
105
|
"MemoryStore",
|
|
106
|
+
"PasswordCookieSource",
|
|
105
107
|
"PendingApiCall",
|
|
106
108
|
"ResolveCache",
|
|
107
109
|
"ResolveResult",
|
|
@@ -59,6 +59,7 @@ from .storage import (
|
|
|
59
59
|
from .tokens import (
|
|
60
60
|
CATEGORY_SCOPES,
|
|
61
61
|
SCOPE_TITLES,
|
|
62
|
+
PasswordCookieSource,
|
|
62
63
|
StaticTokenSource,
|
|
63
64
|
TokenSource,
|
|
64
65
|
TokenSourceError,
|
|
@@ -94,6 +95,7 @@ __all__ = [
|
|
|
94
95
|
"HttpResponse",
|
|
95
96
|
"JSONStore",
|
|
96
97
|
"MemoryStore",
|
|
98
|
+
"PasswordCookieSource",
|
|
97
99
|
"PendingApiCall",
|
|
98
100
|
"ResolveCache",
|
|
99
101
|
"ResolveResult",
|
|
@@ -10,8 +10,9 @@ groups.getById (токен группы). Владельцем становит
|
|
|
10
10
|
vk.permissions # ['Сообщения', 'Друзья', ...]
|
|
11
11
|
await vk.call("messages.send", peer_id=1, message="hi")
|
|
12
12
|
|
|
13
|
-
Источники токенов задаются
|
|
14
|
-
|
|
13
|
+
Источники токенов задаются необязательными tokens (строки или TokenSource),
|
|
14
|
+
cookies (словари, JSON-записи или путь к JSON-файлу), login/password или sources
|
|
15
|
+
(готовые TokenSource).
|
|
15
16
|
Из tokens/cookies источники создаются автоматически; можно передавать один
|
|
16
17
|
элемент или iterable. Все полученные источники опрашиваются при probe: нерабочие
|
|
17
18
|
отбрасываются с логированием причины, а источники с чужим владельцем — по id
|
|
@@ -19,10 +20,10 @@ TokenSource), cookies (словари с p и remixsid) и sources (готовы
|
|
|
19
20
|
|
|
20
21
|
HTTP: клиент работает с протоколом HttpClient (асинхронные get/post); httpx —
|
|
21
22
|
опциональная зависимость, создаётся лениво, если свой клиент не передан.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
приоритетнее.
|
|
23
|
+
user_agent при передаче заменяет User-Agent в заголовках HTTP-клиента.
|
|
24
|
+
http_client / base_api_url / v задаются у клиента и могут быть переопределены для
|
|
25
|
+
отдельного запроса (call); method_versions задаёт версии по умолчанию отдельных
|
|
26
|
+
методов (messages.send и messages.edit используют v=5.281). Явный v вызова приоритетнее.
|
|
26
27
|
|
|
27
28
|
Очередь и батчинг: call() фиксирует маршрут запроса (_Queued: кандидаты, HTTP-
|
|
28
29
|
настройки и текущий источник) и кладёт его в очередь; PendingApiCall ждёт
|
|
@@ -50,6 +51,7 @@ import time
|
|
|
50
51
|
from collections import deque
|
|
51
52
|
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Mapping
|
|
52
53
|
from dataclasses import dataclass
|
|
54
|
+
from pathlib import Path
|
|
53
55
|
from typing import Any, Self, overload
|
|
54
56
|
|
|
55
57
|
from ..models.categories import APICategories
|
|
@@ -60,7 +62,7 @@ from ..models.formatting import (
|
|
|
60
62
|
)
|
|
61
63
|
from . import pagination
|
|
62
64
|
from .errors import VKError, VKTimeoutError, VKTransportError, build_error
|
|
63
|
-
from .http import API_HOST, API_VERSION, HttpClient, create_http_client
|
|
65
|
+
from .http import API_HOST, API_VERSION, HttpClient, create_http_client, override_user_agent
|
|
64
66
|
from .resolve import (
|
|
65
67
|
DEFAULT_RESOLVE_CACHE_SIZE,
|
|
66
68
|
DEFAULT_RESOLVE_CACHE_TTL,
|
|
@@ -74,6 +76,7 @@ from .resolve import (
|
|
|
74
76
|
from .storage import Store
|
|
75
77
|
from .tokens import (
|
|
76
78
|
ALL_SCOPES,
|
|
79
|
+
PasswordCookieSource,
|
|
77
80
|
StaticTokenSource,
|
|
78
81
|
TokenSource,
|
|
79
82
|
TokenSourceError,
|
|
@@ -103,7 +106,7 @@ _FORMAT_METHODS: frozenset[str] = frozenset({"messages.send", "messages.edit"})
|
|
|
103
106
|
CookieMap = Mapping[str, str]
|
|
104
107
|
TokenInput = str | TokenSource
|
|
105
108
|
TokensInput = TokenInput | Iterable[TokenInput] | None
|
|
106
|
-
CookiesInput = CookieMap | Iterable[CookieMap] | None
|
|
109
|
+
CookiesInput = CookieMap | Iterable[CookieMap] | str | Path | None
|
|
107
110
|
SourcesInput = TokenSource | Iterable[TokenSource] | None
|
|
108
111
|
CaptchaHandler = Callable[[VKError], "Awaitable[Mapping[str, str] | None] | Mapping[str, str] | None"]
|
|
109
112
|
"""Обработчик капчи: получает VKCaptchaError, возвращает лишние параметры запроса."""
|
|
@@ -244,15 +247,43 @@ def _as_list(value: Any) -> list[Any]:
|
|
|
244
247
|
|
|
245
248
|
|
|
246
249
|
def _iter_cookie_dicts(cookies: CookiesInput) -> list[dict[str, str]]:
|
|
247
|
-
"""
|
|
250
|
+
"""Читает и нормализует cookies из словаря, iterable или JSON-файла."""
|
|
248
251
|
if cookies is None:
|
|
249
252
|
return []
|
|
250
|
-
|
|
253
|
+
|
|
254
|
+
payload: Any = cookies
|
|
255
|
+
if isinstance(cookies, (str, Path)):
|
|
256
|
+
path = Path(cookies)
|
|
257
|
+
try:
|
|
258
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
259
|
+
except (OSError, ValueError) as exc:
|
|
260
|
+
raise TokenSourceError(f"не удалось прочитать JSON cookies из {path}") from exc
|
|
261
|
+
|
|
262
|
+
if isinstance(payload, Mapping):
|
|
263
|
+
if "cookies" in payload and not all(
|
|
264
|
+
name in payload for name in ("p", "remixsid")
|
|
265
|
+
):
|
|
266
|
+
items = payload["cookies"]
|
|
267
|
+
if not isinstance(items, list):
|
|
268
|
+
raise TokenSourceError('cookies: поле "cookies" в JSON должно быть списком')
|
|
269
|
+
else:
|
|
270
|
+
items = [payload]
|
|
271
|
+
elif isinstance(payload, Iterable) and not isinstance(payload, (str, bytes)):
|
|
272
|
+
items = list(payload)
|
|
273
|
+
else:
|
|
274
|
+
raise TokenSourceError(
|
|
275
|
+
"cookies: ожидался словарь, список cookie-записей или JSON-файл"
|
|
276
|
+
)
|
|
277
|
+
|
|
251
278
|
result: list[dict[str, str]] = []
|
|
252
279
|
for item in items:
|
|
253
280
|
if not isinstance(item, Mapping):
|
|
254
|
-
raise
|
|
281
|
+
raise TokenSourceError("cookies: каждая запись должна быть JSON-объектом")
|
|
255
282
|
value = {str(key): str(val) for key, val in item.items()}
|
|
283
|
+
if "p" in item and "remixsid" in item:
|
|
284
|
+
# Метаданные записей vkqr — не cookie и не должны уходить HTTP-клиенту.
|
|
285
|
+
for name in ("created_at", "user_id", "_vkx"):
|
|
286
|
+
value.pop(name, None)
|
|
256
287
|
validate_web_cookies(value)
|
|
257
288
|
result.append(value)
|
|
258
289
|
return result
|
|
@@ -487,8 +518,9 @@ class VKClient(APICategories):
|
|
|
487
518
|
"""Клиент VK API: источники токенов одного владельца, очередь и батчинг execute.
|
|
488
519
|
|
|
489
520
|
Источники задаются необязательными tokens (строки/TokenSource), cookies
|
|
490
|
-
(словари с p и remixsid
|
|
491
|
-
|
|
521
|
+
(словари с p и remixsid, JSON-структура или путь к JSON-файлу), логином/паролем
|
|
522
|
+
или готовыми sources (TokenSource). Источники проверяются при probe; клиент
|
|
523
|
+
сохраняет привязку к владельцу первого рабочего источника.
|
|
492
524
|
Конструктор не обращается к сети. probe() (автоматически при первом call())
|
|
493
525
|
опрашивает все источники: нерабочие отбрасываются с логированием причины,
|
|
494
526
|
клиент привязывается к владельцу первого валидного токена (user_id
|
|
@@ -504,11 +536,14 @@ class VKClient(APICategories):
|
|
|
504
536
|
*,
|
|
505
537
|
logger: logging.Logger | None = None,
|
|
506
538
|
http_client: HttpClient | None = None,
|
|
539
|
+
user_agent: str | None = None,
|
|
507
540
|
base_api_url: str = API_HOST,
|
|
508
541
|
v: str = API_VERSION,
|
|
509
542
|
method_versions: Mapping[str, str] | None = None,
|
|
510
543
|
lang: int = 0,
|
|
511
544
|
store: Store | None = None,
|
|
545
|
+
login: str | None = None,
|
|
546
|
+
password: str | None = None,
|
|
512
547
|
interval: float | None = None,
|
|
513
548
|
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
514
549
|
retry_backoff: float = DEFAULT_RETRY_BACKOFF,
|
|
@@ -534,18 +569,38 @@ class VKClient(APICategories):
|
|
|
534
569
|
raise ValueError("resolve_cache_ttl: ttl кеша не может быть отрицательным")
|
|
535
570
|
if resolve_cache_size < 0:
|
|
536
571
|
raise ValueError("resolve_cache_size: размер кеша не может быть отрицательным")
|
|
572
|
+
if login is not None and not login.strip():
|
|
573
|
+
raise ValueError("login: укажите логин VK")
|
|
574
|
+
if password is not None and login is None:
|
|
575
|
+
raise ValueError("password: для пароля требуется login")
|
|
576
|
+
if password == "":
|
|
577
|
+
raise ValueError("password: пароль не может быть пустым")
|
|
537
578
|
cookie_dicts = _iter_cookie_dicts(cookies)
|
|
538
579
|
token_sources = _token_sources(tokens, store=store)
|
|
539
580
|
explicit_sources = _source_objects(sources)
|
|
540
581
|
self._own_http = http_client is None
|
|
541
582
|
self._http: HttpClient = http_client or create_http_client()
|
|
583
|
+
if user_agent is not None:
|
|
584
|
+
override_user_agent(self._http, user_agent)
|
|
542
585
|
self._base_api_url = base_api_url.rstrip("/")
|
|
586
|
+
self._password_source = (
|
|
587
|
+
PasswordCookieSource(
|
|
588
|
+
login,
|
|
589
|
+
password,
|
|
590
|
+
http=http_client,
|
|
591
|
+
store=store,
|
|
592
|
+
user_agent=user_agent,
|
|
593
|
+
)
|
|
594
|
+
if login is not None
|
|
595
|
+
else None
|
|
596
|
+
)
|
|
543
597
|
built: list[TokenSource] = [
|
|
544
598
|
*token_sources,
|
|
545
599
|
*(
|
|
546
600
|
WebCookieSource(item, http=self._http, store=store, source=f"web{index}")
|
|
547
601
|
for index, item in enumerate(cookie_dicts)
|
|
548
602
|
),
|
|
603
|
+
*([self._password_source] if self._password_source is not None else []),
|
|
549
604
|
*explicit_sources,
|
|
550
605
|
]
|
|
551
606
|
self._entries = [TokenSourceEntry(s) for s in built]
|
|
@@ -1612,6 +1667,8 @@ class VKClient(APICategories):
|
|
|
1612
1667
|
error = VKError("клиент закрыт", client=self)
|
|
1613
1668
|
while self._queue:
|
|
1614
1669
|
self._queue.popleft().request.set_error(error)
|
|
1670
|
+
if self._password_source is not None:
|
|
1671
|
+
await self._password_source.aclose()
|
|
1615
1672
|
if self._own_http:
|
|
1616
1673
|
await self._http.aclose()
|
|
1617
1674
|
|
|
@@ -8,6 +8,7 @@ status_code / text / json(). httpx — опциональная зависимо
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
from collections.abc import MutableMapping
|
|
11
12
|
from typing import Any, Protocol, cast, runtime_checkable
|
|
12
13
|
|
|
13
14
|
DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:159.0) Gecko/20100101 Firefox/159.0"
|
|
@@ -43,6 +44,20 @@ class HttpClient(Protocol):
|
|
|
43
44
|
async def aclose(self) -> None: ...
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
def override_user_agent(http_client: HttpClient, user_agent: str) -> None:
|
|
48
|
+
"""Переопределить User-Agent в заголовках HTTP-клиента."""
|
|
49
|
+
headers = getattr(http_client, "headers", None)
|
|
50
|
+
if not isinstance(headers, MutableMapping):
|
|
51
|
+
raise TypeError(
|
|
52
|
+
"user_agent: HTTP-клиент должен предоставлять изменяемые заголовки headers"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
for name in tuple(headers):
|
|
56
|
+
if isinstance(name, str) and name.casefold() == "user-agent":
|
|
57
|
+
del headers[name]
|
|
58
|
+
headers["User-Agent"] = user_agent
|
|
59
|
+
|
|
60
|
+
|
|
46
61
|
def create_http_client() -> HttpClient:
|
|
47
62
|
"""HTTP-клиент по умолчанию; httpx импортируется лениво (опциональная зависимость)."""
|
|
48
63
|
try:
|