From c9a50e38607a8dc4225b44a52fcc840f3c882199 Mon Sep 17 00:00:00 2001 From: Petro1990 Date: Wed, 26 Nov 2025 10:55:23 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BA=D0=BD?= =?UTF-8?q?=D0=BE=D0=BF=D0=BA=D1=83=20=D0=A4=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mihomo_editor.py | 192 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 185 insertions(+), 7 deletions(-) diff --git a/mihomo_editor.py b/mihomo_editor.py index 824e248..eb172a7 100644 --- a/mihomo_editor.py +++ b/mihomo_editor.py @@ -90,6 +90,84 @@ def parse_vless(link): return None, str(e) +def parse_wireguard(config_text): + try: + name = "WireGuard" + params = {} + current_section = None + for line in config_text.splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + if line.startswith('[') and line.endswith(']'): + current_section = line[1:-1].lower() + continue + if '=' in line: + key, value = map(str.strip, line.split('=', 1)) + if current_section: + if current_section not in params: + params[current_section] = {} + params[current_section][key] = value + + if 'interface' not in params or 'peer' not in params: + return None, "Invalid WireGuard config: missing [Interface] or [Peer] section." + + interface = params.get('interface', {}) + peer = params.get('peer', {}) + + # Extract name from comment if exists + name_match = re.search(r'#\s*(.+)', config_text.splitlines()[0]) + if name_match: + name = name_match.group(1).strip() + + name = f"WG-{name}" + + server, port = peer.get('Endpoint', ':').rsplit(':', 1) + + y = [ + f'- name: "{name}"', + ' type: wireguard', + f' server: {server}', + f' port: {port}', + f' private-key: "{interface.get("PrivateKey")}"', + f' public-key: "{peer.get("PublicKey")}"', + f' ip: "{interface.get("Address").split("/")[0]}"' + ] + + if interface.get('DNS'): + dns_servers = [d.strip() for d in interface.get('DNS').split(',')] + y.append(f' dns: {dns_servers}') + + if peer.get('PresharedKey'): + y.append(f' preshared-key: "{peer.get("PresharedKey")}"') + + if peer.get('PersistentKeepalive'): + y.append(f' keep-alive: {peer.get("PersistentKeepalive")}') + + amnezia_opts = { + 'Jc': interface.get('Jc'), + 'Jmin': interface.get('Jmin'), + 'Jmax': interface.get('Jmax'), + 'S1': interface.get('S1'), + 'S2': interface.get('S2'), + 'H1': interface.get('H1'), + 'H2': interface.get('H2'), + 'H3': interface.get('H3'), + 'H4': interface.get('H4') + } + + if any(v is not None for v in amnezia_opts.values()): + y.append(' amnezia-wg-opts:') + for key, value in amnezia_opts.items(): + if value is not None: + y.append(f' {key.lower()}: {value}') + + return {"yaml": "\n".join(y), "name": name}, None + + except Exception as e: + return None, str(e) + + def insert_proxy_logic(content, proxy_name, target_groups): lines = content.splitlines() new_lines = [] @@ -272,6 +350,21 @@ button:hover{filter:brightness(1.1)} .g-item input:checked + label {background: var(--btn-s);color: white;border-color: var(--btn-s);font-weight: bold;} .toast {position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: var(--btn-r); color: white; padding: 10px 20px; border-radius: 5px; z-index: 3000; display: none; box-shadow: 0 2px 10px rgba(0,0,0,0.5);} +.modal-tabs { display: flex; border-bottom: 1px solid var(--bd); margin-bottom: 15px; } +.modal-tabs button { + flex: 1; justify-content: center; background: none; border: none; border-bottom: 2px solid transparent; + border-radius: 0; padding: 10px; font-size: 14px; color: var(--txt-sec); height: auto; +} +.modal-tabs button.active { color: var(--txt); border-bottom-color: var(--btn-s); font-weight: bold; } +.tab-content { display: none; } +.tab-content.active { display: block; } +.file-drop-zone { + border: 2px dashed var(--bd); border-radius: 4px; padding: 20px; text-align: center; + color: var(--txt-sec); cursor: pointer; transition: 0.2s; margin-bottom: 10px; +} +.file-drop-zone:hover { background: var(--bg-ter); border-color: var(--btn-s); } +.file-drop-zone.dragover { background: var(--bg-ter); border-color: var(--btn-s); } + .log-time { color: #888; margin-right: 8px; } .log-info { color: #2196f3; font-weight: bold; } .log-warn { color: #ff9800; font-weight: bold; } @@ -324,11 +417,8 @@ button:hover{filter:brightness(1.1)}
-

Быстрый VLESS

- - -
-
+

Управление прокси

+
@@ -380,6 +470,31 @@ button:hover{filter:brightness(1.1)} +
+
+

Добавить прокси

+ +
+ + +
+ + + +
+ +
+ + + + + +
+
+

Просмотр бэкапа


@@ -567,10 +682,59 @@ function restoreBackup(fname){
     });
 }
 
+function openAddProxyModal() {
+    document.getElementById('addProxyModal').style.display = 'flex';
+}
+
+function switchTab(evt, tabName) {
+    var i, tabcontent, tablinks;
+    tabcontent = document.getElementsByClassName("tab-content");
+    for (i = 0; i < tabcontent.length; i++) {
+        tabcontent[i].style.display = "none";
+        tabcontent[i].classList.remove("active");
+    }
+    tablinks = document.getElementsByClassName("modal-tabs")[0].getElementsByTagName("button");
+    for (i = 0; i < tablinks.length; i++) {
+        tablinks[i].className = tablinks[i].className.replace(" active", "");
+    }
+    document.getElementById(tabName).style.display = "block";
+    document.getElementById(tabName).classList.add("active");
+    evt.currentTarget.className += " active";
+}
+
+function loadWgFile(input) {
+    var f=input.files[0];
+    if (!f) return;
+    var r=new FileReader();
+    r.onload=function(e){ document.getElementById('wgConfig').value = e.target.result; };
+    r.readAsText(f);
+    input.value = '';
+}
+
+function addWireguard() {
+    var conf = document.getElementById('wgConfig').value;
+    if (!conf) return alert("Конфигурация WireGuard не может быть пустой.");
+    var p = new URLSearchParams();
+    p.append('act', 'add_wireguard');
+    p.append('config_text', conf);
+    fetch('/', { method: 'POST', body: p })
+        .then(r => r.json())
+        .then(d => {
+            if (d.error) {
+                alert(d.error);
+            } else {
+                pData = d;
+                closeM('addProxyModal');
+                document.getElementById('wgConfig').value = '';
+                showG();
+            }
+        });
+}
+
 function parseVless(){
-    var l=document.getElementById('vl').value;if(!l)return;
+    var l=document.getElementById('vlessLink').value;if(!l)return;
     var p=new URLSearchParams();p.append('act','parse');p.append('link',l);
-    fetch('/',{method:'POST',body:p}).then(r=>r.json()).then(d=>{if(d.error)alert(d.error);else{pData=d;showG();document.getElementById('vl').value=''}})
+    fetch('/',{method:'POST',body:p}).then(r=>r.json()).then(d=>{if(d.error)alert(d.error);else{pData=d; closeM('addProxyModal'); document.getElementById('vlessLink').value=''; showG();}})
 }
 function showG(){
     var txt=ed.getValue(); var ls=txt.split(/\\r?\\n/); var grps=[], inG=false;
@@ -891,6 +1055,20 @@ class H(http.server.SimpleHTTPRequestHandler):
             s.wfile.write(json.dumps(d if d else {'error': e}).encode('utf-8'));
             return
 
+        if a == 'add_wireguard':
+            config_text = p.get('config_text', '')
+            if not config_text:
+                s.wfile.write(json.dumps({'error': 'Empty config'}).encode('utf-8'))
+                return
+            
+            proxy_data, err = parse_wireguard(config_text)
+            if err:
+                s.wfile.write(json.dumps({'error': err}).encode('utf-8'))
+                return
+            
+            s.wfile.write(json.dumps(proxy_data).encode('utf-8'))
+            return
+
         if a == 'apply_insert':
             content = p.get('content', '');
             p_name = p.get('proxy_name', '');