RubigramClient 1.4.1__tar.gz → 1.4.2__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.

Potentially problematic release.


This version of RubigramClient might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RubigramClient
3
- Version: 1.4.1
3
+ Version: 1.4.2
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RubigramClient
3
- Version: 1.4.1
3
+ Version: 1.4.2
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
@@ -11,5 +11,4 @@ rubigram/client.py
11
11
  rubigram/filters.py
12
12
  rubigram/method.py
13
13
  rubigram/network.py
14
- rubigram/state.py
15
14
  rubigram/types.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "RubigramClient"
3
- version = "1.4.1"
3
+ version = "1.4.2"
4
4
  description = "A simple and flexible Python library for building advanced Rubika bots with powerful message handling, inline buttons, and custom filters."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.7"
@@ -1,5 +1,4 @@
1
1
  from .network import Network
2
2
  from .method import Method
3
3
  from .client import Client
4
- from . import filters
5
- from . import state
4
+ from . import filters
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+ from typing import Optional, Callable, Awaitable
3
+ from aiohttp import web
4
+ from functools import wraps
5
+ from rubigram.types import Update, InlineMessage
6
+ from rubigram.method import Method
7
+ from rubigram.filters import Filter
8
+ from datetime import datetime
9
+ import asyncio
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class Client(Method):
15
+ def __init__(self, token: str, endpoint: Optional[str] = None, host: str = "0.0.0.0", port: int = 8000):
16
+ self.token = token
17
+ self.endpoint = endpoint
18
+ self.host = host
19
+ self.port = port
20
+ self.offset_id = None
21
+ self.routes = web.RouteTableDef()
22
+ self.message_handlers = []
23
+ self.inline_handlers = []
24
+ super().__init__(token)
25
+
26
+ def on_message(self, *filters: Filter):
27
+ def decorator(func: Callable[[Client, Update], Awaitable]):
28
+ @wraps(func)
29
+ async def wrapper(client: Client, update: Update):
30
+ try:
31
+ combined_filter = filters[0] if filters else None
32
+ for filter in filters[1:]:
33
+ combined_filter = combined_filter & filter
34
+
35
+ if combined_filter is None or await combined_filter(update):
36
+ await func(client, update)
37
+ return True
38
+ return False
39
+ except Exception as e:
40
+ logger.exception(f"Error in message handler {func.__name__}: {e}")
41
+ return False
42
+ self.message_handlers.append(wrapper)
43
+ return func
44
+ return decorator
45
+
46
+
47
+ def on_inline_message(self, *filters: Filter):
48
+ def decorator(func: Callable[[Client, InlineMessage], Awaitable]):
49
+ @wraps(func)
50
+ async def wrapper(client: Client, update: InlineMessage):
51
+ try:
52
+ combined_filter = filters[0] if filters else None
53
+ for filter in filters[1:]:
54
+ combined_filter = combined_filter & filter
55
+
56
+ if combined_filter is None or await combined_filter(update):
57
+ await func(client, update)
58
+ return True
59
+ return False
60
+ except Exception as e:
61
+ logger.exception(f"Error in inline handler {func.__name__}: {e}")
62
+ return False
63
+ self.inline_handlers.append(wrapper)
64
+ return func
65
+ return decorator
66
+
67
+
68
+ async def dispatch_update(self, update: Update):
69
+ for handler in self.message_handlers:
70
+ try:
71
+ matched = await handler(self, update)
72
+ if matched:
73
+ return
74
+ except Exception as error:
75
+ logger.exception(f"Error in handler dispatch: {error}")
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}")
85
+
86
+ async def updater(self, data: dict):
87
+ if "inline_message" in data:
88
+ event = InlineMessage.from_dict(data["inline_message"])
89
+ await self.dispatch_inline(event)
90
+ elif "update" in data:
91
+ event = Update.from_dict(data["update"])
92
+ event.client = self
93
+ await self.dispatch_update(event)
94
+
95
+ async def set_endpoints(self):
96
+ if self.endpoint:
97
+ await self.update_bot_endpoint(f"{self.endpoint}/ReceiveUpdate", "ReceiveUpdate")
98
+ await self.update_bot_endpoint(f"{self.endpoint}/ReceiveInlineMessage", "ReceiveInlineMessage")
99
+
100
+ def run(self):
101
+ if self.endpoint:
102
+ @self.routes.post("/ReceiveUpdate")
103
+ async def receive_update(request: web.Request):
104
+ data = await request.json()
105
+ await self.updater(data)
106
+ return web.json_response({"status": "OK"})
107
+
108
+ @self.routes.post("/ReceiveInlineMessage")
109
+ async def receive_inline_message(request: web.Request):
110
+ data = await request.json()
111
+ await self.updater(data)
112
+ return web.json_response({"status": "OK"})
113
+
114
+ app = web.Application()
115
+ app.add_routes(self.routes)
116
+
117
+ async def on_startup(_):
118
+ await self.set_endpoints()
119
+ app.on_startup.append(on_startup)
120
+ web.run_app(app, host=self.host, port=self.port)
121
+
122
+ else:
123
+ async def polling():
124
+ while True:
125
+ try:
126
+ get_update = await self.get_update(100, self.offset_id)
127
+ if get_update.updates:
128
+ updates = get_update.updates
129
+ for update in updates:
130
+ if update.type == "NewMessage":
131
+ message_time = int(update.new_message.time)
132
+ elif update.type == "UpdatedMessage":
133
+ message_time = int(update.updated_message.time)
134
+ else:
135
+ continue
136
+
137
+ now = int(datetime.now().timestamp())
138
+ if message_time >= now or message_time + 2 >= now:
139
+ await self.dispatch_update(update)
140
+
141
+ self.offset_id = get_update.next_offset_id
142
+ except Exception as e:
143
+ logger.exception(f"Polling error: {e}")
144
+ await asyncio.sleep(1)
145
+
146
+ asyncio.run(polling())
@@ -0,0 +1,165 @@
1
+ from rubigram.types import Update, InlineMessage
2
+ from typing import Union, Callable, Awaitable
3
+ import re
4
+
5
+
6
+ class Filter:
7
+ def __init__(self, func: Callable[[Union[Update, InlineMessage]], bool | Awaitable[bool]]):
8
+ self.func = func
9
+
10
+ async def __call__(self, update: Union[Update, InlineMessage]) -> bool:
11
+ result = self.func(update)
12
+ if isinstance(result, Awaitable):
13
+ return await result
14
+ return result
15
+
16
+ def __and__(self, other: "Filter"):
17
+ async def combined(update):
18
+ return await self(update) and await other(update)
19
+ return Filter(combined)
20
+
21
+ def __or__(self, other: "Filter"):
22
+ async def combined(update):
23
+ return await self(update) or await other(update)
24
+ return Filter(combined)
25
+
26
+
27
+ class command(Filter):
28
+ def __init__(self, cmd: Union[str, list[str]], prefix: str = "/"):
29
+ self.cmd = cmd
30
+ self.prefix = prefix
31
+ super().__init__(self.filter)
32
+
33
+ def filter(self, update: Update):
34
+ commands = [self.cmd] if isinstance(self.cmd, str) else self.cmd
35
+ text = ""
36
+ if isinstance(update, Update):
37
+ if update.type == "NewMessage":
38
+ text = update.new_message.text
39
+ elif update.type == "UpdatedMessage":
40
+ text = update.updated_message.text
41
+
42
+ for cmd in commands:
43
+ if text.lower().startswith(self.prefix + cmd.lower()):
44
+ return True
45
+ return False
46
+
47
+
48
+ class regex(Filter):
49
+ def __init__(self, pattern: str):
50
+ self.pattern = pattern
51
+ super().__init__(self.filter)
52
+
53
+ def filter(self, update: Union[Update, InlineMessage]):
54
+ text = ""
55
+ if isinstance(update, Update):
56
+ if update.type == "NewMessage":
57
+ text = getattr(update.new_message, "text", "")
58
+ elif update.type == "UpdatedMessage":
59
+ text = getattr(update.updated_message, "text", "")
60
+ elif isinstance(update, InlineMessage):
61
+ text = getattr(update, "text", "")
62
+
63
+ if text:
64
+ return bool(re.search(self.pattern, text))
65
+ return False
66
+
67
+
68
+ class chat(Filter):
69
+ def __init__(self, chat_id: Union[str, list[str]]):
70
+ self.chat_id = chat_id
71
+ super().__init__(self.filter)
72
+
73
+ def filter(self, update: Union[Update, InlineMessage]):
74
+ chat_ids = [self.chat_id] if isinstance(
75
+ self.chat_id, str) else self.chat_id
76
+ return update.chat_id in chat_ids
77
+
78
+
79
+ class button(Filter):
80
+ def __init__(self, button_id: Union[str, list[str]]):
81
+ self.button_id = button_id
82
+ super().__init__(self.filter)
83
+
84
+ def filter(self, update: InlineMessage):
85
+ if isinstance(update, InlineMessage):
86
+ button_ids = [self.button_id] if isinstance(
87
+ self.button_id, str) else self.button_id
88
+ return update.aux_data.button_id in button_ids
89
+
90
+
91
+
92
+ def TEXT(update: Update):
93
+ return bool(update.new_message and getattr(update.new_message, "text", None))
94
+
95
+
96
+ def FILE(update: Update):
97
+ return bool(update.new_message and getattr(update.new_message, "file", None))
98
+
99
+
100
+ def LIVE(update: Update):
101
+ return bool(update.new_message and getattr(update.new_message, "live_location", None))
102
+
103
+
104
+ def POLL(update: Update):
105
+ return bool(update.new_message and getattr(update.new_message, "poll", None))
106
+
107
+
108
+ def CONTACT(update: Update):
109
+ return bool(update.new_message and getattr(update.new_message, "contact_message", None))
110
+
111
+
112
+ def STICKER(update: Update):
113
+ return bool(update.new_message and getattr(update.new_message, "sticker", None))
114
+
115
+
116
+ def LOCATION(update: Update):
117
+ return bool(update.new_message and getattr(update.new_message, "location", None))
118
+
119
+
120
+ def FORWARD(update: Update):
121
+ return bool(update.new_message and getattr(update.new_message, "forwarded_from", None))
122
+
123
+
124
+ def EDITED(update: Update):
125
+ if isinstance(update, Update) and update.type == "UpdatedMessage":
126
+ return update.updated_message.is_edited
127
+
128
+
129
+ def PRIVATE(update: Update):
130
+ if isinstance(update, Update) and update.type == "NewMessage":
131
+ return update.new_message.sender_type in ["User", "Bot"]
132
+ return False
133
+
134
+
135
+ def FORWARD_BOT(update: Update):
136
+ if isinstance(update, Update) and update.type == "NewMessage" and update.new_message.forwarded_from:
137
+ return update.new_message.forwarded_from.type_from == "Bot"
138
+ return False
139
+
140
+
141
+ def FORWARD_USER(update: Update):
142
+ if isinstance(update, Update) and update.type == "NewMessage" and update.new_message.forwarded_from:
143
+ return update.new_message.forwarded_from.type_from == "User"
144
+ return False
145
+
146
+
147
+ def FORWARD_CHANNEL(update: Update):
148
+ if isinstance(update, Update) and update.type == "NewMessage" and update.new_message.forwarded_from:
149
+ return update.new_message.forwarded_from.type_from == "Channel"
150
+ return False
151
+
152
+
153
+ text = Filter(TEXT)
154
+ file = Filter(FILE)
155
+ live = Filter(LIVE)
156
+ poll = Filter(POLL)
157
+ edited = Filter(EDITED)
158
+ contact = Filter(CONTACT)
159
+ sticker = Filter(STICKER)
160
+ location = Filter(LOCATION)
161
+ forward = Filter(FORWARD)
162
+ private = Filter(PRIVATE)
163
+ forward_bot = Filter(FORWARD_BOT)
164
+ forward_user = Filter(FORWARD_USER)
165
+ forward_channel = Filter(FORWARD_CHANNEL)
@@ -1,65 +1,66 @@
1
- from rubigram.network import Network
1
+ from .network import Network
2
2
  from typing import Literal, Optional
3
- from rubigram.types import Bot, Chat, Update, Keypad, MessageId
3
+ from .types import Bot, Chat, Keypad, MessageId, Updates
4
4
 
5
5
 
6
6
  class Method(Network):
7
7
  def __init__(self, token: str):
8
8
  super().__init__(token)
9
-
9
+
10
10
  async def get_me(self) -> "Bot":
11
11
  response = await self.request("getMe", {})
12
12
  return Bot.from_dict(response["bot"])
13
-
13
+
14
14
  async def get_chat(self, chat_id: str) -> "Chat":
15
15
  response = await self.request("getChat", {"chat_id": chat_id})
16
16
  return Chat.from_dict(response["chat"])
17
-
18
- async def get_update(self, limit: int = 1, offset_id: Optional[int] = None) -> list[Update]:
17
+
18
+ async def get_update(self, limit: int = 1, offset_id: Optional[int] = None) -> "Updates":
19
19
  response = await self.request("getUpdates", {"limit": limit, "offset_id": offset_id})
20
- return [Update.from_dict(update) for update in response["updates"]]
21
-
20
+ return Updates.from_dict(response)
21
+
22
22
  async def get_file(self, file_id: str) -> str:
23
23
  response = await self.request("getFile", {"file_id": file_id})
24
24
  return response["download_url"]
25
-
25
+
26
26
  async def set_command(self, command: list):
27
27
  response = await self.request("setCommands", {"bot_commands": command})
28
28
  return response
29
-
29
+
30
30
  async def update_bot_endpoint(self, url: str, type: Literal["ReceiveUpdate", "ReceiveInlineMessage", "ReceiveQuery", "GetSelectionItem", "SearchSelectionItems"]):
31
31
  response = await self.request("updateBotEndpoints", {"url": url, "type": type})
32
32
  return response
33
-
34
- async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> "MessageId":
35
- data = {"from_chat_id": from_chat_id, "message_id": message_id, "to_chat_id": to_chat_id, "disable_notification": disable_notification}
36
- response = await self.request("forwardMessage", data)
37
- return MessageId.from_dict(response)
38
-
33
+
39
34
  async def delete_message(self, chat_id: str, message_id: str):
40
35
  await self.request("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
41
-
36
+
42
37
  async def remove_chat_keypad(self, chat_id: str):
43
38
  await self.request("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Remove"})
44
-
39
+
45
40
  async def edit_chat_keypad(self, chat_id: str, chat_keypad):
46
41
  await self.request("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
47
42
 
48
43
  async def edit_message_keypad(self, chat_id: str, message_id: str, inline_keypad):
49
44
  await self.request("editMessageKeypad", {"chat_id": chat_id, "message_id": message_id, "inline_keypad": inline_keypad})
50
-
45
+
51
46
  async def edit_message_text(self, chat_id: str, message_id: str, text: str):
52
47
  await self.request("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
53
-
48
+
49
+ 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
+ response = await self.request("forwardMessage", data)
53
+ return MessageId.from_dict(response)
54
+
54
55
  async def send_message(
55
56
  self,
56
57
  chat_id: str,
57
58
  text: str,
58
59
  chat_keypad: Keypad = None,
59
- inline_keypad: Keypad= None,
60
+ inline_keypad: Keypad = None,
60
61
  chat_keypad_type: Literal["New", "Remove"] = None,
61
62
  disable_notification: bool = None,
62
- reply_to_message_id = None
63
+ reply_to_message_id=None
63
64
  ) -> "MessageId":
64
65
  data = {
65
66
  "chat_id": chat_id,
@@ -72,7 +73,7 @@ class Method(Network):
72
73
  }
73
74
  response = await self.request("sendMessage", data)
74
75
  return MessageId.from_dict(response)
75
-
76
+
76
77
  async def send_poll(
77
78
  self,
78
79
  chat_id: str,
@@ -96,7 +97,7 @@ class Method(Network):
96
97
  }
97
98
  response = await self.request("sendPoll", data)
98
99
  return MessageId.from_dict(response)
99
-
100
+
100
101
  async def send_location(
101
102
  self,
102
103
  chat_id: str,
@@ -120,7 +121,7 @@ class Method(Network):
120
121
  }
121
122
  response = await self.request("sendLocation", data)
122
123
  return MessageId.from_dict(response)
123
-
124
+
124
125
  async def send_contact(
125
126
  self,
126
127
  chat_id: str,
@@ -146,7 +147,7 @@ class Method(Network):
146
147
  }
147
148
  response = await self.request("sendContact", data)
148
149
  return MessageId.from_dict(response)
149
-
150
+
150
151
  async def send_sticker(
151
152
  self,
152
153
  chat_id: str,
@@ -166,30 +167,31 @@ class Method(Network):
166
167
  "reply_to_message_id": reply_to_message_id,
167
168
  "chat_keypad_type": chat_keypad_type
168
169
  }
170
+
169
171
  response = await self.request("sendSticker", data)
170
172
  return MessageId.from_dict(response)
171
-
173
+
172
174
  async def request_send_file(self, type: str):
173
175
  response = await self.request("requestSendFile", {"type": type})
174
176
  return response["upload_url"]
175
-
177
+
176
178
  async def upload_file(self, file: str, name: str, type: str):
177
179
  upload_url = await self.request_send_file(type)
178
180
  response = await self.request_upload_file(upload_url, file, name)
179
181
  return response
180
-
181
-
182
+
182
183
  async def download_file(self, file_id: str, file_name: str):
183
184
  download_url = await self.get_file(file_id)
184
185
  response = await self.request_download_file(download_url, file_name)
185
186
  return response
186
-
187
+
187
188
  async def send_file(
188
189
  self,
189
190
  chat_id: str,
190
191
  file: str,
191
192
  file_name: str,
192
- type: Literal["File", "Image", "Voice", "Music", "Gif", "Video"] = "File",
193
+ type: Literal["File", "Image", "Voice",
194
+ "Music", "Gif", "Video"] = "File",
193
195
  chat_keypad: Keypad = None,
194
196
  inline_keypad: Keypad = None,
195
197
  chat_keypad_type: Literal["New", "Remove"] = None,
@@ -208,148 +210,21 @@ class Method(Network):
208
210
  }
209
211
  response = await self.request("sendFile", data)
210
212
  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,
220
- chat_keypad_type: Literal["New", "Remove"] = None,
221
- disable_notification: bool = False,
222
- reply_to_message_id: str = None,
223
- ) -> "MessageId":
224
- file_id = await self.upload_file(document, name, "File")
225
- data = {
226
- "chat_id": chat_id,
227
- "file_id": file_id,
228
- "chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
229
- "inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
230
- "disable_notification": disable_notification,
231
- "reply_to_message_id": reply_to_message_id,
232
- "chat_keypad_type": chat_keypad_type,
233
- }
234
- response = await self.request("sendFile", data)
235
- return MessageId.from_dict(response)
236
-
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)
213
+
214
+ async def send_document(self, chat_id: str, document: str, name: str, **kwargs):
215
+ return await self.send_file(chat_id, document, name, "File", **kwargs)
216
+
217
+ async def send_photo(self, chat_id: str, photo: str, name: str, **kwargs):
218
+ return await self.send_file(chat_id, photo, name, "Image", **kwargs)
219
+
220
+ async def send_video(self, chat_id: str, video: str, name: str, **kwargs):
221
+ return await self.send_file(chat_id, video, name, "Video", **kwargs)
222
+
223
+ async def send_gif(self, chat_id: str, gif: str, name: str, **kwargs):
224
+ return await self.send_file(chat_id, gif, name, "Gif", **kwargs)
225
+
226
+ async def send_music(self, chat_id: str, music: str, name: str, **kwargs):
227
+ return await self.send_file(chat_id, music, name, "Music", **kwargs)
228
+
229
+ async def send_voice(self, chat_id: str, voice: str, name: str, **kwargs):
230
+ return await self.send_file(chat_id, voice, name, "Voice", **kwargs)
@@ -5,6 +5,7 @@ import rubigram
5
5
 
6
6
  T = TypeVar("T", bound="Dict")
7
7
 
8
+
8
9
  @dataclass
9
10
  class Dict:
10
11
  def to_dict(self) -> dict[str, Any]:
@@ -14,7 +15,8 @@ class Dict:
14
15
  if is_dataclass(value):
15
16
  data[field.name] = value.to_dict()
16
17
  elif isinstance(value, list):
17
- data[field.name] = [i.to_dict() if is_dataclass(i) else i for i in value]
18
+ data[field.name] = [i.to_dict() if is_dataclass(i)
19
+ else i for i in value]
18
20
  else:
19
21
  data[field.name] = value
20
22
  return data
@@ -22,7 +24,7 @@ class Dict:
22
24
  @classmethod
23
25
  def from_dict(cls: Type[T], data: dict[str, Any]) -> Optional[T]:
24
26
  if data is None:
25
- return cls()
27
+ data = {}
26
28
  init_data = {}
27
29
  for field in fields(cls):
28
30
  value = data.get(field.name)
@@ -33,12 +35,14 @@ class Dict:
33
35
  elif origin == list:
34
36
  inner_type = get_args(field_type)[0]
35
37
  if isinstance(inner_type, type) and issubclass(inner_type, Dict):
36
- init_data[field.name] = [inner_type.from_dict(v) if isinstance(v, dict) else v for v in (value or [])]
38
+ init_data[field.name] = [inner_type.from_dict(
39
+ v) if isinstance(v, dict) else v for v in (value or [])]
37
40
  else:
38
41
  init_data[field.name] = value or []
39
42
  elif get_origin(field_type) is Union:
40
43
  args = get_args(field_type)
41
- dict_type = next((a for a in args if isinstance(a, type) and issubclass(a, Dict)), None)
44
+ dict_type = next((a for a in args if isinstance(
45
+ a, type) and issubclass(a, Dict)), None)
42
46
  if dict_type and isinstance(value, dict):
43
47
  init_data[field.name] = dict_type.from_dict(value)
44
48
  else:
@@ -65,6 +69,7 @@ class JoinChannelData(Dict):
65
69
  username: Optional[str] = None
66
70
  ask_join: Optional[bool] = False
67
71
 
72
+
68
73
  @dataclass
69
74
  class ButtonLink(Dict):
70
75
  type: Optional[Literal["joinchannel", "url"]] = None
@@ -292,172 +297,63 @@ class MessageId(Dict):
292
297
  @dataclass
293
298
  class Update(Dict):
294
299
  client: Optional["rubigram.Client"] = None
295
- type: Optional[Literal["NewMessage", "UpdatedMessage", "RemovedMessage", "StartedBot", "StoppedBot", "UpdatedPayment"]] = None
300
+ type: Optional[Literal["NewMessage", "UpdatedMessage", "RemovedMessage",
301
+ "StartedBot", "StoppedBot", "UpdatedPayment"]] = None
296
302
  chat_id: Optional[str] = None
297
303
  removed_message_id: Optional[str] = None
298
304
  new_message: Optional[Message] = None
299
305
  updated_message: Optional[Message] = None
300
306
  updated_payment: Optional[PaymentStatus] = None
307
+
308
+ async def send_text(self, text: str,) -> "MessageId":
309
+ return await self.client.send_message(self.chat_id, text)
301
310
 
302
- async def send_text(
303
- self,
304
- text: str,
305
- chat_keypad: Keypad = None,
306
- inline_keypad: Keypad= None,
307
- chat_keypad_type: Literal["New", "Remove"] = None,
308
- disable_notification: bool = None,
309
- reply_to_message_id = None
310
- ) -> "MessageId":
311
- return await self.client.send_message(self.chat_id, text, chat_keypad, inline_keypad, chat_keypad_type, disable_notification, reply_to_message_id)
312
-
311
+ async def download(self, name: str):
312
+ return await self.client.download_file(self.new_message.file.file_id, name)
313
+
313
314
  async def reply(
314
315
  self,
315
316
  text: str,
316
317
  chat_keypad: Keypad = None,
317
- inline_keypad: Keypad= None,
318
+ inline_keypad: Keypad = None,
318
319
  chat_keypad_type: Literal["New", "Remove"] = None,
319
320
  disable_notification: bool = None,
320
321
  ) -> "MessageId":
321
322
  return await self.client.send_message(self.chat_id, text, chat_keypad, inline_keypad, chat_keypad_type, disable_notification, self.new_message.message_id)
322
-
323
+
323
324
  async def reply_file(
324
325
  self,
325
326
  file: str,
326
327
  file_name: str,
327
- type: Literal["File", "Image", "Voice", "Music", "Gif", "Video"] = "File",
328
- chat_keypad: Keypad = None,
329
- inline_keypad: Keypad = None,
330
- chat_keypad_type: Literal["New", "Remove"] = None,
331
- disable_notification: bool = False,
332
- ) -> "MessageId":
333
- return await self.client.send_file(
334
- self.chat_id,
335
- file,
336
- file_name,
337
- type,
338
- chat_keypad,
339
- inline_keypad,
340
- chat_keypad_type,
341
- disable_notification,
342
- self.new_message.message_id
343
- )
344
-
345
- async def reply_document(
346
- self,
347
- document: str,
348
- name: str,
328
+ type: Literal["File", "Image", "Voice",
329
+ "Music", "Gif", "Video"] = "File",
349
330
  chat_keypad: Keypad = None,
350
331
  inline_keypad: Keypad = None,
351
332
  chat_keypad_type: Literal["New", "Remove"] = None,
352
333
  disable_notification: bool = False,
353
334
  ) -> "MessageId":
354
- return await self.client.send_document(
355
- self.chat_id,
356
- document,
357
- name,
358
- chat_keypad,
359
- inline_keypad,
360
- chat_keypad_type,
361
- disable_notification,
362
- self.new_message.message_id
363
- )
364
-
365
- async def reply_photo(
366
- self,
367
- photo: str,
368
- name: str,
369
- chat_keypad: Keypad = None,
370
- inline_keypad: Keypad = None,
371
- chat_keypad_type: Literal["New", "Remove"] = None,
372
- disable_notification: bool = False,
373
- ) -> "MessageId":
374
- return await self.client.send_photo(
375
- self.chat_id,
376
- photo,
377
- name,
378
- chat_keypad,
379
- inline_keypad,
380
- chat_keypad_type,
381
- disable_notification,
382
- self.new_message.message_id
383
- )
384
-
385
- async def reply_video(
386
- self,
387
- video: str,
388
- name: str,
389
- chat_keypad: Keypad = None,
390
- inline_keypad: Keypad = None,
391
- chat_keypad_type: Literal["New", "Remove"] = None,
392
- disable_notification: bool = False,
393
- ) -> "MessageId":
394
- return await self.client.send_video(
395
- self.chat_id,
396
- video,
397
- name,
398
- chat_keypad,
399
- inline_keypad,
400
- chat_keypad_type,
401
- disable_notification,
402
- self.new_message.message_id
403
- )
404
-
405
- async def reply_gif(
406
- self,
407
- gif: str,
408
- name: str,
409
- chat_keypad: Keypad = None,
410
- inline_keypad: Keypad = None,
411
- chat_keypad_type: Literal["New", "Remove"] = None,
412
- disable_notification: bool = False,
413
- ) -> "MessageId":
414
- return await self.client.send_gif(
415
- self.chat_id,
416
- gif,
417
- name,
418
- chat_keypad,
419
- inline_keypad,
420
- chat_keypad_type,
421
- disable_notification,
422
- self.new_message.message_id
423
- )
424
-
425
- async def reply_music(
426
- self,
427
- music: str,
428
- name: str,
429
- chat_keypad: Keypad = None,
430
- inline_keypad: Keypad = None,
431
- chat_keypad_type: Literal["New", "Remove"] = None,
432
- disable_notification: bool = False,
433
- ) -> "MessageId":
434
- return await self.client.send_music(
435
- self.chat_id,
436
- music,
437
- name,
438
- chat_keypad,
439
- inline_keypad,
440
- chat_keypad_type,
441
- disable_notification,
442
- self.new_message.message_id
443
- )
444
-
445
- async def replyvoice(
446
- self,
447
- voice: str,
448
- name: str,
449
- chat_keypad: Keypad = None,
450
- inline_keypad: Keypad = None,
451
- chat_keypad_type: Literal["New", "Remove"] = None,
452
- disable_notification: bool = False,
453
- ) -> "MessageId":
454
- return await self.client.send_voice(
455
- self.chat_id,
456
- voice,
457
- name,
458
- chat_keypad,
459
- inline_keypad,
460
- chat_keypad_type,
461
- disable_notification,
462
- self.new_message.message_id
463
- )
335
+ return await self.client.send_file(self.chat_id, file, file_name, type, chat_keypad, inline_keypad, chat_keypad_type, disable_notification, self.new_message.message_id)
336
+
337
+ async def reply_document(self, document: str, name: str, **kwargs) -> "MessageId":
338
+ return await self.reply_file(document, name, "File", **kwargs)
339
+
340
+ async def reply_photo(self, photo: str, name: str, **kwargs) -> "MessageId":
341
+ return await self.reply_file(photo, name, "Image", **kwargs)
342
+
343
+ async def reply_video(self, video: str, name: str, **kwargs) -> "MessageId":
344
+ return await self.reply_file(video, name, "Video", **kwargs)
345
+
346
+ async def reply_gif(self, gif: str, name: str, **kwargs) -> "MessageId":
347
+ return await self.reply_file(gif, name, "Gif", **kwargs)
348
+
349
+ async def reply_music(self, music: str, name: str, **kwargs) -> "MessageId":
350
+ return await self.reply_file(music, name, "Music", **kwargs)
351
+
352
+ async def reply_voice(self, voice: str, name: str, **kwargs) -> "MessageId":
353
+ return await self.reply_file(voice, name, "Voice", **kwargs)
354
+
355
+
356
+ @dataclass
357
+ class Updates(Dict):
358
+ updates: list[Update] = None
359
+ next_offset_id: Optional[str] = None
@@ -1,92 +0,0 @@
1
- from __future__ import annotations
2
- from typing import Callable, Awaitable, Optional
3
- from aiohttp import web
4
- from functools import wraps
5
- from rubigram.types import Update, InlineMessage
6
- from rubigram.method import Method
7
- import asyncio
8
- import logging
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- class Client(Method):
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
- ):
21
- self.token = token
22
- self.port = port
23
- self.host = host
24
- self.endpoint = endpoint
25
- self.messages_handler: list[Callable[[Client, Update], Awaitable]] = []
26
- self.inlines_handler: list[Callable[[Client, InlineMessage], Awaitable]] = []
27
- self.routes = web.RouteTableDef()
28
- super().__init__(token)
29
-
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}")
39
- self.messages_handler.append(wrapper)
40
- return func
41
- return decorator
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}")
52
- self.inlines_handler.append(wrapper)
53
- return func
54
- return decorator
55
-
56
- async def handle_update(self, data: dict):
57
- if "inline_message" in data:
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
-
66
- async def set_endpoints(self):
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")
71
-
72
- def run(self):
73
- @self.routes.post("/ReceiveUpdate")
74
- async def receive_update(request: web.Request):
75
- data = await request.json()
76
- await self.handle_update(data)
77
- return web.json_response({"status": "OK"})
78
-
79
- @self.routes.post("/ReceiveInlineMessage")
80
- async def receive_inline_message(request: web.Request):
81
- data = await request.json()
82
- await self.handle_update(data)
83
- return web.json_response({"status": "OK"})
84
-
85
- app = web.Application()
86
- app.add_routes(self.routes)
87
-
88
- async def on_startup(_):
89
- await self.set_endpoints()
90
-
91
- app.on_startup.append(on_startup)
92
- web.run_app(app, host=self.host, port=self.port)
@@ -1,145 +0,0 @@
1
- from rubigram.types import Update, InlineMessage
2
- from rubigram.state import state_manager
3
- from typing import Union
4
- import re
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():
14
- def filter(message: Update):
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()):
27
- return True
28
- return False
29
- return filter
30
-
31
- def button(id: Union[str, list[str]]):
32
- def filter(message: InlineMessage):
33
- if isinstance(message, InlineMessage):
34
- button_id = message.aux_data.button_id
35
- ID = id if isinstance(id, list) else [id]
36
- for i in ID:
37
- if button_id == i:
38
- return True
39
- return False
40
- return filter
41
-
42
- def chat(chat_id: Union[str, list[str]]):
43
- def filter(message: Union[Update, InlineMessage]):
44
- chat_ids = chat_id if isinstance(chat_id, list) else [chat_id]
45
- if isinstance(message, Update) or isinstance(message, InlineMessage):
46
- return message.chat_id in chat_ids
47
- return False
48
- return filter
49
-
50
- def regex(pattern: str):
51
- def filter(message: Union[Update, InlineMessage]):
52
- if isinstance(message, Update) and message.type == "NewMessage" and message.new_message.text:
53
- return bool(re.search(pattern, message.new_message.text))
54
- elif isinstance(message, InlineMessage) and message.text:
55
- return bool(re.search(pattern, message.text))
56
- return False
57
- return filter
58
-
59
- def text():
60
- def filter(message: Update):
61
- if isinstance(message, Update) and message.type == "NewMessage":
62
- return message.new_message.text is None
63
- return False
64
- return filter
65
-
66
- def file():
67
- def filter(message: Update):
68
- if isinstance(message, Update) and message.type == "NewMessage":
69
- return message.new_message.file is None
70
- return False
71
- return filter
72
-
73
- def private():
74
- def filter(message: Update):
75
- if isinstance(message, Update) and message.type == "NewMessage":
76
- return message.new_message.sender_type in ["User", "Bot"]
77
- return False
78
- return filter
79
-
80
-
81
- def forward():
82
- def filter(message: Update):
83
- if isinstance(message, Update) and message.type == "NewMessage":
84
- return message.new_message.forwarded_from is None
85
- return False
86
- return filter
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
-
112
- def location():
113
- def filter(message: Update):
114
- if isinstance(message, Update) and message.type == "NewMessage":
115
- return message.new_message.location is None
116
- return False
117
- return filter
118
-
119
- def sticker():
120
- def filter(message: Update):
121
- if isinstance(message, Update) and message.type == "NewMessage":
122
- return message.new_message.sticker is None
123
- return False
124
- return filter
125
-
126
- def contact():
127
- def filter(message: Update):
128
- if isinstance(message, Update) and message.type == "NewMessage":
129
- return message.new_message.contact_message is None
130
- return False
131
- return filter
132
-
133
- def poll():
134
- def filter(message: Update):
135
- if isinstance(message, Update) and message.type == "NewMessage":
136
- return message.new_message.poll is None
137
- return False
138
- return filter
139
-
140
- def live():
141
- def filter(message: Update):
142
- if isinstance(message, Update) and message.type == "NewMessage":
143
- return message.new_message.live_location is None
144
- return False
145
- return filter
@@ -1,30 +0,0 @@
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()
File without changes
File without changes
File without changes