yuchatlib 0.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 (56) hide show
  1. yuchatlib-0.2.2/PKG-INFO +190 -0
  2. yuchatlib-0.2.2/README.md +166 -0
  3. yuchatlib-0.2.2/pyproject.toml +34 -0
  4. yuchatlib-0.2.2/setup.cfg +4 -0
  5. yuchatlib-0.2.2/src/yuchatlib/__init__.py +7 -0
  6. yuchatlib-0.2.2/src/yuchatlib/api/__init__.py +23 -0
  7. yuchatlib-0.2.2/src/yuchatlib/api/_transport.py +59 -0
  8. yuchatlib-0.2.2/src/yuchatlib/api/dto/__init__.py +1 -0
  9. yuchatlib-0.2.2/src/yuchatlib/api/dto/me/__init__.py +1 -0
  10. yuchatlib-0.2.2/src/yuchatlib/api/dto/me/me_info.py +19 -0
  11. yuchatlib-0.2.2/src/yuchatlib/api/dto/me/profile.py +13 -0
  12. yuchatlib-0.2.2/src/yuchatlib/api/dto/message/__init__.py +1 -0
  13. yuchatlib-0.2.2/src/yuchatlib/api/dto/message/button.py +13 -0
  14. yuchatlib-0.2.2/src/yuchatlib/api/dto/message/button_bar.py +13 -0
  15. yuchatlib-0.2.2/src/yuchatlib/api/dto/message/button_group.py +11 -0
  16. yuchatlib-0.2.2/src/yuchatlib/api/dto/message/command_button.py +12 -0
  17. yuchatlib-0.2.2/src/yuchatlib/api/dto/message/get_messages_response.py +11 -0
  18. yuchatlib-0.2.2/src/yuchatlib/api/dto/message/send_message_response.py +8 -0
  19. yuchatlib-0.2.2/src/yuchatlib/api/dto/update/__init__.py +1 -0
  20. yuchatlib-0.2.2/src/yuchatlib/api/dto/update/message.py +20 -0
  21. yuchatlib-0.2.2/src/yuchatlib/api/dto/update/message_action.py +15 -0
  22. yuchatlib-0.2.2/src/yuchatlib/api/dto/update/message_content.py +11 -0
  23. yuchatlib-0.2.2/src/yuchatlib/api/dto/update/pressed_button_command.py +9 -0
  24. yuchatlib-0.2.2/src/yuchatlib/api/dto/update/update.py +21 -0
  25. yuchatlib-0.2.2/src/yuchatlib/api/dto/update/updates_response.py +9 -0
  26. yuchatlib-0.2.2/src/yuchatlib/api/dto/webhook_info.py +19 -0
  27. yuchatlib-0.2.2/src/yuchatlib/bot.py +220 -0
  28. yuchatlib-0.2.2/src/yuchatlib/config.py +120 -0
  29. yuchatlib-0.2.2/src/yuchatlib/cursor.py +89 -0
  30. yuchatlib-0.2.2/src/yuchatlib/dispatcher.py +284 -0
  31. yuchatlib-0.2.2/src/yuchatlib/exceptions.py +33 -0
  32. yuchatlib-0.2.2/src/yuchatlib/fsm/__init__.py +6 -0
  33. yuchatlib-0.2.2/src/yuchatlib/fsm/context.py +100 -0
  34. yuchatlib-0.2.2/src/yuchatlib/fsm/sqlite.py +110 -0
  35. yuchatlib-0.2.2/src/yuchatlib/router.py +70 -0
  36. yuchatlib-0.2.2/src/yuchatlib/webhook.py +62 -0
  37. yuchatlib-0.2.2/src/yuchatlib.egg-info/PKG-INFO +190 -0
  38. yuchatlib-0.2.2/src/yuchatlib.egg-info/SOURCES.txt +54 -0
  39. yuchatlib-0.2.2/src/yuchatlib.egg-info/dependency_links.txt +1 -0
  40. yuchatlib-0.2.2/src/yuchatlib.egg-info/requires.txt +23 -0
  41. yuchatlib-0.2.2/src/yuchatlib.egg-info/top_level.txt +1 -0
  42. yuchatlib-0.2.2/tests/test_bot_transport_contract.py +41 -0
  43. yuchatlib-0.2.2/tests/test_button_bar.py +44 -0
  44. yuchatlib-0.2.2/tests/test_config.py +128 -0
  45. yuchatlib-0.2.2/tests/test_dispatcher.py +100 -0
  46. yuchatlib-0.2.2/tests/test_dispatcher_contract.py +123 -0
  47. yuchatlib-0.2.2/tests/test_full_stand_check.py +505 -0
  48. yuchatlib-0.2.2/tests/test_message_methods.py +167 -0
  49. yuchatlib-0.2.2/tests/test_mvp.py +209 -0
  50. yuchatlib-0.2.2/tests/test_polling_baseline.py +22 -0
  51. yuchatlib-0.2.2/tests/test_polling_cursor.py +296 -0
  52. yuchatlib-0.2.2/tests/test_reliability.py +249 -0
  53. yuchatlib-0.2.2/tests/test_security_boundaries.py +117 -0
  54. yuchatlib-0.2.2/tests/test_sqlite_fsm.py +194 -0
  55. yuchatlib-0.2.2/tests/test_stand_probe.py +133 -0
  56. yuchatlib-0.2.2/tests/test_webhook.py +152 -0
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: yuchatlib
3
+ Version: 0.2.2
4
+ Summary: Асинхронная библиотека для ботов YuChat
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx<1,>=0.27
8
+ Requires-Dist: pydantic<3,>=2
9
+ Provides-Extra: test
10
+ Requires-Dist: pytest<10,>=8; extra == "test"
11
+ Requires-Dist: pytest-asyncio<2,>=0.23; extra == "test"
12
+ Requires-Dist: python-dotenv<2,>=1; extra == "test"
13
+ Requires-Dist: aiosqlite<1,>=0.20; extra == "test"
14
+ Provides-Extra: stand
15
+ Requires-Dist: python-dotenv<2,>=1; extra == "stand"
16
+ Provides-Extra: env
17
+ Requires-Dist: python-dotenv<2,>=1; extra == "env"
18
+ Provides-Extra: dev
19
+ Requires-Dist: ruff<1,>=0.11; extra == "dev"
20
+ Provides-Extra: sqlite
21
+ Requires-Dist: aiosqlite<1,>=0.20; extra == "sqlite"
22
+ Provides-Extra: webhook
23
+ Requires-Dist: aiohttp<4,>=3.10; extra == "webhook"
24
+
25
+ # yuchatlib
26
+
27
+ Асинхронная Python-библиотека для создания ботов YuChat. Она предоставляет обработчики команд и сообщений, роутеры, фильтры, middleware и управление состояниями диалога.
28
+
29
+ Библиотека поддерживает **Python 3.11 и новее**, работу с YuChat Cloud и On-Premises.
30
+
31
+ ## Возможности
32
+
33
+ - Получение событий в режимах **polling** и **webhook**.
34
+ - Последовательная обработка событий через `Dispatcher`.
35
+ - Вложенные роутеры и асинхронные обработчики.
36
+ - Команды с префиксом `/`, фильтры и middleware.
37
+ - Командные кнопки и обработка нажатий.
38
+ - FSM для хранения состояния и данных диалога.
39
+ - Хранение FSM и курсора polling в памяти или SQLite.
40
+ - Отправка, чтение и редактирование сообщений.
41
+
42
+ ## Настройки
43
+
44
+ Параметры можно указать в файле `.env` или в переменных окружения. Значения переменных окружения имеют приоритет над значениями из файла.
45
+
46
+ | Переменная | Назначение | Значение по умолчанию |
47
+ |---|---|---|
48
+ | `YUCHAT_TOKEN` | Токен бота. | Не задан |
49
+ | `YUCHAT_BASE_URL` | Базовый адрес API YuChat Cloud или вашего On-Premises-стенда. | Не задан |
50
+ | `YUCHAT_WEBHOOK_SECRET` | Секрет для проверки входящих webhook-запросов. | Не задан |
51
+ | `YUCHAT_WORKSPACE_ID` | Идентификатор рабочего пространства для сценариев, которым он необходим. | Не задан |
52
+ | `YUCHAT_CHAT_ID` | Идентификатор чата для сценариев, которым он необходим. | Не задан |
53
+ | `YUCHAT_FSM_NAMESPACE` | Постоянная идентичность бота для SQLite FSM и курсора. | Не задан |
54
+ | `YUCHAT_FSM_SQLITE_PATH` | Путь к файлу SQLite для FSM и курсора. | Не задан |
55
+ | `YUCHAT_HTTP_TIMEOUT` | Таймаут HTTP-запросов в секундах. | `30.0` |
56
+ | `YUCHAT_POLLING_INTERVAL` | Задержка между запросами polling в секундах. | `1.0` |
57
+ | `YUCHAT_POLLING_LIMIT` | Максимальное число событий в одном запросе. | `20` |
58
+ | `YUCHAT_POLLING_MAX_RETRIES` | Максимальное число повторных попыток при временных ошибках. | `5` |
59
+
60
+ ## Быстрый старт: polling и команда
61
+
62
+ ```python
63
+ import asyncio
64
+
65
+ from yuchatlib import Bot, Dispatcher, Router
66
+ from yuchatlib.api import MessageDto
67
+ from yuchatlib.config import Settings
68
+
69
+ router = Router()
70
+
71
+
72
+ @router.command("start")
73
+ async def start(message: MessageDto, bot: Bot) -> None:
74
+ await bot.send_message(
75
+ workspace_id=message.workspace_id,
76
+ chat_id=message.chat_id,
77
+ text="Привет! Я бот YuChat.",
78
+ )
79
+
80
+
81
+ async def main() -> None:
82
+ settings = Settings.from_env()
83
+
84
+ dispatcher = Dispatcher()
85
+ dispatcher.include_router(router)
86
+
87
+ async with Bot(
88
+ settings.require_token(),
89
+ base_url=settings.require_base_url(),
90
+ timeout=settings.http_timeout,
91
+ ) as bot:
92
+ await dispatcher.start_polling(
93
+ bot,
94
+ interval=settings.polling_interval,
95
+ limit=settings.polling_limit,
96
+ max_retries=settings.polling_max_retries,
97
+ )
98
+
99
+
100
+ if __name__ == "__main__":
101
+ asyncio.run(main())
102
+ ```
103
+
104
+ После запуска отправьте боту команду `/start`. В декораторе имя команды указывается **без** символа `/`.
105
+
106
+ ## Состояния диалога
107
+
108
+ FSM позволяет сохранять состояние и данные отдельно для каждого участника чата. Обработчики ниже можно добавить в пример с polling.
109
+
110
+ ```python
111
+ from yuchatlib.api import MessageDto
112
+ from yuchatlib.fsm import FSMContext, State, StatesGroup
113
+
114
+
115
+ class Form(StatesGroup):
116
+ waiting_for_name = State()
117
+
118
+
119
+ @router.command("name")
120
+ async def ask_name(message: MessageDto, state: FSMContext, bot: Bot) -> None:
121
+ await state.set_state(Form.waiting_for_name)
122
+ await bot.send_message(
123
+ workspace_id=message.workspace_id,
124
+ chat_id=message.chat_id,
125
+ text="Как вас зовут?",
126
+ )
127
+
128
+
129
+ @router.message(Form.waiting_for_name)
130
+ async def save_name(message: MessageDto, state: FSMContext, bot: Bot) -> None:
131
+ await state.update_data(name=message.content.text)
132
+ await bot.send_message(
133
+ workspace_id=message.workspace_id,
134
+ chat_id=message.chat_id,
135
+ text="Имя сохранено.",
136
+ )
137
+ await state.clear()
138
+ ```
139
+
140
+ По умолчанию FSM хранится в памяти процесса и очищается при его завершении. `clear()` удаляет состояние и данные текущего контекста.
141
+
142
+ ## Командные кнопки
143
+
144
+ Бот может отправить сообщение с кнопкой и обработать её нажатие.
145
+
146
+ ```python
147
+ from yuchatlib.api import (
148
+ ButtonBarDto,
149
+ ButtonDto,
150
+ ButtonGroupDto,
151
+ CommandButtonDto,
152
+ MessageActionDto,
153
+ MessageDto,
154
+ )
155
+
156
+
157
+ @router.command("buttons")
158
+ async def show_buttons(message: MessageDto, bot: Bot) -> None:
159
+ button_bar = ButtonBarDto(
160
+ button_groups=[
161
+ ButtonGroupDto(
162
+ buttons=[
163
+ ButtonDto(
164
+ command_button=CommandButtonDto(
165
+ display_text="Подтвердить",
166
+ command_key="/confirm",
167
+ )
168
+ )
169
+ ]
170
+ )
171
+ ]
172
+ )
173
+ await bot.send_message(
174
+ workspace_id=message.workspace_id,
175
+ chat_id=message.chat_id,
176
+ text="Выберите действие:",
177
+ button_bar=button_bar,
178
+ )
179
+
180
+
181
+ @router.callback_query(command_key="/confirm")
182
+ async def confirm(action: MessageActionDto, bot: Bot) -> None:
183
+ await bot.send_message(
184
+ workspace_id=action.workspace_id,
185
+ chat_id=action.chat_id,
186
+ text="Действие подтверждено.",
187
+ )
188
+ ```
189
+
190
+ Для получения нажатий у бота должен быть включён тип обновлений `MESSAGE_ACTION`. Идентификатор нажавшего пользователя в модели действия кнопки не предусмотрен, поэтому персональный FSM-контекст для такого действия автоматически не создаётся.
@@ -0,0 +1,166 @@
1
+ # yuchatlib
2
+
3
+ Асинхронная Python-библиотека для создания ботов YuChat. Она предоставляет обработчики команд и сообщений, роутеры, фильтры, middleware и управление состояниями диалога.
4
+
5
+ Библиотека поддерживает **Python 3.11 и новее**, работу с YuChat Cloud и On-Premises.
6
+
7
+ ## Возможности
8
+
9
+ - Получение событий в режимах **polling** и **webhook**.
10
+ - Последовательная обработка событий через `Dispatcher`.
11
+ - Вложенные роутеры и асинхронные обработчики.
12
+ - Команды с префиксом `/`, фильтры и middleware.
13
+ - Командные кнопки и обработка нажатий.
14
+ - FSM для хранения состояния и данных диалога.
15
+ - Хранение FSM и курсора polling в памяти или SQLite.
16
+ - Отправка, чтение и редактирование сообщений.
17
+
18
+ ## Настройки
19
+
20
+ Параметры можно указать в файле `.env` или в переменных окружения. Значения переменных окружения имеют приоритет над значениями из файла.
21
+
22
+ | Переменная | Назначение | Значение по умолчанию |
23
+ |---|---|---|
24
+ | `YUCHAT_TOKEN` | Токен бота. | Не задан |
25
+ | `YUCHAT_BASE_URL` | Базовый адрес API YuChat Cloud или вашего On-Premises-стенда. | Не задан |
26
+ | `YUCHAT_WEBHOOK_SECRET` | Секрет для проверки входящих webhook-запросов. | Не задан |
27
+ | `YUCHAT_WORKSPACE_ID` | Идентификатор рабочего пространства для сценариев, которым он необходим. | Не задан |
28
+ | `YUCHAT_CHAT_ID` | Идентификатор чата для сценариев, которым он необходим. | Не задан |
29
+ | `YUCHAT_FSM_NAMESPACE` | Постоянная идентичность бота для SQLite FSM и курсора. | Не задан |
30
+ | `YUCHAT_FSM_SQLITE_PATH` | Путь к файлу SQLite для FSM и курсора. | Не задан |
31
+ | `YUCHAT_HTTP_TIMEOUT` | Таймаут HTTP-запросов в секундах. | `30.0` |
32
+ | `YUCHAT_POLLING_INTERVAL` | Задержка между запросами polling в секундах. | `1.0` |
33
+ | `YUCHAT_POLLING_LIMIT` | Максимальное число событий в одном запросе. | `20` |
34
+ | `YUCHAT_POLLING_MAX_RETRIES` | Максимальное число повторных попыток при временных ошибках. | `5` |
35
+
36
+ ## Быстрый старт: polling и команда
37
+
38
+ ```python
39
+ import asyncio
40
+
41
+ from yuchatlib import Bot, Dispatcher, Router
42
+ from yuchatlib.api import MessageDto
43
+ from yuchatlib.config import Settings
44
+
45
+ router = Router()
46
+
47
+
48
+ @router.command("start")
49
+ async def start(message: MessageDto, bot: Bot) -> None:
50
+ await bot.send_message(
51
+ workspace_id=message.workspace_id,
52
+ chat_id=message.chat_id,
53
+ text="Привет! Я бот YuChat.",
54
+ )
55
+
56
+
57
+ async def main() -> None:
58
+ settings = Settings.from_env()
59
+
60
+ dispatcher = Dispatcher()
61
+ dispatcher.include_router(router)
62
+
63
+ async with Bot(
64
+ settings.require_token(),
65
+ base_url=settings.require_base_url(),
66
+ timeout=settings.http_timeout,
67
+ ) as bot:
68
+ await dispatcher.start_polling(
69
+ bot,
70
+ interval=settings.polling_interval,
71
+ limit=settings.polling_limit,
72
+ max_retries=settings.polling_max_retries,
73
+ )
74
+
75
+
76
+ if __name__ == "__main__":
77
+ asyncio.run(main())
78
+ ```
79
+
80
+ После запуска отправьте боту команду `/start`. В декораторе имя команды указывается **без** символа `/`.
81
+
82
+ ## Состояния диалога
83
+
84
+ FSM позволяет сохранять состояние и данные отдельно для каждого участника чата. Обработчики ниже можно добавить в пример с polling.
85
+
86
+ ```python
87
+ from yuchatlib.api import MessageDto
88
+ from yuchatlib.fsm import FSMContext, State, StatesGroup
89
+
90
+
91
+ class Form(StatesGroup):
92
+ waiting_for_name = State()
93
+
94
+
95
+ @router.command("name")
96
+ async def ask_name(message: MessageDto, state: FSMContext, bot: Bot) -> None:
97
+ await state.set_state(Form.waiting_for_name)
98
+ await bot.send_message(
99
+ workspace_id=message.workspace_id,
100
+ chat_id=message.chat_id,
101
+ text="Как вас зовут?",
102
+ )
103
+
104
+
105
+ @router.message(Form.waiting_for_name)
106
+ async def save_name(message: MessageDto, state: FSMContext, bot: Bot) -> None:
107
+ await state.update_data(name=message.content.text)
108
+ await bot.send_message(
109
+ workspace_id=message.workspace_id,
110
+ chat_id=message.chat_id,
111
+ text="Имя сохранено.",
112
+ )
113
+ await state.clear()
114
+ ```
115
+
116
+ По умолчанию FSM хранится в памяти процесса и очищается при его завершении. `clear()` удаляет состояние и данные текущего контекста.
117
+
118
+ ## Командные кнопки
119
+
120
+ Бот может отправить сообщение с кнопкой и обработать её нажатие.
121
+
122
+ ```python
123
+ from yuchatlib.api import (
124
+ ButtonBarDto,
125
+ ButtonDto,
126
+ ButtonGroupDto,
127
+ CommandButtonDto,
128
+ MessageActionDto,
129
+ MessageDto,
130
+ )
131
+
132
+
133
+ @router.command("buttons")
134
+ async def show_buttons(message: MessageDto, bot: Bot) -> None:
135
+ button_bar = ButtonBarDto(
136
+ button_groups=[
137
+ ButtonGroupDto(
138
+ buttons=[
139
+ ButtonDto(
140
+ command_button=CommandButtonDto(
141
+ display_text="Подтвердить",
142
+ command_key="/confirm",
143
+ )
144
+ )
145
+ ]
146
+ )
147
+ ]
148
+ )
149
+ await bot.send_message(
150
+ workspace_id=message.workspace_id,
151
+ chat_id=message.chat_id,
152
+ text="Выберите действие:",
153
+ button_bar=button_bar,
154
+ )
155
+
156
+
157
+ @router.callback_query(command_key="/confirm")
158
+ async def confirm(action: MessageActionDto, bot: Bot) -> None:
159
+ await bot.send_message(
160
+ workspace_id=action.workspace_id,
161
+ chat_id=action.chat_id,
162
+ text="Действие подтверждено.",
163
+ )
164
+ ```
165
+
166
+ Для получения нажатий у бота должен быть включён тип обновлений `MESSAGE_ACTION`. Идентификатор нажавшего пользователя в модели действия кнопки не предусмотрен, поэтому персональный FSM-контекст для такого действия автоматически не создаётся.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "yuchatlib"
7
+ version = "0.2.2"
8
+ description = "Асинхронная библиотека для ботов YuChat"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = ["httpx>=0.27,<1", "pydantic>=2,<3"]
12
+
13
+ [project.optional-dependencies]
14
+ test = ["pytest>=8,<10", "pytest-asyncio>=0.23,<2", "python-dotenv>=1,<2", "aiosqlite>=0.20,<1"]
15
+ stand = ["python-dotenv>=1,<2"]
16
+ env = ["python-dotenv>=1,<2"]
17
+ dev = ["ruff>=0.11,<1"]
18
+ sqlite = ["aiosqlite>=0.20,<1"]
19
+ webhook = ["aiohttp>=3.10,<4"]
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
23
+
24
+ [tool.pytest.ini_options]
25
+ testpaths = ["tests"]
26
+ asyncio_mode = "auto"
27
+ asyncio_default_fixture_loop_scope = "function"
28
+
29
+ [tool.ruff]
30
+ line-length = 120
31
+ target-version = "py311"
32
+
33
+ [tool.ruff.lint]
34
+ select = ["E", "F", "I"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """Асинхронная библиотека для ботов YuChat."""
2
+
3
+ from .bot import Bot
4
+ from .dispatcher import Dispatcher
5
+ from .router import Router
6
+
7
+ __all__ = ["Bot", "Dispatcher", "Router"]
@@ -0,0 +1,23 @@
1
+ """Интеграция с публичным API YuChat."""
2
+
3
+ from .dto.message.button import ButtonDto
4
+ from .dto.message.button_bar import ButtonBarDto
5
+ from .dto.message.button_group import ButtonGroupDto
6
+ from .dto.message.command_button import CommandButtonDto
7
+ from .dto.message.get_messages_response import GetMessagesResponseDto
8
+ from .dto.update.message import MessageDto
9
+ from .dto.update.message_action import MessageActionDto
10
+ from .dto.update.update import UpdateDto
11
+ from .dto.webhook_info import WebhookInfoDto
12
+
13
+ __all__ = [
14
+ "ButtonDto",
15
+ "ButtonBarDto",
16
+ "ButtonGroupDto",
17
+ "CommandButtonDto",
18
+ "GetMessagesResponseDto",
19
+ "MessageDto",
20
+ "MessageActionDto",
21
+ "UpdateDto",
22
+ "WebhookInfoDto",
23
+ ]
@@ -0,0 +1,59 @@
1
+ """Внутренний HTTP-транспорт публичного API YuChat."""
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from yuchatlib.exceptions import ApiError, AuthenticationError, InvalidResponseError, NetworkError, RateLimitError
8
+
9
+
10
+ class _YuChatHttpTransport:
11
+ """Отправляет запросы и преобразует ответы без раскрытия данных."""
12
+
13
+ def __init__(self, client: httpx.AsyncClient, base_url: str, token: str) -> None:
14
+ self._client = client
15
+ self._base_url = base_url
16
+ self._token = token
17
+
18
+ async def post(self, method: str, body: dict[str, Any] | None = None) -> Any:
19
+ try:
20
+ response = await self._client.post(
21
+ f"{self._base_url}/public/v2/{method}",
22
+ headers={"Authorization": f"Bearer {self._token}"},
23
+ json=body,
24
+ )
25
+ except httpx.RequestError:
26
+ pass
27
+ else:
28
+ return self.parse_response(response)
29
+ raise NetworkError("Ошибка соединения с YuChat")
30
+
31
+ async def request(self, http_method: str, method: str) -> httpx.Response:
32
+ try:
33
+ response = await self._client.request(
34
+ http_method,
35
+ f"{self._base_url}/public/v2/{method}",
36
+ headers={"Authorization": f"Bearer {self._token}"},
37
+ )
38
+ except httpx.RequestError:
39
+ pass
40
+ else:
41
+ return response
42
+ raise NetworkError("Ошибка соединения с YuChat")
43
+
44
+ @staticmethod
45
+ def parse_response(response: httpx.Response) -> Any:
46
+ """Возвращает JSON или преобразует ошибки API в исключения SDK."""
47
+ if response.status_code in (401, 403):
48
+ raise AuthenticationError(response.status_code)
49
+ if response.status_code == 429:
50
+ raise RateLimitError(429, response.headers.get("Retry-After"))
51
+ if not response.is_success:
52
+ raise ApiError(response.status_code)
53
+ if not response.content:
54
+ return None
55
+ try:
56
+ return response.json()
57
+ except ValueError:
58
+ pass
59
+ raise InvalidResponseError("Ответ YuChat не содержит корректный JSON")
@@ -0,0 +1 @@
1
+ """Контракты публичного API YuChat."""
@@ -0,0 +1 @@
1
+ """Модели сведений о боте."""
@@ -0,0 +1,19 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+ from .profile import ProfileDto
6
+
7
+
8
+ class MeInfoDto(BaseModel):
9
+ """Сведения о боте и настройках обновлений."""
10
+
11
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
12
+ profile: ProfileDto = Field(description="Профиль бота.")
13
+ workspaces: list[str] = Field(description="Рабочие пространства бота.")
14
+ update_api_version: int = Field(alias="updateApiVersion", description="Версия API обновлений.")
15
+ update_settings: list[str] = Field(alias="updateSettings", description="Включённые типы обновлений.")
16
+ auto_accept_workspace_invites: bool = Field(
17
+ alias="autoAcceptWorkspaceInvites", description="Автоматическое принятие приглашений."
18
+ )
19
+ scope: dict[str, Any] = Field(description="Область действия бота.")
@@ -0,0 +1,13 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class ProfileDto(BaseModel):
7
+ """Профиль аккаунта бота."""
8
+
9
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
10
+ account_id: str = Field(alias="accountId", description="Идентификатор аккаунта.")
11
+ created_at: datetime = Field(alias="createdAt", description="Время создания аккаунта.")
12
+ updated_at: datetime = Field(alias="updatedAt", description="Время изменения аккаунта.")
13
+ account_type: str = Field(alias="accountType", description="Тип аккаунта.")
@@ -0,0 +1 @@
1
+ """Модели операций с сообщениями."""
@@ -0,0 +1,13 @@
1
+ """Контракт кнопки сообщения."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+ from .command_button import CommandButtonDto
6
+
7
+
8
+ class ButtonDto(BaseModel):
9
+ """Командная кнопка сообщения."""
10
+
11
+ model_config = ConfigDict(populate_by_name=True)
12
+
13
+ command_button: CommandButtonDto = Field(alias="commandButton", description="Командная кнопка.")
@@ -0,0 +1,13 @@
1
+ """Контракт панели кнопок."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+ from .button_group import ButtonGroupDto
6
+
7
+
8
+ class ButtonBarDto(BaseModel):
9
+ """Панель кнопок сообщения."""
10
+
11
+ model_config = ConfigDict(populate_by_name=True)
12
+
13
+ button_groups: list[ButtonGroupDto] = Field(alias="buttonGroups", description="Ряды кнопок.")
@@ -0,0 +1,11 @@
1
+ """Контракт группы кнопок."""
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from .button import ButtonDto
6
+
7
+
8
+ class ButtonGroupDto(BaseModel):
9
+ """Ряд кнопок сообщения."""
10
+
11
+ buttons: list[ButtonDto] = Field(description="Кнопки в ряду.")
@@ -0,0 +1,12 @@
1
+ """Контракт командной кнопки."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class CommandButtonDto(BaseModel):
7
+ """Команда и отображаемый текст кнопки."""
8
+
9
+ model_config = ConfigDict(populate_by_name=True)
10
+
11
+ display_text: str = Field(alias="displayText", description="Текст кнопки.")
12
+ command_key: str = Field(alias="commandKey", description="Команда нажатой кнопки.")
@@ -0,0 +1,11 @@
1
+ from pydantic import BaseModel, ConfigDict, Field
2
+
3
+ from yuchatlib.api.dto.update.message import MessageDto
4
+
5
+
6
+ class GetMessagesResponseDto(BaseModel):
7
+ """Список сообщений, полученных из чата."""
8
+
9
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
10
+
11
+ messages: list[MessageDto] = Field(description="Сообщения чата.")
@@ -0,0 +1,8 @@
1
+ from pydantic import BaseModel, ConfigDict, Field
2
+
3
+
4
+ class SendMessageResponseDto(BaseModel):
5
+ """Результат отправки сообщения."""
6
+
7
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
8
+ message_id: str = Field(alias="messageId", description="Идентификатор отправленного сообщения.")
@@ -0,0 +1 @@
1
+ """Модели получаемых обновлений."""
@@ -0,0 +1,20 @@
1
+ from datetime import datetime
2
+ from typing import Literal
3
+
4
+ from pydantic import BaseModel, ConfigDict, Field
5
+
6
+ from .message_content import MessageContentDto
7
+
8
+
9
+ class MessageDto(BaseModel):
10
+ """Сообщение в чате YuChat."""
11
+
12
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
13
+ workspace_id: str = Field(alias="workspaceId", description="Идентификатор рабочего пространства.")
14
+ chat_id: str = Field(alias="chatId", description="Идентификатор чата.")
15
+ message_id: str = Field(alias="messageId", description="Идентификатор сообщения.")
16
+ message_type: Literal["USER", "SYSTEM"] = Field(alias="messageType", description="Тип сообщения.")
17
+ membership_id: str = Field(alias="membershipId", description="Идентификатор автора в рабочем пространстве.")
18
+ created_at: datetime = Field(alias="createdAt", description="Время создания сообщения.")
19
+ updated_at: datetime = Field(alias="updatedAt", description="Время изменения сообщения.")
20
+ content: MessageContentDto = Field(description="Содержимое сообщения.")
@@ -0,0 +1,15 @@
1
+ from pydantic import BaseModel, ConfigDict, Field
2
+
3
+ from .pressed_button_command import PressedButtonCommandDto
4
+
5
+
6
+ class MessageActionDto(BaseModel):
7
+ """Действие с кнопкой сообщения."""
8
+
9
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
10
+ workspace_id: str = Field(alias="workspaceId", description="Идентификатор рабочего пространства.")
11
+ chat_id: str = Field(alias="chatId", description="Идентификатор чата.")
12
+ message_id: str = Field(alias="messageId", description="Идентификатор сообщения.")
13
+ pressed_button_command: PressedButtonCommandDto | None = Field(
14
+ default=None, alias="pressedButtonCommand", description="Команда нажатой кнопки, если передана."
15
+ )
@@ -0,0 +1,11 @@
1
+ from pydantic import BaseModel, ConfigDict, Field
2
+
3
+
4
+ class MessageContentDto(BaseModel):
5
+ """Содержимое сообщения YuChat."""
6
+
7
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
8
+ text: str | None = Field(default=None, description="Текст сообщения в формате Markdown.")
9
+ file_ids: list[str] | None = Field(
10
+ default=None, alias="fileIds", description="Идентификаторы прикреплённых файлов."
11
+ )