python-trueconf-bot 1.0.0__py3-none-any.whl

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 (120) hide show
  1. python_trueconf_bot-1.0.0.dist-info/METADATA +115 -0
  2. python_trueconf_bot-1.0.0.dist-info/RECORD +120 -0
  3. python_trueconf_bot-1.0.0.dist-info/WHEEL +5 -0
  4. python_trueconf_bot-1.0.0.dist-info/licenses/LICENSE +32 -0
  5. python_trueconf_bot-1.0.0.dist-info/top_level.txt +1 -0
  6. trueconf/__init__.py +18 -0
  7. trueconf/_version.py +34 -0
  8. trueconf/client/__init__.py +0 -0
  9. trueconf/client/bot.py +1269 -0
  10. trueconf/client/context_controller.py +27 -0
  11. trueconf/client/session.py +60 -0
  12. trueconf/dispatcher/__init__.py +0 -0
  13. trueconf/dispatcher/dispatcher.py +56 -0
  14. trueconf/dispatcher/router.py +207 -0
  15. trueconf/enums/__init__.py +23 -0
  16. trueconf/enums/aouth_error.py +15 -0
  17. trueconf/enums/chat_participant_role.py +18 -0
  18. trueconf/enums/chat_type.py +17 -0
  19. trueconf/enums/envelope_author_type.py +13 -0
  20. trueconf/enums/file_ready_state.py +14 -0
  21. trueconf/enums/incoming_update_method.py +14 -0
  22. trueconf/enums/message_type.py +27 -0
  23. trueconf/enums/parse_mode.py +14 -0
  24. trueconf/enums/survey_type.py +6 -0
  25. trueconf/enums/update_type.py +14 -0
  26. trueconf/exceptions.py +17 -0
  27. trueconf/filters/__init__.py +11 -0
  28. trueconf/filters/base.py +8 -0
  29. trueconf/filters/command.py +32 -0
  30. trueconf/filters/instance_of.py +11 -0
  31. trueconf/filters/message.py +15 -0
  32. trueconf/filters/method.py +12 -0
  33. trueconf/loggers.py +4 -0
  34. trueconf/methods/__init__.py +59 -0
  35. trueconf/methods/add_participant_to_chat.py +22 -0
  36. trueconf/methods/auth.py +25 -0
  37. trueconf/methods/base.py +107 -0
  38. trueconf/methods/create_channel.py +20 -0
  39. trueconf/methods/create_group_chat.py +20 -0
  40. trueconf/methods/create_p2p_chat.py +20 -0
  41. trueconf/methods/edit_message.py +25 -0
  42. trueconf/methods/edit_survey.py +32 -0
  43. trueconf/methods/forward_message.py +22 -0
  44. trueconf/methods/get_chat_by_id.py +20 -0
  45. trueconf/methods/get_chat_history.py +24 -0
  46. trueconf/methods/get_chat_participants.py +24 -0
  47. trueconf/methods/get_chats.py +22 -0
  48. trueconf/methods/get_file_info.py +20 -0
  49. trueconf/methods/get_message_by_id.py +19 -0
  50. trueconf/methods/get_user_display_name.py +20 -0
  51. trueconf/methods/has_chat_participant.py +22 -0
  52. trueconf/methods/remove_chat.py +20 -0
  53. trueconf/methods/remove_message.py +23 -0
  54. trueconf/methods/remove_participant_from_chat.py +23 -0
  55. trueconf/methods/send_file.py +25 -0
  56. trueconf/methods/send_message.py +29 -0
  57. trueconf/methods/send_survey.py +40 -0
  58. trueconf/methods/subscribe_file_progress.py +21 -0
  59. trueconf/methods/unsubscribe_file_progress.py +21 -0
  60. trueconf/methods/upload_file.py +21 -0
  61. trueconf/py.typed +0 -0
  62. trueconf/types/__init__.py +25 -0
  63. trueconf/types/author_box.py +17 -0
  64. trueconf/types/chat_participant.py +11 -0
  65. trueconf/types/content/__init__.py +25 -0
  66. trueconf/types/content/attachment.py +14 -0
  67. trueconf/types/content/base.py +8 -0
  68. trueconf/types/content/chat_created.py +9 -0
  69. trueconf/types/content/document.py +71 -0
  70. trueconf/types/content/forward_message.py +21 -0
  71. trueconf/types/content/photo.py +70 -0
  72. trueconf/types/content/remove_participant.py +9 -0
  73. trueconf/types/content/sticker.py +70 -0
  74. trueconf/types/content/survey.py +19 -0
  75. trueconf/types/content/text.py +9 -0
  76. trueconf/types/content/video.py +71 -0
  77. trueconf/types/last_message.py +33 -0
  78. trueconf/types/message.py +411 -0
  79. trueconf/types/parser.py +90 -0
  80. trueconf/types/requests/__init__.py +21 -0
  81. trueconf/types/requests/added_chat_participant.py +40 -0
  82. trueconf/types/requests/created_channel.py +42 -0
  83. trueconf/types/requests/created_group_chat.py +42 -0
  84. trueconf/types/requests/created_personal_chat.py +42 -0
  85. trueconf/types/requests/edited_message.py +37 -0
  86. trueconf/types/requests/removed_chat.py +32 -0
  87. trueconf/types/requests/removed_chat_participant.py +39 -0
  88. trueconf/types/requests/removed_message.py +37 -0
  89. trueconf/types/requests/uploading_progress.py +34 -0
  90. trueconf/types/responses/__init__.py +57 -0
  91. trueconf/types/responses/add_chat_participant_response.py +8 -0
  92. trueconf/types/responses/api_error.py +38 -0
  93. trueconf/types/responses/auth_response_payload.py +9 -0
  94. trueconf/types/responses/create_channel_response.py +8 -0
  95. trueconf/types/responses/create_group_chat_response.py +8 -0
  96. trueconf/types/responses/create_p2p_chat_response.py +8 -0
  97. trueconf/types/responses/edit_message_response.py +9 -0
  98. trueconf/types/responses/edit_survey_response.py +9 -0
  99. trueconf/types/responses/forward_message_response.py +10 -0
  100. trueconf/types/responses/get_chat_by_id_response.py +13 -0
  101. trueconf/types/responses/get_chat_history_response.py +13 -0
  102. trueconf/types/responses/get_chat_participants_response.py +12 -0
  103. trueconf/types/responses/get_chats_response.py +12 -0
  104. trueconf/types/responses/get_file_info_response.py +24 -0
  105. trueconf/types/responses/get_message_by_id_response.py +26 -0
  106. trueconf/types/responses/get_user_display_name_response.py +8 -0
  107. trueconf/types/responses/has_chat_participant_response.py +8 -0
  108. trueconf/types/responses/remove_chat_participant_response.py +8 -0
  109. trueconf/types/responses/remove_chat_response.py +8 -0
  110. trueconf/types/responses/remove_message_response.py +8 -0
  111. trueconf/types/responses/send_file_response.py +10 -0
  112. trueconf/types/responses/send_message_response.py +10 -0
  113. trueconf/types/responses/send_survey_response.py +10 -0
  114. trueconf/types/responses/subscribe_file_progress_response.py +8 -0
  115. trueconf/types/responses/unsubscribe_file_progress_response.py +8 -0
  116. trueconf/types/responses/upload_file_response.py +8 -0
  117. trueconf/types/update.py +12 -0
  118. trueconf/utils/__init__.py +3 -0
  119. trueconf/utils/generate_secret_for_survey.py +10 -0
  120. trueconf/utils/token.py +78 -0
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from .bot import Bot
7
+
8
+ @dataclass
9
+ class BoundToBot:
10
+ __slots__ = ('_bot',)
11
+
12
+ def __init__(self) -> None:
13
+ self._bot = None
14
+
15
+ def bind(self, bot: "Bot"):
16
+ self._bot = bot
17
+ return self
18
+
19
+ if TYPE_CHECKING:
20
+ @property
21
+ def bot(self) -> "Bot": ...
22
+ else:
23
+ @property
24
+ def bot(self) :
25
+ if not self._bot or self._bot is None:
26
+ raise RuntimeError("Object isn’t bound to ChatBot. Bind via bind(bot) before using shortcuts.")
27
+ return self._bot
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+ import asyncio
3
+ import contextlib
4
+ import json
5
+ import logging
6
+ from typing import Awaitable, Callable, Optional
7
+
8
+ logger = logging.getLogger("chat_bot")
9
+
10
+
11
+ class WebSocketSession:
12
+ """
13
+ Владеет объектом ws, поднимает фоновый listener и
14
+ пробрасывает сырые сообщения в callback on_message(raw: str).
15
+ """
16
+ def __init__(self, on_message: Optional[Callable[[str], Awaitable[None]]] = None):
17
+ self.ws = None
18
+ self._listener_task: Optional[asyncio.Task] = None
19
+ self._on_message = on_message
20
+
21
+ def attach(self, ws) -> None:
22
+ """Подключить актуальный ws и запустить фонового listener-а."""
23
+ self.ws = ws
24
+ if self._listener_task and not self._listener_task.done():
25
+ self._listener_task.cancel()
26
+ self._listener_task = asyncio.create_task(self._listener())
27
+
28
+ async def detach(self) -> None:
29
+ """Остановить listener (ws остаётся под управлением ChatBot/connect loop)."""
30
+ if self._listener_task:
31
+ try:
32
+ self._listener_task.cancel()
33
+ with contextlib.suppress(asyncio.CancelledError):
34
+ await self._listener_task
35
+ finally:
36
+ self._listener_task = None
37
+ self.ws = None
38
+
39
+ async def close(self) -> None:
40
+ """Закрыть ws и listener."""
41
+ if self.ws:
42
+ await self.ws.close()
43
+ await self.detach()
44
+
45
+ async def send_json(self, message: dict) -> None:
46
+ if not self.ws:
47
+ raise RuntimeError("WS is not attached")
48
+ await self.ws.send(json.dumps(message))
49
+
50
+ async def _listener(self):
51
+ try:
52
+ async for raw in self.ws:
53
+ if self._on_message:
54
+ try:
55
+ await self._on_message(raw)
56
+ except Exception as e:
57
+ logger.error(f"on_message callback error: {e}")
58
+ except Exception as e:
59
+ # Закрытие/обрыв — это нормально, верхний цикл переподключит
60
+ logger.debug(f"Session listener stopped: {type(e).__name__}: {e}")
File without changes
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+ from typing import List
3
+ from trueconf.filters.base import Event
4
+ from trueconf.dispatcher.router import Router
5
+
6
+
7
+ class Dispatcher:
8
+ """
9
+ Central event dispatcher for processing and routing incoming events.
10
+
11
+ The `Dispatcher` aggregates one or more `Router` instances and feeds each
12
+ incoming event through them. The routers are traversed recursively via their
13
+ `subrouters` (using `_iter_all()`), and each event is passed to `_feed()` of
14
+ each router in order until it is handled.
15
+
16
+ Typical usage includes registering routers with handlers and then calling
17
+ `feed_update()` with incoming events.
18
+
19
+ Examples:
20
+ >>> dispatcher = Dispatcher()
21
+ >>> dispatcher.include_router(my_router)
22
+
23
+ Attributes:
24
+ routers (List[Router]): List of root routers included in the dispatcher.
25
+
26
+ """
27
+
28
+ def __init__(self):
29
+ """Initializes an empty dispatcher with no routers."""
30
+ self.routers: List[Router] = []
31
+
32
+ def include_router(self, router: Router):
33
+ """
34
+ Includes a router to be used by the dispatcher.
35
+
36
+ Args:
37
+ router (Router): A `Router` instance to include.
38
+ """
39
+ self.routers.append(router)
40
+
41
+ async def _feed_update(self, event: Event):
42
+ """
43
+ Feeds an event to all routers and subrouters in order,
44
+ stopping at the first one that handles it.
45
+
46
+ Args:
47
+ event (Event): The event to be processed.
48
+
49
+ Returns:
50
+ None
51
+ """
52
+ for root in self.routers:
53
+ for r in root._iter_all():
54
+ handled = await r._feed(event)
55
+ if handled:
56
+ return
@@ -0,0 +1,207 @@
1
+ from __future__ import annotations
2
+ import asyncio
3
+ import logging
4
+ import inspect
5
+ from typing import Callable, Awaitable, List, Tuple, Any, Union
6
+ from magic_filter import MagicFilter
7
+ from trueconf.filters.base import Event
8
+ from trueconf.filters.base import Filter
9
+ from trueconf.filters.instance_of import InstanceOfFilter
10
+ from trueconf.filters.method import MethodFilter
11
+ from trueconf.types.message import Message
12
+ from trueconf.types.requests.added_chat_participant import AddedChatParticipant
13
+ from trueconf.types.requests.created_channel import CreatedChannel
14
+ from trueconf.types.requests.created_group_chat import CreatedGroupChat
15
+ from trueconf.types.requests.created_personal_chat import CreatedPersonalChat
16
+ from trueconf.types.requests.edited_message import EditedMessage
17
+ from trueconf.types.requests.removed_chat import RemovedChat
18
+ from trueconf.types.requests.removed_chat_participant import RemovedChatParticipant
19
+ from trueconf.types.requests.removed_message import RemovedMessage
20
+ from trueconf.types.requests.uploading_progress import UploadingProgress
21
+
22
+ logger = logging.getLogger("chat_bot")
23
+
24
+ Handler = Callable[[Event], Awaitable[None]]
25
+ FilterLike = Union[Filter, MagicFilter, Callable[[Event], bool], Callable[[Event], Awaitable[bool]], Any]
26
+
27
+
28
+ class Router:
29
+ """
30
+ Event router for handling incoming events in a structured and extensible way.
31
+
32
+ A `Router` allows you to register event handlers with specific filters,
33
+ such as message types, chat events, or custom logic.
34
+
35
+ You can also include nested routers using `include_router()` to build modular and reusable event structures.
36
+
37
+ Handlers can be registered for:
38
+
39
+ - Messages (`@<router>.message(...)`)
40
+ - Chat creation events (`@<router>.created_personal_chat()`, `@<router>.created_group_chat()`, `@<router>.created_channel()`)
41
+ - Participant events (`@<router>.added_chat_participant()`, `@<router>.removed_chat_participant()`)
42
+ - Message lifecycle events (`@<router>.edited_message()`, `@<router>.removed_message()`)
43
+ - File upload events (`@<router>.uploading_progress()`)
44
+ - Removed chats (`@<router>.removed_chat()`)
45
+
46
+ Example:
47
+
48
+ ```python
49
+ router = Router()
50
+
51
+ @router.message(F.text == "hello")
52
+ async def handle_hello(msg: Message):
53
+ await msg.answer("Hi there!")
54
+ ```
55
+
56
+ If you have multiple routers, use `.include_router()` to add them to a parent router.
57
+ """
58
+
59
+ def __init__(self, name: str | None = None, stop_on_first: bool = True):
60
+
61
+ self.name = name or hex(id(self))
62
+ self.stop_on_first = stop_on_first
63
+ self._handlers: List[Tuple[Tuple[FilterLike, ...], Handler]] = []
64
+ self._subrouters: List["Router"] = []
65
+
66
+ def include_router(self, router: "Router") -> None:
67
+ """Include a child router for hierarchical event routing."""
68
+ self._subrouters.append(router)
69
+
70
+ def _iter_all(self) -> List["Router"]:
71
+ """Return a list of this router and all nested subrouters recursively."""
72
+ out = [self]
73
+ for child in self._subrouters:
74
+ out.extend(child._iter_all())
75
+ return out
76
+
77
+ def event(self, method: str, *filters: FilterLike):
78
+ """
79
+ Register a handler for a generic event type, filtered by method name.
80
+
81
+ Examples:
82
+ >>> @r.event(F.method == "SendMessage")
83
+ >>> async def handle_message(msg: Message): ...
84
+
85
+ """
86
+ mf = MethodFilter(method)
87
+ return self._register((mf, *filters))
88
+
89
+ def message(self, *filters: FilterLike):
90
+ """Register a handler for incoming `Message` events."""
91
+ return self._register((InstanceOfFilter(Message), *filters))
92
+
93
+ def uploading_progress(self, *filters: FilterLike):
94
+ """Register a handler for file uploading progress events."""
95
+ return self._register((InstanceOfFilter(UploadingProgress), *filters))
96
+
97
+ def created_personal_chat(self, *filters: FilterLike):
98
+ """Register a handler for personal chat creation events."""
99
+ return self._register((InstanceOfFilter(CreatedPersonalChat), *filters))
100
+
101
+ def created_group_chat(self, *filters: FilterLike):
102
+ """Register a handler for group chat creation events."""
103
+ return self._register((InstanceOfFilter(CreatedGroupChat), *filters))
104
+
105
+ def created_channel(self, *filters: FilterLike):
106
+ """Register a handler for channel creation events."""
107
+ return self._register((InstanceOfFilter(CreatedChannel), *filters))
108
+
109
+ def added_chat_participant(self, *filters: FilterLike):
110
+ """Register a handler when a participant is added to a chat."""
111
+ return self._register((InstanceOfFilter(AddedChatParticipant), *filters))
112
+
113
+ def removed_chat_participant(self, *filters: FilterLike):
114
+ """Register a handler when a participant is removed from a chat."""
115
+ return self._register((InstanceOfFilter(RemovedChatParticipant), *filters))
116
+
117
+ def removed_chat(self, *filters: FilterLike):
118
+ """Register a handler when a chat is removed."""
119
+ return self._register((InstanceOfFilter(RemovedChat), *filters))
120
+
121
+ def edited_message(self, *filters: FilterLike):
122
+ """Register a handler for message edit events."""
123
+ return self._register((InstanceOfFilter(EditedMessage), *filters))
124
+
125
+ def removed_message(self, *filters: FilterLike):
126
+ """Register a handler for message deletion events."""
127
+ return self._register((InstanceOfFilter(RemovedMessage), *filters))
128
+
129
+ def _register(self, filters: Tuple[FilterLike, ...]):
130
+ """Internal decorator for registering handlers with filters."""
131
+
132
+ def decorator(func: Handler):
133
+ if not asyncio.iscoroutinefunction(func):
134
+ async def async_wrapper(evt: Event):
135
+ return func(evt)
136
+
137
+ self._handlers.append((filters, async_wrapper))
138
+ return func
139
+ self._handlers.append((filters, func))
140
+ return func
141
+
142
+ return decorator
143
+
144
+ async def _feed(self, event: Event) -> bool:
145
+ """Feed an incoming event to the router and invoke the first matching handler."""
146
+ logger.info(f"📥 Incoming event: {event}")
147
+ for flts, handler in self._handlers:
148
+
149
+ if not flts:
150
+ self._spawn(handler, event, "<none>")
151
+ return True
152
+
153
+ matched = True
154
+ for f in flts:
155
+ try:
156
+ if not await self._apply_filter(f, event):
157
+ matched = False
158
+ break
159
+ except Exception as e:
160
+ logger.exception(f"Filter {type(f).__name__} error: {e}")
161
+ matched = False
162
+ break
163
+
164
+ if matched:
165
+ filters_str = ", ".join(
166
+ getattr(f, "__name__", type(f).__name__) if callable(f) else type(f).__name__
167
+ for f in flts
168
+ )
169
+ self._spawn(handler, event, filters_str)
170
+ return True
171
+ return False
172
+
173
+ def _spawn(self, handler: Handler, event: Event, filters_str: str):
174
+ """Internal method to spawn a task for executing the matched handler."""
175
+ name = getattr(handler, "__name__", "<handler>")
176
+ logger.info(f"[router:{self.name}] matched handler={name} filters=[{filters_str}]")
177
+
178
+ async def _run():
179
+ try:
180
+ await handler(event)
181
+ except Exception as e:
182
+ logger.exception(f"Handler {name} failed: {e}")
183
+
184
+ asyncio.create_task(_run())
185
+
186
+ async def _apply_filter(self, f: Filter | Any, event: Event) -> bool:
187
+ """Evaluate a filter (sync or async) against the event."""
188
+ if isinstance(f, MagicFilter):
189
+ try:
190
+ return bool(f.resolve(event))
191
+ except Exception:
192
+ return False
193
+
194
+ try:
195
+ res = f(event)
196
+ except Exception:
197
+ return False
198
+
199
+ if inspect.isawaitable(res):
200
+ try:
201
+ res = await res
202
+ except Exception:
203
+ return False
204
+
205
+ if isinstance(res, bool):
206
+ return res
207
+ return bool(res)
@@ -0,0 +1,23 @@
1
+ from .aouth_error import OAuthError
2
+ from .chat_participant_role import ChatParticipantRole
3
+ from .chat_type import ChatType
4
+ from .envelope_author_type import EnvelopeAuthorType
5
+ from .message_type import MessageType
6
+ from .file_ready_state import FileReadyState
7
+ from .incoming_update_method import IncomingUpdateMethod
8
+ from .update_type import UpdateType
9
+ from .parse_mode import ParseMode
10
+ from .survey_type import SurveyType
11
+
12
+ __all__ = [
13
+ 'UpdateType',
14
+ 'FileReadyState',
15
+ 'ParseMode',
16
+ 'ChatType',
17
+ 'ChatParticipantRole',
18
+ 'OAuthError',
19
+ 'MessageType',
20
+ 'EnvelopeAuthorType',
21
+ 'SurveyType',
22
+ 'IncomingUpdateMethod',
23
+ ]
@@ -0,0 +1,15 @@
1
+ from enum import Enum
2
+
3
+
4
+ class OAuthError(str, Enum):
5
+ """
6
+ Error codes, according to the OAuth 2.0 specification, are presented as ASCII strings from the list specified in the specification.
7
+
8
+ Source:
9
+ https://trueconf.com/docs/chatbot-connector/en/objects/#oauth-error
10
+ """
11
+
12
+ INVALID_REQUEST = "invalid_request"
13
+ INVALID_CLIENT = "invalid_client"
14
+ INVALID_GRANT = "invalid_grant"
15
+ UNSUPORTED_GRANT_TYPE = "unsupported_grant_type"
@@ -0,0 +1,18 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ChatParticipantRole(str, Enum):
5
+ """
6
+ This object represents a possible participant role in a chat.
7
+
8
+ Source:
9
+ https://trueconf.com/docs/chatbot-connector/en/objects/#chatparticipantroleenum
10
+ """
11
+
12
+ OWNER = "owner"
13
+ ADMIN = "admin"
14
+ USER = "user"
15
+ CONF_OWNER = "conf_owner"
16
+ CONF_MODERATOR = "conf_moderator"
17
+ FAVORITES_OWNER = "favorites_owner"
18
+ WRITER = "writer"
@@ -0,0 +1,17 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ChatType(int, Enum):
5
+ """
6
+ The enumeration contains possible chat types.
7
+
8
+ Source:
9
+ https://trueconf.com/docs/chatbot-connector/en/objects/#chattypeenum
10
+ """
11
+
12
+ UNDEF = 0
13
+ P2P = 1
14
+ GROUP = 2
15
+ SYSTEM = 3
16
+ FAVORITES = 5
17
+ CHANNEL = 6
@@ -0,0 +1,13 @@
1
+ from enum import Enum
2
+
3
+
4
+ class EnvelopeAuthorType(int, Enum):
5
+ """
6
+ The enumeration contains possible author types.
7
+
8
+ Source:
9
+ https://trueconf.com/docs/chatbot-connector/en/objects/#envelopeauthortypeenum
10
+ """
11
+
12
+ SYSTEM = 0
13
+ USER = 1
@@ -0,0 +1,14 @@
1
+ from enum import Enum
2
+
3
+
4
+ class FileReadyState(int, Enum):
5
+ """
6
+ This enumeration is used to indicate the status of a file on the server.
7
+
8
+ Source:
9
+ https://trueconf.com/docs/chatbot-connector/en/objects/#filereadystateenum
10
+ """
11
+
12
+ NOT_AVAILABLE = 0
13
+ UPLOADING = 1
14
+ READY = 2
@@ -0,0 +1,14 @@
1
+ from enum import Enum
2
+
3
+
4
+ class IncomingUpdateMethod(str, Enum):
5
+ ADDED_CHAT_PARTICIPANT = "addChatParticipant"
6
+ CREATED_CHANNEL = "createChannel"
7
+ CREATED_GROUP_CHAT = "createGroupChat"
8
+ CREATED_PERSONAL_CHAT = "createP2PChat"
9
+ EDITED_MESSAGE = "editMessage"
10
+ REMOVED_CHAT_PARTICIPANT = "removeChatParticipant"
11
+ REMOVED_MESSAGE = "removeMessage"
12
+ MESSAGE = "sendMessage"
13
+ UPLOADING_PROGRESS = "uploadFileProgress"
14
+ REMOVED_CHAT = "removeChat"
@@ -0,0 +1,27 @@
1
+ from enum import Enum
2
+ from typing import Any
3
+
4
+
5
+ class MessageType(int, Enum):
6
+ """
7
+ The enumeration contains the message type.
8
+
9
+ Source:
10
+ https://trueconf.com/docs/chatbot-connector/en/objects/#envelopetypeenum
11
+ """
12
+
13
+ ADD_PARTICIPANT = 1
14
+ REMOVE_PARTICIPANT = 2
15
+ PARTICIPANT_ROLE = 110
16
+ PLAIN_MESSAGE = 200
17
+ FORWARDED_MESSAGE = 201
18
+ ATTACHMENT = 202
19
+ SURVEY = 204
20
+
21
+ async def __call__(self, event: Any) -> bool:
22
+ if getattr(event, "type", None) is None:
23
+ return False
24
+ try:
25
+ return int(getattr(event, "type")) == int(self)
26
+ except Exception:
27
+ return False
@@ -0,0 +1,14 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ParseMode(str, Enum):
5
+ """
6
+ Formatting options
7
+
8
+ Source:
9
+ https://trueconf.com/docs/chatbot-connector/en/messages/#message-formatting
10
+ """
11
+
12
+ TEXT = "text"
13
+ MARKDOWN = "markdown"
14
+ HTML = "html"
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class SurveyType(str, Enum):
5
+ NON_ANONYMOUS = "{{Survey}}"
6
+ ANONYMOUS = "{{Anonymous survey}}"
@@ -0,0 +1,14 @@
1
+ from enum import Enum
2
+
3
+
4
+ class UpdateType(int, Enum):
5
+ """
6
+ There are three types of messages. Only REQUEST and RESPONSE are applicable.
7
+
8
+ Source:
9
+ https://trueconf.com/docs/chatbot-connector/en/objects/#message-type
10
+ """
11
+
12
+ RESERVED = 0
13
+ REQUEST = 1
14
+ RESPONSE = 2
trueconf/exceptions.py ADDED
@@ -0,0 +1,17 @@
1
+
2
+
3
+
4
+ class TrueConfChatBotError(Exception):
5
+ """
6
+ Base exception for all TrueConf ChatBot Connector errors.
7
+ """
8
+
9
+ class TokenValidationError(TrueConfChatBotError):
10
+ pass
11
+
12
+ class InvalidGrantError(TrueConfChatBotError):
13
+ pass
14
+
15
+
16
+ class ApiError(TrueConfChatBotError):
17
+ pass
@@ -0,0 +1,11 @@
1
+ from .base import Filter, Event
2
+ from .command import Command
3
+ from .instance_of import InstanceOfFilter
4
+ from .method import MethodFilter
5
+
6
+ __all__ = [
7
+ 'Command',
8
+ 'Filter',
9
+ 'MethodFilter',
10
+ 'InstanceOfFilter',
11
+ ]
@@ -0,0 +1,8 @@
1
+ from typing import Any
2
+ from typing import Protocol, runtime_checkable
3
+
4
+ Event = Any
5
+
6
+ @runtime_checkable
7
+ class Filter(Protocol):
8
+ async def __call__(self, event: Event) -> bool: ...
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+ from typing import Any
3
+ from trueconf.enums.message_type import MessageType
4
+ from trueconf.types.message import Message
5
+
6
+ Event = Any # позже можно заменить на типизированные модели
7
+
8
+ class Command:
9
+ """
10
+ /start, /help, /echo hi
11
+ Command("start") == Command("/start") # оба варианта валидны
12
+ Command(("start", "join")) — строго '/start join'
13
+ Command("echo", with_param=True) — '/echo <любой текст>'
14
+ """
15
+
16
+ def __init__(self, cmd: str, with_param: bool = False):
17
+ self.cmd = cmd[1:] if cmd.startswith("/") else cmd
18
+ self.with_param = with_param
19
+
20
+ async def __call__(self, event: Event) -> bool:
21
+ if not isinstance(event, Message):
22
+ return False
23
+ if event.type != MessageType.PLAIN_MESSAGE:
24
+ return False
25
+ text = (event.content.text or "")
26
+ if not text.startswith("/"):
27
+ return False
28
+ no_slash = text[1:]
29
+ parts = no_slash.split(maxsplit=1)
30
+ if not parts or parts[0] != self.cmd:
31
+ return False
32
+ return (len(parts) == 2 and bool(parts[1].strip())) if self.with_param else True
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+ from typing import Type
3
+ from trueconf.filters.base import Event
4
+
5
+
6
+ class InstanceOfFilter:
7
+ def __init__(self, cls: Type[object]):
8
+ self.cls = cls
9
+
10
+ async def __call__(self, event: Event) -> bool:
11
+ return isinstance(event, self.cls)
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+ from trueconf.enums.message_type import MessageType
3
+ from trueconf.types.message import Message
4
+
5
+
6
+ class MessageFilter:
7
+ """Фильтр по payload.type (= EnvelopeType)."""
8
+
9
+ def __init__(self, *types: MessageType):
10
+ self.types = frozenset(int(t) for t in types)
11
+
12
+ async def __call__(self, event: Message) -> bool:
13
+ if not isinstance(event, Message):
14
+ return False
15
+ return int(event.type) in self.types
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+ from trueconf.filters.base import Event
3
+
4
+
5
+ class MethodFilter:
6
+ def __init__(self, method: str):
7
+ self.method = method
8
+
9
+ async def __call__(self, event: Event) -> bool:
10
+ if isinstance(event, dict):
11
+ return event.get("method") == self.method
12
+ return getattr(event, "method", None) == self.method
trueconf/loggers.py ADDED
@@ -0,0 +1,4 @@
1
+ import logging
2
+
3
+ dispatcher = logging.getLogger("trueconf.dispatcher")
4
+ chatbot = logging.getLogger("trueconf.client.chatbot")