pyrobale 0.3.7__tar.gz → 0.3.8.5__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.
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/LICENSE +2 -2
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/PKG-INFO +5 -6
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/README.md +3 -4
- pyrobale-0.3.8.5/examples/command.py +12 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyproject.toml +1 -1
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/StateMachine/__init__.py +21 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/__init__.py +3 -1
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/client/__init__.py +141 -24
- pyrobale-0.3.8.5/pyrobale/exceptions/__init__.py +1 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/exceptions/common.py +6 -0
- pyrobale-0.3.8.5/pyrobale/filters/__init__.py +2 -0
- pyrobale-0.3.8.5/pyrobale/filters/enum_filters.py +12 -0
- pyrobale-0.3.8.5/pyrobale/filters/func_filters.py +17 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/chat.py +11 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/enums.py +1 -9
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/inlinekeyboardmarkup.py +11 -4
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/message.py +6 -8
- pyrobale-0.3.8.5/pyrobale/objects/utils.py +48 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale.png +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobaletext.png +0 -0
- pyrobale-0.3.7/pyrobale/exceptions/__init__.py +0 -0
- pyrobale-0.3.7/pyrobale/objects/utils.py +0 -27
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/.github/ISSUE_TEMPLATE/issue-template-/342/204/271/357/270/217.md" +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/.github/workflows/docs.yml +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/examples/echo_bot.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/examples/handler_system.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/examples/inline_keyboard.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/__init__.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/animation.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/audio.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/callbackquery.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/chatmember.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/chatphoto.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/contact.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/copytextbutton.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/document.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/file.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/inlinekeyboardbutton.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/inputfile.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/inputmedias.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/invoice.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/keyboardbutton.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/labeledprice.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/location.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/messageid.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/photosize.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/precheckoutquery.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/replykeyboardmarkup.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/sticker.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/stickerset.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/successfulpayment.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/update.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/user.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/video.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/voice.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/webappdata.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.8.5}/pyrobale/objects/webappinfo.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
MIT License
|
2
2
|
|
3
|
-
Copyright (c) 2025
|
3
|
+
Copyright (c) 2025 PyroBale Team
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
SOFTWARE.
|
21
|
+
SOFTWARE.
|
@@ -1,13 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pyrobale
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.8.5
|
4
4
|
Summary: A python wrapper for bale api
|
5
5
|
Project-URL: github, https://github.com/pyrobale/pyrobale
|
6
6
|
Project-URL: website, https://pyrobale.github.io
|
7
7
|
Author-email: Ali Safamanesh <darg.q.a.a@gmail.com>, Aydin Rahbaran <codewizaard9@gmail.com>
|
8
8
|
License: MIT License
|
9
9
|
|
10
|
-
Copyright (c) 2025
|
10
|
+
Copyright (c) 2025 PyroBale Team
|
11
11
|
|
12
12
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
13
13
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -49,7 +49,7 @@ A modern, easy-to-use Python wrapper for the Bale Bot API that makes building Ba
|
|
49
49
|
- 📁 **File Handling** - Easy upload and download of media files
|
50
50
|
- 🛡️ **Error Handling** - Comprehensive exception handling
|
51
51
|
- 📖 **Type Hints** - Full typing support for better development experience
|
52
|
-
- ⚡ **Async Support** -
|
52
|
+
- ⚡ **Async Support** - asynchronous operations
|
53
53
|
|
54
54
|
## Installation
|
55
55
|
|
@@ -66,7 +66,7 @@ from pyrobale.objects import Message, UpdatesTypes
|
|
66
66
|
bot = Client("YOUR_BOT_TOKEN")
|
67
67
|
|
68
68
|
@bot.on_message()
|
69
|
-
async def message_handler(message:
|
69
|
+
async def message_handler(message: Message):
|
70
70
|
await message.reply("Hello, world!")
|
71
71
|
|
72
72
|
bot.run()
|
@@ -78,13 +78,12 @@ bot.run()
|
|
78
78
|
```python
|
79
79
|
from pyrobale.objects import *
|
80
80
|
from pyrobale.client import Client, Message, UpdatesTypes
|
81
|
-
import asyncio
|
82
81
|
|
83
82
|
client = Client("YOUR_BOT_TOKEN")
|
84
83
|
|
85
84
|
async def handle_message(message: Message):
|
86
85
|
if message.text == "/start":
|
87
|
-
await message.reply("
|
86
|
+
await message.reply("Hi! Im a pyrobale RoBot!")
|
88
87
|
await client.wait_for(UpdatesTypes.MESSAGE)
|
89
88
|
await message.reply("Okay! wait_for Test Compeleted")
|
90
89
|
|
@@ -13,7 +13,7 @@ A modern, easy-to-use Python wrapper for the Bale Bot API that makes building Ba
|
|
13
13
|
- 📁 **File Handling** - Easy upload and download of media files
|
14
14
|
- 🛡️ **Error Handling** - Comprehensive exception handling
|
15
15
|
- 📖 **Type Hints** - Full typing support for better development experience
|
16
|
-
- ⚡ **Async Support** -
|
16
|
+
- ⚡ **Async Support** - asynchronous operations
|
17
17
|
|
18
18
|
## Installation
|
19
19
|
|
@@ -30,7 +30,7 @@ from pyrobale.objects import Message, UpdatesTypes
|
|
30
30
|
bot = Client("YOUR_BOT_TOKEN")
|
31
31
|
|
32
32
|
@bot.on_message()
|
33
|
-
async def message_handler(message:
|
33
|
+
async def message_handler(message: Message):
|
34
34
|
await message.reply("Hello, world!")
|
35
35
|
|
36
36
|
bot.run()
|
@@ -42,13 +42,12 @@ bot.run()
|
|
42
42
|
```python
|
43
43
|
from pyrobale.objects import *
|
44
44
|
from pyrobale.client import Client, Message, UpdatesTypes
|
45
|
-
import asyncio
|
46
45
|
|
47
46
|
client = Client("YOUR_BOT_TOKEN")
|
48
47
|
|
49
48
|
async def handle_message(message: Message):
|
50
49
|
if message.text == "/start":
|
51
|
-
await message.reply("
|
50
|
+
await message.reply("Hi! Im a pyrobale RoBot!")
|
52
51
|
await client.wait_for(UpdatesTypes.MESSAGE)
|
53
52
|
await message.reply("Okay! wait_for Test Compeleted")
|
54
53
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from pyrobale.client import Client
|
2
|
+
from pyrobale.objects import Message, UpdatesTypes
|
3
|
+
|
4
|
+
token = "YOUR_BOT_TOKEN"
|
5
|
+
|
6
|
+
bot = Client(token)
|
7
|
+
|
8
|
+
@bot.on_command('start')
|
9
|
+
async def start(message: Message):
|
10
|
+
await message.reply("Hello, world!")
|
11
|
+
|
12
|
+
bot.run()
|
@@ -43,3 +43,24 @@ class StateMachine:
|
|
43
43
|
del self.__states[user_id]
|
44
44
|
else:
|
45
45
|
raise KeyError
|
46
|
+
|
47
|
+
def save_local(self, file_name: str):
|
48
|
+
"""Saves the state of all users to a file
|
49
|
+
|
50
|
+
Args:
|
51
|
+
file_name (string): name of file to save the state of users
|
52
|
+
"""
|
53
|
+
with open(file_name, "w") as f:
|
54
|
+
for user_id, state in self.__states.items():
|
55
|
+
f.write(f"{user_id} {state}\n")
|
56
|
+
|
57
|
+
def load_local(self, file_name: str):
|
58
|
+
"""Loads the state of all users from a file
|
59
|
+
|
60
|
+
Args:
|
61
|
+
file_name (string): name of file to load the state of users
|
62
|
+
"""
|
63
|
+
with open(file_name, "r") as f:
|
64
|
+
for line in f:
|
65
|
+
user_id, state = line.split()
|
66
|
+
self.__states[user_id] = state
|
@@ -31,7 +31,7 @@ from pyrobale.objects import Message, UpdatesTypes
|
|
31
31
|
bot = Client("YOUR_BOT_TOKEN")
|
32
32
|
|
33
33
|
@bot.on_message()
|
34
|
-
async def message_handler(message:
|
34
|
+
async def message_handler(message: Message):
|
35
35
|
await message.reply("Hello, world!")
|
36
36
|
|
37
37
|
bot.run()
|
@@ -116,8 +116,10 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
116
116
|
- 📖 [Documentation](https://pyrobale.readthedocs.io)
|
117
117
|
- 🐛 [Issue Tracker](https://github.com/pyrobale/pyrobale/issues)
|
118
118
|
- 💬 [Discussions](https://github.com/pyrobale/pyrobale/discussions)
|
119
|
+
|
119
120
|
"""
|
120
121
|
|
121
122
|
from .objects.utils import *
|
122
123
|
from .exceptions import *
|
123
124
|
from .objects import *
|
125
|
+
from .client import Client
|
@@ -40,7 +40,9 @@ from ..objects.utils import *
|
|
40
40
|
import asyncio
|
41
41
|
from enum import Enum
|
42
42
|
from ..objects.enums import UpdatesTypes, ChatAction, ChatType
|
43
|
+
from ..filters import Filters, equals
|
43
44
|
from ..StateMachine import StateMachine
|
45
|
+
from ..exceptions import NotFoundException, InvalidTokenException, PyroBaleException
|
44
46
|
|
45
47
|
|
46
48
|
class Client:
|
@@ -81,7 +83,12 @@ class Client:
|
|
81
83
|
self.requests_base
|
82
84
|
+ f"/getUpdates?offset={offset}&limit={limit}&timeout={timeout}"
|
83
85
|
)
|
84
|
-
|
86
|
+
if data['ok']:
|
87
|
+
if 'result' in data.keys():
|
88
|
+
return data["result"]
|
89
|
+
else:
|
90
|
+
if data['error_code'] == 403:
|
91
|
+
raise InvalidTokenException("Forbidden 403 : --ENTERED TOKEN IS NOT VALID--")
|
85
92
|
|
86
93
|
async def set_webhook(self, url: str) -> bool:
|
87
94
|
"""Set the webhook for the bot.
|
@@ -421,6 +428,7 @@ class Client:
|
|
421
428
|
chat_id: int,
|
422
429
|
latitude: float,
|
423
430
|
longitude: float,
|
431
|
+
horizontal_accuracy: Optional[float] = None,
|
424
432
|
reply_to_message_id: Optional[int] = None,
|
425
433
|
reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
|
426
434
|
) -> Message:
|
@@ -441,6 +449,7 @@ class Client:
|
|
441
449
|
"chat_id": chat_id,
|
442
450
|
"latitude": latitude,
|
443
451
|
"longitude": longitude,
|
452
|
+
"horizontal_accuracy": horizontal_accuracy,
|
444
453
|
"reply_to_message_id": reply_to_message_id,
|
445
454
|
"reply_markup": reply_markup.to_dict() if reply_markup else None,
|
446
455
|
},
|
@@ -507,6 +516,9 @@ class Client:
|
|
507
516
|
Returns:
|
508
517
|
Message: returns the sent message with invoice
|
509
518
|
"""
|
519
|
+
new_prices = []
|
520
|
+
for price in prices:
|
521
|
+
new_prices.append(price.json)
|
510
522
|
data = await make_post(
|
511
523
|
self.requests_base + "/sendInvoice",
|
512
524
|
data={
|
@@ -515,7 +527,7 @@ class Client:
|
|
515
527
|
"description": description,
|
516
528
|
"payload": payload,
|
517
529
|
"provider_token": provider_token,
|
518
|
-
"prices":
|
530
|
+
"prices": new_prices,
|
519
531
|
"photo_url": photo_url,
|
520
532
|
"reply_to_message_id": reply_to_message_id,
|
521
533
|
},
|
@@ -683,6 +695,22 @@ class Client:
|
|
683
695
|
self.requests_base + "/leaveChat", data={"chat_id": chat_id}
|
684
696
|
)
|
685
697
|
return data.get("ok", False)
|
698
|
+
|
699
|
+
async def is_joined(self, user_id: int, chat_id: int) -> bool:
|
700
|
+
"""Check if a user is joined to a chat.
|
701
|
+
|
702
|
+
Args:
|
703
|
+
user_id (int): Unique identifier for the target chat
|
704
|
+
chat_id (int): Unique identifier for the target chat
|
705
|
+
|
706
|
+
Returns:
|
707
|
+
bool: True if the user is joined to the chat, False otherwise
|
708
|
+
"""
|
709
|
+
data = await make_post(
|
710
|
+
self.requests_base + "/getChatMember",
|
711
|
+
data={"chat_id": chat_id, "user_id": user_id},
|
712
|
+
)
|
713
|
+
return data.get("result", {}).get("status") in ["member", "creator", "administrator"]
|
686
714
|
|
687
715
|
async def get_chat(self, chat_id: int) -> Chat:
|
688
716
|
"""Get up to date information about the chat.
|
@@ -922,51 +950,136 @@ class Client:
|
|
922
950
|
if not future.done():
|
923
951
|
future.set_result(event)
|
924
952
|
self._waiters.remove(waiter)
|
925
|
-
return
|
953
|
+
return
|
926
954
|
|
927
955
|
for handler in self.handlers:
|
928
956
|
update_type = handler["type"].value
|
929
957
|
if update_type in update:
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
958
|
+
raw_event = update[update_type]
|
959
|
+
event = self._convert_event(handler["type"], raw_event)
|
960
|
+
|
961
|
+
if handler["type"] == UpdatesTypes.COMMAND:
|
962
|
+
if hasattr(event, 'text') and event.text and event.text.startswith('/'):
|
963
|
+
command_text = event.text[1:]
|
964
|
+
command_parts = command_text.split()
|
965
|
+
if command_parts:
|
966
|
+
actual_command = command_parts[0]
|
967
|
+
expected_command = handler.get("command", "")
|
968
|
+
|
969
|
+
if actual_command != expected_command:
|
970
|
+
continue
|
971
|
+
|
972
|
+
flt = handler.get("filter")
|
973
|
+
if flt is not None:
|
974
|
+
if callable(flt):
|
975
|
+
try:
|
976
|
+
if not flt(event):
|
977
|
+
continue
|
978
|
+
except Exception as e:
|
979
|
+
print(f"[Filter Error] {e}")
|
980
|
+
continue
|
981
|
+
elif isinstance(flt, Filters):
|
982
|
+
if not hasattr(event, flt.value):
|
983
|
+
continue
|
984
|
+
|
934
985
|
if asyncio.iscoroutinefunction(handler["callback"]):
|
935
986
|
asyncio.create_task(handler["callback"](event))
|
936
987
|
else:
|
937
988
|
handler["callback"](event)
|
938
989
|
|
990
|
+
|
991
|
+
|
939
992
|
def base_handler_decorator(self, update_type: UpdatesTypes):
|
993
|
+
"""Base decorator for handling different types of updates.
|
994
|
+
|
995
|
+
Args:
|
996
|
+
update_type (UpdatesTypes): The type of update to handle.
|
997
|
+
|
998
|
+
Returns:
|
999
|
+
Callable: A decorator function that registers the callback for the specified update type.
|
1000
|
+
"""
|
1001
|
+
def wrapper(filter: Optional[Filters] = None):
|
1002
|
+
def decorator(callback: Callable[[Any], Union[None, Awaitable[None]]]):
|
1003
|
+
self.add_handler(update_type, callback, filter)
|
1004
|
+
return callback
|
1005
|
+
return decorator
|
1006
|
+
return wrapper
|
1007
|
+
|
1008
|
+
def on_command(self, command: str, filter: Optional[Filters] = None):
|
1009
|
+
"""Decorator for handling command updates.
|
1010
|
+
|
1011
|
+
Args:
|
1012
|
+
command (str): The command to handle.
|
1013
|
+
filter (Optional[Filters]): An optional filter to apply to the command.
|
1014
|
+
Returns:
|
1015
|
+
Callable: A decorator function that registers the callback for the specified command.
|
1016
|
+
"""
|
940
1017
|
def decorator(callback: Callable[[Any], Union[None, Awaitable[None]]]):
|
941
|
-
self.add_handler(
|
1018
|
+
self.add_handler(UpdatesTypes.COMMAND, callback, filter, command=command)
|
942
1019
|
return callback
|
943
|
-
|
944
1020
|
return decorator
|
945
1021
|
|
946
|
-
|
947
|
-
|
1022
|
+
|
1023
|
+
def on_message(self, filter: Optional[Filters] = None):
|
1024
|
+
"""Decorator for handling new message updates.
|
1025
|
+
|
1026
|
+
Returns:
|
1027
|
+
Callable: A decorator function that registers the callback for message updates.
|
1028
|
+
"""
|
1029
|
+
return self.base_handler_decorator(UpdatesTypes.MESSAGE)(filter)
|
1030
|
+
|
948
1031
|
|
949
1032
|
def on_edited_message(self):
|
1033
|
+
"""Decorator for handling edited message updates.
|
1034
|
+
|
1035
|
+
Returns:
|
1036
|
+
Callable: A decorator function that registers the callback for edited message updates.
|
1037
|
+
"""
|
950
1038
|
return self.base_handler_decorator(UpdatesTypes.MESSAGE_EDITED)
|
951
1039
|
|
952
1040
|
def on_callback_query(self):
|
1041
|
+
"""Decorator for handling callback query updates.
|
1042
|
+
|
1043
|
+
Returns:
|
1044
|
+
Callable: A decorator function that registers the callback for callback query updates.
|
1045
|
+
"""
|
953
1046
|
return self.base_handler_decorator(UpdatesTypes.CALLBACK_QUERY)
|
954
1047
|
|
955
1048
|
def on_new_members(self):
|
1049
|
+
"""Decorator for handling new chat members updates.
|
1050
|
+
|
1051
|
+
Returns:
|
1052
|
+
Callable: A decorator function that registers the callback for new members updates.
|
1053
|
+
"""
|
956
1054
|
return self.base_handler_decorator(UpdatesTypes.MEMBER_JOINED)
|
957
1055
|
|
958
|
-
def
|
1056
|
+
def on_members_left(self):
|
959
1057
|
return self.base_handler_decorator(UpdatesTypes.MEMBER_LEFT)
|
960
1058
|
|
961
1059
|
def on_pre_checkout_query(self):
|
1060
|
+
"""Decorator for handling pre-checkout query updates.
|
1061
|
+
|
1062
|
+
Returns:
|
1063
|
+
Callable: A decorator function that registers the callback for pre-checkout query updates.
|
1064
|
+
"""
|
962
1065
|
return self.base_handler_decorator(UpdatesTypes.PRE_CHECKOUT_QUERY)
|
963
1066
|
|
964
1067
|
def on_photo(self):
|
1068
|
+
"""Decorator for handling photo updates.
|
1069
|
+
|
1070
|
+
Returns:
|
1071
|
+
Callable: A decorator function that registers the callback for photo updates.
|
1072
|
+
"""
|
965
1073
|
return self.base_handler_decorator(UpdatesTypes.PHOTO)
|
966
1074
|
|
967
1075
|
def on_successful_payment(self):
|
968
|
-
|
1076
|
+
"""Decorator for handling successful payment updates.
|
969
1077
|
|
1078
|
+
Returns:
|
1079
|
+
Callable: A decorator function that registers the callback for successful payment updates.
|
1080
|
+
"""
|
1081
|
+
return self.base_handler_decorator(UpdatesTypes.SUCCESSFUL_PAYMENT)
|
1082
|
+
|
970
1083
|
def _convert_event(self, handler_type: UpdatesTypes, event: Dict[str, Any]) -> Any:
|
971
1084
|
"""Convert raw event data to appropriate object type.
|
972
1085
|
|
@@ -983,6 +1096,7 @@ class Client:
|
|
983
1096
|
UpdatesTypes.MEMBER_JOINED,
|
984
1097
|
UpdatesTypes.MEMBER_LEFT,
|
985
1098
|
UpdatesTypes.SUCCESSFUL_PAYMENT,
|
1099
|
+
UpdatesTypes.COMMAND
|
986
1100
|
):
|
987
1101
|
if (
|
988
1102
|
event.get("new_chat_member", False)
|
@@ -1033,18 +1147,22 @@ class Client:
|
|
1033
1147
|
|
1034
1148
|
return event
|
1035
1149
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
update_type: UpdatesTypes,
|
1039
|
-
callback: Callable[[Any], Union[None, Awaitable[None]]],
|
1040
|
-
) -> None:
|
1150
|
+
|
1151
|
+
def add_handler(self, update_type, callback, filter: Optional[Filters] = None, **kwargs):
|
1041
1152
|
"""Register a handler for specific update type.
|
1042
1153
|
|
1043
1154
|
Args:
|
1044
1155
|
update_type (UpdatesTypes): Type of update to handle
|
1045
1156
|
callback (Callable): Function to call when update is received
|
1046
1157
|
"""
|
1047
|
-
|
1158
|
+
data = {
|
1159
|
+
"type": update_type,
|
1160
|
+
"callback": callback,
|
1161
|
+
"filter": filter,
|
1162
|
+
}
|
1163
|
+
data.update(kwargs)
|
1164
|
+
self.handlers.append(data)
|
1165
|
+
|
1048
1166
|
|
1049
1167
|
def remove_handler(
|
1050
1168
|
self, callback: Callable[[Any], Union[None, Awaitable[None]]]
|
@@ -1077,17 +1195,16 @@ class Client:
|
|
1077
1195
|
|
1078
1196
|
self.running = True
|
1079
1197
|
while self.running:
|
1080
|
-
|
1198
|
+
try:
|
1081
1199
|
updates = await self.get_updates(
|
1082
1200
|
offset=self.last_update_id, limit=limit, timeout=timeout
|
1083
1201
|
)
|
1084
1202
|
|
1085
1203
|
for update in updates:
|
1086
1204
|
await self.process_update(update)
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
await asyncio.sleep(5)
|
1205
|
+
|
1206
|
+
except Exception as e:
|
1207
|
+
raise e
|
1091
1208
|
|
1092
1209
|
async def stop_polling(self) -> None:
|
1093
1210
|
"""Stop polling updates."""
|
@@ -0,0 +1 @@
|
|
1
|
+
from .common import *
|
@@ -0,0 +1,17 @@
|
|
1
|
+
def equals(expected_text: str):
|
2
|
+
"""
|
3
|
+
Check if the event text or caption is equal to the expected text.
|
4
|
+
|
5
|
+
Args:
|
6
|
+
expected_text (str): The expected text to compare with.
|
7
|
+
|
8
|
+
Returns:
|
9
|
+
Callable: A function that checks if the event text or caption is equal to the expected text.
|
10
|
+
"""
|
11
|
+
def check(event):
|
12
|
+
try:
|
13
|
+
return getattr(event, "text", None) == expected_text or getattr(event, "caption", None) == expected_text
|
14
|
+
except:
|
15
|
+
return False
|
16
|
+
return check
|
17
|
+
|
@@ -386,6 +386,17 @@ class Chat:
|
|
386
386
|
bool: True on success
|
387
387
|
"""
|
388
388
|
return await self.client.leave_chat(chat_id=self.id)
|
389
|
+
|
390
|
+
async def is_joined(self, user_id: int) -> bool:
|
391
|
+
"""Check if a user is joined to the chat.
|
392
|
+
|
393
|
+
Parameters:
|
394
|
+
user_id (int): Unique identifier of the target user
|
395
|
+
|
396
|
+
Returns:
|
397
|
+
bool: True if the user is joined to the chat, False otherwise
|
398
|
+
"""
|
399
|
+
return await self.client.is_joined(user_id, self.id)
|
389
400
|
|
390
401
|
async def pin(self, message_id: int) -> bool:
|
391
402
|
"""Pin a message in the chat.
|
@@ -11,18 +11,10 @@ class UpdatesTypes(Enum):
|
|
11
11
|
MEMBER_JOINED = "member_joined"
|
12
12
|
MEMBER_LEFT = "member_left"
|
13
13
|
SUCCESSFUL_PAYMENT = "successful_payment"
|
14
|
+
COMMAND = "message"
|
14
15
|
|
15
16
|
|
16
|
-
class Filters(Enum):
|
17
|
-
"""Filters that you can use in handlers"""
|
18
17
|
|
19
|
-
TEXT = "text"
|
20
|
-
PHOTO = "photo"
|
21
|
-
VIDEO = "video"
|
22
|
-
AUDIO = "audio"
|
23
|
-
VOICE = "voice"
|
24
|
-
CONTACT = "contact"
|
25
|
-
LOCATION = "location"
|
26
18
|
|
27
19
|
|
28
20
|
class ChatAction(Enum):
|
@@ -15,7 +15,7 @@ class InlineKeyboardMarkup:
|
|
15
15
|
callback_data: Optional[str] = None,
|
16
16
|
url: Optional[str] = None,
|
17
17
|
web_app: Optional[Union["WebAppInfo", str]] = None,
|
18
|
-
copy_text_button: Optional["CopyTextButton"] = None,
|
18
|
+
copy_text_button: Optional[Union["CopyTextButton", str]] = None,
|
19
19
|
**kwargs
|
20
20
|
) -> "InlineKeyboardMarkup":
|
21
21
|
"""Adds a button to the inline keyboard.
|
@@ -24,8 +24,8 @@ class InlineKeyboardMarkup:
|
|
24
24
|
text (str): The text to display on the button.
|
25
25
|
callback_data (str, optional): The callback data to send when the button is clicked.
|
26
26
|
url (str, optional): The URL to open when the button is clicked.
|
27
|
-
web_app (WebAppInfo, optional): The web app to open when the button is clicked.
|
28
|
-
copy_text_button (CopyTextButton, optional): The copy text button to add to the button.
|
27
|
+
web_app (WebAppInfo OR string, optional): The web app to open when the button is clicked.
|
28
|
+
copy_text_button (CopyTextButton OR string, optional): The copy text button to add to the button.
|
29
29
|
|
30
30
|
Returns:
|
31
31
|
InlineKeyboardMarkup: The updated InlineKeyboardMarkup object.
|
@@ -59,7 +59,14 @@ class InlineKeyboardMarkup:
|
|
59
59
|
"web_app must be a string URL or an object with to_dict() method."
|
60
60
|
)
|
61
61
|
elif copy_text_button:
|
62
|
-
|
62
|
+
if isinstance(copy_text_button, str):
|
63
|
+
button["copy_text"] = {"text": copy_text_button}
|
64
|
+
elif hasattr(copy_text_button, "text"):
|
65
|
+
button["copy_text"] = {"text": copy_text_button.text}
|
66
|
+
else:
|
67
|
+
raise ValueError(
|
68
|
+
"copy_text_button must be a string or an object with a 'text' attribute."
|
69
|
+
)
|
63
70
|
|
64
71
|
if not self.inline_keyboard:
|
65
72
|
self.inline_keyboard.append([])
|
@@ -124,6 +124,7 @@ class Message:
|
|
124
124
|
reply_markup: Inline keyboard markup
|
125
125
|
**kwargs: Additional keyword arguments
|
126
126
|
"""
|
127
|
+
self.client: Client = kwargs.get("kwargs", {}).get("client")
|
127
128
|
self.id: int = message_id
|
128
129
|
self.user: "User" = (
|
129
130
|
User(**from_user, kwargs={"client": self.client}) if from_user else None
|
@@ -154,7 +155,7 @@ class Message:
|
|
154
155
|
self.successful_payment: Optional["SuccessfulPayment"] = successful_payment
|
155
156
|
self.web_app_data: Optional["WebAppData"] = web_app_data
|
156
157
|
self.reply_markup: Optional["InlineKeyboardMarkup"] = reply_markup
|
157
|
-
|
158
|
+
|
158
159
|
|
159
160
|
async def reply(
|
160
161
|
self,
|
@@ -313,6 +314,7 @@ class Message:
|
|
313
314
|
self,
|
314
315
|
latitude: float,
|
315
316
|
longitude: float,
|
317
|
+
horizontal_accuracy: Optional[float] = None,
|
316
318
|
reply_markup: Union["ReplyKeyboardMarkup", "InlineKeyboardMarkup"] = None,
|
317
319
|
):
|
318
320
|
"""Reply with a location to the current message.
|
@@ -327,6 +329,7 @@ class Message:
|
|
327
329
|
self.chat.id,
|
328
330
|
latitude=latitude,
|
329
331
|
longitude=longitude,
|
332
|
+
horizontal_accuracy=horizontal_accuracy,
|
330
333
|
reply_to_message_id=self.id,
|
331
334
|
reply_markup=reply_markup,
|
332
335
|
)
|
@@ -359,9 +362,7 @@ class Message:
|
|
359
362
|
description: str,
|
360
363
|
payload: str,
|
361
364
|
provider_token: str,
|
362
|
-
|
363
|
-
prices: list,
|
364
|
-
reply_markup: Union["ReplyKeyboardMarkup", "InlineKeyboardMarkup"] = None,
|
365
|
+
prices: list
|
365
366
|
):
|
366
367
|
"""Reply with an invoice to the current message.
|
367
368
|
|
@@ -370,7 +371,6 @@ class Message:
|
|
370
371
|
description: Product description
|
371
372
|
payload: Bot-defined invoice payload
|
372
373
|
provider_token: Payment provider token
|
373
|
-
currency: Three-letter ISO 4217 currency code
|
374
374
|
prices: Price breakdown (amount in smallest units)
|
375
375
|
reply_markup: Optional keyboard markup
|
376
376
|
"""
|
@@ -381,7 +381,5 @@ class Message:
|
|
381
381
|
description=description,
|
382
382
|
payload=payload,
|
383
383
|
provider_token=provider_token,
|
384
|
-
|
385
|
-
prices=prices,
|
386
|
-
reply_markup=reply_markup,
|
384
|
+
prices=prices
|
387
385
|
)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
from ..exceptions import *
|
2
|
+
import aiohttp
|
3
|
+
|
4
|
+
|
5
|
+
def build_api_url(base: str, endpoint: str) -> str:
|
6
|
+
return f"{base}/{endpoint}"
|
7
|
+
|
8
|
+
|
9
|
+
async def make_post(url: str, data: dict = None, headers: dict = None) -> dict:
|
10
|
+
async with aiohttp.ClientSession() as session:
|
11
|
+
async with session.post(url, json=data, headers=headers) as response:
|
12
|
+
json = await response.json()
|
13
|
+
if json['ok']:
|
14
|
+
if 'result' in json.keys():
|
15
|
+
return json
|
16
|
+
else:
|
17
|
+
if json['error_code'] == 404:
|
18
|
+
raise NotFoundException(f"Error not found 404 : {json['description'] if json['description'] else 'No description returned in error'}")
|
19
|
+
elif json['error_code'] == 403:
|
20
|
+
raise ForbiddenException(f"Error Forbidden 403 : {json['description'] if json['description'] else 'No description returned in error'}")
|
21
|
+
else:
|
22
|
+
raise PyroBaleException(f"unknown error : {json['description'] if json['description'] else 'No description!'}")
|
23
|
+
|
24
|
+
|
25
|
+
async def make_get(url: str, headers: dict = None) -> dict:
|
26
|
+
async with aiohttp.ClientSession() as session:
|
27
|
+
async with session.get(url, headers=headers) as response:
|
28
|
+
json = await response.json()
|
29
|
+
if json['ok']:
|
30
|
+
if 'result' in json.keys():
|
31
|
+
return json
|
32
|
+
else:
|
33
|
+
if json['error_code'] == 404:
|
34
|
+
raise NotFoundException(f"Error not found 404 : {json['description'] if json['description'] else 'No description returned in error'}")
|
35
|
+
elif json['error_code'] == 403:
|
36
|
+
raise ForbiddenException(f"Error Forbidden 403 : {json['description'] if json['description'] else 'No description returned in error'}")
|
37
|
+
else:
|
38
|
+
raise PyroBaleException(f"unknown error : {json['description'] if json['description'] else 'No description'}")
|
39
|
+
|
40
|
+
|
41
|
+
def pythonize(dictionary: dict) -> dict:
|
42
|
+
"""Converts a dictionary with keys in snake_case to camelCase."""
|
43
|
+
result = {}
|
44
|
+
for key, value in dictionary.items():
|
45
|
+
if key == "from":
|
46
|
+
key = "from_user"
|
47
|
+
result[key] = value
|
48
|
+
return result
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,27 +0,0 @@
|
|
1
|
-
import aiohttp
|
2
|
-
|
3
|
-
|
4
|
-
def build_api_url(base: str, endpoint: str) -> str:
|
5
|
-
return f"{base}/{endpoint}"
|
6
|
-
|
7
|
-
|
8
|
-
async def make_post(url: str, data: dict = None, headers: dict = None) -> dict:
|
9
|
-
async with aiohttp.ClientSession() as session:
|
10
|
-
async with session.post(url, json=data, headers=headers) as response:
|
11
|
-
return await response.json()
|
12
|
-
|
13
|
-
|
14
|
-
async def make_get(url: str, headers: dict = None) -> dict:
|
15
|
-
async with aiohttp.ClientSession() as session:
|
16
|
-
async with session.get(url, headers=headers) as response:
|
17
|
-
return await response.json()
|
18
|
-
|
19
|
-
|
20
|
-
def pythonize(dictionary: dict) -> dict:
|
21
|
-
"""Converts a dictionary with keys in snake_case to camelCase."""
|
22
|
-
result = {}
|
23
|
-
for key, value in dictionary.items():
|
24
|
-
if key == "from":
|
25
|
-
key = "from_user"
|
26
|
-
result[key] = value
|
27
|
-
return result
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|