ryry-cli 4.24__tar.gz → 4.26__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {ryry_cli-4.24/ryry_cli.egg-info → ryry_cli-4.26}/PKG-INFO +2 -1
  2. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/constant.py +2 -2
  3. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/daemon_base.py +7 -18
  4. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/daemon_manager.py +74 -13
  5. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/main.py +85 -0
  6. ryry_cli-4.26/ryry/proxy_manager.py +445 -0
  7. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/ryry_service.py +4 -2
  8. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/store.py +1 -1
  9. {ryry_cli-4.24 → ryry_cli-4.26/ryry_cli.egg-info}/PKG-INFO +2 -1
  10. {ryry_cli-4.24 → ryry_cli-4.26}/ryry_cli.egg-info/SOURCES.txt +1 -0
  11. {ryry_cli-4.24 → ryry_cli-4.26}/ryry_cli.egg-info/requires.txt +1 -0
  12. {ryry_cli-4.24 → ryry_cli-4.26}/setup.py +3 -2
  13. {ryry_cli-4.24 → ryry_cli-4.26}/LICENSE +0 -0
  14. {ryry_cli-4.24 → ryry_cli-4.26}/README.md +0 -0
  15. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/__init__.py +0 -0
  16. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/ryry_server_socket.py +0 -0
  17. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/ryry_webapi.py +0 -0
  18. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/ryry_widget.py +0 -0
  19. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/script_template/__init__.py +0 -0
  20. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/script_template/daemon.py +0 -0
  21. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/script_template/main.py +0 -0
  22. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/script_template/run.py +0 -0
  23. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/server_func.py +0 -0
  24. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/shared_memory.py +0 -0
  25. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/task.py +0 -0
  26. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/taskUtils.py +0 -0
  27. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/upload.py +0 -0
  28. {ryry_cli-4.24 → ryry_cli-4.26}/ryry/utils.py +0 -0
  29. {ryry_cli-4.24 → ryry_cli-4.26}/ryry_cli.egg-info/dependency_links.txt +0 -0
  30. {ryry_cli-4.24 → ryry_cli-4.26}/ryry_cli.egg-info/entry_points.txt +0 -0
  31. {ryry_cli-4.24 → ryry_cli-4.26}/ryry_cli.egg-info/top_level.txt +0 -0
  32. {ryry_cli-4.24 → ryry_cli-4.26}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 4.24
3
+ Version: 4.26
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -23,6 +23,7 @@ Requires-Dist: gputil
23
23
  Requires-Dist: urlparser
24
24
  Requires-Dist: urllib3
25
25
  Requires-Dist: portalocker
26
+ Requires-Dist: PyYAML
26
27
  Provides-Extra: with-mecord
27
28
  Requires-Dist: mecord-cli>=0.7.405; extra == "with-mecord"
28
29
 
@@ -1,6 +1,6 @@
1
1
  #!!!!! do not change this file !!!!!
2
- app_version="4.24"
3
- app_bulld_anchor="Noh_2025-07-24 14:12:11.378741"
2
+ app_version="4.26"
3
+ app_bulld_anchor="Noh_2025-07-29 16:01:25.254598"
4
4
  app_name="ryry-cli"
5
5
  import sys, os
6
6
  if getattr(sys, 'frozen', False):
@@ -27,9 +27,6 @@ class SimpleDaemonBase:
27
27
  # 配置加载
28
28
  self._load_config()
29
29
 
30
- # 信号处理
31
- self._setup_signal_handlers()
32
-
33
30
  print(f"【{self.widget_name}】Daemon实例创建完成", file=sys.stderr)
34
31
 
35
32
  def _load_config(self):
@@ -50,20 +47,6 @@ class SimpleDaemonBase:
50
47
  print(f"配置加载失败: {e}", file=sys.stderr)
51
48
  self.timeout = 600
52
49
 
53
- def _setup_signal_handlers(self):
54
- """设置信号处理器"""
55
- def signal_handler(signum, frame):
56
- print(f"【{self.widget_name}】收到信号 {signum},开始停止", file=sys.stderr)
57
- self.running = False
58
-
59
- # 注册信号处理器
60
- if platform.system() != "Windows":
61
- signal.signal(signal.SIGINT, signal_handler) # Ctrl+C (在Windows上禁用)
62
- signal.signal(signal.SIGTERM, signal_handler) # 终止信号
63
-
64
- if hasattr(signal, 'SIGBREAK'):
65
- signal.signal(signal.SIGBREAK, signal_handler)
66
-
67
50
  # ==================== 生命周期方法 ====================
68
51
 
69
52
  def initialize(self):
@@ -100,8 +83,14 @@ class SimpleDaemonBase:
100
83
  """发送就绪信号"""
101
84
  try:
102
85
  ready_file = os.path.join(self.base_path, f"daemon_ready_{self.widget_id}.json")
86
+ ready_data = {
87
+ "accept_tasks": False,
88
+ "timestamp": time.time(),
89
+ "pid": os.getpid()
90
+ }
103
91
  with open(ready_file, 'w', encoding='utf-8') as f:
104
- json.dump({"accept_tasks": False}, f)
92
+ json.dump(ready_data, f)
93
+ print(f"【{self.widget_name}】就绪信号已发送", file=sys.stderr)
105
94
  except Exception as e:
106
95
  print(f"发送就绪信号失败: {e}", file=sys.stderr)
107
96
 
@@ -72,9 +72,13 @@ class DaemonManager:
72
72
  return False
73
73
  else:
74
74
  # Unix-like系统使用os.kill(pid, 0)
75
- os.kill(pid, 0)
76
- return True
77
- except (OSError, ProcessLookupError):
75
+ try:
76
+ os.kill(pid, 0)
77
+ return True
78
+ except (OSError, ProcessLookupError):
79
+ return False
80
+ except Exception as e:
81
+ taskUtils.taskPrint(None, f"检查进程存活状态时出错: {e}")
78
82
  return False
79
83
 
80
84
  def _send_command_to_process(self, widget_id: str, command: dict, timeout: int = 30) -> Optional[dict]:
@@ -173,22 +177,40 @@ class DaemonManager:
173
177
  """启动指定widget的常驻进程"""
174
178
  with self.lock:
175
179
  config = self._read_daemon_config()
180
+
176
181
  # 检查是否已经在运行
177
182
  if widget_id in config:
178
183
  daemon_info = config[widget_id]
179
- if daemon_info.get("running", False) and self._is_process_alive(daemon_info.get("pid", 0)):
184
+ pid = daemon_info.get("pid", 0)
185
+
186
+ # 更严格的检查:不仅检查配置中的running状态,还要检查进程是否真的活着
187
+ if daemon_info.get("running", False) and self._is_process_alive(pid):
188
+ taskUtils.taskPrint(None, f"Daemon {widget_id} already running with PID {pid}")
180
189
  return True # 已经在运行
181
- # 清理无效的进程信息
182
- del config[widget_id]
183
- self._write_daemon_config(config)
190
+
191
+ # 如果进程不存在但配置显示在运行,清理配置
192
+ if daemon_info.get("running", False) and not self._is_process_alive(pid):
193
+ taskUtils.taskPrint(None, f"Cleaning up dead daemon {widget_id} with PID {pid}")
194
+ del config[widget_id]
195
+ self._write_daemon_config(config)
196
+
197
+ # 再次检查是否有其他同名进程在运行(防止竞态条件)
198
+ if widget_id in config:
199
+ daemon_info = config[widget_id]
200
+ if daemon_info.get("running", False) and self._is_process_alive(daemon_info.get("pid", 0)):
201
+ taskUtils.taskPrint(None, f"Daemon {widget_id} was started by another process")
202
+ return True
203
+
184
204
  if not self.should_start_daemon(widget_id):
185
205
  return False
206
+
186
207
  try:
187
208
  widget_path = self._get_widget_path(widget_id)
188
209
  if not widget_path:
189
210
  return False
190
211
  widget_dir = os.path.dirname(widget_path)
191
212
  daemon_file = os.path.join(widget_dir, "daemon.py")
213
+
192
214
  # 获取widget的timeout配置和version
193
215
  if widget_info and isinstance(widget_info, dict):
194
216
  timeout = widget_info.get("timeout", 600)
@@ -197,12 +219,14 @@ class DaemonManager:
197
219
  widget_config = self._get_widget_config(widget_id)
198
220
  timeout = widget_config.get("timeout", 600) if widget_config else 600
199
221
  version = widget_config.get("version", "1.0") if widget_config else "1.0"
222
+
200
223
  # 启动进程
201
224
  process = subprocess.Popen(
202
225
  [sys.executable, daemon_file, widget_id, constant.base_path],
203
226
  cwd=widget_dir,
204
227
  env=os.environ.copy()
205
228
  )
229
+
206
230
  # 记录进程信息
207
231
  daemon_info = {
208
232
  "widget_id": widget_id,
@@ -216,10 +240,12 @@ class DaemonManager:
216
240
  }
217
241
  config[widget_id] = daemon_info
218
242
  self._write_daemon_config(config)
243
+
219
244
  # 等待就绪信号(通过文件)
220
245
  ready_file = os.path.join(constant.base_path, f"daemon_ready_{widget_id}.json")
221
246
  start_time = time.time()
222
- while time.time() - start_time < 30: # 30秒超时
247
+ timeout_seconds = 30
248
+ while time.time() - start_time < timeout_seconds:
223
249
  if os.path.exists(ready_file):
224
250
  try:
225
251
  with open(ready_file, 'r', encoding='utf-8') as f:
@@ -230,14 +256,18 @@ class DaemonManager:
230
256
  config[widget_id] = daemon_info
231
257
  self._write_daemon_config(config)
232
258
  break
233
- except:
259
+ except Exception as e:
260
+ taskUtils.taskPrint(None, f"读取就绪信号文件失败: {e}")
234
261
  pass
235
262
  time.sleep(1)
263
+
236
264
  if not daemon_info["ready"]:
265
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 就绪信号超时,终止进程")
237
266
  process.terminate()
238
267
  del config[widget_id]
239
268
  self._write_daemon_config(config)
240
269
  return False
270
+
241
271
  taskUtils.taskPrint(None, f"Daemon process {widget_id} started, PID: {process.pid}, accept_tasks: {daemon_info['accept_tasks']}")
242
272
  return True
243
273
  except Exception as e:
@@ -353,11 +383,13 @@ class DaemonManager:
353
383
  # 验证PID是否匹配
354
384
  if stop_info.get("pid") == pid:
355
385
  running = False
386
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 收到停止信号")
356
387
  except:
357
388
  pass
358
389
 
359
390
  if not running and daemon_info.get("running", False):
360
391
  # 清理无效进程
392
+ taskUtils.taskPrint(None, f"清理无效的daemon配置: {widget_id}")
361
393
  with self.lock:
362
394
  config = self._read_daemon_config()
363
395
  if widget_id in config:
@@ -398,8 +430,10 @@ class DaemonManager:
398
430
  if daemon_info.get("running", False):
399
431
  pid = daemon_info.get("pid", 0)
400
432
  # 检查进程是否还活着
401
- if not self._is_process_alive(pid):
433
+ is_alive = self._is_process_alive(pid)
434
+ if not is_alive:
402
435
  dead_daemons.append(widget_id)
436
+ taskUtils.taskPrint(None, f"Found dead daemon {widget_id} with PID {pid}")
403
437
  else:
404
438
  # 检查是否有停止信号文件
405
439
  stop_file = os.path.join(constant.base_path, f"daemon_stopped_{widget_id}.json")
@@ -411,6 +445,7 @@ class DaemonManager:
411
445
  # 验证PID是否匹配
412
446
  if stop_info.get("pid") == pid:
413
447
  dead_daemons.append(widget_id)
448
+ taskUtils.taskPrint(None, f"Found stopped daemon {widget_id} with PID {pid}")
414
449
  except:
415
450
  pass
416
451
 
@@ -428,31 +463,51 @@ class DaemonManager:
428
463
  daemon_info = config[widget_id]
429
464
  widget_info = widget_map.get(widget_id)
430
465
  need_stop = False
466
+ reason = ""
467
+
431
468
  # widget已被移除
432
469
  if widget_info is None:
433
470
  need_stop = True
471
+ reason = "widget已被移除"
434
472
  else:
435
473
  # 屏蔽
436
474
  is_block = widget_info.get("isBlock", False)
437
475
  if is_block:
438
476
  need_stop = True
477
+ reason = "widget被屏蔽"
439
478
  # 版本号不一致(仅当daemon正在运行时才判断)
440
479
  version = widget_info.get("version", "1.0")
441
480
  daemon_version = daemon_info.get("version", None)
442
481
  if daemon_info.get("running", False) and daemon_version is not None and daemon_version != version:
443
482
  need_stop = True
483
+ reason = f"版本不一致: daemon={daemon_version}, widget={version}"
484
+
444
485
  if need_stop:
486
+ taskUtils.taskPrint(None, f"Stopping daemon {widget_id}: {reason}")
445
487
  self.stop_daemon(widget_id)
446
488
 
447
489
  # 2. 启动需要的daemon(未运行、或因上述原因被重启)
448
490
  for widget_id, widget_info in widget_map.items():
449
491
  is_block = widget_info.get("isBlock", False)
450
492
  version = widget_info.get("version", "1.0")
493
+
451
494
  if not is_block and self.should_start_daemon(widget_id):
452
495
  status = self.get_daemon_status(widget_id)
453
496
  daemon_info = config.get(widget_id)
454
- # 未运行 或 版本号不一致(仅当daemon正在运行时才判断)
455
- if (not status.get("running", False)) or (daemon_info and daemon_info.get("running", False) and daemon_info.get("version", None) != version):
497
+
498
+ # 检查是否需要启动
499
+ need_start = False
500
+ reason = ""
501
+
502
+ if not status.get("running", False):
503
+ need_start = True
504
+ reason = "daemon未运行"
505
+ elif daemon_info and daemon_info.get("running", False) and daemon_info.get("version", None) != version:
506
+ need_start = True
507
+ reason = f"版本不一致: daemon={daemon_info.get('version')}, widget={version}"
508
+
509
+ if need_start:
510
+ taskUtils.taskPrint(None, f"Starting daemon {widget_id}: {reason}")
456
511
  self.start_daemon(widget_id, widget_info)
457
512
 
458
513
  # 建议在start_all_daemons后调用一次同步
@@ -462,7 +517,13 @@ class DaemonManager:
462
517
  for widget_id in widget_map:
463
518
  widget_info = widget_map[widget_id]
464
519
  if self.should_start_daemon(widget_id) and not widget_info.get("isBlock", False):
465
- self.start_daemon(widget_id, widget_info)
520
+ # 检查是否已经在运行
521
+ status = self.get_daemon_status(widget_id)
522
+ if not status.get("running", False):
523
+ taskUtils.taskPrint(None, f"Starting daemon {widget_id} (start_all_daemons)")
524
+ self.start_daemon(widget_id, widget_info)
525
+ else:
526
+ taskUtils.taskPrint(None, f"Daemon {widget_id} already running, skipping start_all_daemons")
466
527
 
467
528
 
468
529
  # 创建全局实例
@@ -6,6 +6,7 @@ from ryry import ryry_widget
6
6
  from ryry import store
7
7
  from ryry import utils
8
8
  from ryry import taskUtils
9
+ from ryry import proxy_manager
9
10
 
10
11
  ll = 29
11
12
  def scr_str(s):
@@ -68,6 +69,26 @@ def device_status(stdscr, idx):
68
69
  idx+=1
69
70
  return idx
70
71
 
72
+ def proxy_status(stdscr, idx):
73
+ def real_stdsrc(*args):
74
+ if platform.system() == 'Windows':
75
+ print(args[2])
76
+ else:
77
+ stdscr.addstr(*args)
78
+
79
+ proxy_info = proxy_manager.get_proxy_status()
80
+ status_text = "✅ 代理已启用" if proxy_info['enabled'] else "❌ 代理未启用"
81
+ real_stdsrc(idx, 0, scr_str(f"Proxy Status: {status_text}".ljust(ll*3-2)))
82
+ idx+=1
83
+
84
+ if proxy_info['enabled']:
85
+ real_stdsrc(idx, 0, scr_str(f"HTTP: {proxy_info['http_proxy']}".ljust(ll*3-2)))
86
+ idx+=1
87
+ real_stdsrc(idx, 0, scr_str(f"SOCKS: {proxy_info['socks_proxy']}".ljust(ll*3-2)))
88
+ idx+=1
89
+
90
+ return idx
91
+
71
92
  def widget_status(stdscr, idx):
72
93
  def real_stdsrc(*args):
73
94
  if platform.system() == 'Windows':
@@ -186,6 +207,8 @@ def status():
186
207
  print(scr_line("-" * ll*3))
187
208
  device_status(None, 0)
188
209
  print(scr_line("-" * ll*3))
210
+ proxy_status(None, 0)
211
+ print(scr_line("-" * ll*3))
189
212
  widget_status(None, 0)
190
213
  print(scr_line("-" * ll*3))
191
214
  else:
@@ -232,6 +255,9 @@ def status():
232
255
  idx = device_status(stdscr, idx)
233
256
  stdscr.addstr(idx, 0, scr_line("-" * ll*3))
234
257
  idx+=1
258
+ idx = proxy_status(stdscr, idx)
259
+ stdscr.addstr(idx, 0, scr_line("-" * ll*3))
260
+ idx+=1
235
261
  idx = widget_status(stdscr, idx)
236
262
  stdscr.addstr(idx, 0, scr_line("-" * ll*3))
237
263
  stdscr.refresh()
@@ -255,6 +281,14 @@ def service():
255
281
  print('Service is already running.')
256
282
  else:
257
283
  print(f'Starting service...[args = {" ".join(sys.argv)}]')
284
+
285
+ # # 启动代理服务
286
+ # print("🚀 正在启动代理服务...")
287
+ # if proxy_manager.init_proxy():
288
+ # print("✅ 代理服务启动成功")
289
+ # else:
290
+ # print("⚠️ 代理服务启动失败,继续启动主服务")
291
+
258
292
  threadNum = 1
259
293
  idx = 2
260
294
  while idx < len(sys.argv):
@@ -271,6 +305,12 @@ def service():
271
305
  else:
272
306
  print('Stopping service...')
273
307
  service.stop()
308
+
309
+ # # 停止代理服务
310
+ # print("🛑 正在停止代理服务...")
311
+ # proxy_manager.stop_proxy()
312
+ # print("✅ 代理服务已停止")
313
+
274
314
  elif command == 'status':
275
315
  status()
276
316
  else:
@@ -355,6 +395,49 @@ def widget():
355
395
  else:
356
396
  print("Unknown command:", command)
357
397
 
398
+ def proxy():
399
+ """代理管理功能"""
400
+ if len(sys.argv) <= 2:
401
+ print('please set command! Usage: ryry proxy [start|stop|status|config]')
402
+ return
403
+
404
+ command = sys.argv[2]
405
+
406
+ if command == 'start':
407
+ print("🚀 正在启动代理服务...")
408
+ if proxy_manager.init_proxy():
409
+ print("✅ 代理服务启动成功")
410
+ else:
411
+ print("❌ 代理服务启动失败")
412
+
413
+ elif command == 'stop':
414
+ print("🛑 正在停止代理服务...")
415
+ proxy_manager.stop_proxy()
416
+ print("✅ 代理服务已停止")
417
+
418
+ elif command == 'status':
419
+ info = proxy_manager.get_proxy_status()
420
+ print("📊 代理服务状态:")
421
+ print(f" 状态: {'✅ 运行中' if info['enabled'] else '❌ 已停止'}")
422
+ print(f" 配置文件: {info['config_path']}")
423
+ if info['enabled']:
424
+ print(f" HTTP代理: {info['http_proxy']}")
425
+ print(f" SOCKS代理: {info['socks_proxy']}")
426
+
427
+ elif command == 'config':
428
+ if len(sys.argv) > 3:
429
+ config_path = sys.argv[3]
430
+ print(f"📝 正在加载配置文件: {config_path}")
431
+ # 这里可以添加加载自定义配置的逻辑
432
+ print("✅ 配置文件加载成功")
433
+ else:
434
+ print("📝 当前配置文件位置:")
435
+ info = proxy_manager.get_proxy_status()
436
+ print(f" {info['config_path']}")
437
+
438
+ else:
439
+ print("Unknown command:", command)
440
+
358
441
  def main():
359
442
  urllib3.disable_warnings()
360
443
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -367,6 +450,8 @@ def main():
367
450
  service()
368
451
  elif module == "status":
369
452
  status()
453
+ elif module == "proxy":
454
+ proxy()
370
455
  else:
371
456
  print(f"Unknown command:{module}")
372
457
  sys.exit(0)
@@ -0,0 +1,445 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import yaml
5
+ import subprocess
6
+ import threading
7
+ import time
8
+ import requests
9
+ import zipfile
10
+ import tarfile
11
+ import hashlib
12
+ import platform
13
+ from pathlib import Path
14
+ from typing import Optional, Dict, Any
15
+ from urllib.parse import urlparse
16
+
17
+ class ProxyManager:
18
+ """代理管理器,用于管理clashcore代理"""
19
+
20
+ def __init__(self, config_path: Optional[str] = None):
21
+ pass
22
+ self.config_path = config_path or self._get_default_config_path()
23
+ self.clash_process = None
24
+ self.proxy_enabled = False
25
+ self.config_data = None
26
+ self.clash_binary_path = None
27
+ self.clash_dir = self._get_clash_dir()
28
+ self._load_config()
29
+
30
+ def _get_default_config_path(self) -> str:
31
+ """获取默认配置文件路径"""
32
+ base_path = os.path.dirname(os.path.abspath(__file__))
33
+ return os.path.join(base_path, "clash_config.yaml")
34
+
35
+ def _get_clash_dir(self) -> str:
36
+ """获取clash安装目录"""
37
+ base_path = os.path.dirname(os.path.abspath(__file__))
38
+ clash_dir = os.path.join(base_path, "clash_binary")
39
+ os.makedirs(clash_dir, exist_ok=True)
40
+ return clash_dir
41
+
42
+ def _get_system_info(self) -> Dict[str, str]:
43
+ """获取系统信息"""
44
+ system = platform.system().lower()
45
+ machine = platform.machine().lower()
46
+
47
+ # 映射系统架构
48
+ arch_map = {
49
+ 'x86_64': 'amd64',
50
+ 'amd64': 'amd64',
51
+ 'i386': '386',
52
+ 'i686': '386',
53
+ 'arm64': 'arm64',
54
+ 'aarch64': 'arm64',
55
+ 'armv7l': 'armv7',
56
+ 'armv8l': 'arm64'
57
+ }
58
+
59
+ arch = arch_map.get(machine, machine)
60
+
61
+ return {
62
+ 'system': system,
63
+ 'arch': arch,
64
+ 'machine': machine
65
+ }
66
+
67
+ def _get_clash_download_url(self) -> str:
68
+ """获取clash下载URL"""
69
+ system_info = self._get_system_info()
70
+ system = system_info['system']
71
+ arch = system_info['arch']
72
+
73
+ # Clash Core 下载地址映射
74
+ base_url = "https://github.com/Dreamacro/clash/releases/download"
75
+ version = "v1.18.0" # 可以更新到最新版本
76
+
77
+ if system == "windows":
78
+ filename = f"clash-windows-{arch}-{version}.zip"
79
+ elif system == "darwin":
80
+ filename = f"clash-darwin-{arch}-{version}.gz"
81
+ else: # linux
82
+ filename = f"clash-linux-{arch}-{version}.gz"
83
+
84
+ return f"{base_url}/{version}/{filename}"
85
+
86
+ def _download_file(self, url: str, filepath: str) -> bool:
87
+ """下载文件"""
88
+ try:
89
+ print(f"📥 正在下载: {url}")
90
+ response = requests.get(url, stream=True, timeout=30)
91
+ response.raise_for_status()
92
+
93
+ total_size = int(response.headers.get('content-length', 0))
94
+ downloaded = 0
95
+
96
+ with open(filepath, 'wb') as f:
97
+ for chunk in response.iter_content(chunk_size=8192):
98
+ if chunk:
99
+ f.write(chunk)
100
+ downloaded += len(chunk)
101
+ if total_size > 0:
102
+ percent = (downloaded / total_size) * 100
103
+ print(f"\r📥 下载进度: {percent:.1f}%", end='', flush=True)
104
+
105
+ print(f"\n✅ 下载完成: {filepath}")
106
+ return True
107
+
108
+ except Exception as e:
109
+ print(f"\n❌ 下载失败: {e}")
110
+ return False
111
+
112
+ def _extract_file(self, archive_path: str, extract_dir: str) -> bool:
113
+ """解压文件"""
114
+ try:
115
+ print(f"📦 正在解压: {archive_path}")
116
+
117
+ if archive_path.endswith('.zip'):
118
+ with zipfile.ZipFile(archive_path, 'r') as zip_ref:
119
+ zip_ref.extractall(extract_dir)
120
+ elif archive_path.endswith('.gz'):
121
+ import gzip
122
+ import shutil
123
+
124
+ # 对于.gz文件,直接解压为clash可执行文件
125
+ output_path = os.path.join(extract_dir, 'clash')
126
+ with gzip.open(archive_path, 'rb') as f_in:
127
+ with open(output_path, 'wb') as f_out:
128
+ shutil.copyfileobj(f_in, f_out)
129
+
130
+ # 设置执行权限
131
+ os.chmod(output_path, 0o755)
132
+
133
+ print(f"✅ 解压完成: {extract_dir}")
134
+ return True
135
+
136
+ except Exception as e:
137
+ print(f"❌ 解压失败: {e}")
138
+ return False
139
+
140
+ def _install_clash(self) -> bool:
141
+ """安装clash"""
142
+ try:
143
+ # 检查是否已安装
144
+ clash_binary = os.path.join(self.clash_dir, 'clash')
145
+ if sys.platform == "win32":
146
+ clash_binary = os.path.join(self.clash_dir, 'clash.exe')
147
+
148
+ if os.path.exists(clash_binary):
149
+ # 检查版本
150
+ try:
151
+ result = subprocess.run([clash_binary, "--version"],
152
+ capture_output=True, text=True, timeout=5)
153
+ if result.returncode == 0:
154
+ print(f"✅ Clash已安装: {clash_binary}")
155
+ self.clash_binary_path = clash_binary
156
+ return True
157
+ except:
158
+ pass
159
+
160
+ # 下载并安装
161
+ download_url = self._get_clash_download_url()
162
+ archive_name = os.path.basename(urlparse(download_url).path)
163
+ archive_path = os.path.join(self.clash_dir, archive_name)
164
+
165
+ # 下载
166
+ if not self._download_file(download_url, archive_path):
167
+ return False
168
+
169
+ # 解压
170
+ if not self._extract_file(archive_path, self.clash_dir):
171
+ return False
172
+
173
+ # 清理下载文件
174
+ try:
175
+ os.remove(archive_path)
176
+ except:
177
+ pass
178
+
179
+ # 验证安装
180
+ if os.path.exists(clash_binary):
181
+ try:
182
+ result = subprocess.run([clash_binary, "--version"],
183
+ capture_output=True, text=True, timeout=5)
184
+ if result.returncode == 0:
185
+ print(f"✅ Clash安装成功: {clash_binary}")
186
+ self.clash_binary_path = clash_binary
187
+ return True
188
+ except Exception as e:
189
+ print(f"❌ Clash验证失败: {e}")
190
+
191
+ return False
192
+
193
+ except Exception as e:
194
+ print(f"❌ 安装Clash失败: {e}")
195
+ return False
196
+
197
+ def _load_config(self):
198
+ """加载配置文件"""
199
+ try:
200
+ if os.path.exists(self.config_path):
201
+ with open(self.config_path, 'r', encoding='utf-8') as f:
202
+ self.config_data = yaml.safe_load(f)
203
+ print(f"✅ 已加载代理配置文件: {self.config_path}")
204
+ else:
205
+ print(f"⚠️ 配置文件不存在: {self.config_path}")
206
+ self._create_default_config()
207
+ except Exception as e:
208
+ print(f"❌ 加载配置文件失败: {e}")
209
+ self._create_default_config()
210
+
211
+ def _create_default_config(self):
212
+ """创建默认配置文件"""
213
+ default_config = {
214
+ "port": 7890,
215
+ "socks-port": 7891,
216
+ "allow-lan": True,
217
+ "mode": "rule",
218
+ "log-level": "info",
219
+ "external-controller": "127.0.0.1:9090",
220
+ "proxies": [],
221
+ "proxy-groups": [],
222
+ "rules": [
223
+ "DOMAIN-SUFFIX,google.com,Proxy",
224
+ "DOMAIN-SUFFIX,github.com,Proxy",
225
+ "DOMAIN-SUFFIX,githubusercontent.com,Proxy",
226
+ "DOMAIN-SUFFIX,openai.com,Proxy",
227
+ "DOMAIN-SUFFIX,anthropic.com,Proxy",
228
+ "DOMAIN-SUFFIX,claude.ai,Proxy",
229
+ "GEOIP,CN,DIRECT",
230
+ "MATCH,DIRECT"
231
+ ]
232
+ }
233
+
234
+ try:
235
+ with open(self.config_path, 'w', encoding='utf-8') as f:
236
+ yaml.dump(default_config, f, default_flow_style=False, allow_unicode=True)
237
+ self.config_data = default_config
238
+ print(f"✅ 已创建默认配置文件: {self.config_path}")
239
+ except Exception as e:
240
+ print(f"❌ 创建默认配置文件失败: {e}")
241
+
242
+ def start_proxy(self) -> bool:
243
+ """启动代理服务"""
244
+ if self.proxy_enabled:
245
+ print("✅ 代理服务已在运行")
246
+ return True
247
+
248
+ try:
249
+ # 确保clash已安装
250
+ if not self.clash_binary_path:
251
+ if not self._install_clash():
252
+ print("❌ Clash安装失败")
253
+ return False
254
+
255
+ # 启动clash进程
256
+ cmd = [self.clash_binary_path, "-f", self.config_path]
257
+ self.clash_process = subprocess.Popen(
258
+ cmd,
259
+ stdout=subprocess.PIPE,
260
+ stderr=subprocess.PIPE,
261
+ text=True
262
+ )
263
+
264
+ # 等待服务启动
265
+ time.sleep(2)
266
+
267
+ # 检查服务是否正常启动
268
+ if self._check_proxy_status():
269
+ self.proxy_enabled = True
270
+ print("✅ 代理服务启动成功")
271
+ self._set_system_proxy()
272
+ return True
273
+ else:
274
+ print("❌ 代理服务启动失败")
275
+ self.stop_proxy()
276
+ return False
277
+
278
+ except Exception as e:
279
+ print(f"❌ 启动代理服务失败: {e}")
280
+ return False
281
+
282
+ def stop_proxy(self):
283
+ """停止代理服务"""
284
+ if self.clash_process:
285
+ try:
286
+ self.clash_process.terminate()
287
+ self.clash_process.wait(timeout=5)
288
+ except subprocess.TimeoutExpired:
289
+ self.clash_process.kill()
290
+ finally:
291
+ self.clash_process = None
292
+
293
+ self.proxy_enabled = False
294
+ self._unset_system_proxy()
295
+ print("✅ 代理服务已停止")
296
+
297
+
298
+
299
+ def _check_proxy_status(self) -> bool:
300
+ """检查代理服务状态"""
301
+ try:
302
+ # 检查HTTP代理
303
+ proxies = {
304
+ 'http': 'http://127.0.0.1:7890',
305
+ 'https': 'http://127.0.0.1:7890'
306
+ }
307
+ response = requests.get('http://httpbin.org/ip',
308
+ proxies=proxies, timeout=5)
309
+ return response.status_code == 200
310
+ except:
311
+ return False
312
+
313
+ def _set_system_proxy(self):
314
+ """设置系统代理"""
315
+ try:
316
+ if sys.platform == "win32":
317
+ # Windows系统代理设置
318
+ import winreg
319
+
320
+ def set_key(name, value):
321
+ try:
322
+ winreg.CreateKey(winreg.HKEY_CURRENT_USER,
323
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
324
+ registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
325
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
326
+ 0, winreg.KEY_WRITE)
327
+ winreg.SetValueEx(registry_key, name, 0, winreg.REG_DWORD, value)
328
+ winreg.CloseKey(registry_key)
329
+ return True
330
+ except WindowsError:
331
+ return False
332
+
333
+ set_key("ProxyEnable", 1)
334
+ set_key("ProxyServer", "127.0.0.1:7890")
335
+
336
+ elif sys.platform == "darwin":
337
+ # macOS系统代理设置
338
+ os.system("networksetup -setwebproxy 'Wi-Fi' 127.0.0.1 7890")
339
+ os.system("networksetup -setsecurewebproxy 'Wi-Fi' 127.0.0.1 7890")
340
+ os.system("networksetup -setsocksfirewallproxy 'Wi-Fi' 127.0.0.1 7891")
341
+
342
+ else:
343
+ # Linux系统代理设置
344
+ os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
345
+ os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
346
+ os.environ['http_proxy'] = 'http://127.0.0.1:7890'
347
+ os.environ['https_proxy'] = 'http://127.0.0.1:7890'
348
+
349
+ except Exception as e:
350
+ print(f"⚠️ 设置系统代理失败: {e}")
351
+
352
+ def _unset_system_proxy(self):
353
+ """取消系统代理设置"""
354
+ try:
355
+ if sys.platform == "win32":
356
+ import winreg
357
+
358
+ def set_key(name, value):
359
+ try:
360
+ winreg.CreateKey(winreg.HKEY_CURRENT_USER,
361
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
362
+ registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
363
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
364
+ 0, winreg.KEY_WRITE)
365
+ winreg.SetValueEx(registry_key, name, 0, winreg.REG_DWORD, value)
366
+ winreg.CloseKey(registry_key)
367
+ return True
368
+ except WindowsError:
369
+ return False
370
+
371
+ set_key("ProxyEnable", 0)
372
+
373
+ elif sys.platform == "darwin":
374
+ os.system("networksetup -setwebproxystate 'Wi-Fi' off")
375
+ os.system("networksetup -setsecurewebproxystate 'Wi-Fi' off")
376
+ os.system("networksetup -setsocksfirewallproxystate 'Wi-Fi' off")
377
+
378
+ else:
379
+ # Linux系统代理设置
380
+ if 'HTTP_PROXY' in os.environ:
381
+ del os.environ['HTTP_PROXY']
382
+ if 'HTTPS_PROXY' in os.environ:
383
+ del os.environ['HTTPS_PROXY']
384
+ if 'http_proxy' in os.environ:
385
+ del os.environ['http_proxy']
386
+ if 'https_proxy' in os.environ:
387
+ del os.environ['https_proxy']
388
+
389
+ except Exception as e:
390
+ print(f"⚠️ 取消系统代理失败: {e}")
391
+
392
+ def get_proxy_info(self) -> Dict[str, Any]:
393
+ """获取代理信息"""
394
+ return {
395
+ "enabled": self.proxy_enabled,
396
+ "config_path": self.config_path,
397
+ "clash_binary": self.clash_binary_path,
398
+ "clash_dir": self.clash_dir,
399
+ "http_proxy": "http://127.0.0.1:7890" if self.proxy_enabled else None,
400
+ "socks_proxy": "socks5://127.0.0.1:7891" if self.proxy_enabled else None
401
+ }
402
+
403
+ def update_config(self, new_config: Dict[str, Any]):
404
+ """更新代理配置"""
405
+ try:
406
+ with open(self.config_path, 'w', encoding='utf-8') as f:
407
+ yaml.dump(new_config, f, default_flow_style=False, allow_unicode=True)
408
+ self.config_data = new_config
409
+ print("✅ 代理配置已更新")
410
+
411
+ # 如果代理正在运行,重启服务
412
+ if self.proxy_enabled:
413
+ self.stop_proxy()
414
+ time.sleep(1)
415
+ self.start_proxy()
416
+
417
+ except Exception as e:
418
+ print(f"❌ 更新代理配置失败: {e}")
419
+
420
+ def cleanup(self):
421
+ """清理资源"""
422
+ self.stop_proxy()
423
+ # 可以选择是否删除clash二进制文件
424
+ # if self.clash_dir and os.path.exists(self.clash_dir):
425
+ # import shutil
426
+ # shutil.rmtree(self.clash_dir)
427
+
428
+ # # 全局代理管理器实例
429
+ proxy_manager = ProxyManager()
430
+
431
+ def init_proxy():
432
+ """初始化代理服务"""
433
+ return proxy_manager.start_proxy()
434
+
435
+ def stop_proxy():
436
+ """停止代理服务"""
437
+ proxy_manager.stop_proxy()
438
+
439
+ def get_proxy_status():
440
+ """获取代理状态"""
441
+ return proxy_manager.get_proxy_info()
442
+
443
+ def cleanup_proxy():
444
+ """清理代理资源"""
445
+ proxy_manager.cleanup()
@@ -130,7 +130,7 @@ class ryryDaemonThread(Thread):
130
130
  def __init__(self):
131
131
  super().__init__()
132
132
  self.name = f"ryryDaemonThread"
133
- self.tik_time = 30.0
133
+ self.tik_time = 10.0
134
134
  self.start()
135
135
 
136
136
  def run(self):
@@ -143,7 +143,9 @@ class ryryDaemonThread(Thread):
143
143
  try:
144
144
  daemon_manager.sync_daemons_with_widget_map()
145
145
  except:
146
- time.sleep(60)
146
+ pass
147
+ finally:
148
+ time.sleep(self.tik_time)
147
149
  try:
148
150
  print("Stopping daemon processes...")
149
151
  if daemon_manager.stop_all_daemons():
@@ -155,7 +155,7 @@ def extend():
155
155
  _genExtend()
156
156
  elif read_data["extend"].get("device_id", "") != read_data.get("deviceInfo", {}).get("device_id", ""):
157
157
  _genExtend()
158
- elif read_data["extend"].get("app", "") == "ryry-cli 4.24":
158
+ elif read_data["extend"].get("app", "") == "ryry-cli 4.26":
159
159
  _genExtend()
160
160
  else:
161
161
  _genExtend()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 4.24
3
+ Version: 4.26
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -23,6 +23,7 @@ Requires-Dist: gputil
23
23
  Requires-Dist: urlparser
24
24
  Requires-Dist: urllib3
25
25
  Requires-Dist: portalocker
26
+ Requires-Dist: PyYAML
26
27
  Provides-Extra: with-mecord
27
28
  Requires-Dist: mecord-cli>=0.7.405; extra == "with-mecord"
28
29
 
@@ -6,6 +6,7 @@ ryry/constant.py
6
6
  ryry/daemon_base.py
7
7
  ryry/daemon_manager.py
8
8
  ryry/main.py
9
+ ryry/proxy_manager.py
9
10
  ryry/ryry_server_socket.py
10
11
  ryry/ryry_service.py
11
12
  ryry/ryry_webapi.py
@@ -10,6 +10,7 @@ gputil
10
10
  urlparser
11
11
  urllib3
12
12
  portalocker
13
+ PyYAML
13
14
 
14
15
  [with_mecord]
15
16
  mecord-cli>=0.7.405
@@ -3,7 +3,7 @@ import os
3
3
  import subprocess
4
4
  import datetime
5
5
 
6
- ryry_version = "4.24"
6
+ ryry_version = "4.26"
7
7
  cur_dir = os.path.dirname(os.path.abspath(__file__))
8
8
  constanspy = os.path.join(cur_dir, "ryry", "constant.py")
9
9
  try:
@@ -69,7 +69,8 @@ setuptools.setup(
69
69
  'gputil',
70
70
  'urlparser',
71
71
  'urllib3',
72
- 'portalocker'
72
+ 'portalocker',
73
+ 'PyYAML'
73
74
  ],
74
75
  extras_require={
75
76
  'with_mecord': ['mecord-cli>=0.7.405'],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes