RubigramClient 1.3.9__py3-none-any.whl → 1.4.1__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/__init__.py +4 -3
- rubigram/client.py +58 -38
- rubigram/filters.py +46 -6
- rubigram/method.py +188 -41
- rubigram/network.py +53 -25
- rubigram/state.py +30 -0
- rubigram/types.py +322 -332
- rubigramclient-1.4.1.dist-info/METADATA +70 -0
- rubigramclient-1.4.1.dist-info/RECORD +12 -0
- rubigramclient-1.3.9.dist-info/METADATA +0 -35
- rubigramclient-1.3.9.dist-info/RECORD +0 -11
- {rubigramclient-1.3.9.dist-info → rubigramclient-1.4.1.dist-info}/WHEEL +0 -0
- {rubigramclient-1.3.9.dist-info → rubigramclient-1.4.1.dist-info}/licenses/LICENSE +0 -0
- {rubigramclient-1.3.9.dist-info → rubigramclient-1.4.1.dist-info}/top_level.txt +0 -0
rubigram/__init__.py
CHANGED
rubigram/client.py
CHANGED
|
@@ -1,72 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Callable, Awaitable, Optional
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
from functools import wraps
|
|
1
5
|
from rubigram.types import Update, InlineMessage
|
|
2
6
|
from rubigram.method import Method
|
|
3
|
-
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
4
11
|
|
|
5
12
|
|
|
6
13
|
class Client(Method):
|
|
7
|
-
def __init__(
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
token: str,
|
|
17
|
+
endpoint: Optional[str] = None,
|
|
18
|
+
host: str = "0.0.0.0",
|
|
19
|
+
port: int = 8000
|
|
20
|
+
):
|
|
8
21
|
self.token = token
|
|
9
22
|
self.port = port
|
|
10
23
|
self.host = host
|
|
11
24
|
self.endpoint = endpoint
|
|
12
|
-
self.messages_handler = []
|
|
13
|
-
self.inlines_handler = []
|
|
25
|
+
self.messages_handler: list[Callable[[Client, Update], Awaitable]] = []
|
|
26
|
+
self.inlines_handler: list[Callable[[Client, InlineMessage], Awaitable]] = []
|
|
14
27
|
self.routes = web.RouteTableDef()
|
|
15
|
-
self.api = f"https://botapi.rubika.ir/v3/{self.token}/"
|
|
16
28
|
super().__init__(token)
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async def wrapper(client, update):
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
def on_message(self, *filters: Callable[[Update], bool]):
|
|
31
|
+
def decorator(func: Callable[[Client, Update], Awaitable]):
|
|
32
|
+
@wraps(func)
|
|
33
|
+
async def wrapper(client: Client, update: Update):
|
|
34
|
+
try:
|
|
35
|
+
if all(f(update) for f in filters):
|
|
36
|
+
await func(client, update)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.exception(f"Error in message handler {func.__name__}: {e}")
|
|
24
39
|
self.messages_handler.append(wrapper)
|
|
25
40
|
return func
|
|
26
41
|
return decorator
|
|
27
|
-
|
|
28
|
-
def on_inline_message(self, *filters):
|
|
29
|
-
def decorator(func):
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
|
|
43
|
+
def on_inline_message(self, *filters: Callable[[InlineMessage], bool]):
|
|
44
|
+
def decorator(func: Callable[[Client, InlineMessage], Awaitable]):
|
|
45
|
+
@wraps(func)
|
|
46
|
+
async def wrapper(client: Client, update: InlineMessage):
|
|
47
|
+
try:
|
|
48
|
+
if all(f(update) for f in filters):
|
|
49
|
+
await func(client, update)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.exception(f"Error in inline handler {func.__name__}: {e}")
|
|
33
52
|
self.inlines_handler.append(wrapper)
|
|
34
53
|
return func
|
|
35
54
|
return decorator
|
|
36
|
-
|
|
37
|
-
async def
|
|
55
|
+
|
|
56
|
+
async def handle_update(self, data: dict):
|
|
38
57
|
if "inline_message" in data:
|
|
39
|
-
event = InlineMessage.
|
|
40
|
-
for
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
event =
|
|
44
|
-
for
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
event = InlineMessage.from_dict(data["inline_message"])
|
|
59
|
+
await asyncio.gather(*(h(self, event) for h in self.inlines_handler))
|
|
60
|
+
elif "update" in data:
|
|
61
|
+
event = Update.from_dict(data["update"])
|
|
62
|
+
event.client = self
|
|
63
|
+
await asyncio.gather(*(h(self, event) for h in self.messages_handler))
|
|
64
|
+
|
|
65
|
+
|
|
47
66
|
async def set_endpoints(self):
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
if not self.endpoint:
|
|
68
|
+
return
|
|
69
|
+
await self.update_bot_endpoint(f"{self.endpoint}/ReceiveUpdate", "ReceiveUpdate")
|
|
70
|
+
await self.update_bot_endpoint(f"{self.endpoint}/ReceiveInlineMessage", "ReceiveInlineMessage")
|
|
50
71
|
|
|
51
72
|
def run(self):
|
|
52
73
|
@self.routes.post("/ReceiveUpdate")
|
|
53
|
-
async def receive_update(request):
|
|
74
|
+
async def receive_update(request: web.Request):
|
|
54
75
|
data = await request.json()
|
|
55
|
-
await self.
|
|
76
|
+
await self.handle_update(data)
|
|
56
77
|
return web.json_response({"status": "OK"})
|
|
57
78
|
|
|
58
79
|
@self.routes.post("/ReceiveInlineMessage")
|
|
59
|
-
async def receive_inline_message(request):
|
|
80
|
+
async def receive_inline_message(request: web.Request):
|
|
60
81
|
data = await request.json()
|
|
61
|
-
await self.
|
|
62
|
-
return web.json_response({"status": "
|
|
82
|
+
await self.handle_update(data)
|
|
83
|
+
return web.json_response({"status": "OK"})
|
|
63
84
|
|
|
64
85
|
app = web.Application()
|
|
65
86
|
app.add_routes(self.routes)
|
|
66
87
|
|
|
67
|
-
async def on_startup(
|
|
68
|
-
|
|
69
|
-
await self.set_endpoints()
|
|
88
|
+
async def on_startup(_):
|
|
89
|
+
await self.set_endpoints()
|
|
70
90
|
|
|
71
91
|
app.on_startup.append(on_startup)
|
|
72
|
-
web.run_app(app, host
|
|
92
|
+
web.run_app(app, host=self.host, port=self.port)
|
rubigram/filters.py
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
from rubigram.types import Update, InlineMessage
|
|
2
|
+
from rubigram.state import state_manager
|
|
2
3
|
from typing import Union
|
|
3
4
|
import re
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
def state(states: Union[str, list[str]]):
|
|
8
|
+
def filter(message: Union[Update, InlineMessage]):
|
|
9
|
+
user_state = state_manager.get_state(message.chat_id)
|
|
10
|
+
return user_state in states if isinstance(states, list) else user_state == states
|
|
11
|
+
return filter
|
|
12
|
+
|
|
13
|
+
def edited():
|
|
6
14
|
def filter(message: Update):
|
|
7
|
-
if isinstance(message, Update) and message.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
if isinstance(message, Update) and message.updated_message:
|
|
16
|
+
return message.updated_message.is_edited
|
|
17
|
+
return False
|
|
18
|
+
return filter
|
|
19
|
+
|
|
20
|
+
def command(commands: Union[str, list[str]], prefix: str = "/"):
|
|
21
|
+
def filter(message: Update):
|
|
22
|
+
if isinstance(message, Update) and message.new_message and message.new_message.text:
|
|
23
|
+
text = message.new_message.text.strip()
|
|
24
|
+
cmds = commands if isinstance(commands, list) else [commands]
|
|
25
|
+
for cmd in cmds:
|
|
26
|
+
if text.lower().startswith(prefix + cmd.lower()):
|
|
12
27
|
return True
|
|
13
28
|
return False
|
|
14
29
|
return filter
|
|
@@ -62,6 +77,7 @@ def private():
|
|
|
62
77
|
return False
|
|
63
78
|
return filter
|
|
64
79
|
|
|
80
|
+
|
|
65
81
|
def forward():
|
|
66
82
|
def filter(message: Update):
|
|
67
83
|
if isinstance(message, Update) and message.type == "NewMessage":
|
|
@@ -69,6 +85,30 @@ def forward():
|
|
|
69
85
|
return False
|
|
70
86
|
return filter
|
|
71
87
|
|
|
88
|
+
def forward_channel():
|
|
89
|
+
def filter(message: Update):
|
|
90
|
+
if isinstance(message, Update) and message.type == "NewMessage" and message.new_message.forwarded_from:
|
|
91
|
+
return message.new_message.forwarded_from.from_sender_id == "Channel"
|
|
92
|
+
return False
|
|
93
|
+
return filter
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def forward_user():
|
|
97
|
+
def filter(message: Update):
|
|
98
|
+
if isinstance(message, Update) and message.type == "NewMessage" and message.new_message.forwarded_from:
|
|
99
|
+
return message.new_message.forwarded_from.from_sender_id == "User"
|
|
100
|
+
return False
|
|
101
|
+
return filter
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def forward_bot():
|
|
105
|
+
def filter(message: Update):
|
|
106
|
+
if isinstance(message, Update) and message.type == "NewMessage" and message.new_message.forwarded_from:
|
|
107
|
+
return message.new_message.forwarded_from.from_sender_id == "Bot"
|
|
108
|
+
return False
|
|
109
|
+
return filter
|
|
110
|
+
|
|
111
|
+
|
|
72
112
|
def location():
|
|
73
113
|
def filter(message: Update):
|
|
74
114
|
if isinstance(message, Update) and message.type == "NewMessage":
|
rubigram/method.py
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
from rubigram.network import
|
|
1
|
+
from rubigram.network import Network
|
|
2
2
|
from typing import Literal, Optional
|
|
3
3
|
from rubigram.types import Bot, Chat, Update, Keypad, MessageId
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
class Method(Network):
|
|
6
7
|
def __init__(self, token: str):
|
|
7
8
|
super().__init__(token)
|
|
8
9
|
|
|
9
10
|
async def get_me(self) -> "Bot":
|
|
10
11
|
response = await self.request("getMe", {})
|
|
11
|
-
return Bot.
|
|
12
|
-
|
|
12
|
+
return Bot.from_dict(response["bot"])
|
|
13
|
+
|
|
13
14
|
async def get_chat(self, chat_id: str) -> "Chat":
|
|
14
15
|
response = await self.request("getChat", {"chat_id": chat_id})
|
|
15
|
-
return Chat.
|
|
16
|
+
return Chat.from_dict(response["chat"])
|
|
16
17
|
|
|
17
18
|
async def get_update(self, limit: int = 1, offset_id: Optional[int] = None) -> list[Update]:
|
|
18
19
|
response = await self.request("getUpdates", {"limit": limit, "offset_id": offset_id})
|
|
19
|
-
return [Update.
|
|
20
|
+
return [Update.from_dict(update) for update in response["updates"]]
|
|
20
21
|
|
|
21
22
|
async def get_file(self, file_id: str) -> str:
|
|
22
23
|
response = await self.request("getFile", {"file_id": file_id})
|
|
23
|
-
return response["
|
|
24
|
+
return response["download_url"]
|
|
24
25
|
|
|
25
26
|
async def set_command(self, command: list):
|
|
26
27
|
response = await self.request("setCommands", {"bot_commands": command})
|
|
@@ -30,19 +31,10 @@ class Method(NetWork):
|
|
|
30
31
|
response = await self.request("updateBotEndpoints", {"url": url, "type": type})
|
|
31
32
|
return response
|
|
32
33
|
|
|
33
|
-
async def request_send_file(self, type: str):
|
|
34
|
-
response = await self.request("requestSendFile", {"type": type})
|
|
35
|
-
return response["data"]["upload_url"]
|
|
36
|
-
|
|
37
|
-
async def upload_file(self, path: str, name: str, type: str):
|
|
38
|
-
upload_url = await self.request_send_file(type)
|
|
39
|
-
response = await self.request_upload_file(upload_url, path, name)
|
|
40
|
-
return response
|
|
41
|
-
|
|
42
34
|
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> "MessageId":
|
|
43
35
|
data = {"from_chat_id": from_chat_id, "message_id": message_id, "to_chat_id": to_chat_id, "disable_notification": disable_notification}
|
|
44
36
|
response = await self.request("forwardMessage", data)
|
|
45
|
-
return MessageId.
|
|
37
|
+
return MessageId.from_dict(response)
|
|
46
38
|
|
|
47
39
|
async def delete_message(self, chat_id: str, message_id: str):
|
|
48
40
|
await self.request("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
@@ -72,14 +64,14 @@ class Method(NetWork):
|
|
|
72
64
|
data = {
|
|
73
65
|
"chat_id": chat_id,
|
|
74
66
|
"text": text,
|
|
75
|
-
"chat_keypad": chat_keypad.
|
|
76
|
-
"inline_keypad": inline_keypad.
|
|
67
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
68
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
77
69
|
"chat_keypad_type": chat_keypad_type,
|
|
78
70
|
"disable_notification": disable_notification,
|
|
79
71
|
"reply_to_message_id": reply_to_message_id
|
|
80
72
|
}
|
|
81
73
|
response = await self.request("sendMessage", data)
|
|
82
|
-
return MessageId.
|
|
74
|
+
return MessageId.from_dict(response)
|
|
83
75
|
|
|
84
76
|
async def send_poll(
|
|
85
77
|
self,
|
|
@@ -96,14 +88,14 @@ class Method(NetWork):
|
|
|
96
88
|
"chat_id": chat_id,
|
|
97
89
|
"question": question,
|
|
98
90
|
"options": options,
|
|
99
|
-
"chat_keypad": chat_keypad.
|
|
100
|
-
"inline_keypad": inline_keypad.
|
|
91
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
92
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
101
93
|
"disable_notification": disable_notification,
|
|
102
94
|
"reply_to_message_id": reply_to_message_id,
|
|
103
95
|
"chat_keypad_type": chat_keypad_type
|
|
104
96
|
}
|
|
105
97
|
response = await self.request("sendPoll", data)
|
|
106
|
-
return MessageId.
|
|
98
|
+
return MessageId.from_dict(response)
|
|
107
99
|
|
|
108
100
|
async def send_location(
|
|
109
101
|
self,
|
|
@@ -120,14 +112,14 @@ class Method(NetWork):
|
|
|
120
112
|
"chat_id": chat_id,
|
|
121
113
|
"latitude": latitude,
|
|
122
114
|
"longitude": longitude,
|
|
123
|
-
"chat_keypad": chat_keypad.
|
|
124
|
-
"inline_keypad": inline_keypad.
|
|
115
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
116
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
125
117
|
"disable_notification": disable_notification,
|
|
126
118
|
"reply_to_message_id": reply_to_message_id,
|
|
127
119
|
"chat_keypad_type": chat_keypad_type
|
|
128
120
|
}
|
|
129
121
|
response = await self.request("sendLocation", data)
|
|
130
|
-
return MessageId.
|
|
122
|
+
return MessageId.from_dict(response)
|
|
131
123
|
|
|
132
124
|
async def send_contact(
|
|
133
125
|
self,
|
|
@@ -146,14 +138,14 @@ class Method(NetWork):
|
|
|
146
138
|
"first_name": first_name,
|
|
147
139
|
"last_name": last_name,
|
|
148
140
|
"phone_number": phone_number,
|
|
149
|
-
"chat_keypad": chat_keypad.
|
|
150
|
-
"inline_keypad": inline_keypad.
|
|
141
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
142
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
151
143
|
"disable_notification": disable_notification,
|
|
152
144
|
"reply_to_message_id": reply_to_message_id,
|
|
153
145
|
"chat_keypad_type": chat_keypad_type
|
|
154
146
|
}
|
|
155
147
|
response = await self.request("sendContact", data)
|
|
156
|
-
return MessageId.
|
|
148
|
+
return MessageId.from_dict(response)
|
|
157
149
|
|
|
158
150
|
async def send_sticker(
|
|
159
151
|
self,
|
|
@@ -168,41 +160,196 @@ class Method(NetWork):
|
|
|
168
160
|
data = {
|
|
169
161
|
"chat_id": chat_id,
|
|
170
162
|
"sticker_id": sticker_id,
|
|
171
|
-
"chat_keypad": chat_keypad.
|
|
172
|
-
"inline_keypad": inline_keypad.
|
|
163
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
164
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
173
165
|
"disable_notification": disable_notification,
|
|
174
166
|
"reply_to_message_id": reply_to_message_id,
|
|
175
167
|
"chat_keypad_type": chat_keypad_type
|
|
176
168
|
}
|
|
177
169
|
response = await self.request("sendSticker", data)
|
|
178
|
-
return MessageId.
|
|
170
|
+
return MessageId.from_dict(response)
|
|
171
|
+
|
|
172
|
+
async def request_send_file(self, type: str):
|
|
173
|
+
response = await self.request("requestSendFile", {"type": type})
|
|
174
|
+
return response["upload_url"]
|
|
175
|
+
|
|
176
|
+
async def upload_file(self, file: str, name: str, type: str):
|
|
177
|
+
upload_url = await self.request_send_file(type)
|
|
178
|
+
response = await self.request_upload_file(upload_url, file, name)
|
|
179
|
+
return response
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def download_file(self, file_id: str, file_name: str):
|
|
183
|
+
download_url = await self.get_file(file_id)
|
|
184
|
+
response = await self.request_download_file(download_url, file_name)
|
|
185
|
+
return response
|
|
179
186
|
|
|
180
187
|
async def send_file(
|
|
181
188
|
self,
|
|
182
189
|
chat_id: str,
|
|
183
|
-
|
|
190
|
+
file: str,
|
|
184
191
|
file_name: str,
|
|
185
192
|
type: Literal["File", "Image", "Voice", "Music", "Gif", "Video"] = "File",
|
|
186
193
|
chat_keypad: Keypad = None,
|
|
187
194
|
inline_keypad: Keypad = None,
|
|
195
|
+
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
188
196
|
disable_notification: bool = False,
|
|
189
197
|
reply_to_message_id: str = None,
|
|
198
|
+
) -> "MessageId":
|
|
199
|
+
file_id = await self.upload_file(file, file_name, type)
|
|
200
|
+
data = {
|
|
201
|
+
"chat_id": chat_id,
|
|
202
|
+
"file_id": file_id,
|
|
203
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
204
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
205
|
+
"disable_notification": disable_notification,
|
|
206
|
+
"reply_to_message_id": reply_to_message_id,
|
|
207
|
+
"chat_keypad_type": chat_keypad_type,
|
|
208
|
+
}
|
|
209
|
+
response = await self.request("sendFile", data)
|
|
210
|
+
return MessageId.from_dict(response)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def send_document(
|
|
214
|
+
self,
|
|
215
|
+
chat_id: str,
|
|
216
|
+
document: str,
|
|
217
|
+
name: str,
|
|
218
|
+
chat_keypad: Keypad = None,
|
|
219
|
+
inline_keypad: Keypad = None,
|
|
190
220
|
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
221
|
+
disable_notification: bool = False,
|
|
222
|
+
reply_to_message_id: str = None,
|
|
191
223
|
) -> "MessageId":
|
|
192
|
-
file_id = await self.upload_file(
|
|
224
|
+
file_id = await self.upload_file(document, name, "File")
|
|
193
225
|
data = {
|
|
194
226
|
"chat_id": chat_id,
|
|
195
227
|
"file_id": file_id,
|
|
196
|
-
"chat_keypad": chat_keypad.
|
|
197
|
-
"inline_keypad": inline_keypad.
|
|
228
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
229
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
198
230
|
"disable_notification": disable_notification,
|
|
199
231
|
"reply_to_message_id": reply_to_message_id,
|
|
200
232
|
"chat_keypad_type": chat_keypad_type,
|
|
201
233
|
}
|
|
202
234
|
response = await self.request("sendFile", data)
|
|
203
|
-
return MessageId.
|
|
235
|
+
return MessageId.from_dict(response)
|
|
204
236
|
|
|
205
|
-
async def
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
237
|
+
async def send_photo(
|
|
238
|
+
self,
|
|
239
|
+
chat_id: str,
|
|
240
|
+
photo: str,
|
|
241
|
+
name: str,
|
|
242
|
+
chat_keypad: Keypad = None,
|
|
243
|
+
inline_keypad: Keypad = None,
|
|
244
|
+
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
245
|
+
disable_notification: bool = False,
|
|
246
|
+
reply_to_message_id: str = None,
|
|
247
|
+
) -> "MessageId":
|
|
248
|
+
file_id = await self.upload_file(photo, name, "Image")
|
|
249
|
+
data = {
|
|
250
|
+
"chat_id": chat_id,
|
|
251
|
+
"file_id": file_id,
|
|
252
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
253
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
254
|
+
"disable_notification": disable_notification,
|
|
255
|
+
"reply_to_message_id": reply_to_message_id,
|
|
256
|
+
"chat_keypad_type": chat_keypad_type,
|
|
257
|
+
}
|
|
258
|
+
response = await self.request("sendFile", data)
|
|
259
|
+
return MessageId.from_dict(response)
|
|
260
|
+
|
|
261
|
+
async def send_video(
|
|
262
|
+
self,
|
|
263
|
+
chat_id: str,
|
|
264
|
+
video: str,
|
|
265
|
+
name: str,
|
|
266
|
+
chat_keypad: Keypad = None,
|
|
267
|
+
inline_keypad: Keypad = None,
|
|
268
|
+
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
269
|
+
disable_notification: bool = False,
|
|
270
|
+
reply_to_message_id: str = None,
|
|
271
|
+
) -> "MessageId":
|
|
272
|
+
file_id = await self.upload_file(video, name, "Video")
|
|
273
|
+
data = {
|
|
274
|
+
"chat_id": chat_id,
|
|
275
|
+
"file_id": file_id,
|
|
276
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
277
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
278
|
+
"disable_notification": disable_notification,
|
|
279
|
+
"reply_to_message_id": reply_to_message_id,
|
|
280
|
+
"chat_keypad_type": chat_keypad_type,
|
|
281
|
+
}
|
|
282
|
+
response = await self.request("sendFile", data)
|
|
283
|
+
return MessageId.from_dict(response)
|
|
284
|
+
|
|
285
|
+
async def send_gif(
|
|
286
|
+
self,
|
|
287
|
+
chat_id: str,
|
|
288
|
+
gif: str,
|
|
289
|
+
name: str,
|
|
290
|
+
chat_keypad: Keypad = None,
|
|
291
|
+
inline_keypad: Keypad = None,
|
|
292
|
+
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
293
|
+
disable_notification: bool = False,
|
|
294
|
+
reply_to_message_id: str = None,
|
|
295
|
+
) -> "MessageId":
|
|
296
|
+
file_id = await self.upload_file(gif, name, "Gif")
|
|
297
|
+
data = {
|
|
298
|
+
"chat_id": chat_id,
|
|
299
|
+
"file_id": file_id,
|
|
300
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
301
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
302
|
+
"disable_notification": disable_notification,
|
|
303
|
+
"reply_to_message_id": reply_to_message_id,
|
|
304
|
+
"chat_keypad_type": chat_keypad_type,
|
|
305
|
+
}
|
|
306
|
+
response = await self.request("sendFile", data)
|
|
307
|
+
return MessageId.from_dict(response)
|
|
308
|
+
|
|
309
|
+
async def send_music(
|
|
310
|
+
self,
|
|
311
|
+
chat_id: str,
|
|
312
|
+
music: str,
|
|
313
|
+
name: str,
|
|
314
|
+
chat_keypad: Keypad = None,
|
|
315
|
+
inline_keypad: Keypad = None,
|
|
316
|
+
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
317
|
+
disable_notification: bool = False,
|
|
318
|
+
reply_to_message_id: str = None,
|
|
319
|
+
) -> "MessageId":
|
|
320
|
+
file_id = await self.upload_file(music, name, "Music")
|
|
321
|
+
data = {
|
|
322
|
+
"chat_id": chat_id,
|
|
323
|
+
"file_id": file_id,
|
|
324
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
325
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
326
|
+
"disable_notification": disable_notification,
|
|
327
|
+
"reply_to_message_id": reply_to_message_id,
|
|
328
|
+
"chat_keypad_type": chat_keypad_type,
|
|
329
|
+
}
|
|
330
|
+
response = await self.request("sendFile", data)
|
|
331
|
+
return MessageId.from_dict(response)
|
|
332
|
+
|
|
333
|
+
async def send_voice(
|
|
334
|
+
self,
|
|
335
|
+
chat_id: str,
|
|
336
|
+
voice: str,
|
|
337
|
+
name: str,
|
|
338
|
+
chat_keypad: Keypad = None,
|
|
339
|
+
inline_keypad: Keypad = None,
|
|
340
|
+
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
341
|
+
disable_notification: bool = False,
|
|
342
|
+
reply_to_message_id: str = None,
|
|
343
|
+
) -> "MessageId":
|
|
344
|
+
file_id = await self.upload_file(voice, name, "Voice")
|
|
345
|
+
data = {
|
|
346
|
+
"chat_id": chat_id,
|
|
347
|
+
"file_id": file_id,
|
|
348
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
349
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
350
|
+
"disable_notification": disable_notification,
|
|
351
|
+
"reply_to_message_id": reply_to_message_id,
|
|
352
|
+
"chat_keypad_type": chat_keypad_type,
|
|
353
|
+
}
|
|
354
|
+
response = await self.request("sendFile", data)
|
|
355
|
+
return MessageId.from_dict(response)
|
rubigram/network.py
CHANGED
|
@@ -1,30 +1,58 @@
|
|
|
1
1
|
from aiohttp import ClientSession, FormData
|
|
2
|
+
from typing import Any, Optional, Dict, Union
|
|
2
3
|
import aiofiles
|
|
4
|
+
import re
|
|
5
|
+
import os
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
self.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
|
|
8
|
+
class Network:
|
|
9
|
+
def __init__(self, token: str) -> None:
|
|
10
|
+
self.token: str = token
|
|
11
|
+
self.session: Optional[ClientSession] = None
|
|
12
|
+
self.api: str = f"https://botapi.rubika.ir/v3/{self.token}/"
|
|
13
|
+
|
|
14
|
+
async def get_session(self) -> ClientSession:
|
|
15
|
+
if self.session is None or self.session.closed:
|
|
16
|
+
self.session = ClientSession()
|
|
17
|
+
return self.session
|
|
18
|
+
|
|
19
|
+
async def close(self) -> None:
|
|
20
|
+
if self.session and not self.session.closed:
|
|
21
|
+
await self.session.close()
|
|
22
|
+
|
|
23
|
+
async def request(self, method: str, json: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
24
|
+
session = await self.get_session()
|
|
25
|
+
async with session.post(self.api + method, json=json) as response:
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
data: dict = await response.json()
|
|
28
|
+
return data.get("data")
|
|
29
|
+
|
|
30
|
+
async def request_bytes_file(self, url: str) -> bytes:
|
|
31
|
+
session = await self.get_session()
|
|
32
|
+
async with session.get(url) as response:
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
return await response.read()
|
|
35
|
+
|
|
36
|
+
async def request_upload_file(self, upload_url: str, file: Union[str], name: str) -> str:
|
|
37
|
+
session = await self.get_session()
|
|
38
|
+
|
|
39
|
+
if isinstance(file, str) and re.match(r"^https?://", file):
|
|
40
|
+
file = await self.request_bytes_file(file)
|
|
14
41
|
|
|
15
|
-
|
|
42
|
+
elif isinstance(file, str) and os.path.isfile(file):
|
|
43
|
+
async with aiofiles.open(file, "rb") as f:
|
|
44
|
+
file = await f.read()
|
|
45
|
+
|
|
16
46
|
form = FormData()
|
|
17
|
-
form.add_field("file",
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async def request_download_file(self, url: str, name: str):
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
await file.write(await response.read())
|
|
30
|
-
return {"status": True, "file": name}
|
|
47
|
+
form.add_field("file", file, filename=name, content_type="application/octet-stream")
|
|
48
|
+
|
|
49
|
+
async with session.post(upload_url, data=form) as response:
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
data = await response.json()
|
|
52
|
+
return data["data"]["file_id"]
|
|
53
|
+
|
|
54
|
+
async def request_download_file(self, url: str, name: str) -> dict[str, Union[str, bool]]:
|
|
55
|
+
file = await self.request_bytes_file(url)
|
|
56
|
+
async with aiofiles.open(name, "wb") as f:
|
|
57
|
+
await f.write(file)
|
|
58
|
+
return {"status": True, "file": name}
|
rubigram/state.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Any, Union, Optional
|
|
2
|
+
|
|
3
|
+
class StateManager:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self.states: dict[str, Any] = {}
|
|
6
|
+
self.datas: dict[str, dict[str, Any]] = {}
|
|
7
|
+
|
|
8
|
+
def set_state(self, user_id: str, state: Any):
|
|
9
|
+
self.states[user_id] = state
|
|
10
|
+
|
|
11
|
+
def get_state(self, user_id: str):
|
|
12
|
+
return self.states.get(user_id)
|
|
13
|
+
|
|
14
|
+
def clear_state(self, user_id: str):
|
|
15
|
+
self.states.pop(user_id, None)
|
|
16
|
+
|
|
17
|
+
def set_data(self, user_id: str, **data):
|
|
18
|
+
if user_id not in self.datas:
|
|
19
|
+
self.datas[user_id] = {}
|
|
20
|
+
self.datas[user_id].update(data)
|
|
21
|
+
|
|
22
|
+
def get_data(self, user_id: str, key: Optional[Union[str, int]] = None):
|
|
23
|
+
if key is not None:
|
|
24
|
+
return self.datas.get(user_id, {}).get(key)
|
|
25
|
+
return self.datas.get(user_id)
|
|
26
|
+
|
|
27
|
+
def clear_data(self, user_id: str):
|
|
28
|
+
self.datas.pop(user_id, None)
|
|
29
|
+
|
|
30
|
+
state_manager = StateManager()
|