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.
- r_tg_bot-0.1.0/PKG-INFO +10 -0
- r_tg_bot-0.1.0/pyproject.toml +20 -0
- r_tg_bot-0.1.0/r_tg_bot/__init__.py +27 -0
- r_tg_bot-0.1.0/r_tg_bot/client.py +175 -0
- r_tg_bot-0.1.0/r_tg_bot/exceptions.py +27 -0
- r_tg_bot-0.1.0/r_tg_bot/types.py +90 -0
- r_tg_bot-0.1.0/r_tg_bot.egg-info/PKG-INFO +10 -0
- r_tg_bot-0.1.0/r_tg_bot.egg-info/SOURCES.txt +10 -0
- r_tg_bot-0.1.0/r_tg_bot.egg-info/dependency_links.txt +1 -0
- r_tg_bot-0.1.0/r_tg_bot.egg-info/requires.txt +1 -0
- r_tg_bot-0.1.0/r_tg_bot.egg-info/top_level.txt +1 -0
- r_tg_bot-0.1.0/setup.cfg +4 -0
r_tg_bot-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiohttp>=3.8.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
r_tg_bot
|
r_tg_bot-0.1.0/setup.cfg
ADDED