RubigramClient 1.7.17__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.
Files changed (132) hide show
  1. rubigram/__init__.py +15 -0
  2. rubigram/client.py +294 -0
  3. rubigram/enums/__init__.py +52 -0
  4. rubigram/enums/buttons/__init__.py +13 -0
  5. rubigram/enums/buttons/button_calendar_type.py +20 -0
  6. rubigram/enums/buttons/button_location_type.py +20 -0
  7. rubigram/enums/buttons/button_selection_get_type.py +20 -0
  8. rubigram/enums/buttons/button_selection_search_type.py +20 -0
  9. rubigram/enums/buttons/button_selection_type.py +22 -0
  10. rubigram/enums/buttons/button_textbox_type_keypad.py +20 -0
  11. rubigram/enums/buttons/button_textbox_type_line.py +20 -0
  12. rubigram/enums/buttons/button_type.py +58 -0
  13. rubigram/enums/chat_action_type.py +22 -0
  14. rubigram/enums/chat_keypad_type.py +20 -0
  15. rubigram/enums/chat_type.py +24 -0
  16. rubigram/enums/enum.py +6 -0
  17. rubigram/enums/file_type.py +28 -0
  18. rubigram/enums/forwarded_from_type.py +22 -0
  19. rubigram/enums/live_location_status.py +20 -0
  20. rubigram/enums/message_sender_type.py +20 -0
  21. rubigram/enums/metadata_type.py +18 -0
  22. rubigram/enums/parse_mode.py +20 -0
  23. rubigram/enums/payment_status_type.py +20 -0
  24. rubigram/enums/poll_status_type.py +20 -0
  25. rubigram/enums/update_endpoint_type.py +26 -0
  26. rubigram/enums/update_type.py +28 -0
  27. rubigram/errors.py +16 -0
  28. rubigram/filters.py +865 -0
  29. rubigram/http_session.py +96 -0
  30. rubigram/methods/__init__.py +26 -0
  31. rubigram/methods/chats/__init__.py +10 -0
  32. rubigram/methods/chats/get_chat.py +53 -0
  33. rubigram/methods/decorators/__init__.py +25 -0
  34. rubigram/methods/decorators/on_inline_message.py +70 -0
  35. rubigram/methods/decorators/on_message.py +62 -0
  36. rubigram/methods/decorators/on_remove_message.py +65 -0
  37. rubigram/methods/decorators/on_start.py +65 -0
  38. rubigram/methods/decorators/on_started_bot.py +70 -0
  39. rubigram/methods/decorators/on_stop.py +65 -0
  40. rubigram/methods/decorators/on_stopped_bot.py +70 -0
  41. rubigram/methods/decorators/on_update_message.py +70 -0
  42. rubigram/methods/files/__init__.py +32 -0
  43. rubigram/methods/files/download_file.py +118 -0
  44. rubigram/methods/files/get_file.py +41 -0
  45. rubigram/methods/files/get_file_name.py +41 -0
  46. rubigram/methods/files/request_send_file.py +49 -0
  47. rubigram/methods/files/send_file.py +133 -0
  48. rubigram/methods/files/send_gif.py +51 -0
  49. rubigram/methods/files/send_music.py +97 -0
  50. rubigram/methods/files/send_photo.py +95 -0
  51. rubigram/methods/files/send_video.py +96 -0
  52. rubigram/methods/files/send_voice.py +96 -0
  53. rubigram/methods/files/upload_file.py +114 -0
  54. rubigram/methods/messages/__init__.py +34 -0
  55. rubigram/methods/messages/delete_messages.py +84 -0
  56. rubigram/methods/messages/edit_chat_keypad.py +68 -0
  57. rubigram/methods/messages/edit_message.py +82 -0
  58. rubigram/methods/messages/edit_message_keypad.py +72 -0
  59. rubigram/methods/messages/edit_message_text.py +68 -0
  60. rubigram/methods/messages/forward_message.py +78 -0
  61. rubigram/methods/messages/remove_chat_keypad.py +46 -0
  62. rubigram/methods/messages/send_contact.py +114 -0
  63. rubigram/methods/messages/send_location.py +108 -0
  64. rubigram/methods/messages/send_message.py +115 -0
  65. rubigram/methods/messages/send_poll.py +104 -0
  66. rubigram/methods/messages/send_sticker.py +98 -0
  67. rubigram/methods/network/__init__.py +12 -0
  68. rubigram/methods/network/request.py +129 -0
  69. rubigram/methods/settings/__init__.py +16 -0
  70. rubigram/methods/settings/set_command.py +50 -0
  71. rubigram/methods/settings/setup_endpoints.py +48 -0
  72. rubigram/methods/settings/update_bot_endpoint.py +62 -0
  73. rubigram/methods/updates/__init__.py +14 -0
  74. rubigram/methods/updates/get_me.py +35 -0
  75. rubigram/methods/updates/get_update.py +62 -0
  76. rubigram/methods/utilities/__init__.py +14 -0
  77. rubigram/methods/utilities/dispatcher.py +66 -0
  78. rubigram/methods/utilities/run.py +118 -0
  79. rubigram/rubino/__init__.py +6 -0
  80. rubigram/rubino/client.py +374 -0
  81. rubigram/rubino/network.py +129 -0
  82. rubigram/server/__init__.py +6 -0
  83. rubigram/server/server.py +245 -0
  84. rubigram/state/__init__.py +7 -0
  85. rubigram/state/state.py +121 -0
  86. rubigram/state/storage.py +131 -0
  87. rubigram/types/__init__.py +66 -0
  88. rubigram/types/aux_data.py +28 -0
  89. rubigram/types/bot.py +51 -0
  90. rubigram/types/bot_command.py +26 -0
  91. rubigram/types/buttons/__init__.py +13 -0
  92. rubigram/types/buttons/button.py +59 -0
  93. rubigram/types/buttons/button_calendar.py +40 -0
  94. rubigram/types/buttons/button_location.py +40 -0
  95. rubigram/types/buttons/button_number_picker.py +34 -0
  96. rubigram/types/buttons/button_selection.py +48 -0
  97. rubigram/types/buttons/button_selection_item.py +32 -0
  98. rubigram/types/buttons/button_string_picker.py +29 -0
  99. rubigram/types/buttons/button_text_box.py +40 -0
  100. rubigram/types/chat.py +86 -0
  101. rubigram/types/config/__init__.py +6 -0
  102. rubigram/types/config/object.py +442 -0
  103. rubigram/types/contact_message.py +29 -0
  104. rubigram/types/file.py +78 -0
  105. rubigram/types/forwarded_from.py +39 -0
  106. rubigram/types/keypads/__init__.py +7 -0
  107. rubigram/types/keypads/keypad.py +31 -0
  108. rubigram/types/keypads/keypad_row.py +23 -0
  109. rubigram/types/live_location.py +44 -0
  110. rubigram/types/location.py +24 -0
  111. rubigram/types/messages/__init__.py +8 -0
  112. rubigram/types/messages/inline_message.py +78 -0
  113. rubigram/types/messages/message.py +117 -0
  114. rubigram/types/messages/update_message.py +341 -0
  115. rubigram/types/metadata/__init__.py +7 -0
  116. rubigram/types/metadata/metadata.py +43 -0
  117. rubigram/types/metadata/metadata_parts.py +42 -0
  118. rubigram/types/payment_status.py +30 -0
  119. rubigram/types/poll.py +32 -0
  120. rubigram/types/poll_status.py +40 -0
  121. rubigram/types/sticker.py +33 -0
  122. rubigram/types/updates/__init__.py +7 -0
  123. rubigram/types/updates/update.py +917 -0
  124. rubigram/types/updates/updates.py +56 -0
  125. rubigram/utils/__init__.py +14 -0
  126. rubigram/utils/auto_delete.py +93 -0
  127. rubigram/utils/parser.py +99 -0
  128. rubigramclient-1.7.17.dist-info/METADATA +215 -0
  129. rubigramclient-1.7.17.dist-info/RECORD +132 -0
  130. rubigramclient-1.7.17.dist-info/WHEEL +5 -0
  131. rubigramclient-1.7.17.dist-info/licenses/LICENSE +21 -0
  132. rubigramclient-1.7.17.dist-info/top_level.txt +1 -0
rubigram/filters.py ADDED
@@ -0,0 +1,865 @@
1
+ # RubigramClient - Rubika API library for python
2
+ # Copyright (C) 2025-present Javad <https://github.com/DevJavad>
3
+ # Github - https://github.com/DevJavad/rubigram
4
+
5
+
6
+ from __future__ import annotations
7
+ import re
8
+ from typing import Callable, Optional, Union
9
+ from rubigram.types import Update, InlineMessage
10
+ import rubigram
11
+
12
+
13
+ CUSTOM_FILTER_NAME = "CustomFilter"
14
+ URL_PATTERN = re.compile(
15
+ r"(?:(?:https?|ftp):\/\/)?(?:www\.)?[a-z0-9]+(?:[.\-][a-z0-9]+)*\.[a-z]{2,}(?:\/[^\s]*)?",
16
+ re.IGNORECASE
17
+ )
18
+ USERNAME_PATTERN = re.compile(r"@[A-Za-z0-9_]{3,32}")
19
+
20
+
21
+ class Filter:
22
+ """
23
+ Asynchronous filter for processing updates in Rubigram.
24
+
25
+ This class provides a flexible way to create and combine filters for
26
+ message handling. Filters can be logically combined using AND (&),
27
+ OR (|), and NOT (~) operators.
28
+
29
+ Parameters:
30
+ func (callable):
31
+ An async function that takes (client, update) as arguments
32
+ and returns a boolean.
33
+
34
+ Example:
35
+ .. code-block:: python
36
+ async def my_filter(client, update):
37
+ return update.text == "hello"
38
+
39
+ filter_obj = Filter(my_filter)
40
+ result = await filter_obj(client, update)
41
+ """
42
+
43
+ __slots__ = ("func",)
44
+
45
+ def __init__(self, func):
46
+ self.func = func
47
+
48
+ async def __call__(self, client, update):
49
+ """
50
+ Execute the filter.
51
+
52
+ Parameters:
53
+ client (rubigram.Client):
54
+ The client instance.
55
+ update (Update | InlineMessage):
56
+ The update to check.
57
+
58
+ Returns:
59
+ bool: True if the filter matches, False otherwise.
60
+ """
61
+ return await self.func(client, update)
62
+
63
+ def __and__(self, other):
64
+ """
65
+ Create a new filter that is the logical AND of two filters.
66
+
67
+ Parameters:
68
+ other (Filter):
69
+ Another filter to combine with.
70
+
71
+ Returns:
72
+ Filter: A new filter that returns True only if both filters match.
73
+
74
+ Example:
75
+ .. code-block:: python
76
+ combined = text & private # Matches text messages in private chats
77
+ """
78
+ async def func(client, update):
79
+ return await self(client, update) and await other(client, update)
80
+ return Filter(func)
81
+
82
+ def __or__(self, other):
83
+ """
84
+ Create a new filter that is the logical OR of two filters.
85
+
86
+ Parameters:
87
+ other (Filter):
88
+ Another filter to combine with.
89
+
90
+ Returns:
91
+ Filter: A new filter that returns True if either filter matches.
92
+
93
+ Example:
94
+ .. code-block:: python
95
+ combined = text | file # Matches text OR file messages
96
+ """
97
+ async def func(client, update):
98
+ return await self(client, update) or await other(client, update)
99
+ return Filter(func)
100
+
101
+ def __invert__(self):
102
+ """
103
+ Create a new filter that is the logical NOT of this filter.
104
+
105
+ Returns:
106
+ Filter: A new filter that returns True when the original returns False.
107
+
108
+ Example:
109
+ .. code-block:: python
110
+ not_text = ~text # Matches non-text messages
111
+ """
112
+ async def func(client, update):
113
+ return not await self(client, update)
114
+ return Filter(func)
115
+
116
+
117
+ def create(func: Callable, name: Optional[str] = None, **kwargs) -> Filter:
118
+ """
119
+ **Create a custom Rubigram filter.**
120
+ `filters.create(my_filter_func)`
121
+
122
+ Custom filters let you control which updates your handlers receive.
123
+
124
+ Args:
125
+ func (`Callable`):
126
+ Async function that takes (filter, client, update) and returns bool.
127
+
128
+ name (`Optional[str]`):
129
+ Filter class name. Defaults to 'CustomFilter'.
130
+
131
+ **kwargs: Extra parameters accessible inside the filter.
132
+
133
+ Returns:
134
+ Filter: A custom filter instance.
135
+
136
+ Example:
137
+ .. code-block:: python
138
+
139
+ from rubigram import filters
140
+
141
+ async def is_admin(client, update):
142
+ return update.chat_id in ADMIN_IDS
143
+
144
+ admin = filters.create(is_admin)
145
+
146
+ @client.on_message(admin)
147
+ async def handle_admin(client, update):
148
+ await update.reply(text="Admin command received!")
149
+ """
150
+ return type(
151
+ name or func.__name__ or CUSTOM_FILTER_NAME,
152
+ (Filter,),
153
+ {"__call__": func, **kwargs}
154
+ )()
155
+
156
+
157
+ async def gif_filter(client, update: "Update") -> bool:
158
+ message = update.new_message
159
+ if not message:
160
+ return False
161
+ file = message.file
162
+ if not file:
163
+ return False
164
+ return bool(file.size and file.size < 1024 * 1024)
165
+
166
+
167
+ async def caption_filter(client, update: "Update") -> bool:
168
+ message = update.new_message
169
+ if not message:
170
+ return False
171
+ file = message.file
172
+ return bool(file and update.new_message.text)
173
+
174
+
175
+ async def reply_filter(client, update: "Update") -> bool:
176
+ message = update.new_message
177
+ return bool(message and getattr(message, "reply_to_message_id", None))
178
+
179
+
180
+ async def text_filter(client, update: "Update") -> bool:
181
+ """
182
+ Filter for text messages.
183
+
184
+ Parameters:
185
+ client (rubigram.Client): The client instance.
186
+ update (Update): The update to check.
187
+
188
+ Returns:
189
+ bool: True if the message contains text, False otherwise.
190
+ """
191
+ return bool(getattr(update, "text", None))
192
+
193
+
194
+ async def file_filter(client, update: "Update") -> bool:
195
+ """
196
+ Filter for file messages.
197
+
198
+ Parameters:
199
+ client (rubigram.Client): The client instance.
200
+ update (Update): The update to check.
201
+
202
+ Returns:
203
+ bool: True if the message contains a file, False otherwise.
204
+ """
205
+ message = update.new_message
206
+ return bool(message and getattr(message, "file", None))
207
+
208
+
209
+ async def live_filter(client, update: "Update") -> bool:
210
+ """
211
+ Filter for live location messages.
212
+
213
+ Parameters:
214
+ client (rubigram.Client): The client instance.
215
+ update (Update): The update to check.
216
+
217
+ Returns:
218
+ bool: True if the message contains live location, False otherwise.
219
+ """
220
+ message = update.new_message
221
+ return bool(message and getattr(message, "live_location", None))
222
+
223
+
224
+ async def poll_filter(client, update: "Update") -> bool:
225
+ """
226
+ Filter for poll messages.
227
+
228
+ Parameters:
229
+ client (rubigram.Client): The client instance.
230
+ update (Update): The update to check.
231
+
232
+ Returns:
233
+ bool: True if the message contains a poll, False otherwise.
234
+ """
235
+ message = update.new_message
236
+ return bool(message and getattr(message, "poll", None))
237
+
238
+
239
+ async def contact_filter(client, update: "Update") -> bool:
240
+ """
241
+ Filter for contact messages.
242
+
243
+ Parameters:
244
+ client (rubigram.Client): The client instance.
245
+ update (Update): The update to check.
246
+
247
+ Returns:
248
+ bool: True if the message contains contact information, False otherwise.
249
+ """
250
+ message = update.new_message
251
+ return bool(message and getattr(message, "contact_message", None))
252
+
253
+
254
+ async def sticker_filter(client, update: "Update") -> bool:
255
+ """
256
+ Filter for sticker messages.
257
+
258
+ Parameters:
259
+ client (rubigram.Client): The client instance.
260
+ update (Update): The update to check.
261
+
262
+ Returns:
263
+ bool: True if the message contains a sticker, False otherwise.
264
+ """
265
+ message = update.new_message
266
+ return bool(message and getattr(message, "sticker", None))
267
+
268
+
269
+ async def location_filter(client, update: "Update") -> bool:
270
+ """
271
+ Filter for location messages.
272
+
273
+ Parameters:
274
+ client (rubigram.Client): The client instance.
275
+ update (Update): The update to check.
276
+
277
+ Returns:
278
+ bool: True if the message contains a location, False otherwise.
279
+ """
280
+ message = update.new_message
281
+ return bool(message and getattr(message, "location", None))
282
+
283
+
284
+ async def forward_filter(client, update: "Update") -> bool:
285
+ """
286
+ Filter for forwarded messages.
287
+
288
+ Parameters:
289
+ client (rubigram.Client): The client instance.
290
+ update (Update): The update to check.
291
+
292
+ Returns:
293
+ bool: True if the message is forwarded, False otherwise.
294
+ """
295
+ message = update.new_message
296
+ return bool(message and getattr(message, "forwarded_from", None))
297
+
298
+
299
+ async def edited_filter(client, update: "Update") -> bool:
300
+ """
301
+ Filter for edited messages.
302
+
303
+ Parameters:
304
+ client (rubigram.Client): The client instance.
305
+ update (Update): The update to check.
306
+
307
+ Returns:
308
+ bool: True if the message is edited, False otherwise.
309
+ """
310
+ return bool(update.updated_message)
311
+
312
+
313
+ async def group_filter(client, update: Union["Update", "InlineMessage"]) -> bool:
314
+ """
315
+ Filter for group chats.
316
+
317
+ Parameters:
318
+ client (rubigram.Client): The client instance.
319
+ update (Update | InlineMessage): The update to check.
320
+
321
+ Returns:
322
+ bool: True if the chat is a group, False otherwise.
323
+
324
+ Note:
325
+ Group chat IDs start with "g0".
326
+ """
327
+ return update.chat_id.startswith("g0")
328
+
329
+
330
+ async def channel_filter(client, update: Union["Update", "InlineMessage"]) -> bool:
331
+ """
332
+ Filter for channel chats.
333
+
334
+ Parameters:
335
+ client (rubigram.Client): The client instance.
336
+ update (Update | InlineMessage): The update to check.
337
+
338
+ Returns:
339
+ bool: True if the chat is a channel, False otherwise.
340
+
341
+ Note:
342
+ Channel chat IDs start with "c0".
343
+ """
344
+ return update.chat_id.startswith("c0")
345
+
346
+
347
+ async def private_filter(client, update: Union["Update", "InlineMessage"]) -> bool:
348
+ """
349
+ Filter for private chats.
350
+
351
+ Parameters:
352
+ client (rubigram.Client): The client instance.
353
+ update (Update | InlineMessage): The update to check.
354
+
355
+ Returns:
356
+ bool: True if the chat is private, False otherwise.
357
+
358
+ Note:
359
+ Private chat IDs start with "b0".
360
+ """
361
+ return update.chat_id.startswith("b0")
362
+
363
+
364
+ def file_type_filter(type: str):
365
+ """
366
+ Create a filter for specific file types.
367
+
368
+ Parameters:
369
+ type (str):
370
+ The file type to filter for (e.g., "Gif", "Image", "Video").
371
+
372
+ Returns:
373
+ Filter: A filter that matches messages with the specified file type.
374
+
375
+ Example:
376
+ .. code-block:: python
377
+ gif_filter = file_type_filter("Gif")
378
+ # Equivalent to using the pre-defined `gif` filter
379
+ """
380
+ async def wrapper(client, update: Update):
381
+ message = update.new_message
382
+ file = message.file or None
383
+ return bool(file and file.file_type == type)
384
+ return Filter(wrapper)
385
+
386
+
387
+ def forwarded_filter(type: str):
388
+ """
389
+ Create a filter for messages forwarded from specific sources.
390
+
391
+ Parameters:
392
+ type (str):
393
+ The source type ("Bot", "User", "Channel").
394
+
395
+ Returns:
396
+ Filter: A filter that matches messages forwarded from the specified source.
397
+
398
+ Example:
399
+ .. code-block:: python
400
+ from_bot = forwarded_filter("Bot")
401
+ # Matches messages forwarded from bots
402
+ """
403
+ async def wrapper(client, update: Update):
404
+ message = update.new_message
405
+ forwarded = message.forwarded_from or None
406
+ return bool(forwarded and forwarded.type_from == type)
407
+ return Filter(wrapper)
408
+
409
+
410
+ async def url_filter(client, update: Update) -> bool:
411
+ """
412
+ Filter for messages containing URLs.
413
+
414
+ Parameters:
415
+ client (rubigram.Client): The client instance.
416
+ update (Update): The update to check.
417
+
418
+ Returns:
419
+ bool: True if the message text contains a URL, False otherwise.
420
+ """
421
+ text = update.text or ""
422
+ if not text:
423
+ return False
424
+ return bool(URL_PATTERN.search(text))
425
+
426
+
427
+ async def hyperlink_filter(client, update: Update):
428
+ """
429
+ Filter for messages with clickable hyperlinks.
430
+
431
+ Parameters:
432
+ client (rubigram.Client): The client instance.
433
+ update (Update): The update to check.
434
+
435
+ Returns:
436
+ bool: True if the message contains hyperlink metadata, False otherwise.
437
+
438
+ Note:
439
+ Checks for "Link" type in message metadata, not plain text URLs.
440
+ """
441
+ message = update.new_message or update.updated_message
442
+ if not message:
443
+ return False
444
+ metadata = message.metadata
445
+ if not metadata:
446
+ return False
447
+ for i in metadata.meta_data_parts:
448
+ if i.type == "Link":
449
+ return True
450
+ return False
451
+
452
+
453
+ async def mention_filter(client, update: Update):
454
+ """
455
+ Filter for messages containing mentions.
456
+
457
+ Parameters:
458
+ client (rubigram.Client): The client instance.
459
+ update (Update): The update to check.
460
+
461
+ Returns:
462
+ bool: True if the message contains user mentions, False otherwise.
463
+ """
464
+ message = update.new_message or update.updated_message
465
+ if not message:
466
+ return False
467
+ metadata = message.metadata
468
+ if not metadata:
469
+ return False
470
+ for i in metadata.meta_data_parts:
471
+ if i.type == "MentionText":
472
+ return True
473
+ return False
474
+
475
+
476
+ async def text_bold_filter(client, update: "Update"):
477
+ message = update.new_message or update.updated_message
478
+ if not message:
479
+ return False
480
+ metadata = message.metadata
481
+ if not metadata:
482
+ return False
483
+ for i in metadata.meta_data_parts:
484
+ if i.type == "Bold":
485
+ return True
486
+ return False
487
+
488
+
489
+ async def text_mono_filter(client, update: "Update"):
490
+ message = update.new_message or update.updated_message
491
+ if not message:
492
+ return False
493
+ metadata = message.metadata
494
+ if not metadata:
495
+ return False
496
+ for i in metadata.meta_data_parts:
497
+ if i.type == "Mono":
498
+ return True
499
+ return False
500
+
501
+
502
+ async def text_quote_filter(client, update: "Update"):
503
+ message = update.new_message or update.updated_message
504
+ if not message:
505
+ return False
506
+ metadata = message.metadata
507
+ if not metadata:
508
+ return False
509
+ for i in metadata.meta_data_parts:
510
+ if i.type == "Quote":
511
+ return True
512
+ return False
513
+
514
+
515
+ async def text_italic_filter(client, update: "Update"):
516
+ message = update.new_message or update.updated_message
517
+ if not message:
518
+ return False
519
+ metadata = message.metadata
520
+ if not metadata:
521
+ return False
522
+ for i in metadata.meta_data_parts:
523
+ if i.type == "Italic":
524
+ return True
525
+ return False
526
+
527
+
528
+ async def text_strike_filter(client, update: "Update"):
529
+ message = update.new_message or update.updated_message
530
+ if not message:
531
+ return False
532
+ metadata = message.metadata
533
+ if not metadata:
534
+ return False
535
+ for i in metadata.meta_data_parts:
536
+ if i.type == "Strike":
537
+ return True
538
+ return False
539
+
540
+
541
+ async def text_spoiler_filter(client, update: "Update"):
542
+ message = update.new_message or update.updated_message
543
+ if not message:
544
+ return False
545
+ metadata = message.metadata
546
+ if not metadata:
547
+ return False
548
+ for i in metadata.meta_data_parts:
549
+ if i.type == "Spoiler":
550
+ return True
551
+ return False
552
+
553
+
554
+ async def text_underline_filter(client, update: "Update"):
555
+ message = update.new_message or update.updated_message
556
+ if not message:
557
+ return False
558
+ metadata = message.metadata
559
+ if not metadata:
560
+ return False
561
+ for i in metadata.meta_data_parts:
562
+ if i.type == "Underline":
563
+ return True
564
+ return False
565
+
566
+
567
+ async def metadata_filter(client, update: "Update"):
568
+ message = update.new_message or update.updated_message
569
+ return bool(message and message.metadata)
570
+
571
+
572
+ async def username_filter(client, update: "Update"):
573
+ return bool(USERNAME_PATTERN.search(update.text or ""))
574
+
575
+
576
+ # Pre-defined filter instances for common use cases
577
+ username = Filter(username_filter)
578
+
579
+ metadata = Filter(metadata_filter)
580
+
581
+ text_bold = Filter(text_bold_filter)
582
+
583
+ text_mono = Filter(text_mono_filter)
584
+
585
+ text_quote = Filter(text_quote_filter)
586
+
587
+ text_italic = Filter(text_italic_filter)
588
+
589
+ text_strike = Filter(text_strike_filter)
590
+
591
+ text_spoiler = Filter(text_spoiler_filter)
592
+
593
+ text_underline = Filter(text_underline_filter)
594
+
595
+ caption = Filter(caption_filter)
596
+
597
+ reply = Filter(reply_filter)
598
+
599
+ url = Filter(url_filter)
600
+ """Filter for messages containing URLs in text."""
601
+
602
+ hyperlink = Filter(hyperlink_filter)
603
+ """Filter for messages with clickable hyperlinks."""
604
+
605
+ mention = Filter(mention_filter)
606
+ """Filter for messages containing user mentions."""
607
+
608
+ text = Filter(text_filter)
609
+ """Filter for text messages."""
610
+
611
+ file = Filter(file_filter)
612
+ """Filter for file messages."""
613
+
614
+ live = Filter(live_filter)
615
+ """Filter for live location messages."""
616
+
617
+ poll = Filter(poll_filter)
618
+ """Filter for poll messages."""
619
+
620
+ contact = Filter(contact_filter)
621
+ """Filter for contact messages."""
622
+
623
+ sticker = Filter(sticker_filter)
624
+ """Filter for sticker messages."""
625
+
626
+ location = Filter(location_filter)
627
+ """Filter for location messages."""
628
+
629
+ forward = Filter(forward_filter)
630
+ """Filter for forwarded messages."""
631
+
632
+ edited = Filter(edited_filter)
633
+ """Filter for edited messages."""
634
+
635
+ group = Filter(group_filter)
636
+ """Filter for group chats."""
637
+
638
+ channel = Filter(channel_filter)
639
+ """Filter for channel chats."""
640
+
641
+ private = Filter(private_filter)
642
+ """Filter for private chats."""
643
+
644
+ gif = Filter(gif_filter)
645
+ """Filter for GIF file messages."""
646
+
647
+ photo = file_type_filter("Image")
648
+ """Filter for image file messages."""
649
+
650
+ video = file_type_filter("Video")
651
+ """Filter for video file messages."""
652
+
653
+ music = file_type_filter("Music")
654
+ """Filter for music/audio file messages."""
655
+
656
+ voice = file_type_filter("Voice")
657
+ """Filter for voice message files."""
658
+
659
+ document = file_type_filter("File")
660
+ """Filter for document file messages."""
661
+
662
+ forwarded_bot = forwarded_filter("Bot")
663
+ """Filter for messages forwarded from bots."""
664
+
665
+ forwarded_user = forwarded_filter("User")
666
+ """Filter for messages forwarded from users."""
667
+
668
+ forwarded_channel = forwarded_filter("Channel")
669
+ """Filter for messages forwarded from channels."""
670
+
671
+
672
+ def command(
673
+ command: Union[str, list[str]],
674
+ prefix: Union[str, list[str]] = "/",
675
+ case_sensitive: bool = False,
676
+ start_with: bool = False
677
+ ) -> bool:
678
+ """
679
+ Create a filter for command messages.
680
+
681
+ Parameters:
682
+ command (str | list[str]):
683
+ The command(s) to match (without prefix).
684
+ prefix (str | list[str], optional):
685
+ The prefix(es) to use. Defaults to "/".
686
+ case_sensitive (bool, optional):
687
+ Whether the match should be case-sensitive. Defaults to False.
688
+
689
+ Returns:
690
+ Filter: A filter that matches command messages.
691
+
692
+ Example:
693
+ .. code-block:: python
694
+ start_cmd = command("start")
695
+ # Matches "/start"
696
+
697
+ multi_cmd = command(["start", "help"], prefix=["/", "!"])
698
+ # Matches "/start", "/help", "!start", "!help"
699
+ """
700
+ commands = command if isinstance(command, list) else [command]
701
+ prefixs = prefix if isinstance(prefix, list) else [prefix]
702
+
703
+ if not case_sensitive:
704
+ commands = [c.lower() for c in commands]
705
+
706
+ command_list = tuple(p + c for p in prefixs for c in commands)
707
+
708
+ async def wrapper(client, update: Update):
709
+ if update.text is None:
710
+ return False
711
+
712
+ text = update.text if case_sensitive else update.text.lower()
713
+ return any(text.startswith(c) if start_with else text == c for c in command_list)
714
+
715
+ return Filter(wrapper)
716
+
717
+
718
+ def chat(chat_id: Union[str, list[str]]) -> bool:
719
+ """
720
+ Create a filter for specific chat IDs.
721
+
722
+ Parameters:
723
+ chat_id (str | list[str]):
724
+ The chat ID(s) to match.
725
+
726
+ Returns:
727
+ Filter: A filter that matches messages from specific chats.
728
+
729
+ Example:
730
+ .. code-block:: python
731
+ specific_chat = chat("b0123456789")
732
+ # Matches messages from chat ID "b0123456789"
733
+
734
+ multiple_chats = chat(["b0123456789", "g0987654321"])
735
+ # Matches messages from either chat
736
+ """
737
+ async def wrapper(client, update: Union["Update", "InlineMessage"]):
738
+ chat_ids = chat_id if isinstance(chat_id, list) else [chat_id]
739
+ return update.chat_id in chat_ids
740
+ return Filter(wrapper)
741
+
742
+
743
+ def regex(
744
+ pattern: Union[str, list[str]],
745
+ flags: int = re.IGNORECASE
746
+ ) -> bool:
747
+ """
748
+ Create a filter for messages matching regular expressions.
749
+
750
+ Parameters:
751
+ pattern (str | list[str]):
752
+ The regex pattern(s) to match.
753
+ flags (int, optional):
754
+ Regex flags. Defaults to re.IGNORECASE.
755
+
756
+ Returns:
757
+ Filter: A filter that matches messages based on regex patterns.
758
+
759
+ Example:
760
+ .. code-block:: python
761
+ hello_filter = regex(r"^hello.*")
762
+ # Matches messages starting with "hello"
763
+
764
+ number_filter = regex([r"\d+", r"number"])
765
+ # Matches messages containing digits or the word "number"
766
+ """
767
+ patterns = pattern if isinstance(pattern, list) else [pattern]
768
+ compiled_patterns = [re.compile(p, flags) for p in patterns]
769
+
770
+ async def wrapper(client, update: Union["Update", "InlineMessage"]):
771
+ text = getattr(update, "text", None)
772
+ if not text:
773
+ return False
774
+ return any(p.search(text) for p in compiled_patterns)
775
+
776
+ return Filter(wrapper)
777
+
778
+
779
+ def button(
780
+ button_id: Union[str, list[str]],
781
+ prefix: Union[str, list[str]] = "",
782
+ case_sensitive: bool = False
783
+ ) -> bool:
784
+ """
785
+ Create a filter for inline button clicks.
786
+
787
+ Parameters:
788
+ button_id (str | list[str]):
789
+ The button ID(s) to match.
790
+ prefix (str | list[str], optional):
791
+ Prefix(es) for button IDs. Defaults to "".
792
+ case_sensitive (bool, optional):
793
+ Whether the match should be case-sensitive. Defaults to False.
794
+
795
+ Returns:
796
+ Filter: A filter that matches inline button clicks.
797
+
798
+ Note:
799
+ Only works with InlineMessage updates.
800
+
801
+ Example:
802
+ .. code-block:: python
803
+ vote_btn = button("vote_yes", prefix="poll_")
804
+ # Matches button with ID "poll_vote_yes"
805
+ """
806
+ button_ids = button_id if isinstance(button_id, list) else [button_id]
807
+ prefixs = prefix if isinstance(prefix, list) else [prefix]
808
+
809
+ if not case_sensitive:
810
+ button_ids = [b.lower() for b in button_ids]
811
+
812
+ button_id_list = tuple(p + b for p in prefixs for b in button_ids)
813
+
814
+ async def wrapper(client, update: "InlineMessage"):
815
+ btn_id = update.aux_data.button_id if case_sensitive else update.aux_data.button_id.lower()
816
+ return any(btn_id.startswith(b) for b in button_id_list)
817
+ return Filter(wrapper)
818
+
819
+
820
+ def state(
821
+ state: Union[str, list[str]],
822
+ prefix: Union[str, list[str]] = "",
823
+ case_sensitive: bool = False
824
+ ) -> bool:
825
+ """
826
+ Create a filter based on user conversation state.
827
+
828
+ Parameters:
829
+ state (str | list[str]):
830
+ The state value(s) to match.
831
+ prefix (str | list[str], optional):
832
+ Prefix(es) for state values. Defaults to "".
833
+ case_sensitive (bool, optional):
834
+ Whether the match should be case-sensitive. Defaults to False.
835
+
836
+ Returns:
837
+ Filter: A filter that matches when the user is in a specific state.
838
+
839
+ Example:
840
+ .. code-block:: python
841
+ waiting_state = state("waiting_for_name")
842
+ # Matches when user's state is "waiting_for_name"
843
+ """
844
+ states = state if isinstance(state, list) else [state]
845
+ prefixs = prefix if isinstance(prefix, list) else [prefix]
846
+
847
+ if not case_sensitive:
848
+ states = [s.lower() for s in states]
849
+
850
+ state_list = tuple(p + s for p in prefixs for s in states)
851
+
852
+ async def wrapper(client: "rubigram.Client", update: Union["Update", "InlineMessage"]):
853
+ stmt = client.state(update.chat_id)
854
+ data = await stmt.get()
855
+ return data.get("state") in state_list
856
+ return Filter(wrapper)
857
+
858
+
859
+ def sender_id(sender_id: Union[str, list[str]]) -> bool:
860
+ sender_ids = sender_id if isinstance(sender_id, list) else [sender_id]
861
+
862
+ async def wrapper(client, update: "Update"):
863
+ message = update.new_message or update.updated_message
864
+ return bool(message and message.sender_id in sender_ids)
865
+ return Filter(wrapper)