├── __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 | ![X-UI Bot Screenshot 1](screen/InShot_20250711_185303033.jpg) 5 | ![X-UI Bot Screenshot 2](screen/InShot_20250711_185241985.jpg) 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 | --------------------------------------------------------------------------------