botiksdk 0.1.0__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.
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: botiksdk
3
+ Version: 0.1.0
4
+ Summary: Vondic Botik SDK
5
+ Author-email: Vondic <support@vondic.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/vondic/botiksdk
8
+ Project-URL: Bug Tracker, https://github.com/vondic/botiksdk/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: requests>=2.32.5
15
+
16
+ # Vondic Botik SDK
17
+
18
+ A Python SDK for building bots on the Vondic platform.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install botiksdk
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```python
29
+ from botiksdk import Bot, Dispatcher, Message
30
+
31
+ bot = Bot(token="YOUR_BOT_TOKEN")
32
+ dp = Dispatcher(bot)
33
+
34
+ @dp.message_handler()
35
+ async def echo(message: Message):
36
+ await message.answer(message.text)
37
+
38
+ if __name__ == "__main__":
39
+ dp.start_polling()
40
+ ```
@@ -0,0 +1,25 @@
1
+ # Vondic Botik SDK
2
+
3
+ A Python SDK for building bots on the Vondic platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install botiksdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from botiksdk import Bot, Dispatcher, Message
15
+
16
+ bot = Bot(token="YOUR_BOT_TOKEN")
17
+ dp = Dispatcher(bot)
18
+
19
+ @dp.message_handler()
20
+ async def echo(message: Message):
21
+ await message.answer(message.text)
22
+
23
+ if __name__ == "__main__":
24
+ dp.start_polling()
25
+ ```
@@ -0,0 +1,20 @@
1
+ from botiksdk.bot import Bot
2
+ from botiksdk.bot_types import Chat, Message, Update, User
3
+ from botiksdk.client import PublicAPIClient
4
+ from botiksdk.dispatcher import Dispatcher
5
+ from botiksdk.filters import Command, F, Text
6
+ from botiksdk.router import Router
7
+
8
+ __all__ = [
9
+ "Bot",
10
+ "PublicAPIClient",
11
+ "Dispatcher",
12
+ "Router",
13
+ "Command",
14
+ "Text",
15
+ "F",
16
+ "Update",
17
+ "Message",
18
+ "User",
19
+ "Chat",
20
+ ]
botiksdk-0.1.0/bot.py ADDED
@@ -0,0 +1,57 @@
1
+ import logging
2
+ from typing import Optional
3
+
4
+ from botiksdk.client import PublicAPIClient
5
+
6
+
7
+ class Bot:
8
+ def __init__(
9
+ self,
10
+ bot_id: Optional[str] = None,
11
+ token: Optional[str] = None,
12
+ *,
13
+ base_url: str = "http://localhost:5050",
14
+ api_key: Optional[str] = None,
15
+ ):
16
+ self.bot_id = bot_id
17
+ self.token = token
18
+ self.api_key = api_key
19
+ self.public = PublicAPIClient(base_url=base_url)
20
+
21
+ def set_token(self, token: str):
22
+ self.token = token
23
+ return self
24
+
25
+ def set_bot_id(self, bot_id: str):
26
+ self.bot_id = bot_id
27
+ return self
28
+
29
+ def set_api_key(self, api_key: str):
30
+ self.api_key = api_key
31
+ return self
32
+
33
+ def _ensure_ready(self):
34
+ if not self.bot_id:
35
+ raise ValueError("bot_id is required")
36
+ if not self.token:
37
+ raise ValueError("bot token is required")
38
+
39
+ def get_updates(self, *, offset: int = 0, limit: int = 100, timeout: int = 20):
40
+ self._ensure_ready()
41
+ return self.public.get_updates(
42
+ self.bot_id,
43
+ self.token,
44
+ offset=offset,
45
+ limit=limit,
46
+ timeout=timeout,
47
+ )
48
+
49
+ def send_message(self, chat_id: str, text: str):
50
+ self._ensure_ready()
51
+ logging.getLogger(__name__).info(
52
+ "botiksdk_send_message bot_id=%s chat_id=%s text=%s",
53
+ self.bot_id,
54
+ chat_id,
55
+ text,
56
+ )
57
+ return self.public.send_message(self.bot_id, self.token, chat_id, text)
@@ -0,0 +1,87 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from typing import Any, Dict, Optional
4
+
5
+
6
+ @dataclass
7
+ class User:
8
+ id: str
9
+ username: Optional[str] = None
10
+ avatar_url: Optional[str] = None
11
+
12
+ @classmethod
13
+ def from_dict(cls, data: Dict[str, Any]):
14
+ if data is None:
15
+ return None
16
+ return cls(
17
+ id=str(data.get("id") or ""),
18
+ username=data.get("username"),
19
+ avatar_url=data.get("avatar_url"),
20
+ )
21
+
22
+
23
+ @dataclass
24
+ class Chat:
25
+ id: str
26
+ type: str = "private"
27
+ title: Optional[str] = None
28
+
29
+ @classmethod
30
+ def from_dict(cls, data: Dict[str, Any]):
31
+ if data is None:
32
+ return None
33
+ return cls(
34
+ id=str(data.get("id") or ""),
35
+ type=data.get("type") or "private",
36
+ title=data.get("title"),
37
+ )
38
+
39
+
40
+ @dataclass
41
+ class Message:
42
+ message_id: str
43
+ text: Optional[str]
44
+ from_user: Optional[User]
45
+ chat: Optional[Chat]
46
+ date: Optional[datetime]
47
+ raw: Dict[str, Any]
48
+
49
+ @classmethod
50
+ def from_dict(cls, data: Dict[str, Any]):
51
+ if data is None:
52
+ return None
53
+ ts = data.get("date")
54
+ if isinstance(ts, (int, float)):
55
+ date_value = datetime.fromtimestamp(ts)
56
+ elif isinstance(ts, str):
57
+ try:
58
+ date_value = datetime.fromisoformat(ts)
59
+ except ValueError:
60
+ date_value = None
61
+ else:
62
+ date_value = None
63
+ return cls(
64
+ message_id=str(data.get("message_id") or data.get("id") or ""),
65
+ text=data.get("text"),
66
+ from_user=User.from_dict(data.get("from_user")),
67
+ chat=Chat.from_dict(data.get("chat")),
68
+ date=date_value,
69
+ raw=data,
70
+ )
71
+
72
+
73
+ @dataclass
74
+ class Update:
75
+ update_id: str
76
+ message: Optional[Message]
77
+ raw: Dict[str, Any]
78
+
79
+ @classmethod
80
+ def from_dict(cls, data: Dict[str, Any]):
81
+ if data is None:
82
+ return None
83
+ return cls(
84
+ update_id=str(data.get("update_id") or data.get("id") or ""),
85
+ message=Message.from_dict(data.get("message")),
86
+ raw=data,
87
+ )
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: botiksdk
3
+ Version: 0.1.0
4
+ Summary: Vondic Botik SDK
5
+ Author-email: Vondic <support@vondic.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/vondic/botiksdk
8
+ Project-URL: Bug Tracker, https://github.com/vondic/botiksdk/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: requests>=2.32.5
15
+
16
+ # Vondic Botik SDK
17
+
18
+ A Python SDK for building bots on the Vondic platform.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install botiksdk
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```python
29
+ from botiksdk import Bot, Dispatcher, Message
30
+
31
+ bot = Bot(token="YOUR_BOT_TOKEN")
32
+ dp = Dispatcher(bot)
33
+
34
+ @dp.message_handler()
35
+ async def echo(message: Message):
36
+ await message.answer(message.text)
37
+
38
+ if __name__ == "__main__":
39
+ dp.start_polling()
40
+ ```
@@ -0,0 +1,27 @@
1
+ README.md
2
+ __init__.py
3
+ bot.py
4
+ bot_types.py
5
+ client.py
6
+ dispatcher.py
7
+ exceptions.py
8
+ filters.py
9
+ pyproject.toml
10
+ router.py
11
+ setup.py
12
+ test_bot.py
13
+ ./__init__.py
14
+ ./bot.py
15
+ ./bot_types.py
16
+ ./client.py
17
+ ./dispatcher.py
18
+ ./exceptions.py
19
+ ./filters.py
20
+ ./router.py
21
+ ./setup.py
22
+ ./test_bot.py
23
+ botiksdk.egg-info/PKG-INFO
24
+ botiksdk.egg-info/SOURCES.txt
25
+ botiksdk.egg-info/dependency_links.txt
26
+ botiksdk.egg-info/requires.txt
27
+ botiksdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.32.5
@@ -0,0 +1 @@
1
+ botiksdk
@@ -0,0 +1,160 @@
1
+ import json
2
+ import logging
3
+ from typing import Any, Dict, Optional
4
+
5
+ import requests
6
+
7
+ from botiksdk.exceptions import (
8
+ APIError,
9
+ BadRequestError,
10
+ NotFoundError,
11
+ UnauthorizedError,
12
+ )
13
+
14
+
15
+ class PublicAPIClient:
16
+ def __init__(self, base_url: str = "http://localhost:5050"):
17
+ normalized = (base_url or "http://localhost:5050").strip()
18
+ if "://" not in normalized:
19
+ normalized = f"http://{normalized}"
20
+ self.base_url = normalized.rstrip("/")
21
+
22
+ def _request(
23
+ self,
24
+ method: str,
25
+ path: str,
26
+ *,
27
+ access_token: Optional[str] = None,
28
+ api_key: Optional[str] = None,
29
+ bot_token: Optional[str] = None,
30
+ params: Optional[Dict[str, Any]] = None,
31
+ json_body: Optional[Dict[str, Any]] = None,
32
+ ):
33
+ url = f"{self.base_url}{path}"
34
+ logger = logging.getLogger(__name__)
35
+ headers: Dict[str, str] = {"Content-Type": "application/json"}
36
+ if access_token:
37
+ headers["Authorization"] = f"Bearer {access_token}"
38
+ if api_key:
39
+ headers["X-API-Key"] = api_key
40
+ if bot_token:
41
+ headers["X-Bot-Token"] = bot_token
42
+ try:
43
+ response = requests.request(
44
+ method,
45
+ url,
46
+ headers=headers,
47
+ params=params,
48
+ json=json_body,
49
+ timeout=30,
50
+ )
51
+ except requests.RequestException:
52
+ logger.exception(
53
+ "botiksdk_request_error method=%s url=%s params=%s",
54
+ method,
55
+ url,
56
+ params,
57
+ )
58
+ raise
59
+ if response.ok:
60
+ if not response.text:
61
+ return None
62
+ try:
63
+ return response.json()
64
+ except json.JSONDecodeError:
65
+ return response.text
66
+ payload = None
67
+ try:
68
+ payload = response.json()
69
+ except json.JSONDecodeError:
70
+ payload = response.text
71
+ if response.status_code >= 400:
72
+ text_preview = str(payload)
73
+ if len(text_preview) > 500:
74
+ text_preview = f"{text_preview[:500]}..."
75
+ logger.info(
76
+ "botiksdk_request_failed method=%s url=%s status=%s body=%s",
77
+ method,
78
+ url,
79
+ response.status_code,
80
+ text_preview,
81
+ )
82
+ if response.status_code == 401:
83
+ raise UnauthorizedError(
84
+ response.status_code, "Unauthorized", payload)
85
+ if response.status_code == 404:
86
+ raise NotFoundError(response.status_code, "Not Found", payload)
87
+ if response.status_code == 400:
88
+ raise BadRequestError(response.status_code, "Bad Request", payload)
89
+ raise APIError(response.status_code, "API Error", payload)
90
+
91
+ def list_bots(self):
92
+ return self._request("GET", "/api/public/v1/bots")
93
+
94
+ def get_bot(self, bot_id: str):
95
+ return self._request("GET", f"/api/public/v1/bots/{bot_id}")
96
+
97
+ def get_bot_by_name(self, name: str):
98
+ return self._request("GET", f"/api/public/v1/bots/by-name/{name}")
99
+
100
+ def search_bots(self, query: str):
101
+ return self._request("GET", "/api/public/v1/bots/search", params={"q": query})
102
+
103
+ def generate_bot_token(self, bot_id: str, api_key: str):
104
+ return self._request(
105
+ "POST",
106
+ f"/api/public/v1/bots/{bot_id}/token",
107
+ api_key=api_key,
108
+ )
109
+
110
+ def get_updates(
111
+ self,
112
+ bot_id: str,
113
+ bot_token: str,
114
+ *,
115
+ offset: int = 0,
116
+ limit: int = 100,
117
+ timeout: int = 20,
118
+ ):
119
+ data = self._request(
120
+ "GET",
121
+ f"/api/public/v1/bots/{bot_id}/updates",
122
+ params={"offset": offset, "limit": limit, "timeout": timeout},
123
+ bot_token=bot_token,
124
+ )
125
+ if isinstance(data, dict) and "items" in data:
126
+ return data["items"]
127
+ if data is None:
128
+ return []
129
+ return data
130
+
131
+ def push_update(self, bot_id: str, bot_token: str, message: Dict[str, Any]):
132
+ return self._request(
133
+ "POST",
134
+ f"/api/public/v1/bots/{bot_id}/updates/push",
135
+ json_body={"message": message},
136
+ bot_token=bot_token,
137
+ )
138
+
139
+ def send_message(self, bot_id: str, bot_token: str, chat_id: str, text: str):
140
+ return self._request(
141
+ "POST",
142
+ f"/api/public/v1/bots/{bot_id}/send",
143
+ json_body={"chat_id": chat_id, "text": text},
144
+ bot_token=bot_token,
145
+ )
146
+
147
+ def get_api_key(self, access_token: str):
148
+ return self._request(
149
+ "GET",
150
+ "/api/public/v1/account/api-key",
151
+ access_token=access_token,
152
+ )
153
+
154
+ def generate_api_key(self, access_token: str, rotate: bool = False):
155
+ return self._request(
156
+ "POST",
157
+ "/api/public/v1/account/api-key",
158
+ access_token=access_token,
159
+ json_body={"rotate": rotate},
160
+ )
@@ -0,0 +1,89 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import Iterable
4
+
5
+ from botiksdk.bot_types import Message, Update
6
+ from botiksdk.filters import BaseFilter
7
+ from botiksdk.router import Router
8
+
9
+
10
+ class Dispatcher:
11
+ def __init__(self):
12
+ self._routers = [Router()]
13
+
14
+ def include_router(self, router: Router):
15
+ self._routers.append(router)
16
+ return self
17
+
18
+ def message(self, *filters: BaseFilter):
19
+ return self._routers[0].message(*filters)
20
+
21
+ async def feed_update(self, bot, update: Update):
22
+ if update is None:
23
+ return
24
+ message = update.message
25
+ if message is not None:
26
+ logging.getLogger(__name__).info(
27
+ "botiksdk_update_received bot_id=%s update_id=%s text=%s",
28
+ getattr(bot, "bot_id", None),
29
+ update.update_id,
30
+ message.text,
31
+ )
32
+ await self._dispatch_message(bot, message)
33
+
34
+ async def _dispatch_message(self, bot, message: Message):
35
+ for router in self._routers:
36
+ for handler in router.message_handlers:
37
+ if await self._check_filters(handler.filters, message):
38
+ logging.getLogger(__name__).info(
39
+ "botiksdk_handler_matched bot_id=%s text=%s",
40
+ getattr(bot, "bot_id", None),
41
+ message.text,
42
+ )
43
+ await handler.callback(message, bot)
44
+
45
+ async def _check_filters(self, filters: Iterable[BaseFilter], message: Message):
46
+ for f in filters:
47
+ result = f(message)
48
+ if asyncio.iscoroutine(result):
49
+ result = await result
50
+ if not result:
51
+ return False
52
+ return True
53
+
54
+ async def start_polling(self, *bots):
55
+ tasks = []
56
+ for bot in bots:
57
+ tasks.append(asyncio.create_task(self._poll_bot(bot)))
58
+ if not tasks:
59
+ return
60
+ await asyncio.gather(*tasks)
61
+
62
+ async def start_webhook(self, *bots, **kwargs):
63
+ raise NotImplementedError(
64
+ "Public API does not provide webhook updates")
65
+
66
+ async def _poll_bot(self, bot):
67
+ offset = 0
68
+ while True:
69
+ try:
70
+ updates = bot.get_updates(offset=offset, timeout=20, limit=100)
71
+ except Exception:
72
+ logging.getLogger(__name__).exception(
73
+ "botiksdk_poll_error bot_id=%s", getattr(
74
+ bot, "bot_id", None)
75
+ )
76
+ await asyncio.sleep(1)
77
+ continue
78
+ if not updates:
79
+ await asyncio.sleep(0.2)
80
+ continue
81
+ for raw in updates:
82
+ update = Update.from_dict(raw)
83
+ if update is None:
84
+ continue
85
+ await self.feed_update(bot, update)
86
+ try:
87
+ offset = max(offset, int(update.update_id))
88
+ except Exception:
89
+ offset = offset
@@ -0,0 +1,21 @@
1
+ class BotikSDKError(Exception):
2
+ pass
3
+
4
+
5
+ class APIError(BotikSDKError):
6
+ def __init__(self, status_code, message, payload=None):
7
+ super().__init__(message)
8
+ self.status_code = status_code
9
+ self.payload = payload
10
+
11
+
12
+ class UnauthorizedError(APIError):
13
+ pass
14
+
15
+
16
+ class NotFoundError(APIError):
17
+ pass
18
+
19
+
20
+ class BadRequestError(APIError):
21
+ pass
@@ -0,0 +1,101 @@
1
+ import re
2
+ from typing import Any, Iterable, Optional
3
+
4
+
5
+ class BaseFilter:
6
+ def __call__(self, message) -> bool:
7
+ return True
8
+
9
+
10
+ class Command(BaseFilter):
11
+ def __init__(self, *commands: str, prefix: str = "/"):
12
+ self.commands = {c.lstrip(prefix).lower() for c in commands if c}
13
+ self.prefix = prefix
14
+
15
+ def __call__(self, message) -> bool:
16
+ text = getattr(message, "text", None)
17
+ if not text:
18
+ return False
19
+ if not text.startswith(self.prefix):
20
+ return False
21
+ cmd = text[len(self.prefix):].split()[0].lower()
22
+ if not self.commands:
23
+ return True
24
+ return cmd in self.commands
25
+
26
+
27
+ class Text(BaseFilter):
28
+ def __init__(self, equals: Optional[str] = None, contains: Optional[str] = None):
29
+ self.equals = equals
30
+ self.contains_value = contains
31
+
32
+ def __call__(self, message) -> bool:
33
+ text = getattr(message, "text", None)
34
+ if text is None:
35
+ return False
36
+ if self.equals is not None:
37
+ return text == self.equals
38
+ if self.contains_value is not None:
39
+ return self.contains_value in text
40
+ return True
41
+
42
+
43
+ class Regex(BaseFilter):
44
+ def __init__(self, pattern: str, flags: int = 0):
45
+ self.pattern = re.compile(pattern, flags=flags)
46
+
47
+ def __call__(self, message) -> bool:
48
+ text = getattr(message, "text", None)
49
+ if not text:
50
+ return False
51
+ return self.pattern.search(text) is not None
52
+
53
+
54
+ class FilterExpression(BaseFilter):
55
+ def __init__(self, getter, op: str, value: Any):
56
+ self.getter = getter
57
+ self.op = op
58
+ self.value = value
59
+
60
+ def __call__(self, message) -> bool:
61
+ current = self.getter(message)
62
+ if self.op == "eq":
63
+ return current == self.value
64
+ if self.op == "contains":
65
+ return current is not None and self.value in current
66
+ if self.op == "in":
67
+ return current in self.value
68
+ return False
69
+
70
+
71
+ class FieldRef:
72
+ def __init__(self, path: Iterable[str]):
73
+ self.path = tuple(path)
74
+
75
+ def __getattr__(self, name: str):
76
+ return FieldRef(self.path + (name,))
77
+
78
+ def _get_value(self, message):
79
+ current = message
80
+ for name in self.path:
81
+ if current is None:
82
+ return None
83
+ current = getattr(current, name, None)
84
+ return current
85
+
86
+ def __eq__(self, other):
87
+ return FilterExpression(self._get_value, "eq", other)
88
+
89
+ def contains(self, value):
90
+ return FilterExpression(self._get_value, "contains", value)
91
+
92
+ def isin(self, values):
93
+ return FilterExpression(self._get_value, "in", values)
94
+
95
+
96
+ class FieldAccessor:
97
+ def __getattr__(self, name: str):
98
+ return FieldRef((name,))
99
+
100
+
101
+ F = FieldAccessor()
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "botiksdk"
7
+ version = "0.1.0"
8
+ description = "Vondic Botik SDK"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Vondic", email = "support@vondic.com" },
12
+ ]
13
+ license = { text = "MIT" }
14
+ requires-python = ">=3.9"
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ dependencies = [
21
+ "requests>=2.32.5",
22
+ ]
23
+
24
+ [project.urls]
25
+ "Homepage" = "https://github.com/vondic/botiksdk"
26
+ "Bug Tracker" = "https://github.com/vondic/botiksdk/issues"
27
+
28
+ [tool.setuptools]
29
+ packages = ["botiksdk"]
30
+
31
+ [tool.setuptools.package-dir]
32
+ botiksdk = "."
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import Awaitable, Callable, List
3
+
4
+ from botiksdk.filters import BaseFilter
5
+
6
+ HandlerCallable = Callable[..., Awaitable[None]]
7
+
8
+
9
+ @dataclass
10
+ class Handler:
11
+ filters: List[BaseFilter]
12
+ callback: HandlerCallable
13
+
14
+
15
+ class Router:
16
+ def __init__(self):
17
+ self.message_handlers: List[Handler] = []
18
+
19
+ def message(self, *filters: BaseFilter):
20
+ def decorator(func: HandlerCallable):
21
+ self.message_handlers.append(
22
+ Handler(filters=list(filters), callback=func)
23
+ )
24
+ return func
25
+
26
+ return decorator
27
+
28
+ def include_router(self, router: "Router"):
29
+ self.message_handlers.extend(router.message_handlers)
30
+ return self
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -0,0 +1,65 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+
5
+ from botiksdk import Bot, Command, Dispatcher
6
+
7
+
8
+ async def main():
9
+ logging.basicConfig(
10
+ level=logging.INFO,
11
+ format="%(asctime)s %(levelname)s %(name)s %(message)s",
12
+ )
13
+ bot_id = os.environ.get(
14
+ "BOTIK_BOT_ID", "eee845bc-8bf7-49da-9ec9-304832e9189b"
15
+ )
16
+ bot_token = os.environ.get(
17
+ "BOTIK_BOT_TOKEN", "ktybQlSZb6xX5FH3j9FoeUKa79xCTUx3Gsio9_5dW3Y"
18
+ )
19
+ base_url = os.environ.get("BOTIK_BASE_URL", "http://localhost:5050")
20
+ logging.getLogger(__name__).info(
21
+ "botiksdk_config bot_id=%s base_url=%s", bot_id, base_url
22
+ )
23
+
24
+ bot = Bot(bot_id=bot_id, token=bot_token, base_url=base_url)
25
+ dp = Dispatcher()
26
+
27
+ @dp.message(Command("start"))
28
+ async def start_handler(message, bot_instance):
29
+ logging.getLogger(__name__).info(
30
+ "handler_start chat_id=%s", message.chat.id)
31
+ result = await asyncio.to_thread(
32
+ bot_instance.send_message,
33
+ message.chat.id,
34
+ "Привет! Команды: /id /help",
35
+ )
36
+ logging.getLogger(__name__).info("handler_start_result %s", result)
37
+
38
+ @dp.message(Command("id"))
39
+ async def id_handler(message, bot_instance):
40
+ user_id = message.from_user.id if message.from_user else "unknown"
41
+ logging.getLogger(__name__).info(
42
+ "handler_id chat_id=%s", message.chat.id)
43
+ result = await asyncio.to_thread(
44
+ bot_instance.send_message,
45
+ message.chat.id,
46
+ f"Ваш id: {user_id}",
47
+ )
48
+ logging.getLogger(__name__).info("handler_id_result %s", result)
49
+
50
+ @dp.message(Command("help"))
51
+ async def help_handler(message, bot_instance):
52
+ logging.getLogger(__name__).info(
53
+ "handler_help chat_id=%s", message.chat.id)
54
+ result = await asyncio.to_thread(
55
+ bot_instance.send_message,
56
+ message.chat.id,
57
+ "Доступные команды: /start /id /help",
58
+ )
59
+ logging.getLogger(__name__).info("handler_help_result %s", result)
60
+
61
+ await dp.start_polling(bot)
62
+
63
+
64
+ if __name__ == "__main__":
65
+ asyncio.run(main())