RubigramClient 1.7.1__py3-none-any.whl → 1.7.3__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.

Files changed (74) hide show
  1. rubigram/__init__.py +1 -5
  2. rubigram/client.py +102 -154
  3. rubigram/enums.py +4 -3
  4. rubigram/filters.py +600 -139
  5. rubigram/handler.py +24 -0
  6. rubigram/http.py +32 -0
  7. rubigram/logger.py +20 -0
  8. rubigram/method/__init__.py +18 -0
  9. rubigram/method/chat/__init__.py +10 -0
  10. rubigram/method/chat/get_chat.py +26 -0
  11. rubigram/method/chat/get_me.py +22 -0
  12. rubigram/method/chat/get_update.py +32 -0
  13. rubigram/method/decorator/__init__.py +19 -0
  14. rubigram/method/decorator/on_delete_message.py +37 -0
  15. rubigram/method/decorator/on_edit_message.py +37 -0
  16. rubigram/method/decorator/on_inline_message.py +40 -0
  17. rubigram/method/decorator/on_message.py +38 -0
  18. rubigram/method/decorator/on_start.py +30 -0
  19. rubigram/method/decorator/on_stop.py +29 -0
  20. rubigram/method/decorator/register.py +43 -0
  21. rubigram/method/file/__init__.py +32 -0
  22. rubigram/method/file/download_file.py +34 -0
  23. rubigram/method/file/get_bytes.py +25 -0
  24. rubigram/method/file/get_file.py +27 -0
  25. rubigram/method/file/get_file_name.py +29 -0
  26. rubigram/method/file/request_download_file.py +35 -0
  27. rubigram/method/file/request_send_file.py +28 -0
  28. rubigram/method/file/request_upload_file.py +62 -0
  29. rubigram/method/file/send_document.py +58 -0
  30. rubigram/method/file/send_file.py +78 -0
  31. rubigram/method/file/send_gif.py +58 -0
  32. rubigram/method/file/send_music.py +58 -0
  33. rubigram/method/file/send_photo.py +58 -0
  34. rubigram/method/file/send_video.py +58 -0
  35. rubigram/method/file/send_voice.py +55 -0
  36. rubigram/method/messages/__init__.py +29 -0
  37. rubigram/method/messages/delete_message.py +50 -0
  38. rubigram/method/messages/edit_chat_keypad.py +34 -0
  39. rubigram/method/messages/edit_message.py +41 -0
  40. rubigram/method/messages/edit_message_keypad.py +38 -0
  41. rubigram/method/messages/edit_message_text.py +34 -0
  42. rubigram/method/messages/forward_message.py +43 -0
  43. rubigram/method/messages/remove_chat_keypad.py +28 -0
  44. rubigram/method/messages/send_contact.py +74 -0
  45. rubigram/method/messages/send_location.py +70 -0
  46. rubigram/method/messages/send_message.py +67 -0
  47. rubigram/method/messages/send_poll.py +71 -0
  48. rubigram/method/messages/send_sticker.py +66 -0
  49. rubigram/method/network/__init__.py +7 -0
  50. rubigram/method/network/request.py +20 -0
  51. rubigram/method/setting/__init__.py +9 -0
  52. rubigram/method/setting/set_command.py +32 -0
  53. rubigram/method/setting/update_bot_endpoint.py +31 -0
  54. rubigram/method/utilities/__init__.py +11 -0
  55. rubigram/method/utilities/dispatch.py +25 -0
  56. rubigram/method/utilities/setup_endpoint.py +16 -0
  57. rubigram/method/utilities/updater.py +17 -0
  58. rubigram/rubino/client.py +16 -130
  59. rubigram/rubino/network.py +121 -0
  60. rubigram/state.py +14 -19
  61. rubigram/types/__init__.py +3 -0
  62. rubigram/types/messages.py +175 -0
  63. rubigram/types/object.py +112 -0
  64. rubigram/types/types.py +211 -0
  65. rubigram/types/updates.py +572 -0
  66. {rubigramclient-1.7.1.dist-info → rubigramclient-1.7.3.dist-info}/METADATA +4 -3
  67. rubigramclient-1.7.3.dist-info/RECORD +71 -0
  68. rubigram/method.py +0 -354
  69. rubigram/network.py +0 -80
  70. rubigram/types.py +0 -538
  71. rubigramclient-1.7.1.dist-info/RECORD +0 -15
  72. {rubigramclient-1.7.1.dist-info → rubigramclient-1.7.3.dist-info}/WHEEL +0 -0
  73. {rubigramclient-1.7.1.dist-info → rubigramclient-1.7.3.dist-info}/licenses/LICENSE +0 -0
  74. {rubigramclient-1.7.1.dist-info → rubigramclient-1.7.3.dist-info}/top_level.txt +0 -0
rubigram/filters.py CHANGED
@@ -1,163 +1,579 @@
1
+ from typing import Callable, Any, Optional, Union
1
2
  from rubigram.types import Update, InlineMessage
2
- from typing import Union
3
3
  import re
4
-
4
+ import rubigram
5
5
 
6
6
 
7
7
  class Filter:
8
- def __init__(self, func):
9
- self.func = func
10
-
11
- async def __call__(self, update: Union[Update, InlineMessage]):
12
- return await self.func(update)
13
-
14
- def __and__(self, other: "Filter"):
15
- async def filter(update):
16
- return await self(update) and await other(update)
17
- return Filter(filter)
18
-
19
- def __or__(self, other: "Filter"):
20
- async def filter(update):
21
- return await self(update) or await other(update)
22
- return Filter(filter)
23
-
24
-
25
-
26
- async def TEXT(message: Update):
27
- if isinstance(message, Update):
28
- return True if message.new_message and message.new_message.text or message.updated_message.text else False
8
+ async def __call__(
9
+ self,
10
+ client: "rubigram.Client",
11
+ update: Union[Update, InlineMessage]
12
+ ) -> bool:
13
+ raise NotImplementedError
14
+
15
+ def __invert__(self) -> "Filter":
16
+ return InvertFilter(self)
17
+
18
+ def __and__(self, other: "Filter") -> "Filter":
19
+ return AndFilter(self, other)
20
+
21
+ def __or__(self, other: "Filter") -> "Filter":
22
+ return OrFilter(self, other)
23
+
24
+
25
+ class InvertFilter(Filter):
26
+ def __init__(self, base: Filter):
27
+ self.base = base
28
+
29
+ async def __call__(self, client: "rubigram.Client", update: Any) -> bool:
30
+ return not await self.base(client, update)
31
+
32
+
33
+ class AndFilter(Filter):
34
+ def __init__(self, base: Filter, other: Filter):
35
+ self.base = base
36
+ self.other = other
37
+
38
+ async def __call__(self, client: "rubigram.Client", update: Any) -> bool:
39
+ return await self.base(client, update) and await self.other(client, update)
40
+
41
+
42
+ class OrFilter(Filter):
43
+ def __init__(self, base: Filter, other: Filter):
44
+ self.base = base
45
+ self.other = other
46
+
47
+ async def __call__(self, client: "rubigram.Client", update: Any) -> bool:
48
+ return await self.base(client, update) or await self.other(client, update)
49
+
50
+
51
+ CUSTOM_FILTER_NAME = "CustomFilter"
52
+
53
+
54
+ def create(func: Callable, name: Optional[str] = None, **kwargs) -> Filter:
55
+ """Create a custom Rubigram filter.
56
+
57
+ Custom filters let you control which updates your handlers receive.
58
+
59
+ Args:
60
+ func (Callable): Async function that takes (filter, client, update) and returns bool.
61
+ name (str, optional): Filter class name. Defaults to 'CustomFilter'.
62
+ **kwargs: Extra parameters accessible inside the filter.
63
+
64
+ Example:
65
+ >>> from rubigram import filters
66
+ >>>
67
+ >>> async def is_admin(client, update):
68
+ >>> return update.chat_id in ADMIN_IDS
69
+ >>>
70
+ >>> admin = filters.create(is_admin)
71
+ """
72
+ return type(
73
+ name or func.__name__ or CUSTOM_FILTER_NAME,
74
+ (Filter,),
75
+ {"__call__": func, **kwargs}
76
+ )()
77
+
78
+
79
+ async def text_filter(client, update: Union[Update, InlineMessage]) -> bool:
80
+ """Filter updates that contain text messages.
81
+
82
+ This filter passes only updates that include text content, whether they are
83
+ new messages, edited messages, or inline messages.
84
+
85
+ Args:
86
+ client (rubigram.Client): The Rubigram client instance.
87
+ update (Union[Update, InlineMessage]): The update to check for text content.
88
+
89
+ Returns:
90
+ bool: True if the update contains text, False otherwise.
91
+
92
+ Example:
93
+ >>> from rubigram improt filters
94
+
95
+ >>> @client.on_message(filters.text)
96
+ >>> async def handle_text(client, update):
97
+ >>> await client.send_message(update.chat_id, "Received a text message!")
98
+ """
99
+ if isinstance(update, Update):
100
+ msg = update.new_message or update.updated_message
101
+ return bool(getattr(msg, "text", None))
102
+ elif isinstance(update, InlineMessage):
103
+ return bool(getattr(update, "text", None))
29
104
  return False
30
105
 
31
- async def FILE(message: Update):
32
- if isinstance(message, Update):
33
- return True if message.new_message and message.new_message.file else False
106
+ text = create(text_filter)
107
+
108
+
109
+ async def file_filter(client, update: Union[Update, InlineMessage]) -> bool:
110
+ """Filter updates that contain a file.
111
+
112
+ This filter passes only updates that include a file attachment in new messages.
113
+
114
+ Args:
115
+ client (rubigram.Client): The Rubigram client instance.
116
+ update (Union[Update, InlineMessage]): The update to check for a file.
117
+
118
+ Returns:
119
+ bool: True if the update contains a file, False otherwise.
120
+
121
+ Example:
122
+ >>> from rubigram import filters
123
+ >>>
124
+ >>> @client.on_message(filters.file)
125
+ >>> async def handle_file(client, update):
126
+ >>> await client.send_message(update.chat_id, "Received a file!")
127
+ """
128
+ if isinstance(update, Update):
129
+ msg = update.new_message
130
+ return bool(getattr(msg, "file", None))
34
131
  return False
35
132
 
36
- async def LIVE(message: Update):
37
- if isinstance(message, Update):
38
- return True if message.new_message and message.new_message.live_location else False
133
+ file = create(file_filter)
134
+
135
+
136
+ async def live_filter(client, update: Union[Update, InlineMessage]) -> bool:
137
+ """Filter updates that contain a live location.
138
+
139
+ This filter passes only updates that include a live location in new messages.
140
+
141
+ Args:
142
+ client (rubigram.Client): The Rubigram client instance.
143
+ update (Union[Update, InlineMessage]): The update to check for a live location.
144
+
145
+ Returns:
146
+ bool: True if the update contains a live location, False otherwise.
147
+
148
+ Example:
149
+ >>> from rubigram import filters
150
+ >>>
151
+ >>> @client.on_message(filters.live)
152
+ >>> async def handle_live(client, update):
153
+ >>> await client.send_message(update.chat_id, "Received a live location!")
154
+ """
155
+ if isinstance(update, Update):
156
+ msg = update.new_message
157
+ return bool(getattr(msg, "live_location", None))
39
158
  return False
40
159
 
41
- async def POLL(message: Update):
42
- if isinstance(message, Update):
43
- return True if message.new_message and message.new_message.poll else False
160
+ live = create(live_filter)
161
+
162
+
163
+ async def poll_filter(client, update: Union[Update, InlineMessage]) -> bool:
164
+ """Filter updates that contain a poll.
165
+
166
+ This filter passes only updates that include a poll in new messages.
167
+
168
+ Args:
169
+ client (rubigram.Client): The Rubigram client instance.
170
+ update (Union[Update, InlineMessage]): The update to check for a poll.
171
+
172
+ Returns:
173
+ bool: True if the update contains a poll, False otherwise.
174
+
175
+ Example:
176
+ >>> from rubigram import filters
177
+ >>>
178
+ >>> @client.on_message(filters.poll)
179
+ >>> async def handle_poll(client, update):
180
+ >>> await client.send_message(update.chat_id, "Received a poll!")
181
+ """
182
+ if isinstance(update, Update):
183
+ msg = update.new_message
184
+ return bool(getattr(msg, "poll", None))
44
185
  return False
45
186
 
46
- async def CONTACT(message: Update):
47
- if isinstance(message, Update):
48
- return True if message.new_message and message.new_message.contact_message else False
187
+ poll = create(poll_filter)
188
+
189
+
190
+ async def contact_filter(client, update: Union[Update, InlineMessage]) -> bool:
191
+ """Check if the update contains a contact message.
192
+
193
+ This filter checks if the incoming update is a message of type `Update`
194
+ and whether it includes a contact object (`contact_message`).
195
+ Inline messages will always return False for this filter because contacts
196
+ cannot be sent via inline messages.
197
+
198
+ Args:
199
+ client (rubigram.Client): The Rubigram client instance.
200
+ update (Union[Update, InlineMessage]): The incoming update to check.
201
+
202
+ Returns:
203
+ bool: True if the update contains a contact message, False otherwise.
204
+
205
+ Example:
206
+ >>> @client.on_message(filters.contact)
207
+ >>> async def handle_contact(client, update):
208
+ >>> await client.send_message(update.chat_id, "You sent a contact!")
209
+ """
210
+ if isinstance(update, Update):
211
+ msg = update.new_message
212
+ return bool(getattr(msg, "contact_message", None))
49
213
  return False
50
214
 
51
- async def STICKER(message: Update):
52
- if isinstance(message, Update):
53
- return True if message.new_message and message.new_message.sticker else False
215
+ contact = create(contact_filter)
216
+
217
+
218
+ async def sticker_filter(client, update: Union[Update, InlineMessage]) -> bool:
219
+ """Check if the update contains a sticker.
220
+
221
+ This filter checks if the incoming update is a message of type `Update`
222
+ and whether it includes a sticker object (`sticker`).
223
+ Inline messages will always return False for this filter because stickers
224
+ cannot be sent via inline messages.
225
+
226
+ Args:
227
+ client (rubigram.Client): The Rubigram client instance.
228
+ update (Union[Update, InlineMessage]): The incoming update to check.
229
+
230
+ Returns:
231
+ bool: True if the update contains a sticker, False otherwise.
232
+
233
+ Example:
234
+ >>> @client.on_message(filters.sticker)
235
+ >>> async def handle_sticker(client, update):
236
+ >>> await client.send_message(update.chat_id, "You sent a sticker!")
237
+ """
238
+ if isinstance(update, Update):
239
+ msg = update.new_message
240
+ return bool(getattr(msg, "sticker", None))
54
241
  return False
55
242
 
56
- async def LOCATION(message: Update):
57
- if isinstance(message, Update):
58
- return True if message.new_message and message.new_message.location else False
243
+ sticker = create(sticker_filter)
244
+
245
+
246
+ async def location_filter(client, update: Union[Update, InlineMessage]) -> bool:
247
+ """Check if the update contains a location.
248
+
249
+ This filter checks if the incoming update is a message of type `Update`
250
+ and whether it includes a location object (`location`).
251
+ Inline messages will always return False because locations cannot be sent
252
+ via inline messages.
253
+
254
+ Args:
255
+ client (rubigram.Client): The Rubigram client instance.
256
+ update (Union[Update, InlineMessage]): The incoming update to check.
257
+
258
+ Returns:
259
+ bool: True if the update contains a location, False otherwise.
260
+
261
+ Example:
262
+ >>> @client.on_message(filters.location)
263
+ >>> async def handle_location(client, update):
264
+ >>> await client.send_message(update.chat_id, "You shared a location!")
265
+ """
266
+ if isinstance(update, Update):
267
+ msg = update.new_message
268
+ return bool(getattr(msg, "location", None))
59
269
  return False
60
270
 
61
- async def FORWARD(message: Update):
62
- if isinstance(message, Update):
63
- return True if message.new_message and message.new_message.forwarded_from else False
271
+ location = create(location_filter)
272
+
273
+
274
+ async def forward_filter(client, update: Union[Update, InlineMessage]) -> bool:
275
+ """Check if the update is a forwarded message.
276
+
277
+ This filter checks if the incoming update is a message of type `Update`
278
+ and whether it includes a `forwarded_from` object indicating the message
279
+ was forwarded from another user, bot, or channel.
280
+ Inline messages will always return False because forwarding is only for chat messages.
281
+
282
+ Args:
283
+ client (rubigram.Client): The Rubigram client instance.
284
+ update (Union[Update, InlineMessage]): The incoming update to check.
285
+
286
+ Returns:
287
+ bool: True if the update is forwarded, False otherwise.
288
+
289
+ Example:
290
+ >>> @client.on_message(filters.forward)
291
+ >>> async def handle_forward(client, update):
292
+ >>> await client.send_message(update.chat_id, "This message was forwarded!")
293
+ """
294
+ if isinstance(update, Update):
295
+ msg = update.new_message
296
+ return bool(getattr(msg, "forwarded_from", None))
64
297
  return False
65
298
 
66
- async def EDITED(message: Update):
67
- if isinstance(message, Update):
68
- return True if message.updated_message else False
299
+ forward = create(forward_filter)
300
+
301
+
302
+ async def edited_filter(client, update: Union[Update, InlineMessage]) -> bool:
303
+ """Check if the update contains an edited message.
304
+
305
+ This filter checks if the incoming update is of type `Update` and contains
306
+ an `updated_message` object. Inline messages cannot be edited, so they
307
+ will always return False.
308
+
309
+ Args:
310
+ client (rubigram.Client): The Rubigram client instance.
311
+ update (Union[Update, InlineMessage]): The incoming update to check.
312
+
313
+ Returns:
314
+ bool: True if the update is an edited message, False otherwise.
315
+
316
+ Example:
317
+ >>> @client.on_message(filters.edited)
318
+ >>> async def handle_edited(client, update):
319
+ >>> await client.send_message(update.chat_id, "This message was edited!")
320
+ """
321
+ if isinstance(update, Update):
322
+ return bool(update.updated_message)
69
323
  return False
70
324
 
71
- async def PRIVATE(message: Union[Update, InlineMessage]):
72
- return True if message.chat_id.startswith("b0") else False
73
-
74
- async def GROUP(message: Union[Update, InlineMessage]):
75
- return True if message.chat_id.startswith("g0") else False
76
-
77
- async def CHANNEL(message: Union[Update, InlineMessage]):
78
- return True if message.chat_id.startswith("c0") else False
79
-
80
- async def FORWARD_BOT(message: Update):
81
- if isinstance(message, Update) and message.new_message:
82
- return True if message.new_message.forwarded_from and message.new_message.forwarded_from.type_from == "Bot" else False
83
-
84
- async def FORWARD_USER(message: Update):
85
- if isinstance(message, Update) and message.new_message:
86
- return True if message.new_message.forwarded_from and message.new_message.forwarded_from.type_from == "User" else False
87
-
88
- async def FORWARD_CHANNEL(message: Update):
89
- if isinstance(message, Update) and message.new_message:
90
- return True if message.new_message.forwarded_from and message.new_message.forwarded_from.type_from == "Channel" else False
91
-
92
-
93
- text = Filter(TEXT)
94
- file = Filter(FILE)
95
- live = Filter(LIVE)
96
- poll = Filter(POLL)
97
- contact = Filter(CONTACT)
98
- sticker = Filter(STICKER)
99
- location = Filter(LOCATION)
100
- forward = Filter(FORWARD)
101
- edited = Filter(EDITED)
102
- private = Filter(PRIVATE)
103
- group = Filter(GROUP)
104
- channel = Filter(CHANNEL)
105
- forward_bot = Filter(FORWARD_BOT)
106
- forward_user = Filter(FORWARD_USER)
107
- forward_channel = Filter(FORWARD_CHANNEL)
108
-
109
-
110
- class state(Filter):
111
- def __init__(self, state: Union[str, list[str]]):
112
- self.states = state if isinstance(state, list) else [state]
113
- super().__init__(self.filter)
114
-
115
- async def filter(self, update: Union[Update, InlineMessage]):
116
- user_state = await update.client.state.get_state(update.chat_id)
117
- return user_state in self.states
118
-
119
-
120
- class command(Filter):
121
- def __init__(self, command: Union[str, list[str]], prefix: Union[str, list[str]] = "/", case_sensitive: bool = False):
122
- self.commands = [c if case_sensitive else c.lower() for c in (command if isinstance(command, list) else [command])]
123
- self.prefixs = prefix if isinstance(prefix, list) else [prefix]
124
- self.cmds = [p + c for p in self.prefixs for c in self.commands]
125
- self.case_sensitive = case_sensitive
126
- super().__init__(self.filter)
127
-
128
- async def filter(self, update: Update):
325
+ edited = create(edited_filter)
326
+
327
+
328
+ async def private_filter(client, update: Union[Update, InlineMessage]) -> bool:
329
+ """Check if the update comes from a private chat.
330
+
331
+ This filter verifies if the incoming update belongs to a private chat.
332
+ Private chat IDs in Rubigram start with 'b0'.
333
+
334
+ Args:
335
+ client (rubigram.Client): The Rubigram client instance.
336
+ update (Union[Update, InlineMessage]): The update to check.
337
+
338
+ Returns:
339
+ bool: True if the update is from a private chat, False otherwise.
340
+
341
+ Example:
342
+ >>> @client.on_message(filters.private)
343
+ >>> async def handle_private(client, update):
344
+ >>> await client.send_message(update.chat_id, "Private chat message received!")
345
+ """
346
+ return str(update.chat_id).startswith("b0")
347
+
348
+ private = create(private_filter)
349
+
350
+
351
+ async def group_filter(client, update: Union[Update, InlineMessage]) -> bool:
352
+ """Check if the update comes from a group chat.
353
+
354
+ This filter verifies if the incoming update belongs to a group chat.
355
+ Group chat IDs in Rubigram start with 'g0'.
356
+
357
+ Args:
358
+ client (rubigram.Client): The Rubigram client instance.
359
+ update (Union[Update, InlineMessage]): The update to check.
360
+
361
+ Returns:
362
+ bool: True if the update is from a group chat, False otherwise.
363
+
364
+ Example:
365
+ >>> @client.on_message(filters.group)
366
+ >>> async def handle_group(client, update):
367
+ >>> await client.send_message(update.chat_id, "Group chat message received!")
368
+ """
369
+ return str(update.chat_id).startswith("g0")
370
+
371
+ group = create(group_filter)
372
+
373
+
374
+ async def channel_filter(client, update: Union[Update, InlineMessage]) -> bool:
375
+ """Check if the update comes from a channel.
376
+
377
+ This filter verifies if the incoming update belongs to a channel.
378
+ Channel IDs in Rubigram start with 'c0'.
379
+
380
+ Args:
381
+ client (rubigram.Client): The Rubigram client instance.
382
+ update (Union[Update, InlineMessage]): The update to check.
383
+
384
+ Returns:
385
+ bool: True if the update is from a channel, False otherwise.
386
+
387
+ Example:
388
+ >>> @client.on_message(filters.channel)
389
+ >>> async def handle_channel(client, update):
390
+ >>> await client.send_message(update.chat_id, "Channel message received!")
391
+ """
392
+ return str(update.chat_id).startswith("c0")
393
+
394
+ channel = create(channel_filter)
395
+
396
+
397
+ async def forward_bot_filter(client, update: Update) -> bool:
398
+ """Check if a message is forwarded from a bot.
399
+
400
+ This filter passes only updates where the message is a forward
401
+ from a bot account.
402
+
403
+ Args:
404
+ client (rubigram.Client): The Rubigram client instance.
405
+ update (Update): The update to check.
406
+
407
+ Returns:
408
+ bool: True if the message was forwarded from a bot, False otherwise.
409
+
410
+ Example:
411
+ >>> @client.on_message(filters.forward_bot)
412
+ >>> async def handle_forward_bot(client, update):
413
+ >>> await client.send_message(update.chat_id, "Forwarded from a bot!")
414
+ """
415
+ if update.new_message and update.new_message.forwarded_from:
416
+ return update.new_message.forwarded_from.type_from == "Bot"
417
+ return False
418
+
419
+ forward_bot = create(forward_bot_filter)
420
+
421
+
422
+ async def forward_user_filter(client, update: Update) -> bool:
423
+ """Check if a message is forwarded from a user.
424
+
425
+ This filter passes only updates where the message is a forward
426
+ from a regular user account.
427
+
428
+ Args:
429
+ client (rubigram.Client): The Rubigram client instance.
430
+ update (Update): The update to check.
431
+
432
+ Returns:
433
+ bool: True if the message was forwarded from a user, False otherwise.
434
+
435
+ Example:
436
+ >>> @client.on_message(filters.forward_user)
437
+ >>> async def handle_forward_user(client, update):
438
+ >>> await client.send_message(update.chat_id, "Forwarded from a user!")
439
+ """
440
+ if update.new_message and update.new_message.forwarded_from:
441
+ return update.new_message.forwarded_from.type_from == "User"
442
+ return False
443
+
444
+ forward_user = create(forward_user_filter)
445
+
446
+
447
+ async def forward_channel_filter(client, update: Update) -> bool:
448
+ """Check if a message is forwarded from a channel.
449
+
450
+ This filter passes only updates where the message is a forward
451
+ from a channel.
452
+
453
+ Args:
454
+ client (rubigram.Client): The Rubigram client instance.
455
+ update (Update): The update to check.
456
+
457
+ Returns:
458
+ bool: True if the message was forwarded from a channel, False otherwise.
459
+
460
+ Example:
461
+ >>> @client.on_message(filters.forward_channel)
462
+ >>> async def handle_forward_channel(client, update):
463
+ >>> await client.send_message(update.chat_id, "Forwarded from a channel!")
464
+ """
465
+ if update.new_message and update.new_message.forwarded_from:
466
+ return update.new_message.forwarded_from.type_from == "Channel"
467
+ return False
468
+
469
+ forward_channel = create(forward_channel_filter)
470
+
471
+
472
+ def command(
473
+ commands: Union[str, list[str]],
474
+ prefix: Union[str, list[str]] = "/",
475
+ case_sensitive: bool = False
476
+ ) -> bool:
477
+ """Filter updates that start with specific command(s).
478
+
479
+ This filter passes only updates where the message text starts with
480
+ one of the specified commands, optionally supporting multiple prefixes
481
+ and case sensitivity.
482
+
483
+ Args:
484
+ commands (Union[str, list[str]]): A command or list of commands to match.
485
+ prefix (Union[str, list[str]], optional): Command prefix(es). Defaults to "/".
486
+ case_sensitive (bool, optional): Whether matching should be case sensitive. Defaults to False.
487
+
488
+ Returns:
489
+ Filter: A Rubigram filter that can be used with @app.on_message.
490
+
491
+ Example:
492
+ >>> @app.on_message(filters.command("start"))
493
+ >>> async def handle_start(client, update):
494
+ >>> await client.send_message(update.chat_id, "Bot started!")
495
+
496
+ >>> @app.on_message(filters.command(["start", "help"], prefix=["/", "!"]))
497
+ >>> async def handle_multiple(client, update):
498
+ >>> await client.send_message(update.chat_id, "Command received!")
499
+ """
500
+
501
+ async def func(client, update):
129
502
  if isinstance(update, Update):
130
- if update.new_message:
131
- text = update.new_message.text
132
- elif update.updated_message:
133
- text = update.updated_message.text
134
- else: return False
135
- text = text if self.case_sensitive else text.lower()
136
- return any(text.startswith(cmd) for cmd in self.cmds)
503
+ msg = update.new_message or update.updated_message
504
+ if not msg or not getattr(msg, "text", None):
505
+ return False
506
+ text = msg.text if case_sensitive else msg.text.lower()
507
+ cmds = [
508
+ c if case_sensitive else c.lower() for c in (
509
+ commands if isinstance(commands, list) else [commands]
510
+ )
511
+ ]
512
+ prefixes_list = prefix if isinstance(prefix, list) else [prefix]
513
+ full_cmds = [p + c for p in prefixes_list for c in cmds]
514
+ return any(text.startswith(cmd) for cmd in full_cmds)
137
515
  return False
138
-
139
516
 
140
- class button(Filter):
141
- def __init__(self, button_id: Union[str, list[str]], prefix: Union[str, list[str]] = "", case_sensitive: bool = False):
142
- self.button_ids = [btn_id if case_sensitive else btn_id.lower() for btn_id in (button_id if isinstance(button_id, list) else [button_id])]
143
- self.prefixs = prefix if isinstance(prefix, list) else [prefix]
144
- self.btn_ids = [p + b for p in self.prefixs for b in self.button_ids]
145
- self.case_sensitive = case_sensitive
146
- super().__init__(self.filter)
517
+ return create(
518
+ func,
519
+ "CommandFilter",
520
+ commands=commands,
521
+ prefix=prefix,
522
+ case_sensitive=case_sensitive
523
+ )
147
524
 
148
- async def filter(self, update: InlineMessage):
149
- if isinstance(update, InlineMessage):
150
- text = update.aux_data.button_id or ""
151
- text = text if self.case_sensitive else text.lower()
152
- return any(text.startswith(btn) for btn in self.btn_ids)
153
- return False
154
525
 
155
- class regex(Filter):
156
- def __init__(self, pattern: str):
157
- self.pattern = pattern
158
- super().__init__(self.filter)
526
+ def chat(chat_id: Union[str, list[str]]):
527
+ """Filter updates based on chat ID(s).
528
+
529
+ This filter passes only updates that belong to the specified chat ID(s).
530
+
531
+ Args:
532
+ chat_id (Union[str, list[str]]): A chat ID or list of chat IDs to filter.
533
+
534
+ Returns:
535
+ Filter: A Rubigram filter that can be used with @app.on_message or @app.on_inline.
536
+
537
+ Example:
538
+ >>> @app.on_message(filters.chat("b0123456789"))
539
+ >>> async def handle_private(client, update):
540
+ >>> await client.send_message(update.chat_id, "Hello private chat!")
541
+
542
+ >>> @app.on_message(filters.chat(["g0123", "g0456"]))
543
+ >>> async def handle_groups(client, update):
544
+ >>> await client.send_message(update.chat_id, "Hello group!")
545
+ """
159
546
 
160
- async def filter(self, update: Union[Update, InlineMessage]):
547
+ async def func(client, update: Union[Update, InlineMessage]):
548
+ chat_ids = chat_id if isinstance(chat_id, list) else [chat_id]
549
+ return update.chat_id in chat_ids
550
+
551
+ return create(
552
+ func,
553
+ "ChatFilter",
554
+ chat_id=chat_id
555
+ )
556
+
557
+
558
+ def regex(pattern: str):
559
+ """Filter updates whose text matches a regular expression.
560
+
561
+ This filter passes only updates (new messages, edited messages, or inline messages)
562
+ where the text content matches the specified regex pattern.
563
+
564
+ Args:
565
+ pattern (str): The regular expression pattern to match against the message text.
566
+
567
+ Returns:
568
+ Filter: A Rubigram filter that can be used with @app.on_message or @app.on_inline.
569
+
570
+ Example:
571
+ >>> @app.on_message(filters.regex(r"hello|hi"))
572
+ >>> async def handle_greetings(client, update):
573
+ >>> await client.send_message(update.chat_id, "Hello there!")
574
+ """
575
+
576
+ async def func(client, update: Union[Update, InlineMessage]):
161
577
  text = ""
162
578
  if isinstance(update, Update):
163
579
  if update.type == "NewMessage":
@@ -167,14 +583,59 @@ class regex(Filter):
167
583
  elif isinstance(update, InlineMessage):
168
584
  text = getattr(update, "text", "")
169
585
 
170
- return bool(re.search(self.pattern, text)) if text else False
586
+ return bool(re.search(pattern, text)) if text else False
587
+
588
+ return create(
589
+ func,
590
+ "RegexFilter",
591
+ pattern=pattern
592
+ )
593
+
171
594
 
595
+ def button(button_id: Union[str, list[str]], prefix: Union[str, list[str]] = "", case_sensitive: bool = False):
596
+ """Filter inline messages based on button IDs.
172
597
 
173
- class chat(Filter):
174
- def __init__(self, chat_id: Union[str, list[str]]):
175
- self.chat_id = chat_id
176
- super().__init__(self.filter)
598
+ This filter passes only `InlineMessage` updates whose button ID matches
599
+ one of the specified IDs, optionally supporting multiple prefixes
600
+ and case sensitivity.
601
+
602
+ Args:
603
+ button_id (str | list[str]): Button ID or list of button IDs to match.
604
+ prefix (str | list[str], optional): Prefix or list of prefixes for the button IDs. Defaults to "".
605
+ case_sensitive (bool, optional): Whether matching should be case sensitive. Defaults to False.
606
+
607
+ Returns:
608
+ Filter: A Rubigram filter that can be used with @app.on_inline or similar handlers.
609
+
610
+ Example:
611
+ >>> @app.on_inline(filters.button("btn_1"))
612
+ >>> async def handle_btn(client, update):
613
+ >>> await client.send_message(update.chat_id, "Button 1 pressed!")
614
+
615
+ >>> @app.on_inline(filters.button(["btn_1", "btn_2"], prefix="prefix_"))
616
+ >>> async def handle_multiple_buttons(client, update):
617
+ >>> await client.send_message(update.chat_id, "Button pressed!")
618
+ """
619
+
620
+ button_ids = [
621
+ btn if case_sensitive else btn.lower() for btn in (
622
+ button_id if isinstance(button_id, list) else [button_id]
623
+ )
624
+ ]
625
+ prefixes = prefix if isinstance(prefix, list) else [prefix]
626
+ btn_ids = [p + b for p in prefixes for b in button_ids]
627
+
628
+ async def func(flt, client, update: InlineMessage):
629
+ if isinstance(update, InlineMessage):
630
+ text = update.aux_data.button_id or ""
631
+ text = text if case_sensitive else text.lower()
632
+ return any(text.startswith(b) for b in btn_ids)
633
+ return False
177
634
 
178
- async def filter(self, update: Union[Update, InlineMessage]):
179
- chat_ids = self.chat_id if isinstance(self.chat_id, list) else [self.chat_id]
180
- return update.chat_id in chat_ids
635
+ return create(
636
+ func,
637
+ "ButtonFilter",
638
+ button_ids=button_ids,
639
+ prefixes=prefixes,
640
+ case_sensitive=case_sensitive
641
+ )