r-tg-bot 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,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: r-tg-bot
3
+ Version: 0.1.0
4
+ Summary: An asynchronous Python library for the Telegram Bot API
5
+ Author-email: Rixa <mrereyoshiki12@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/rixa8/r-tg-bot
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: aiohttp>=3.8.0
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "r-tg-bot"
7
+ version = "0.1.0"
8
+ description = "An asynchronous Python library for the Telegram Bot API"
9
+ authors = [
10
+ {name = "Rixa", email = "mrereyoshiki12@gmail.com"}
11
+ ]
12
+ license = {text = "MIT"}
13
+ readme = "README.md"
14
+ requires-python = ">=3.8"
15
+ dependencies = [
16
+ "aiohttp>=3.8.0"
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/rixa8/r-tg-bot"
@@ -0,0 +1,27 @@
1
+ """r-tg-bot: Asynchronous Python library for the Telegram Bot API."""
2
+
3
+ from .client import Bot
4
+ from .exceptions import (
5
+ TelegramAPIError,
6
+ InvalidTokenError,
7
+ NetworkError,
8
+ TimeoutError,
9
+ )
10
+ from .types import (
11
+ Message,
12
+ Update,
13
+ User,
14
+ Chat,
15
+ )
16
+
17
+ __all__ = [
18
+ "Bot",
19
+ "TelegramAPIError",
20
+ "InvalidTokenError",
21
+ "NetworkError",
22
+ "TimeoutError",
23
+ "Message",
24
+ "Update",
25
+ "User",
26
+ "Chat",
27
+ ]
@@ -0,0 +1,175 @@
1
+ """Asynchronous Telegram Bot client."""
2
+
3
+ import asyncio
4
+ import json
5
+ from typing import Any, Callable, Coroutine, Optional
6
+
7
+ import aiohttp
8
+ from aiohttp import ClientTimeout
9
+
10
+ from .exceptions import InvalidTokenError, NetworkError, TelegramAPIError, TimeoutError
11
+ from .types import Update
12
+
13
+
14
+ class Bot:
15
+ """Main client for interacting with the Telegram Bot API."""
16
+
17
+ BASE_URL = "https://api.telegram.org/bot{token}"
18
+
19
+ def __init__(self, token: str, session: Optional[aiohttp.ClientSession] = None):
20
+ """
21
+ Initialize the Bot client.
22
+
23
+ :param token: Bot token from BotFather.
24
+ :param session: Optional aiohttp session (reused if provided).
25
+ """
26
+ self.token = token
27
+ self._session = session
28
+ self._owned_session = session is None
29
+ self._offset = 0
30
+
31
+ async def _get_session(self) -> aiohttp.ClientSession:
32
+ """Return existing session or create a new one."""
33
+ if self._session is None or self._session.closed:
34
+ self._session = aiohttp.ClientSession()
35
+ self._owned_session = True
36
+ return self._session
37
+
38
+ async def _request(
39
+ self,
40
+ method: str,
41
+ params: Optional[dict] = None,
42
+ timeout: int = 30,
43
+ ) -> dict[str, Any]:
44
+ """
45
+ Make an asynchronous request to the Telegram Bot API.
46
+
47
+ :param method: API method name (e.g., 'getMe').
48
+ :param params: Optional query or JSON parameters.
49
+ :param timeout: Request timeout in seconds.
50
+ :return: Parsed JSON response.
51
+ :raises TelegramAPIError: If the API returns an error.
52
+ """
53
+ url = self.BASE_URL.format(token=self.token) + f"/{method}"
54
+ session = await self._get_session()
55
+
56
+ try:
57
+ async with session.post(
58
+ url,
59
+ json=params,
60
+ timeout=ClientTimeout(total=timeout),
61
+ ) as response:
62
+ data = await response.json()
63
+ except aiohttp.ClientConnectionError as e:
64
+ raise NetworkError(f"Network error: {e}") from e
65
+ except asyncio.TimeoutError as e:
66
+ raise TimeoutError(f"Request timed out after {timeout}s") from e
67
+
68
+ if not data.get("ok"):
69
+ error_msg = data.get("description", "Unknown error")
70
+ status_code = response.status if "response" in locals() else None
71
+ if status_code == 401:
72
+ raise InvalidTokenError(error_msg, status_code)
73
+ raise TelegramAPIError(error_msg, status_code)
74
+
75
+ return data["result"]
76
+
77
+ async def get_me(self) -> dict[str, Any]:
78
+ """
79
+ Get basic information about the bot.
80
+
81
+ :return: Bot user information as dictionary.
82
+ """
83
+ return await self._request("getMe")
84
+
85
+ async def send_message(
86
+ self,
87
+ chat_id: int | str,
88
+ text: str,
89
+ parse_mode: Optional[str] = None,
90
+ disable_web_page_preview: bool = False,
91
+ reply_to_message_id: Optional[int] = None,
92
+ ) -> dict[str, Any]:
93
+ """
94
+ Send a text message.
95
+
96
+ :param chat_id: Unique identifier for the target chat.
97
+ :param text: Text of the message.
98
+ :param parse_mode: 'HTML' or 'MarkdownV2'.
99
+ :param disable_web_page_preview: Disables link previews.
100
+ :param reply_to_message_id: Message ID to reply to.
101
+ :return: API response containing sent Message.
102
+ """
103
+ payload = {
104
+ "chat_id": chat_id,
105
+ "text": text,
106
+ "disable_web_page_preview": disable_web_page_preview,
107
+ }
108
+ if parse_mode:
109
+ payload["parse_mode"] = parse_mode
110
+ if reply_to_message_id:
111
+ payload["reply_to_message_id"] = reply_to_message_id
112
+
113
+ return await self._request("sendMessage", payload)
114
+
115
+ async def get_updates(
116
+ self,
117
+ offset: Optional[int] = None,
118
+ limit: int = 100,
119
+ timeout: int = 0,
120
+ ) -> list[Update]:
121
+ """
122
+ Get incoming updates using long polling.
123
+
124
+ :param offset: Identifier of the first update to be returned.
125
+ :param limit: Maximum number of updates to be fetched (1-100).
126
+ :param timeout: Long polling timeout in seconds.
127
+ :return: List of Update objects.
128
+ """
129
+ params = {
130
+ "offset": offset if offset is not None else self._offset,
131
+ "limit": limit,
132
+ "timeout": timeout,
133
+ }
134
+ result = await self._request("getUpdates", params, timeout=timeout + 5)
135
+ updates = [Update.from_dict(item) for item in result]
136
+ if updates:
137
+ self._offset = updates[-1].update_id + 1
138
+ return updates
139
+
140
+ async def start_polling(
141
+ self,
142
+ update_handler: Callable[[Update], Coroutine[Any, Any, None]],
143
+ interval: float = 0.0,
144
+ timeout: int = 30,
145
+ ) -> None:
146
+ """
147
+ Start long polling to receive updates continuously.
148
+
149
+ :param update_handler: Async function that takes an Update and processes it.
150
+ :param interval: Sleep interval between polling cycles (seconds).
151
+ :param timeout: Long polling timeout for getUpdates.
152
+ """
153
+ while True:
154
+ try:
155
+ updates = await self.get_updates(timeout=timeout)
156
+ for update in updates:
157
+ await update_handler(update)
158
+ except TelegramAPIError as e:
159
+ # Log error but continue polling
160
+ print(f"Polling error: {e}")
161
+ except Exception as e:
162
+ print(f"Unexpected error: {e}")
163
+ if interval > 0:
164
+ await asyncio.sleep(interval)
165
+
166
+ async def close(self) -> None:
167
+ """Close the aiohttp session if owned by this client."""
168
+ if self._session and self._owned_session:
169
+ await self._session.close()
170
+
171
+ async def __aenter__(self) -> "Bot":
172
+ return self
173
+
174
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
175
+ await self.close()
@@ -0,0 +1,27 @@
1
+ """Custom exceptions for the r-tg-bot library."""
2
+
3
+
4
+ class TelegramAPIError(Exception):
5
+ """Base exception for Telegram API related errors."""
6
+
7
+ def __init__(self, message: str, status_code: int | None = None):
8
+ self.status_code = status_code
9
+ super().__init__(message)
10
+
11
+
12
+ class InvalidTokenError(TelegramAPIError):
13
+ """Raised when the provided bot token is invalid."""
14
+
15
+ pass
16
+
17
+
18
+ class NetworkError(TelegramAPIError):
19
+ """Raised when a network-related error occurs."""
20
+
21
+ pass
22
+
23
+
24
+ class TimeoutError(TelegramAPIError):
25
+ """Raised when a request times out."""
26
+
27
+ pass
@@ -0,0 +1,90 @@
1
+ """Data types representing Telegram API objects."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Optional
5
+
6
+
7
+ @dataclass
8
+ class User:
9
+ """Telegram User object."""
10
+
11
+ id: int
12
+ is_bot: bool
13
+ first_name: str
14
+ last_name: Optional[str] = None
15
+ username: Optional[str] = None
16
+ language_code: Optional[str] = None
17
+
18
+ @classmethod
19
+ def from_dict(cls, data: dict[str, Any]) -> "User":
20
+ """Create User from API dictionary."""
21
+ return cls(
22
+ id=data["id"],
23
+ is_bot=data["is_bot"],
24
+ first_name=data["first_name"],
25
+ last_name=data.get("last_name"),
26
+ username=data.get("username"),
27
+ language_code=data.get("language_code"),
28
+ )
29
+
30
+
31
+ @dataclass
32
+ class Chat:
33
+ """Telegram Chat object."""
34
+
35
+ id: int
36
+ type: str
37
+ title: Optional[str] = None
38
+ username: Optional[str] = None
39
+ first_name: Optional[str] = None
40
+ last_name: Optional[str] = None
41
+
42
+ @classmethod
43
+ def from_dict(cls, data: dict[str, Any]) -> "Chat":
44
+ """Create Chat from API dictionary."""
45
+ return cls(
46
+ id=data["id"],
47
+ type=data["type"],
48
+ title=data.get("title"),
49
+ username=data.get("username"),
50
+ first_name=data.get("first_name"),
51
+ last_name=data.get("last_name"),
52
+ )
53
+
54
+
55
+ @dataclass
56
+ class Message:
57
+ """Telegram Message object."""
58
+
59
+ message_id: int
60
+ date: int
61
+ text: Optional[str] = None
62
+ chat: Optional[Chat] = None
63
+ from_user: Optional[User] = None
64
+
65
+ @classmethod
66
+ def from_dict(cls, data: dict[str, Any]) -> "Message":
67
+ """Create Message from API dictionary."""
68
+ chat = Chat.from_dict(data["chat"]) if "chat" in data else None
69
+ from_user = User.from_dict(data["from"]) if "from" in data else None
70
+ return cls(
71
+ message_id=data["message_id"],
72
+ date=data["date"],
73
+ text=data.get("text"),
74
+ chat=chat,
75
+ from_user=from_user,
76
+ )
77
+
78
+
79
+ @dataclass
80
+ class Update:
81
+ """Telegram Update object."""
82
+
83
+ update_id: int
84
+ message: Optional[Message] = None
85
+
86
+ @classmethod
87
+ def from_dict(cls, data: dict[str, Any]) -> "Update":
88
+ """Create Update from API dictionary."""
89
+ message = Message.from_dict(data["message"]) if "message" in data else None
90
+ return cls(update_id=data["update_id"], message=message)
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: r-tg-bot
3
+ Version: 0.1.0
4
+ Summary: An asynchronous Python library for the Telegram Bot API
5
+ Author-email: Rixa <mrereyoshiki12@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/rixa8/r-tg-bot
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: aiohttp>=3.8.0
@@ -0,0 +1,10 @@
1
+ pyproject.toml
2
+ r_tg_bot/__init__.py
3
+ r_tg_bot/client.py
4
+ r_tg_bot/exceptions.py
5
+ r_tg_bot/types.py
6
+ r_tg_bot.egg-info/PKG-INFO
7
+ r_tg_bot.egg-info/SOURCES.txt
8
+ r_tg_bot.egg-info/dependency_links.txt
9
+ r_tg_bot.egg-info/requires.txt
10
+ r_tg_bot.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ aiohttp>=3.8.0
@@ -0,0 +1 @@
1
+ r_tg_bot
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+