RubigramClient 1.4.7__py3-none-any.whl → 1.4.9__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.
Potentially problematic release.
This version of RubigramClient might be problematic. Click here for more details.
- rubigram/client.py +56 -58
- rubigram/filters.py +1 -1
- rubigram/method.py +6 -6
- rubigram/network.py +51 -33
- rubigram/types.py +2 -1
- {rubigramclient-1.4.7.dist-info → rubigramclient-1.4.9.dist-info}/METADATA +17 -2
- rubigramclient-1.4.9.dist-info/RECORD +11 -0
- rubigramclient-1.4.7.dist-info/RECORD +0 -11
- {rubigramclient-1.4.7.dist-info → rubigramclient-1.4.9.dist-info}/WHEEL +0 -0
- {rubigramclient-1.4.7.dist-info → rubigramclient-1.4.9.dist-info}/licenses/LICENSE +0 -0
- {rubigramclient-1.4.7.dist-info → rubigramclient-1.4.9.dist-info}/top_level.txt +0 -0
rubigram/client.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Optional, Callable,
|
|
2
|
+
from typing import Optional, Callable, Literal
|
|
3
3
|
from aiohttp import web
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from rubigram.types import Update, InlineMessage
|
|
@@ -9,8 +9,10 @@ from datetime import datetime
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import logging
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
15
|
+
|
|
14
16
|
class Client(Method):
|
|
15
17
|
def __init__(self, token: str, endpoint: Optional[str] = None, host: str = "0.0.0.0", port: int = 8000):
|
|
16
18
|
self.token = token
|
|
@@ -24,74 +26,67 @@ class Client(Method):
|
|
|
24
26
|
super().__init__(token)
|
|
25
27
|
|
|
26
28
|
def on_message(self, *filters: Filter):
|
|
27
|
-
def decorator(func: Callable
|
|
29
|
+
def decorator(func: Callable) -> Callable:
|
|
28
30
|
@wraps(func)
|
|
29
31
|
async def wrapper(client: Client, update: Update):
|
|
30
32
|
try:
|
|
31
33
|
combined_filter = filters[0] if filters else None
|
|
32
34
|
for filter in filters[1:]:
|
|
33
35
|
combined_filter = combined_filter & filter
|
|
34
|
-
|
|
36
|
+
|
|
35
37
|
if combined_filter is None or await combined_filter(update):
|
|
36
38
|
await func(client, update)
|
|
37
39
|
return True
|
|
38
40
|
return False
|
|
39
|
-
except Exception as
|
|
40
|
-
logger.exception(
|
|
41
|
-
|
|
41
|
+
except Exception as error:
|
|
42
|
+
logger.exception("Error {}: {}".format(func.__name__, error))
|
|
43
|
+
|
|
42
44
|
self.message_handlers.append(wrapper)
|
|
43
45
|
return func
|
|
44
46
|
return decorator
|
|
45
47
|
|
|
46
|
-
|
|
47
48
|
def on_inline_message(self, *filters: Filter):
|
|
48
|
-
def decorator(func: Callable
|
|
49
|
+
def decorator(func: Callable) -> Callable:
|
|
49
50
|
@wraps(func)
|
|
50
51
|
async def wrapper(client: Client, update: InlineMessage):
|
|
51
52
|
try:
|
|
52
53
|
combined_filter = filters[0] if filters else None
|
|
53
54
|
for filter in filters[1:]:
|
|
54
55
|
combined_filter = combined_filter & filter
|
|
55
|
-
|
|
56
|
+
|
|
56
57
|
if combined_filter is None or await combined_filter(update):
|
|
57
58
|
await func(client, update)
|
|
58
59
|
return True
|
|
59
60
|
return False
|
|
60
|
-
except Exception as
|
|
61
|
-
logger.exception(
|
|
62
|
-
|
|
61
|
+
except Exception as error:
|
|
62
|
+
logger.exception("Error {}: {}".format(func.__name__, error))
|
|
63
|
+
|
|
63
64
|
self.inline_handlers.append(wrapper)
|
|
64
65
|
return func
|
|
65
66
|
return decorator
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
for handler in
|
|
68
|
+
async def dispatch(self, update: Update, type: Literal["update", "inline_message"] = "update"):
|
|
69
|
+
handlers = self.message_handlers if type == "update" else self.inline_handlers
|
|
70
|
+
for handler in handlers:
|
|
70
71
|
try:
|
|
71
72
|
matched = await handler(self, update)
|
|
72
73
|
if matched:
|
|
73
74
|
return
|
|
74
75
|
except Exception as error:
|
|
75
|
-
logger.exception(f"Error in handler
|
|
76
|
-
|
|
77
|
-
async def dispatch_inline(self, update: InlineMessage):
|
|
78
|
-
for handler in self.inline_handlers:
|
|
79
|
-
try:
|
|
80
|
-
matched = await handler(self, update)
|
|
81
|
-
if matched:
|
|
82
|
-
return
|
|
83
|
-
except Exception as error:
|
|
84
|
-
logger.exception(f"Error in inline handler dispatch: {error}")
|
|
76
|
+
logger.exception(f"Dispatch Error in handler [ {handler.__name__} ] : {error}")
|
|
85
77
|
|
|
86
78
|
async def updater(self, data: dict):
|
|
87
79
|
if "inline_message" in data:
|
|
88
|
-
event = InlineMessage.from_dict(data
|
|
89
|
-
|
|
80
|
+
event = InlineMessage.from_dict(data.get("inline_message", {}))
|
|
81
|
+
event.client = self
|
|
82
|
+
await self.dispatch(event, "inline_message")
|
|
90
83
|
elif "update" in data:
|
|
91
|
-
event = Update.from_dict(data
|
|
84
|
+
event = Update.from_dict(data.get("update", {}))
|
|
92
85
|
event.client = self
|
|
93
|
-
await self.
|
|
94
|
-
|
|
86
|
+
await self.dispatch(event)
|
|
87
|
+
else:
|
|
88
|
+
return
|
|
89
|
+
|
|
95
90
|
async def set_endpoints(self):
|
|
96
91
|
if self.endpoint:
|
|
97
92
|
await self.update_bot_endpoint(f"{self.endpoint}/ReceiveUpdate", "ReceiveUpdate")
|
|
@@ -113,37 +108,40 @@ class Client(Method):
|
|
|
113
108
|
|
|
114
109
|
app = web.Application()
|
|
115
110
|
app.add_routes(self.routes)
|
|
116
|
-
|
|
111
|
+
|
|
117
112
|
async def on_startup(_):
|
|
118
113
|
await self.set_endpoints()
|
|
119
114
|
app.on_startup.append(on_startup)
|
|
120
115
|
web.run_app(app, host=self.host, port=self.port)
|
|
121
|
-
|
|
116
|
+
|
|
122
117
|
else:
|
|
123
118
|
async def polling():
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
119
|
+
try:
|
|
120
|
+
while True:
|
|
121
|
+
try:
|
|
122
|
+
get_update = await self.get_update(100, self.offset_id)
|
|
123
|
+
if get_update.updates:
|
|
124
|
+
updates = get_update.updates
|
|
125
|
+
for update in updates:
|
|
126
|
+
if update.type == "NewMessage":
|
|
127
|
+
message_time = int(update.new_message.time)
|
|
128
|
+
elif update.type == "UpdatedMessage":
|
|
129
|
+
message_time = int(
|
|
130
|
+
update.updated_message.time)
|
|
131
|
+
else:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
now = int(datetime.now().timestamp())
|
|
135
|
+
if message_time >= now or message_time + 2 >= now:
|
|
136
|
+
if isinstance(update, Update):
|
|
137
|
+
update.client = self
|
|
138
|
+
await self.dispatch(update)
|
|
139
|
+
|
|
140
|
+
self.offset_id = get_update.next_offset_id
|
|
141
|
+
except Exception as error:
|
|
142
|
+
logger.exception("Polling Error : {}".format(error))
|
|
143
|
+
await asyncio.sleep(1)
|
|
144
|
+
|
|
145
|
+
except: pass
|
|
146
|
+
finally: await self.stop()
|
|
147
|
+
asyncio.run(polling())
|
rubigram/filters.py
CHANGED
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Filter:
|
|
7
|
-
def __init__(self, func: Callable[[Union[Update, InlineMessage]], bool
|
|
7
|
+
def __init__(self, func: Callable[[Union[Update, InlineMessage]], Union[bool, Awaitable[bool]]]):
|
|
8
8
|
self.func = func
|
|
9
9
|
|
|
10
10
|
async def __call__(self, update: Union[Update, InlineMessage]) -> bool:
|
rubigram/method.py
CHANGED
|
@@ -3,10 +3,12 @@ from typing import Literal, Optional
|
|
|
3
3
|
from .types import Bot, Chat, Keypad, MessageId, Updates
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class Method(Network):
|
|
7
8
|
def __init__(self, token: str):
|
|
8
9
|
super().__init__(token)
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
async def get_me(self) -> "Bot":
|
|
11
13
|
response = await self.request("getMe", {})
|
|
12
14
|
return Bot.from_dict(response["bot"])
|
|
@@ -47,8 +49,7 @@ class Method(Network):
|
|
|
47
49
|
await self.request("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
|
|
48
50
|
|
|
49
51
|
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> "MessageId":
|
|
50
|
-
data = {"from_chat_id": from_chat_id, "message_id": message_id,
|
|
51
|
-
"to_chat_id": to_chat_id, "disable_notification": disable_notification}
|
|
52
|
+
data = {"from_chat_id": from_chat_id, "message_id": message_id, "to_chat_id": to_chat_id, "disable_notification": disable_notification}
|
|
52
53
|
response = await self.request("forwardMessage", data)
|
|
53
54
|
return MessageId.from_dict(response)
|
|
54
55
|
|
|
@@ -181,12 +182,12 @@ class Method(Network):
|
|
|
181
182
|
|
|
182
183
|
async def upload_file(self, file: str, name: str, type: str):
|
|
183
184
|
upload_url = await self.request_send_file(type)
|
|
184
|
-
response = await self.
|
|
185
|
+
response = await self.RequestUploadFile(upload_url, file, name)
|
|
185
186
|
return response
|
|
186
187
|
|
|
187
188
|
async def download_file(self, file_id: str, file_name: str):
|
|
188
189
|
download_url = await self.get_file(file_id)
|
|
189
|
-
response = await self.
|
|
190
|
+
response = await self.RequestDownloadFile(download_url, file_name)
|
|
190
191
|
return response
|
|
191
192
|
|
|
192
193
|
async def send_file(
|
|
@@ -194,8 +195,7 @@ class Method(Network):
|
|
|
194
195
|
chat_id: str,
|
|
195
196
|
file: str,
|
|
196
197
|
file_name: str,
|
|
197
|
-
type: Literal["File", "Image", "Voice",
|
|
198
|
-
"Music", "Gif", "Video"] = "File",
|
|
198
|
+
type: Literal["File", "Image", "Voice", "Music", "Gif", "Video"] = "File",
|
|
199
199
|
chat_keypad: Keypad = None,
|
|
200
200
|
inline_keypad: Keypad = None,
|
|
201
201
|
chat_keypad_type: Literal["New", "Remove"] = None,
|
rubigram/network.py
CHANGED
|
@@ -1,47 +1,65 @@
|
|
|
1
1
|
from aiohttp import ClientSession, FormData
|
|
2
|
-
from typing import Any, Optional
|
|
3
|
-
import aiofiles
|
|
4
|
-
|
|
5
|
-
import os
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
import aiofiles, re, os
|
|
4
|
+
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class Network:
|
|
9
8
|
def __init__(self, token: str) -> None:
|
|
10
9
|
self.token: str = token
|
|
11
10
|
self.session: Optional[ClientSession] = None
|
|
12
|
-
self.api: str = f"https://botapi.rubika.ir/v3/{
|
|
13
|
-
|
|
11
|
+
self.api: str = f"https://botapi.rubika.ir/v3/{token}/"
|
|
12
|
+
|
|
13
|
+
async def start(self):
|
|
14
|
+
if not self.session:
|
|
15
|
+
self.session = ClientSession()
|
|
16
|
+
|
|
17
|
+
async def stop(self):
|
|
18
|
+
if self.session:
|
|
19
|
+
await self.session.close()
|
|
20
|
+
self.session = None
|
|
21
|
+
|
|
22
|
+
async def __aenter__(self):
|
|
23
|
+
await self.start()
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
27
|
+
await self.stop()
|
|
14
28
|
|
|
15
|
-
async def request(self, method: str, json:
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
async def request(self, method: str, json: dict[str, Any]):
|
|
30
|
+
await self.start()
|
|
31
|
+
async with self.session.post(self.api + method, json=json) as response:
|
|
32
|
+
response.raise_for_status()
|
|
33
|
+
data: dict = await response.json()
|
|
34
|
+
return data.get("data")
|
|
35
|
+
|
|
36
|
+
async def ContentFile(self, url: str) -> bytes:
|
|
37
|
+
await self.start()
|
|
38
|
+
async with self.session.get(url) as response:
|
|
39
|
+
response.raise_for_status()
|
|
40
|
+
return await response.read()
|
|
41
|
+
|
|
42
|
+
async def RequestUploadFile(self, upload_url: str, file: str, name: str):
|
|
43
|
+
if isinstance(file, str):
|
|
44
|
+
if re.match(r"^https?://", file):
|
|
45
|
+
file = await self.ContentFile(file)
|
|
46
|
+
elif os.path.isfile(file):
|
|
47
|
+
async with aiofiles.open(file, "rb") as f:
|
|
48
|
+
file = await f.read()
|
|
49
|
+
else:
|
|
50
|
+
raise Exception("file not found : {}".format(file))
|
|
51
|
+
|
|
52
|
+
form = FormData().add_field("file", filename=name, content_type="application/octet-stream")
|
|
53
|
+
await self.start()
|
|
54
|
+
async with self.session.post(upload_url, data=form) as response:
|
|
18
55
|
response.raise_for_status()
|
|
19
56
|
data: dict = await response.json()
|
|
20
|
-
return data.get("data")
|
|
57
|
+
return data.get("data", {}).get("file_id")
|
|
21
58
|
|
|
22
|
-
|
|
23
|
-
async with ClientSession() as session:
|
|
24
|
-
async with session.get(url) as response:
|
|
25
|
-
response.raise_for_status()
|
|
26
|
-
return await response.read()
|
|
27
|
-
|
|
28
|
-
async def request_upload_file(self, upload_url: str, file: Union[str], name: str) -> str:
|
|
29
|
-
if isinstance(file, str) and re.match(r"^https?://", file):
|
|
30
|
-
file = await self.request_bytes_file(file)
|
|
31
|
-
elif isinstance(file, str) and os.path.isfile(file):
|
|
32
|
-
async with aiofiles.open(file, "rb") as f:
|
|
33
|
-
file = await f.read()
|
|
34
|
-
|
|
35
|
-
form = FormData()
|
|
36
|
-
form.add_field("file", file, filename=name, content_type="application/octet-stream")
|
|
37
|
-
async with ClientSession() as session:
|
|
38
|
-
async with session.post(upload_url, data=form) as response:
|
|
39
|
-
response.raise_for_status()
|
|
40
|
-
data = await response.json()
|
|
41
|
-
return data["data"]["file_id"]
|
|
59
|
+
raise Exception("Format Of file is invalid")
|
|
42
60
|
|
|
43
|
-
async def
|
|
44
|
-
file = await self.
|
|
61
|
+
async def RequestDownloadFile(self, url: str, name: str):
|
|
62
|
+
file = await self.ContentFile(url)
|
|
45
63
|
async with aiofiles.open(name, "wb") as f:
|
|
46
64
|
await f.write(file)
|
|
47
|
-
|
|
65
|
+
return name
|
rubigram/types.py
CHANGED
|
@@ -334,6 +334,7 @@ class Message(Dict):
|
|
|
334
334
|
|
|
335
335
|
@dataclass
|
|
336
336
|
class InlineMessage(Dict):
|
|
337
|
+
client: Optional["rubigram.Client"] = None
|
|
337
338
|
sender_id: Optional[str] = None
|
|
338
339
|
text: Optional[str] = None
|
|
339
340
|
message_id: Optional[str] = None
|
|
@@ -393,7 +394,7 @@ class Update(Dict):
|
|
|
393
394
|
updated_message: Optional[Message] = None
|
|
394
395
|
updated_payment: Optional[PaymentStatus] = None
|
|
395
396
|
|
|
396
|
-
def
|
|
397
|
+
def __str__(self):
|
|
397
398
|
return self.to_json()
|
|
398
399
|
|
|
399
400
|
async def send_text(self, text: str,) -> "MessageId":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: RubigramClient
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.9
|
|
4
4
|
Summary: A simple and flexible Python library for building advanced Rubika bots with powerful message handling, inline buttons, and custom filters.
|
|
5
5
|
Author-email: Javad RZ <Javad.Py1385@gmail.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -28,7 +28,7 @@ from rubigram.types import Update
|
|
|
28
28
|
|
|
29
29
|
bot = Client("your_bot_token", "you_endpoint_url")
|
|
30
30
|
|
|
31
|
-
@bot.on_message(filters.command("start"))
|
|
31
|
+
@bot.on_message(filters.command("start") & filters.private)
|
|
32
32
|
async def start_handler(client, message: Update):
|
|
33
33
|
await message.reply("Hi, WELCOME TO RUBIGRAM")
|
|
34
34
|
|
|
@@ -68,3 +68,18 @@ async def button(_, message: InlineMessage):
|
|
|
68
68
|
|
|
69
69
|
bot.run()
|
|
70
70
|
```
|
|
71
|
+
|
|
72
|
+
## Contex Manager
|
|
73
|
+
```python
|
|
74
|
+
from rubigram import Client
|
|
75
|
+
import asyncio
|
|
76
|
+
|
|
77
|
+
bot = Client("bot_token")
|
|
78
|
+
|
|
79
|
+
async def main():
|
|
80
|
+
async with bot:
|
|
81
|
+
data = await bot.get_me()
|
|
82
|
+
print(data.bot_id)
|
|
83
|
+
|
|
84
|
+
asyncio.run(main())
|
|
85
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
rubigram/__init__.py,sha256=Z0foTm6Hpy4CDEH5am-IFWifi0bo4jLvpkfj3JQQHPc,107
|
|
2
|
+
rubigram/client.py,sha256=YV4epKXNH-WaqtioIuNdelDfO304Qk2MnSWJZie8bLE,6140
|
|
3
|
+
rubigram/filters.py,sha256=YS1fUmtZON8XTMc6IwzXUH3Qw58uMhXD9cbTeP8cjr4,5750
|
|
4
|
+
rubigram/method.py,sha256=bPK2VMtJtRnrN-wrEXH7ijcPzovm_pqFObmViNfWUHM,10364
|
|
5
|
+
rubigram/network.py,sha256=Dy2M4NSdN2iaNP0wwlmVcS408Omq6QhiWUWExrp9Ers,2340
|
|
6
|
+
rubigram/types.py,sha256=QJ-nzd0HNhxCwhCOC6_fuBmJBG2M6Cg9oWH9sIFRNdA,13603
|
|
7
|
+
rubigramclient-1.4.9.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
rubigramclient-1.4.9.dist-info/METADATA,sha256=uUw_-eLAT3z8PJOXRL_fHsBXbBcoXmRD4mJNN9NjllM,2292
|
|
9
|
+
rubigramclient-1.4.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
rubigramclient-1.4.9.dist-info/top_level.txt,sha256=Mhg5HfkL6rLec5sI4ClGmwoqYUANAZUz8sVa1sT_cas,9
|
|
11
|
+
rubigramclient-1.4.9.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
rubigram/__init__.py,sha256=Z0foTm6Hpy4CDEH5am-IFWifi0bo4jLvpkfj3JQQHPc,107
|
|
2
|
-
rubigram/client.py,sha256=o-txKYvZJ8nyGeLTYXCUWjjSAHBySh9Hh4G7F3sUF_U,6242
|
|
3
|
-
rubigram/filters.py,sha256=1g1B9wQHOCSxql10g2qjj7SNwstkEk2euJSL5RKgEKE,5744
|
|
4
|
-
rubigram/method.py,sha256=pKqpQY4ALrhPlJVtE4zBW2hZ659U8Fof7Sb5lAn0BtM,10404
|
|
5
|
-
rubigram/network.py,sha256=BbHXfLxA8-GqTbZBWfJ3A_F8PYVSRydWKIOEC1J2gO8,2024
|
|
6
|
-
rubigram/types.py,sha256=PIbj-unWGMQMGyT6eG6VQvSHzx0TWoQOc_Aq2PlSlK8,13556
|
|
7
|
-
rubigramclient-1.4.7.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
rubigramclient-1.4.7.dist-info/METADATA,sha256=DFLhWJzaU_lwiINX0z7repQKfJqFWrtYsCdx4gHhAPA,2035
|
|
9
|
-
rubigramclient-1.4.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
rubigramclient-1.4.7.dist-info/top_level.txt,sha256=Mhg5HfkL6rLec5sI4ClGmwoqYUANAZUz8sVa1sT_cas,9
|
|
11
|
-
rubigramclient-1.4.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|