-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun_vfs_bot.py
More file actions
433 lines (367 loc) · 20.3 KB
/
run_vfs_bot.py
File metadata and controls
433 lines (367 loc) · 20.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
VFS Appointment Bot - Python Script
بوت مواعيد VFS - سكريبت Python
هذا السكريبت لتشغيل بوت مواعيد VFS
"""
import logging
import os
import sys
from pathlib import Path
from types import SimpleNamespace
from typing import Dict
# إضافة المسار الحالي إلى sys.path للسماح بالاستيراد
# إضافة مجلد المشروع الحالي إلى المسار
current_dir = Path(__file__).parent
sys.path.insert(0, str(current_dir))
from utils.config_reader import get_config_value, initialize_config
from vfs_bot.vfs_bot import LoginError
from vfs_bot.vfs_bot_factory import (
UnsupportedCountryError,
get_vfs_bot,
)
def initialize_logger():
"""Initialize logging configuration"""
# إعداد الترميز UTF-8 للنوافذ في Windows
import io
import os
if sys.platform == 'win32':
# تعيين الترميز الافتراضي للنظام
os.environ['PYTHONIOENCODING'] = 'utf-8'
# التحقق من وجود buffer قبل المحاولة
try:
if hasattr(sys.stdout, 'buffer'):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
else:
# إذا لم يكن هناك buffer، استخدم sys.stdout مباشرة
sys.stdout = io.TextIOWrapper(sys.__stdout__.buffer, encoding='utf-8', errors='replace')
except (AttributeError, io.UnsupportedOperation):
# إذا فشل، استخدم sys.__stdout__
try:
sys.stdout = io.TextIOWrapper(sys.__stdout__.buffer, encoding='utf-8', errors='replace')
except:
pass
try:
if hasattr(sys.stderr, 'buffer'):
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
else:
sys.stderr = io.TextIOWrapper(sys.__stderr__.buffer, encoding='utf-8', errors='replace')
except (AttributeError, io.UnsupportedOperation):
try:
sys.stderr = io.TextIOWrapper(sys.__stderr__.buffer, encoding='utf-8', errors='replace')
except:
pass
file_handler = logging.FileHandler("app.log", mode="a", encoding='utf-8')
file_handler.setFormatter(
logging.Formatter(
"[%(asctime)s] %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
)
)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(logging.Formatter("[%(asctime)s] %(message)s"))
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] %(levelname)s [%(filename)s:%(lineno)d] %(message)s",
handlers=[
file_handler,
stream_handler,
],
)
def check_configuration(source_country_code: str, destination_country_code: str) -> bool:
"""
Verify that all required configuration is set
Args:
source_country_code: Source country code
destination_country_code: Destination country code
Returns:
True if configuration is valid, False otherwise
"""
errors = []
warnings = []
# التحقق من وجود ملفات الإعدادات
if not os.path.exists("config"):
errors.append("❌ مجلد config غير موجود!")
else:
ini_files = [f for f in os.listdir("config") if f.endswith(".ini")]
if not ini_files:
warnings.append("⚠️ لا توجد ملفات .ini في مجلد config")
else:
print(f"✓ تم العثور على {len(ini_files)} ملف إعدادات")
# التحقق من بيانات الاعتماد
email = get_config_value("vfs-credential", "email")
password = get_config_value("vfs-credential", "password")
if not email or not password:
errors.append("❌ البريد الإلكتروني أو كلمة المرور غير موجودة في الإعدادات!")
else:
print("✓ تم العثور على بيانات الاعتماد")
# التحقق من URL
url_key = f"{source_country_code}-{destination_country_code}"
vfs_url = get_config_value("vfs-url", url_key)
if not vfs_url:
errors.append(f"❌ لا يوجد URL مخصص لـ {url_key}")
else:
print(f"✓ تم العثور على URL: {vfs_url[:50]}...")
# التحقق من البلدان المدعومة
supported_countries = ["DE", "IT", "PT"]
if destination_country_code not in supported_countries:
errors.append(
f"❌ البلد {destination_country_code} غير مدعوم. "
f"البلدان المدعومة: {', '.join(supported_countries)}"
)
else:
print(f"✓ البلد {destination_country_code} مدعوم")
# التحقق من إعدادات GPM (اختياري)
from utils.config_reader import get_config_section
gpm_section = get_config_section("gpm", {})
use_gpm = str(gpm_section.get("enabled", "False")).lower() == "true"
if use_gpm:
api_url = gpm_section.get("api_url", "http://127.0.0.1:14517")
profile_id = gpm_section.get("profile_id")
if not profile_id or profile_id == "your_profile_id_here":
# إذا لم يكن هناك profile_id، سيتم إنشاؤه تلقائياً
warnings.append(
"⚠️ GPM مفعّل لكن profile_id غير موجود\n"
" سيتم إنشاء بروفايل جديد تلقائياً عند التشغيل"
)
else:
# التحقق من توفر GPM (استخدام API الصحيح)
try:
import requests
# استخدام API الصحيح: /api/v3/profiles
response = requests.get(f"{api_url}/api/v3/profiles", timeout=3)
if response.status_code == 200:
data = response.json()
if data.get("success", False):
print(f"✓ GPM مفعّل - API URL: {api_url}, Profile ID: {profile_id}")
else:
warnings.append(f"⚠️ GPM مفعّل لكن API أرجعت خطأ: {data.get('message', 'Unknown')}")
else:
warnings.append(f"⚠️ GPM مفعّل لكن API غير متاح على {api_url} (Status: {response.status_code})")
except Exception as e:
warnings.append(
f"⚠️ GPM مفعّل لكن غير متاح على {api_url}\n"
f" تأكد من تشغيل GPM (الافتراضي: http://127.0.0.1:14517)\n"
f" أو قم بتعطيله بضبط enabled = False"
)
else:
print("✓ استخدام المتصفح العادي (GPM غير مفعّل)")
# عرض النتائج
print("\n" + "="*50)
if errors:
print("الأخطاء:")
for error in errors:
print(f" {error}")
if warnings:
print("\nالتحذيرات:")
for warning in warnings:
print(f" {warning}")
if not errors and not warnings:
print("✓ جميع الإعدادات صحيحة! جاهز للتشغيل")
elif errors:
print("\n❌ يرجى إصلاح الأخطاء قبل التشغيل")
else:
print("\n⚠️ يمكنك المتابعة، لكن يرجى مراجعة التحذيرات")
print(" (سيتم إنشاء بروفايل GPM تلقائياً إذا لزم الأمر)")
print("="*50 + "\n")
# التحذيرات لا تمنع التشغيل، فقط الأخطاء
return len(errors) == 0
def main():
"""Main function to run the VFS bot"""
# ============================================
# إعدادات البوت - قم بتعديل هذه القيم
# ============================================
# كود البلد المصدر (ISO 3166-1 alpha-2)
# مثال: "AE" للإمارات، "SA" للسعودية، "EG" لمصر
source_country_code = "AE"
# كود البلد الوجهة (ISO 3166-1 alpha-2)
# البلدان المدعومة حالياً: DE (ألمانيا), IT (إيطاليا), PT (البرتغال)
destination_country_code = "PT"
# معاملات الموعد الإضافية (اختياري)
# للبلدان المدعومة:
# - DE (ألمانيا): visa_center, visa_category, visa_sub_category
# - IT (إيطاليا): visa_center, visa_category, visa_sub_category
# - PT (البرتغال): visa_center, visa_category, visa_sub_category
#
# مثال لألمانيا:
appointment_params = {
"visa_center": "Dubai", # مركز التأشيرة
"visa_category": "Tourism", # فئة التأشيرة
"visa_sub_category": "Short stay", # الفئة الفرعية
}
# أو اتركه None وسيطلب منك البوت إدخالها أثناء التشغيل
# appointment_params = None
# حد أقصى لعدد المحاولات (يمكنك تغييره)
max_iterations = 100
# عدد النوافذ/البروفايلات التي تريد فتحها بشكل متوازي
# كل نافذة ستعمل بشكل مستقل للتحقق من الحجز
number_of_windows = 3 # قم بتغيير هذا الرقم حسب احتياجك (مثال: 3 نوافذ)
# ============================================
# إعدادات GPM (GoLogin Profile Manager) - اختياري
# ============================================
# لاستخدام GPM، يجب إضافة قسم [gpm] في ملف config/*.ini
# مثال على إعدادات GPM في ملف config:
#
# [gpm]
# enabled = True
# api_url = http://127.0.0.1:14517
# profile_id = your_profile_id_here
# start_path = /browser/start-profile
# start_method = GET
# stop_path = /browser/stop-profile
# stop_method = GET
# auto_stop = True
# api_token = your_api_token_here (اختياري)
#
# ملاحظات:
# - api_url: عنوان GPM API (افتراضي: http://127.0.0.1:14517)
# - profile_id: معرف البروفايل في GPM
# - enabled: True لتفعيل GPM، False لاستخدام المتصفح العادي
# - auto_stop: True لإيقاف البروفايل تلقائياً بعد الانتهاء
# ============================================
# تهيئة النظام
# ============================================
print("="*60)
print("VFS Appointment Bot - بوت مواعيد VFS")
print("="*60 + "\n")
initialize_logger()
initialize_config()
print(f"البلد المصدر: {source_country_code}")
print(f"البلد الوجهة: {destination_country_code}")
print(f"معاملات الموعد: {appointment_params}\n")
# التحقق من الإعدادات
is_ready = check_configuration(source_country_code, destination_country_code)
if not is_ready:
print("⚠️ يرجى إصلاح الأخطاء قبل المتابعة")
sys.exit(1)
# ============================================
# تشغيل البوت
# ============================================
# إنشاء كائن Namespace لمحاكاة argparse.Namespace
args = SimpleNamespace()
args.source_country_code = source_country_code
args.destination_country_code = destination_country_code
args.appointment_params = appointment_params
try:
import time
import threading
from datetime import datetime, timedelta
PROFILE_MAX_AGE_MINUTES = 20 # الحد الأقصى لعمر البروفايل: 20 دقيقة
print(f"\n🚀 بدء تشغيل البوت...")
print(f"البلد المصدر: {source_country_code}")
print(f"البلد الوجهة: {destination_country_code}")
print(f"⏱️ مدة البروفايل: {PROFILE_MAX_AGE_MINUTES} دقيقة")
print(f"🔄 التحقق من الحجز: كل 5 ثواني")
print(f"🪟 عدد النوافذ: {number_of_windows}\n")
# متغير مشترك لتتبع العثور على موعد
appointment_found_global = threading.Event()
threads = []
def run_bot_window(window_number: int):
"""تشغيل نافذة واحدة من البوت"""
try:
iteration = 0
profile_start_time = None
consecutive_errors = 0
max_consecutive_errors = 3
logging.info(f"🪟 بدء النافذة #{window_number}")
print(f"🪟 بدء النافذة #{window_number}")
while not appointment_found_global.is_set():
iteration += 1
logging.info(f"[النافذة #{window_number}] المحاولة رقم {iteration}")
# التحقق من عمر البروفايل - إنشاء بروفايل جديد كل 20 دقيقة
current_time = datetime.now()
if profile_start_time is None or (current_time - profile_start_time).total_seconds() >= (PROFILE_MAX_AGE_MINUTES * 60):
if profile_start_time is not None:
elapsed = (current_time - profile_start_time).total_seconds() / 60
logging.info(f"[النافذة #{window_number}] ⏰ البروفايل تجاوز {PROFILE_MAX_AGE_MINUTES} دقيقة ({elapsed:.1f} دقيقة). إنشاء بروفايل جديد...")
profile_start_time = current_time
logging.info(f"[النافذة #{window_number}] 🆕 بروفايل جديد - الوقت: {profile_start_time.strftime('%Y-%m-%d %H:%M:%S')}")
# الحصول على البوت المناسب للبلد
vfs_bot = get_vfs_bot(source_country_code, destination_country_code)
# تشغيل البوت والتحقق من المواعيد (مع حلقة داخلية كل 5 ثواني)
# تمرير رقم النافذة وعدد النوافذ الإجمالي لترتيبها بجانب بعضها
# المتصفح سيُغلق تلقائياً في finally block داخل vfs_bot.run()
try:
appointment_found = vfs_bot.run(
args,
continuous_check=True,
check_interval=5,
window_number=window_number,
total_windows=number_of_windows
)
# إعادة تعيين عداد الأخطاء المتتالية عند النجاح
consecutive_errors = 0
if appointment_found:
logging.info(f"[النافذة #{window_number}] تم العثور على موعد متاح! 🎉")
print(f"\n[النافذة #{window_number}] 🎉 تم العثور على موعد متاح! 🎉")
appointment_found_global.set() # إيقاف جميع النوافذ الأخرى
break
# إذا كان appointment_found = False (مثل Permission Issue)، انتظر قبل إعادة المحاولة
# هذا يضمن إغلاق المتصفح السابق قبل فتح متصفح جديد
if not appointment_found and not appointment_found_global.is_set():
wait_time = 20 # انتظار 20 ثانية بعد إغلاق المتصفح السابق
logging.info(f"[النافذة #{window_number}] تم إغلاق المتصفح السابق. الانتظار {wait_time} ثانية قبل فتح متصفح جديد...")
time.sleep(wait_time)
except Exception as run_error:
consecutive_errors += 1
logging.error(f"[النافذة #{window_number}] خطأ في تشغيل البوت: {run_error}")
print(f"[النافذة #{window_number}] ❌ خطأ في تشغيل البوت: {run_error}")
# إذا حدثت أخطاء متتالية كثيرة، انتظر وقتاً أطول
if consecutive_errors >= max_consecutive_errors:
wait_time = 30 # انتظار 30 ثانية
logging.warning(f"[النافذة #{window_number}] حدثت {consecutive_errors} أخطاء متتالية. الانتظار {wait_time} ثانية...")
print(f"[النافذة #{window_number}] ⚠️ حدثت {consecutive_errors} أخطاء متتالية. الانتظار {wait_time} ثانية...")
time.sleep(wait_time)
consecutive_errors = 0 # إعادة تعيين العداد بعد الانتظار
else:
# انتظار قبل إعادة المحاولة بعد الخطأ
wait_time = 20
logging.info(f"[النافذة #{window_number}] الانتظار {wait_time} ثانية قبل إعادة المحاولة...")
time.sleep(wait_time)
except Exception as e:
logging.error(f"[النافذة #{window_number}] خطأ: {e}")
print(f"[النافذة #{window_number}] ❌ خطأ: {e}")
# انتظار قبل إعادة المحاولة في حالة الخطأ
if not appointment_found_global.is_set():
time.sleep(20)
# تشغيل عدة نوافذ بشكل متوازي
for i in range(1, number_of_windows + 1):
thread = threading.Thread(target=run_bot_window, args=(i,), daemon=True)
thread.start()
threads.append(thread)
time.sleep(2) # تأخير بسيط بين بدء كل نافذة
print(f"\n✅ تم بدء {number_of_windows} نافذة بشكل متوازي")
print("⏳ في انتظار العثور على موعد متاح...\n")
# انتظار حتى يتم العثور على موعد أو إيقاف البرنامج
try:
while not appointment_found_global.is_set():
time.sleep(1)
except KeyboardInterrupt:
logging.info("تم إيقاف البوت بواسطة المستخدم")
print("\n⚠️ تم إيقاف البوت بواسطة المستخدم")
appointment_found_global.set()
# انتظار انتهاء جميع الخيوط (سيتم إغلاق المتصفحات تلقائياً عند انتهاء كل thread)
print("\n⏳ انتظار انتهاء جميع النوافذ...")
logging.info("انتظار انتهاء جميع النوافذ...")
for thread in threads:
thread.join(timeout=10) # زيادة timeout إلى 10 ثواني
if appointment_found_global.is_set():
print("\n" + "="*50)
print("🎉 تم العثور على موعد متاح! 🎉")
print("="*50)
except (UnsupportedCountryError, LoginError) as e:
logging.error(f"خطأ: {e}")
print(f"\n❌ خطأ: {e}")
sys.exit(1)
except KeyboardInterrupt:
logging.info("تم إيقاف البوت بواسطة المستخدم")
print("\n⚠️ تم إيقاف البوت بواسطة المستخدم")
sys.exit(0)
except Exception as e:
logging.exception(f"حدث خطأ غير متوقع: {e}")
print(f"\n❌ حدث خطأ غير متوقع: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()