├── __init__.py ├── xui ├── __init__.py └── api.py ├── database ├── __init__.py ├── database.py └── models.py ├── requirements.txt ├── screen ├── InShot_20250711_185241985.jpg └── InShot_20250711_185303033.jpg ├── README.md ├── utils ├── fucntions.py └── structs.py └── main.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /xui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | telethon 2 | asyncio 3 | requests 4 | sqlalchemy -------------------------------------------------------------------------------- /screen/InShot_20250711_185241985.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6-E-L-F-6/xui-manager/HEAD/screen/InShot_20250711_185241985.jpg -------------------------------------------------------------------------------- /screen/InShot_20250711_185303033.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6-E-L-F-6/xui-manager/HEAD/screen/InShot_20250711_185303033.jpg -------------------------------------------------------------------------------- /database/database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.orm import sessionmaker 3 | 4 | class DBManager: 5 | def __init__(self, db_url: str = "sqlite:///xui_bot.db"): 6 | self.engine = create_engine(db_url, connect_args={"check_same_thread": False}) 7 | self.SessionLocal = sessionmaker(bind=self.engine) 8 | 9 | def get_session(self): 10 | return self.SessionLocal() 11 | -------------------------------------------------------------------------------- /database/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String 2 | from sqlalchemy.orm import declarative_base 3 | 4 | Base = declarative_base() 5 | 6 | class Panel(Base): 7 | __tablename__ = "panels" 8 | id = Column(Integer, primary_key=True, index=True) 9 | name = Column(String, unique=True, nullable=False) 10 | base_url = Column(String, nullable=False) 11 | username = Column(String, nullable=False) 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # X-UI Manager 2 | A project based on the Telegram bot platform to manage the X-UI panel 3 | 4 |  5 |  6 | 7 | # Install X-UI panel 8 | 9 | 1. execute command on terminal 10 | 11 | ```bash 12 | bash <(curl -Ls https://raw.githubusercontent.com/NidukaAkalanka/x-ui-english/master/install.sh) 13 | ``` 14 | 15 | 2. setup username, password and port 16 | 17 | # Start the robot 18 | 19 | 1. edit source bot 20 | 21 | > open main.py file and edit this lines 22 | ```python 23 | api_id = '' 24 | api_hash = '' 25 | bot_token = '' 26 | ``` 27 | 28 | 2. execute command in terminal 29 | 30 | **debian** 31 | ```bash 32 | apt-get install python3 python3-pip git 33 | ``` 34 | 35 | **clone project** 36 | ```bash 37 | git clone https://github.com/6-E-L-F-6/xui-manager 38 | cd xui-manager 39 | ``` 40 | **Install the prerequisites.** 41 | ```bash 42 | pip install -r requirements.txt 43 | ``` 44 | **lunch bot** 45 | ```bash 46 | python3 main.py 47 | ``` 48 | 49 | **I would be happy to hear your comments or criticisms about my project. Also, if you are interested in collaborating on this project, please contact me. Telegram: [My Account](https://t.me/E6L6F6)** 50 | -------------------------------------------------------------------------------- /utils/fucntions.py: -------------------------------------------------------------------------------- 1 | import base64 ,urllib ,json 2 | from .structs import ServerStatus 3 | 4 | class StepManager: 5 | def __init__(self): 6 | self.user_steps = {} 7 | 8 | def set_step(self, user_id, step): 9 | self.user_steps[user_id] = step 10 | 11 | def get_step(self, user_id): 12 | return self.user_steps.get(user_id) 13 | 14 | def reset_step(self, user_id): 15 | if user_id in self.user_steps: 16 | del self.user_steps[user_id] 17 | 18 | def is_in_step(self, user_id, step): 19 | return self.user_steps.get(user_id) == step 20 | 21 | def format_size(bytes_amount): 22 | for unit in ['B', 'KB', 'MB', 'GB', 'TB']: 23 | if bytes_amount < 1024: 24 | return f"{bytes_amount:.2f} {unit}" 25 | bytes_amount /= 1024 26 | return f"{bytes_amount:.2f} PB" 27 | 28 | def format_server_status(status: ServerStatus , users) -> str: 29 | uptime_hours = status.uptime // 3600 30 | uptime_minutes = (status.uptime % 3600) // 60 31 | uptime_seconds = status.uptime % 60 32 | 33 | message = ( 34 | f"📊 وضعیت سرور\n\n" 35 | f"🖥️ CPU: {status.cpu:.2f}%\n" 36 | f"💾 RAM: {format_size(status.mem.current)} / {format_size(status.mem.total)}\n" 37 | f"📀 SWAP: {format_size(status.swap.current)} / {format_size(status.swap.total)}\n" 38 | f"💽 Disk: {format_size(status.disk.current)} / {format_size(status.disk.total)}\n\n" 39 | 40 | f"⚙️ Xray:\n" 41 | f"▫️State: {status.xray.state}\n" 42 | f"▫️Version: {status.xray.version}\n" 43 | f"▫️Error: {status.xray.errorMsg or 'None'}\n\n" 44 | 45 | f"⏳ Uptime: {uptime_hours}h {uptime_minutes}m {uptime_seconds}s\n" 46 | f"📈 Loads: {', '.join([str(load) for load in status.loads])}\n\n" 47 | 48 | f"🔌 Connections:\n" 49 | f"▫️TCP: {status.tcpCount}\n" 50 | f"▫️UDP: {status.udpCount}\n\n" 51 | 52 | f"📡 Network IO:\n" 53 | f"▫️Upload: {format_size(status.netIO.up)}\n" 54 | f"▫️Download: {format_size(status.netIO.down)}\n\n" 55 | 56 | f"📥 Network Traffic:\n" 57 | f"▫️Sent: {format_size(status.netTraffic.sent)}\n" 58 | f"▫️Received: {format_size(status.netTraffic.recv)}\n\n" 59 | 60 | f"📥Users:" 61 | f" {len(users)}\n" 62 | ) 63 | 64 | return message 65 | 66 | def build_vmess_link(client_id: str, remark, address: str, port: int, alter_id=0, security='none', network='tcp'): 67 | vmess_config = { 68 | "v": "2", 69 | "ps": remark, 70 | "add": address, 71 | "port": str(port), 72 | "id": client_id, 73 | "aid": str(alter_id), 74 | "net": network, 75 | "type": "none", 76 | "host": "", 77 | "path": "", 78 | "tls": "none", 79 | "sni": "" 80 | } 81 | json_str = json.dumps(vmess_config, separators=(',', ':')) 82 | b64 = base64.b64encode(json_str.encode()).decode() 83 | return "vmess://" + b64 84 | 85 | def build_shadowsocks_link(method: str, password: str, address: str, port: int, remark): 86 | user_info = f"{method}:{password}@{address}:{port}" 87 | base64_user_info = base64.urlsafe_b64encode(user_info.encode()).decode().rstrip('=') 88 | tag = urllib.parse.quote(remark) if remark else 'shadowsocks' 89 | return f"ss://{base64_user_info}#{tag}" 90 | 91 | def build_vless_link(client_id, address, port, remark,network='tcp', security='none', ): 92 | query_params = { 93 | "type": network, 94 | "security": security, 95 | } 96 | query_string = urllib.parse.urlencode(query_params) 97 | tag = urllib.parse.quote(remark) if remark else client_id[:8] 98 | 99 | link = f"vless://{client_id}@{address}:{port}?{query_string}#{tag}" 100 | return link 101 | 102 | def get_user_config_link(remark, inbounds, server_address: str): 103 | for inbound in inbounds: 104 | if inbound.remark != remark: 105 | continue 106 | 107 | protocol = inbound.protocol 108 | settings = inbound.settings 109 | if isinstance(settings, str): 110 | settings = json.loads(settings) 111 | 112 | stream_settings = inbound.streamSettings 113 | if isinstance(stream_settings, str): 114 | stream_settings = json.loads(stream_settings) 115 | 116 | port = inbound.port 117 | network = stream_settings.get('network', 'tcp') 118 | security = stream_settings.get('security', 'none') 119 | 120 | if protocol in ('vmess', 'vless'): 121 | clients = settings.get('clients', []) 122 | if not clients: 123 | return None 124 | 125 | client = clients[0] 126 | client_id = client['id'] 127 | 128 | if protocol == 'vmess': 129 | return build_vmess_link( 130 | remark=remark, 131 | client_id=client_id, 132 | address=server_address.split(":")[0], 133 | port=port, 134 | alter_id=client.get('alterId', 0), 135 | security=security, 136 | network=network 137 | ) 138 | else: 139 | return build_vless_link( 140 | client_id=client_id, 141 | address=server_address.split(":")[0], 142 | port=port, 143 | network=network, 144 | security=security, 145 | remark=remark 146 | ) 147 | 148 | elif protocol == 'shadowsocks': 149 | method = settings.get('method', '') 150 | password = settings.get('password', '') 151 | return build_shadowsocks_link( 152 | method=method, 153 | password=password, 154 | address=server_address.split(":")[0], 155 | port=port, 156 | remark=remark 157 | ) 158 | 159 | return None 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /utils/structs.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Dict, Any 3 | import uuid,json 4 | 5 | @dataclass 6 | class ClientStat: 7 | id: int 8 | inboundId: int 9 | enable: bool 10 | email: str 11 | up: int 12 | down: int 13 | expiryTime: int 14 | total: int 15 | 16 | @dataclass 17 | class Inbound: 18 | id: int 19 | up: int 20 | down: int 21 | total: int 22 | remark: str 23 | enable: bool 24 | expiryTime: int 25 | clientStats: List[ClientStat] 26 | listen: str 27 | port: int 28 | protocol: str 29 | settings: Dict[str, Any] 30 | streamSettings: Dict[str, Any] 31 | tag: str 32 | sniffing: Dict[str, Any] 33 | 34 | @dataclass 35 | class MemoryInfo: 36 | current: int 37 | total: int 38 | 39 | @dataclass 40 | class SwapInfo: 41 | current: int 42 | total: int 43 | 44 | @dataclass 45 | class DiskInfo: 46 | current: int 47 | total: int 48 | 49 | @dataclass 50 | class XrayInfo: 51 | state: str 52 | errorMsg: str 53 | version: str 54 | 55 | @dataclass 56 | class NetIOInfo: 57 | up: int 58 | down: int 59 | 60 | @dataclass 61 | class NetTrafficInfo: 62 | sent: int 63 | recv: int 64 | 65 | @dataclass 66 | class ServerStatus: 67 | cpu: float 68 | mem: MemoryInfo 69 | swap: SwapInfo 70 | disk: DiskInfo 71 | xray: XrayInfo 72 | uptime: int 73 | loads: List[float] 74 | tcpCount: int 75 | udpCount: int 76 | netIO: NetIOInfo 77 | netTraffic: NetTrafficInfo 78 | 79 | 80 | @dataclass 81 | class User: 82 | up: int 83 | down: int 84 | total: int 85 | remark: str 86 | enable: bool 87 | expiryTime: int 88 | listen: str 89 | port: int 90 | protocol: str 91 | 92 | def to_payload(self): 93 | raise NotImplementedError("This method must be implemented in subclasses.") 94 | 95 | @dataclass 96 | class VmessUser(User): 97 | client_id: str = str(uuid.uuid1()) 98 | alter_id: int = 0 99 | 100 | def to_payload(self): 101 | return { 102 | 'up': self.up, 103 | 'down': self.down, 104 | 'total': self.total, 105 | 'remark': self.remark, 106 | 'enable': str(self.enable).lower(), 107 | 'expiryTime': self.expiryTime, 108 | 'listen': self.listen, 109 | 'port': self.port, 110 | 'protocol': self.protocol, 111 | 'settings': json.dumps({ 112 | 'clients': [{ 113 | 'id': self.client_id, 114 | 'alterId': self.alter_id, 115 | 'email': '', 116 | 'limitIp': 0, 117 | 'totalGB': 0, 118 | 'expiryTime': '' 119 | }], 120 | 'disableInsecureEncryption': False 121 | }), 122 | 'streamSettings': json.dumps({ 123 | 'network': 'tcp', 124 | 'security': 'none', 125 | 'tcpSettings': { 126 | 'acceptProxyProtocol': False, 127 | 'header': {'type': 'none'} 128 | } 129 | }), 130 | 'sniffing': json.dumps({ 131 | 'enabled': True, 132 | 'destOverride': ['http', 'tls'] 133 | }) 134 | } 135 | 136 | @dataclass 137 | class VlessUser(User): 138 | client_id: str = str(uuid.uuid1()) 139 | flow: str = 'xtls-rprx-direct' 140 | 141 | def to_payload(self): 142 | return { 143 | 'up': self.up, 144 | 'down': self.down, 145 | 'total': self.total, 146 | 'remark': self.remark, 147 | 'enable': str(self.enable).lower(), 148 | 'expiryTime': self.expiryTime, 149 | 'listen': self.listen, 150 | 'port': self.port, 151 | 'protocol': self.protocol, 152 | 'settings': json.dumps({ 153 | 'clients': [{ 154 | 'id': self.client_id, 155 | 'flow': self.flow, 156 | 'email': '', 157 | 'limitIp': 0, 158 | 'totalGB': 0, 159 | 'expiryTime': '' 160 | }], 161 | 'decryption': 'none', 162 | 'fallbacks': [] 163 | }), 164 | 'streamSettings': json.dumps({ 165 | 'network': 'tcp', 166 | 'security': 'none', 167 | 'tcpSettings': { 168 | 'acceptProxyProtocol': False, 169 | 'header': {'type': 'none'} 170 | } 171 | }), 172 | 'sniffing': json.dumps({ 173 | 'enabled': True, 174 | 'destOverride': ['http', 'tls'] 175 | }) 176 | } 177 | 178 | @dataclass 179 | class ShadowsocksUser(User): 180 | method: str 181 | password: str 182 | 183 | def to_payload(self): 184 | return { 185 | 'up': self.up, 186 | 'down': self.down, 187 | 'total': self.total, 188 | 'remark': self.remark, 189 | 'enable': str(self.enable).lower(), 190 | 'expiryTime': self.expiryTime, 191 | 'listen': self.listen, 192 | 'port': self.port, 193 | 'protocol': self.protocol, 194 | 'settings': json.dumps({ 195 | 'method': self.method, 196 | 'password': self.password, 197 | 'network': 'tcp,udp' 198 | }), 199 | 'streamSettings': json.dumps({ 200 | 'network': 'tcp', 201 | 'security': 'tls', 202 | 'tlsSettings': { 203 | 'serverName': '', 204 | 'certificates': [{'certificateFile': '', 'keyFile': ''}], 205 | 'alpn': [] 206 | }, 207 | 'tcpSettings': { 208 | 'acceptProxyProtocol': False, 209 | 'header': {'type': 'none'} 210 | } 211 | }), 212 | 'sniffing': json.dumps({ 213 | 'enabled': True, 214 | 'destOverride': ['http', 'tls'] 215 | }) 216 | } 217 | -------------------------------------------------------------------------------- /xui/api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from utils.fucntions import * 3 | from utils.structs import * 4 | 5 | class UserFactory: 6 | @staticmethod 7 | def create_user(protocol: str, **kwargs): 8 | if protocol == 'vmess': 9 | return VmessUser(protocol='vmess', **kwargs) 10 | elif protocol == 'vless': 11 | return VlessUser(protocol='vless', **kwargs) 12 | elif protocol == 'shadowsocks': 13 | return ShadowsocksUser(protocol='shadowsocks', **kwargs) 14 | else: 15 | raise ValueError(f"Unsupported protocol: {protocol}") 16 | 17 | class XUIPanelAPI: 18 | def __init__(self, base_url: str): 19 | self.base_url = base_url.rstrip('/') 20 | self.session = requests.Session() 21 | self.session.headers.update({ 22 | 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0', 23 | 'Accept': 'application/json, text/plain, */*', 24 | 'Accept-Language': 'en-US,en;q=0.5', 25 | 'X-Requested-With': 'XMLHttpRequest', 26 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 27 | 'Origin': self.base_url, 28 | 'Referer': f'{self.base_url}/', 29 | 'Connection': 'keep-alive', 30 | 'Cookie': 'lang=en-US', 31 | }) 32 | self.is_logged_in = False 33 | 34 | def login(self, username: str, password: str) -> bool: 35 | clean_base_url = self.base_url.rstrip('/') 36 | if clean_base_url.endswith('/xui'): 37 | clean_base_url = clean_base_url[:-4] 38 | 39 | 40 | url = f"{clean_base_url}/login" 41 | data = { 42 | 'username': username, 43 | 'password': password 44 | } 45 | 46 | response = self.session.post(url, data=data) 47 | 48 | if response.ok and response.json().get('success'): 49 | print("[+] Login successful!") 50 | self.is_logged_in = True 51 | return True 52 | else: 53 | print("[-] Login failed:", response.text) 54 | return False 55 | 56 | 57 | def logout(self) -> bool: 58 | if not self.is_logged_in: 59 | print("[-] You are not logged in.") 60 | return False 61 | clean_base_url = self.base_url.rstrip('/') 62 | if clean_base_url.endswith('/xui'): 63 | clean_base_url = clean_base_url[:-4] 64 | 65 | url = f"{clean_base_url}/logout" 66 | 67 | cookies = self.session.cookies.get_dict() 68 | cookie_header = f"lang=en-US; session={cookies.get('session')}" 69 | 70 | response = self.session.get( 71 | url, 72 | headers={ 73 | 'Referer': f'{self.base_url}/inbounds', 74 | 'Cookie': cookie_header 75 | } 76 | ) 77 | 78 | if response.ok: 79 | print("[+] Logged out successfully.") 80 | self.is_logged_in = False 81 | self.session.cookies.clear() 82 | return True 83 | else: 84 | print(f"[-] Logout failed: HTTP {response.status_code}") 85 | return False 86 | 87 | def get_server_status(self): 88 | if not self.is_logged_in: 89 | print("[-] Please login first!") 90 | return None 91 | 92 | clean_base_url = self.base_url.rstrip('/') 93 | if clean_base_url.endswith('/xui'): 94 | clean_base_url = clean_base_url[:-4] 95 | 96 | url = f"{clean_base_url}/server/status" 97 | 98 | cookies = self.session.cookies.get_dict() 99 | cookie_header = f"lang=en-US; session={cookies.get('session')}" 100 | 101 | response = self.session.post( 102 | url, 103 | data={}, 104 | headers={ 105 | 'Referer': f'{self.base_url}/', 106 | 'Cookie': cookie_header 107 | } 108 | ) 109 | 110 | if response.ok: 111 | data = response.json() 112 | 113 | if not data.get('success'): 114 | print("[-] Server returned an error.") 115 | return None 116 | 117 | obj = data.get('obj') 118 | status = ServerStatus( 119 | cpu=obj['cpu'], 120 | mem=MemoryInfo(current=obj['mem']['current'], total=obj['mem']['total']), 121 | swap=SwapInfo(current=obj['swap']['current'], total=obj['swap']['total']), 122 | disk=DiskInfo(current=obj['disk']['current'], total=obj['disk']['total']), 123 | xray=XrayInfo(**obj['xray']), 124 | uptime=obj['uptime'], 125 | loads=obj['loads'], 126 | tcpCount=obj['tcpCount'], 127 | udpCount=obj['udpCount'], 128 | netIO=NetIOInfo(**obj['netIO']), 129 | netTraffic=NetTrafficInfo(**obj['netTraffic']) 130 | ) 131 | 132 | return status 133 | 134 | else: 135 | print("[-] Failed to get server status:", response.text) 136 | return None 137 | 138 | 139 | 140 | def add_user(self, user: User): 141 | if not self.is_logged_in: 142 | print("[-] Please login first!") 143 | return None 144 | 145 | url = f"{self.base_url}/inbound/add" 146 | 147 | cookies = self.session.cookies.get_dict() 148 | cookie_header = f"lang=en-US; session={cookies.get('session')}" 149 | 150 | response = self.session.post( 151 | url, 152 | data=user.to_payload(), 153 | headers={ 154 | 'Referer': f'{self.base_url}/inbounds', 155 | 'Cookie': cookie_header 156 | } 157 | ) 158 | 159 | if response.ok: 160 | data = response.json() 161 | if data.get('success'): 162 | print("[+] User added successfully!") 163 | return data 164 | else: 165 | print("[-] Failed to add user:", data.get('msg')) 166 | return None 167 | else: 168 | print("[-] HTTP request failed:", response.status_code) 169 | return None 170 | 171 | def list_inbounds(self): 172 | if not self.is_logged_in: 173 | print("[-] Please login first!") 174 | return None 175 | 176 | url = f"{self.base_url}/inbound/list" 177 | 178 | cookies = self.session.cookies.get_dict() 179 | cookie_header = f"lang=en-US; session={cookies.get('session')}" 180 | 181 | response = self.session.post( 182 | url, 183 | data={}, 184 | headers={ 185 | 'Referer': f'{self.base_url}/inbounds', 186 | 'Cookie': cookie_header 187 | } 188 | ) 189 | 190 | if response.ok: 191 | data = response.json() 192 | 193 | if not data.get('success'): 194 | print("[-] Server returned an error.") 195 | return None 196 | 197 | inbounds_list: List[Inbound] = [] 198 | for item in data.get('obj', []): 199 | client_stats = [ClientStat(**client) for client in item.get('clientStats', [])] 200 | 201 | inbound = Inbound( 202 | id=item['id'], 203 | up=item['up'], 204 | down=item['down'], 205 | total=item['total'], 206 | remark=item['remark'], 207 | enable=item['enable'], 208 | expiryTime=item['expiryTime'], 209 | clientStats=client_stats, 210 | listen=item.get('listen', ''), 211 | port=item['port'], 212 | protocol=item['protocol'], 213 | settings=json.loads(item.get('settings', '{}')), 214 | streamSettings=json.loads(item.get('streamSettings', '{}')), 215 | tag=item['tag'], 216 | sniffing=json.loads(item.get('sniffing', '{}')) 217 | ) 218 | inbounds_list.append(inbound) 219 | 220 | return inbounds_list 221 | 222 | else: 223 | print("[-] Failed to get inbounds list:", response.text) 224 | return None 225 | 226 | 227 | def update_inbound(self, inbound): 228 | if not self.is_logged_in: 229 | print("[-] Please login first!") 230 | return False 231 | 232 | url = f"{self.base_url}/inbound/update/{inbound.id}" 233 | 234 | data = { 235 | "up": inbound.up, 236 | "down": inbound.down, 237 | "total": inbound.total, 238 | "remark": inbound.remark, 239 | "enable": str(inbound.enable).lower(), 240 | "expiryTime": inbound.expiryTime, 241 | "listen": inbound.listen or "", 242 | "port": inbound.port, 243 | "protocol": inbound.protocol, 244 | "settings": json.dumps(inbound.settings, separators=(',', ':')), 245 | "streamSettings": json.dumps(inbound.streamSettings, separators=(',', ':')), 246 | "sniffing": json.dumps(inbound.sniffing, separators=(',', ':')) 247 | } 248 | 249 | cookies = self.session.cookies.get_dict() 250 | cookie_header = f"lang=en-US; session={cookies.get('session')}" 251 | 252 | response = self.session.post( 253 | url, 254 | data=data, 255 | headers={ 256 | 'Referer': f'{self.base_url}/inbounds', 257 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 258 | 'Cookie': cookie_header 259 | } 260 | ) 261 | 262 | if response.ok: 263 | resp_json = response.json() 264 | if resp_json.get('success'): 265 | print(f"[+] Inbound {inbound.id} updated successfully.") 266 | return True 267 | else: 268 | print(f"[-] Update failed: {resp_json.get('msg')}") 269 | return False 270 | else: 271 | print(f"[-] HTTP error: {response.status_code} - {response.text}") 272 | return False 273 | 274 | def delete_inbound(self, inbound_id) : 275 | if not self.is_logged_in: 276 | print("[-] Please login first!") 277 | return False 278 | 279 | url = f"{self.base_url}/inbound/del/{inbound_id}" 280 | 281 | cookies = self.session.cookies.get_dict() 282 | cookie_header = f"lang=en-US; session={cookies.get('session')}" 283 | 284 | response = self.session.post( 285 | url, 286 | data={}, 287 | headers={ 288 | 'Referer': f'{self.base_url}/inbounds', 289 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 290 | 'Cookie': cookie_header 291 | } 292 | ) 293 | 294 | if response.ok: 295 | resp_json = response.json() 296 | if resp_json.get('success'): 297 | print(f"[+] Inbound {inbound_id} deleted successfully.") 298 | return True 299 | else: 300 | print(f"[-] Failed to delete inbound: {resp_json.get('msg')}") 301 | return False 302 | else: 303 | print(f"[-] HTTP error: {response.status_code} - {response.text}") 304 | return False 305 | 306 | 307 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from telethon import TelegramClient, events, Button 2 | from datetime import datetime 3 | from pprint import pformat 4 | 5 | from database.database import DBManager 6 | from database.models import Base, Panel 7 | 8 | from xui.api import XUIPanelAPI 9 | 10 | from utils.fucntions import * 11 | from utils.structs import * 12 | 13 | import random 14 | import asyncio 15 | 16 | api_id = '' 17 | api_hash = '' 18 | bot_token = '' 19 | 20 | client = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token) 21 | step_manager = StepManager() 22 | 23 | db = DBManager() 24 | Base.metadata.create_all(bind=db.engine) 25 | 26 | sessions = {} 27 | user_states = {} 28 | step_functions = {} 29 | 30 | 31 | 32 | def register_step(step_name): 33 | def wrapper(func): 34 | step_functions[step_name] = func 35 | return func 36 | return wrapper 37 | 38 | def admin_dashboard(): 39 | return [ 40 | [Button.inline('افزودن پنل', b'set_panel')], 41 | [Button.inline('انتخاب پنل', b'create_config')] 42 | ] 43 | 44 | async def send_temp_message(event, text, delay=5): 45 | msg = await event.respond(text) 46 | await asyncio.sleep(delay) 47 | await client.delete_messages(event.chat_id, msg.id) 48 | 49 | async def edit_or_send(event, message, buttons=None, parse_mode='html'): 50 | user_id = event.sender_id 51 | 52 | if 'message_id' in sessions.get(user_id, {}): 53 | try: 54 | await client.edit_message(event.chat_id, sessions[user_id]['message_id'], message, buttons=buttons, parse_mode=parse_mode) 55 | return 56 | except: 57 | pass 58 | 59 | msg = await event.respond(message, buttons=buttons, parse_mode=parse_mode) 60 | 61 | if user_id not in sessions: 62 | sessions[user_id] = {} 63 | sessions[user_id]['message_id'] = msg.id 64 | 65 | 66 | @client.on(events.NewMessage(pattern='/start')) 67 | async def start_handler(event): 68 | session = db.get_session() 69 | panel_count = session.query(Panel).count() 70 | session.close() 71 | 72 | message = f"""به ربات مدیریت پنل خوش آمدید! 🤖 73 | 🔢 تعداد سرورهای ثبتشده: {panel_count} 74 | """ 75 | await edit_or_send(event, message, buttons=admin_dashboard(), parse_mode='html') 76 | 77 | @client.on(events.CallbackQuery(pattern=b'create_user_config')) 78 | async def start_create_config(event): 79 | if event.sender_id not in sessions: 80 | await edit_or_send(event, 'لطفا ابتدا یک پنل انتخاب کنید.') 81 | return 82 | 83 | buttons = [ 84 | [Button.inline('VMess', b'select_config_type_vmess')], 85 | [Button.inline('VLess', b'select_config_type_vless')], 86 | [Button.inline('Shadowsocks', b'select_config_type_shadowsocks')], 87 | [Button.inline('🔙 بازگشت', b'back_to_panel')] 88 | 89 | ] 90 | await edit_or_send(event, 'لطفا نوع کانفیگ را انتخاب کنید:', buttons=buttons) 91 | step_manager.set_step(event.sender_id, 'SELECT_CONFIG_TYPE') 92 | 93 | @client.on(events.CallbackQuery(pattern=b'list_user_configs')) 94 | async def list_configs_menu_wrapper(event): 95 | await list_configs_menu(event, page=0) 96 | 97 | async def list_configs_menu(event, page=0): 98 | user_id = event.sender_id 99 | if user_id not in sessions: 100 | await edit_or_send(event, 'لطفا ابتدا یک پنل انتخاب کنید.') 101 | return 102 | 103 | api = sessions[user_id]['api'] 104 | inbounds = api.list_inbounds() 105 | 106 | if not inbounds: 107 | await edit_or_send(event, '❌ هیچ کانفیگی برای نمایش وجود ندارد.') 108 | return 109 | 110 | items_per_page = 10 111 | start = page * items_per_page 112 | end = start + items_per_page 113 | page_items = inbounds[start:end] 114 | sessions[user_id]['list_config_page'] = page 115 | 116 | buttons = [] 117 | for inbound in page_items: 118 | label = inbound.remark if inbound.remark else f"None_{inbound.id}" 119 | buttons.append([Button.inline(f"📄 {label}", f'show_config_{inbound.id}'.encode())]) 120 | 121 | nav_buttons = [] 122 | if page > 0: 123 | nav_buttons.append(Button.inline('⬅️ قبلی', f'list_config_page_{page - 1}'.encode())) 124 | if end < len(inbounds): 125 | nav_buttons.append(Button.inline('➡️ بعدی', f'list_config_page_{page + 1}'.encode())) 126 | if nav_buttons: 127 | buttons.append(nav_buttons) 128 | 129 | buttons.append([Button.inline('🔙 بازگشت', b'back_to_panel')]) 130 | await edit_or_send(event, 'لیست کانفیگها:', buttons=buttons) 131 | 132 | @client.on(events.CallbackQuery(pattern=b'list_config_page_')) 133 | async def list_config_page_handler(event): 134 | try: 135 | page = int(event.data.decode().split('_')[-1]) 136 | await list_configs_menu(event, page) 137 | except ValueError: 138 | pass 139 | 140 | @client.on(events.CallbackQuery(pattern=b'delete_config_')) 141 | async def delete_selected_config(event): 142 | user_id = event.sender_id 143 | api = sessions[user_id]['api'] 144 | 145 | inbound_id = int(event.data.decode().split('_')[-1]) 146 | 147 | if api.delete_inbound(inbound_id): 148 | await send_temp_message(event, '✅ کانفیگ با موفقیت حذف شد.') 149 | else: 150 | await send_temp_message(event,'❌ کانفیگ با موفقیت حذف نشد.') 151 | 152 | 153 | await list_configs_menu(event, page=sessions[user_id].get('list_config_page', 0)) 154 | 155 | 156 | @client.on(events.CallbackQuery(pattern=b'edit_config_')) 157 | async def start_edit_config(event): 158 | user_id = event.sender_id 159 | inbound_id = int(event.data.decode().split('_')[-1]) 160 | api = sessions[user_id]['api'] 161 | inbound = next((i for i in api.list_inbounds() if i.id == inbound_id), None) 162 | back_btn = [ 163 | [Button.inline('🔙 بازگشت', b'back_to_panel')] 164 | ] 165 | if not inbound: 166 | await edit_or_send(event, '❌ کانفیگ پیدا نشد.',buttons=back_btn) 167 | return 168 | 169 | sessions[user_id]['edit_inbound'] = inbound 170 | step_manager.set_step(user_id, 'UPDATE_CONFIG_NAME') 171 | await edit_or_send(event, f"نام جدید برای کانفیگ وارد کنید (فعلی: {inbound.remark}):", buttons=back_btn) 172 | 173 | @client.on(events.CallbackQuery(pattern=b'show_config_')) 174 | async def show_config_detail(event): 175 | user_id = event.sender_id 176 | inbound_id = int(event.data.decode().split('_')[-1]) 177 | 178 | api = sessions[user_id]['api'] 179 | inbound = next((i for i in api.list_inbounds() if i.id == inbound_id), None) 180 | back_btn = [ 181 | [Button.inline('🔙 بازگشت', b'back_to_panel')] 182 | ] 183 | if not inbound: 184 | await edit_or_send(event, '❌ کانفیگ پیدا نشد.', buttons=back_btn) 185 | return 186 | 187 | sessions[user_id]['view_inbound'] = inbound 188 | 189 | expiry = datetime.fromtimestamp(inbound.expiryTime / 1000).strftime('%Y-%m-%d') 190 | 191 | detail = f""" 192 | 🔹 نام: {inbound.remark} 193 | 📦 حجم کل: {inbound.total / (1024**3):.2f} GB 194 | 📥 دانلود: {inbound.down / (1024**2):.2f} MB 195 | 📤 آپلود: {inbound.up / (1024**2):.2f} MB 196 | ⏳ تاریخ انقضا: {expiry} 197 | 🟢 فعال: {"بله" if inbound.enable else "خیر"} 198 | 199 | 🌐 پورت: {inbound.port} 200 | 🔀 پروتکل: {inbound.protocol} 201 | 📍 Listen: {inbound.listen} 202 | 🏷️ تگ: {inbound.tag} 203 | 204 | ⚙️ تنظیمات: 205 |
{pformat(inbound.settings, width=40)}
206 |
207 | 📡 Stream Settings:
208 | {pformat(inbound.streamSettings, width=40)}
209 |
210 | 🔍 Sniffing:
211 | {pformat(inbound.sniffing, width=40)}
212 | """.strip()
213 |
214 | if inbound.clientStats:
215 | client_info = "\n👥 کاربران:\n"
216 | for client in inbound.clientStats:
217 | client_info += (
218 | f"— {client.email} | "
219 | f"📤 {client.up / (1024**2):.2f} MB / "
220 | f"📥 {client.down / (1024**2):.2f} MB | "
221 | f"📦 {client.total / (1024**3):.2f} GB | "
222 | f"⏳ {datetime.fromtimestamp(client.expiryTime / 1000).strftime('%Y-%m-%d')}\n"
223 | )
224 | detail += f"\n{client_info}"
225 |
226 | buttons = [
227 | [Button.inline('🗑️ حذف', f'delete_config_{inbound.id}'.encode()),
228 | Button.inline('✏️ ویرایش', f'edit_config_{inbound.id}'.encode())],
229 | [Button.inline('🔙 بازگشت', b'list_user_configs')]
230 | ]
231 |
232 | await edit_or_send(event, detail, buttons=buttons)
233 |
234 | @client.on(events.CallbackQuery(pattern=b'back_to_main'))
235 | async def back_to_main(event):
236 | user_id = event.sender_id
237 | step_manager.reset_step(user_id)
238 | session = db.get_session()
239 | panel_count = session.query(Panel).count()
240 | session.close()
241 |
242 | message = f"""به ربات مدیریت پنل خوش آمدید! 🤖
243 | 🔢 تعداد سرورهای ثبتشده: {panel_count}
244 | """
245 | await edit_or_send(event, message, buttons=admin_dashboard(), parse_mode='html')
246 |
247 | @client.on(events.CallbackQuery(pattern=b'back_to_panel'))
248 | async def back_to_panel(event):
249 | user_id = event.sender_id
250 | step_manager.reset_step(user_id)
251 | session = db.get_session()
252 | back_btn = [
253 | [Button.inline('🔙 بازگشت', b'back_to_panel')]
254 | ]
255 | if sessions:
256 | panel = session.query(Panel).filter_by(id=sessions[user_id]['panel_id']).first()
257 | session.close()
258 |
259 | if not panel:
260 |
261 | await edit_or_send(event, 'پنل پیدا نشد.' , buttons=back_btn)
262 | return
263 |
264 | xui = XUIPanelAPI(panel.base_url)
265 | if not xui.login(panel.username, sessions[user_id]['panel_password']):
266 | await edit_or_send(event, 'ورود به پنل ناموفق بود.',buttons=back_btn)
267 | return
268 |
269 | sessions[user_id]['api'] = xui
270 |
271 | buttons = [
272 | [Button.inline('➕ ایجاد کانفیگ', b'create_user_config')],
273 | [Button.inline('📋 لیست کانفیگها', b'list_user_configs')],
274 | [Button.inline('🔙 بازگشت', b'back_to_main')]
275 | ]
276 |
277 | status = xui.get_server_status()
278 | users = xui.list_inbounds()
279 |
280 | if status:
281 | message = format_server_status(status, users)
282 | await client.edit_message(event.chat_id, sessions[user_id]['message_id'], message, buttons=buttons , parse_mode='html')
283 | else:
284 | await client.edit_message(event.chat_id, sessions[user_id]['message_id'], '❌ خطا در دریافت وضعیت سرور.',buttons=back_btn)
285 |
286 | @client.on(events.CallbackQuery)
287 | async def callback_handler(event):
288 | data = event.data.decode()
289 | user_id = event.sender_id
290 | back_btn = [
291 | [Button.inline('🔙 بازگشت', b'back_to_main')]
292 | ]
293 | if data == 'set_panel':
294 | step_manager.set_step(user_id, 'ENTER_PANEL_NAME')
295 | await edit_or_send(event, 'لطفاً نام پنل را وارد کنید:' ,buttons=back_btn)
296 | return
297 |
298 | elif data == 'create_config':
299 | session = db.get_session()
300 | panels = session.query(Panel).all()
301 | session.close()
302 | if not panels:
303 | await edit_or_send(event, 'فعلا هیچ پنلی ثبت نشده است. ابتدا پنل اضافه کنید.',buttons=back_btn)
304 | return
305 |
306 | buttons = [[Button.inline(p.name, f'select_panel_{p.id}'.encode())] for p in panels]
307 | await client.edit_message(event.chat_id, sessions[user_id]['message_id'], 'یک پنل انتخاب کنید:', buttons=buttons)
308 | return
309 |
310 | elif data.startswith('select_panel_'):
311 | panel_id = int(data.split('_')[-1])
312 | sessions[user_id]['panel_id'] = panel_id
313 | step_manager.set_step(user_id, 'ENTER_PANEL_PASSWORD')
314 | await client.delete_messages(event.chat_id, sessions[user_id]['message_id'])
315 | await send_temp_message(event, 'رمز عبور پنل را وارد کنید:', delay=15)
316 | return
317 |
318 |
319 | elif data.startswith('select_config_type_'):
320 | config_type = data.split('_')[-1]
321 | sessions[user_id]['config_type'] = config_type
322 | step_manager.set_step(user_id, 'CREATE_CONFIG_NAME')
323 | await edit_or_send(event, 'لطفا یک نام برای کانفیگ وارد کنید:',buttons=[[Button.inline('🔙 بازگشت', b'back_to_panel')]])
324 | return
325 |
326 | @client.on(events.NewMessage)
327 | async def message_handler(event):
328 | user_id = event.sender_id
329 | step = step_manager.get_step(user_id)
330 | buttons = [
331 | [Button.inline('🔙 بازگشت', b'back_to_main')]
332 | ]
333 | await client.delete_messages(event.chat_id, event.id)
334 |
335 | if not step:
336 | return
337 |
338 | if step and step in step_functions:
339 | await step_functions[step](event)
340 | else:
341 | await edit_or_send(event, '❌ مرحله نامعتبر است یا جلسه منقضی شده است.',buttons=buttons)
342 |
343 | @register_step('UPDATE_CONFIG_NAME')
344 | async def update_config_name(event):
345 | user_id = event.sender_id
346 | sessions[user_id]['edit_inbound'].remark = event.text
347 | step_manager.set_step(user_id, 'UPDATE_CONFIG_VOLUME')
348 | buttons = [[Button.inline('🔙 بازگشت', b'back_to_panel')]]
349 | await edit_or_send(event, 'حجم جدید (گیگابایت) را وارد کنید:', buttons=buttons)
350 |
351 | @register_step('UPDATE_CONFIG_VOLUME')
352 | async def update_config_volume(event):
353 | user_id = event.sender_id
354 |
355 | try:
356 | volume = int(event.text)
357 | except ValueError:
358 | buttons = [[Button.inline('🔙 بازگشت', b'back_to_panel')]]
359 | await edit_or_send(event, '❌ مقدار نامعتبر. عدد وارد کنید.', buttons=buttons)
360 | return
361 |
362 | sessions[user_id]['edit_inbound'].total = volume * 1024 * 1024 * 1024
363 | step_manager.set_step(user_id, 'UPDATE_CONFIG_EXPIRY')
364 | buttons = [[Button.inline('🔙 بازگشت', b'back_to_panel')]]
365 | await edit_or_send(event, 'تاریخ انقضا جدید را وارد کنید (مثلا: 2025-12-31):', buttons=buttons)
366 |
367 | @register_step('UPDATE_CONFIG_EXPIRY')
368 | async def update_config_expiry(event):
369 | user_id = event.sender_id
370 |
371 | try:
372 | expiry = int(datetime.strptime(event.text, '%Y-%m-%d').timestamp() * 1000)
373 | except ValueError:
374 | buttons = [[Button.inline('🔙 بازگشت', b'back_to_panel')]]
375 | await edit_or_send(event, '❌ فرمت تاریخ اشتباه است.', buttons=buttons)
376 | return
377 |
378 | inbound = sessions[user_id]['edit_inbound']
379 | inbound.expiryTime = expiry
380 |
381 | api = sessions[user_id]['api']
382 | success = api.update_inbound(inbound)
383 |
384 | if success:
385 | await edit_or_send(event, '✅ کانفیگ با موفقیت آپدیت شد.', buttons=admin_dashboard())
386 | else:
387 | await edit_or_send(event, '❌ خطا در آپدیت کانفیگ.')
388 |
389 | step_manager.reset_step(user_id)
390 | await list_configs_menu(event, page=sessions[user_id].get('list_config_page', 0))
391 |
392 |
393 | @register_step('ENTER_PANEL_NAME')
394 | async def handle_panel_name(event):
395 | user_id = event.sender_id
396 | user_states[user_id] = {'panel_name': event.text}
397 | step_manager.set_step(user_id, 'ENTER_PANEL_URL')
398 | buttons = [[Button.inline('🔙 بازگشت', b'back_to_main')]]
399 | await edit_or_send(event, 'آدرس پنل را وارد کنید (مثلا http://localhost:54321/xui):', buttons=buttons)
400 |
401 | @register_step('ENTER_PANEL_URL')
402 | async def handle_panel_url(event):
403 | user_id = event.sender_id
404 | user_states[user_id]['panel_url'] = event.text
405 | step_manager.set_step(user_id, 'ENTER_USERNAME')
406 | await edit_or_send(event, 'نام کاربری پنل را وارد کنید:', buttons=[[Button.inline('🔙 بازگشت', b'back_to_main')]])
407 |
408 | @register_step('ENTER_USERNAME')
409 | async def handle_username(event):
410 | user_id = event.sender_id
411 | user_states[user_id]['username'] = event.text
412 |
413 | session = db.get_session()
414 | panel = Panel(
415 | name=user_states[user_id]['panel_name'],
416 | base_url=user_states[user_id]['panel_url'],
417 | username=user_states[user_id]['username']
418 | )
419 | session.add(panel)
420 | session.commit()
421 | session.close()
422 |
423 | await edit_or_send(event, '✅ پنل با موفقیت ذخیره شد.', buttons=admin_dashboard())
424 | step_manager.reset_step(user_id)
425 |
426 |
427 | @register_step('ENTER_PANEL_PASSWORD')
428 | async def handle_panel_password(event):
429 | user_id = event.sender_id
430 | sessions[user_id]['panel_password'] = event.text
431 | await connect_to_panel(event, sessions[user_id]['panel_id'], event.text)
432 |
433 |
434 | @register_step('CREATE_CONFIG_NAME')
435 | async def handle_config_name(event):
436 | user_id = event.sender_id
437 | sessions[user_id]['config_name'] = event.text
438 | step_manager.set_step(user_id, 'CREATE_CONFIG_VOLUME')
439 | buttons = [[Button.inline('🔙 بازگشت', b'back_to_panel')]]
440 | await edit_or_send(event, 'لطفا حجم کانفیگ (بر حسب گیگ) را وارد کنید:', buttons=buttons)
441 |
442 | @register_step('CREATE_CONFIG_VOLUME')
443 | async def handle_config_volume(event):
444 | user_id = event.sender_id
445 | sessions[user_id]['config_volume'] = event.text
446 | step_manager.set_step(user_id, 'CREATE_CONFIG_EXPIRY')
447 | buttons = [[Button.inline('🔙 بازگشت', b'back_to_panel')]]
448 | await edit_or_send(event, 'لطفا تاریخ انقضا را وارد کنید (مثلا: 2025-12-31):', buttons=buttons)
449 |
450 | @register_step('CREATE_CONFIG_EXPIRY')
451 | async def handle_config_expiry(event):
452 | user_id = event.sender_id
453 | sessions[user_id]['config_expiry'] = event.text
454 |
455 | session = db.get_session()
456 | panel = session.query(Panel).filter_by(id=sessions[user_id]['panel_id']).first()
457 | session.close()
458 |
459 | back_btn = [[Button.inline('🔙 بازگشت', b'back_to_panel')]]
460 | api = XUIPanelAPI(panel.base_url)
461 | if not api.login(panel.username, sessions[user_id]['panel_password']):
462 | await edit_or_send(event, '❌ ورود به پنل ناموفق بود.',buttons=[[Button.inline('🔙 بازگشت', b'back_to_main')]])
463 | return
464 | try:
465 | expiry_timestamp = int(datetime.strptime(sessions[user_id]['config_expiry'], '%Y-%m-%d').timestamp() * 1000)
466 | config_type = sessions[user_id]['config_type']
467 | except:
468 | await edit_or_send(event, '❌ فرمت تاریخ اشتباه است.' , buttons=back_btn)
469 | return
470 |
471 | if config_type == 'vmess':
472 | user = VmessUser(
473 | up=0,
474 | down=0,
475 | total=int(sessions[user_id]['config_volume']) * 1024 * 1024 * 1024,
476 | remark=sessions[user_id]['config_name'],
477 | enable=True,
478 | expiryTime=expiry_timestamp,
479 | listen='',
480 | port=random.randint(10000, 20000),
481 | protocol='vmess'
482 | )
483 | elif config_type == 'vless':
484 | user = VlessUser(
485 | up=0,
486 | down=0,
487 | total=int(sessions[user_id]['config_volume']) * 1024 * 1024 * 1024,
488 | remark=sessions[user_id]['config_name'],
489 | enable=True,
490 | expiryTime=expiry_timestamp,
491 | listen='',
492 | port=random.randint(10000, 20000),
493 | protocol='vless'
494 | )
495 | elif config_type == 'shadowsocks':
496 | user = ShadowsocksUser(
497 | up=0,
498 | down=0,
499 | total=int(sessions[user_id]['config_volume']) * 1024 * 1024 * 1024,
500 | remark=sessions[user_id]['config_name'],
501 | enable=True,
502 | expiryTime=expiry_timestamp,
503 | listen='',
504 | port=random.randint(10000, 20000),
505 | protocol='shadowsocks',
506 | method='aes-256-gcm',
507 | password=generate_random_password()
508 | )
509 | else:
510 | await edit_or_send(event, '❌ نوع کانفیگ معتبر نیست.' , buttons=back_btn)
511 | step_manager.reset_step(user_id)
512 | return
513 |
514 | api.add_user(user)
515 | inbounds = api.list_inbounds()
516 |
517 | config_link = get_user_config_link(
518 | remark=sessions[user_id]['config_name'],
519 | inbounds=inbounds,
520 | server_address=panel.base_url.replace('http://', '').replace('https://', '')
521 | )
522 |
523 | if config_link:
524 | await edit_or_send(event, f'✅ کانفیگ با موفقیت ایجاد شد:{config_link} ', buttons=back_btn)
525 | else:
526 | await edit_or_send(event, '❌ خطا در ایجاد کانفیگ.', buttons=back_btn)
527 |
528 | step_manager.reset_step(user_id)
529 | api.logout()
530 |
531 | async def connect_to_panel(event, panel_id, password):
532 | session = db.get_session()
533 | panel = session.query(Panel).filter_by(id=panel_id).first()
534 | session.close()
535 | back_btn = [[Button.inline('🔙 بازگشت', b'back_to_main')]]
536 |
537 | if not panel:
538 | await edit_or_send(event, '❌ پنل پیدا نشد.',buttons=back_btn)
539 | return
540 |
541 | xui = XUIPanelAPI(panel.base_url)
542 | if not xui.login(panel.username, password):
543 | await edit_or_send(event, '❌ ورود به پنل ناموفق بود.',buttons=back_btn)
544 | return
545 |
546 | sessions[event.sender_id] = {
547 | 'api': xui,
548 | 'panel_id': panel_id,
549 | 'panel_password': password
550 | }
551 |
552 | buttons = [
553 | [Button.inline('➕ ایجاد کانفیگ', b'create_user_config')],
554 | [Button.inline('📋 لیست کانفیگها', b'list_user_configs')],
555 | [Button.inline('🔙 بازگشت', b'back_to_main')]
556 | ]
557 |
558 | status = xui.get_server_status()
559 | users = xui.list_inbounds()
560 |
561 | if status:
562 | message = format_server_status(status, users)
563 | await edit_or_send(event, message, buttons=buttons)
564 | else:
565 | await edit_or_send(event, '❌ خطا در دریافت وضعیت سرور.',buttons=back_btn)
566 |
567 |
568 | def generate_random_password():
569 | return ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=10))
570 |
571 | client.start()
572 | print('Bot is running...')
573 | client.run_until_disconnected()
574 |
--------------------------------------------------------------------------------