pyjetra 1.0.0__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.
- pyjetra-1.0.0/AUTHORS.md +12 -0
- pyjetra-1.0.0/LICENSE +5 -0
- pyjetra-1.0.0/PKG-INFO +70 -0
- pyjetra-1.0.0/README.md +51 -0
- pyjetra-1.0.0/pyjetra/__init__.py +12 -0
- pyjetra-1.0.0/pyjetra/async_client.py +61 -0
- pyjetra-1.0.0/pyjetra/client.py +72 -0
- pyjetra-1.0.0/pyjetra/dev_info.py +11 -0
- pyjetra-1.0.0/pyjetra/exceptions.py +2 -0
- pyjetra-1.0.0/pyjetra/filters.py +16 -0
- pyjetra-1.0.0/pyjetra/types/__init__.py +7 -0
- pyjetra-1.0.0/pyjetra/types/callback.py +21 -0
- pyjetra-1.0.0/pyjetra/types/chat.py +11 -0
- pyjetra-1.0.0/pyjetra/types/markup.py +18 -0
- pyjetra-1.0.0/pyjetra/types/message.py +30 -0
- pyjetra-1.0.0/pyjetra/types/user.py +10 -0
- pyjetra-1.0.0/pyjetra.egg-info/PKG-INFO +70 -0
- pyjetra-1.0.0/pyjetra.egg-info/SOURCES.txt +26 -0
- pyjetra-1.0.0/pyjetra.egg-info/dependency_links.txt +1 -0
- pyjetra-1.0.0/pyjetra.egg-info/requires.txt +2 -0
- pyjetra-1.0.0/pyjetra.egg-info/top_level.txt +1 -0
- pyjetra-1.0.0/pyproject.toml +27 -0
- pyjetra-1.0.0/setup.cfg +4 -0
- pyjetra-1.0.0/tests/test_advanced_buttons.py +44 -0
- pyjetra-1.0.0/tests/test_async.py +15 -0
- pyjetra-1.0.0/tests/test_error_handling.py +24 -0
- pyjetra-1.0.0/tests/test_registration_fsm.py +34 -0
- pyjetra-1.0.0/tests/test_sync.py +15 -0
pyjetra-1.0.0/AUTHORS.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Authors & Contributors
|
|
2
|
+
### [EN] Project Developer
|
|
3
|
+
### [FA] توسعهدهنده پروژه
|
|
4
|
+
|
|
5
|
+
* **Name / نام**: Ali VEX
|
|
6
|
+
* **Email / ایمیل**: lyrdaadhrpyk802@gmail.com
|
|
7
|
+
* **Jetra Profile / پروفایل جترا**: https://jetra.ir/UserBoT
|
|
8
|
+
* **GitHub / گیتهاب**: https://github.com/lyrdaadhrpyk802-ux/pyjetra
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
*This library was built with passion for the Jetra community.*
|
|
12
|
+
*این کتابخانه با عشق برای جامعه جترا ساخته شده است.*
|
pyjetra-1.0.0/LICENSE
ADDED
pyjetra-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyjetra
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A modern, bilingual (Sync/Async) Python wrapper for the Jetra Bot API.
|
|
5
|
+
Author-email: Ali VEX <lyrdaadhrpyk802@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/lyrdaadhrpyk802-ux/pyjetra
|
|
7
|
+
Project-URL: Documentation, https://github.com/lyrdaadhrpyk802-ux/pyjetra/blob/main/README.md
|
|
8
|
+
Project-URL: Jetra, https://jetra.ir/UserBoT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
License-File: AUTHORS.md
|
|
16
|
+
Requires-Dist: requests>=2.25.0
|
|
17
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# PyJetra Library
|
|
21
|
+
### [EN] A Modern Python Wrapper for Jetra Bot API
|
|
22
|
+
### [FA] یک کتابخانه مدرن پایتون برای API ربات جترا
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## [EN] Features / [FA] امکانات
|
|
27
|
+
* **Sync & Async Support** / پشتیبانی از Sync و Async
|
|
28
|
+
* **State Management** / مدیریت وضعیت
|
|
29
|
+
* **Advanced Filters** / فیلترهای پیشرفته
|
|
30
|
+
* **Full Media Support** / پشتیبانی کامل از رسانهها
|
|
31
|
+
|
|
32
|
+
## [EN] Installation / [FA] نصب
|
|
33
|
+
```bash
|
|
34
|
+
pip install requests aiohttp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## [EN] Quick Start (Sync) / [FA] شروع سریع (همزمان)
|
|
38
|
+
```python
|
|
39
|
+
from pyjetra import JetraBot, CommandFilter
|
|
40
|
+
|
|
41
|
+
bot = JetraBot('YOUR_TOKEN')
|
|
42
|
+
|
|
43
|
+
@bot.on_message(filters=[CommandFilter('start')])
|
|
44
|
+
def start(msg):
|
|
45
|
+
msg.reply('Hello from PyJetra!')
|
|
46
|
+
|
|
47
|
+
bot.poll()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## [EN] Quick Start (Async) / [FA] شروع سریع (غیرهمزمان)
|
|
51
|
+
```python
|
|
52
|
+
from pyjetra import AsyncJetraBot, CommandFilter
|
|
53
|
+
import asyncio
|
|
54
|
+
|
|
55
|
+
bot = AsyncJetraBot('YOUR_TOKEN')
|
|
56
|
+
|
|
57
|
+
@bot.on_message(filters=[CommandFilter('start')])
|
|
58
|
+
async def start(msg):
|
|
59
|
+
await msg.reply('Hello Fast!')
|
|
60
|
+
|
|
61
|
+
asyncio.run(bot.start_polling())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## [EN] Testing / [FA] تست کردن
|
|
65
|
+
Navigate to the `tests/` folder and run the files.
|
|
66
|
+
Read the comments inside each test file.
|
|
67
|
+
|
|
68
|
+
## [EN] Developer / [FA] توسعهدهنده
|
|
69
|
+
Created with ❤️ by Ali VEX
|
|
70
|
+
GitHub: https://github.com/lyrdaadhrpyk802-ux
|
pyjetra-1.0.0/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# PyJetra Library
|
|
2
|
+
### [EN] A Modern Python Wrapper for Jetra Bot API
|
|
3
|
+
### [FA] یک کتابخانه مدرن پایتون برای API ربات جترا
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## [EN] Features / [FA] امکانات
|
|
8
|
+
* **Sync & Async Support** / پشتیبانی از Sync و Async
|
|
9
|
+
* **State Management** / مدیریت وضعیت
|
|
10
|
+
* **Advanced Filters** / فیلترهای پیشرفته
|
|
11
|
+
* **Full Media Support** / پشتیبانی کامل از رسانهها
|
|
12
|
+
|
|
13
|
+
## [EN] Installation / [FA] نصب
|
|
14
|
+
```bash
|
|
15
|
+
pip install requests aiohttp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## [EN] Quick Start (Sync) / [FA] شروع سریع (همزمان)
|
|
19
|
+
```python
|
|
20
|
+
from pyjetra import JetraBot, CommandFilter
|
|
21
|
+
|
|
22
|
+
bot = JetraBot('YOUR_TOKEN')
|
|
23
|
+
|
|
24
|
+
@bot.on_message(filters=[CommandFilter('start')])
|
|
25
|
+
def start(msg):
|
|
26
|
+
msg.reply('Hello from PyJetra!')
|
|
27
|
+
|
|
28
|
+
bot.poll()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## [EN] Quick Start (Async) / [FA] شروع سریع (غیرهمزمان)
|
|
32
|
+
```python
|
|
33
|
+
from pyjetra import AsyncJetraBot, CommandFilter
|
|
34
|
+
import asyncio
|
|
35
|
+
|
|
36
|
+
bot = AsyncJetraBot('YOUR_TOKEN')
|
|
37
|
+
|
|
38
|
+
@bot.on_message(filters=[CommandFilter('start')])
|
|
39
|
+
async def start(msg):
|
|
40
|
+
await msg.reply('Hello Fast!')
|
|
41
|
+
|
|
42
|
+
asyncio.run(bot.start_polling())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## [EN] Testing / [FA] تست کردن
|
|
46
|
+
Navigate to the `tests/` folder and run the files.
|
|
47
|
+
Read the comments inside each test file.
|
|
48
|
+
|
|
49
|
+
## [EN] Developer / [FA] توسعهدهنده
|
|
50
|
+
Created with ❤️ by Ali VEX
|
|
51
|
+
GitHub: https://github.com/lyrdaadhrpyk802-ux
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .client import JetraBot
|
|
2
|
+
from .async_client import AsyncJetraBot
|
|
3
|
+
from .exceptions import JetraError
|
|
4
|
+
from .types import User, Chat, Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
5
|
+
from .filters import CommandFilter, TextFilter
|
|
6
|
+
from .dev_info import get_info, DEVELOPER_NAME, VERSION
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"JetraBot", "AsyncJetraBot", "JetraError", "User", "Chat", "Message",
|
|
10
|
+
"CallbackQuery", "InlineKeyboardMarkup", "InlineKeyboardButton",
|
|
11
|
+
"CommandFilter", "TextFilter", "get_info", "DEVELOPER_NAME", "VERSION"
|
|
12
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import List, Optional, Callable, Any
|
|
4
|
+
from .exceptions import JetraError
|
|
5
|
+
from .types import User, Message, CallbackQuery, InlineKeyboardMarkup
|
|
6
|
+
|
|
7
|
+
class AsyncJetraBot:
|
|
8
|
+
def __init__(self, token: str):
|
|
9
|
+
self.token = token
|
|
10
|
+
self.base_url = f"https://jetra.ir/bot{token}/"
|
|
11
|
+
self._message_handlers = []
|
|
12
|
+
self._callback_handlers = []
|
|
13
|
+
|
|
14
|
+
async def _request(self, method: str, **params) -> Any:
|
|
15
|
+
async with aiohttp.ClientSession() as session:
|
|
16
|
+
async with session.post(self.base_url + method, json=params) as resp:
|
|
17
|
+
data = await resp.json()
|
|
18
|
+
if not data.get("ok"): raise JetraError(f"API Error: {data.get('description')}")
|
|
19
|
+
return data.get("result")
|
|
20
|
+
|
|
21
|
+
async def send_message(self, chat_id: int, text: str, reply_to_message_id: Optional[int] = None, reply_markup: Optional[InlineKeyboardMarkup] = None) -> dict:
|
|
22
|
+
params = {"chat_id": chat_id, "text": text}
|
|
23
|
+
if reply_to_message_id: params["reply_to_message_id"] = reply_to_message_id
|
|
24
|
+
if reply_markup: params["reply_markup"] = reply_markup.to_dict()
|
|
25
|
+
return await self._request("sendMessage", **params)
|
|
26
|
+
|
|
27
|
+
async def answer_callback_query(self, callback_query_id: str, text: Optional[str] = None, show_alert: bool = False) -> dict:
|
|
28
|
+
return await self._request("answerCallbackQuery", callback_query_id=callback_query_id, text=text, show_alert=show_alert)
|
|
29
|
+
|
|
30
|
+
async def get_updates(self, offset: int = 0) -> List[dict]:
|
|
31
|
+
return await self._request("getUpdates", offset=offset, timeout=30)
|
|
32
|
+
|
|
33
|
+
def on_message(self, func: Callable = None, filters: list = None):
|
|
34
|
+
def decorator(f):
|
|
35
|
+
self._message_handlers.append({"func": f, "filters": filters or []})
|
|
36
|
+
return f
|
|
37
|
+
return decorator(func) if func else decorator
|
|
38
|
+
|
|
39
|
+
def on_callback_query(self, func: Callable):
|
|
40
|
+
self._callback_handlers.append(func)
|
|
41
|
+
return func
|
|
42
|
+
|
|
43
|
+
async def start_polling(self):
|
|
44
|
+
print("Async Bot polling...")
|
|
45
|
+
offset = 0
|
|
46
|
+
while True:
|
|
47
|
+
try:
|
|
48
|
+
updates = await self.get_updates(offset)
|
|
49
|
+
for update in updates:
|
|
50
|
+
offset = update["update_id"] + 1
|
|
51
|
+
if "message" in update:
|
|
52
|
+
msg = Message(update["message"], self)
|
|
53
|
+
for handler in self._message_handlers:
|
|
54
|
+
if all(f(msg) for f in handler["filters"]):
|
|
55
|
+
await handler["func"](msg)
|
|
56
|
+
elif "callback_query" in update:
|
|
57
|
+
cb = CallbackQuery(update["callback_query"], self)
|
|
58
|
+
for handler in self._callback_handlers:
|
|
59
|
+
await handler(cb)
|
|
60
|
+
except Exception:
|
|
61
|
+
await asyncio.sleep(2)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import time
|
|
3
|
+
from typing import List, Optional, Callable, Any
|
|
4
|
+
from .exceptions import JetraError
|
|
5
|
+
from .types import User, Message, CallbackQuery, InlineKeyboardMarkup
|
|
6
|
+
|
|
7
|
+
class JetraBot:
|
|
8
|
+
def __init__(self, token: str):
|
|
9
|
+
self.token = token
|
|
10
|
+
self.base_url = f"https://jetra.ir/bot{token}/"
|
|
11
|
+
self.session = requests.Session()
|
|
12
|
+
self._message_handlers = []
|
|
13
|
+
self._callback_handlers = []
|
|
14
|
+
self._states = {}
|
|
15
|
+
|
|
16
|
+
def set_state(self, user_id, state): self._states[user_id] = state
|
|
17
|
+
def get_state(self, user_id): return self._states.get(user_id)
|
|
18
|
+
def clear_state(self, user_id):
|
|
19
|
+
if user_id in self._states: del self._states[user_id]
|
|
20
|
+
|
|
21
|
+
def _request(self, method: str, **params) -> Any:
|
|
22
|
+
url = self.base_url + method
|
|
23
|
+
try:
|
|
24
|
+
response = self.session.post(url, json=params, timeout=30)
|
|
25
|
+
data = response.json()
|
|
26
|
+
if not data.get("ok"): raise JetraError(f"API Error: {data.get('description')}")
|
|
27
|
+
return data.get("result")
|
|
28
|
+
except requests.exceptions.ReadTimeout: return []
|
|
29
|
+
except Exception as e: raise JetraError(f"Network Error: {e}")
|
|
30
|
+
|
|
31
|
+
def send_message(self, chat_id: int, text: str, reply_to_message_id: Optional[int] = None, reply_markup: Optional[InlineKeyboardMarkup] = None) -> dict:
|
|
32
|
+
params = {"chat_id": chat_id, "text": text}
|
|
33
|
+
if reply_to_message_id: params["reply_to_message_id"] = reply_to_message_id
|
|
34
|
+
if reply_markup: params["reply_markup"] = reply_markup.to_dict()
|
|
35
|
+
return self._request("sendMessage", **params)
|
|
36
|
+
|
|
37
|
+
def answer_callback_query(self, callback_query_id: str, text: Optional[str] = None, show_alert: bool = False) -> dict:
|
|
38
|
+
params = {"callback_query_id": callback_query_id, "text": text, "show_alert": show_alert}
|
|
39
|
+
return self._request("answerCallbackQuery", **params)
|
|
40
|
+
|
|
41
|
+
def get_updates(self, offset: int = 0) -> List[dict]:
|
|
42
|
+
return self._request("getUpdates", offset=offset, timeout=30)
|
|
43
|
+
|
|
44
|
+
def on_message(self, func: Callable = None, filters: list = None):
|
|
45
|
+
def decorator(f):
|
|
46
|
+
self._message_handlers.append({"func": f, "filters": filters or []})
|
|
47
|
+
return f
|
|
48
|
+
return decorator(func) if func else decorator
|
|
49
|
+
|
|
50
|
+
def on_callback_query(self, func: Callable):
|
|
51
|
+
self._callback_handlers.append(func)
|
|
52
|
+
return func
|
|
53
|
+
|
|
54
|
+
def poll(self):
|
|
55
|
+
print("Bot polling...")
|
|
56
|
+
offset = 0
|
|
57
|
+
while True:
|
|
58
|
+
try:
|
|
59
|
+
updates = self.get_updates(offset)
|
|
60
|
+
for update in updates:
|
|
61
|
+
offset = update["update_id"] + 1
|
|
62
|
+
if "message" in update:
|
|
63
|
+
msg = Message(update["message"], self)
|
|
64
|
+
for handler in self._message_handlers:
|
|
65
|
+
if all(f(msg) for f in handler["filters"]):
|
|
66
|
+
handler["func"](msg)
|
|
67
|
+
elif "callback_query" in update:
|
|
68
|
+
cb = CallbackQuery(update["callback_query"], self)
|
|
69
|
+
for handler in self._callback_handlers:
|
|
70
|
+
handler(cb)
|
|
71
|
+
except Exception:
|
|
72
|
+
time.sleep(2)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# [EN] Developer Information
|
|
2
|
+
# [FA] مشخصات توسعهدهنده
|
|
3
|
+
|
|
4
|
+
DEVELOPER_NAME = "Ali VEX"
|
|
5
|
+
DEVELOPER_EMAIL = "lyrdaadhrpyk802@gmail.com"
|
|
6
|
+
JETRA_LINK = "https://jetra.ir/UserBoT"
|
|
7
|
+
GITHUB_URL = "https://github.com/lyrdaadhrpyk802-ux/pyjetra"
|
|
8
|
+
VERSION = "1.0.0"
|
|
9
|
+
|
|
10
|
+
def get_info():
|
|
11
|
+
return f"PyJetra v{VERSION} | Developed by {DEVELOPER_NAME} | {GITHUB_URL}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Filter:
|
|
2
|
+
def __call__(self, message): return True
|
|
3
|
+
|
|
4
|
+
class CommandFilter(Filter):
|
|
5
|
+
def __init__(self, commands):
|
|
6
|
+
self.commands = commands if isinstance(commands, list) else [commands]
|
|
7
|
+
def __call__(self, message):
|
|
8
|
+
if not message.text: return False
|
|
9
|
+
cmd = message.text.split()[0].lower()
|
|
10
|
+
return cmd in [f"/{c}" for c in self.commands]
|
|
11
|
+
|
|
12
|
+
class TextFilter(Filter):
|
|
13
|
+
def __init__(self, text):
|
|
14
|
+
self.text = text
|
|
15
|
+
def __call__(self, message):
|
|
16
|
+
return bool(message.text and message.text.lower() == self.text.lower())
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .user import User
|
|
2
|
+
from .chat import Chat
|
|
3
|
+
from .message import Message
|
|
4
|
+
from .callback import CallbackQuery
|
|
5
|
+
from .markup import InlineKeyboardMarkup, InlineKeyboardButton
|
|
6
|
+
|
|
7
|
+
__all__ = ["User", "Chat", "Message", "CallbackQuery", "InlineKeyboardMarkup", "InlineKeyboardButton"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from .user import User
|
|
3
|
+
from .message import Message
|
|
4
|
+
|
|
5
|
+
class CallbackQuery:
|
|
6
|
+
def __init__(self, data: dict, bot_instance):
|
|
7
|
+
self.id = data.get("id")
|
|
8
|
+
self.from_user = None
|
|
9
|
+
if "from" in data:
|
|
10
|
+
u = data["from"]
|
|
11
|
+
self.from_user = User(
|
|
12
|
+
id=u.get("id", 0), is_bot=u.get("is_bot", False),
|
|
13
|
+
first_name=u.get("first_name", ""), last_name=u.get("last_name"),
|
|
14
|
+
username=u.get("username")
|
|
15
|
+
)
|
|
16
|
+
self.data = data.get("data")
|
|
17
|
+
self.message = Message(data["message"], bot_instance) if "message" in data else None
|
|
18
|
+
self._bot = bot_instance
|
|
19
|
+
|
|
20
|
+
def answer(self, text: Optional[str] = None, show_alert: bool = False):
|
|
21
|
+
return self._bot.answer_callback_query(self.id, text, show_alert)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
class InlineKeyboardButton:
|
|
4
|
+
def __init__(self, text: str, callback_data: Optional[str] = None, url: Optional[str] = None):
|
|
5
|
+
self.text = text
|
|
6
|
+
self.callback_data = callback_data
|
|
7
|
+
self.url = url
|
|
8
|
+
def to_dict(self) -> dict:
|
|
9
|
+
data = {"text": self.text}
|
|
10
|
+
if self.callback_data: data["callback_data"] = self.callback_data
|
|
11
|
+
if self.url: data["url"] = self.url
|
|
12
|
+
return data
|
|
13
|
+
|
|
14
|
+
class InlineKeyboardMarkup:
|
|
15
|
+
def __init__(self, inline_keyboard: List[List[InlineKeyboardButton]]):
|
|
16
|
+
self.inline_keyboard = inline_keyboard
|
|
17
|
+
def to_dict(self) -> dict:
|
|
18
|
+
return {"inline_keyboard": [[btn.to_dict() for btn in row] for row in self.inline_keyboard]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Optional, TYPE_CHECKING
|
|
2
|
+
from .user import User
|
|
3
|
+
from .chat import Chat
|
|
4
|
+
|
|
5
|
+
class Message:
|
|
6
|
+
def __init__(self, data: dict, bot_instance):
|
|
7
|
+
self.message_id = data.get("message_id")
|
|
8
|
+
chat_data = data.get("chat", {})
|
|
9
|
+
self.chat = Chat(
|
|
10
|
+
id=chat_data.get("id", 0), type=chat_data.get("type", "private"),
|
|
11
|
+
title=chat_data.get("title"), first_name=chat_data.get("first_name"),
|
|
12
|
+
last_name=chat_data.get("last_name"), username=chat_data.get("username")
|
|
13
|
+
)
|
|
14
|
+
self.text = data.get("text")
|
|
15
|
+
self.caption = data.get("caption")
|
|
16
|
+
self.raw = data
|
|
17
|
+
self._bot = bot_instance
|
|
18
|
+
|
|
19
|
+
user_data = data.get("from")
|
|
20
|
+
if user_data:
|
|
21
|
+
self.from_user = User(
|
|
22
|
+
id=user_data.get("id", 0), is_bot=user_data.get("is_bot", False),
|
|
23
|
+
first_name=user_data.get("first_name", ""), last_name=user_data.get("last_name"),
|
|
24
|
+
username=user_data.get("username")
|
|
25
|
+
)
|
|
26
|
+
else:
|
|
27
|
+
self.from_user = None
|
|
28
|
+
|
|
29
|
+
def reply(self, text: str, reply_markup: Optional['InlineKeyboardMarkup'] = None):
|
|
30
|
+
return self._bot.send_message(self.chat.id, text, reply_to_message_id=self.message_id, reply_markup=reply_markup)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyjetra
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A modern, bilingual (Sync/Async) Python wrapper for the Jetra Bot API.
|
|
5
|
+
Author-email: Ali VEX <lyrdaadhrpyk802@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/lyrdaadhrpyk802-ux/pyjetra
|
|
7
|
+
Project-URL: Documentation, https://github.com/lyrdaadhrpyk802-ux/pyjetra/blob/main/README.md
|
|
8
|
+
Project-URL: Jetra, https://jetra.ir/UserBoT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
License-File: AUTHORS.md
|
|
16
|
+
Requires-Dist: requests>=2.25.0
|
|
17
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# PyJetra Library
|
|
21
|
+
### [EN] A Modern Python Wrapper for Jetra Bot API
|
|
22
|
+
### [FA] یک کتابخانه مدرن پایتون برای API ربات جترا
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## [EN] Features / [FA] امکانات
|
|
27
|
+
* **Sync & Async Support** / پشتیبانی از Sync و Async
|
|
28
|
+
* **State Management** / مدیریت وضعیت
|
|
29
|
+
* **Advanced Filters** / فیلترهای پیشرفته
|
|
30
|
+
* **Full Media Support** / پشتیبانی کامل از رسانهها
|
|
31
|
+
|
|
32
|
+
## [EN] Installation / [FA] نصب
|
|
33
|
+
```bash
|
|
34
|
+
pip install requests aiohttp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## [EN] Quick Start (Sync) / [FA] شروع سریع (همزمان)
|
|
38
|
+
```python
|
|
39
|
+
from pyjetra import JetraBot, CommandFilter
|
|
40
|
+
|
|
41
|
+
bot = JetraBot('YOUR_TOKEN')
|
|
42
|
+
|
|
43
|
+
@bot.on_message(filters=[CommandFilter('start')])
|
|
44
|
+
def start(msg):
|
|
45
|
+
msg.reply('Hello from PyJetra!')
|
|
46
|
+
|
|
47
|
+
bot.poll()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## [EN] Quick Start (Async) / [FA] شروع سریع (غیرهمزمان)
|
|
51
|
+
```python
|
|
52
|
+
from pyjetra import AsyncJetraBot, CommandFilter
|
|
53
|
+
import asyncio
|
|
54
|
+
|
|
55
|
+
bot = AsyncJetraBot('YOUR_TOKEN')
|
|
56
|
+
|
|
57
|
+
@bot.on_message(filters=[CommandFilter('start')])
|
|
58
|
+
async def start(msg):
|
|
59
|
+
await msg.reply('Hello Fast!')
|
|
60
|
+
|
|
61
|
+
asyncio.run(bot.start_polling())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## [EN] Testing / [FA] تست کردن
|
|
65
|
+
Navigate to the `tests/` folder and run the files.
|
|
66
|
+
Read the comments inside each test file.
|
|
67
|
+
|
|
68
|
+
## [EN] Developer / [FA] توسعهدهنده
|
|
69
|
+
Created with ❤️ by Ali VEX
|
|
70
|
+
GitHub: https://github.com/lyrdaadhrpyk802-ux
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
AUTHORS.md
|
|
2
|
+
LICENSE
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
pyjetra/__init__.py
|
|
6
|
+
pyjetra/async_client.py
|
|
7
|
+
pyjetra/client.py
|
|
8
|
+
pyjetra/dev_info.py
|
|
9
|
+
pyjetra/exceptions.py
|
|
10
|
+
pyjetra/filters.py
|
|
11
|
+
pyjetra.egg-info/PKG-INFO
|
|
12
|
+
pyjetra.egg-info/SOURCES.txt
|
|
13
|
+
pyjetra.egg-info/dependency_links.txt
|
|
14
|
+
pyjetra.egg-info/requires.txt
|
|
15
|
+
pyjetra.egg-info/top_level.txt
|
|
16
|
+
pyjetra/types/__init__.py
|
|
17
|
+
pyjetra/types/callback.py
|
|
18
|
+
pyjetra/types/chat.py
|
|
19
|
+
pyjetra/types/markup.py
|
|
20
|
+
pyjetra/types/message.py
|
|
21
|
+
pyjetra/types/user.py
|
|
22
|
+
tests/test_advanced_buttons.py
|
|
23
|
+
tests/test_async.py
|
|
24
|
+
tests/test_error_handling.py
|
|
25
|
+
tests/test_registration_fsm.py
|
|
26
|
+
tests/test_sync.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyjetra
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyjetra"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Ali VEX", email = "lyrdaadhrpyk802@gmail.com"}
|
|
10
|
+
]
|
|
11
|
+
description = "A modern, bilingual (Sync/Async) Python wrapper for the Jetra Bot API."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.7"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"requests>=2.25.0",
|
|
21
|
+
"aiohttp>=3.8.0"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/lyrdaadhrpyk802-ux/pyjetra"
|
|
26
|
+
Documentation = "https://github.com/lyrdaadhrpyk802-ux/pyjetra/blob/main/README.md"
|
|
27
|
+
Jetra = "https://jetra.ir/UserBoT"
|
pyjetra-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# [EN] Test for multi-level Inline Keyboards and Callback Parsing.
|
|
2
|
+
# [FA] تست برای کیبوردهای شیشهای چند سطحی و پردازش کلیکها.
|
|
3
|
+
# How to run: python tests/test_advanced_buttons.py
|
|
4
|
+
# Send /menu in the bot chat.
|
|
5
|
+
|
|
6
|
+
import sys, os
|
|
7
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
8
|
+
from pyjetra import JetraBot, CommandFilter, InlineKeyboardMarkup, InlineKeyboardButton
|
|
9
|
+
|
|
10
|
+
BOT_TOKEN = "1780246392:3W4m3UParm6nelq3mS2HmjtgV6_pWiIfCj8"
|
|
11
|
+
bot = JetraBot(BOT_TOKEN)
|
|
12
|
+
|
|
13
|
+
@bot.on_message(filters=[CommandFilter("menu")])
|
|
14
|
+
def show_menu(msg):
|
|
15
|
+
kb = InlineKeyboardMarkup([
|
|
16
|
+
[InlineKeyboardButton("Settings ⚙️", callback_data="settings")],
|
|
17
|
+
[InlineKeyboardButton("Help ❓", callback_data="help")]
|
|
18
|
+
])
|
|
19
|
+
msg.reply("Main Menu:", reply_markup=kb)
|
|
20
|
+
|
|
21
|
+
@bot.on_callback_query()
|
|
22
|
+
def handle_click(cb):
|
|
23
|
+
if cb.data == "settings":
|
|
24
|
+
cb.answer("Opening settings...")
|
|
25
|
+
kb = InlineKeyboardMarkup([
|
|
26
|
+
[InlineKeyboardButton("Language 🌐", callback_data="lang")],
|
|
27
|
+
[InlineKeyboardButton("Back 🔙", callback_data="back")]
|
|
28
|
+
])
|
|
29
|
+
# Editing the message to show the sub-menu
|
|
30
|
+
bot.edit_message_text(cb.message.chat.id, cb.message.message_id, "Settings Menu:", reply_markup=kb)
|
|
31
|
+
|
|
32
|
+
elif cb.data == "back":
|
|
33
|
+
cb.answer("Going back...")
|
|
34
|
+
kb = InlineKeyboardMarkup([
|
|
35
|
+
[InlineKeyboardButton("Settings ⚙️", callback_data="settings")],
|
|
36
|
+
[InlineKeyboardButton("Help ❓", callback_data="help")]
|
|
37
|
+
])
|
|
38
|
+
bot.edit_message_text(cb.message.chat.id, cb.message.message_id, "Main Menu:", reply_markup=kb)
|
|
39
|
+
|
|
40
|
+
elif cb.data == "help":
|
|
41
|
+
cb.answer("Help is on the way!", show_alert=True)
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
bot.poll()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import sys, os, asyncio
|
|
2
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
3
|
+
from pyjetra import AsyncJetraBot, CommandFilter
|
|
4
|
+
|
|
5
|
+
BOT_TOKEN = "1780246392:3W4m3UParm6nelq3mS2HmjtgV6_pWiIfCj8"
|
|
6
|
+
bot = AsyncJetraBot(BOT_TOKEN)
|
|
7
|
+
|
|
8
|
+
@bot.on_message(filters=[CommandFilter("start")])
|
|
9
|
+
async def start_handler(msg):
|
|
10
|
+
await msg.reply("Async Bot is working at lightning speed!")
|
|
11
|
+
print("Received /start in Async mode.")
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
print("Starting Async Test...")
|
|
15
|
+
asyncio.run(bot.start_polling())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# [EN] Test how the library handles API errors gracefully.
|
|
2
|
+
# [FA] تست اینکه کتابخانه چگونه خطاهای API را مدیریت میکند.
|
|
3
|
+
# How to run: python tests/test_error_handling.py
|
|
4
|
+
# Send /error in the bot chat.
|
|
5
|
+
|
|
6
|
+
import sys, os
|
|
7
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
8
|
+
from pyjetra import JetraBot, CommandFilter, JetraError
|
|
9
|
+
|
|
10
|
+
BOT_TOKEN = "1780246392:3W4m3UParm6nelq3mS2HmjtgV6_pWiIfCj8"
|
|
11
|
+
bot = JetraBot(BOT_TOKEN)
|
|
12
|
+
|
|
13
|
+
@bot.on_message(filters=[CommandFilter("error")])
|
|
14
|
+
def trigger_error(msg):
|
|
15
|
+
try:
|
|
16
|
+
# Trying to send a message to an invalid Chat ID (999999999)
|
|
17
|
+
bot.send_message(999999999, "This will fail!")
|
|
18
|
+
except JetraError as e:
|
|
19
|
+
# Catch the error and inform the user
|
|
20
|
+
msg.reply(f"Successfully caught an error: {e}")
|
|
21
|
+
print(f"Error logged: {e}")
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
bot.poll()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# [EN] Test for State Management (FSM) - A step-by-step registration.
|
|
2
|
+
# [FA] تست برای مدیریت وضعیت (FSM) - یک ثبتنام مرحله به مرحله.
|
|
3
|
+
# How to run: python tests/test_registration_fsm.py
|
|
4
|
+
# Send /register in the bot chat.
|
|
5
|
+
|
|
6
|
+
import sys, os
|
|
7
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
8
|
+
from pyjetra import JetraBot, CommandFilter
|
|
9
|
+
|
|
10
|
+
BOT_TOKEN = "1780246392:3W4m3UParm6nelq3mS2HmjtgV6_pWiIfCj8"
|
|
11
|
+
bot = JetraBot(BOT_TOKEN)
|
|
12
|
+
|
|
13
|
+
@bot.on_message(filters=[CommandFilter("register")])
|
|
14
|
+
def start_registration(msg):
|
|
15
|
+
bot.set_state(msg.from_user.id, "waiting_for_name")
|
|
16
|
+
msg.reply("Welcome! Please type your name:")
|
|
17
|
+
|
|
18
|
+
@bot.on_message()
|
|
19
|
+
def handle_steps(msg):
|
|
20
|
+
if not msg.text or msg.text.startswith("/"):
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
state = bot.get_state(msg.from_user.id)
|
|
24
|
+
|
|
25
|
+
if state == "waiting_for_name":
|
|
26
|
+
bot.set_state(msg.from_user.id, "waiting_for_age")
|
|
27
|
+
msg.reply(f"Nice to meet you, {msg.text}! How old are you?")
|
|
28
|
+
|
|
29
|
+
elif state == "waiting_for_age":
|
|
30
|
+
bot.clear_state(msg.from_user.id)
|
|
31
|
+
msg.reply(f"You are {msg.text} years old. Registration complete! ✅")
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
bot.poll()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import sys, os
|
|
2
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
3
|
+
from pyjetra import JetraBot, CommandFilter
|
|
4
|
+
|
|
5
|
+
BOT_TOKEN = "1780246392:3W4m3UParm6nelq3mS2HmjtgV6_pWiIfCj8"
|
|
6
|
+
bot = JetraBot(BOT_TOKEN)
|
|
7
|
+
|
|
8
|
+
@bot.on_message(filters=[CommandFilter("start")])
|
|
9
|
+
def start_handler(msg):
|
|
10
|
+
msg.reply("Sync Bot is working perfectly!")
|
|
11
|
+
print("Received /start in Sync mode.")
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
print("Starting Sync Test...")
|
|
15
|
+
bot.poll()
|