Skip to content

Commit 8dc302a

Browse files
committed
feat(clash): add arabic translation for clash automation post
1 parent cff4341 commit 8dc302a

File tree

2 files changed

+663
-0
lines changed

2 files changed

+663
-0
lines changed

_posts/ar/2025-06-08-clash-ar.md

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
---
2+
audio: false
3+
generated: false
4+
lang: ar
5+
layout: post
6+
title: أتمتة إدارة بروكسي التصادم
7+
translated: true
8+
---
9+
10+
هذا المنشور يوضح برنامجًا نصيًا بلغة بايثون، `clash.py`، مصممًا **لأتمتة إدارة تكوين بروكسي Clash الخاص بك**. يتولى كل شيء بدءًا من **تنزيل تكوينات البروكسي المحدثة دوريًا** و**إعادة تشغيل خدمة Clash** وحتى **اختيار التحويل الذكي إلى أسرع بروكسي متاح** ضمن مجموعة محددة. إلى جانب `clash.py`، فإن وحدة `speed.py` تسهل **اختبار زمن الوصل لكل بروكسي Clash بشكل متزامن**، مما يضمن أن اتصالك دائمًا ما يتم توجيهه عبر الخادم الأمثل.
11+
12+
## clash.py
13+
14+
```python
15+
import os
16+
import subprocess
17+
import time
18+
import shutil
19+
import argparse
20+
import logging
21+
import requests
22+
import json
23+
import urllib.parse
24+
25+
# بافتراض أن speed.py موجود في نفس الدليل أو متاح في PYTHONPATH
26+
from speed import get_top_proxies
27+
28+
# --- التكوين ---
29+
CLASH_CONTROLLER_HOST = "127.0.0.1"
30+
CLASH_CONTROLLER_PORT = 9090
31+
CLASH_API_BASE_URL = f"http://{CLASH_CONTROLLER_HOST}:{CLASH_CONTROLLER_PORT}"
32+
# اسم مجموعة البروكسي التي سيتم تعيين أفضل بروكسي فردي إليها.
33+
# تأكد من وجود هذه المجموعة في تكوين Clash الخاص بك.
34+
TARGET_PROXY_GROUP = "🚧Proxy"
35+
36+
def setup_logging():
37+
"""يضبط تسجيل الأحداث الأساسي للنص البرمجي."""
38+
logging.basicConfig(
39+
filename='clash.log',
40+
level=logging.INFO,
41+
format='%(asctime)s - %(message)s',
42+
datefmt='%Y-%m-%d %H:%M:%S'
43+
)
44+
45+
def start_system_proxy(global_proxy_address):
46+
"""يضبط متغيرات بيئة البروكسي على مستوى النظام."""
47+
os.environ["GLOBAL_PROXY"] = global_proxy_address # يتم تعيينه للاتساق إذا كان مطلوبًا في مكان آخر
48+
os.environ["HTTP_PROXY"] = f"http://{global_proxy_address}"
49+
os.environ["HTTPS_PROXY"] = f"http://{global_proxy_address}"
50+
os.environ["http_proxy"] = f"http://{global_proxy_address}"
51+
os.environ["https_proxy"] = f"http://{global_proxy_address}"
52+
# هذه عادةً لا تحتاج إلى تعيين صريح إلى "false" مع الأدوات الحديثة،
53+
# ولكن يتم الاحتفاظ بها لتوافقها مع نية النص البرمجي الأصلي.
54+
os.environ["HTTP_PROXY_REQUEST_FULLURI"] = "false"
55+
os.environ["HTTPS_PROXY_REQUEST_FULLURI"] = "false"
56+
os.environ["ALL_PROXY"] = os.environ["http_proxy"]
57+
logging.info(f"تم تعيين البروكسي على مستوى النظام إلى: {global_proxy_address}")
58+
59+
def stop_system_proxy():
60+
"""يمسح متغيرات بيئة البروكسي على مستوى النظام."""
61+
os.environ["http_proxy"] = ""
62+
os.environ["HTTP_PROXY"] = ""
63+
os.environ["https_proxy"] = ""
64+
os.environ["HTTPS_PROXY"] = ""
65+
os.environ["HTTP_PROXY_REQUEST_FULLURI"] = "true" # العودة إلى الإعداد الافتراضي
66+
os.environ["HTTPS_PROXY_REQUEST_FULLURI"] = "true"
67+
os.environ["ALL_PROXY"] = ""
68+
logging.info("تم إيقاف البروكسي على مستوى النظام (تم مسح متغيرات البيئة).")
69+
70+
def switch_clash_proxy_group(group_name, proxy_name):
71+
"""
72+
يحول البروكسي النشط في مجموعة بروكسي Clash محددة إلى بروكسي جديد.
73+
"""
74+
encoded_group_name = urllib.parse.quote(group_name)
75+
url = f"{CLASH_API_BASE_URL}/proxies/{encoded_group_name}"
76+
headers = {"Content-Type": "application/json"}
77+
payload = {"name": proxy_name}
78+
79+
try:
80+
response = requests.put(url, headers=headers, data=json.dumps(payload), timeout=5)
81+
response.raise_for_status()
82+
logging.info(f"تم التحويل بنجاح من '{group_name}' إلى '{proxy_name}'.")
83+
return True
84+
except requests.exceptions.ConnectionError:
85+
logging.error(f"خطأ: لا يمكن الاتصال بـ Clash API في {CLASH_API_BASE_URL} لتحويل البروكسي.")
86+
logging.error("تأكد من أن Clash يعمل وأن external-controller مضبوط.")
87+
return False
88+
except requests.exceptions.Timeout:
89+
logging.error(f"خطأ: انتهت مهلة الاتصال بـ Clash API أثناء تحويل البروكسي لـ '{group_name}'.")
90+
return False
91+
except requests.exceptions.RequestException as e:
92+
logging.error(f"حدث خطأ غير متوقع أثناء تحويل البروكسي لـ '{group_name}': {e}")
93+
return False
94+
95+
def main():
96+
"""الوظيفة الرئيسية لإدارة تكوين Clash، إعادة التشغيل، واختيار أفضل بروكسي."""
97+
setup_logging()
98+
99+
parser = argparse.ArgumentParser(description="نص برمجي لإدارة وتكوين Clash.")
100+
parser.add_argument("--minutes", type=int, default=10, help="الدقائق بين التحديثات (الافتراضي: 10)")
101+
parser.add_argument("--iterations", type=int, default=1000, help="عدد التكرارات (الافتراضي: 1000)")
102+
parser.add_argument(
103+
"--config-url",
104+
type=str,
105+
default=os.getenv("CLASH_DOWNLOAD_URL"),
106+
help="رابط تنزيل تكوين Clash منه. الافتراضي هو متغير البيئة CLASH_DOWNLOAD_URL إذا تم تعيينه، وإلا فسيتم استخدام رابط ثابت."
107+
)
108+
args = parser.parse_args()
109+
110+
ITERATIONS = args.iterations
111+
SLEEP_SECONDS = args.minutes * 60
112+
config_download_url = args.config_url
113+
114+
if not config_download_url:
115+
logging.critical("خطأ: لم يتم تقديم رابط تنزيل التكوين. يرجى تعيين متغير البيئة CLASH_DOWNLOAD_URL أو استخدام وسيطة --config-url.")
116+
return # الخروج إذا لم يكن هناك رابط متاح
117+
118+
clash_executable_path = "/home/lzw/clash-linux-386-v1.17.0/clash-linux-386"
119+
clash_config_dir = os.path.expanduser("~/.config/clash")
120+
clash_config_path = os.path.join(clash_config_dir, "config.yaml")
121+
122+
for i in range(1, ITERATIONS + 1):
123+
logging.info(f"--- بدء التكرار {i} من {ITERATIONS} ---")
124+
125+
# الخطوة 1: إيقاف أي إعدادات بروكسي نظامية موجودة
126+
stop_system_proxy()
127+
128+
# الخطوة 2: تنزيل وتحديث تكوين Clash
129+
try:
130+
logging.info(f"جاري تنزيل التكوين الجديد من: {config_download_url}")
131+
subprocess.run(["wget", config_download_url, "-O", "zhs4.yaml"], check=True, capture_output=True)
132+
os.makedirs(clash_config_dir, exist_ok=True)
133+
shutil.move("zhs4.yaml", clash_config_path)
134+
logging.info("تم تحديث تكوين Clash بنجاح!")
135+
except subprocess.CalledProcessError as e:
136+
logging.error(f"فشل في تنزيل أو نقل ملف التكوين: {e.stderr.decode().strip()}")
137+
logging.error("تخطي إلى التكرار التالي.")
138+
time.sleep(10) # الانتظار قليلاً قبل إعادة المحاولة
139+
continue
140+
except Exception as e:
141+
logging.error(f"حدث خطأ غير متوقع أثناء تحديث التكوين: {e}")
142+
logging.error("تخطي إلى التكرار التالي.")
143+
time.sleep(10)
144+
continue
145+
146+
# الخطوة 3: تشغيل Clash في الخلفية
147+
clash_process = None
148+
try:
149+
# من الضروري أن يبدأ Clash مع تفعيل external-controller ويمكن الوصول إليه
150+
# هذا عادةً ما يتم تكوينه داخل ملف config.yaml نفسه.
151+
clash_process = subprocess.Popen([clash_executable_path],
152+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153+
logging.info(f"تم تشغيل Clash بمعرف العملية {clash_process.pid}")
154+
# إعطاء Clash لحظة للتهيئة الكاملة وفتح منفذ API الخاص به
155+
time.sleep(5)
156+
except FileNotFoundError:
157+
logging.critical(f"لم يتم العثور على ملف تنفيذ Clash في: {clash_executable_path}")
158+
logging.critical("يرجى التأكد من صحة المسار وأن Clash مثبت.")
159+
return # خطأ حرج، الخروج من النص البرمجي
160+
except Exception as e:
161+
logging.error(f"فشل في تشغيل Clash: {e}")
162+
logging.error("تخطي إلى التكرار التالي.")
163+
if clash_process: clash_process.terminate()
164+
time.sleep(10)
165+
continue
166+
167+
# الخطوة 4: اختبار سرعات البروكسي واختيار الأفضل
168+
best_proxy_name = None
169+
try:
170+
logging.info("جاري اختبار سرعات البروكسي للعثور على الأفضل...")
171+
top_proxies = get_top_proxies(num_results=1) # الحصول على أفضل بروكسي واحد فقط
172+
if top_proxies:
173+
best_proxy_name = top_proxies[0]['name']
174+
logging.info(f"تم تحديد أفضل بروكسي: '{best_proxy_name}' بزمن وصل {top_proxies[0]['latency']}ms")
175+
else:
176+
logging.warning("لا توجد اختبارات بروكسي ناجحة. لا يمكن اختيار أفضل بروكسي لهذا التكرار.")
177+
except Exception as e:
178+
logging.error(f"خطأ أثناء اختبار سرعة البروكسي: {e}")
179+
180+
# الخطوة 5: تحويل مجموعة بروكسي Clash إلى البروكسي الأفضل (إذا تم العثور عليه)
181+
if best_proxy_name:
182+
# قبل تعيين بروكسي النظام، تأكد من أن Clash مضبوط بشكل صحيح.
183+
# تعيين بروكسي النظام للإشارة إلى بروكسي HTTP المحلي لـ Clash.
184+
# عادةً ما يعمل Clash بروكسي HTTP على المنفذ 7890 (أو ما شابه، تحقق من تكوينك).
185+
clash_local_proxy_address = f"{CLASH_CONTROLLER_HOST}:7890" # اضبط إذا كان منفذ HTTP لـ Clash مختلفًا
186+
start_system_proxy(clash_local_proxy_address)
187+
188+
if not switch_clash_proxy_group(TARGET_PROXY_GROUP, best_proxy_name):
189+
logging.error(f"فشل في تحويل مجموعة Clash '{TARGET_PROXY_GROUP}' إلى '{best_proxy_name}'.")
190+
else:
191+
logging.warning("لم يتم العثور على أفضل بروكسي، تخطي تحويل مجموعة البروكسي وإعداد بروكسي النظام لهذا التكرار.")
192+
193+
# الخطوة 6: الانتظار للمدة المحددة
194+
logging.info(f"الانتظار لمدة {SLEEP_SECONDS / 60} دقائق قبل التكرار التالي...")
195+
time.sleep(SLEEP_SECONDS)
196+
197+
# الخطوة 7: إيقاف عملية Clash
198+
if clash_process:
199+
logging.info("جاري إنهاء عملية Clash...")
200+
clash_process.terminate()
201+
try:
202+
clash_process.wait(timeout=10) # إعطاء Clash وقتًا إضافيًا للإغلاق بلطف
203+
logging.info("تم إيقاف Clash بنجاح.")
204+
except subprocess.TimeoutExpired:
205+
logging.warning("لم يتم إنهاء Clash بلطف، جاري قتل العملية.")
206+
clash_process.kill()
207+
clash_process.wait() # التأكد من قتل العملية بالكامل
208+
except Exception as e:
209+
logging.error(f"خطأ أثناء انتظار إيقاف Clash: {e}")
210+
211+
logging.info(f"--- اكتمل التكرار {i} ---")
212+
213+
logging.info(f"تم إكمال {ITERATIONS} تكرارات. انتهى النص البرمجي.")
214+
215+
if __name__ == "__main__":
216+
main()
217+
```
218+
219+
## speed.py
220+
221+
```python
222+
import requests
223+
import json
224+
import urllib.parse
225+
import time
226+
from concurrent.futures import ThreadPoolExecutor, as_completed
227+
import logging # استيراد وحدة تسجيل الأحداث
228+
229+
# --- التكوين ---
230+
CLASH_CONTROLLER_HOST = "127.0.0.1" # استخدام 127.0.0.1 حيث أن المتحكم على نفس الجهاز
231+
CLASH_CONTROLLER_PORT = 9090
232+
CLASH_API_BASE_URL = f"http://{CLASH_CONTROLLER_HOST}:{CLASH_CONTROLLER_PORT}"
233+
LATENCY_TEST_URL = "https://github.com" # رابط الاختبار المحدث
234+
LATENCY_TEST_TIMEOUT_MS = 5000 # ميلي ثانية
235+
CONCURRENT_CONNECTIONS = 10 # عدد الاختبارات المتزامنة
236+
237+
# قائمة بأسماء مجموعات البروكسي المعروفة لاستبعادها من اختبار السرعة
238+
# هذه عادةً ما لا تكون عقدًا فردية ولكن مجموعات سياسات أو بروكسيات خاصة.
239+
EXCLUDE_PROXY_GROUPS = [
240+
"DIRECT",
241+
"REJECT",
242+
"GLOBAL", # مستبعد بالفعل افتراضيًا في API
243+
"🇨🇳国内网站或资源",
244+
"🌵其它规则外",
245+
"🎬Netflix等国外流媒体",
246+
"📦ChatGPT",
247+
"📹Youtube",
248+
"📺爱奇艺等国内流媒体",
249+
"🚧Proxy",
250+
# أضف أي أسماء مجموعات أخرى تريد استبعادها هنا
251+
]
252+
253+
# --- إعداد تسجيل الأحداث لـ speed.py ---
254+
# تكوين تسجيل الأحداث لهذا النص البرمجي المحدد
255+
# هذا يضمن أنه عند استيراد speed.py واستدعاء وظائفه،
256+
# يذهب إخراجه إلى speed.log، منفصل عن clash_manager.log.
257+
logging.basicConfig(
258+
filename='clash.log',
259+
level=logging.INFO,
260+
format='%(asctime)s - %(levelname)s - %(message)s',
261+
datefmt='%Y-%m-%d %H:%M:%S'
262+
)
263+
# اختياريًا، إذا كنت تريد أيضًا رؤية الإخراج في وحدة التحكم، أضف StreamHandler:
264+
# console_handler = logging.StreamHandler()
265+
# console_handler.setLevel(logging.INFO)
266+
# formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
267+
# console_handler.setFormatter(formatter)
268+
# logging.getLogger().addHandler(console_handler)
269+
270+
# --- منطق النص البرمجي ---
271+
272+
def get_all_proxy_names():
273+
"""يجلب جميع أسماء البروكسي من Clash API، باستثناء المجموعات المعروفة."""
274+
try:
275+
response = requests.get(f"{CLASH_API_BASE_URL}/proxies", timeout=5)
276+
response.raise_for_status() # إثارة استثناء لأخطاء HTTP (4xx أو 5xx)
277+
proxies_data = response.json()
278+
279+
all_names = proxies_data.get("proxies", {}).keys()
280+
281+
# تصفية أسماء المجموعات
282+
filtered_names = [name for name in all_names if name not in EXCLUDE_PROXY_GROUPS]
283+
284+
logging.info(f"تم جلب {len(filtered_names)} أسماء بروكسي قابلة للاختبار بنجاح.")
285+
return filtered_names
286+
except requests.exceptions.ConnectionError:
287+
logging.error(f"لا يمكن الاتصال بـ Clash API في {CLASH_API_BASE_URL}. تأكد من أن Clash يعمل.")
288+
return []
289+
except requests.exceptions.Timeout:
290+
logging.error(f"انتهت مهلة الاتصال بـ Clash API بعد 5 ثوانٍ.")
291+
return []
292+
except requests.exceptions.RequestException as e:
293+
logging.error(f"حدث خطأ غير متوقع أثناء جلب أسماء البروكسي: {e}")
294+
return []
295+
296+
def test_proxy_latency(proxy_name):
297+
"""يختبر زمن الوصل لبروكسي واحد باستخدام Clash API.
298+
يعيد tuple (اسم البروكسي, زمن الوصل) أو (اسم البروكسي, None) في حالة الفشل.
299+
"""
300+
encoded_proxy_name = urllib.parse.quote(proxy_name)
301+
url = f"{CLASH_API_BASE_URL}/proxies/{encoded_proxy_name}/delay"
302+
params = {
303+
"url": LATENCY_TEST_URL,
304+
"timeout": LATENCY_TEST_TIMEOUT_MS
305+
}
306+
try:
307+
# مهلة requests بالثواني، تحويل الميلي ثانية
308+
response = requests.get(url, params=params, timeout=(LATENCY_TEST_TIMEOUT_MS / 1000) + 1)
309+
response.raise_for_status()
310+
latency_data = response.json()
311+
latency = latency_data.get("delay")
312+
logging.info(f"البروكسي: {proxy_name} - زمن الوصل: {latency}ms")
313+
return proxy_name, latency
314+
except requests.exceptions.RequestException as e:
315+
logging.warning(f"خطأ في اختبار '{proxy_name}': {e}")
316+
return proxy_name, None
317+
318+
def get_top_proxies(num_results=5):
319+
"""
320+
يختبر سرعات بروكسي Clash بشكل متزامن ويعيد أفضل N بروكسي فردي من حيث السرعة.
321+
322+
الإرجاع:
323+
list: قائمة من القواميس، كل منها يحتوي على 'name' و 'latency' لأفضل البروكسيات.
324+
تعيد قائمة فارغة إذا لم يتم العثور على بروكس

0 commit comments

Comments
 (0)