Forráskód Böngészése

feat: support cloud configuration fetching and dynamic auto-reload via VSCloudApi

jerry 8 órája
szülő
commit
0d51d65959

+ 571 - 403
booker_order.py → booker.py

@@ -1,403 +1,571 @@
-import os
-import time
-import json
-import threading
-import random
-from datetime import datetime
-from typing import List, Dict, Callable
-
-from vs_types import GroupConfig, VSPlgConfig, Task, VSQueryResult, AppointmentType, AvailabilityStatus
-from vs_plg_factory import VSPlgFactory 
-from toolkit.thread_pool import ThreadPool 
-from toolkit.vs_cloud_api import VSCloudApi
-from utils.safe_redis_cli import SafeRedisClient
-
-
-class OrderBookerGCO:
-    """
-    绑定模式 (订单自带账号):
-    - 按城市队列维护热机配额。
-    - 绝对的 1 对 1 关系:一个实例绑定一个云端订单。
-    - 预订成功后,实例立即销毁。
-    """
-    def __init__(self, cfg: GroupConfig, redis_conf: Dict, logger: Callable[[str], None] = None):
-        self.m_cfg = cfg
-        self.m_factory = VSPlgFactory()
-        self.m_logger = logger
-        self.m_tasks: List[Task] = []
-        self.m_lock = threading.RLock()
-        self.m_stop_event = threading.Event()
-        self.redis_client = SafeRedisClient(redis_conf, self.m_logger)
-        self.m_task_data_cache: Dict[str, dict] = {}
-        self.m_tracker_key = f"vs:worker:tasks_tracker:{self.m_cfg.identifier}"
-        self.heartbeat_ttl = 2*60.0
-
-    def _log(self, message):
-        if self.m_logger:
-            self.m_logger(f'[ORDER-BOOKER] [{self.m_cfg.identifier}] {message}')
-        else:
-            print(f'[ORDER-BOOKER] [{self.m_cfg.identifier}] {message}')
-
-    def start(self):
-        if not self.m_cfg.enable:
-            return
-        self._log("Starting Order Booker...")
-        plugin_name = self.m_cfg.plugin_config.plugin_name
-        class_name = "".join(part.title() for part in plugin_name.split('_'))
-        plugin_path = os.path.join(self.m_cfg.plugin_config.lib_path, self.m_cfg.plugin_config.plugin_bin)
-        self.m_factory.register_plugin(plugin_name, plugin_path, class_name)
-
-        threading.Thread(target=self._booking_trigger_loop, daemon=True).start()
-        threading.Thread(target=self._creator_loop, daemon=True).start()
-        threading.Thread(target=self._maintain_loop, daemon=True).start()
-        threading.Thread(target=self._cache_refresh_loop, daemon=True).start()
-
-    def stop(self):
-        self._log("Stopping Booker...")
-        self.m_stop_event.set()
-        self._cleanup_all_tasks("booker stop")
-        
-    def update_config(self, new_cfg: GroupConfig):
-        """
-        动态更新配置
-        """
-        with self.m_lock:
-            if self.m_cfg.enable and not new_cfg.enable:
-                self._log("Config dynamically updated: Group DISABLED. Will stop creating new tasks.")
-            elif not self.m_cfg.enable and new_cfg.enable:
-                self._log("Config dynamically updated: Group ENABLED.")
-            else:
-                self._log("Config dynamically updated: Parameters refreshed.")
-                
-            self.m_cfg = new_cfg
-
-    def _cleanup_task(self, task: Task, reason: str = ""):
-        try:
-            if task and task.instance:
-                task.instance.cleanup()
-                self._log(f"🧹 Cleaned up instance for task={task.task_ref}. Reason: {reason}")
-        except Exception as e:
-            self._log(f"Cleanup failed for task={task.task_ref}. Reason: {reason}. Error: {e}")
-
-    def _remove_task(self, task: Task, reason: str = "", cleanup: bool = True):
-        removed = False
-        with self.m_lock:
-            if task in self.m_tasks:
-                self.m_tasks.remove(task)
-                removed = True
-            task_id = task.task_ref
-            self.m_task_data_cache.pop(task_id, None)
-                
-        if cleanup and removed:
-            self._cleanup_task(task, reason)
-        return removed
-
-    def _cleanup_all_tasks(self, reason: str = ""):
-        with self.m_lock:
-            tasks = list(self.m_tasks)
-            self.m_tasks.clear()
-            self.m_task_data_cache.clear()
-        for task in tasks:
-            self._cleanup_task(task, reason)
-
-    def _get_redis_key(self, routing_key: str) -> str:
-        return f"vs:signal:{routing_key}"
-    
-    def _is_within_active_hours(self) -> bool:
-        """
-        判断当前是否在允许创建实例的时间段内
-        """
-        start_str = self.m_cfg.active_time_start
-        end_str = self.m_cfg.active_time_end
-        current_bj_time = datetime.utcnow().time()
-        start_time = datetime.strptime(start_str, "%H:%M").time()
-        end_time = datetime.strptime(end_str, "%H:%M").time()
-        return start_time <= current_bj_time <= end_time
-            
-    def _maintain_loop(self):
-        self._log("Maintain loop started.")
-        while not self.m_stop_event.is_set():
-            try:
-                time.sleep(1)
-                with self.m_lock:
-                    tasks_to_check = list(self.m_tasks)
-                    
-                if not tasks_to_check:
-                    continue
-                
-                healthy_tasks = []
-                dead_tasks = []
-                now = time.time()
-                for t in tasks_to_check:
-                    if now >= t.next_remote_ping:
-                        t.instance.keep_alive()
-                        if t.instance.health_check(): 
-                            healthy_tasks.append(t)
-                            t.next_remote_ping = now + random.gauss(self.m_cfg.booker.keep_alive, 5)
-                        else:
-                            dead_tasks.append(t)
-                    else:
-                        healthy_tasks.append(t)
-                
-                if healthy_tasks:
-                    new_deadline = time.time() + self.heartbeat_ttl
-                    mapping = {str(t.task_ref): new_deadline for t in healthy_tasks}
-                    self.redis_client.bulk_zadd(self.m_tracker_key, mapping)
-
-                if dead_tasks:
-                    mapping = {str(t.task_ref): 0 for t in dead_tasks}
-                    self.redis_client.bulk_zadd(self.m_tracker_key, mapping)
-                
-                if dead_tasks:
-                    with self.m_lock:
-                        current_tasks = list(self.m_tasks)
-                        self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
-                    for t in dead_tasks:
-                        if t in current_tasks:
-                            self._cleanup_task(t, "unhealthy or keep-alive failed")
-                else:
-                    with self.m_lock:
-                        self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
-            except Exception as e:
-                self._log(f'Maintain loop exception:{e}')
-
-    def _cache_refresh_loop(self):
-        self._log("Cache refresh loop started.")
-        refresh_interval = 15 * 60
-        
-        while not self.m_stop_event.is_set():
-            try:
-                time.sleep(1)
-                with self.m_lock:
-                    tasks_to_check = {
-                        tid: data.get('_last_refresh', 0) 
-                        for tid, data in self.m_task_data_cache.items()
-                    }
-                
-                if not tasks_to_check:
-                    continue
-                
-                now = time.time()
-                for tid, last_refresh in tasks_to_check.items():
-                    if now - last_refresh >= refresh_interval:
-                        fresh_data = VSCloudApi.Instance().get_vas_task(tid)
-                        if fresh_data:
-                            fresh_data['_last_refresh'] = time.time()
-                            
-                            with self.m_lock:
-                                if tid in self.m_task_data_cache:
-                                    self.m_task_data_cache[tid] = fresh_data
-                        time.sleep(0.5)
-            except Exception as e:
-                self._log(f'Cache refresh loop exception: {e}')
-    
-    def _is_date_of_interest(self, task, query_result: VSQueryResult) -> bool:
-        """
-        判断 query_result 中的可用日期,是否在 task 的意向日期范围内。
-        """
-        if query_result.availability_status != AvailabilityStatus.Available:
-            return True
-        task_id = task.task_ref
-        task_data = self.m_task_data_cache.get(str(task_id), {})
-        user_input = task_data.get('user_inputs', {})
-        expected_end_date = (
-            user_input.get('expected_end_date')
-            or '2100-01-01'
-        )
-        available_date = query_result.earliest_date
-        dt = available_date.strftime("%Y-%m-%d")
-        return dt <= expected_end_date
-
-    def _booking_trigger_loop(self):
-        self._log("Trigger loop started.")
-        while not self.m_stop_event.is_set():
-            try:
-                time.sleep(0.1)
-                now = time.time()
-                for apt_type in self.m_cfg.appointment_types:
-                    redis_key = self._get_redis_key(apt_type.routing_key)
-                    raw_data = self.redis_client.get(redis_key)
-                    if not raw_data:
-                        continue
-                    try:
-                        data = json.loads(raw_data)
-                        query_result = VSQueryResult.model_validate(data['query_result'])
-                        query_result.apt_type = AppointmentType.model_validate(data['apt_type'])
-                    except Exception as parse_err:
-                        self._log(f"Data parsing error for {redis_key}: {parse_err}. Deleting corrupted signal.")
-                        self.redis_client.delete(redis_key)
-                        continue
-                    
-                    matching_tasks = []
-                    with self.m_lock:
-                        for task in self.m_tasks:
-                            if now < task.next_run or not task.book_allowed:
-                                continue
-                            if apt_type.routing_key not in task.acceptable_routing_keys:
-                                continue
-                            if not self._is_date_of_interest(task, query_result):
-                                continue
-                            
-                            task.next_run = now + self.m_cfg.booker.booking_cooldown
-                            matching_tasks.append(task)
-                            
-                    if matching_tasks:
-                        threads = []
-                        for task in matching_tasks:
-                            self._log(f"🚀 Triggering BOOK for {apt_type.routing_key} | Order Ref: {task.task_ref}")
-                            t = threading.Thread(target=self._execute_book_job, args=(task, query_result))
-                            threads.append(t)
-                            t.start()
-                        for t in threads:
-                            t.join() 
-            except Exception as e:
-                self._log(f"Booking trigger loop exception: {e}")
-
-    def _execute_book_job(self, task: Task, query_result: VSQueryResult):
-        task_id = task.task_ref
-        task_data = None
-
-        try:
-            with self.m_lock:
-                task_data = self.m_task_data_cache.get(str(task_id))
-            if not task_data or task_data.get('status') in ['grabbed', 'pause', 'completed', 'cancelled']:
-                self._log(f"Bound Task={task_id} is no longer valid or already processed. Removing instance.")
-                self._remove_task(task, "bound task no longer valid")
-                self.redis_client.zrem(self.m_tracker_key, task_id)
-            
-            order_id = task_data.get('order_id')
-            user_input = task_data.get('user_inputs', {})
-            book_res = task.instance.book(query_result, user_input)
-
-            if book_res.success:
-                self._log(f"✅ BOOK SUCCESS! Order: {order_id}. Destroying instance.")
-                grab_info = {
-                    "account": book_res.account,
-                    "session_id": book_res.session_id,
-                    "urn": book_res.urn,
-                    "slot_date": book_res.book_date,
-                    "slot_time": book_res.book_time,
-                    "timestamp": int(time.time()),
-                    "payment_link": book_res.payment_link
-                }
-                
-                def _update_cloud_success():
-                    try:
-                        VSCloudApi.Instance().update_vas_task(str(task_id), {"status": "grabbed", "grabbed_history": grab_info})
-                        push_content = (
-                            f"🎉 【预定成功通知】\n"
-                            f"━━━━━━━━━━━━━━━\n"
-                            f"订单编号: {order_id}\n"
-                            f"预约账号: {book_res.account}\n"
-                            f"预约日期: {book_res.book_date}\n"
-                            f"预约时间: {book_res.book_time}\n"
-                            f"预约编号: {book_res.urn}\n"
-                            f"支付链接: {book_res.payment_link if book_res.payment_link else '无需支付/暂无'}\n"
-                            f"━━━━━━━━━━━━━━━\n"
-                        )
-                        VSCloudApi.Instance().push_weixin_text(push_content)
-                    except Exception as e:
-                        self._log(f"Failed to update success state to cloud: {e}")
-                
-                ThreadPool.getInstance().enqueue(_update_cloud_success)
-                self.redis_client.zrem(self.m_tracker_key, task_id)
-                self._remove_task(task, "booking success")
-            else:
-                self._log(f"❌ BOOK FAILED for Order: {order_id}. Will retry on next signal.")
-
-        except Exception as e:
-            err_str = str(e)
-            self._log(f"Exception during booking: {err_str}")
-            rate_limited_indicators = [
-                "42901" in err_str,
-                "Rate limited" in err_str
-            ]
-            if any(rate_limited_indicators):
-                self._remove_task(task, "booking rate limited")
-                    
-    def _creator_loop(self):
-        self._log("Creator loop started.")
-        while not self.m_stop_event.wait(1.0):
-            try:
-                if not self._is_within_active_hours():
-                    continue
-                for apt in self.m_cfg.appointment_types:
-                    r_key = apt.routing_key
-                    with self.m_lock:
-                        active = sum(1 for t in self.m_tasks if t.source_queue == r_key)
-                        target = self.m_cfg.booker.target_instances
-                    if active < target:
-                        self._spawn_worker(r_key)
-            except Exception as e:
-                self._log(f'Creator loop exception:{e}')
-
-    def _spawn_worker(self, target_routing_key: str):
-        instance = None
-        success = False
-        task_id = None
-        try:
-            queue_name = f"auto.{target_routing_key}"
-            task_data = VSCloudApi.Instance().get_vas_task_pop(queue_name)
-            if not task_data:
-                return 
-            
-            task_id = task_data['id']
-            
-            with self.m_lock:
-                self.m_task_data_cache[str(task_id)] = task_data
-            
-            self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + 8*60.0})
-            user_inputs = task_data.get('user_inputs', {})
-            
-            plg_cfg = VSPlgConfig()
-            plg_cfg.debug = self.m_cfg.debug
-            plg_cfg.free_config = self.m_cfg.free_config
-            plg_cfg.session_max_life = self.m_cfg.session_max_life
-            plg_cfg.account.username = user_inputs.get("username", "")
-            plg_cfg.account.password = user_inputs.get("password", "")
-            if not plg_cfg.account.username:
-                return
-            
-            acceptable_keys = [target_routing_key]
-            if self.m_cfg.need_proxy:
-                proxy = VSCloudApi.Instance().get_next_proxy(self.m_cfg.proxy_pool, self.m_cfg.proxy_cd)
-                plg_cfg.proxy = type(plg_cfg.proxy)(**proxy)
-
-            instance = self.m_factory.create(self.m_cfg.identifier, self.m_cfg.plugin_config.plugin_name)
-            instance.set_log(self.m_logger)
-            instance.set_config(plg_cfg)
-            instance.create_session() 
-            
-            with self.m_lock:
-                self.m_tasks.append(
-                    Task(
-                        instance=instance,
-                        next_run=time.time(), 
-                        task_ref=task_id,
-                        acceptable_routing_keys=acceptable_keys, 
-                        source_queue=target_routing_key,
-                        book_allowed=True,
-                        next_remote_ping=time.time() + random.gauss(self.m_cfg.booker.keep_alive, 5)
-                    )
-                )
-            success = True             
-            self._log(f"+++ Order Booker spawned: {plg_cfg.account.username} (Target: {acceptable_keys})")
-        except Exception as e:
-            err_str = str(e)
-            self._log(f"Order Booker spawn failed: {err_str}")
-            rate_limited_indicators = [
-                "42901" in err_str,
-                "Rate limited" in err_str
-            ]
-            if any(rate_limited_indicators):
-                if task_id is not None:
-                    self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + self.m_cfg.login_backoff})  
-        
-        finally:
-            if not success:
-                if task_id:
-                    with self.m_lock:
-                        self.m_task_data_cache.pop(str(task_id), None)
-                if instance:
-                    instance.cleanup()
-                        
+import os
+import time
+import json
+import threading
+import random
+from datetime import datetime
+from typing import List, Dict, Callable, Optional
+
+from vs_types import GroupConfig, VSPlgConfig, Task, VSQueryResult, AppointmentType, AvailabilityStatus
+from vs_plg_factory import VSPlgFactory
+from toolkit.thread_pool import ThreadPool
+from toolkit.vs_cloud_api import VSCloudApi
+from utils.safe_redis_cli import SafeRedisClient
+
+
+class BaseBookerGCO:
+    """
+    Booker 基类,封装公共的基础设施与通用的生命周期管理、逻辑循环等。
+    """
+    TAG = "BOOKER"
+
+    def __init__(self, cfg: GroupConfig, redis_conf: Dict, logger: Callable[[str], None] = None):
+        self.m_cfg = cfg
+        self.m_factory = VSPlgFactory()
+        self.m_logger = logger
+        self.m_tasks: List[Task] = []
+        self.m_lock = threading.RLock()
+        self.m_stop_event = threading.Event()
+        self.redis_client = SafeRedisClient(redis_conf, self.m_logger)
+        self.m_tracker_key = f"vs:worker:tasks_tracker:{self.m_cfg.identifier}"
+
+    def _log(self, message: str):
+        prefix = f'[{self.TAG}] [{self.m_cfg.identifier}]'
+        if self.m_logger:
+            self.m_logger(f'{prefix} {message}')
+        else:
+            print(f'{prefix} {message}')
+
+    def start(self):
+        if not self.m_cfg.enable:
+            return
+        self._log(f"Starting {self.TAG}...")
+        plugin_name = self.m_cfg.plugin_config.plugin_name
+        class_name = "".join(part.title() for part in plugin_name.split('_'))
+        plugin_path = os.path.join(self.m_cfg.plugin_config.lib_path, self.m_cfg.plugin_config.plugin_bin)
+        self.m_factory.register_plugin(plugin_name, plugin_path, class_name)
+
+        threading.Thread(target=self._booking_trigger_loop, daemon=True).start()
+        threading.Thread(target=self._creator_loop, daemon=True).start()
+        threading.Thread(target=self._maintain_loop, daemon=True).start()
+        self._start_additional_threads()
+
+    def _start_additional_threads(self):
+        """子类扩展线程的 Hook"""
+        pass
+
+    def stop(self):
+        self._log("Stopping Booker...")
+        self.m_stop_event.set()
+        self._cleanup_all_tasks("booker stop")
+
+    def update_config(self, new_cfg: GroupConfig):
+        """动态更新配置"""
+        with self.m_lock:
+            if self.m_cfg.enable and not new_cfg.enable:
+                self._log("Config dynamically updated: Group DISABLED. Will stop creating new tasks.")
+            elif not self.m_cfg.enable and new_cfg.enable:
+                self._log("Config dynamically updated: Group ENABLED.")
+            else:
+                self._log("Config dynamically updated: Parameters refreshed.")
+                
+            self.m_cfg = new_cfg
+
+    def _cleanup_task(self, task: Task, reason: str = ""):
+        try:
+            if task and task.instance:
+                task.instance.cleanup()
+                ref_str = f" for task={task.task_ref}" if task.task_ref else ""
+                self._log(f"🧹 Cleaned up instance{ref_str}. Reason: {reason}")
+        except Exception as e:
+            self._log(f"Cleanup failed for instance. Reason: {reason}. Error: {e}")
+
+    def _remove_task(self, task: Task, reason: str = "", cleanup: bool = True):
+        removed = False
+        with self.m_lock:
+            if task in self.m_tasks:
+                self.m_tasks.remove(task)
+                removed = True
+            self._on_task_removed(task)
+                
+        if cleanup and removed:
+            self._cleanup_task(task, reason)
+        return removed
+
+    def _on_task_removed(self, task: Task):
+        """子类在 task 被移除时的自定义 Hook(如清理缓存)"""
+        pass
+
+    def _cleanup_all_tasks(self, reason: str = ""):
+        with self.m_lock:
+            tasks = list(self.m_tasks)
+            self.m_tasks.clear()
+            self._on_all_tasks_cleaned()
+        for task in tasks:
+            self._cleanup_task(task, reason)
+
+    def _on_all_tasks_cleaned(self):
+        """子类在所有 task 被清空时的自定义 Hook"""
+        pass
+
+    def _get_redis_key(self, routing_key: str) -> str:
+        return f"vs:signal:{routing_key}"
+
+    def _is_within_active_hours(self) -> bool:
+        """判断当前是否在允许创建实例的时间段内"""
+        start_str = self.m_cfg.active_time_start
+        end_str = self.m_cfg.active_time_end
+        current_bj_time = datetime.utcnow().time()
+        start_time = datetime.strptime(start_str, "%H:%M").time()
+        end_time = datetime.strptime(end_str, "%H:%M").time()
+        return start_time <= current_bj_time <= end_time
+
+    def _maintain_loop(self):
+        self._log("Maintain loop started.")
+        while not self.m_stop_event.is_set():
+            try:
+                time.sleep(1.0)
+                with self.m_lock:
+                    tasks_to_check = list(self.m_tasks)
+                
+                if not tasks_to_check:
+                    continue
+                
+                dead_tasks = []
+                healthy_tasks = []
+                now = time.time()
+                for t in tasks_to_check:
+                    if now >= t.next_remote_ping:
+                        t.instance.keep_alive()
+                        if t.instance.health_check():
+                            healthy_tasks.append(t)
+                            t.next_remote_ping = now + random.gauss(self.m_cfg.booker.keep_alive, 5)
+                        else:
+                            dead_tasks.append(t)
+                    else:
+                        healthy_tasks.append(t)
+                
+                self._on_maintain_ping(healthy_tasks, dead_tasks)
+
+                if dead_tasks:
+                    with self.m_lock:
+                        current_tasks = list(self.m_tasks)
+                        self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
+                    for t in dead_tasks:
+                        if t in current_tasks:
+                            self._cleanup_task(t, "unhealthy or keep-alive failed")
+                else:
+                    with self.m_lock:
+                        self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
+            except Exception as e:
+                self._log(f'Maintain loop exception: {e}')
+
+    def _on_maintain_ping(self, healthy_tasks: List[Task], dead_tasks: List[Task]):
+        """子类维护循环中对于健康/异常 Task 的额外处理"""
+        pass
+
+    def _is_date_of_interest(self, task: Task, query_result: VSQueryResult) -> bool:
+        """判断 query_result 中的可用日期是否在 task 意向范围内(默认 True,Order 模式重写)"""
+        return True
+
+    def _booking_trigger_loop(self):
+        self._log("Trigger loop started.")
+        while not self.m_stop_event.is_set():
+            try:
+                time.sleep(0.1)
+                now = time.time()
+                for apt_type in self.m_cfg.appointment_types:
+                    redis_key = self._get_redis_key(apt_type.routing_key)
+                    raw_data = self.redis_client.get(redis_key)
+                    if not raw_data:
+                        continue
+                    try:
+                        data = json.loads(raw_data)
+                        query_result = VSQueryResult.model_validate(data['query_result'])
+                        query_result.apt_type = AppointmentType.model_validate(data['apt_type'])
+                    except Exception as parse_err:
+                        self._log(f"Data parsing error for {redis_key}: {parse_err}. Deleting corrupted signal.")
+                        self.redis_client.delete(redis_key)
+                        continue
+                    
+                    matching_tasks = []
+                    with self.m_lock:
+                        for task in self.m_tasks:
+                            if now < task.next_run or not task.book_allowed:
+                                continue
+                            if apt_type.routing_key not in task.acceptable_routing_keys:
+                                continue
+                            if not self._is_date_of_interest(task, query_result):
+                                continue
+                            
+                            task.next_run = now + self.m_cfg.booker.booking_cooldown
+                            matching_tasks.append(task)
+                            
+                    if matching_tasks:
+                        threads = []
+                        for task in matching_tasks:
+                            self._log(f"🚀 Triggering BOOK for {apt_type.routing_key} | Order Ref: {task.task_ref}")
+                            t = threading.Thread(target=self._execute_book_job, args=(task, query_result))
+                            threads.append(t)
+                            t.start()
+                        
+                        for t in threads:
+                            t.join() 
+                    
+            except Exception as e:
+                self._log(f"Booking trigger loop exception: {e}")
+
+    def _execute_book_job(self, task: Task, query_result: VSQueryResult):
+        raise NotImplementedError
+
+    def _creator_loop(self):
+        raise NotImplementedError
+
+    def _push_success_notification(self, task_id, order_id, book_res):
+        """成功落单后同步云端与推送微信通知的公共方法"""
+        grab_info = {
+            "account": book_res.account,
+            "session_id": book_res.session_id,
+            "urn": book_res.urn,
+            "slot_date": book_res.book_date,
+            "slot_time": book_res.book_time,
+            "timestamp": int(time.time()),
+            "payment_link": book_res.payment_link
+        }
+        
+        def _update_cloud_success():
+            try:
+                VSCloudApi.Instance().update_vas_task(str(task_id), {"status": "grabbed", "grabbed_history": grab_info})
+                push_content = (
+                    f"🎉 【预定成功通知】\n"
+                    f"━━━━━━━━━━━━━━━\n"
+                    f"订单编号: {order_id}\n"
+                    f"预约账号: {book_res.account}\n"
+                    f"预约日期: {book_res.book_date}\n"
+                    f"预约时间: {book_res.book_time}\n"
+                    f"预约编号: {book_res.urn}\n"
+                    f"支付链接: {book_res.payment_link if book_res.payment_link else '无需支付/暂无'}\n"
+                    f"━━━━━━━━━━━━━━━\n"
+                )
+                VSCloudApi.Instance().push_weixin_text(push_content)
+            except Exception as e:
+                self._log(f"Failed to update success state to cloud: {e}")
+
+        ThreadPool.getInstance().enqueue(_update_cloud_success)
+        self.redis_client.zrem(self.m_tracker_key, task_id)
+
+
+class BuiltinBookerGCO(BaseBookerGCO):
+    """
+    非绑定模式 (公共内置账号池):
+    - 只维护全局 target_instances 数量的实例。
+    - 所有实例热机等待,发现信号后临时去云端 Pop 订单。
+    """
+    TAG = "BUILTIN-BOOKER"
+
+    def _creator_loop(self):
+        self._log("Creator loop started.")
+        while not self.m_stop_event.wait(1.0):
+            try:
+                if not self._is_within_active_hours():
+                    continue
+                
+                with self.m_lock:
+                    current = len(self.m_tasks)
+                    target = self.m_cfg.booker.target_instances
+                if current < target:
+                    self._spawn_worker()
+            except Exception as e:
+                self._log(f'Creator loop exception: {e}')
+
+    def _spawn_worker(self):
+        instance = None
+        success = False
+        plg_cfg = None
+        
+        try:
+            plg_cfg = VSPlgConfig()
+            plg_cfg.debug = self.m_cfg.debug
+            plg_cfg.free_config = self.m_cfg.free_config
+            plg_cfg.session_max_life = self.m_cfg.session_max_life
+
+            if self.m_cfg.need_account:
+                acc = VSCloudApi.Instance().get_next_account(self.m_cfg.booker.account_pool_id, self.m_cfg.booker.account_cd)
+                plg_cfg.account = type(plg_cfg.account)(**acc)
+
+            if self.m_cfg.need_proxy:
+                proxy = VSCloudApi.Instance().get_next_proxy(self.m_cfg.proxy_pool, self.m_cfg.proxy_cd)
+                plg_cfg.proxy = type(plg_cfg.proxy)(**proxy)
+
+            instance = self.m_factory.create(self.m_cfg.identifier, self.m_cfg.plugin_config.plugin_name)
+            instance.set_log(self.m_logger)
+            instance.set_config(plg_cfg)
+            instance.create_session()
+            success = True
+            with self.m_lock:
+                all_keys = [apt.routing_key for apt in self.m_cfg.appointment_types]
+                self.m_tasks.append(
+                    Task(
+                        instance=instance,
+                        next_run=time.time(), 
+                        task_ref=None,
+                        acceptable_routing_keys=all_keys,
+                        source_queue="built-in",
+                        book_allowed=True,
+                        next_remote_ping=time.time() + random.gauss(self.m_cfg.booker.keep_alive, 5) 
+                    )
+                )
+            success = True
+            self._log(f"+++ Built-in Booker spawned: {plg_cfg.account.username}")
+        except Exception as e:
+            err_str = str(e)
+            self._log(f"Spawn failed: {err_str}")
+            rate_limited_indicators = [
+                "42901" in err_str,
+                "Rate limited" in err_str
+            ]
+            if any(rate_limited_indicators):
+                if plg_cfg and plg_cfg.account.username != "Guest":
+                    VSCloudApi.lock_account(plg_cfg.account.id, self.m_cfg.login_backoff)
+        finally:
+            if not success:
+                if instance:
+                    instance.cleanup()
+
+    def _execute_book_job(self, task: Task, query_result: VSQueryResult):
+        queue_name = f"auto.{query_result.apt_type.routing_key}"
+
+        task_id = None
+        task_data = None
+        
+        try:
+            task_data = VSCloudApi.Instance().get_vas_task_pop(queue_name)
+            if not task_data:
+                return 
+            task_id = task_data['id']
+            order_id = task_data.get('order_id')
+            self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + 30.0})
+                        
+            user_input = task_data.get('user_inputs', {})
+            book_res = task.instance.book(query_result, user_input)
+            
+            if book_res.success:
+                self._log(f"✅ BOOK SUCCESS! Order: {order_id}")
+                self._push_success_notification(task_id, order_id, book_res)
+                
+                task.successful_bookings += 1
+                max_b = self.m_cfg.booker.max_bookings_per_account
+                if max_b > 0 and task.successful_bookings >= max_b:
+                    self._log(f"Account reached max bookings ({max_b}). Destroying instance.")
+                    self._remove_task(task, "max bookings reached")
+            else:
+                self._log(f"❌ BOOK FAILED for Order: {order_id}")
+        except Exception as e:
+            err_str = str(e)
+            self._log(f"Exception during booking: {err_str}")
+            rate_limited_indicators = [
+                "42901" in err_str,
+                "Rate limited" in err_str
+            ]
+            if any(rate_limited_indicators):
+                self._remove_task(task, "booking rate limited")
+
+
+class OrderBookerGCO(BaseBookerGCO):
+    """
+    绑定模式 (订单自带账号):
+    - 按城市队列维护热机配额。
+    - 绝对的 1 对 1 关系:一个实例绑定一个云端订单。
+    - 预订成功后,实例立即销毁。
+    """
+    TAG = "ORDER-BOOKER"
+
+    def __init__(self, cfg: GroupConfig, redis_conf: Dict, logger: Callable[[str], None] = None):
+        super().__init__(cfg, redis_conf, logger)
+        self.m_task_data_cache: Dict[str, dict] = {}
+        self.heartbeat_ttl = 2 * 60.0
+
+    def _start_additional_threads(self):
+        threading.Thread(target=self._cache_refresh_loop, daemon=True).start()
+
+    def _on_task_removed(self, task: Task):
+        task_id = task.task_ref
+        if task_id:
+            self.m_task_data_cache.pop(str(task_id), None)
+
+    def _on_all_tasks_cleaned(self):
+        self.m_task_data_cache.clear()
+
+    def _on_maintain_ping(self, healthy_tasks: List[Task], dead_tasks: List[Task]):
+        if healthy_tasks:
+            new_deadline = time.time() + self.heartbeat_ttl
+            mapping = {str(t.task_ref): new_deadline for t in healthy_tasks}
+            self.redis_client.bulk_zadd(self.m_tracker_key, mapping)
+
+        if dead_tasks:
+            mapping = {str(t.task_ref): 0 for t in dead_tasks}
+            self.redis_client.bulk_zadd(self.m_tracker_key, mapping)
+
+    def _cache_refresh_loop(self):
+        self._log("Cache refresh loop started.")
+        refresh_interval = 15 * 60
+        
+        while not self.m_stop_event.is_set():
+            try:
+                time.sleep(1)
+                with self.m_lock:
+                    tasks_to_check = {
+                        tid: data.get('_last_refresh', 0) 
+                        for tid, data in self.m_task_data_cache.items()
+                    }
+                
+                if not tasks_to_check:
+                    continue
+                
+                now = time.time()
+                for tid, last_refresh in tasks_to_check.items():
+                    if now - last_refresh >= refresh_interval:
+                        fresh_data = VSCloudApi.Instance().get_vas_task(tid)
+                        if fresh_data:
+                            fresh_data['_last_refresh'] = time.time()
+                            
+                            with self.m_lock:
+                                if tid in self.m_task_data_cache:
+                                    self.m_task_data_cache[tid] = fresh_data
+                        time.sleep(0.5)
+            except Exception as e:
+                self._log(f'Cache refresh loop exception: {e}')
+
+    def _is_date_of_interest(self, task: Task, query_result: VSQueryResult) -> bool:
+        if query_result.availability_status != AvailabilityStatus.Available:
+            return True
+        task_id = task.task_ref
+        task_data = self.m_task_data_cache.get(str(task_id), {})
+        user_input = task_data.get('user_inputs', {})
+        expected_end_date = (
+            user_input.get('expected_end_date')
+            or '2100-01-01'
+        )
+        available_date = query_result.earliest_date
+        dt = available_date.strftime("%Y-%m-%d")
+        return dt <= expected_end_date
+
+    def _execute_book_job(self, task: Task, query_result: VSQueryResult):
+        task_id = task.task_ref
+        task_data = None
+
+        try:
+            with self.m_lock:
+                task_data = self.m_task_data_cache.get(str(task_id))
+            if not task_data or task_data.get('status') in ['grabbed', 'pause', 'completed', 'cancelled']:
+                self._log(f"Bound Task={task_id} is no longer valid or already processed. Removing instance.")
+                self._remove_task(task, "bound task no longer valid")
+                self.redis_client.zrem(self.m_tracker_key, task_id)
+                return
+            
+            order_id = task_data.get('order_id')
+            user_input = task_data.get('user_inputs', {})
+            book_res = task.instance.book(query_result, user_input)
+
+            if book_res.success:
+                self._log(f"✅ BOOK SUCCESS! Order: {order_id}. Destroying instance.")
+                self._push_success_notification(task_id, order_id, book_res)
+                self._remove_task(task, "booking success")
+            else:
+                self._log(f"❌ BOOK FAILED for Order: {order_id}. Will retry on next signal.")
+
+        except Exception as e:
+            err_str = str(e)
+            self._log(f"Exception during booking: {err_str}")
+            rate_limited_indicators = [
+                "42901" in err_str,
+                "Rate limited" in err_str
+            ]
+            if any(rate_limited_indicators):
+                self._remove_task(task, "booking rate limited")
+
+    def _creator_loop(self):
+        self._log("Creator loop started.")
+        while not self.m_stop_event.wait(1.0):
+            try:
+                if not self._is_within_active_hours():
+                    continue
+                for apt in self.m_cfg.appointment_types:
+                    r_key = apt.routing_key
+                    with self.m_lock:
+                        active = sum(1 for t in self.m_tasks if t.source_queue == r_key)
+                        target = self.m_cfg.booker.target_instances
+                    if active < target:
+                        self._spawn_worker(r_key)
+            except Exception as e:
+                self._log(f'Creator loop exception:{e}')
+
+    def _spawn_worker(self, target_routing_key: str):
+        instance = None
+        success = False
+        task_id = None
+        try:
+            queue_name = f"auto.{target_routing_key}"
+            task_data = VSCloudApi.Instance().get_vas_task_pop(queue_name)
+            if not task_data:
+                return 
+            
+            task_id = task_data['id']
+            
+            with self.m_lock:
+                self.m_task_data_cache[str(task_id)] = task_data
+            
+            self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + 8*60.0})
+            user_inputs = task_data.get('user_inputs', {})
+            
+            plg_cfg = VSPlgConfig()
+            plg_cfg.debug = self.m_cfg.debug
+            plg_cfg.free_config = self.m_cfg.free_config
+            plg_cfg.session_max_life = self.m_cfg.session_max_life
+            plg_cfg.account.username = user_inputs.get("username", "")
+            plg_cfg.account.password = user_inputs.get("password", "")
+            if not plg_cfg.account.username:
+                return
+            
+            acceptable_keys = [target_routing_key]
+            if self.m_cfg.need_proxy:
+                proxy = VSCloudApi.Instance().get_next_proxy(self.m_cfg.proxy_pool, self.m_cfg.proxy_cd)
+                plg_cfg.proxy = type(plg_cfg.proxy)(**proxy)
+
+            instance = self.m_factory.create(self.m_cfg.identifier, self.m_cfg.plugin_config.plugin_name)
+            instance.set_log(self.m_logger)
+            instance.set_config(plg_cfg)
+            instance.create_session() 
+            
+            with self.m_lock:
+                self.m_tasks.append(
+                    Task(
+                        instance=instance,
+                        next_run=time.time(), 
+                        task_ref=task_id,
+                        acceptable_routing_keys=acceptable_keys, 
+                        source_queue=target_routing_key,
+                        book_allowed=True,
+                        next_remote_ping=time.time() + random.gauss(self.m_cfg.booker.keep_alive, 5)
+                    )
+                )
+            success = True             
+            self._log(f"+++ Order Booker spawned: {plg_cfg.account.username} (Target: {acceptable_keys})")
+        except Exception as e:
+            err_str = str(e)
+            self._log(f"Order Booker spawn failed: {err_str}")
+            rate_limited_indicators = [
+                "42901" in err_str,
+                "Rate limited" in err_str
+            ]
+            if any(rate_limited_indicators):
+                if task_id is not None:
+                    self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + self.m_cfg.login_backoff})  
+        
+        finally:
+            if not success:
+                if task_id:
+                    with self.m_lock:
+                        self.m_task_data_cache.pop(str(task_id), None)
+                if instance:
+                    instance.cleanup()

+ 0 - 326
booker_builtin.py

@@ -1,326 +0,0 @@
-import os
-import time
-import json
-import threading
-import random
-from datetime import datetime
-from typing import List, Dict, Callable
-
-from vs_types import GroupConfig, VSPlgConfig, Task, VSQueryResult, AppointmentType
-from vs_plg_factory import VSPlgFactory 
-from toolkit.thread_pool import ThreadPool 
-from toolkit.vs_cloud_api import VSCloudApi
-from utils.safe_redis_cli import SafeRedisClient
-
-
-class BuiltinBookerGCO:
-    """
-    非绑定模式 (公共内置账号池):
-    - 只维护全局 target_instances 数量的实例。
-    - 所有实例热机等待,发现信号后临时去云端 Pop 订单。
-    """
-    def __init__(self, cfg: GroupConfig, redis_conf: Dict, logger: Callable[[str], None] = None):
-        self.m_cfg = cfg
-        self.m_factory = VSPlgFactory()
-        self.m_logger = logger
-        self.m_tasks: List[Task] = []
-        self.m_lock = threading.RLock()
-        self.m_stop_event = threading.Event()
-        self.redis_client = SafeRedisClient(redis_conf, self.m_logger)
-        self.m_tracker_key = f"vs:worker:tasks_tracker:{self.m_cfg.identifier}"
-
-    def _log(self, message):
-        if self.m_logger:
-            self.m_logger(f'[BUILTIN-BOOKER] [{self.m_cfg.identifier}] {message}')
-        else:
-            print(f'[BUILTIN-BOOKER] [{self.m_cfg.identifier}] {message}')
-
-    def start(self):
-        if not self.m_cfg.enable:
-            return
-        self._log("Starting Built-in Booker...")
-        plugin_name = self.m_cfg.plugin_config.plugin_name
-        class_name = "".join(part.title() for part in plugin_name.split('_'))
-        plugin_path = os.path.join(self.m_cfg.plugin_config.lib_path, self.m_cfg.plugin_config.plugin_bin)
-        self.m_factory.register_plugin(plugin_name, plugin_path, class_name)
-        threading.Thread(target=self._booking_trigger_loop, daemon=True).start()
-        threading.Thread(target=self._creator_loop, daemon=True).start()
-        threading.Thread(target=self._maintain_loop, daemon=True).start()
-
-    def stop(self):
-        self._log("Stopping Booker...")
-        self.m_stop_event.set()
-        self._cleanup_all_tasks("booker stop")
-        
-    def update_config(self, new_cfg: GroupConfig):
-        """
-        动态更新配置
-        """
-        with self.m_lock:
-            if self.m_cfg.enable and not new_cfg.enable:
-                self._log("Config dynamically updated: Group DISABLED. Will stop creating new tasks.")
-            elif not self.m_cfg.enable and new_cfg.enable:
-                self._log("Config dynamically updated: Group ENABLED.")
-            else:
-                self._log("Config dynamically updated: Parameters refreshed.")
-                
-            self.m_cfg = new_cfg
-
-    def _cleanup_task(self, task: Task, reason: str = ""):
-        try:
-            if task and task.instance:
-                task.instance.cleanup()
-                self._log(f"🧹 Cleaned up built-in instance. Reason: {reason}")
-        except Exception as e:
-            self._log(f"Cleanup failed for built-in instance. Reason: {reason}. Error: {e}")
-
-    def _remove_task(self, task: Task, reason: str = "", cleanup: bool = True):
-        removed = False
-        with self.m_lock:
-            if task in self.m_tasks:
-                self.m_tasks.remove(task)
-                removed = True
-        if cleanup and removed:
-            self._cleanup_task(task, reason)
-        return removed
-
-    def _cleanup_all_tasks(self, reason: str = ""):
-        with self.m_lock:
-            tasks = list(self.m_tasks)
-            self.m_tasks.clear()
-        for task in tasks:
-            self._cleanup_task(task, reason)
-
-    def _get_redis_key(self, routing_key: str) -> str:
-        return f"vs:signal:{routing_key}"
-    
-    def _is_within_active_hours(self) -> bool:
-        """
-        判断当前是否在允许创建实例的时间段内
-        """
-        start_str = self.m_cfg.active_time_start
-        end_str = self.m_cfg.active_time_end
-        current_bj_time = datetime.utcnow().time()
-        start_time = datetime.strptime(start_str, "%H:%M").time()
-        end_time = datetime.strptime(end_str, "%H:%M").time()
-        return start_time <= current_bj_time <= end_time
-
-    def _maintain_loop(self):
-        self._log("Maintain loop started.")
-        while not self.m_stop_event.is_set():
-            try:
-                time.sleep(1.0)
-                with self.m_lock:
-                    tasks_to_check = list(self.m_tasks)
-                
-                if not tasks_to_check:
-                    continue
-                
-                dead_tasks = []
-                healthy_tasks = []
-                now = time.time()
-                for t in tasks_to_check:
-                    if now >= t.next_remote_ping:
-                        t.instance.keep_alive()
-                        if t.instance.health_check():
-                            healthy_tasks.append(t)
-                            t.next_remote_ping = now + random.gauss(self.m_cfg.booker.keep_alive, 5)
-                        else:
-                            dead_tasks.append(t)
-                    else:
-                        healthy_tasks.append(t)
-                
-                if dead_tasks:
-                    with self.m_lock:
-                        current_tasks = list(self.m_tasks)
-                        self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
-                    for t in dead_tasks:
-                        if t in current_tasks:
-                            self._cleanup_task(t, "unhealthy or keep-alive failed")
-                else:
-                    with self.m_lock:
-                        self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
-            except Exception as e:
-                self._log(f'Maintain loop exception: {e}')
-
-    def _booking_trigger_loop(self):
-        self._log("Trigger loop started.")
-        while not self.m_stop_event.is_set():
-            try:
-                time.sleep(0.1)
-                now = time.time()
-                for apt_type in self.m_cfg.appointment_types:
-                    redis_key = self._get_redis_key(apt_type.routing_key)
-                    raw_data = self.redis_client.get(redis_key)
-                    if not raw_data:
-                        continue
-                    try:
-                        data = json.loads(raw_data)
-                        query_result = VSQueryResult.model_validate(data['query_result'])
-                        query_result.apt_type = AppointmentType.model_validate(data['apt_type'])
-                    except Exception as parse_err:
-                        self._log(f"Data parsing error for {redis_key}: {parse_err}. Deleting corrupted signal.")
-                        self.redis_client.delete(redis_key)
-                        continue
-                    
-                    matching_tasks = []
-                    with self.m_lock:
-                        for task in self.m_tasks:
-                            if now < task.next_run or not task.book_allowed:
-                                continue
-                            if apt_type.routing_key not in task.acceptable_routing_keys:
-                                continue
-                            
-                            task.next_run = now + self.m_cfg.booker.booking_cooldown
-                            matching_tasks.append(task)
-                            
-                    if matching_tasks:
-                        threads = []
-                        for task in matching_tasks:
-                            self._log(f"🚀 Triggering BOOK for {apt_type.routing_key} | Order Ref: {task.task_ref}")
-                            t = threading.Thread(target=self._execute_book_job, args=(task, query_result))
-                            threads.append(t)
-                            t.start()
-                        
-                        for t in threads:
-                            t.join() 
-                    
-            except Exception as e:
-                self._log(f"Booking trigger loop exception: {e}")
-
-    def _execute_book_job(self, task: Task, query_result: VSQueryResult):
-        queue_name = f"auto.{query_result.apt_type.routing_key}"
-
-        task_id = None
-        task_data = None
-        
-        try:
-            task_data = VSCloudApi.Instance().get_vas_task_pop(queue_name)
-            if not task_data:
-                return 
-            task_id = task_data['id']
-            order_id = task_data.get('order_id')
-            self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + 30.0})
-                        
-            user_input = task_data.get('user_inputs', {})
-            book_res = task.instance.book(query_result, user_input)
-            
-            if book_res.success:
-                self._log(f"✅ BOOK SUCCESS! Order: {order_id}")
-                
-                grab_info = {
-                    "account": book_res.account,
-                    "session_id": book_res.session_id,
-                    "urn": book_res.urn,
-                    "slot_date": book_res.book_date,
-                    "slot_time": book_res.book_time,
-                    "timestamp": int(time.time()),
-                    "payment_link": book_res.payment_link
-                }
-                
-                def _update_cloud_success():
-                    try:
-                        VSCloudApi.Instance().update_vas_task(str(task_id), {"status": "grabbed", "grabbed_history": grab_info})
-                        push_content = (
-                            f"🎉 【预定成功通知】\n"
-                            f"━━━━━━━━━━━━━━━\n"
-                            f"订单编号: {order_id}\n"
-                            f"预约账号: {book_res.account}\n"
-                            f"预约日期: {book_res.book_date}\n"
-                            f"预约时间: {book_res.book_time}\n"
-                            f"预约编号: {book_res.urn}\n"
-                            f"支付链接: {book_res.payment_link if book_res.payment_link else '无需支付/暂无'}\n"
-                            f"━━━━━━━━━━━━━━━\n"
-                        )
-                        VSCloudApi.Instance().push_weixin_text(push_content)
-                    except Exception as e:
-                        self._log(f"Failed to update success state to cloud: {e}")
-
-                ThreadPool.getInstance().enqueue(_update_cloud_success)
-                self.redis_client.zrem(self.m_tracker_key, task_id)
-                
-                task.successful_bookings += 1
-                max_b = self.m_cfg.booker.max_bookings_per_account
-                if max_b > 0 and task.successful_bookings >= max_b:
-                    self._log(f"Account reached max bookings ({max_b}). Destroying instance.")
-                    self._remove_task(task, "max bookings reached")
-            else:
-                self._log(f"❌ BOOK FAILED for Order: {order_id}")
-        except Exception as e:
-            err_str = str(e)
-            self._log(f"Exception during booking: {err_str}")
-            rate_limited_indicators = [
-                "42901" in err_str,
-                "Rate limited" in err_str
-            ]
-            if any(rate_limited_indicators):
-                self._remove_task(task, "booking rate limited")
-                
-    def _creator_loop(self):
-        self._log("Creator loop started.")
-        while not self.m_stop_event.wait(1.0):
-            try:
-                if not self._is_within_active_hours():
-                    continue
-                
-                with self.m_lock:
-                    current = len(self.m_tasks)
-                    target = self.m_cfg.booker.target_instances
-                if current < target:
-                    self._spawn_worker()
-            except Exception as e:
-                self._log(f'Creator loop exception: {e}')
-
-    def _spawn_worker(self):
-        instance = None
-        success = False
-        plg_cfg = None
-        
-        try:
-            plg_cfg = VSPlgConfig()
-            plg_cfg.debug = self.m_cfg.debug
-            plg_cfg.free_config = self.m_cfg.free_config
-            plg_cfg.session_max_life = self.m_cfg.session_max_life
-
-            if self.m_cfg.need_account:
-                acc = VSCloudApi.Instance().get_next_account(self.m_cfg.booker.account_pool_id, self.m_cfg.booker.account_cd)
-                plg_cfg.account = type(plg_cfg.account)(**acc)
-
-            if self.m_cfg.need_proxy:
-                proxy = VSCloudApi.Instance().get_next_proxy(self.m_cfg.proxy_pool, self.m_cfg.proxy_cd)
-                plg_cfg.proxy = type(plg_cfg.proxy)(**proxy)
-
-            instance = self.m_factory.create(self.m_cfg.identifier, self.m_cfg.plugin_config.plugin_name)
-            instance.set_log(self.m_logger)
-            instance.set_config(plg_cfg)
-            instance.create_session()
-            success = True
-            with self.m_lock:
-                all_keys = [apt.routing_key for apt in self.m_cfg.appointment_types]
-                self.m_tasks.append(
-                    Task(
-                        instance=instance,
-                        next_run=time.time(), 
-                        task_ref=None,
-                        acceptable_routing_keys=all_keys,
-                        source_queue="built-in",
-                        book_allowed=True,
-                        next_remote_ping = time.time() + random.gauss(self.m_cfg.booker.keep_alive, 5) 
-                    )
-                )
-            success = True
-            self._log(f"+++ Built-in Booker spawned: {plg_cfg.account.username}")
-        except Exception as e:
-            err_str = str(e)
-            self._log(f"Spawn failed: {err_str}")
-            rate_limited_indicators = [
-                "42901" in err_str,
-                "Rate limited" in err_str
-            ]
-            if any(rate_limited_indicators):
-                if plg_cfg and plg_cfg.account.username != "Guest":
-                    VSCloudApi.lock_account(plg_cfg.account.id, self.m_cfg.login_backoff)
-        finally:
-            if not success:
-                if instance:
-                    instance.cleanup()

+ 0 - 9
config/config.json

@@ -1,9 +0,0 @@
-{
-  "redis": {
-    "host": "text.skin",
-    "port": 6379,
-    "db": 0,
-    "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
-  },
-  "group_list": []
-}

+ 0 - 1632
config/config.json.example

@@ -1,1632 +0,0 @@
-{
-  "version": 202605241517,
-  "redis": {
-    "host": "text.skin",
-    "port": 6379,
-    "db": 0,
-    "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
-  },
-  "group_list": [
-    {
-      "identifier": "vfs.ie.nl",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.nl.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.nl.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 300,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.nl.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/nld/login",
-      "free_config": {
-        "mission_code": "nld",
-        "mission_name": "Netherlands",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.nl.tourist": {
-            "center_name": "Netherlands Visa Application Center - Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
-            "vac_code": "NTDB",
-            "category_name": "All Short stay Categories",
-            "category_code": "TA",
-            "subcategory_name": "Tourist",
-            "subcategory_code": "To"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.sg.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "sg.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "sg.fr.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.sin.fr.tourist",
-          "city": "Singapore",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/sgp/en/fra/login",
-      "free_config": {
-        "mission_code": "fra",
-        "mission_name": "France",
-        "country_code": "sgp",
-        "country_name": "Singapore",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.sin.fr.tourist": {
-            "center_name": "France Visa Application Center, Singapore",
-            "address": "79 Anson Road #15-01 Singapore 079906",
-            "vac_code": "FRSN",
-            "category_name": "Short Stay",
-            "category_code": "02",
-            "subcategory_name": "Short Stay Tourist, Family Visit",
-            "subcategory_code": "Six"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.au.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "au.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "au.fr.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.syd.fr.tourist",
-          "city": "Sydney",
-          "visa_type": "Tourist",
-          "country": "France"
-        },
-        {
-          "weight": 10,
-          "routing_key": "slot.mel.fr.tourist",
-          "city": "Melbourne",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/aus/en/fra/login",
-      "free_config": {
-        "mission_code": "fra",
-        "mission_name": "France",
-        "country_code": "aus",
-        "country_name": "Australia",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.syd.fr.tourist": {
-            "center_name": "France Visa Application Center - Sydney",
-            "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
-            "vac_code": "SYD",
-            "category_name": "VISA",
-            "category_code": "VISA",
-            "subcategory_name": "Short Stay Schengen Visa",
-            "subcategory_code": "ShortStaySchengenVisa"
-          },
-          "slot.mel.fr.tourist": {
-            "center_name": "France Visa Application Center - Melbourne",
-            "address": "Level 5 332 St. Kilda road level 5 Melbourne 3004",
-            "vac_code": "MEL",
-            "category_name": "VISA",
-            "category_code": "VISA",
-            "subcategory_name": "Short Stay Schengen Visa",
-            "subcategory_code": "ShortStaySchengenVisa"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.gb.it",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.it.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.it.booker",
-        "target_instances": 0,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 80,
-          "routing_key": "slot.lon.it.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Italy"
-        },
-        {
-          "weight": 20,
-          "routing_key": "slot.man.it.tourist",
-          "city": "Manchester",
-          "visa_type": "Tourist",
-          "country": "Italy"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
-      "free_config": {
-        "mission_code": "ita",
-        "mission_name": "Italy",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.it.tourist": {
-            "center_name": "Italy Visa Application Centre, London",
-            "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
-            "vac_code": "ILON",
-            "category_name": "Italy UK VisaCategory",
-            "category_code": "UKITVED",
-            "subcategory_name": "Tourist/ Business/ EU Family",
-            "subcategory_code": "TBE"
-          },
-          "slot.man.it.tourist": {
-            "center_name": "Italy Visa Application Centre, Manchester",
-            "address": "50 Devonshire Street North, M12 6JH",
-            "vac_code": "IMAN",
-            "category_name": "Italy UK VisaCategory",
-            "category_code": "UKITVED",
-            "subcategory_name": "Tourist/ Business/ EU Family",
-            "subcategory_code": "TBE"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.gb.nl",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.nl.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.nl.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 90,
-          "routing_key": "slot.lon.nl.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
-        },
-        {
-          "weight": 10,
-          "routing_key": "slot.man.nl.tourist",
-          "city": "Manchester",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
-      "free_config": {
-        "mission_code": "nld",
-        "mission_name": "Netherland",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.nl.tourist": {
-            "center_name": "Netherlands Visa application centre - London",
-            "address": "66 Wilson Street, EC2A 2BT",
-            "vac_code": "NAKN",
-            "category_name": "Schengen Visa",
-            "category_code": "Schengen Visa",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          },
-          "slot.man.nl.tourist": {
-            "center_name": "Netherlands Visa application centre - Manchester",
-            "address": "50 Devonshire Street North, M12 6JH",
-            "vac_code": "NAKT",
-            "category_name": "Schengen Visa",
-            "category_code": "Schengen Visa",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.gb.no",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.no.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.no.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.no.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Norway"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/nor/login",
-      "free_config": {
-        "mission_code": "nor",
-        "mission_name": "Norway",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.no.tourist": {
-            "center_name": "Norway Visa Application Centre, London",
-            "address": "66 Wilson street, EC2A 2BT",
-            "vac_code": "NLON",
-            "category_name": "Schengen Visa C",
-            "category_code": "SCHVISA",
-            "subcategory_name": "Tourist Visa",
-            "subcategory_code": "TOU"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.ie.at",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.at.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.at.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 300,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.at.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.at.tourist": {
-            "center_name": "Austria / Switzerland / Liechtenstein/ Slovenia Visa Application Center, Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "AUT-DUB",
-            "category_name": "Other Visas",
-            "category_code": "Default_Austria_Ireland ",
-            "subcategory_name": "All Visas ",
-            "subcategory_code": "TA"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.ie.dk",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.dk.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.dk.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.dk.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Denmark"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/dnk/login",
-      "free_config": {
-        "mission_code": "dnk",
-        "mission_name": "Denmark",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.dk.tourist": {
-            "center_name": "Denmark Visa Application Center, Dublin ",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "DIDUB",
-            "category_name": "Schengen Visa",
-            "category_code": "SV",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TV"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.ie.fi",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.fi.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.fi.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.fi.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Finland"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/fin/login",
-      "free_config": {
-        "mission_code": "fin",
-        "mission_name": "Finland",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.fi.tourist": {
-            "center_name": "Application Centre, Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "Dubb",
-            "category_name": "VISA",
-            "category_code": "S S",
-            "subcategory_name": "Tourist Category",
-            "subcategory_code": "Tourist Category"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.ie.hu",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.hu.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.hu.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 180,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.hu.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Hungary"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/hun/login",
-      "free_config": {
-        "mission_code": "hun",
-        "mission_name": "Hungary",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.hu.tourist": {
-            "center_name": "Ireland Visa Application Center,Dublin",
-            "address": "Cunningham House, 130 Francis Street Dublin",
-            "vac_code": "DUB",
-            "category_name": "Short Stay",
-            "category_code": "SS",
-            "subcategory_name": "Schengen Visa",
-            "subcategory_code": "Schengen Visa"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.ie.is",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.is.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.is.booker",
-        "target_instances": 0,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.is.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Iceland"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/isl/login",
-      "free_config": {
-        "mission_code": "isl",
-        "mission_name": "Iceland",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.is.tourist": {
-            "center_name": "Iceland Visa Application Center- Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
-            "vac_code": "DUB",
-            "category_name": "C-Visa",
-            "category_code": "CVI",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "OTT"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "vfs.gb.at",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.at.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.at.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.at.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.at.tourist": {
-            "center_name": "Austria Visa Application Centre, London",
-            "address": "66 Wilson Street, EC2A 2BT",
-            "vac_code": "ADN",
-            "category_name": "Visa  to  Austria",
-            "category_code": "Visa  to  Austria",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "bls.ie.es",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "spain-isp"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 3600,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.es.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 0,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 180,
-        "random_max": 360
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bls_plugin",
-        "plugin_bin": "bls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.es.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Spain"
-        }
-      ],
-      "website": "https://ireland.blsspainglobal.com/Global/bls/visatypeverification",
-      "free_config": {
-        "domain": "ireland.blsspainglobal.com",
-        "ocr_model": "data/ctc.pth",
-        "apt_configs": {
-          "slot.dub.es.tourist": {
-            "location": "Dublin",
-            "jurisdiction": null,
-            "visa_type": "Schengen Visa/ Short Term Visa",
-            "visa_subtype": "Tourist Visa",
-            "appointment_type": "Individual",
-            "appointment_category": "Normal",
-            "mission_code": "EMBASSY_DUBLIN"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "bls.gb.es",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "spain-isp"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 3600,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.es.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 0,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bls_plugin",
-        "plugin_bin": "bls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.es.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Spain"
-        }
-      ],
-      "website": "https://uk.blsspainglobal.com/Global/bls/visatypeverification",
-      "free_config": {
-        "domain": "uk.blsspainglobal.com",
-        "ocr_model": "data/ocr.pth",
-        "apt_configs": {
-          "slot.lon.es.tourist": {
-            "location": "Dublin",
-            "jurisdiction": "Greater London",
-            "visa_type": "Short Term Visa(Maximum stay of 90 days)",
-            "visa_subtype": "Tourist Visa",
-            "appointment_type": "Individual",
-            "appointment_category": "Normal",
-            "mission_code": "LHR"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "tls.gb.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.gb.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 2,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 55,
-        "random_max": 65
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.fr.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
-        "location": "London",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
-      }
-    },
-    {
-      "identifier": "tls.cn.bjs.fr",
-      "debug": false,
-      "enable": true,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap-good"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.cn.bjs.fr.sentinel",
-        "target_instances": 2,
-        "account_cd": 1800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 55,
-        "random_max": 65
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.bjs.fr.tourist",
-          "city": "Beijing",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
-        "location": "Beijing",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
-      }
-    },
-    {
-      "identifier": "tls.cn.sha.fr",
-      "debug": false,
-      "enable": true,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap-good"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.cn.sha.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 55,
-        "random_max": 65
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.sha.fr.tourist",
-          "city": "Shanghai",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
-        "location": "Shanghai",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
-      }
-    },
-    {
-      "identifier": "tls.ie.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap-good",
-        "decodo-good"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.ie.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.fr.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
-        "location": "Dublin",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
-      }
-    },
-    {
-      "identifier": "vfs.cn.at",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "cn.at.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "cn.at.booker",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.can.at.tourist",
-          "city": "Guangzhou",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/chn/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "chn",
-        "country_name": "China",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.can.at.tourist": {
-            "center_name": "Austria Visa Application Center, Guangzhou",
-            "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
-            "vac_code": "Gua",
-            "category_name": "Visa Type C-Schengen",
-            "category_code": "VTC",
-            "subcategory_name": "Tourism (90 days)",
-            "subcategory_code": "TR"
-          }
-        }
-      }
-    },
-    {
-      "identifier": "e-konsulat.ie.pl",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "",
-        "account_pool_id": "",
-        "target_instances": 1,
-        "account_cd": 0,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "",
-        "target_instances": 0,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "pol_plugin",
-        "plugin_bin": "pol_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.pl.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Poland"
-        }
-      ],
-      "website": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
-      "free_config": {
-        "query_url": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
-        "service_type": "Wiza Schengen",
-        "location": "Dublin"
-      }
-    },
-    {
-      "identifier": "e-konsulat.jp.pl",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "sentinel": {
-        "account_source": "",
-        "account_pool_id": "",
-        "target_instances": 1,
-        "account_cd": 0,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "",
-        "target_instances": 0,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "pol_plugin",
-        "plugin_bin": "pol_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.tyo.pl.tourist",
-          "city": "Tokyo",
-          "visa_type": "Tourist",
-          "country": "Poland"
-        }
-      ],
-      "website": "https://secure.e-konsulat.gov.pl/placowki/178/wiza-krajowa/wizyty/weryfikacja-obrazkowa",
-      "free_config": {
-        "query_url": "https://secure.e-konsulat.gov.pl/placowki/178/wiza-krajowa/wizyty/weryfikacja-obrazkowa",
-        "service_type": "wiza krajowa",
-        "location": "Tokio"
-      }
-    },
-    {
-      "identifier": "visaonweb.ie.be",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "local"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 10800,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.be.sentinel",
-        "target_instances": 1,
-        "account_cd": 3600,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "",
-        "target_instances": 0,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bel_plugin",
-        "plugin_bin": "bel_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.be.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Belgium"
-        }
-      ],
-      "website": "https://visaonweb.diplomatie.be/en",
-      "free_config": {
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
-      }
-    },
-    {
-      "identifier": "visametric.ie.de",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 900,
-      "sentinel": {
-        "account_source": "",
-        "account_pool_id": "",
-        "target_instances": 1,
-        "account_cd": 0,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "",
-        "account_pool_id": "",
-        "target_instances": 1,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "de_plugin",
-        "plugin_bin": "de_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.de.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Germany"
-        }
-      ],
-      "website": "https://ie-appointment.visametric.com/en",
-      "free_config": {
-        "base_url": "https://ie-appointment.visametric.com",
-        "consularid": 1
-      }
-    },
-    {
-      "identifier": "pernotami.ie.it",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 900,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.it.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "ita_plugin",
-        "plugin_bin": "ita_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.it.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Italy"
-        }
-      ],
-      "website": "https://prenotami.esteri.it/Home",
-      "free_config": {
-        "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525"
-      }
-    },
-    {
-      "identifier": "greekemba.ie.gr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap",
-        "decodo"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 86400,
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.gr.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30
-      },
-      "booker": {
-        "account_source": "order",
-        "target_instances": 10,
-        "account_cd": 10800,
-        "booking_cooldown": 30,
-        "max_bookings_per_account": 1
-      },
-      "query_wait": {
-        "mode": "Random",
-        "fixed_wait": 10,
-        "random_min": 60,
-        "random_max": 300
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "grc_plugin",
-        "plugin_bin": "grc_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.gr.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Greece"
-        }
-      ],
-      "website": "https://www.supersaas.com/schedule/GreekEmbassyInDublin/Visas",
-      "free_config": {}
-    }
-  ]
-}

+ 1420 - 1429
config/config_booker.json

@@ -1,1507 +1,1498 @@
-{
-  "version": 202606011114,
-  "redis": {
-    "host": "text.skin",
-    "port": 6379,
-    "db": 0,
-    "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
-  },
-  "group_list": [
-    {
-      "identifier": "vfs.ie.nl",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.nl.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 300,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.nl.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/nld/login",
-      "free_config": {
-        "mission_code": "nld",
-        "mission_name": "Netherlands",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.nl.tourist": {
-            "center_name": "Netherlands Visa Application Center - Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
-            "vac_code": "NTDB",
-            "category_name": "All Short stay Categories",
-            "category_code": "TA",
-            "subcategory_name": "Tourist",
-            "subcategory_code": "To"
-          }
+[
+  {
+    "identifier": "vfs.ie.nl",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.nl.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 300,
+      "max_bookings_per_account": 8
+    },
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.nl.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Netherlands"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/nld/login",
+    "free_config": {
+      "mission_code": "nld",
+      "mission_name": "Netherlands",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.nl.tourist": {
+          "center_name": "Netherlands Visa Application Center - Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
+          "vac_code": "NTDB",
+          "category_name": "All Short stay Categories",
+          "category_code": "TA",
+          "subcategory_name": "Tourist",
+          "subcategory_code": "To"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.sg.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "sg.fr.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.sg.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "sg.fr.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.sin.fr.tourist",
-          "city": "Singapore",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/sgp/en/fra/login",
-      "free_config": {
-        "mission_code": "fra",
-        "mission_name": "France",
-        "country_code": "sgp",
-        "country_name": "Singapore",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.sin.fr.tourist": {
-            "center_name": "France Visa Application Center, Singapore",
-            "address": "79 Anson Road #15-01 Singapore 079906",
-            "vac_code": "FRSN",
-            "category_name": "Short Stay",
-            "category_code": "02",
-            "subcategory_name": "Short Stay Tourist, Family Visit",
-            "subcategory_code": "Six"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.sin.fr.tourist",
+        "city": "Singapore",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/sgp/en/fra/login",
+    "free_config": {
+      "mission_code": "fra",
+      "mission_name": "France",
+      "country_code": "sgp",
+      "country_name": "Singapore",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.sin.fr.tourist": {
+          "center_name": "France Visa Application Center, Singapore",
+          "address": "79 Anson Road #15-01 Singapore 079906",
+          "vac_code": "FRSN",
+          "category_name": "Short Stay",
+          "category_code": "02",
+          "subcategory_name": "Short Stay Tourist, Family Visit",
+          "subcategory_code": "Six"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.au.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "au.fr.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.au.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "au.fr.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.syd.fr.tourist",
+        "city": "Sydney",
+        "visa_type": "Tourist",
+        "country": "France"
       },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.syd.fr.tourist",
-          "city": "Sydney",
-          "visa_type": "Tourist",
-          "country": "France"
+      {
+        "weight": 10,
+        "routing_key": "slot.mel.fr.tourist",
+        "city": "Melbourne",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/aus/en/fra/login",
+    "free_config": {
+      "mission_code": "fra",
+      "mission_name": "France",
+      "country_code": "aus",
+      "country_name": "Australia",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.syd.fr.tourist": {
+          "center_name": "France Visa Application Center - Sydney",
+          "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
+          "vac_code": "SYD",
+          "category_name": "VISA",
+          "category_code": "VISA",
+          "subcategory_name": "Short Stay Schengen Visa",
+          "subcategory_code": "ShortStaySchengenVisa"
         },
-        {
-          "weight": 10,
-          "routing_key": "slot.mel.fr.tourist",
-          "city": "Melbourne",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/aus/en/fra/login",
-      "free_config": {
-        "mission_code": "fra",
-        "mission_name": "France",
-        "country_code": "aus",
-        "country_name": "Australia",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.syd.fr.tourist": {
-            "center_name": "France Visa Application Center - Sydney",
-            "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
-            "vac_code": "SYD",
-            "category_name": "VISA",
-            "category_code": "VISA",
-            "subcategory_name": "Short Stay Schengen Visa",
-            "subcategory_code": "ShortStaySchengenVisa"
-          },
-          "slot.mel.fr.tourist": {
-            "center_name": "France Visa Application Center - Melbourne",
-            "address": "Level 5 332 St. Kilda road level 5 Melbourne 3004",
-            "vac_code": "MEL",
-            "category_name": "VISA",
-            "category_code": "VISA",
-            "subcategory_name": "Short Stay Schengen Visa",
-            "subcategory_code": "ShortStaySchengenVisa"
-          }
+        "slot.mel.fr.tourist": {
+          "center_name": "France Visa Application Center - Melbourne",
+          "address": "Level 5 332 St. Kilda road level 5 Melbourne 3004",
+          "vac_code": "MEL",
+          "category_name": "VISA",
+          "category_code": "VISA",
+          "subcategory_name": "Short Stay Schengen Visa",
+          "subcategory_code": "ShortStaySchengenVisa"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.it",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.it.booker",
+      "target_instances": 0,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.gb.it",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.it.booker",
-        "target_instances": 0,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 80,
+        "routing_key": "slot.lon.it.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Italy"
       },
-      "appointment_types": [
-        {
-          "weight": 80,
-          "routing_key": "slot.lon.it.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Italy"
+      {
+        "weight": 20,
+        "routing_key": "slot.man.it.tourist",
+        "city": "Manchester",
+        "visa_type": "Tourist",
+        "country": "Italy"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
+    "free_config": {
+      "mission_code": "ita",
+      "mission_name": "Italy",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.it.tourist": {
+          "center_name": "Italy Visa Application Centre, London",
+          "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
+          "vac_code": "ILON",
+          "category_name": "Italy UK VisaCategory",
+          "category_code": "UKITVED",
+          "subcategory_name": "Tourist/ Business/ EU Family",
+          "subcategory_code": "TBE"
         },
-        {
-          "weight": 20,
-          "routing_key": "slot.man.it.tourist",
-          "city": "Manchester",
-          "visa_type": "Tourist",
-          "country": "Italy"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
-      "free_config": {
-        "mission_code": "ita",
-        "mission_name": "Italy",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.it.tourist": {
-            "center_name": "Italy Visa Application Centre, London",
-            "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
-            "vac_code": "ILON",
-            "category_name": "Italy UK VisaCategory",
-            "category_code": "UKITVED",
-            "subcategory_name": "Tourist/ Business/ EU Family",
-            "subcategory_code": "TBE"
-          },
-          "slot.man.it.tourist": {
-            "center_name": "Italy Visa Application Centre, Manchester",
-            "address": "50 Devonshire Street North, M12 6JH",
-            "vac_code": "IMAN",
-            "category_name": "Italy UK VisaCategory",
-            "category_code": "UKITVED",
-            "subcategory_name": "Tourist/ Business/ EU Family",
-            "subcategory_code": "TBE"
-          }
+        "slot.man.it.tourist": {
+          "center_name": "Italy Visa Application Centre, Manchester",
+          "address": "50 Devonshire Street North, M12 6JH",
+          "vac_code": "IMAN",
+          "category_name": "Italy UK VisaCategory",
+          "category_code": "UKITVED",
+          "subcategory_name": "Tourist/ Business/ EU Family",
+          "subcategory_code": "TBE"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.nl",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.nl.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.gb.nl",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.nl.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 90,
+        "routing_key": "slot.lon.nl.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Netherlands"
       },
-      "appointment_types": [
-        {
-          "weight": 90,
-          "routing_key": "slot.lon.nl.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
+      {
+        "weight": 10,
+        "routing_key": "slot.man.nl.tourist",
+        "city": "Manchester",
+        "visa_type": "Tourist",
+        "country": "Netherlands"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
+    "free_config": {
+      "mission_code": "nld",
+      "mission_name": "Netherland",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.nl.tourist": {
+          "center_name": "Netherlands Visa application centre - London",
+          "address": "66 Wilson Street, EC2A 2BT",
+          "vac_code": "NAKN",
+          "category_name": "Schengen Visa",
+          "category_code": "Schengen Visa",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TA"
         },
-        {
-          "weight": 10,
-          "routing_key": "slot.man.nl.tourist",
-          "city": "Manchester",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
-      "free_config": {
-        "mission_code": "nld",
-        "mission_name": "Netherland",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.nl.tourist": {
-            "center_name": "Netherlands Visa application centre - London",
-            "address": "66 Wilson Street, EC2A 2BT",
-            "vac_code": "NAKN",
-            "category_name": "Schengen Visa",
-            "category_code": "Schengen Visa",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          },
-          "slot.man.nl.tourist": {
-            "center_name": "Netherlands Visa application centre - Manchester",
-            "address": "50 Devonshire Street North, M12 6JH",
-            "vac_code": "NAKT",
-            "category_name": "Schengen Visa",
-            "category_code": "Schengen Visa",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          }
+        "slot.man.nl.tourist": {
+          "center_name": "Netherlands Visa application centre - Manchester",
+          "address": "50 Devonshire Street North, M12 6JH",
+          "vac_code": "NAKT",
+          "category_name": "Schengen Visa",
+          "category_code": "Schengen Visa",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TA"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.no",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.no.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.gb.no",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.no.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.no.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Norway"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/nor/login",
-      "free_config": {
-        "mission_code": "nor",
-        "mission_name": "Norway",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.no.tourist": {
-            "center_name": "Norway Visa Application Centre, London",
-            "address": "66 Wilson street, EC2A 2BT",
-            "vac_code": "NLON",
-            "category_name": "Schengen Visa C",
-            "category_code": "SCHVISA",
-            "subcategory_name": "Tourist Visa",
-            "subcategory_code": "TOU"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.no.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Norway"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/nor/login",
+    "free_config": {
+      "mission_code": "nor",
+      "mission_name": "Norway",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.no.tourist": {
+          "center_name": "Norway Visa Application Centre, London",
+          "address": "66 Wilson street, EC2A 2BT",
+          "vac_code": "NLON",
+          "category_name": "Schengen Visa C",
+          "category_code": "SCHVISA",
+          "subcategory_name": "Tourist Visa",
+          "subcategory_code": "TOU"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.at",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.at.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 300,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.ie.at",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.at.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 300,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.at.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.at.tourist": {
-            "center_name": "Austria / Switzerland / Liechtenstein/ Slovenia Visa Application Center, Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "AUT-DUB",
-            "category_name": "Other Visas",
-            "category_code": "Default_Austria_Ireland ",
-            "subcategory_name": "All Visas ",
-            "subcategory_code": "TA"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.at.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/aut/login",
+    "free_config": {
+      "mission_code": "aut",
+      "mission_name": "Austria",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.at.tourist": {
+          "center_name": "Austria / Switzerland / Liechtenstein/ Slovenia Visa Application Center, Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
+          "vac_code": "AUT-DUB",
+          "category_name": "Other Visas",
+          "category_code": "Default_Austria_Ireland ",
+          "subcategory_name": "All Visas ",
+          "subcategory_code": "TA"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.dk",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.dk.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.ie.dk",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.dk.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.dk.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Denmark"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/dnk/login",
-      "free_config": {
-        "mission_code": "dnk",
-        "mission_name": "Denmark",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.dk.tourist": {
-            "center_name": "Denmark Visa Application Center, Dublin ",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "DIDUB",
-            "category_name": "Schengen Visa",
-            "category_code": "SV",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TV"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.dk.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Denmark"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/dnk/login",
+    "free_config": {
+      "mission_code": "dnk",
+      "mission_name": "Denmark",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.dk.tourist": {
+          "center_name": "Denmark Visa Application Center, Dublin ",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
+          "vac_code": "DIDUB",
+          "category_name": "Schengen Visa",
+          "category_code": "SV",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TV"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.fi",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.fi.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.ie.fi",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.fi.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.fi.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Finland"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/fin/login",
-      "free_config": {
-        "mission_code": "fin",
-        "mission_name": "Finland",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.fi.tourist": {
-            "center_name": "Application Centre, Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "Dubb",
-            "category_name": "VISA",
-            "category_code": "S S",
-            "subcategory_name": "Tourist Category",
-            "subcategory_code": "Tourist Category"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.fi.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Finland"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/fin/login",
+    "free_config": {
+      "mission_code": "fin",
+      "mission_name": "Finland",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.fi.tourist": {
+          "center_name": "Application Centre, Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
+          "vac_code": "Dubb",
+          "category_name": "VISA",
+          "category_code": "S S",
+          "subcategory_name": "Tourist Category",
+          "subcategory_code": "Tourist Category"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.hu",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.hu.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 180,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.ie.hu",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.hu.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 180,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.hu.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Hungary"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/hun/login",
-      "free_config": {
-        "mission_code": "hun",
-        "mission_name": "Hungary",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.hu.tourist": {
-            "center_name": "Ireland Visa Application Center,Dublin",
-            "address": "Cunningham House, 130 Francis Street Dublin",
-            "vac_code": "DUB",
-            "category_name": "Short Stay",
-            "category_code": "SS",
-            "subcategory_name": "Schengen Visa",
-            "subcategory_code": "Schengen Visa"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.hu.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Hungary"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/hun/login",
+    "free_config": {
+      "mission_code": "hun",
+      "mission_name": "Hungary",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.hu.tourist": {
+          "center_name": "Ireland Visa Application Center,Dublin",
+          "address": "Cunningham House, 130 Francis Street Dublin",
+          "vac_code": "DUB",
+          "category_name": "Short Stay",
+          "category_code": "SS",
+          "subcategory_name": "Schengen Visa",
+          "subcategory_code": "Schengen Visa"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.is",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.is.booker",
+      "target_instances": 0,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.ie.is",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.is.booker",
-        "target_instances": 0,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.is.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Iceland"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/isl/login",
-      "free_config": {
-        "mission_code": "isl",
-        "mission_name": "Iceland",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.is.tourist": {
-            "center_name": "Iceland Visa Application Center- Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
-            "vac_code": "DUB",
-            "category_name": "C-Visa",
-            "category_code": "CVI",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "OTT"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.is.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Iceland"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/isl/login",
+    "free_config": {
+      "mission_code": "isl",
+      "mission_name": "Iceland",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.is.tourist": {
+          "center_name": "Iceland Visa Application Center- Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
+          "vac_code": "DUB",
+          "category_name": "C-Visa",
+          "category_code": "CVI",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "OTT"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.at",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.at.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
     },
-    {
-      "identifier": "vfs.gb.at",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.at.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.at.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.at.tourist": {
-            "center_name": "Austria Visa Application Centre, London",
-            "address": "66 Wilson Street, EC2A 2BT",
-            "vac_code": "ADN",
-            "category_name": "Visa  to  Austria",
-            "category_code": "Visa  to  Austria",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.at.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/aut/login",
+    "free_config": {
+      "mission_code": "aut",
+      "mission_name": "Austria",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.at.tourist": {
+          "center_name": "Austria Visa Application Centre, London",
+          "address": "66 Wilson Street, EC2A 2BT",
+          "vac_code": "ADN",
+          "category_name": "Visa  to  Austria",
+          "category_code": "Visa  to  Austria",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TA"
         }
       }
+    }
+  },
+  {
+    "identifier": "bls.ie.es",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "spain-isp"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 3600,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 0,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "bls.ie.es",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "spain-isp"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 3600,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 0,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bls_plugin",
-        "plugin_bin": "bls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.es.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Spain"
-        }
-      ],
-      "website": "https://ireland.blsspainglobal.com/Global/bls/visatypeverification",
-      "free_config": {
-        "domain": "ireland.blsspainglobal.com",
-        "ocr_model": "data/ctc.pth",
-        "apt_configs": {
-          "slot.dub.es.tourist": {
-            "location": "Dublin",
-            "jurisdiction": null,
-            "visa_type": "Schengen Visa/ Short Term Visa",
-            "visa_subtype": "Tourist Visa",
-            "appointment_type": "Individual",
-            "appointment_category": "Normal",
-            "mission_code": "EMBASSY_DUBLIN"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "bls_plugin",
+      "plugin_bin": "bls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.es.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Spain"
+      }
+    ],
+    "website": "https://ireland.blsspainglobal.com/Global/bls/visatypeverification",
+    "free_config": {
+      "domain": "ireland.blsspainglobal.com",
+      "ocr_model": "data/ctc.pth",
+      "apt_configs": {
+        "slot.dub.es.tourist": {
+          "location": "Dublin",
+          "jurisdiction": null,
+          "visa_type": "Schengen Visa/ Short Term Visa",
+          "visa_subtype": "Tourist Visa",
+          "appointment_type": "Individual",
+          "appointment_category": "Normal",
+          "mission_code": "EMBASSY_DUBLIN"
         }
       }
+    }
+  },
+  {
+    "identifier": "bls.gb.es",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "spain-isp"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 3600,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 0,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "bls.gb.es",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "spain-isp"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 3600,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 0,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bls_plugin",
-        "plugin_bin": "bls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.es.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Spain"
-        }
-      ],
-      "website": "https://uk.blsspainglobal.com/Global/bls/visatypeverification",
-      "free_config": {
-        "domain": "uk.blsspainglobal.com",
-        "ocr_model": "data/ocr.pth",
-        "apt_configs": {
-          "slot.lon.es.tourist": {
-            "location": "Dublin",
-            "jurisdiction": "Greater London",
-            "visa_type": "Short Term Visa(Maximum stay of 90 days)",
-            "visa_subtype": "Tourist Visa",
-            "appointment_type": "Individual",
-            "appointment_category": "Normal",
-            "mission_code": "LHR"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "bls_plugin",
+      "plugin_bin": "bls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.es.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Spain"
+      }
+    ],
+    "website": "https://uk.blsspainglobal.com/Global/bls/visatypeverification",
+    "free_config": {
+      "domain": "uk.blsspainglobal.com",
+      "ocr_model": "data/ocr.pth",
+      "apt_configs": {
+        "slot.lon.es.tourist": {
+          "location": "Dublin",
+          "jurisdiction": "Greater London",
+          "visa_type": "Short Term Visa(Maximum stay of 90 days)",
+          "visa_subtype": "Tourist Visa",
+          "appointment_type": "Individual",
+          "appointment_category": "Normal",
+          "mission_code": "LHR"
         }
       }
+    }
+  },
+  {
+    "identifier": "tls.gb.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "07:00",
+    "active_time_end": "17:30",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 1,
+      "keep_alive": 60,
+      "account_cd": 1800,
+      "booking_cooldown": 3,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "tls.gb.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "07:00",
-      "active_time_end": "17:30",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "keep_alive": 60,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.fr.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
-        "location": "London",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": true,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.fr.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
+      "location": "London",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": true,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
       }
+    }
+  },
+  {
+    "identifier": "tls.cn.hgh.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:30",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 1,
+      "keep_alive": 60,
+      "account_cd": 1800,
+      "booking_cooldown": 3,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "tls.cn.hgh.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:30",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "keep_alive": 60,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.hgh.fr.tourist",
-          "city": "Hangzhou",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
-        "location": "Hangzhou",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.hgh.fr.tourist",
+        "city": "Hangzhou",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
+      "location": "Hangzhou",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
       }
+    }
+  },
+  {
+    "identifier": "tls.cn.can.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:30",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 1,
+      "keep_alive": 60,
+      "account_cd": 1800,
+      "booking_cooldown": 3,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "tls.cn.can.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:30",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "keep_alive": 60,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.can.fr.tourist",
-          "city": "Guangzhou",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHCAN2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHCAN2fr",
-        "location": "Guangzhou",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.can.fr.tourist",
+        "city": "Guangzhou",
+        "visa_type": "Tourist",
+        "country": "France"
       }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHCAN2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHCAN2fr",
+      "location": "Guangzhou",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "tls.cn.bjs.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:30",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 1,
+      "keep_alive": 60,
+      "account_cd": 1800,
+      "booking_cooldown": 3,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "tls.cn.bjs.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:30",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "keep_alive": 60,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.bjs.fr.tourist",
-          "city": "Beijing",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
-        "location": "Beijing",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.bjs.fr.tourist",
+        "city": "Beijing",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
+      "location": "Beijing",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
       }
+    }
+  },
+  {
+    "identifier": "tls.cn.sha.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:30",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 1,
+      "keep_alive": 60,
+      "account_cd": 1800,
+      "booking_cooldown": 3,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "tls.cn.sha.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:30",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "keep_alive": 60,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.sha.fr.tourist",
-          "city": "Shanghai",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
-        "location": "Shanghai",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.sha.fr.tourist",
+        "city": "Shanghai",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
+      "location": "Shanghai",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
       }
+    }
+  },
+  {
+    "identifier": "tls.ie.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 1,
+      "keep_alive": 60,
+      "account_cd": 1800,
+      "booking_cooldown": 3,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "tls.ie.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "keep_alive": 60,
-        "account_cd": 1800,
-        "booking_cooldown": 3,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.fr.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
-        "location": "Dublin",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.fr.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
+      "location": "Dublin",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
       }
+    }
+  },
+  {
+    "identifier": "vfs.cn.at",
+    "debug": false,
+    "enable": true,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "built-in",
+      "account_pool_id": "cn.at.booker",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 8
+    },
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
     },
-    {
-      "identifier": "vfs.cn.at",
-      "debug": false,
-      "enable": true,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "built-in",
-        "account_pool_id": "cn.at.booker",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 8
+    "appointment_types": [
+      {
+        "weight": 1,
+        "routing_key": "slot.bjs.at.tourist",
+        "city": "Beijing",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      },
+      {
+        "weight": 0,
+        "routing_key": "slot.can.at.tourist",
+        "city": "Guangzhou",
+        "visa_type": "Tourist",
+        "country": "Austria"
       },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
+      {
+        "weight": 0,
+        "routing_key": "slot.szx.at.tourist",
+        "city": "Shenzhen",
+        "visa_type": "Tourist",
+        "country": "Austria"
       },
-      "appointment_types": [
-        {
-          "weight": 1,
-          "routing_key": "slot.bjs.at.tourist",
-          "city": "Beijing",
-          "visa_type": "Tourist",
-          "country": "Austria"
+      {
+        "weight": 1,
+        "routing_key": "slot.sha.at.tourist",
+        "city": "Shanghai",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/chn/en/aut/login",
+    "free_config": {
+      "mission_code": "aut",
+      "mission_name": "Austria",
+      "country_code": "chn",
+      "country_name": "China",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.bjs.at.tourist": {
+          "center_name": "Austria Visa Application Center, Beijing",
+          "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
+          "vac_code": "Bei",
+          "category_name": "Visa Type C-Schengen",
+          "category_code": "VTC",
+          "subcategory_name": "Tourism (90 days)",
+          "subcategory_code": "TR"
         },
-        {
-          "weight": 0,
-          "routing_key": "slot.can.at.tourist",
-          "city": "Guangzhou",
-          "visa_type": "Tourist",
-          "country": "Austria"
+        "slot.can.at.tourist": {
+          "center_name": "Austria Visa Application Center, Guangzhou",
+          "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
+          "vac_code": "Gua",
+          "category_name": "Visa Type C-Schengen",
+          "category_code": "VTC",
+          "subcategory_name": "Tourism (90 days)",
+          "subcategory_code": "TR"
         },
-        {
-          "weight": 0,
-          "routing_key": "slot.szx.at.tourist",
-          "city": "Shenzhen",
-          "visa_type": "Tourist",
-          "country": "Austria"
+        "slot.sha.at.tourist": {
+          "center_name": "Austria Visa Application Center, Shanghai",
+          "address": "3F, Jiushi Commercial Building, No. 213, Middle Sichuan Road, Huangpu District",
+          "vac_code": "Shaa",
+          "category_name": "Visa Type C - Tourism Visa",
+          "category_code": "TT",
+          "subcategory_name": "Visa Type C - Tourism Visa",
+          "subcategory_code": "VTCT"
         },
-        {
-          "weight": 1,
-          "routing_key": "slot.sha.at.tourist",
-          "city": "Shanghai",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/chn/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "chn",
-        "country_name": "China",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.bjs.at.tourist": {
-            "center_name": "Austria Visa Application Center, Beijing",
-            "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
-            "vac_code": "Bei",
-            "category_name": "Visa Type C-Schengen",
-            "category_code": "VTC",
-            "subcategory_name": "Tourism (90 days)",
-            "subcategory_code": "TR"
-          },
-          "slot.can.at.tourist": {
-            "center_name": "Austria Visa Application Center, Guangzhou",
-            "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
-            "vac_code": "Gua",
-            "category_name": "Visa Type C-Schengen",
-            "category_code": "VTC",
-            "subcategory_name": "Tourism (90 days)",
-            "subcategory_code": "TR"
-          },
-          "slot.sha.at.tourist": {
-            "center_name": "Austria Visa Application Center, Shanghai",
-            "address": "3F, Jiushi Commercial Building, No. 213, Middle Sichuan Road, Huangpu District",
-            "vac_code": "Shaa",
-            "category_name": "Visa Type C - Tourism Visa",
-            "category_code": "TT",
-            "subcategory_name": "Visa Type C - Tourism Visa",
-            "subcategory_code": "VTCT"
-          },
-          "slot.szx.at.tourist": {
-            "center_name": "Austria Visa Application Center, Shenzhen",
-            "address": "Unit 2303, Floor 23, East Tower, C Future City, No. 9285 Binhe Avenue, Futian District",
-            "vac_code": "ASSZA",
-            "category_name": "Visa Type C-Schengen",
-            "category_code": "VTC",
-            "subcategory_name": "Tourism (90 days)",
-            "subcategory_code": "TR"
-          }
+        "slot.szx.at.tourist": {
+          "center_name": "Austria Visa Application Center, Shenzhen",
+          "address": "Unit 2303, Floor 23, East Tower, C Future City, No. 9285 Binhe Avenue, Futian District",
+          "vac_code": "ASSZA",
+          "category_name": "Visa Type C-Schengen",
+          "category_code": "VTC",
+          "subcategory_name": "Tourism (90 days)",
+          "subcategory_code": "TR"
         }
       }
+    }
+  },
+  {
+    "identifier": "e-konsulat.ie.pl",
+    "debug": false,
+    "enable": false,
+    "need_account": false,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "",
+      "target_instances": 0,
+      "account_cd": 0,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "e-konsulat.ie.pl",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "",
-        "target_instances": 0,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "pol_plugin",
-        "plugin_bin": "pol_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.pl.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Poland"
-        }
-      ],
-      "website": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
-      "free_config": {
-        "query_url": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
-        "service_type": "Wiza Schengen",
-        "location": "Dublin"
-      }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "pol_plugin",
+      "plugin_bin": "pol_plugin.py",
+      "plugin_proto": "IVSPlg"
     },
-    {
-      "identifier": "e-konsulat.jp.pl",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "",
-        "target_instances": 0,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "pol_plugin",
-        "plugin_bin": "pol_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.tyo.pl.tourist",
-          "city": "Tokyo",
-          "visa_type": "Tourist",
-          "country": "Poland"
-        }
-      ],
-      "website": "https://secure.e-konsulat.gov.pl/placowki/178/wiza-krajowa/wizyty/weryfikacja-obrazkowa",
-      "free_config": {
-        "query_url": "https://secure.e-konsulat.gov.pl/placowki/178/wiza-krajowa/wizyty/weryfikacja-obrazkowa",
-        "service_type": "wiza krajowa",
-        "location": "Tokio"
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.pl.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Poland"
       }
+    ],
+    "website": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
+    "free_config": {
+      "query_url": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
+      "service_type": "Wiza Schengen",
+      "location": "Dublin"
+    }
+  },
+  {
+    "identifier": "e-konsulat.jp.pl",
+    "debug": false,
+    "enable": false,
+    "need_account": false,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "",
+      "target_instances": 0,
+      "account_cd": 0,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "visaonweb.ie.be",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "local"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 10800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "",
-        "target_instances": 0,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bel_plugin",
-        "plugin_bin": "bel_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.be.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Belgium"
-        }
-      ],
-      "website": "https://visaonweb.diplomatie.be/en",
-      "free_config": {
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "pol_plugin",
+      "plugin_bin": "pol_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.tyo.pl.tourist",
+        "city": "Tokyo",
+        "visa_type": "Tourist",
+        "country": "Poland"
       }
+    ],
+    "website": "https://secure.e-konsulat.gov.pl/placowki/178/wiza-krajowa/wizyty/weryfikacja-obrazkowa",
+    "free_config": {
+      "query_url": "https://secure.e-konsulat.gov.pl/placowki/178/wiza-krajowa/wizyty/weryfikacja-obrazkowa",
+      "service_type": "wiza krajowa",
+      "location": "Tokio"
+    }
+  },
+  {
+    "identifier": "visaonweb.ie.be",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "local"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 10800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "",
+      "target_instances": 0,
+      "account_cd": 0,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "visametric.ie.de",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 900,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "",
-        "account_pool_id": "",
-        "target_instances": 1,
-        "account_cd": 0,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "de_plugin",
-        "plugin_bin": "de_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.de.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Germany"
-        }
-      ],
-      "website": "https://ie-appointment.visametric.com/en",
-      "free_config": {
-        "base_url": "https://ie-appointment.visametric.com",
-        "consularid": 1
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "bel_plugin",
+      "plugin_bin": "bel_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.be.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Belgium"
       }
+    ],
+    "website": "https://visaonweb.diplomatie.be/en",
+    "free_config": {
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
+    }
+  },
+  {
+    "identifier": "visametric.ie.de",
+    "debug": false,
+    "enable": false,
+    "need_account": false,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 900,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "",
+      "account_pool_id": "",
+      "target_instances": 1,
+      "account_cd": 0,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "pernotami.ie.it",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 900,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 1,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 10,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "ita_plugin",
-        "plugin_bin": "ita_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.it.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Italy"
-        }
-      ],
-      "website": "https://prenotami.esteri.it/Home",
-      "free_config": {
-        "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "de_plugin",
+      "plugin_bin": "de_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.de.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Germany"
       }
+    ],
+    "website": "https://ie-appointment.visametric.com/en",
+    "free_config": {
+      "base_url": "https://ie-appointment.visametric.com",
+      "consularid": 1
+    }
+  },
+  {
+    "identifier": "pernotami.ie.it",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 900,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 1,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 10,
+      "max_bookings_per_account": 1
     },
-    {
-      "identifier": "greekemba.ie.gr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 86400,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "booker": {
-        "account_source": "order",
-        "target_instances": 10,
-        "keep_alive": 120,
-        "account_cd": 10800,
-        "booking_cooldown": 30,
-        "max_bookings_per_account": 1
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "grc_plugin",
-        "plugin_bin": "grc_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.gr.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Greece"
-        }
-      ],
-      "website": "https://www.supersaas.com/schedule/GreekEmbassyInDublin/Visas",
-      "free_config": {}
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "ita_plugin",
+      "plugin_bin": "ita_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.it.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Italy"
+      }
+    ],
+    "website": "https://prenotami.esteri.it/Home",
+    "free_config": {
+      "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525"
     }
-  ]
-}
+  },
+  {
+    "identifier": "greekemba.ie.gr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 86400,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "booker": {
+      "account_source": "order",
+      "target_instances": 10,
+      "keep_alive": 120,
+      "account_cd": 10800,
+      "booking_cooldown": 30,
+      "max_bookings_per_account": 1
+    },
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "grc_plugin",
+      "plugin_bin": "grc_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.gr.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Greece"
+      }
+    ],
+    "website": "https://www.supersaas.com/schedule/GreekEmbassyInDublin/Visas",
+    "free_config": {}
+  }
+]

+ 1499 - 1508
config/config_sentinel.json

@@ -1,1584 +1,1575 @@
-{
-  "version": 202606011112,
-  "redis": {
-    "host": "text.skin",
-    "port": 6379,
-    "db": 0,
-    "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
-  },
-  "group_list": [
-    {
-      "identifier": "vfs.ie.nl",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.nl.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.nl.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/nld/login",
-      "free_config": {
-        "mission_code": "nld",
-        "mission_name": "Netherlands",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.nl.tourist": {
-            "center_name": "Netherlands Visa Application Center - Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
-            "vac_code": "NTDB",
-            "category_name": "All Short stay Categories",
-            "category_code": "TA",
-            "subcategory_name": "Tourist",
-            "subcategory_code": "To"
-          }
-        }
+[
+  {
+    "identifier": "vfs.ie.nl",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.nl.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
       }
     },
-    {
-      "identifier": "vfs.sg.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "sg.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.sin.fr.tourist",
-          "city": "Singapore",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/sgp/en/fra/login",
-      "free_config": {
-        "mission_code": "fra",
-        "mission_name": "France",
-        "country_code": "sgp",
-        "country_name": "Singapore",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.sin.fr.tourist": {
-            "center_name": "France Visa Application Center, Singapore",
-            "address": "79 Anson Road #15-01 Singapore 079906",
-            "vac_code": "FRSN",
-            "category_name": "Short Stay",
-            "category_code": "02",
-            "subcategory_name": "Short Stay Tourist, Family Visit",
-            "subcategory_code": "Six"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.nl.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Netherlands"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/nld/login",
+    "free_config": {
+      "mission_code": "nld",
+      "mission_name": "Netherlands",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.nl.tourist": {
+          "center_name": "Netherlands Visa Application Center - Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
+          "vac_code": "NTDB",
+          "category_name": "All Short stay Categories",
+          "category_code": "TA",
+          "subcategory_name": "Tourist",
+          "subcategory_code": "To"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.sg.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "sg.fr.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.au.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "au.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.sin.fr.tourist",
+        "city": "Singapore",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/sgp/en/fra/login",
+    "free_config": {
+      "mission_code": "fra",
+      "mission_name": "France",
+      "country_code": "sgp",
+      "country_name": "Singapore",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.sin.fr.tourist": {
+          "center_name": "France Visa Application Center, Singapore",
+          "address": "79 Anson Road #15-01 Singapore 079906",
+          "vac_code": "FRSN",
+          "category_name": "Short Stay",
+          "category_code": "02",
+          "subcategory_name": "Short Stay Tourist, Family Visit",
+          "subcategory_code": "Six"
         }
+      }
+    }
+  },
+  {
+    "identifier": "vfs.au.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "au.fr.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
+    },
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.syd.fr.tourist",
+        "city": "Sydney",
+        "visa_type": "Tourist",
+        "country": "France"
       },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.syd.fr.tourist",
-          "city": "Sydney",
-          "visa_type": "Tourist",
-          "country": "France"
+      {
+        "weight": 10,
+        "routing_key": "slot.mel.fr.tourist",
+        "city": "Melbourne",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/aus/en/fra/login",
+    "free_config": {
+      "mission_code": "fra",
+      "mission_name": "France",
+      "country_code": "aus",
+      "country_name": "Australia",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.syd.fr.tourist": {
+          "center_name": "France Visa Application Center - Sydney",
+          "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
+          "vac_code": "SYD",
+          "category_name": "VISA",
+          "category_code": "VISA",
+          "subcategory_name": "Short Stay Schengen Visa",
+          "subcategory_code": "ShortStaySchengenVisa"
         },
-        {
-          "weight": 10,
-          "routing_key": "slot.mel.fr.tourist",
-          "city": "Melbourne",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/aus/en/fra/login",
-      "free_config": {
-        "mission_code": "fra",
-        "mission_name": "France",
-        "country_code": "aus",
-        "country_name": "Australia",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.syd.fr.tourist": {
-            "center_name": "France Visa Application Center - Sydney",
-            "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
-            "vac_code": "SYD",
-            "category_name": "VISA",
-            "category_code": "VISA",
-            "subcategory_name": "Short Stay Schengen Visa",
-            "subcategory_code": "ShortStaySchengenVisa"
-          },
-          "slot.mel.fr.tourist": {
-            "center_name": "France Visa Application Center - Melbourne",
-            "address": "Level 5 332 St. Kilda road level 5 Melbourne 3004",
-            "vac_code": "MEL",
-            "category_name": "VISA",
-            "category_code": "VISA",
-            "subcategory_name": "Short Stay Schengen Visa",
-            "subcategory_code": "ShortStaySchengenVisa"
-          }
+        "slot.mel.fr.tourist": {
+          "center_name": "France Visa Application Center - Melbourne",
+          "address": "Level 5 332 St. Kilda road level 5 Melbourne 3004",
+          "vac_code": "MEL",
+          "category_name": "VISA",
+          "category_code": "VISA",
+          "subcategory_name": "Short Stay Schengen Visa",
+          "subcategory_code": "ShortStaySchengenVisa"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.it",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.it.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.gb.it",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.it.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 80,
+        "routing_key": "slot.lon.it.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Italy"
       },
-      "appointment_types": [
-        {
-          "weight": 80,
-          "routing_key": "slot.lon.it.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Italy"
+      {
+        "weight": 20,
+        "routing_key": "slot.man.it.tourist",
+        "city": "Manchester",
+        "visa_type": "Tourist",
+        "country": "Italy"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
+    "free_config": {
+      "mission_code": "ita",
+      "mission_name": "Italy",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.it.tourist": {
+          "center_name": "Italy Visa Application Centre, London",
+          "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
+          "vac_code": "ILON",
+          "category_name": "Italy UK VisaCategory",
+          "category_code": "UKITVED",
+          "subcategory_name": "Tourist/ Business/ EU Family",
+          "subcategory_code": "TBE"
         },
-        {
-          "weight": 20,
-          "routing_key": "slot.man.it.tourist",
-          "city": "Manchester",
-          "visa_type": "Tourist",
-          "country": "Italy"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
-      "free_config": {
-        "mission_code": "ita",
-        "mission_name": "Italy",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.it.tourist": {
-            "center_name": "Italy Visa Application Centre, London",
-            "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
-            "vac_code": "ILON",
-            "category_name": "Italy UK VisaCategory",
-            "category_code": "UKITVED",
-            "subcategory_name": "Tourist/ Business/ EU Family",
-            "subcategory_code": "TBE"
-          },
-          "slot.man.it.tourist": {
-            "center_name": "Italy Visa Application Centre, Manchester",
-            "address": "50 Devonshire Street North, M12 6JH",
-            "vac_code": "IMAN",
-            "category_name": "Italy UK VisaCategory",
-            "category_code": "UKITVED",
-            "subcategory_name": "Tourist/ Business/ EU Family",
-            "subcategory_code": "TBE"
-          }
+        "slot.man.it.tourist": {
+          "center_name": "Italy Visa Application Centre, Manchester",
+          "address": "50 Devonshire Street North, M12 6JH",
+          "vac_code": "IMAN",
+          "category_name": "Italy UK VisaCategory",
+          "category_code": "UKITVED",
+          "subcategory_name": "Tourist/ Business/ EU Family",
+          "subcategory_code": "TBE"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.nl",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.nl.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.gb.nl",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.nl.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 90,
+        "routing_key": "slot.lon.nl.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Netherlands"
       },
-      "appointment_types": [
-        {
-          "weight": 90,
-          "routing_key": "slot.lon.nl.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
+      {
+        "weight": 10,
+        "routing_key": "slot.man.nl.tourist",
+        "city": "Manchester",
+        "visa_type": "Tourist",
+        "country": "Netherlands"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
+    "free_config": {
+      "mission_code": "nld",
+      "mission_name": "Netherland",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.nl.tourist": {
+          "center_name": "Netherlands Visa application centre - London",
+          "address": "66 Wilson Street, EC2A 2BT",
+          "vac_code": "NAKN",
+          "category_name": "Schengen Visa",
+          "category_code": "Schengen Visa",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TA"
         },
-        {
-          "weight": 10,
-          "routing_key": "slot.man.nl.tourist",
-          "city": "Manchester",
-          "visa_type": "Tourist",
-          "country": "Netherlands"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
-      "free_config": {
-        "mission_code": "nld",
-        "mission_name": "Netherland",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.nl.tourist": {
-            "center_name": "Netherlands Visa application centre - London",
-            "address": "66 Wilson Street, EC2A 2BT",
-            "vac_code": "NAKN",
-            "category_name": "Schengen Visa",
-            "category_code": "Schengen Visa",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          },
-          "slot.man.nl.tourist": {
-            "center_name": "Netherlands Visa application centre - Manchester",
-            "address": "50 Devonshire Street North, M12 6JH",
-            "vac_code": "NAKT",
-            "category_name": "Schengen Visa",
-            "category_code": "Schengen Visa",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          }
+        "slot.man.nl.tourist": {
+          "center_name": "Netherlands Visa application centre - Manchester",
+          "address": "50 Devonshire Street North, M12 6JH",
+          "vac_code": "NAKT",
+          "category_name": "Schengen Visa",
+          "category_code": "Schengen Visa",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TA"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.no",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.no.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.gb.no",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.no.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.no.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Norway"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/nor/login",
-      "free_config": {
-        "mission_code": "nor",
-        "mission_name": "Norway",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.no.tourist": {
-            "center_name": "Norway Visa Application Centre, London",
-            "address": "66 Wilson street, EC2A 2BT",
-            "vac_code": "NLON",
-            "category_name": "Schengen Visa C",
-            "category_code": "SCHVISA",
-            "subcategory_name": "Tourist Visa",
-            "subcategory_code": "TOU"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.no.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Norway"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/nor/login",
+    "free_config": {
+      "mission_code": "nor",
+      "mission_name": "Norway",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.no.tourist": {
+          "center_name": "Norway Visa Application Centre, London",
+          "address": "66 Wilson street, EC2A 2BT",
+          "vac_code": "NLON",
+          "category_name": "Schengen Visa C",
+          "category_code": "SCHVISA",
+          "subcategory_name": "Tourist Visa",
+          "subcategory_code": "TOU"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.at",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.at.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.ie.at",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.at.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.at.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.at.tourist": {
-            "center_name": "Austria / Switzerland / Liechtenstein/ Slovenia Visa Application Center, Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "AUT-DUB",
-            "category_name": "Other Visas",
-            "category_code": "Default_Austria_Ireland ",
-            "subcategory_name": "All Visas ",
-            "subcategory_code": "TA"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.at.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/aut/login",
+    "free_config": {
+      "mission_code": "aut",
+      "mission_name": "Austria",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.at.tourist": {
+          "center_name": "Austria / Switzerland / Liechtenstein/ Slovenia Visa Application Center, Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
+          "vac_code": "AUT-DUB",
+          "category_name": "Other Visas",
+          "category_code": "Default_Austria_Ireland ",
+          "subcategory_name": "All Visas ",
+          "subcategory_code": "TA"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.dk",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.dk.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.ie.dk",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.dk.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.dk.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Denmark"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/dnk/login",
-      "free_config": {
-        "mission_code": "dnk",
-        "mission_name": "Denmark",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.dk.tourist": {
-            "center_name": "Denmark Visa Application Center, Dublin ",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "DIDUB",
-            "category_name": "Schengen Visa",
-            "category_code": "SV",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TV"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.dk.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Denmark"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/dnk/login",
+    "free_config": {
+      "mission_code": "dnk",
+      "mission_name": "Denmark",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.dk.tourist": {
+          "center_name": "Denmark Visa Application Center, Dublin ",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
+          "vac_code": "DIDUB",
+          "category_name": "Schengen Visa",
+          "category_code": "SV",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TV"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.fi",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.fi.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.ie.fi",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.fi.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.fi.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Finland"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/fin/login",
-      "free_config": {
-        "mission_code": "fin",
-        "mission_name": "Finland",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.fi.tourist": {
-            "center_name": "Application Centre, Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
-            "vac_code": "Dubb",
-            "category_name": "VISA",
-            "category_code": "S S",
-            "subcategory_name": "Tourist Category",
-            "subcategory_code": "Tourist Category"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.fi.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Finland"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/fin/login",
+    "free_config": {
+      "mission_code": "fin",
+      "mission_name": "Finland",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.fi.tourist": {
+          "center_name": "Application Centre, Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
+          "vac_code": "Dubb",
+          "category_name": "VISA",
+          "category_code": "S S",
+          "subcategory_name": "Tourist Category",
+          "subcategory_code": "Tourist Category"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.hu",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.hu.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.ie.hu",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.hu.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.hu.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Hungary"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/hun/login",
-      "free_config": {
-        "mission_code": "hun",
-        "mission_name": "Hungary",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.hu.tourist": {
-            "center_name": "Ireland Visa Application Center,Dublin",
-            "address": "Cunningham House, 130 Francis Street Dublin",
-            "vac_code": "DUB",
-            "category_name": "Short Stay",
-            "category_code": "SS",
-            "subcategory_name": "Schengen Visa",
-            "subcategory_code": "Schengen Visa"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.hu.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Hungary"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/hun/login",
+    "free_config": {
+      "mission_code": "hun",
+      "mission_name": "Hungary",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.hu.tourist": {
+          "center_name": "Ireland Visa Application Center,Dublin",
+          "address": "Cunningham House, 130 Francis Street Dublin",
+          "vac_code": "DUB",
+          "category_name": "Short Stay",
+          "category_code": "SS",
+          "subcategory_name": "Schengen Visa",
+          "subcategory_code": "Schengen Visa"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.ie.is",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.is.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.ie.is",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.is.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.is.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Iceland"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/irl/en/isl/login",
-      "free_config": {
-        "mission_code": "isl",
-        "mission_name": "Iceland",
-        "country_code": "irl",
-        "country_name": "Ireland",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.dub.is.tourist": {
-            "center_name": "Iceland Visa Application Center- Dublin",
-            "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
-            "vac_code": "DUB",
-            "category_name": "C-Visa",
-            "category_code": "CVI",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "OTT"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.is.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Iceland"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/irl/en/isl/login",
+    "free_config": {
+      "mission_code": "isl",
+      "mission_name": "Iceland",
+      "country_code": "irl",
+      "country_name": "Ireland",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.dub.is.tourist": {
+          "center_name": "Iceland Visa Application Center- Dublin",
+          "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
+          "vac_code": "DUB",
+          "category_name": "C-Visa",
+          "category_code": "CVI",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "OTT"
         }
       }
+    }
+  },
+  {
+    "identifier": "vfs.gb.at",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.at.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "vfs.gb.at",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.at.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.at.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/gbr/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "gbr",
-        "country_name": "United Kingdom",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.lon.at.tourist": {
-            "center_name": "Austria Visa Application Centre, London",
-            "address": "66 Wilson Street, EC2A 2BT",
-            "vac_code": "ADN",
-            "category_name": "Visa  to  Austria",
-            "category_code": "Visa  to  Austria",
-            "subcategory_name": "Tourism",
-            "subcategory_code": "TA"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.at.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/gbr/en/aut/login",
+    "free_config": {
+      "mission_code": "aut",
+      "mission_name": "Austria",
+      "country_code": "gbr",
+      "country_name": "United Kingdom",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.lon.at.tourist": {
+          "center_name": "Austria Visa Application Centre, London",
+          "address": "66 Wilson Street, EC2A 2BT",
+          "vac_code": "ADN",
+          "category_name": "Visa  to  Austria",
+          "category_code": "Visa  to  Austria",
+          "subcategory_name": "Tourism",
+          "subcategory_code": "TA"
         }
       }
+    }
+  },
+  {
+    "identifier": "bls.ie.es",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "spain-isp"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 3600,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.es.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 180,
+        "random_max": 360
+      }
     },
-    {
-      "identifier": "bls.ie.es",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "spain-isp"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 3600,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.es.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 180,
-          "random_max": 360
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bls_plugin",
-        "plugin_bin": "bls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.es.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Spain"
-        }
-      ],
-      "website": "https://ireland.blsspainglobal.com/Global/bls/visatypeverification",
-      "free_config": {
-        "domain": "ireland.blsspainglobal.com",
-        "ocr_model": "data/ctc.pth",
-        "apt_configs": {
-          "slot.dub.es.tourist": {
-            "location": "Dublin",
-            "jurisdiction": null,
-            "visa_type": "Schengen Visa/ Short Term Visa",
-            "visa_subtype": "Tourist Visa",
-            "appointment_type": "Individual",
-            "appointment_category": "Normal",
-            "mission_code": "EMBASSY_DUBLIN"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "bls_plugin",
+      "plugin_bin": "bls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.es.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Spain"
+      }
+    ],
+    "website": "https://ireland.blsspainglobal.com/Global/bls/visatypeverification",
+    "free_config": {
+      "domain": "ireland.blsspainglobal.com",
+      "ocr_model": "data/ctc.pth",
+      "apt_configs": {
+        "slot.dub.es.tourist": {
+          "location": "Dublin",
+          "jurisdiction": null,
+          "visa_type": "Schengen Visa/ Short Term Visa",
+          "visa_subtype": "Tourist Visa",
+          "appointment_type": "Individual",
+          "appointment_category": "Normal",
+          "mission_code": "EMBASSY_DUBLIN"
         }
       }
+    }
+  },
+  {
+    "identifier": "bls.gb.es",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "spain-isp"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 3600,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "gb.es.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
     },
-    {
-      "identifier": "bls.gb.es",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "spain-isp"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 3600,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "gb.es.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bls_plugin",
-        "plugin_bin": "bls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.es.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "Spain"
-        }
-      ],
-      "website": "https://uk.blsspainglobal.com/Global/bls/visatypeverification",
-      "free_config": {
-        "domain": "uk.blsspainglobal.com",
-        "ocr_model": "data/ocr.pth",
-        "apt_configs": {
-          "slot.lon.es.tourist": {
-            "location": "Dublin",
-            "jurisdiction": "Greater London",
-            "visa_type": "Short Term Visa(Maximum stay of 90 days)",
-            "visa_subtype": "Tourist Visa",
-            "appointment_type": "Individual",
-            "appointment_category": "Normal",
-            "mission_code": "LHR"
-          }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "bls_plugin",
+      "plugin_bin": "bls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.es.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "Spain"
+      }
+    ],
+    "website": "https://uk.blsspainglobal.com/Global/bls/visatypeverification",
+    "free_config": {
+      "domain": "uk.blsspainglobal.com",
+      "ocr_model": "data/ocr.pth",
+      "apt_configs": {
+        "slot.lon.es.tourist": {
+          "location": "Dublin",
+          "jurisdiction": "Greater London",
+          "visa_type": "Short Term Visa(Maximum stay of 90 days)",
+          "visa_subtype": "Tourist Visa",
+          "appointment_type": "Individual",
+          "appointment_category": "Normal",
+          "mission_code": "LHR"
         }
       }
+    }
+  },
+  {
+    "identifier": "tls.gb.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "07:00",
+    "active_time_end": "17:30",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "tls.gb.lon.fr.sentinel",
+      "target_instances": 3,
+      "account_cd": 1800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 55,
+        "random_max": 65
+      }
     },
-    {
-      "identifier": "tls.gb.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "07:00",
-      "active_time_end": "17:30",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.gb.lon.fr.sentinel",
-        "target_instances": 3,
-        "account_cd": 1800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 55,
-          "random_max": 65
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.lon.fr.tourist",
-          "city": "London",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
-        "location": "London",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.lon.fr.tourist",
+        "city": "London",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
+      "location": "London",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "tls.cn.bjs.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:00",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "tls.cn.bjs.fr.sentinel",
+      "target_instances": 2,
+      "account_cd": 1800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 55,
+        "random_max": 65
       }
     },
-    {
-      "identifier": "tls.cn.bjs.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:00",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.cn.bjs.fr.sentinel",
-        "target_instances": 2,
-        "account_cd": 1800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 55,
-          "random_max": 65
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.bjs.fr.tourist",
-          "city": "Beijing",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
-        "location": "Beijing",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.bjs.fr.tourist",
+        "city": "Beijing",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnBJS2fr",
+      "location": "Beijing",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "tls.cn.hgh.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:00",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "tls.cn.hgh.fr.sentinel",
+      "target_instances": 3,
+      "account_cd": 1800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 55,
+        "random_max": 65
       }
     },
-    {
-      "identifier": "tls.cn.hgh.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:00",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.cn.hgh.fr.sentinel",
-        "target_instances": 3,
-        "account_cd": 1800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 55,
-          "random_max": 65
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.hgh.fr.tourist",
-          "city": "Hangzhou",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
-        "location": "Hangzhou",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.hgh.fr.tourist",
+        "city": "Hangzhou",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnHGH2fr",
+      "location": "Hangzhou",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "tls.cn.can.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:00",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "tls.cn.can.fr.sentinel",
+      "target_instances": 5,
+      "account_cd": 1800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 55,
+        "random_max": 65
       }
     },
-    {
-      "identifier": "tls.cn.can.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:00",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.cn.can.fr.sentinel",
-        "target_instances": 5,
-        "account_cd": 1800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 55,
-          "random_max": 65
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.can.fr.tourist",
-          "city": "Guangzhou",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnCAN2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnCAN2fr",
-        "location": "Guangzhou",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.can.fr.tourist",
+        "city": "Guangzhou",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnCAN2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnCAN2fr",
+      "location": "Guangzhou",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "tls.cn.sha.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "15:00",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "tls.cn.sha.fr.sentinel",
+      "target_instances": 1,
+      "account_cd": 1800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 55,
+        "random_max": 65
       }
     },
-    {
-      "identifier": "tls.cn.sha.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "15:00",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.cn.sha.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 55,
-          "random_max": 65
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.sha.fr.tourist",
-          "city": "Shanghai",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
-        "location": "Shanghai",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.sha.fr.tourist",
+        "city": "Shanghai",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
+      "location": "Shanghai",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "tls.ie.fr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap-good"
+    ],
+    "proxy_cd": 900,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "tls.ie.fr.sentinel",
+      "target_instances": 1,
+      "account_cd": 1800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
       }
     },
-    {
-      "identifier": "tls.ie.fr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap-good"
-      ],
-      "proxy_cd": 900,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "tls.ie.fr.sentinel",
-        "target_instances": 1,
-        "account_cd": 1800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "tls_plugin",
-        "plugin_bin": "tls_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.fr.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "France"
-        }
-      ],
-      "website": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
-      "free_config": {
-        "tls_url": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
-        "location": "Dublin",
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-        "login_captcha": {
-          "solve_advance": false,
-          "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-          "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-          "task": "ReCaptchaV2TaskProxyLess"
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.fr.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "website": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/ie/vac/ieDUB2fr",
+      "location": "Dublin",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "vfs.cn.at",
+    "debug": false,
+    "enable": true,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "cn.at.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
       }
     },
-    {
-      "identifier": "vfs.cn.at",
-      "debug": false,
-      "enable": true,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "cn.at.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 0,
+        "routing_key": "slot.bjs.at.tourist",
+        "city": "Beijing",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      },
+      {
+        "weight": 0,
+        "routing_key": "slot.can.at.tourist",
+        "city": "Guangzhou",
+        "visa_type": "Tourist",
+        "country": "Austria"
       },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "vfs_plugin",
-        "plugin_bin": "vfs_plugin.py",
-        "plugin_proto": "IVSPlg"
+      {
+        "weight": 0,
+        "routing_key": "slot.szx.at.tourist",
+        "city": "Shenzhen",
+        "visa_type": "Tourist",
+        "country": "Austria"
       },
-      "appointment_types": [
-        {
-          "weight": 0,
-          "routing_key": "slot.bjs.at.tourist",
-          "city": "Beijing",
-          "visa_type": "Tourist",
-          "country": "Austria"
+      {
+        "weight": 1,
+        "routing_key": "slot.sha.at.tourist",
+        "city": "Shanghai",
+        "visa_type": "Tourist",
+        "country": "Austria"
+      }
+    ],
+    "website": "https://visa.vfsglobal.com/chn/en/aut/login",
+    "free_config": {
+      "mission_code": "aut",
+      "mission_name": "Austria",
+      "country_code": "chn",
+      "country_name": "China",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.bjs.at.tourist": {
+          "center_name": "Austria Visa Application Center, Beijing",
+          "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
+          "vac_code": "Bei",
+          "category_name": "Visa Type C-Schengen",
+          "category_code": "VTC",
+          "subcategory_name": "Tourism (90 days)",
+          "subcategory_code": "TR"
         },
-        {
-          "weight": 0,
-          "routing_key": "slot.can.at.tourist",
-          "city": "Guangzhou",
-          "visa_type": "Tourist",
-          "country": "Austria"
+        "slot.can.at.tourist": {
+          "center_name": "Austria Visa Application Center, Guangzhou",
+          "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
+          "vac_code": "Gua",
+          "category_name": "Visa Type C-Schengen",
+          "category_code": "VTC",
+          "subcategory_name": "Tourism (90 days)",
+          "subcategory_code": "TR"
         },
-        {
-          "weight": 0,
-          "routing_key": "slot.szx.at.tourist",
-          "city": "Shenzhen",
-          "visa_type": "Tourist",
-          "country": "Austria"
+        "slot.sha.at.tourist": {
+          "center_name": "Austria Visa Application Center, Shanghai",
+          "address": "3F, Jiushi Commercial Building, No. 213, Middle Sichuan Road, Huangpu District",
+          "vac_code": "Shaa",
+          "category_name": "Visa Type C - Tourism Visa",
+          "category_code": "TT",
+          "subcategory_name": "Visa Type C - Tourism Visa",
+          "subcategory_code": "VTCT"
         },
-        {
-          "weight": 1,
-          "routing_key": "slot.sha.at.tourist",
-          "city": "Shanghai",
-          "visa_type": "Tourist",
-          "country": "Austria"
-        }
-      ],
-      "website": "https://visa.vfsglobal.com/chn/en/aut/login",
-      "free_config": {
-        "mission_code": "aut",
-        "mission_name": "Austria",
-        "country_code": "chn",
-        "country_name": "China",
-        "culture_code": "en-US",
-        "language": "en",
-        "apt_configs": {
-          "slot.bjs.at.tourist": {
-            "center_name": "Austria Visa Application Center, Beijing",
-            "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
-            "vac_code": "Bei",
-            "category_name": "Visa Type C-Schengen",
-            "category_code": "VTC",
-            "subcategory_name": "Tourism (90 days)",
-            "subcategory_code": "TR"
-          },
-          "slot.can.at.tourist": {
-            "center_name": "Austria Visa Application Center, Guangzhou",
-            "address": "7/F, GAL Tower, No. 78, Pazhou Avenue, Haizhu District",
-            "vac_code": "Gua",
-            "category_name": "Visa Type C-Schengen",
-            "category_code": "VTC",
-            "subcategory_name": "Tourism (90 days)",
-            "subcategory_code": "TR"
-          },
-          "slot.sha.at.tourist": {
-            "center_name": "Austria Visa Application Center, Shanghai",
-            "address": "3F, Jiushi Commercial Building, No. 213, Middle Sichuan Road, Huangpu District",
-            "vac_code": "Shaa",
-            "category_name": "Visa Type C - Tourism Visa",
-            "category_code": "TT",
-            "subcategory_name": "Visa Type C - Tourism Visa",
-            "subcategory_code": "VTCT"
-          },
-          "slot.szx.at.tourist": {
-            "center_name": "Austria Visa Application Center, Shenzhen",
-            "address": "Unit 2303, Floor 23, East Tower, C Future City, No. 9285 Binhe Avenue, Futian District",
-            "vac_code": "ASSZA",
-            "category_name": "Visa Type C-Schengen",
-            "category_code": "VTC",
-            "subcategory_name": "Tourism (90 days)",
-            "subcategory_code": "TR"
-          }
+        "slot.szx.at.tourist": {
+          "center_name": "Austria Visa Application Center, Shenzhen",
+          "address": "Unit 2303, Floor 23, East Tower, C Future City, No. 9285 Binhe Avenue, Futian District",
+          "vac_code": "ASSZA",
+          "category_name": "Visa Type C-Schengen",
+          "category_code": "VTC",
+          "subcategory_name": "Tourism (90 days)",
+          "subcategory_code": "TR"
         }
       }
-    },
-    {
-      "identifier": "e-konsulat.ie.pl",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 1800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "",
-        "account_pool_id": "",
-        "target_instances": 1,
-        "account_cd": 0,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "pol_plugin",
-        "plugin_bin": "pol_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.pl.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Poland"
-        }
-      ],
-      "website": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
-      "free_config": {
-        "query_url": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
-        "service_type": "Wiza Schengen",
-        "location": "Dublin"
+    }
+  },
+  {
+    "identifier": "e-konsulat.ie.pl",
+    "debug": false,
+    "enable": false,
+    "need_account": false,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "",
+      "account_pool_id": "",
+      "target_instances": 1,
+      "account_cd": 0,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
       }
     },
-    {
-      "identifier": "visaonweb.ie.be",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "local"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 10800,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.be.sentinel",
-        "target_instances": 1,
-        "account_cd": 3600,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "bel_plugin",
-        "plugin_bin": "bel_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.be.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Belgium"
-        }
-      ],
-      "website": "https://visaonweb.diplomatie.be/en",
-      "free_config": {
-        "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "pol_plugin",
+      "plugin_bin": "pol_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.pl.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Poland"
+      }
+    ],
+    "website": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
+    "free_config": {
+      "query_url": "https://secure.e-konsulat.gov.pl/placowki/151/wiza-schengen/wizyty/weryfikacja-obrazkowa",
+      "service_type": "Wiza Schengen",
+      "location": "Dublin"
+    }
+  },
+  {
+    "identifier": "visaonweb.ie.be",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "local"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 10800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.be.sentinel",
+      "target_instances": 1,
+      "account_cd": 3600,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
       }
     },
-    {
-      "identifier": "visametric.ie.de",
-      "debug": false,
-      "enable": false,
-      "need_account": false,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 900,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "",
-        "account_pool_id": "",
-        "target_instances": 1,
-        "account_cd": 0,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "de_plugin",
-        "plugin_bin": "de_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.de.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Germany"
-        }
-      ],
-      "website": "https://ie-appointment.visametric.com/en",
-      "free_config": {
-        "base_url": "https://ie-appointment.visametric.com",
-        "consularid": 1
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "bel_plugin",
+      "plugin_bin": "bel_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.be.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Belgium"
+      }
+    ],
+    "website": "https://visaonweb.diplomatie.be/en",
+    "free_config": {
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
+    }
+  },
+  {
+    "identifier": "visametric.ie.de",
+    "debug": false,
+    "enable": false,
+    "need_account": false,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 900,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "",
+      "account_pool_id": "",
+      "target_instances": 1,
+      "account_cd": 0,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
       }
     },
-    {
-      "identifier": "pernotami.ie.it",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 900,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.it.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "ita_plugin",
-        "plugin_bin": "ita_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.it.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Italy"
-        }
-      ],
-      "website": "https://prenotami.esteri.it/Home",
-      "free_config": {
-        "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525"
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "de_plugin",
+      "plugin_bin": "de_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.de.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Germany"
+      }
+    ],
+    "website": "https://ie-appointment.visametric.com/en",
+    "free_config": {
+      "base_url": "https://ie-appointment.visametric.com",
+      "consularid": 1
+    }
+  },
+  {
+    "identifier": "pernotami.ie.it",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 900,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.it.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
       }
     },
-    {
-      "identifier": "greekemba.ie.gr",
-      "debug": false,
-      "enable": false,
-      "need_account": true,
-      "need_proxy": true,
-      "proxy_pool": [
-        "proxy-cheap"
-      ],
-      "proxy_cd": 300,
-      "session_max_life": 86400,
-      "active_time_start": "00:00",
-      "active_time_end": "23:59",
-      "sentinel": {
-        "account_source": "built-in",
-        "account_pool_id": "ie.gr.sentinel",
-        "target_instances": 1,
-        "account_cd": 10800,
-        "signal_ttl": 30,
-        "query_wait": {
-          "mode": "Random",
-          "fixed_wait": 10,
-          "random_min": 60,
-          "random_max": 300
-        }
-      },
-      "plugin_config": {
-        "lib_path": "plugins",
-        "plugin_name": "grc_plugin",
-        "plugin_bin": "grc_plugin.py",
-        "plugin_proto": "IVSPlg"
-      },
-      "appointment_types": [
-        {
-          "weight": 10,
-          "routing_key": "slot.dub.gr.tourist",
-          "city": "Dublin",
-          "visa_type": "Tourist",
-          "country": "Greece"
-        }
-      ],
-      "website": "https://www.supersaas.com/schedule/GreekEmbassyInDublin/Visas",
-      "free_config": {}
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "ita_plugin",
+      "plugin_bin": "ita_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.it.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Italy"
+      }
+    ],
+    "website": "https://prenotami.esteri.it/Home",
+    "free_config": {
+      "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525"
     }
-  ]
-}
+  },
+  {
+    "identifier": "greekemba.ie.gr",
+    "debug": false,
+    "enable": false,
+    "need_account": true,
+    "need_proxy": true,
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "proxy_cd": 300,
+    "session_max_life": 86400,
+    "active_time_start": "00:00",
+    "active_time_end": "23:59",
+    "sentinel": {
+      "account_source": "built-in",
+      "account_pool_id": "ie.gr.sentinel",
+      "target_instances": 1,
+      "account_cd": 10800,
+      "signal_ttl": 30,
+      "query_wait": {
+        "mode": "Random",
+        "fixed_wait": 10,
+        "random_min": 60,
+        "random_max": 300
+      }
+    },
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "grc_plugin",
+      "plugin_bin": "grc_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.dub.gr.tourist",
+        "city": "Dublin",
+        "visa_type": "Tourist",
+        "country": "Greece"
+      }
+    ],
+    "website": "https://www.supersaas.com/schedule/GreekEmbassyInDublin/Visas",
+    "free_config": {}
+  }
+]

+ 200 - 0
config/config_standalone.json

@@ -0,0 +1,200 @@
+[
+  {
+    "identifier": "tls",
+    "debug": true,
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "tls_plugin",
+      "plugin_bin": "tls_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "account": {
+      "username": "18305503085@163.com",
+      "password": "Zhangww@201207"
+    },
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "query_wait": {
+      "mode": "Random",
+      "fixed_wait": 10,
+      "random_min": 55,
+      "random_max": 65
+    },
+    "login_interval": 1800,
+    "session_max_life": 3600,
+    "active_time_start": "00:00",
+    "active_time_end": "23:30",
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.sha.fr.tourist",
+        "city": "Shanghai",
+        "visa_type": "Tourist",
+        "country": "France"
+      }
+    ],
+    "user_preferences": {
+      "support_pta": false,
+      "expected_end_date": "2024-07-01",
+      "expected_start_date": "2024-07-20"
+    },
+    "free_config": {
+      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
+      "location": "Shanghai",
+      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+      "login_captcha": {
+        "solve_advance": false,
+        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
+        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
+        "task": "ReCaptchaV2TaskProxyLess"
+      }
+    }
+  },
+  {
+    "identifier": "usa",
+    "debug": true,
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "usa_plugin",
+      "plugin_bin": "usa_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "account": {
+      "username": "Max0105888",
+      "password": "Zl265498"
+    },
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "query_wait": {
+      "mode": "Random",
+      "fixed_wait": 10,
+      "random_min": 60,
+      "random_max": 600
+    },
+    "login_interval": 3600,
+    "session_max_life": 3600,
+    "active_time_start": "00:00",
+    "active_time_end": "23:30",
+    "appointment_types": [
+      {
+        "weight": 10,
+        "routing_key": "slot.wuh.us.tourist",
+        "city": "Wuhan",
+        "visa_type": "Tourist",
+        "country": "USA"
+      }
+    ],
+    "user_preferences": {
+      "expected_date_start": "2026-06-01",
+      "expected_date_end": "2026-08-05"
+    },
+    "free_config": {
+      "applicant": "jinhao ma",
+      "usa_url": "https://www.usvisascheduling.com",
+      "location": "WUHAN",
+      "security": {
+        "1_quest": {
+          "q": "What is your mother's surname?",
+          "a": "zhou"
+        },
+        "2_quest": {
+          "q": "What is your least favorite food?",
+          "a": "kfc"
+        },
+        "3_quest": {
+          "q": "Who was your childhood hero?",
+          "a": "123456"
+        }
+      }
+    }
+  },
+  {
+    "identifier": "vfs",
+    "debug": true,
+    "plugin_config": {
+      "lib_path": "plugins",
+      "plugin_name": "vfs_plugin",
+      "plugin_bin": "vfs_plugin.py",
+      "plugin_proto": "IVSPlg"
+    },
+    "account": {
+      "username": "italyvisa888@text.skin",
+      "password": "Visafly@111"
+    },
+    "proxy_pool": [
+      "proxy-cheap"
+    ],
+    "query_wait": {
+      "mode": "Random",
+      "fixed_wait": 10,
+      "random_min": 120,
+      "random_max": 180
+    },
+    "login_interval": 7200,
+    "session_max_life": 1800,
+    "active_time_start": "00:00",
+    "active_time_end": "23:30",
+    "appointment_types": [
+      {
+        "weight": 0,
+        "routing_key": "slot.csx.it.tourist",
+        "city": "Changsha",
+        "visa_type": "Tourist",
+        "country": "Italy"
+      },
+      {
+        "weight": 10,
+        "routing_key": "slot.bjs.it.tourist",
+        "city": "Beijing",
+        "visa_type": "Tourist",
+        "country": "Italy"
+      }
+    ],
+    "user_preferences": {
+      "email": "italyvisa666@text.skin",
+      "phone": "019074224604",
+      "gender": "female",
+      "birthday": "1997-09-09",
+      "last_name": "ZHAO",
+      "first_name": "LINJIE",
+      "nationality": "China",
+      "passport_no": "EE4571001",
+      "expected_end_date": "2026-07-03",
+      "phone_country_code": "86",
+      "expected_start_date": "2026-06-12",
+      "passport_expiry_date": "2028-10-08",
+      "social_media_account": "13370148298",
+      "passport_image_url": "data/passport.jpg"
+    },
+    "free_config": {
+      "mission_code": "ita",
+      "mission_name": "Italy",
+      "country_code": "chn",
+      "country_name": "China",
+      "culture_code": "en-US",
+      "language": "en",
+      "apt_configs": {
+        "slot.csx.it.tourist": {
+          "center_name": "Italy Visa Application Center, Changsha",
+          "address": "1F, Changsha Rongchuang Steigenberger Hotel, No. 3-9, Xueyuan Road, Changsha County, Changsha City",
+          "vac_code": "CGA",
+          "category_name": "SchengenVisa",
+          "category_code": "Schengen Visa",
+          "subcategory_name": "C visa / Schengen visa",
+          "subcategory_code": "Schengen "
+        },
+        "slot.bjs.it.tourist": {
+          "center_name": "Italy Visa Application Center, Beijing",
+          "address": "East 101, Floor B1, Block C, Guanghua Lu SOHO II, No. 9 Guanghua Lu, Chaoyang District",
+          "vac_code": "BJII",
+          "category_name": "Beijing Italy",
+          "category_code": "Beijing Italy",
+          "subcategory_name": "Standard Beijing Italy",
+          "subcategory_code": "BJ Italy Standard"
+        }
+      }
+    }
+  }
+]

+ 0 - 57
config/config_tls.json.example

@@ -1,57 +0,0 @@
-{
-  "redis": {
-    "host": "text.skin",
-    "port": 6379,
-    "db": 0,
-    "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
-  },
-  "booker_standalone_config": {
-    "debug": true,
-    "plugin_config": {
-      "lib_path": "plugins",
-      "plugin_name": "tls_plugin",
-      "plugin_bin": "tls_plugin.py",
-      "plugin_proto": "IVSPlg"
-    },
-    "account": {
-      "username": "18305503085@163.com",
-      "password": "Zhangww@201207"
-    },
-    "proxy_pool": ["proxy-cheap"],
-    "query_wait": {
-      "mode": "Random",
-      "fixed_wait": 10,
-      "random_min": 55,
-      "random_max": 65
-    },
-    "login_interval": 1800,
-    "session_max_life": 3600,
-    "active_time_start": "00:00",
-    "active_time_end": "23:30",
-    "appointment_types": [
-      {
-        "weight": 10,
-        "routing_key": "slot.sha.fr.tourist",
-        "city": "Shanghai",
-        "visa_type": "Tourist",
-        "country": "France"
-      }
-    ],
-    "user_preferences": {
-      "support_pta": false,
-      "expected_end_date": "2024-07-01",
-      "expected_start_date": "2024-07-20"
-    },
-    "free_config": {
-      "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnSHA2fr",
-      "location": "Shanghai",
-      "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-      "login_captcha": {
-        "solve_advance": false,
-        "site_key": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0",
-        "page_url": "https://i2-auth.visas-fr.tlscontact.com",
-        "task": "ReCaptchaV2TaskProxyLess"
-      }
-    }
-  }
-}

+ 0 - 66
config/config_usa.json.example

@@ -1,66 +0,0 @@
-{
-  "redis": {
-    "host": "text.skin",
-    "port": 6379,
-    "db": 0,
-    "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
-  },
-  "booker_standalone_config": {
-    "debug": true,
-    "plugin_config": {
-      "lib_path": "plugins",
-      "plugin_name": "usa_plugin",
-      "plugin_bin": "usa_plugin.py",
-      "plugin_proto": "IVSPlg"
-    },
-    "account": {
-      "username": "Max0105888",
-      "password": "Zl265498"
-    },
-    "proxy_pool": [
-      "proxy-cheap"
-    ],
-    "query_wait": {
-      "mode": "Random",
-      "fixed_wait": 10,
-      "random_min": 60,
-      "random_max": 600
-    },
-    "login_interval": 3600,
-    "session_max_life": 3600,
-    "active_time_start": "00:00",
-    "active_time_end": "23:30",
-    "appointment_types": [
-      {
-        "weight": 10,
-        "routing_key": "slot.wuh.us.tourist",
-        "city": "Wuhan",
-        "visa_type": "Tourist",
-        "country": "USA"
-      }
-    ],
-    "user_preferences": {
-      "expected_date_start": "2026-06-01",
-      "expected_date_end": "2026-08-05"
-    },
-    "free_config": {
-      "applicant": "jinhao ma",
-      "usa_url": "https://www.usvisascheduling.com",
-      "location": "WUHAN",
-      "security": {
-        "1_quest": {
-          "q": "What is your mother's surname?",
-          "a": "zhou"
-        },
-        "2_quest": {
-          "q": "What is your least favorite food?",
-          "a": "kfc"
-        },
-        "3_quest": {
-          "q": "Who was your childhood hero?",
-          "a": "123456"
-        }
-      }
-    }
-  }
-}

+ 0 - 94
config/config_vfs.json.example

@@ -1,94 +0,0 @@
-{
-    "redis": {
-        "host": "text.skin",
-        "port": 6379,
-        "db": 0,
-        "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
-    },
-    "booker_standalone_config": {
-        "debug": true,
-        "plugin_config": {
-            "lib_path": "plugins",
-            "plugin_name": "vfs_plugin",
-            "plugin_bin": "vfs_plugin.py",
-            "plugin_proto": "IVSPlg"
-        },
-        "account": {
-            "username": "italyvisa888@text.skin",
-            "password": "Visafly@111"
-        },
-        "proxy_pool": [
-            "proxy-cheap"
-        ],
-        "query_wait": {
-            "mode": "Random",
-            "fixed_wait": 10,
-            "random_min": 120,
-            "random_max": 180
-        },
-        "login_interval": 7200,
-        "session_max_life": 1800,
-        "active_time_start": "00:00",
-        "active_time_end": "23:30",
-        "appointment_types": [
-            {
-                "weight": 0,
-                "routing_key": "slot.csx.it.tourist",
-                "city": "Changsha",
-                "visa_type": "Tourist",
-                "country": "Italy"
-            },
-            {
-                "weight": 10,
-                "routing_key": "slot.bjs.it.tourist",
-                "city": "Beijing",
-                "visa_type": "Tourist",
-                "country": "Italy"
-            }
-        ],
-        "user_preferences": {
-            "email": "italyvisa666@text.skin",
-            "phone": "019074224604",
-            "gender": "female",
-            "birthday": "1997-09-09",
-            "last_name": "ZHAO",
-            "first_name": "LINJIE",
-            "nationality": "China",
-            "passport_no": "EE4571001",
-            "expected_end_date": "2026-07-03",
-            "phone_country_code": "86",
-            "expected_start_date": "2026-06-12",
-            "passport_expiry_date": "2028-10-08",
-            "social_media_account": "13370148298",
-            "passport_image_url": "data/passport.jpg"
-        },
-        "free_config": {
-            "mission_code": "ita",
-            "mission_name": "Italy",
-            "country_code": "chn",
-            "country_name": "China",
-            "culture_code": "en-US",
-            "language": "en",
-            "apt_configs": {
-                "slot.csx.it.tourist": {
-                    "center_name": "Italy Visa Application Center, Changsha",
-                    "address": "1F, Changsha Rongchuang Steigenberger Hotel, No. 3-9, Xueyuan Road, Changsha County, Changsha City",
-                    "vac_code": "CGA",
-                    "category_name": "SchengenVisa",
-                    "category_code": "Schengen Visa",
-                    "subcategory_name": "C visa / Schengen visa",
-                    "subcategory_code": "Schengen "
-                },
-                "slot.bjs.it.tourist": {
-                    "center_name": "Italy Visa Application Center, Beijing",
-                    "address": "East 101, Floor B1, Block C, Guanghua Lu SOHO II, No. 9 Guanghua Lu, Chaoyang District",
-                    "vac_code": "BJII",
-                    "category_name": "Beijing Italy",
-                    "category_code": "Beijing Italy",
-                    "subcategory_name": "Standard Beijing Italy",
-                    "subcategory_code": "BJ Italy Standard"
-                }
-            }
-        }
-    }
-}

+ 16 - 11
configure.py

@@ -1,5 +1,10 @@
 # 测试任务,这里配置以后任务轮转函数直接使用这个任务
-
+REDIS_CFG = {
+    "host": "text.skin",
+    "port": 6379,
+    "db": 0,
+    "password": "STEs2x6ML0U1HlpE9SojM6YU7QPhqzY8"
+}
 
 TEST_TASK = None
 # TEST_TASK = {
@@ -53,15 +58,15 @@ TEST_ACCOUNT = None
 
 # 测试代理,这里配置以后ip轮转函数直接使用这个代理
 TEST_PROXY = None
-# TEST_PROXY = {
-#     "pool_name": "local",
-#     "proto": "http",
-#     "ip": "127.0.0.1",
-#     "port": 7890,
-#     "username": "",
-#     "password": "",
-#     "id": 0
-# }
+TEST_PROXY = {
+    "pool_name": "local",
+    "proto": "http",
+    "ip": "127.0.0.1",
+    "port": 7890,
+    "username": "",
+    "password": "",
+    "id": 0
+}
 
 
 # Chrome bin 的路径, 这个优先级最高,其次CHROME_BIN 的环境变量,最后系统默认值
@@ -71,7 +76,7 @@ CHROME_PATH = None
 
 # Mohomo bin 的路径, 这个优先级最高,其次MIHOMO_BIN 的环境变量,最后系统默认值
 MIHOMO_BIN_PATH = None
-# MIHOMO_BIN_PATH = 'mihomo-windows-amd64-alpha-98aa7e6/mihomo-windows-amd64.exe'
+# MIHOMO_BIN_PATH = 'downloads/mihomo'
 
 
 # proxy tunnel 中继节点, 列表中随机选择

+ 0 - 325
france_visa_registration_bot.py

@@ -1,325 +0,0 @@
-import time
-import json
-import os
-import re
-import uuid
-import socket
-import shutil
-import random
-import requests
-import argparse
-import concurrent.futures
-import base64
-from urllib.parse import urlencode
-from datetime import datetime, timedelta
-from typing import Optional, Dict
-from DrissionPage.common import Keys
-from DrissionPage import ChromiumPage, ChromiumOptions
-
-import configure
-from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
-from toolkit.vs_cloud_api import VSCloudApi
-from toolkit.proxy_tunnel import ProxyTunnel
-from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
-from utils.mouse import HumanMouse
-from utils.keyboard import HumanKeyboard
-from utils.scroll import HumanScroll
-from utils.fingerprint_utils import FingerprintGenerator
-from toolkit.captcha_breaker import recognize_captcha_with_qwen
-
-    
-def load_proxies(pool_name):
-    """从 config/proxies.json 读取对应的代理池"""
-    config_path = os.path.join(os.path.dirname(__file__), 'config', 'proxies.json')
-    try:
-        with open(config_path, 'r', encoding='utf-8') as f:
-            data = json.load(f)
-            proxies = data.get(pool_name, [])
-            if not proxies:
-                raise ValueError(f"代理池 '{pool_name}' 为空或不存在!")
-            return proxies
-    except Exception as e:
-        print(f"读取代理配置文件失败: {e}")
-        exit(1) 
-
-class FranceVisaRegistrator:
-    def __init__(self, france_visa_url, proxy_config: Optional[Dict]=None, capsolver_key: Optional[str]=None, user_inputs: Optional[Dict]=None):
-        self.proxy_config = proxy_config
-        self.capsolver_key = capsolver_key
-        self.user_inputs = user_inputs
-        # 隔离的用户数据目录
-        self.instance_id = uuid.uuid4().hex[:8]
-        self.france_visa_url = france_visa_url
-        # self.instance_id = '18d389e9'
-        self.workspace = os.path.abspath(os.path.join("data/temp_browser_data", f"reg_session_{self.instance_id}"))
-        self.page = None
-        self.mouse = None
-        self.keyboard = None
-        
-        # 持有隧道实例
-        self.tunnel = None
-
-    def _log(self, msg):
-        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
-        print(f"[{now}][TLS-Reg-{self.instance_id}] {msg}")
-
-    def _get_free_port(self):
-        """获取可用端口,防止 DrissionPage 解析日志报错"""
-        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
-            s.bind(('', 0))
-            return s.getsockname()[1]
-        
-    def save_screenshot(self, name_prefix):
-        try:
-            timestamp = int(time.time())
-            filename = f"{self.instance_id}_{name_prefix}_{timestamp}.jpg"
-            save_path = os.path.join("data", filename)
-            os.makedirs("data", exist_ok=True)
-            self.page.get_screenshot(path=save_path, full_page=False)
-            self._log(f"Screenshot saved to {save_path}")
-        except Exception as e:
-            self._log(f"Failed to save screenshot: {e}")
-
-    def init_browser(self):
-        """初始化独立、配置好代理的浏览器环境"""
-        self._log("Initializing browser...")
-        co = ChromiumOptions()
-        
-        # 1. 端口与路径隔离
-        port = self._get_free_port()
-        co.set_local_port(port)
-        co.set_user_data_path(self.workspace)
-        
-        chrome_path = configure.CHROME_PATH
-        if not chrome_path:
-            chrome_path = os.getenv("CHROME_BIN")
-        if chrome_path and os.path.exists(chrome_path):
-            co.set_paths(browser_path=chrome_path)
-        
-        # 2. 代理配置 (支持账号密码)
-        if self.proxy_config and self.proxy_config.get("ip"):
-            p = self.proxy_config
-            if p.get("username") and p.get("password"):
-                self.tunnel = ProxyTunnel(p['ip'], p['port'], p['username'], p['password'])
-                local_proxy = self.tunnel.start()
-                self._log(f"Tunnel started at {local_proxy}")
-                co.set_argument(f'--proxy-server={local_proxy}')
-            else:
-                proxy_str = f"{p.get('proto', 'http')}://{p['ip']}:{p['port']}"
-                co.set_argument(f'--proxy-server={proxy_str}')
-        else:
-            self._log("[WARN] No proxy configured!")
-
-        fingerprint_gen = FingerprintGenerator()
-        specific_fp = fingerprint_gen.generate(self.instance_id)
-        self._log(f'browser fingerprint={specific_fp}')
-        # 3. 反爬及稳定性配置
-        co.headless(False)
-        co.set_argument('--no-sandbox')
-        co.set_argument('--lang=en-us')
-        co.set_argument('--accept-lang=en-us')
-        # co.set_argument('--disable-gpu')
-        co.set_argument('--disable-dev-shm-usage')
-        co.set_argument('--window-size=1920,1080')
-        co.set_argument('--disable-blink-features=AutomationControlled')
-        co.set_argument(f"--fingerprint={specific_fp.get('seed')}")
-        co.set_argument(f"--fingerprint-platform={specific_fp.get('platform')}")
-        co.set_argument(f"--fingerprint-brand={specific_fp.get('brand')}")
-        self.page = ChromiumPage(co)
-        self.page.get(self.france_visa_url)
-        time.sleep(5)
-        cf_bypasser = CloudflareBypasser(self.page, log=True)
-        cf_bypasser.bypass(max_retry=8)
-        time.sleep(3)
-        cf_bypasser.handle_waiting_room()
-        
-        self._log("正在初始化拟人化工具...")
-        self.mouse = HumanMouse(self.page, debug=True)
-        self.keyboard = HumanKeyboard(self.page)
-        self._log("随机化鼠标开始位置...")
-        viewport_width = self.page.rect.viewport_size[0]
-        viewport_height = self.page.rect.viewport_size[1]
-        init_x = random.randint(10, viewport_width - 10)
-        init_y = random.randint(10, viewport_height - 10)
-        self.mouse.move(init_x, init_y)
-        
-    def register(self):
-        username = self.user_inputs.get('username')
-        first_name = self.user_inputs.get('first_name')
-        last_name = self.user_inputs.get('last_name')
-        password = f'Visafly@1234'
-        self.page.wait.ele_deleted('tag:h2@@text():Log in to France-Visas', timeout=5)
-        self.page.ele('tag:button@@text():Create an account').click()
-        time.sleep(5)
-        
-        self.page.ele('tag:input@@name=lastName').input(last_name)
-        self.page.ele('tag:input@@name=firstName').input(first_name)
-        self.page.ele('tag:input@@name=email').input(username)
-        self.page.ele('tag:input@@name=emailVerif').input(username)
-        self.page.ele('tag:input@@name=password').input(password)
-        self.page.ele('tag:input@@name=password-confirm').input(password)
-        self.page.ele('tag:select@@name=ddeLanguage').select('English')
-        captcha = self.page.ele('#captchaComponent').ele('tag:img')
-        src = captcha.attr("src")
-        print(src)
-        base64_data = src.split(",")[1]
-        with open("captcha.png", "wb") as f:
-            f.write(base64.b64decode(base64_data))
-        result = recognize_captcha_with_qwen("captcha.png", "sk-893e895724c6403d81374e515ffaf427")
-        print(f'captcha result={result}')
-        self.page.ele('tag:input@@name=captchaFormulaireExtInput').input(result)
-        self.page.ele("tag:button@@text():Create an account").click()
-        
-    def activate(self, sent_at=None):
-        username = self.user_inputs.get('username')
-        email_box = 'hujiarui8@gmail.com'
-        sender = 'noreply at interieur.gouv.fr'
-        recipient = username
-        subject_keywords = 'Create your France-Visas account'
-        body_keywords = ''
-        
-        if not sent_at:
-            now_utc = datetime.utcnow()
-            sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
-        
-        content_out = VSCloudApi.Instance().fetch_mail_content(
-            email=email_box,
-            sender=sender,
-            recipient=recipient,
-            subject_keywords=subject_keywords,
-            body_keywords=body_keywords,
-            sent_date=sent_at,
-            expiry=600
-        )
-        self._log(f'activate email content={content_out}')
-        match = re.search(r'https://\S+', content_out)
-        activate_link = match.group(0) if match else None
-        self.page.get(activate_link)
-        time.sleep(3)
-    
-    def make_account_useful(self):
-        
-        def fill_date_field(page, selector, date_str):
-            if not date_str:
-                return
-                
-            ele = page.ele(selector)
-            ele.scroll.to_see(center=True)
-            
-            js_detect_format = """
-                const parts = new Intl.DateTimeFormat().formatToParts(new Date(2023, 11, 31));
-                let format = [];
-                for (let part of parts) {
-                    if (part.type === 'year') format.push('Y');
-                    if (part.type === 'month') format.push('M');
-                    if (part.type === 'day') format.push('D');
-                }
-                return format;
-            """
-            date_format = page.run_js(js_detect_format)
-            
-            year, month, day = date_str.split('-')
-            date_dict = {
-                'Y': year,
-                'M': month.zfill(2),
-                'D': day.zfill(2)
-            }
-            ele.click()
-            time.sleep(0.1)
-            page.actions.type(Keys.LEFT * 3)
-            time.sleep(0.1)
-            for i, char in enumerate(date_format):
-                val = date_dict[char]
-                page.actions.type(val)
-                time.sleep(0.1)
-                if char == 'Y':
-                    if i < 2:
-                        page.actions.type(Keys.RIGHT)
-                        time.sleep(0.1)
-                else:
-                    pass
-        
-        passport_no = self.user_inputs.get('passport_no')
-        passport_issue_date = self.user_inputs.get('passport_issue_date')
-        passport_expiry_date = self.user_inputs.get('passport_expiry_date')
-        nationality = self.user_inputs.get('nationality')
-        passport_issue_from = self.user_inputs.get('passport_issue_from')
-        self.page.ele('#formHeader:navigationLanguage_input').select('English')
-        time.sleep(3)
-        self.page.ele('#formAccueilUsager:ajouterGroupe').click()
-        time.sleep(5)
-        self.page.ele('#formStep1:visas-selected-nationality_input').select(nationality)
-        self.page.ele('#formStep1:Visas-selected-deposit-country_input').select('Ireland')
-        self.page.ele('#formStep1:Visas-selected-stayDuration_input').select('Short-stay (≤ 90 days)')
-        self.page.ele('#formStep1:Visas-selected-destination_input').select('France')
-        self.page.ele('#formStep1:Visas-selected-deposit-town_input').select('Dublin')
-        self.page.ele('#formStep1:Visas-selected-authority_input').select(passport_issue_from)
-        self.page.ele('#formStep1:Visas-dde-travel-document_input').select('Ordinary passport')
-        self.page.ele('#formStep1:Visas-dde-travel-document-number').input(passport_no)
-        fill_date_field(self.page, '#formStep1:Visas-dde-release_date_real_input', passport_issue_date)
-        fill_date_field(self.page, '#formStep1:Visas-dde-expiration_date_input', passport_expiry_date)
-        self.page.ele('#formStep1:Visas-selected-purposeCategory_input').select('Tourism')
-        self.page.ele('#formStep1:Visas-selected-purpose_input').select('Tourism / Private visit')
-        self.page.ele('#formStep1:btnVerifier').click()
-        time.sleep(3)
-        self.page.ele('#formStep1:btnSuivant').click()
-        time.sleep(3)
-        self.page.ele('#formStep1:btnValiderModal').click()
-        time.sleep(3)
-        self.page.ele('.iconeDDEIdPanel').click()
-        time.sleep(0.5)
-        self.page.ele('text():My applications').click()
-        time.sleep(3)
-        html_content = self.page.html
-        match = re.search(r'FRA1[A-Z0-9]+', html_content)
-        if not match:
-            raise BizLogicError(message='FRA1 not found')
-        fra_number = match.group(0)
-        print(fra_number)
-        return fra_number
-    
-    def cleanup(self):
-        """清理浏览器进程和缓存文件夹"""
-        self._log("Cleaning up resources...")
-        if self.page:
-            try: self.page.quit()
-            except: pass
-        if os.path.exists(self.workspace):
-            time.sleep(1) # 等待文件锁释放
-            shutil.rmtree(self.workspace, ignore_errors=True)
-            
-
-def main():
-    france_visa_url = 'https://application-form.france-visas.gouv.fr/fv-fo-dde/'
-    proxy_config = {
-        'ip': '127.0.0.1',
-        'port': 7890,
-        'username': '',
-        'password': ''
-    }
-    capsolver_key = ''
-    user_inputs = {
-        "username": "ManaliAshokGaikwad26@text.skin",
-        "first_name": "Manali Ashok",
-        "last_name": "Gaikwad",
-        "nationality": "Indian",
-        "passport_issue_from": "India",
-        "passport_no": "Z4413123",
-        "passport_issue_date": "2018-01-15",
-        "passport_expiry_date": "2028-01-14",
-    }
-    bot = FranceVisaRegistrator(
-        france_visa_url, 
-        proxy_config=proxy_config, 
-        capsolver_key=capsolver_key, 
-        user_inputs=user_inputs
-    )
-    bot.init_browser()
-    now_utc = datetime.utcnow()
-    sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
-    bot.register() 
-    bot.activate(sent_at=sent_at)
-    bot.make_account_useful()
-
-if __name__ == "__main__":
-    main()

+ 145 - 142
main_booker.py

@@ -1,180 +1,183 @@
 import os
+import sys
 import time
 import json
 import argparse
-from typing import List
+from typing import Dict
 
 from vs_types import GroupConfig
 from gco_wrapper import GCOWrapper
 from logger_setup import setup_app_logger
+from configure import REDIS_CFG
+from booker import BuiltinBookerGCO, OrderBookerGCO
+from utils.cloud_config import load_remote_or_cache, compute_config_hash, fetch_cloud_config, save_local_cache
 
-from booker_builtin import BuiltinBookerGCO
-from booker_order import OrderBookerGCO
-from toolkit.vs_cloud_api import VSCloudApi
 
+def get_gco_class_for_booker(cfg: GroupConfig):
+    if cfg.booker.account_source == "order":
+        return OrderBookerGCO
+    return BuiltinBookerGCO
 
-def load_config(path: str):
-    if not os.path.exists(path):
-        return {}
-    with open(path, "r", encoding="utf-8") as f:
-        return json.load(f)
 
-def save_config(path: str, new_config):
-    new_config_str = json.dumps(new_config, indent=2)
-    with open(path, "w", encoding="utf-8") as f:
-        f.write(new_config_str)
+def sync_booker_groups(
+    groups_conf: list,
+    active_wrappers: Dict[str, GCOWrapper],
+    active_gco_classes: Dict[str, type],
+    app_logger
+):
+    new_groups_by_id: Dict[str, GroupConfig] = {}
+    for item in groups_conf:
+        cfg = GroupConfig.from_json(item)
+        new_groups_by_id[cfg.identifier] = cfg
+
+    current_ids = list(active_wrappers.keys())
+    for gid in current_ids:
+        wrapper = active_wrappers[gid]
+        new_cfg = new_groups_by_id.get(gid)
+
+        if not new_cfg or not new_cfg.enable:
+            app_logger.info(f"Stopping disabled/removed Booker group [{gid}]...")
+            try:
+                wrapper.stop()
+            except Exception as e:
+                app_logger.error(f"Error stopping group [{gid}]: {e}")
+            del active_wrappers[gid]
+            if gid in active_gco_classes:
+                del active_gco_classes[gid]
+
+    for gid, cfg in new_groups_by_id.items():
+        if not cfg.enable:
+            continue
+
+        target_gco_class = get_gco_class_for_booker(cfg)
+
+        if gid in active_wrappers:
+            if active_gco_classes.get(gid) != target_gco_class:
+                app_logger.info(f"Mode changed for Booker group [{gid}]. Restarting group with new class...")
+                try:
+                    active_wrappers[gid].stop()
+                except Exception as e:
+                    app_logger.error(f"Error stopping group [{gid}] for class switch: {e}")
+                
+                try:
+                    wrapper = GCOWrapper(
+                        gco_class=target_gco_class,
+                        gco_cfg=cfg,
+                        redis_conf=REDIS_CFG
+                    )
+                    wrapper.load()
+                    wrapper.start()
+                    active_wrappers[gid] = wrapper
+                    active_gco_classes[gid] = target_gco_class
+                except Exception as e:
+                    app_logger.error(f"Failed to restart group [{gid}]: {e}")
+            else:
+                app_logger.info(f"Updating configuration for active Booker group [{gid}]...")
+                try:
+                    active_wrappers[gid].update_config(cfg)
+                except Exception as e:
+                    app_logger.error(f"Error updating config for group [{gid}]: {e}")
+        else:
+            mode_str = "ORDER (Bound)" if target_gco_class == OrderBookerGCO else "BUILT-IN (Unbound)"
+            app_logger.info(f"Starting new wrapper for Booker group [{gid}] (Mode: {mode_str})...")
+            try:
+                wrapper = GCOWrapper(
+                    gco_class=target_gco_class,
+                    gco_cfg=cfg,
+                    redis_conf=REDIS_CFG
+                )
+                wrapper.load()
+                wrapper.start()
+                active_wrappers[gid] = wrapper
+                active_gco_classes[gid] = target_gco_class
+            except Exception as e:
+                app_logger.error(f"Failed to start group [{gid}]: {e}")
+
 
 def main():
-    # ===== 1️⃣ 命令行参数 =====
     parser = argparse.ArgumentParser(description="Booker Runner")
 
     parser.add_argument(
         "-c", "--config",
         type=str,
         required=False,
-        default="config/config_booker.json",
-        help="Path to booker config.json"
+        default=None,
+        help="Path to local config.json. If specified, disables cloud config and runs with local file."
+    )
+    parser.add_argument(
+        "--poll-interval",
+        type=float,
+        required=False,
+        default=30.0,
+        help="Interval in seconds to poll cloud config (default: 30.0s)"
     )
 
     args = parser.parse_args()
-    config_path = args.config
-    CONF_NAME = 'COORDINATOR_BOOKER'
-    # ===== 2️⃣ 日志 =====
-    app_logger = setup_app_logger("Booker")
-    app_logger.info("Booker Logger is ready!")
 
-    # ===== 3️⃣ 加载配置 =====
-    cfg_data = load_config(config_path)
-    
-    current_version = cfg_data.get('version', 0)
-    redis_conf = cfg_data.get('redis')
-    groups_conf = cfg_data.get('group_list', [])
+    # 逻辑 1:默认开启云端配置,除非命令行指定了 -c/--config 本地文件路径
+    if args.config is not None:
+        config_path = args.config
+        use_cloud = False
+    else:
+        config_path = "config/config_booker.json"
+        use_cloud = True
 
-    wrappers: List[GCOWrapper] = []
+    # 逻辑 2:直接从环境变量获取 node_id,默认 node01
+    node_id = os.getenv("NODE_ID") or "node01"
+    config_key = f"booker_config:{node_id}"
+    poll_interval = args.poll_interval
 
-    # ===== 4️⃣ 启动逻辑 =====
-    for item in groups_conf:
-        cfg = GroupConfig.from_json(item)
-        if not cfg.enable:
-            continue
-
-        if cfg.booker.account_source == "order":
-            gco_class = OrderBookerGCO
-            app_logger.info(f"[{cfg.identifier}] Mode: ORDER (Bound)")
-        else:
-            gco_class = BuiltinBookerGCO
-            app_logger.info(f"[{cfg.identifier}] Mode: BUILT-IN (Unbound)")
+    app_logger = setup_app_logger("Booker")
+    app_logger.info(f"Booker Logger is ready! (Node ID: '{node_id}', Cloud Mode: {use_cloud})")
 
-        wrapper = GCOWrapper(
-            gco_class=gco_class,
-            gco_cfg=cfg,
-            redis_conf=redis_conf
-        )
+    groups_conf, current_hash, loaded_from_remote = load_remote_or_cache(
+        config_key=config_key,
+        local_cache_path=config_path,
+        use_cloud=use_cloud,
+        logger=app_logger.info
+    )
 
-        wrapper.load()
-        wrapper.start()
-        wrappers.append(wrapper)
+    active_wrappers: Dict[str, GCOWrapper] = {}
+    active_gco_classes: Dict[str, type] = {}
 
-    app_logger.info(f"Started {len(wrappers)} Booker groups. Press Ctrl+C to stop.")
+    sync_booker_groups(groups_conf, active_wrappers, active_gco_classes, app_logger)
+    app_logger.info(
+        f"Successfully initialized Booker with {len(active_wrappers)} active group(s)."
+    )
+    if use_cloud:
+        app_logger.info(f"Cloud config active for key '{config_key}'. Polling every {poll_interval}s.")
 
-    # ===== 5️⃣ 保持运行 & 热更新监听 =====
+    last_poll_time = time.time()
     try:
         while True:
-            time.sleep(30)  # 每 3 秒检查一次文件状态
-            
-            try:
-                new_cfg_data = VSCloudApi.Instance().get_dynamic_config(config_name=CONF_NAME)
-                new_version = new_cfg_data.get('version')
-
-                # 如果 API 返回的没有 version 字段,或者版本号没变,则跳过
-                if new_version == current_version:
-                    continue
-
-                app_logger.info("Config file modification detected! Reloading Booker configurations...")
-                save_config(config_path, new_cfg_data)
-                current_version = new_version
-                
-                redis_conf = new_cfg_data.get('redis') 
-                new_groups_conf = new_cfg_data.get('group_list', [])
-                
-                # 转换新配置为字典格式 {identifier: config_dict}
-                new_cfg_dict = {
-                    item.get("identifier"): item 
-                    for item in new_groups_conf 
-                    if item.get("identifier")
-                }
-                
-                # ---------------- A. 处理【参数更新】、【删除/禁用】、【模式切换】 ----------------
-                surviving_wrappers = []
-                for wrapper in wrappers:
-                    current_id = wrapper.m_cfg.identifier
-                    
-                    if current_id in new_cfg_dict:
-                        new_group_cfg = GroupConfig.from_json(new_cfg_dict[current_id])
-                        
-                        # 1. 后端禁用了该组
-                        if not new_group_cfg.enable:
-                            app_logger.info(f"Group [{current_id}] disabled by backend. Stopping...")
-                            wrapper.stop()
-                            
-                        # 2. 账号来源模式发生了改变 (例如 built-in 变 order) -> 需要更换底层类,必须重启该组
-                        elif wrapper.m_cfg.booker.account_source != new_group_cfg.booker.account_source:
-                            app_logger.info(f"Group [{current_id}] Mode changed ({wrapper.m_cfg.booker.account_source} -> {new_group_cfg.booker.account_source}). Stopping old instance to recreate...")
-                            wrapper.stop()
-                            # 不加入 surviving_wrappers,让下面的步骤 B 重新创建它
-                            
-                        # 3. 模式没变,依然启用 -> 热更新参数
-                        else:
-                            wrapper.update_config(new_group_cfg)
-                            surviving_wrappers.append(wrapper)
-                    else:
-                        # 4. 该组被从 JSON 中删除了
-                        app_logger.info(f"Group [{current_id}] deleted from config. Stopping...")
-                        wrapper.stop()
-                        
-                wrappers = surviving_wrappers
-                
-                # ---------------- B. 处理【新增组】、【重新启用组】、【模式切换后的重建】 ----------------
-                existing_ids = {w.m_cfg.identifier for w in wrappers}
-                
-                for new_id, item_data in new_cfg_dict.items():
-                    if new_id not in existing_ids:
-                        cfg = GroupConfig.from_json(item_data)
-                        
-                        if not cfg.enable:
-                            continue
-                            
-                        # 判断采用哪个底层类
-                        if cfg.booker.account_source == "order":
-                            gco_class = OrderBookerGCO
-                            mode_str = "ORDER (Bound)"
-                        else:
-                            gco_class = BuiltinBookerGCO
-                            mode_str = "BUILT-IN (Unbound)"
-                            
-                        app_logger.info(f"Dynamically starting NEW/RECREATED Booker group [{cfg.identifier}] Mode: {mode_str}...")
-                        
-                        new_wrapper = GCOWrapper(
-                            gco_class=gco_class,
-                            gco_cfg=cfg,
-                            redis_conf=redis_conf
-                        )
-                        try:
-                            new_wrapper.load()
-                            new_wrapper.start()
-                            wrappers.append(new_wrapper)
-                        except Exception as e:
-                            app_logger.error(f"Failed to dynamically start Booker group [{cfg.identifier}]: {e}")
-                                
-            except json.JSONDecodeError:
-                app_logger.warning("Config file is currently invalid JSON (maybe still writing), skipping this reload.")
-            except Exception as e:
-                app_logger.error(f"Error while hot-reloading config: {e}")
+            time.sleep(1)
+            now = time.time()
+
+            if use_cloud and (now - last_poll_time >= poll_interval):
+                last_poll_time = now
+                try:
+                    new_conf = fetch_cloud_config(config_key)
+                    if new_conf is not None:
+                        new_hash = compute_config_hash(new_conf)
+
+                        if new_hash != current_hash:
+                            app_logger.info(
+                                f"Cloud config change detected (hash: {current_hash[:8]} -> {new_hash[:8]}). Auto-reloading..."
+                            )
+                            save_local_cache(config_path, new_conf)
+                            sync_booker_groups(new_conf, active_wrappers, active_gco_classes, app_logger)
+                            current_hash = new_hash
+                            app_logger.info(f"Auto-reload completed. Currently {len(active_wrappers)} group(s) running.")
+                except Exception as e:
+                    app_logger.warning(f"Error polling cloud config key '{config_key}': {e}. Retrying next cycle.")
 
     except KeyboardInterrupt:
         app_logger.info("Shutting down Bookers...")
-        for wrapper in wrappers:
-            wrapper.stop()
+        for gid, wrapper in list(active_wrappers.items()):
+            try:
+                wrapper.stop()
+            except Exception as e:
+                app_logger.error(f"Error stopping group [{gid}]: {e}")
 
 
 if __name__ == "__main__":

+ 109 - 126
main_sentinel.py

@@ -1,166 +1,149 @@
 import os
+import sys
 import time
 import json
 import argparse
-from typing import List
+from typing import Dict
 
 from vs_types import GroupConfig
 from gco_wrapper import GCOWrapper
 from logger_setup import setup_app_logger
-
+from configure import REDIS_CFG
 from sentinel import SentinelGCO
-from toolkit.vs_cloud_api import VSCloudApi
+from utils.cloud_config import load_remote_or_cache, compute_config_hash, fetch_cloud_config, save_local_cache
+
+
+def sync_sentinel_groups(
+    groups_conf: list,
+    active_wrappers: Dict[str, GCOWrapper],
+    app_logger
+):
+    new_groups_by_id: Dict[str, GroupConfig] = {}
+    for item in groups_conf:
+        cfg = GroupConfig.from_json(item)
+        new_groups_by_id[cfg.identifier] = cfg
+
+    current_ids = list(active_wrappers.keys())
+    for gid in current_ids:
+        wrapper = active_wrappers[gid]
+        new_cfg = new_groups_by_id.get(gid)
+
+        if not new_cfg or not new_cfg.enable:
+            app_logger.info(f"Stopping disabled/removed Sentinel group [{gid}]...")
+            try:
+                wrapper.stop()
+            except Exception as e:
+                app_logger.error(f"Error stopping group [{gid}]: {e}")
+            del active_wrappers[gid]
 
+    for gid, cfg in new_groups_by_id.items():
+        if not cfg.enable:
+            continue
 
-def load_config(path: str):
-    if not os.path.exists(path):
-        return {}
-    with open(path, "r", encoding="utf-8") as f:
-        return json.load(f)
+        if gid in active_wrappers:
+            app_logger.info(f"Updating configuration for active Sentinel group [{gid}]...")
+            try:
+                active_wrappers[gid].update_config(cfg)
+            except Exception as e:
+                app_logger.error(f"Error updating config for group [{gid}]: {e}")
+        else:
+            app_logger.info(f"Starting new wrapper for Sentinel group [{gid}]...")
+            try:
+                wrapper = GCOWrapper(
+                    gco_class=SentinelGCO,
+                    gco_cfg=cfg,
+                    redis_conf=REDIS_CFG
+                )
+                wrapper.load()
+                wrapper.start()
+                active_wrappers[gid] = wrapper
+            except Exception as e:
+                app_logger.error(f"Failed to start group [{gid}]: {e}")
 
-def save_config(path: str, new_config):
-    new_config_str = json.dumps(new_config, indent=2)
-    with open(path, "w", encoding="utf-8") as f:
-        f.write(new_config_str)
 
 def main():
-    # ===== 1️⃣ 命令行参数 =====
     parser = argparse.ArgumentParser(description="Sentinel Runner")
 
     parser.add_argument(
         "-c", "--config",
         type=str,
         required=False,
-        default="config/config_sentinel.json",
-        help="Path to sentinel config.json"
+        default=None,
+        help="Path to local config.json. If specified, disables cloud config and runs with local file."
+    )
+    parser.add_argument(
+        "--poll-interval",
+        type=float,
+        required=False,
+        default=30.0,
+        help="Interval in seconds to poll cloud config (default: 30.0s)"
     )
 
     args = parser.parse_args()
-    config_path = args.config
-    CONF_NAME = 'COORDINATOR_SENTINEL'
-    # ===== 2️⃣ logger =====
-    app_logger = setup_app_logger("Sentinel")
-    app_logger.info("Sentinel Logger is ready!")
-
-    # ===== 3️⃣ 读取配置 =====
-    cfg_data = load_config(config_path)
-    
-    current_version = cfg_data.get('version', 0)
-    redis_conf = cfg_data.get('redis')
-    groups_conf = cfg_data.get('group_list', [])
 
-    wrappers: List[GCOWrapper] = []
+    # 逻辑 1:默认开启云端配置,除非命令行指定了 -c/--config 本地文件路径
+    if args.config is not None:
+        config_path = args.config
+        use_cloud = False
+    else:
+        config_path = "config/config_sentinel.json"
+        use_cloud = True
 
-    # ===== 4️⃣ 启动 groups =====
-    for item in groups_conf:
-        cfg = GroupConfig.from_json(item)
+    # 逻辑 2:直接从环境变量获取 node_id,默认 node01
+    node_id = os.getenv("NODE_ID") or "node01"
+    config_key = f"sentinel_config:{node_id}"
+    poll_interval = args.poll_interval
 
-        # 初始只启动 enable=True 的组
-        if not cfg.enable:
-            app_logger.info(f"Group [{cfg.identifier}] is disabled initially. Skipping.")
-            continue
-
-        app_logger.info(f"Starting wrapper for group [{cfg.identifier}]...")
+    app_logger = setup_app_logger("Sentinel")
+    app_logger.info(f"Sentinel Logger is ready! (Node ID: '{node_id}', Cloud Mode: {use_cloud})")
 
-        wrapper = GCOWrapper(
-            gco_class=SentinelGCO,
-            gco_cfg=cfg,
-            redis_conf=redis_conf
-        )
+    groups_conf, current_hash, loaded_from_remote = load_remote_or_cache(
+        config_key=config_key,
+        local_cache_path=config_path,
+        use_cloud=use_cloud,
+        logger=app_logger.info
+    )
 
-        wrapper.load()
-        wrapper.start()
-        wrappers.append(wrapper)
+    active_wrappers: Dict[str, GCOWrapper] = {}
 
+    sync_sentinel_groups(groups_conf, active_wrappers, app_logger)
     app_logger.info(
-        f"Successfully started {len(wrappers)} Sentinel groups. Press Ctrl+C to stop."
+        f"Successfully initialized Sentinel with {len(active_wrappers)} active group(s)."
     )
+    if use_cloud:
+        app_logger.info(f"Cloud config active for key '{config_key}'. Polling every {poll_interval}s.")
 
-    # ===== 5️⃣ keep alive & 热更新监听 =====
+    last_poll_time = time.time()
     try:
         while True:
-            time.sleep(30)  # 每 3 秒检查一次文件状态
-            
-            try:
-                new_cfg_data = VSCloudApi.Instance().get_dynamic_config(config_name=CONF_NAME)
-                new_version = new_cfg_data.get('version')
-                # 如果 API 返回的没有 version 字段,或者版本号没变,则跳过
-                if new_version == current_version:
-                    continue
-                
-                app_logger.info("Config file modification detected! Reloading configurations...")
-                save_config(config_path, new_cfg_data)
-                current_version = new_version
-                
-                redis_conf = new_cfg_data.get('redis') 
-                new_groups_conf = new_cfg_data.get('group_list', [])
-                
-                # 转换新配置为 {identifier: config_dict} 的字典格式,方便 O(1) 查找
-                new_cfg_dict = {
-                    item.get("identifier"): item 
-                    for item in new_groups_conf 
-                    if item.get("identifier")
-                }
-                
-                # ---------------- A. 处理【参数热更新】与【删除/禁用组】 ----------------
-                surviving_wrappers = []
-                for wrapper in wrappers:
-                    current_id = wrapper.m_cfg.identifier
-                    
-                    if current_id in new_cfg_dict:
-                        new_group_cfg = GroupConfig.from_json(new_cfg_dict[current_id])
-                        
-                        # 情况 1: 如果后端把这个组的 enable 改成了 false,视同删除,直接停掉
-                        if not new_group_cfg.enable:
-                            app_logger.info(f"Group [{current_id}] disabled by backend. Stopping and removing...")
-                            wrapper.stop()
-                        else:
-                            # 情况 2: 依然启用,调用刚才实现的热更新接口透传参数
-                            wrapper.update_config(new_group_cfg)
-                            surviving_wrappers.append(wrapper)
-                    else:
-                        # 情况 3: 这个组完全从 JSON 中被删除了
-                        app_logger.info(f"Group [{current_id}] deleted from config. Stopping and removing...")
-                        wrapper.stop()
-                
-                # 更新当前正在运行的 wrappers 列表
-                wrappers = surviving_wrappers
-                
-                # ---------------- B. 处理【新增组】与【重新启用组】 ----------------
-                existing_ids = {w.m_cfg.identifier for w in wrappers}
-                
-                for new_id, item_data in new_cfg_dict.items():
-                    # 发现新出现的 ID(全新添加的,或是从 enable: false 变成 enable: true 的)
-                    if new_id not in existing_ids:
-                        cfg = GroupConfig.from_json(item_data)
-                        
-                        # 只有启用的组才会被启动
-                        if not cfg.enable:
-                            continue
-                        
-                        app_logger.info(f"Dynamically starting wrapper for NEW group [{cfg.identifier}]...")
-                        new_wrapper = GCOWrapper(
-                            gco_class=SentinelGCO,
-                            gco_cfg=cfg,
-                            redis_conf=redis_conf
-                        )
-                        try:
-                            new_wrapper.load()
-                            new_wrapper.start()
-                            wrappers.append(new_wrapper)
-                        except Exception as e:
-                            app_logger.error(f"Failed to dynamically start new group [{cfg.identifier}]: {e}")
-            
-            except json.JSONDecodeError:
-                # 捕获异常:防止后端写入文件的中间状态导致 JSON 格式错误而崩溃
-                app_logger.warning("Config file is currently invalid JSON (maybe still writing), skipping this reload.")
-            except Exception as e:
-                app_logger.error(f"Error while hot-reloading config: {e}")
+            time.sleep(1)
+            now = time.time()
+
+            if use_cloud and (now - last_poll_time >= poll_interval):
+                last_poll_time = now
+                try:
+                    new_conf = fetch_cloud_config(config_key)
+                    if new_conf is not None:
+                        new_hash = compute_config_hash(new_conf)
+
+                        if new_hash != current_hash:
+                            app_logger.info(
+                                f"Cloud config change detected (hash: {current_hash[:8]} -> {new_hash[:8]}). Auto-reloading..."
+                            )
+                            save_local_cache(config_path, new_conf)
+                            sync_sentinel_groups(new_conf, active_wrappers, app_logger)
+                            current_hash = new_hash
+                            app_logger.info(f"Auto-reload completed. Currently {len(active_wrappers)} group(s) running.")
+                except Exception as e:
+                    app_logger.warning(f"Error polling cloud config key '{config_key}': {e}. Retrying next cycle.")
 
     except KeyboardInterrupt:
         app_logger.info("Shutting down Sentinels...")
-        for wrapper in wrappers:
-            wrapper.stop()
+        for gid, wrapper in list(active_wrappers.items()):
+            try:
+                wrapper.stop()
+            except Exception as e:
+                app_logger.error(f"Error stopping group [{gid}]: {e}")
 
 
 if __name__ == "__main__":

+ 128 - 21
main_standalone.py

@@ -1,18 +1,79 @@
 import os
+import sys
 import time
 import json
 import argparse
+from typing import Dict, List, Any
+
 from vs_types import BookerStandaloneConfig
 from gco_wrapper import GCOWrapper
 from logger_setup import setup_app_logger
 from booker_standalone import BookerStandalone
+from configure import REDIS_CFG
+from utils.cloud_config import load_remote_or_cache, compute_config_hash, fetch_cloud_config, save_local_cache
+
+
+def sync_standalone_groups(
+    groups_conf: Any,
+    active_wrappers: Dict[str, GCOWrapper],
+    app_logger
+):
+    """
+    根据最新 groups_conf 对比并管理各个 Standalone Booker 实例的生命周期。
+    支持 JSON List(包含多个 group 数组)或单个 Dict。
+    """
+    if isinstance(groups_conf, dict):
+        conf_list = [groups_conf]
+    elif isinstance(groups_conf, list):
+        conf_list = groups_conf
+    else:
+        conf_list = []
+
+    new_groups_by_id: Dict[str, BookerStandaloneConfig] = {}
+    for idx, item in enumerate(conf_list):
+        cfg = BookerStandaloneConfig.from_json(item)
+        gid = cfg.identifier or f"standalone_{idx}"
+        new_groups_by_id[gid] = cfg
+
+    # 1. 停止已被移除或被禁用的组
+    current_ids = list(active_wrappers.keys())
+    for gid in current_ids:
+        wrapper = active_wrappers[gid]
+        new_cfg = new_groups_by_id.get(gid)
 
+        if not new_cfg or not new_cfg.enable:
+            app_logger.info(f"Stopping disabled/removed Standalone group [{gid}]...")
+            try:
+                wrapper.stop()
+            except Exception as e:
+                app_logger.error(f"Error stopping group [{gid}]: {e}")
+            del active_wrappers[gid]
+
+    # 2. 动态更新现有组,或启动新增组
+    for gid, cfg in new_groups_by_id.items():
+        if not cfg.enable:
+            continue
+
+        if gid in active_wrappers:
+            app_logger.info(f"Updating configuration for active Standalone group [{gid}]...")
+            try:
+                active_wrappers[gid].update_config(cfg)
+            except Exception as e:
+                app_logger.error(f"Error updating config for Standalone group [{gid}]: {e}")
+        else:
+            app_logger.info(f"Starting new wrapper for Standalone group [{gid}]...")
+            try:
+                wrapper = GCOWrapper(
+                    gco_class=BookerStandalone,
+                    gco_cfg=cfg,
+                    redis_conf=REDIS_CFG
+                )
+                wrapper.load()
+                wrapper.start()
+                active_wrappers[gid] = wrapper
+            except Exception as e:
+                app_logger.error(f"Failed to start Standalone group [{gid}]: {e}")
 
-def load_config(path: str):
-    if not os.path.exists(path):
-        return {}
-    with open(path, "r", encoding="utf-8") as f:
-        return json.load(f)
 
 def main():
     parser = argparse.ArgumentParser(description="Standalone Booker Runner")
@@ -20,36 +81,82 @@ def main():
         "-c", "--config",
         type=str,
         required=False,
-        default="config/config.json",
-        help="Path to standalone booker config.json"
+        default=None,
+        help="Path to local config.json. If specified, disables cloud config and runs with local file."
+    )
+    parser.add_argument(
+        "--poll-interval",
+        type=float,
+        required=False,
+        default=30.0,
+        help="Interval in seconds to poll cloud config (default: 30.0s)"
     )
 
     args = parser.parse_args()
-    config_path = args.config
-    
-    app_logger = setup_app_logger("Booker")
-    app_logger.info("Booker Logger is ready! All VSC macros are hooked.")
 
-    cfg_data = load_config(config_path)
+    # 逻辑 1:默认开启云端配置,除非命令行指定了 -c/--config 本地文件路径
+    if args.config is not None:
+        config_path = args.config
+        use_cloud = False
+    else:
+        config_path = "config/config_standalone.json"
+        use_cloud = True
+
+    # 逻辑 2:直接从环境变量获取 node_id,默认 node01
+    node_id = os.getenv("NODE_ID") or "node01"
+    config_key = f"standalone_config:{node_id}"
+    poll_interval = args.poll_interval
+
+    app_logger = setup_app_logger("Booker")
+    app_logger.info(f"Booker Logger is ready! (Node ID: '{node_id}', Cloud Mode: {use_cloud})")
 
-    redis_conf = cfg_data.get('redis')
+    groups_conf, current_hash, loaded_from_remote = load_remote_or_cache(
+        config_key=config_key,
+        local_cache_path=config_path,
+        use_cloud=use_cloud,
+        logger=app_logger.info
+    )
 
-    booker_standalone_config = cfg_data.get('booker_standalone_config')
-    cfg = BookerStandaloneConfig.from_json(booker_standalone_config)
+    active_wrappers: Dict[str, GCOWrapper] = {}
 
-    gco_class = BookerStandalone
+    sync_standalone_groups(groups_conf, active_wrappers, app_logger)
+    app_logger.info(f"Successfully initialized {len(active_wrappers)} Standalone Booker group(s).")
 
-    wrapper = GCOWrapper(gco_class=gco_class, gco_cfg=cfg, redis_conf=redis_conf)
-    wrapper.load()
-    wrapper.start()
-    app_logger.info(f"Successfully started booker. Press Ctrl+C to stop.")
+    if use_cloud:
+        app_logger.info(f"Cloud config active for key '{config_key}'. Polling every {poll_interval}s.")
     
+    last_poll_time = time.time()
     try:
         while True:
             time.sleep(1)
+            now = time.time()
+
+            if use_cloud and (now - last_poll_time >= poll_interval):
+                last_poll_time = now
+                try:
+                    new_conf = fetch_cloud_config(config_key)
+                    if new_conf is not None:
+                        new_hash = compute_config_hash(new_conf)
+
+                        if new_hash != current_hash:
+                            app_logger.info(
+                                f"Cloud config change detected (hash: {current_hash[:8]} -> {new_hash[:8]}). Auto-reloading..."
+                            )
+                            save_local_cache(config_path, new_conf)
+                            sync_standalone_groups(new_conf, active_wrappers, app_logger)
+                            current_hash = new_hash
+                            app_logger.info(f"Auto-reload completed. Currently {len(active_wrappers)} Standalone group(s) running.")
+                except Exception as e:
+                    app_logger.warning(f"Error polling cloud config key '{config_key}': {e}. Retrying next cycle.")
+
     except KeyboardInterrupt:
         app_logger.info("Shutting down Bookers...")
-        wrapper.stop()
+        for gid, wrapper in list(active_wrappers.items()):
+            try:
+                wrapper.stop()
+            except Exception as e:
+                app_logger.error(f"Error stopping Standalone group [{gid}]: {e}")
+
 
 if __name__ == "__main__":
     main()

+ 2 - 2
requirements.txt

@@ -10,9 +10,9 @@ fastapi
 numpy
 pydantic
 requests
-uvicorn
+# uvicorn
 psutil
 loguru
-mitmproxy>=10.0.0
+# mitmproxy>=10.0.0
 torch --index-url https://download.pytorch.org/whl/cpu
 torchvision --index-url https://download.pytorch.org/whl/cpu

+ 105 - 0
test/test_cloud_config.py

@@ -0,0 +1,105 @@
+import os
+import json
+import time
+import tempfile
+import threading
+import unittest
+from http.server import HTTPServer, BaseHTTPRequestHandler
+
+from utils.cloud_config import (
+    compute_config_hash,
+    fetch_remote_config,
+    save_local_cache,
+    load_local_config,
+    load_remote_or_cache,
+)
+
+
+class MockConfigHTTPHandler(BaseHTTPRequestHandler):
+    config_payload = [{"identifier": "test_group", "enable": True}]
+    status_code = 200
+
+    def do_GET(self):
+        self.send_response(self.status_code)
+        self.send_header("Content-Type", "application/json")
+        self.end_headers()
+        response_data = json.dumps(self.config_payload).encode("utf-8")
+        self.wfile.write(response_data)
+
+    def log_message(self, format, *args):
+        pass  # 禁用标准输出日志,保持测试输出简洁
+
+
+class TestCloudConfig(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # 启动本地 Mock HTTP 服务器
+        cls.server = HTTPServer(("127.0.0.1", 0), MockConfigHTTPHandler)
+        cls.port = cls.server.server_address[1]
+        cls.server_thread = threading.Thread(target=cls.server.serve_forever)
+        cls.server_thread.daemon = True
+        cls.server_thread.start()
+        cls.url = f"http://127.0.0.1:{cls.port}/config.json"
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.server.shutdown()
+        cls.server.server_close()
+
+    def test_compute_config_hash(self):
+        data1 = {"b": 2, "a": 1}
+        data2 = {"a": 1, "b": 2}
+        data3 = {"a": 1, "b": 3}
+        self.assertEqual(compute_config_hash(data1), compute_config_hash(data2))
+        self.assertNotEqual(compute_config_hash(data1), compute_config_hash(data3))
+
+    def test_fetch_remote_config_success(self):
+        MockConfigHTTPHandler.config_payload = [{"identifier": "group_a", "enable": True}]
+        MockConfigHTTPHandler.status_code = 200
+
+        data = fetch_remote_config(self.url)
+        self.assertEqual(data, [{"identifier": "group_a", "enable": True}])
+
+    def test_fetch_remote_config_http_error(self):
+        MockConfigHTTPHandler.status_code = 500
+        with self.assertRaises(RuntimeError):
+            fetch_remote_config(self.url)
+
+    def test_load_remote_or_cache_success_and_save(self):
+        MockConfigHTTPHandler.config_payload = [{"identifier": "group_remote", "enable": True}]
+        MockConfigHTTPHandler.status_code = 200
+
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            cache_path = os.path.join(tmp_dir, "cache.json")
+            data, hash_val, loaded_remote = load_remote_or_cache(
+                remote_url=self.url,
+                local_cache_path=cache_path
+            )
+
+            self.assertTrue(loaded_remote)
+            self.assertEqual(data, [{"identifier": "group_remote", "enable": True}])
+            self.assertTrue(os.path.exists(cache_path))
+
+            # 验证本地缓存是否被写入
+            cached_data = load_local_config(cache_path)
+            self.assertEqual(cached_data, [{"identifier": "group_remote", "enable": True}])
+
+    def test_load_remote_or_cache_fallback_to_local(self):
+        MockConfigHTTPHandler.status_code = 500  # 模拟云端 API 失败
+
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            cache_path = os.path.join(tmp_dir, "cache.json")
+            # 预先存入本地缓存
+            save_local_cache(cache_path, [{"identifier": "group_cached", "enable": True}])
+
+            data, hash_val, loaded_remote = load_remote_or_cache(
+                remote_url=self.url,
+                local_cache_path=cache_path
+            )
+
+            self.assertFalse(loaded_remote)
+            self.assertEqual(data, [{"identifier": "group_cached", "enable": True}])
+
+
+if __name__ == "__main__":
+    unittest.main()

+ 2 - 2
toolkit/vs_cloud_api.py

@@ -52,8 +52,8 @@ class VSCloudApi:
         else:
             raise BizLogicError(message=f"HTTP Error {resp.status_code}: {resp.text[:100]}")
         
-    def get_dynamic_config(self, config_name: str) -> Dict:
-        url = f'https://api.text.skin/api/dynamic-configurations/key/{quote(config_name)}'
+    def get_dynamic_config(self, config_name: str) -> Any:
+        url = f'{self.base_url}/api/dynamic-configurations/key/{quote(config_name)}'
         headers = self._get_headers()
         resp = self._perform_request('GET', url, headers=headers, timeout=10)
         result = resp.json()

+ 106 - 0
utils/cloud_config.py

@@ -0,0 +1,106 @@
+import os
+import json
+import hashlib
+import time
+from typing import Any, Tuple, Optional, Callable
+from toolkit.vs_cloud_api import VSCloudApi
+
+
+def compute_config_hash(config_data: Any) -> str:
+    """计算 JSON 配置数据的 MD5 哈希,用于快速对比判断配置是否发生变更"""
+    if config_data is None:
+        return ""
+    serialized = json.dumps(config_data, sort_keys=True, ensure_ascii=False)
+    return hashlib.md5(serialized.encode('utf-8')).hexdigest()
+
+
+def fetch_cloud_config(config_key: str) -> Any:
+    """
+    使用内置 VSCloudApi 从云端读取指定 config_key 的动态配置。
+    支持节点专有 key (如 sentinel_config:node01),若无专有配置则自动回退至全局 key (如 sentinel_config)。
+    """
+    try:
+        data = VSCloudApi.Instance().get_dynamic_config(config_key)
+        if data is not None:
+            return data
+    except Exception:
+        pass
+
+    # 若针对节点的特定 Key 未配置或获取失败,自动回退到全局 Key
+    if ":" in config_key:
+        base_key = config_key.split(":")[0]
+        return VSCloudApi.Instance().get_dynamic_config(base_key)
+    
+    return None
+
+
+def save_local_cache(path: str, data: Any) -> bool:
+    """将从云端获取到的最新配置保存到本地缓存文件路径"""
+    try:
+        dir_name = os.path.dirname(path)
+        if dir_name and not os.path.exists(dir_name):
+            os.makedirs(dir_name, exist_ok=True)
+        
+        tmp_path = path + ".tmp"
+        with open(tmp_path, "w", encoding="utf-8") as f:
+            json.dump(data, f, ensure_ascii=False, indent=2)
+        os.replace(tmp_path, path)
+        return True
+    except Exception as e:
+        print(f"[CloudConfig] Failed to save local cache to {path}: {e}")
+        return False
+
+
+def load_local_config(path: str) -> Any:
+    """从本地文件读取配置"""
+    if not os.path.exists(path):
+        return None
+    with open(path, "r", encoding="utf-8") as f:
+        return json.load(f)
+
+
+def load_remote_or_cache(
+    config_key: str,
+    local_cache_path: str,
+    use_cloud: bool = True,
+    logger: Optional[Callable[[str], None]] = None
+) -> Tuple[Any, str, bool]:
+    """
+    核心拉取与 Fallback 逻辑:
+    - 若开启了 use_cloud(默认):尝试调用 VSCloudApi 获取,成功则写入本地缓存文件;失败则 Fallback 读取本地缓存。
+    - 若未开启 use_cloud(显式传了 -c/--config):直接读取本地配置文件。
+    """
+    def log_info(msg: str):
+        if logger:
+            logger(f"[CloudConfig] {msg}")
+        else:
+            print(f"[CloudConfig] {msg}")
+
+    def log_warn(msg: str):
+        if logger:
+            logger(f"[CloudConfig][WARNING] {msg}")
+        else:
+            print(f"[CloudConfig][WARNING] {msg}")
+
+    if use_cloud:
+        try:
+            log_info(f"Fetching cloud configuration key '{config_key}' via VSCloudApi...")
+            data = fetch_cloud_config(config_key)
+            if data is not None:
+                config_hash = compute_config_hash(data)
+                log_info(f"Successfully fetched cloud config '{config_key}' (hash: {config_hash[:8]})")
+                save_local_cache(local_cache_path, data)
+                return data, config_hash, True
+            else:
+                log_warn(f"Cloud config key '{config_key}' returned empty/null data.")
+        except Exception as e:
+            log_warn(f"Failed to fetch cloud config '{config_key}' via VSCloudApi: {e}")
+            log_warn(f"Fallback to local cache file: {local_cache_path}")
+            
+    local_data = load_local_config(local_cache_path)
+    if local_data is None:
+        log_warn(f"Local config file {local_cache_path} does not exist or is empty.")
+        local_data = [] if "booker" in local_cache_path or "sentinel" in local_cache_path else {}
+    
+    config_hash = compute_config_hash(local_data)
+    return local_data, config_hash, False

+ 2 - 0
vs_types.py

@@ -136,6 +136,8 @@ class VSProxy(BaseModel):
     password: str = ""
     
 class BookerStandaloneConfig(BaseModel):
+    identifier: str = ""
+    enable: bool = True
     debug: bool = False
     account: VSAccount = Field(default_factory=VSAccount)
     proxy_pool: List[str] = Field(default_factory=list)