pyrobale 0.3.7__tar.gz → 0.3.9__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.9}/.github/workflows/docs.yml +1 -1
- pyrobale-0.3.9/.github/workflows/pypi.yml +36 -0
- pyrobale-0.3.9/.gitignore +1 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/LICENSE +2 -2
- {pyrobale-0.3.7 → pyrobale-0.3.9}/PKG-INFO +7 -7
- {pyrobale-0.3.7 → pyrobale-0.3.9}/README.md +3 -4
- pyrobale-0.3.9/examples/command.py +12 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyproject.toml +4 -4
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/StateMachine/__init__.py +21 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/__init__.py +3 -1
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/client/__init__.py +250 -27
- pyrobale-0.3.9/pyrobale/exceptions/__init__.py +1 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/exceptions/common.py +6 -0
- pyrobale-0.3.9/pyrobale/filters/__init__.py +2 -0
- pyrobale-0.3.9/pyrobale/filters/enum_filters.py +12 -0
- pyrobale-0.3.9/pyrobale/filters/func_filters.py +17 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/chat.py +11 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/enums.py +1 -9
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/inlinekeyboardmarkup.py +11 -4
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/message.py +6 -8
- pyrobale-0.3.9/pyrobale/objects/peerdata.py +25 -0
- pyrobale-0.3.9/pyrobale/objects/utils.py +48 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale.png +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobaletext.png +0 -0
- pyrobale-0.3.9/version.py +2 -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.9}/.github/ISSUE_TEMPLATE/issue-template-/342/204/271/357/270/217.md" +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/examples/echo_bot.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/examples/handler_system.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/examples/inline_keyboard.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/__init__.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/animation.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/audio.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/callbackquery.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/chatmember.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/chatphoto.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/contact.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/copytextbutton.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/document.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/file.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/inlinekeyboardbutton.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/inputfile.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/inputmedias.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/invoice.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/keyboardbutton.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/labeledprice.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/location.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/messageid.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/photosize.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/precheckoutquery.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/replykeyboardmarkup.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/sticker.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/stickerset.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/successfulpayment.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/update.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/user.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/video.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/voice.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/webappdata.py +0 -0
- {pyrobale-0.3.7 → pyrobale-0.3.9}/pyrobale/objects/webappinfo.py +0 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
name: Publish to PyPI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ main, master ]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
deploy:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v4
|
13
|
+
|
14
|
+
- name: Set up Python
|
15
|
+
uses: actions/setup-python@v4
|
16
|
+
with:
|
17
|
+
python-version: '3.x'
|
18
|
+
|
19
|
+
- name: Extract version info
|
20
|
+
id: version
|
21
|
+
run: |
|
22
|
+
STABLE=$(python -c "from version import stable; print(str(stable).lower())")
|
23
|
+
VERSION=$(python -c "from version import version; print(version)")
|
24
|
+
echo "stable=$STABLE" >> $GITHUB_OUTPUT
|
25
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
26
|
+
echo "Stable: $STABLE, Version: $VERSION"
|
27
|
+
|
28
|
+
- name: Build and publish
|
29
|
+
if: steps.version.outputs.stable == 'true'
|
30
|
+
run: |
|
31
|
+
pip install build twine
|
32
|
+
python -m build
|
33
|
+
twine upload dist/* --username __token__ --password ${{ secrets.PYPI }}
|
34
|
+
env:
|
35
|
+
TWINE_USERNAME: __token__
|
36
|
+
TWINE_PASSWORD: ${{ secrets.PYPI }}
|
@@ -0,0 +1 @@
|
|
1
|
+
dist
|
@@ -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.9
|
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
|
-
Author-email: Ali Safamanesh <
|
7
|
+
Author-email: Ali Safamanesh <daradege@proton.me>, 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
|
@@ -32,6 +32,7 @@ Classifier: Operating System :: OS Independent
|
|
32
32
|
Classifier: Programming Language :: Python :: 3
|
33
33
|
Requires-Python: >=3.9
|
34
34
|
Requires-Dist: aiohttp
|
35
|
+
Requires-Dist: beautifulsoup4
|
35
36
|
Description-Content-Type: text/markdown
|
36
37
|
|
37
38
|

|
@@ -49,7 +50,7 @@ A modern, easy-to-use Python wrapper for the Bale Bot API that makes building Ba
|
|
49
50
|
- 📁 **File Handling** - Easy upload and download of media files
|
50
51
|
- 🛡️ **Error Handling** - Comprehensive exception handling
|
51
52
|
- 📖 **Type Hints** - Full typing support for better development experience
|
52
|
-
- ⚡ **Async Support** -
|
53
|
+
- ⚡ **Async Support** - asynchronous operations
|
53
54
|
|
54
55
|
## Installation
|
55
56
|
|
@@ -66,7 +67,7 @@ from pyrobale.objects import Message, UpdatesTypes
|
|
66
67
|
bot = Client("YOUR_BOT_TOKEN")
|
67
68
|
|
68
69
|
@bot.on_message()
|
69
|
-
async def message_handler(message:
|
70
|
+
async def message_handler(message: Message):
|
70
71
|
await message.reply("Hello, world!")
|
71
72
|
|
72
73
|
bot.run()
|
@@ -78,13 +79,12 @@ bot.run()
|
|
78
79
|
```python
|
79
80
|
from pyrobale.objects import *
|
80
81
|
from pyrobale.client import Client, Message, UpdatesTypes
|
81
|
-
import asyncio
|
82
82
|
|
83
83
|
client = Client("YOUR_BOT_TOKEN")
|
84
84
|
|
85
85
|
async def handle_message(message: Message):
|
86
86
|
if message.text == "/start":
|
87
|
-
await message.reply("
|
87
|
+
await message.reply("Hi! Im a pyrobale RoBot!")
|
88
88
|
await client.wait_for(UpdatesTypes.MESSAGE)
|
89
89
|
await message.reply("Okay! wait_for Test Compeleted")
|
90
90
|
|
@@ -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()
|
@@ -1,9 +1,9 @@
|
|
1
1
|
[project]
|
2
2
|
name = "pyrobale"
|
3
|
-
version = "0.3.
|
3
|
+
version = "0.3.9"
|
4
4
|
authors = [
|
5
|
-
{ name = "Ali Safamanesh", email = "
|
6
|
-
{ name = "Aydin Rahbaran", email = "codewizaard9@gmail.com"}
|
5
|
+
{ name = "Ali Safamanesh", email = "daradege@proton.me" },
|
6
|
+
{ name = "Aydin Rahbaran", email = "codewizaard9@gmail.com"}
|
7
7
|
]
|
8
8
|
description = "A python wrapper for bale api"
|
9
9
|
readme = "README.md"
|
@@ -14,7 +14,7 @@ classifiers = [
|
|
14
14
|
"Operating System :: OS Independent",
|
15
15
|
]
|
16
16
|
license = { file = "LICENSE" }
|
17
|
-
dependencies = ["aiohttp"]
|
17
|
+
dependencies = ["aiohttp", "beautifulsoup4"]
|
18
18
|
[project.urls]
|
19
19
|
github = "https://github.com/pyrobale/pyrobale"
|
20
20
|
website = "https://pyrobale.github.io"
|
@@ -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
|
@@ -37,11 +37,16 @@ from ..objects.voice import Voice
|
|
37
37
|
from ..objects.webappdata import WebAppData
|
38
38
|
from ..objects.webappinfo import WebAppInfo
|
39
39
|
from ..objects.utils import *
|
40
|
-
import asyncio
|
41
|
-
from enum import Enum
|
42
40
|
from ..objects.enums import UpdatesTypes, ChatAction, ChatType
|
41
|
+
from ..objects.peerdata import PeerData
|
42
|
+
from ..filters import Filters, equals
|
43
43
|
from ..StateMachine import StateMachine
|
44
|
+
from ..exceptions import NotFoundException, InvalidTokenException, PyroBaleException
|
44
45
|
|
46
|
+
from enum import Enum
|
47
|
+
import asyncio
|
48
|
+
from bs4 import BeautifulSoup
|
49
|
+
from json import loads, JSONDecodeError
|
45
50
|
|
46
51
|
class Client:
|
47
52
|
"""A client for interacting with the Bale messenger API.
|
@@ -62,6 +67,9 @@ class Client:
|
|
62
67
|
self.last_update_id = 0
|
63
68
|
self.state_machine = StateMachine()
|
64
69
|
|
70
|
+
self.check_defined_message = True
|
71
|
+
self.defined_messages = {}
|
72
|
+
|
65
73
|
async def get_updates(
|
66
74
|
self,
|
67
75
|
offset: Optional[int] = None,
|
@@ -81,7 +89,12 @@ class Client:
|
|
81
89
|
self.requests_base
|
82
90
|
+ f"/getUpdates?offset={offset}&limit={limit}&timeout={timeout}"
|
83
91
|
)
|
84
|
-
|
92
|
+
if data['ok']:
|
93
|
+
if 'result' in data.keys():
|
94
|
+
return data["result"]
|
95
|
+
else:
|
96
|
+
if data['error_code'] == 403:
|
97
|
+
raise InvalidTokenException("Forbidden 403 : --ENTERED TOKEN IS NOT VALID--")
|
85
98
|
|
86
99
|
async def set_webhook(self, url: str) -> bool:
|
87
100
|
"""Set the webhook for the bot.
|
@@ -421,6 +434,7 @@ class Client:
|
|
421
434
|
chat_id: int,
|
422
435
|
latitude: float,
|
423
436
|
longitude: float,
|
437
|
+
horizontal_accuracy: Optional[float] = None,
|
424
438
|
reply_to_message_id: Optional[int] = None,
|
425
439
|
reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
|
426
440
|
) -> Message:
|
@@ -441,6 +455,7 @@ class Client:
|
|
441
455
|
"chat_id": chat_id,
|
442
456
|
"latitude": latitude,
|
443
457
|
"longitude": longitude,
|
458
|
+
"horizontal_accuracy": horizontal_accuracy,
|
444
459
|
"reply_to_message_id": reply_to_message_id,
|
445
460
|
"reply_markup": reply_markup.to_dict() if reply_markup else None,
|
446
461
|
},
|
@@ -507,6 +522,9 @@ class Client:
|
|
507
522
|
Returns:
|
508
523
|
Message: returns the sent message with invoice
|
509
524
|
"""
|
525
|
+
new_prices = []
|
526
|
+
for price in prices:
|
527
|
+
new_prices.append(price.json)
|
510
528
|
data = await make_post(
|
511
529
|
self.requests_base + "/sendInvoice",
|
512
530
|
data={
|
@@ -515,7 +533,7 @@ class Client:
|
|
515
533
|
"description": description,
|
516
534
|
"payload": payload,
|
517
535
|
"provider_token": provider_token,
|
518
|
-
"prices":
|
536
|
+
"prices": new_prices,
|
519
537
|
"photo_url": photo_url,
|
520
538
|
"reply_to_message_id": reply_to_message_id,
|
521
539
|
},
|
@@ -683,6 +701,22 @@ class Client:
|
|
683
701
|
self.requests_base + "/leaveChat", data={"chat_id": chat_id}
|
684
702
|
)
|
685
703
|
return data.get("ok", False)
|
704
|
+
|
705
|
+
async def is_joined(self, user_id: int, chat_id: int) -> bool:
|
706
|
+
"""Check if a user is joined to a chat.
|
707
|
+
|
708
|
+
Args:
|
709
|
+
user_id (int): Unique identifier for the target chat
|
710
|
+
chat_id (int): Unique identifier for the target chat
|
711
|
+
|
712
|
+
Returns:
|
713
|
+
bool: True if the user is joined to the chat, False otherwise
|
714
|
+
"""
|
715
|
+
data = await make_post(
|
716
|
+
self.requests_base + "/getChatMember",
|
717
|
+
data={"chat_id": chat_id, "user_id": user_id},
|
718
|
+
)
|
719
|
+
return data.get("result", {}).get("status") in ["member", "creator", "administrator"]
|
686
720
|
|
687
721
|
async def get_chat(self, chat_id: int) -> Chat:
|
688
722
|
"""Get up to date information about the chat.
|
@@ -697,7 +731,100 @@ class Client:
|
|
697
731
|
self.requests_base + "/getChat", data={"chat_id": chat_id}
|
698
732
|
)
|
699
733
|
return Chat(**pythonize(data["result"]))
|
700
|
-
|
734
|
+
|
735
|
+
@staticmethod
|
736
|
+
async def get_ble_ir_page(username_or_phone_number: str) -> Union[dict, PeerData]:
|
737
|
+
"""Get BleIR user/group information.
|
738
|
+
|
739
|
+
Args:
|
740
|
+
username_or_phone_number (str): Username or phone number
|
741
|
+
|
742
|
+
Returns:
|
743
|
+
Union[dict, PeerData]: User/group information or error dict
|
744
|
+
"""
|
745
|
+
url = f"https://ble.ir/{username_or_phone_number}"
|
746
|
+
|
747
|
+
async with aiohttp.ClientSession() as session:
|
748
|
+
async with session.get(url) as response:
|
749
|
+
req = await response.text()
|
750
|
+
|
751
|
+
if """<p class="__404_title__lxIKL">گفتگوی مورد نظر وجود ندارد.</p>""" in req:
|
752
|
+
return PeerData(
|
753
|
+
is_ok=False,
|
754
|
+
avatar=None,
|
755
|
+
description=None,
|
756
|
+
name=None,
|
757
|
+
is_bot=None,
|
758
|
+
is_verified=None,
|
759
|
+
is_private=None,
|
760
|
+
members=None,
|
761
|
+
last_message=None,
|
762
|
+
user_id=None,
|
763
|
+
username=None,
|
764
|
+
)
|
765
|
+
|
766
|
+
soup = BeautifulSoup(req, "html.parser")
|
767
|
+
json_data = {}
|
768
|
+
|
769
|
+
try:
|
770
|
+
json_script = soup.find("script", id="__NEXT_DATA__").text
|
771
|
+
json_data = loads(json_script)
|
772
|
+
page_props = json_data.get("props", {}).get("pageProps", {})
|
773
|
+
user_data = page_props.get("user", {})
|
774
|
+
group_data = page_props.get("group", {})
|
775
|
+
messages = page_props.get("messages", [])
|
776
|
+
except (AttributeError, KeyError, JSONDecodeError):
|
777
|
+
pass
|
778
|
+
|
779
|
+
try:
|
780
|
+
avatar = soup.find("img", class_="Avatar_img___C2_3")["src"]
|
781
|
+
except (AttributeError, KeyError):
|
782
|
+
avatar = None
|
783
|
+
|
784
|
+
try:
|
785
|
+
description = soup.find("div", class_="Profile_description__YTAr_").text
|
786
|
+
except AttributeError:
|
787
|
+
description = None
|
788
|
+
|
789
|
+
try:
|
790
|
+
name = soup.find("h1", class_="Profile_name__pQglx").text
|
791
|
+
except AttributeError:
|
792
|
+
name = None
|
793
|
+
|
794
|
+
is_bot = user_data.get("isBot", False)
|
795
|
+
is_verified = user_data.get("isVerified", group_data.get("isVerified", False))
|
796
|
+
is_private = user_data.get("isPrivate", group_data.get("isPrivate", False))
|
797
|
+
members = group_data.get("members")
|
798
|
+
username = user_data.get("nick")
|
799
|
+
user_id = page_props.get("peer", {}).get("id")
|
800
|
+
|
801
|
+
last_message = None
|
802
|
+
if messages:
|
803
|
+
try:
|
804
|
+
last_msg = messages[-1]["message"]
|
805
|
+
last_message = (
|
806
|
+
last_msg.get("documentMessage", {}).get("caption", {}).get("text")
|
807
|
+
or last_msg.get("textMessage", {}).get("text")
|
808
|
+
)
|
809
|
+
if last_message:
|
810
|
+
last_message = last_message.replace("‌", "")
|
811
|
+
except (KeyError, IndexError):
|
812
|
+
pass
|
813
|
+
|
814
|
+
return PeerData(
|
815
|
+
True,
|
816
|
+
avatar,
|
817
|
+
description,
|
818
|
+
name,
|
819
|
+
is_bot,
|
820
|
+
is_verified,
|
821
|
+
is_private,
|
822
|
+
members,
|
823
|
+
last_message,
|
824
|
+
user_id,
|
825
|
+
username
|
826
|
+
)
|
827
|
+
|
701
828
|
async def get_chat_members_count(self, chat_id: int) -> int:
|
702
829
|
"""Get the number of members in a chat.
|
703
830
|
|
@@ -913,6 +1040,11 @@ class Client:
|
|
913
1040
|
if update_id:
|
914
1041
|
self.last_update_id = update_id + 1
|
915
1042
|
|
1043
|
+
if self.check_defined_message:
|
1044
|
+
update_raw = update['message']
|
1045
|
+
if update_raw.get("text") in self.defined_messages.keys():
|
1046
|
+
await self.send_message(update_raw.get('chat').get('id'), self.defined_messages.get(update_raw.get("text")), update.get('message_id'))
|
1047
|
+
|
916
1048
|
for waiter in list(self._waiters):
|
917
1049
|
w_type, check, future = waiter
|
918
1050
|
if w_type.value in update:
|
@@ -922,51 +1054,138 @@ class Client:
|
|
922
1054
|
if not future.done():
|
923
1055
|
future.set_result(event)
|
924
1056
|
self._waiters.remove(waiter)
|
925
|
-
return
|
1057
|
+
return
|
926
1058
|
|
927
1059
|
for handler in self.handlers:
|
928
1060
|
update_type = handler["type"].value
|
929
1061
|
if update_type in update:
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
1062
|
+
raw_event = update[update_type]
|
1063
|
+
|
1064
|
+
|
1065
|
+
event = self._convert_event(handler["type"], raw_event)
|
1066
|
+
|
1067
|
+
if handler["type"] == UpdatesTypes.COMMAND:
|
1068
|
+
if hasattr(event, 'text') and event.text and event.text.startswith('/'):
|
1069
|
+
command_text = event.text[1:]
|
1070
|
+
command_parts = command_text.split()
|
1071
|
+
if command_parts:
|
1072
|
+
actual_command = command_parts[0]
|
1073
|
+
expected_command = handler.get("command", "")
|
1074
|
+
|
1075
|
+
if actual_command != expected_command:
|
1076
|
+
continue
|
1077
|
+
|
1078
|
+
flt = handler.get("filter")
|
1079
|
+
if flt is not None:
|
1080
|
+
if callable(flt):
|
1081
|
+
try:
|
1082
|
+
if not flt(event):
|
1083
|
+
continue
|
1084
|
+
except Exception as e:
|
1085
|
+
print(f"[Filter Error] {e}")
|
1086
|
+
continue
|
1087
|
+
elif isinstance(flt, Filters):
|
1088
|
+
if not hasattr(event, flt.value):
|
1089
|
+
continue
|
1090
|
+
|
934
1091
|
if asyncio.iscoroutinefunction(handler["callback"]):
|
935
1092
|
asyncio.create_task(handler["callback"](event))
|
936
1093
|
else:
|
937
1094
|
handler["callback"](event)
|
938
1095
|
|
1096
|
+
|
1097
|
+
|
939
1098
|
def base_handler_decorator(self, update_type: UpdatesTypes):
|
1099
|
+
"""Base decorator for handling different types of updates.
|
1100
|
+
|
1101
|
+
Args:
|
1102
|
+
update_type (UpdatesTypes): The type of update to handle.
|
1103
|
+
|
1104
|
+
Returns:
|
1105
|
+
Callable: A decorator function that registers the callback for the specified update type.
|
1106
|
+
"""
|
1107
|
+
def wrapper(filter: Optional[Filters] = None):
|
1108
|
+
def decorator(callback: Callable[[Any], Union[None, Awaitable[None]]]):
|
1109
|
+
self.add_handler(update_type, callback, filter)
|
1110
|
+
return callback
|
1111
|
+
return decorator
|
1112
|
+
return wrapper
|
1113
|
+
|
1114
|
+
def on_command(self, command: str, filter: Optional[Filters] = None):
|
1115
|
+
"""Decorator for handling command updates.
|
1116
|
+
|
1117
|
+
Args:
|
1118
|
+
command (str): The command to handle.
|
1119
|
+
filter (Optional[Filters]): An optional filter to apply to the command.
|
1120
|
+
Returns:
|
1121
|
+
Callable: A decorator function that registers the callback for the specified command.
|
1122
|
+
"""
|
940
1123
|
def decorator(callback: Callable[[Any], Union[None, Awaitable[None]]]):
|
941
|
-
self.add_handler(
|
1124
|
+
self.add_handler(UpdatesTypes.COMMAND, callback, filter, command=command)
|
942
1125
|
return callback
|
943
|
-
|
944
1126
|
return decorator
|
945
1127
|
|
946
|
-
|
947
|
-
|
1128
|
+
|
1129
|
+
def on_message(self, filter: Optional[Filters] = None):
|
1130
|
+
"""Decorator for handling new message updates.
|
1131
|
+
|
1132
|
+
Returns:
|
1133
|
+
Callable: A decorator function that registers the callback for message updates.
|
1134
|
+
"""
|
1135
|
+
return self.base_handler_decorator(UpdatesTypes.MESSAGE)(filter)
|
1136
|
+
|
948
1137
|
|
949
1138
|
def on_edited_message(self):
|
1139
|
+
"""Decorator for handling edited message updates.
|
1140
|
+
|
1141
|
+
Returns:
|
1142
|
+
Callable: A decorator function that registers the callback for edited message updates.
|
1143
|
+
"""
|
950
1144
|
return self.base_handler_decorator(UpdatesTypes.MESSAGE_EDITED)
|
951
1145
|
|
952
1146
|
def on_callback_query(self):
|
1147
|
+
"""Decorator for handling callback query updates.
|
1148
|
+
|
1149
|
+
Returns:
|
1150
|
+
Callable: A decorator function that registers the callback for callback query updates.
|
1151
|
+
"""
|
953
1152
|
return self.base_handler_decorator(UpdatesTypes.CALLBACK_QUERY)
|
954
1153
|
|
955
1154
|
def on_new_members(self):
|
1155
|
+
"""Decorator for handling new chat members updates.
|
1156
|
+
|
1157
|
+
Returns:
|
1158
|
+
Callable: A decorator function that registers the callback for new members updates.
|
1159
|
+
"""
|
956
1160
|
return self.base_handler_decorator(UpdatesTypes.MEMBER_JOINED)
|
957
1161
|
|
958
|
-
def
|
1162
|
+
def on_members_left(self):
|
959
1163
|
return self.base_handler_decorator(UpdatesTypes.MEMBER_LEFT)
|
960
1164
|
|
961
1165
|
def on_pre_checkout_query(self):
|
1166
|
+
"""Decorator for handling pre-checkout query updates.
|
1167
|
+
|
1168
|
+
Returns:
|
1169
|
+
Callable: A decorator function that registers the callback for pre-checkout query updates.
|
1170
|
+
"""
|
962
1171
|
return self.base_handler_decorator(UpdatesTypes.PRE_CHECKOUT_QUERY)
|
963
1172
|
|
964
1173
|
def on_photo(self):
|
1174
|
+
"""Decorator for handling photo updates.
|
1175
|
+
|
1176
|
+
Returns:
|
1177
|
+
Callable: A decorator function that registers the callback for photo updates.
|
1178
|
+
"""
|
965
1179
|
return self.base_handler_decorator(UpdatesTypes.PHOTO)
|
966
1180
|
|
967
1181
|
def on_successful_payment(self):
|
968
|
-
|
1182
|
+
"""Decorator for handling successful payment updates.
|
969
1183
|
|
1184
|
+
Returns:
|
1185
|
+
Callable: A decorator function that registers the callback for successful payment updates.
|
1186
|
+
"""
|
1187
|
+
return self.base_handler_decorator(UpdatesTypes.SUCCESSFUL_PAYMENT)
|
1188
|
+
|
970
1189
|
def _convert_event(self, handler_type: UpdatesTypes, event: Dict[str, Any]) -> Any:
|
971
1190
|
"""Convert raw event data to appropriate object type.
|
972
1191
|
|
@@ -983,6 +1202,7 @@ class Client:
|
|
983
1202
|
UpdatesTypes.MEMBER_JOINED,
|
984
1203
|
UpdatesTypes.MEMBER_LEFT,
|
985
1204
|
UpdatesTypes.SUCCESSFUL_PAYMENT,
|
1205
|
+
UpdatesTypes.COMMAND
|
986
1206
|
):
|
987
1207
|
if (
|
988
1208
|
event.get("new_chat_member", False)
|
@@ -1033,18 +1253,22 @@ class Client:
|
|
1033
1253
|
|
1034
1254
|
return event
|
1035
1255
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
update_type: UpdatesTypes,
|
1039
|
-
callback: Callable[[Any], Union[None, Awaitable[None]]],
|
1040
|
-
) -> None:
|
1256
|
+
|
1257
|
+
def add_handler(self, update_type, callback, filter: Optional[Filters] = None, **kwargs):
|
1041
1258
|
"""Register a handler for specific update type.
|
1042
1259
|
|
1043
1260
|
Args:
|
1044
1261
|
update_type (UpdatesTypes): Type of update to handle
|
1045
1262
|
callback (Callable): Function to call when update is received
|
1046
1263
|
"""
|
1047
|
-
|
1264
|
+
data = {
|
1265
|
+
"type": update_type,
|
1266
|
+
"callback": callback,
|
1267
|
+
"filter": filter,
|
1268
|
+
}
|
1269
|
+
data.update(kwargs)
|
1270
|
+
self.handlers.append(data)
|
1271
|
+
|
1048
1272
|
|
1049
1273
|
def remove_handler(
|
1050
1274
|
self, callback: Callable[[Any], Union[None, Awaitable[None]]]
|
@@ -1077,17 +1301,16 @@ class Client:
|
|
1077
1301
|
|
1078
1302
|
self.running = True
|
1079
1303
|
while self.running:
|
1080
|
-
|
1304
|
+
try:
|
1081
1305
|
updates = await self.get_updates(
|
1082
1306
|
offset=self.last_update_id, limit=limit, timeout=timeout
|
1083
1307
|
)
|
1084
1308
|
|
1085
1309
|
for update in updates:
|
1086
1310
|
await self.process_update(update)
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
await asyncio.sleep(5)
|
1311
|
+
|
1312
|
+
except Exception as e:
|
1313
|
+
raise e
|
1091
1314
|
|
1092
1315
|
async def stop_polling(self) -> None:
|
1093
1316
|
"""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,25 @@
|
|
1
|
+
class PeerData:
|
2
|
+
"""A class to parse data received from ble.ir pages."""
|
3
|
+
def __init__(self,
|
4
|
+
is_ok: bool,
|
5
|
+
avatar: str,
|
6
|
+
description: str,
|
7
|
+
name: str,
|
8
|
+
is_bot: bool,
|
9
|
+
is_verified: bool,
|
10
|
+
is_private: bool,
|
11
|
+
members: int,
|
12
|
+
last_message: str,
|
13
|
+
user_id: int,
|
14
|
+
username: str):
|
15
|
+
self.is_ok = is_ok
|
16
|
+
self.avatar = avatar
|
17
|
+
self.description = description
|
18
|
+
self.name = name
|
19
|
+
self.is_bot = is_bot
|
20
|
+
self.is_verified = is_verified
|
21
|
+
self.is_private = is_private
|
22
|
+
self.members = members
|
23
|
+
self.last_message = last_message
|
24
|
+
self.user_id = user_id
|
25
|
+
self.username = username
|
@@ -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
|
{pyrobale-0.3.7 → pyrobale-0.3.9}/.github/ISSUE_TEMPLATE/issue-template-/342/204/271/357/270/217.md"
RENAMED
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
|