ElectronRA 1.0.1__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.
- electron-1.0.0/Electron.egg-info/PKG-INFO +12 -0
- electron-1.0.0/Electron.egg-info/SOURCES.txt +16 -0
- electron-1.0.0/Electron.egg-info/dependency_links.txt +1 -0
- electron-1.0.0/Electron.egg-info/requires.txt +2 -0
- electron-1.0.0/Electron.egg-info/top_level.txt +1 -0
- electron-1.0.0/PKG-INFO +12 -0
- electron-1.0.0/README.md +59 -0
- electron-1.0.0/electron/__init__.py +16 -0
- electron-1.0.0/electron/bot.py +171 -0
- electron-1.0.0/electron/client.py +225 -0
- electron-1.0.0/electron/filters.py +32 -0
- electron-1.0.0/electron/handlers.py +62 -0
- electron-1.0.0/electron/methods/__init__.py +0 -0
- electron-1.0.0/electron/real_client.py +121 -0
- electron-1.0.0/electron/types.py +74 -0
- electron-1.0.0/pyproject.toml +3 -0
- electron-1.0.0/setup.cfg +4 -0
- electron-1.0.0/setup.py +14 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Electron
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A powerful Python library for Rubika bots
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
8
|
+
Requires-Dist: requests>=2.28.0
|
|
9
|
+
Dynamic: author
|
|
10
|
+
Dynamic: requires-dist
|
|
11
|
+
Dynamic: requires-python
|
|
12
|
+
Dynamic: summary
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
Electron.egg-info/PKG-INFO
|
|
5
|
+
Electron.egg-info/SOURCES.txt
|
|
6
|
+
Electron.egg-info/dependency_links.txt
|
|
7
|
+
Electron.egg-info/requires.txt
|
|
8
|
+
Electron.egg-info/top_level.txt
|
|
9
|
+
electron/__init__.py
|
|
10
|
+
electron/bot.py
|
|
11
|
+
electron/client.py
|
|
12
|
+
electron/filters.py
|
|
13
|
+
electron/handlers.py
|
|
14
|
+
electron/real_client.py
|
|
15
|
+
electron/types.py
|
|
16
|
+
electron/methods/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
electron
|
electron-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Electron
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A powerful Python library for Rubika bots
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
8
|
+
Requires-Dist: requests>=2.28.0
|
|
9
|
+
Dynamic: author
|
|
10
|
+
Dynamic: requires-dist
|
|
11
|
+
Dynamic: requires-python
|
|
12
|
+
Dynamic: summary
|
electron-1.0.0/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Electron
|
|
2
|
+
|
|
3
|
+
Powerful Python library for Rubika bots - Dual Mode (Bot + Self)
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Dual Mode - Bot Token & Self Session
|
|
8
|
+
- Async/Await - High performance
|
|
9
|
+
- Simple decorator-based API
|
|
10
|
+
- Advanced handler system
|
|
11
|
+
- Fully independent
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install Electron
|
|
17
|
+
from electron import Bot
|
|
18
|
+
|
|
19
|
+
# Bot mode
|
|
20
|
+
bot = Bot(token="YOUR_TOKEN")
|
|
21
|
+
# OR Self mode
|
|
22
|
+
bot = Bot(session="my_account")
|
|
23
|
+
|
|
24
|
+
@bot.on_command("start")
|
|
25
|
+
async def start(message):
|
|
26
|
+
await message.reply("Hello!")
|
|
27
|
+
|
|
28
|
+
bot.run()
|
|
29
|
+
|
|
30
|
+
### ۲. آپدیت setup.py:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cat > setup.py << 'ENDOFFILE'
|
|
34
|
+
from setuptools import setup, find_packages
|
|
35
|
+
|
|
36
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
37
|
+
long_description = fh.read()
|
|
38
|
+
|
|
39
|
+
setup(
|
|
40
|
+
name="Electron",
|
|
41
|
+
version="1.0.0",
|
|
42
|
+
author="Your Name",
|
|
43
|
+
author_email="your@email.com",
|
|
44
|
+
description="Powerful Python library for Rubika bots - Dual Mode",
|
|
45
|
+
long_description=long_description,
|
|
46
|
+
long_description_content_type="text/markdown",
|
|
47
|
+
url="https://github.com/yourusername/Electron",
|
|
48
|
+
packages=find_packages(),
|
|
49
|
+
classifiers=[
|
|
50
|
+
"Programming Language :: Python :: 3",
|
|
51
|
+
"License :: OSI Approved :: MIT License",
|
|
52
|
+
"Operating System :: OS Independent",
|
|
53
|
+
],
|
|
54
|
+
python_requires=">=3.7",
|
|
55
|
+
install_requires=[
|
|
56
|
+
"aiohttp>=3.8.0",
|
|
57
|
+
"requests>=2.28.0",
|
|
58
|
+
],
|
|
59
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Electron - کتابخونه قدرتمند پایتون برای ساخت رباتهای روبیکا
|
|
3
|
+
پشتیبانی از Bot Token و Self Session
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "1.0.0"
|
|
7
|
+
__author__ = "Your Name"
|
|
8
|
+
|
|
9
|
+
from .bot import Bot
|
|
10
|
+
from .types import Message, Chat, User, MessageType
|
|
11
|
+
from .handlers import (
|
|
12
|
+
Handler, MessageHandler, CommandHandler,
|
|
13
|
+
PhotoHandler, VideoHandler, VoiceHandler
|
|
14
|
+
)
|
|
15
|
+
from .filters import Filters
|
|
16
|
+
from .client import create_client, BotClient, SelfClient
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional, Callable, List, Dict, Any, Union
|
|
4
|
+
from .client import create_client, BotClient, SelfClient, BaseClient
|
|
5
|
+
from .types import Message, Chat, User, MessageType
|
|
6
|
+
from .handlers import (
|
|
7
|
+
Handler, MessageHandler, CommandHandler,
|
|
8
|
+
PhotoHandler, VideoHandler, VoiceHandler, DocumentHandler
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
logging.basicConfig(level=logging.INFO)
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class Bot:
|
|
15
|
+
def __init__(self, token: str = None, session: str = None, parse_mode: str = None):
|
|
16
|
+
if not token and not session:
|
|
17
|
+
raise ValueError("Either 'token' or 'session' must be provided!")
|
|
18
|
+
|
|
19
|
+
self.client: BaseClient = create_client(token=token, session=session)
|
|
20
|
+
self.token = token
|
|
21
|
+
self.session_name = session
|
|
22
|
+
self.handlers: List[Handler] = []
|
|
23
|
+
self._running = False
|
|
24
|
+
self._me = None
|
|
25
|
+
self.parse_mode = parse_mode
|
|
26
|
+
|
|
27
|
+
self.is_bot = token is not None
|
|
28
|
+
self.is_self = session is not None
|
|
29
|
+
|
|
30
|
+
if self.is_bot:
|
|
31
|
+
logger.info("Bot mode: Ready")
|
|
32
|
+
else:
|
|
33
|
+
logger.info("Self mode: Ready")
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
async def me(self) -> Dict:
|
|
37
|
+
if not self._me:
|
|
38
|
+
self._me = await self.client.get_me()
|
|
39
|
+
return self._me
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def mode(self) -> str:
|
|
43
|
+
return "bot" if self.is_bot else "self"
|
|
44
|
+
|
|
45
|
+
def on_message(self, callback: Callable = None):
|
|
46
|
+
def decorator(func):
|
|
47
|
+
handler = MessageHandler(func)
|
|
48
|
+
self.handlers.append(handler)
|
|
49
|
+
return func
|
|
50
|
+
if callback:
|
|
51
|
+
return decorator(callback)
|
|
52
|
+
return decorator
|
|
53
|
+
|
|
54
|
+
def on_command(self, command: str, prefix: str = "/"):
|
|
55
|
+
def decorator(func):
|
|
56
|
+
handler = CommandHandler(command, func, prefix)
|
|
57
|
+
self.handlers.append(handler)
|
|
58
|
+
return func
|
|
59
|
+
return decorator
|
|
60
|
+
|
|
61
|
+
def on_photo(self, callback: Callable = None):
|
|
62
|
+
def decorator(func):
|
|
63
|
+
handler = PhotoHandler(func)
|
|
64
|
+
self.handlers.append(handler)
|
|
65
|
+
return func
|
|
66
|
+
if callback:
|
|
67
|
+
return decorator(callback)
|
|
68
|
+
return decorator
|
|
69
|
+
|
|
70
|
+
def on_video(self, callback: Callable = None):
|
|
71
|
+
def decorator(func):
|
|
72
|
+
handler = VideoHandler(func)
|
|
73
|
+
self.handlers.append(handler)
|
|
74
|
+
return func
|
|
75
|
+
if callback:
|
|
76
|
+
return decorator(callback)
|
|
77
|
+
return decorator
|
|
78
|
+
|
|
79
|
+
def on_voice(self, callback: Callable = None):
|
|
80
|
+
def decorator(func):
|
|
81
|
+
handler = VoiceHandler(func)
|
|
82
|
+
self.handlers.append(handler)
|
|
83
|
+
return func
|
|
84
|
+
if callback:
|
|
85
|
+
return decorator(callback)
|
|
86
|
+
return decorator
|
|
87
|
+
|
|
88
|
+
def on_document(self, callback: Callable = None):
|
|
89
|
+
def decorator(func):
|
|
90
|
+
handler = DocumentHandler(func)
|
|
91
|
+
self.handlers.append(handler)
|
|
92
|
+
return func
|
|
93
|
+
if callback:
|
|
94
|
+
return decorator(callback)
|
|
95
|
+
return decorator
|
|
96
|
+
|
|
97
|
+
async def _process_update(self, update: Dict):
|
|
98
|
+
try:
|
|
99
|
+
message = Message(
|
|
100
|
+
message_id=update.get("message_id", ""),
|
|
101
|
+
chat=Chat(**update.get("chat", {})),
|
|
102
|
+
from_user=User(**update.get("from", {})) if update.get("from") else None,
|
|
103
|
+
text=update.get("text"),
|
|
104
|
+
message_type=MessageType(update.get("message_type", "text")),
|
|
105
|
+
date=update.get("date"),
|
|
106
|
+
_client=self.client
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
for handler in self.handlers:
|
|
110
|
+
try:
|
|
111
|
+
if await handler.check(message):
|
|
112
|
+
await handler.execute(message)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(f"Handler error: {e}")
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"Update error: {e}")
|
|
118
|
+
|
|
119
|
+
async def start(self, poll_interval: float = 1.0):
|
|
120
|
+
self._running = True
|
|
121
|
+
logger.info(f"Electron starting in {self.mode} mode...")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
await self.client.connect()
|
|
125
|
+
me = await self.me
|
|
126
|
+
logger.info(f"Connected as: {me}")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error(f"Connection failed: {e}")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
logger.info(f"Listening for updates...")
|
|
132
|
+
|
|
133
|
+
while self._running:
|
|
134
|
+
try:
|
|
135
|
+
updates = await self.client.get_updates()
|
|
136
|
+
for update in updates:
|
|
137
|
+
await self._process_update(update)
|
|
138
|
+
await asyncio.sleep(poll_interval)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.error(f"Polling error: {e}")
|
|
141
|
+
await asyncio.sleep(5)
|
|
142
|
+
|
|
143
|
+
def stop(self):
|
|
144
|
+
self._running = False
|
|
145
|
+
asyncio.create_task(self.client.close())
|
|
146
|
+
logger.info("Bot stopped")
|
|
147
|
+
|
|
148
|
+
def run(self):
|
|
149
|
+
try:
|
|
150
|
+
print("=" * 40)
|
|
151
|
+
print(f" Electron v1.0.0 - {self.mode.upper()}")
|
|
152
|
+
print("=" * 40)
|
|
153
|
+
asyncio.run(self.start())
|
|
154
|
+
except KeyboardInterrupt:
|
|
155
|
+
logger.info("Goodbye!")
|
|
156
|
+
|
|
157
|
+
async def send_message(self, chat_id: str, text: str, reply_to: str = None, **kwargs):
|
|
158
|
+
return await self.client.send_message(chat_id, text, reply_to)
|
|
159
|
+
|
|
160
|
+
async def reply_to(self, message: Message, text: str):
|
|
161
|
+
return await self.send_message(
|
|
162
|
+
chat_id=message.chat.chat_id,
|
|
163
|
+
text=text,
|
|
164
|
+
reply_to=message.message_id
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def add_handler(self, handler: Handler):
|
|
168
|
+
self.handlers.append(handler)
|
|
169
|
+
|
|
170
|
+
def clear_handlers(self):
|
|
171
|
+
self.handlers.clear()
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
کلاینت اصلی Electron
|
|
3
|
+
پشتیبانی از دو حالت: Bot Token و User Session
|
|
4
|
+
"""
|
|
5
|
+
import asyncio
|
|
6
|
+
import aiohttp
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Optional, Dict, Any, List, Union
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class BaseClient:
|
|
14
|
+
"""کلاس پایه برای هر دو نوع کلاینت"""
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"user-agent": "okhttp/3.12.1",
|
|
18
|
+
"content-type": "application/json",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.session = None
|
|
23
|
+
self.api_url = None
|
|
24
|
+
self.connected = False
|
|
25
|
+
self.auth_token = None
|
|
26
|
+
self.is_bot = False
|
|
27
|
+
self.is_self = False
|
|
28
|
+
|
|
29
|
+
async def _get_dcs(self) -> str:
|
|
30
|
+
"""دریافت آدرس سرور روبیکا"""
|
|
31
|
+
urls = [
|
|
32
|
+
"https://getdcs.rubika.ir/",
|
|
33
|
+
"https://d3.im/",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
for url in urls:
|
|
37
|
+
try:
|
|
38
|
+
connector = aiohttp.TCPConnector(ssl=False)
|
|
39
|
+
async with aiohttp.ClientSession(connector=connector, headers=self.HEADERS) as session:
|
|
40
|
+
async with session.post(url, json={
|
|
41
|
+
"api_version": "5",
|
|
42
|
+
"method": "getDcs"
|
|
43
|
+
}, ssl=False, timeout=10) as resp:
|
|
44
|
+
data = await resp.json()
|
|
45
|
+
if data and "data" in data:
|
|
46
|
+
dcs = data["data"]
|
|
47
|
+
api_url = (dcs.get("messenger_url") or
|
|
48
|
+
dcs.get("api_url") or
|
|
49
|
+
dcs.get("url"))
|
|
50
|
+
if api_url:
|
|
51
|
+
return api_url.rstrip("/")
|
|
52
|
+
except:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
return "https://messengerg2c31.iranlms.ir"
|
|
56
|
+
|
|
57
|
+
async def connect(self) -> bool:
|
|
58
|
+
"""اتصال به روبیکا"""
|
|
59
|
+
self.api_url = await self._get_dcs()
|
|
60
|
+
|
|
61
|
+
connector = aiohttp.TCPConnector(ssl=False)
|
|
62
|
+
self.session = aiohttp.ClientSession(
|
|
63
|
+
connector=connector,
|
|
64
|
+
headers=self.HEADERS,
|
|
65
|
+
timeout=aiohttp.ClientTimeout(total=30)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
self.connected = True
|
|
69
|
+
logger.info(f"✅ Connected to: {self.api_url}")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
async def send_request(self, method: str, data: dict = None) -> dict:
|
|
73
|
+
"""ارسال درخواست به API"""
|
|
74
|
+
if not self.connected:
|
|
75
|
+
await self.connect()
|
|
76
|
+
|
|
77
|
+
payload = {
|
|
78
|
+
"api_version": "5",
|
|
79
|
+
"method": method,
|
|
80
|
+
"data": data or {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# اضافه کردن auth_token اگه bot باشه
|
|
84
|
+
if self.is_bot and self.auth_token:
|
|
85
|
+
payload["auth"] = self.auth_token
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
async with self.session.post(self.api_url, json=payload, ssl=False) as resp:
|
|
89
|
+
text = await resp.text()
|
|
90
|
+
try:
|
|
91
|
+
return json.loads(text)
|
|
92
|
+
except:
|
|
93
|
+
return {}
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Request error: {e}")
|
|
96
|
+
return {}
|
|
97
|
+
|
|
98
|
+
async def close(self):
|
|
99
|
+
if self.session:
|
|
100
|
+
await self.session.close()
|
|
101
|
+
self.connected = False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class BotClient(BaseClient):
|
|
105
|
+
"""کلاینت ربات (Bot Token)"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, token: str):
|
|
108
|
+
super().__init__()
|
|
109
|
+
self.auth_token = token
|
|
110
|
+
self.is_bot = True
|
|
111
|
+
self.is_self = False
|
|
112
|
+
|
|
113
|
+
async def get_me(self) -> dict:
|
|
114
|
+
result = await self.send_request("getUserInfo", {})
|
|
115
|
+
return result.get("data", {})
|
|
116
|
+
|
|
117
|
+
async def send_message(self, chat_id: str, text: str, reply_to: str = None) -> dict:
|
|
118
|
+
data = {
|
|
119
|
+
"object_guid": chat_id,
|
|
120
|
+
"text": text,
|
|
121
|
+
"rnd": str(int(asyncio.get_event_loop().time() * 1000))
|
|
122
|
+
}
|
|
123
|
+
if reply_to:
|
|
124
|
+
data["reply_to_message_id"] = reply_to
|
|
125
|
+
return await self.send_request("sendMessage", data)
|
|
126
|
+
|
|
127
|
+
async def get_updates(self) -> list:
|
|
128
|
+
result = await self.send_request("getChatsUpdates", {"state": 0})
|
|
129
|
+
return result.get("data", []) if isinstance(result, dict) else []
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class SelfClient(BaseClient):
|
|
133
|
+
"""کلاینت سلف (User Session)"""
|
|
134
|
+
|
|
135
|
+
def __init__(self, session_name: str = "self_bot"):
|
|
136
|
+
super().__init__()
|
|
137
|
+
self.session_name = session_name
|
|
138
|
+
self.is_bot = False
|
|
139
|
+
self.is_self = True
|
|
140
|
+
self.auth_info = None # auth, guid, private_key
|
|
141
|
+
|
|
142
|
+
def load_session(self) -> bool:
|
|
143
|
+
"""لود کردن سشن از فایل"""
|
|
144
|
+
try:
|
|
145
|
+
with open(f"{self.session_name}.session", "r") as f:
|
|
146
|
+
data = json.load(f)
|
|
147
|
+
self.auth_info = data
|
|
148
|
+
return True
|
|
149
|
+
except FileNotFoundError:
|
|
150
|
+
logger.warning(f"Session {self.session_name}.session not found")
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
def save_session(self):
|
|
154
|
+
"""ذخیره سشن تو فایل"""
|
|
155
|
+
if self.auth_info:
|
|
156
|
+
with open(f"{self.session_name}.session", "w") as f:
|
|
157
|
+
json.dump(self.auth_info, f)
|
|
158
|
+
logger.info(f"Session saved: {self.session_name}.session")
|
|
159
|
+
|
|
160
|
+
async def login(self, phone: str = None, code: str = None) -> bool:
|
|
161
|
+
"""ورود با شماره موبایل"""
|
|
162
|
+
if phone and not code:
|
|
163
|
+
# ارسال کد
|
|
164
|
+
result = await self.send_request("sendCode", {
|
|
165
|
+
"phone_number": phone
|
|
166
|
+
})
|
|
167
|
+
logger.info(f"Code sent: {result}")
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
elif phone and code:
|
|
171
|
+
# تایید کد
|
|
172
|
+
result = await self.send_request("signIn", {
|
|
173
|
+
"phone_number": phone,
|
|
174
|
+
"phone_code_hash": "",
|
|
175
|
+
"phone_code": code
|
|
176
|
+
})
|
|
177
|
+
if result and "data" in result:
|
|
178
|
+
self.auth_info = result["data"]
|
|
179
|
+
self.save_session()
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
async def get_me(self) -> dict:
|
|
185
|
+
result = await self.send_request("getUserInfo", {})
|
|
186
|
+
return result.get("data", {})
|
|
187
|
+
|
|
188
|
+
async def send_message(self, chat_id: str, text: str, reply_to: str = None) -> dict:
|
|
189
|
+
data = {
|
|
190
|
+
"object_guid": chat_id,
|
|
191
|
+
"text": text,
|
|
192
|
+
"rnd": str(int(asyncio.get_event_loop().time() * 1000))
|
|
193
|
+
}
|
|
194
|
+
if reply_to:
|
|
195
|
+
data["reply_to_message_id"] = reply_to
|
|
196
|
+
return await self.send_request("sendMessage", data)
|
|
197
|
+
|
|
198
|
+
async def get_updates(self) -> list:
|
|
199
|
+
result = await self.send_request("getChatsUpdates", {"state": 0})
|
|
200
|
+
return result.get("data", []) if isinstance(result, dict) else []
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def create_client(token: str = None, session: str = None) -> BaseClient:
|
|
204
|
+
"""
|
|
205
|
+
ساخت کلاینت مناسب بر اساس نوع ورودی
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
token: توکن ربات
|
|
209
|
+
session: نام فایل سشن
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
BotClient یا SelfClient
|
|
213
|
+
"""
|
|
214
|
+
if token:
|
|
215
|
+
logger.info("??? Bot mode activated")
|
|
216
|
+
return BotClient(token)
|
|
217
|
+
elif session:
|
|
218
|
+
client = SelfClient(session)
|
|
219
|
+
if client.load_session():
|
|
220
|
+
logger.info("??? Self mode activated (session loaded)")
|
|
221
|
+
else:
|
|
222
|
+
logger.info("??? Self mode activated (new session)")
|
|
223
|
+
return client
|
|
224
|
+
else:
|
|
225
|
+
raise ValueError("Either token or session must be provided")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import List, Callable
|
|
2
|
+
from .types import Message, MessageType
|
|
3
|
+
|
|
4
|
+
class Filters:
|
|
5
|
+
@staticmethod
|
|
6
|
+
def text(message: Message) -> bool:
|
|
7
|
+
return message.message_type == MessageType.TEXT
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def photo(message: Message) -> bool:
|
|
11
|
+
return message.message_type == MessageType.PHOTO
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def video(message: Message) -> bool:
|
|
15
|
+
return message.message_type == MessageType.VIDEO
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def voice(message: Message) -> bool:
|
|
19
|
+
return message.message_type == MessageType.VOICE
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def reply(message: Message) -> bool:
|
|
23
|
+
return message.reply_to_message is not None
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def command(commands: List[str]) -> Callable:
|
|
27
|
+
def filter_func(message: Message) -> bool:
|
|
28
|
+
if not message.text or not message.text.startswith('/'):
|
|
29
|
+
return False
|
|
30
|
+
cmd = message.text.split()[0][1:]
|
|
31
|
+
return cmd in commands
|
|
32
|
+
return filter_func
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Callable, Any
|
|
2
|
+
from .types import Message, MessageType
|
|
3
|
+
import re
|
|
4
|
+
import asyncio
|
|
5
|
+
|
|
6
|
+
class Handler:
|
|
7
|
+
def __init__(self, callback: Callable, **filters):
|
|
8
|
+
self.callback = callback
|
|
9
|
+
self.filters = filters
|
|
10
|
+
|
|
11
|
+
async def check(self, message: Message) -> bool:
|
|
12
|
+
return True
|
|
13
|
+
|
|
14
|
+
async def execute(self, message: Message) -> Any:
|
|
15
|
+
if asyncio.iscoroutinefunction(self.callback):
|
|
16
|
+
return await self.callback(message)
|
|
17
|
+
return self.callback(message)
|
|
18
|
+
|
|
19
|
+
class MessageHandler(Handler):
|
|
20
|
+
async def check(self, message: Message) -> bool:
|
|
21
|
+
return message.message_type == MessageType.TEXT and message.text is not None
|
|
22
|
+
|
|
23
|
+
class CommandHandler(Handler):
|
|
24
|
+
def __init__(self, command: str, callback: Callable, prefix: str = "/"):
|
|
25
|
+
super().__init__(callback)
|
|
26
|
+
self.command = command
|
|
27
|
+
self.prefix = prefix
|
|
28
|
+
|
|
29
|
+
async def check(self, message: Message) -> bool:
|
|
30
|
+
if message.text and message.text.startswith(self.prefix):
|
|
31
|
+
command_part = message.text.split()[0][len(self.prefix):]
|
|
32
|
+
return command_part.lower() == self.command.lower()
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
class RegexHandler(Handler):
|
|
36
|
+
def __init__(self, pattern: str, callback: Callable):
|
|
37
|
+
super().__init__(callback)
|
|
38
|
+
self.pattern = re.compile(pattern)
|
|
39
|
+
|
|
40
|
+
async def check(self, message: Message) -> bool:
|
|
41
|
+
return bool(message.text and self.pattern.search(message.text))
|
|
42
|
+
|
|
43
|
+
async def execute(self, message: Message) -> Any:
|
|
44
|
+
match = self.pattern.search(message.text)
|
|
45
|
+
message.match = match
|
|
46
|
+
return await super().execute(message)
|
|
47
|
+
|
|
48
|
+
class PhotoHandler(Handler):
|
|
49
|
+
async def check(self, message: Message) -> bool:
|
|
50
|
+
return message.message_type == MessageType.PHOTO
|
|
51
|
+
|
|
52
|
+
class VideoHandler(Handler):
|
|
53
|
+
async def check(self, message: Message) -> bool:
|
|
54
|
+
return message.message_type == MessageType.VIDEO
|
|
55
|
+
|
|
56
|
+
class VoiceHandler(Handler):
|
|
57
|
+
async def check(self, message: Message) -> bool:
|
|
58
|
+
return message.message_type == MessageType.VOICE
|
|
59
|
+
|
|
60
|
+
class DocumentHandler(Handler):
|
|
61
|
+
async def check(self, message: Message) -> bool:
|
|
62
|
+
return message.message_type == MessageType.DOCUMENT
|
|
File without changes
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
کلاینت واقعی Electron با استفاده از aiohttp
|
|
3
|
+
مستقل و قدرتمند - بدون وابستگی به rubpy
|
|
4
|
+
"""
|
|
5
|
+
import asyncio
|
|
6
|
+
import aiohttp
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Optional, Dict, Any, List
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class RealRubikaClient:
|
|
14
|
+
"""کلاینت واقعی روبیکا با aiohttp"""
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"origin": "https://m.rubika.ir",
|
|
18
|
+
"referer": "https://m.rubika.ir/",
|
|
19
|
+
"content-type": "application/json",
|
|
20
|
+
"connection": "keep-alive",
|
|
21
|
+
"user-agent": "okhttp/3.12.1"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def __init__(self, auth_token: str):
|
|
25
|
+
self.auth_token = auth_token
|
|
26
|
+
self.session = None
|
|
27
|
+
self.api_url = "https://messenger.rubika.ir/"
|
|
28
|
+
self.connected = False
|
|
29
|
+
|
|
30
|
+
async def connect(self):
|
|
31
|
+
"""اتصال به روبیکا"""
|
|
32
|
+
connector = aiohttp.TCPConnector(verify_ssl=False)
|
|
33
|
+
self.session = aiohttp.ClientSession(
|
|
34
|
+
connector=connector,
|
|
35
|
+
headers=self.HEADERS,
|
|
36
|
+
timeout=aiohttp.ClientTimeout(total=30)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# تست اتصال
|
|
40
|
+
try:
|
|
41
|
+
async with self.session.get(
|
|
42
|
+
"https://messenger.rubika.ir/",
|
|
43
|
+
ssl=False
|
|
44
|
+
) as resp:
|
|
45
|
+
if resp.status == 200:
|
|
46
|
+
self.connected = True
|
|
47
|
+
logger.info("✅ به روبیکا متصل شد!")
|
|
48
|
+
return True
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"❌ خطای اتصال: {e}")
|
|
51
|
+
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
async def send_request(self, method: str, data: dict) -> dict:
|
|
55
|
+
"""ارسال درخواست به API"""
|
|
56
|
+
if not self.connected:
|
|
57
|
+
await self.connect()
|
|
58
|
+
|
|
59
|
+
url = f"{self.api_url}api/{method}"
|
|
60
|
+
|
|
61
|
+
payload = {
|
|
62
|
+
"api_version": "5",
|
|
63
|
+
"auth": self.auth_token,
|
|
64
|
+
"data": data,
|
|
65
|
+
"method": method
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
async with self.session.post(url, json=payload, ssl=False) as resp:
|
|
70
|
+
result = await resp.json()
|
|
71
|
+
return result.get("data", {})
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Request error: {e}")
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
async def get_me(self) -> dict:
|
|
77
|
+
return await self.send_request("getUserInfo", {})
|
|
78
|
+
|
|
79
|
+
async def send_message(self, chat_id: str, text: str, reply_to: str = None) -> dict:
|
|
80
|
+
data = {
|
|
81
|
+
"object_guid": chat_id,
|
|
82
|
+
"text": text
|
|
83
|
+
}
|
|
84
|
+
if reply_to:
|
|
85
|
+
data["reply_to_message_id"] = reply_to
|
|
86
|
+
|
|
87
|
+
return await self.send_request("sendMessage", data)
|
|
88
|
+
|
|
89
|
+
async def get_updates(self, limit: int = 10) -> list:
|
|
90
|
+
result = await self.send_request("getChatsUpdates", {
|
|
91
|
+
"state": 0
|
|
92
|
+
})
|
|
93
|
+
return result if isinstance(result, list) else []
|
|
94
|
+
|
|
95
|
+
async def close(self):
|
|
96
|
+
if self.session:
|
|
97
|
+
await self.session.close()
|
|
98
|
+
self.connected = False
|
|
99
|
+
|
|
100
|
+
# تست
|
|
101
|
+
async def test():
|
|
102
|
+
TOKEN = "BHBAHF0PYXRCYQWUDIGELJPUNRCEXXBUYLMQHJFILWKMEOYWXACICRLKKZZRIMVB"
|
|
103
|
+
|
|
104
|
+
client = RealRubikaClient(TOKEN)
|
|
105
|
+
|
|
106
|
+
print("??? Connecting...")
|
|
107
|
+
if await client.connect():
|
|
108
|
+
print("✅ Connected!")
|
|
109
|
+
|
|
110
|
+
print("\n??? Testing get_me...")
|
|
111
|
+
me = await client.get_me()
|
|
112
|
+
print(f"Result: {me}")
|
|
113
|
+
|
|
114
|
+
print("\n??? Testing get_updates...")
|
|
115
|
+
updates = await client.get_updates()
|
|
116
|
+
print(f"Updates: {len(updates)} found")
|
|
117
|
+
|
|
118
|
+
await client.close()
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
asyncio.run(test())
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Optional, List, Dict, Any
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
class MessageType(Enum):
|
|
6
|
+
TEXT = "text"
|
|
7
|
+
PHOTO = "photo"
|
|
8
|
+
VIDEO = "video"
|
|
9
|
+
VOICE = "voice"
|
|
10
|
+
DOCUMENT = "document"
|
|
11
|
+
STICKER = "sticker"
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class User:
|
|
15
|
+
user_id: str
|
|
16
|
+
username: Optional[str] = None
|
|
17
|
+
first_name: Optional[str] = None
|
|
18
|
+
last_name: Optional[str] = None
|
|
19
|
+
phone: Optional[str] = None
|
|
20
|
+
bio: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def full_name(self) -> str:
|
|
24
|
+
if self.first_name and self.last_name:
|
|
25
|
+
return f"{self.first_name} {self.last_name}"
|
|
26
|
+
return self.first_name or self.username or "Unknown"
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Chat:
|
|
30
|
+
chat_id: str
|
|
31
|
+
type: str = "private"
|
|
32
|
+
title: Optional[str] = None
|
|
33
|
+
username: Optional[str] = None
|
|
34
|
+
members_count: Optional[int] = None
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Message:
|
|
38
|
+
message_id: str
|
|
39
|
+
chat: Chat
|
|
40
|
+
from_user: Optional[User] = None
|
|
41
|
+
text: Optional[str] = None
|
|
42
|
+
message_type: MessageType = MessageType.TEXT
|
|
43
|
+
reply_to_message: Optional['Message'] = None
|
|
44
|
+
file_id: Optional[str] = None
|
|
45
|
+
caption: Optional[str] = None
|
|
46
|
+
date: Optional[int] = None
|
|
47
|
+
_client: Any = None
|
|
48
|
+
|
|
49
|
+
def reply(self, text: str, **kwargs):
|
|
50
|
+
if self._client:
|
|
51
|
+
return self._client.send_message(
|
|
52
|
+
chat_id=self.chat.chat_id,
|
|
53
|
+
text=text,
|
|
54
|
+
reply_to_message_id=self.message_id,
|
|
55
|
+
**kwargs
|
|
56
|
+
)
|
|
57
|
+
raise ValueError("Client not set")
|
|
58
|
+
|
|
59
|
+
def delete(self):
|
|
60
|
+
if self._client:
|
|
61
|
+
return self._client.delete_message(
|
|
62
|
+
chat_id=self.chat.chat_id,
|
|
63
|
+
message_id=self.message_id
|
|
64
|
+
)
|
|
65
|
+
raise ValueError("Client not set")
|
|
66
|
+
|
|
67
|
+
def edit(self, new_text: str):
|
|
68
|
+
if self._client:
|
|
69
|
+
return self._client.edit_message(
|
|
70
|
+
chat_id=self.chat.chat_id,
|
|
71
|
+
message_id=self.message_id,
|
|
72
|
+
text=new_text
|
|
73
|
+
)
|
|
74
|
+
raise ValueError("Client not set")
|
electron-1.0.0/setup.cfg
ADDED
electron-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="Electron",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
author="Your Name",
|
|
7
|
+
description="A powerful Python library for Rubika bots",
|
|
8
|
+
packages=find_packages(),
|
|
9
|
+
install_requires=[
|
|
10
|
+
"aiohttp>=3.8.0",
|
|
11
|
+
"requests>=2.28.0",
|
|
12
|
+
],
|
|
13
|
+
python_requires=">=3.7",
|
|
14
|
+
)
|