jerry 3 месяцев назад
Родитель
Сommit
985ef8c247

+ 258 - 178
config/groups.json

@@ -22,10 +22,20 @@
         },
         },
         "plugin_config": {
         "plugin_config": {
             "lib_path": "plugins",
             "lib_path": "plugins",
-            "plugin_name": "vfs_plugin2",
-            "plugin_bin": "vfs_plugin2.py",
+            "plugin_name": "vfs_plugin",
+            "plugin_bin": "vfs_plugin.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "nld",
             "mission_code": "nld",
             "mission_name": "Netherlands",
             "mission_name": "Netherlands",
@@ -33,15 +43,9 @@
             "country_name": "Ireland",
             "country_name": "Ireland",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/irl/en/nld/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.dub.nl.tourist",
+            "apt_configs": {
+                "slot.dub.nl.tourist": {
                     "center_name": "Netherlands Visa Application Center - Dublin",
                     "center_name": "Netherlands Visa Application Center - Dublin",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Netherlands",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
                     "vac_code": "NTDB",
                     "vac_code": "NTDB",
                     "category_name": "All Short stay Categories",
                     "category_name": "All Short stay Categories",
@@ -49,13 +53,13 @@
                     "subcategory_name": "Tourist",
                     "subcategory_name": "Tourist",
                     "subcategory_code": "To"
                     "subcategory_code": "To"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_SG_FR",
         "identifier": "VFS_SG_FR",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "sg_fr",
         "local_account_pool": "sg_fr",
         "need_proxy": true,
         "need_proxy": true,
@@ -79,6 +83,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "fra",
             "mission_code": "fra",
             "mission_name": "France",
             "mission_name": "France",
@@ -86,15 +100,9 @@
             "country_name": "Singapore",
             "country_name": "Singapore",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/sgp/en/fra/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.sin.fr.tourist",
+            "apt_configs": {
+                "slot.sin.fr.tourist": {
                     "center_name": "France Visa Application Center, Singapore",
                     "center_name": "France Visa Application Center, Singapore",
-                    "city": "Singapore",
-                    "visa_type": "Tourist",
-                    "country": "France",
                     "address": "79 Anson Road #15-01 Singapore 079906",
                     "address": "79 Anson Road #15-01 Singapore 079906",
                     "vac_code": "FRSN",
                     "vac_code": "FRSN",
                     "category_name": "Short Stay",
                     "category_name": "Short Stay",
@@ -102,7 +110,7 @@
                     "subcategory_name": "Short Stay Tourist, Family Visit",
                     "subcategory_name": "Short Stay Tourist, Family Visit",
                     "subcategory_code": "Six"
                     "subcategory_code": "Six"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
@@ -132,6 +140,23 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "fra",
             "mission_code": "fra",
             "mission_name": "France",
             "mission_name": "France",
@@ -139,15 +164,9 @@
             "country_name": "Australia",
             "country_name": "Australia",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/aus/en/fra/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.syd.fr.tourist",
+            "apt_configs": {
+                "slot.syd.fr.tourist": {
                     "center_name": "France Visa Application Center - Sydney",
                     "center_name": "France Visa Application Center - Sydney",
-                    "city": "Sydney",
-                    "visa_type": "Tourist",
-                    "country": "France",
                     "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
                     "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
                     "vac_code": "SYD",
                     "vac_code": "SYD",
                     "category_name": "VISA",
                     "category_name": "VISA",
@@ -155,13 +174,8 @@
                     "subcategory_name": "Short Stay Schengen Visa",
                     "subcategory_name": "Short Stay Schengen Visa",
                     "subcategory_code": "ShortStaySchengenVisa"
                     "subcategory_code": "ShortStaySchengenVisa"
                 },
                 },
-                {
-                    "weight": 10,
-                    "routing_key": "slot.mel.fr.tourist",
+                "slot.mel.fr.tourist": {
                     "center_name": "France Visa Application Center - Melbourne",
                     "center_name": "France Visa Application Center - Melbourne",
-                    "city": "Melbourne",
-                    "visa_type": "Tourist",
-                    "country": "France",
                     "address": "Level 5 332 St. Kilda road level 5 Melbourne 3004",
                     "address": "Level 5 332 St. Kilda road level 5 Melbourne 3004",
                     "vac_code": "MEL",
                     "vac_code": "MEL",
                     "category_name": "VISA",
                     "category_name": "VISA",
@@ -169,13 +183,13 @@
                     "subcategory_name": "Short Stay Schengen Visa",
                     "subcategory_name": "Short Stay Schengen Visa",
                     "subcategory_code": "ShortStaySchengenVisa"
                     "subcategory_code": "ShortStaySchengenVisa"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_GB_IT",
         "identifier": "VFS_GB_IT",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "gb_it",
         "local_account_pool": "gb_it",
         "need_proxy": true,
         "need_proxy": true,
@@ -199,22 +213,33 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "ita",
             "mission_code": "ita",
             "mission_name": "Italy",
             "mission_name": "Italy",
             "country_code": "gbr",
             "country_code": "gbr",
             "country_name": "United Kingdom",
             "country_name": "United Kingdom",
             "culture_code": "en-US",
             "culture_code": "en-US",
-            "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
             "language": "en",
             "language": "en",
-            "appointment_types": [
-                {
-                    "weight": 80,
-                    "routing_key": "slot.lon.it.tourist",
+            "apt_configs": {
+                "slot.lon.it.tourist": {
                     "center_name": "Italy Visa Application Centre, London",
                     "center_name": "Italy Visa Application Centre, London",
-                    "city": "London",
-                    "visa_type": "Tourist",
-                    "country": "Italy",
                     "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
                     "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
                     "vac_code": "ILON",
                     "vac_code": "ILON",
                     "category_name": "Italy UK VisaCategory",
                     "category_name": "Italy UK VisaCategory",
@@ -222,13 +247,8 @@
                     "subcategory_name": "Tourist/ Business/ EU Family",
                     "subcategory_name": "Tourist/ Business/ EU Family",
                     "subcategory_code": "TBE"
                     "subcategory_code": "TBE"
                 },
                 },
-                {
-                    "weight": 20,
-                    "routing_key": "slot.man.it.tourist",
+                "slot.man.it.tourist": {
                     "center_name": "Italy Visa Application Centre, Manchester",
                     "center_name": "Italy Visa Application Centre, Manchester",
-                    "city": "Manchester",
-                    "visa_type": "Tourist",
-                    "country": "Italy",
                     "address": "50 Devonshire Street North, M12 6JH",
                     "address": "50 Devonshire Street North, M12 6JH",
                     "vac_code": "IMAN",
                     "vac_code": "IMAN",
                     "category_name": "Italy UK VisaCategory",
                     "category_name": "Italy UK VisaCategory",
@@ -236,13 +256,13 @@
                     "subcategory_name": "Tourist/ Business/ EU Family",
                     "subcategory_name": "Tourist/ Business/ EU Family",
                     "subcategory_code": "TBE"
                     "subcategory_code": "TBE"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_GB_NL",
         "identifier": "VFS_GB_NL",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "gb_nl",
         "local_account_pool": "gb_nl",
         "need_proxy": true,
         "need_proxy": true,
@@ -266,6 +286,23 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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.it.tourist",
+                "city": "Manchester",
+                "visa_type": "Tourist",
+                "country": "Netherlands"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
         "free_config": {
         "free_config": {
             "mission_code": "nld",
             "mission_code": "nld",
             "mission_name": "Netherland",
             "mission_name": "Netherland",
@@ -273,15 +310,9 @@
             "country_name": "United Kingdom",
             "country_name": "United Kingdom",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
-            "appointment_types": [
-                {
-                    "weight": 90,
-                    "routing_key": "slot.lon.nl.tourist",
+            "apt_configs": {
+                "slot.lon.nl.tourist": {
                     "center_name": "Netherlands Visa application centre - London",
                     "center_name": "Netherlands Visa application centre - London",
-                    "city": "London",
-                    "visa_type": "Tourist",
-                    "country": "Netherlands",
                     "address": "66 Wilson Street, EC2A 2BT",
                     "address": "66 Wilson Street, EC2A 2BT",
                     "vac_code": "NAKN",
                     "vac_code": "NAKN",
                     "category_name": "Schengen Visa",
                     "category_name": "Schengen Visa",
@@ -289,13 +320,8 @@
                     "subcategory_name": "Tourism",
                     "subcategory_name": "Tourism",
                     "subcategory_code": "TA"
                     "subcategory_code": "TA"
                 },
                 },
-                {
-                    "weight": 10,
-                    "routing_key": "slot.man.it.tourist",
+                "slot.man.it.tourist": {
                     "center_name": "Netherlands Visa application centre - Manchester",
                     "center_name": "Netherlands Visa application centre - Manchester",
-                    "city": "Manchester",
-                    "visa_type": "Tourist",
-                    "country": "Netherlands",
                     "address": "50 Devonshire Street North, M12 6JH",
                     "address": "50 Devonshire Street North, M12 6JH",
                     "vac_code": "NAKT",
                     "vac_code": "NAKT",
                     "category_name": "Schengen Visa",
                     "category_name": "Schengen Visa",
@@ -303,13 +329,13 @@
                     "subcategory_name": "Tourism",
                     "subcategory_name": "Tourism",
                     "subcategory_code": "TA"
                     "subcategory_code": "TA"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_GB_NO",
         "identifier": "VFS_GB_NO",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "gb_no",
         "local_account_pool": "gb_no",
         "need_proxy": true,
         "need_proxy": true,
@@ -333,6 +359,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "nor",
             "mission_code": "nor",
             "mission_name": "Norway",
             "mission_name": "Norway",
@@ -340,15 +376,9 @@
             "country_name": "United Kingdom",
             "country_name": "United Kingdom",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/gbr/en/nor/login",
-            "appointment_types": [
+            "apt_configs": [
                 {
                 {
-                    "weight": 10,
-                    "routing_key": "slot.lon.no.tourist",
                     "center_name": "Norway Visa Application Centre, London",
                     "center_name": "Norway Visa Application Centre, London",
-                    "city": "London",
-                    "visa_type": "Tourist",
-                    "country": "Norway",
                     "address": "66 Wilson street, EC2A 2BT",
                     "address": "66 Wilson street, EC2A 2BT",
                     "vac_code": "NLON",
                     "vac_code": "NLON",
                     "category_name": "Schengen Visa C",
                     "category_name": "Schengen Visa C",
@@ -362,7 +392,7 @@
     {
     {
         "identifier": "VFS_IE_AT",
         "identifier": "VFS_IE_AT",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "ie_at",
         "local_account_pool": "ie_at",
         "need_proxy": true,
         "need_proxy": true,
@@ -386,6 +416,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "aut",
             "mission_code": "aut",
             "mission_name": "Austria",
             "mission_name": "Austria",
@@ -393,15 +433,9 @@
             "country_name": "Ireland",
             "country_name": "Ireland",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/irl/en/aut/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.dub.at.tourist",
+            "apt_configs": {
+                "slot.dub.at.tourist": {
                     "center_name": "Austria / Switzerland / Liechtenstein/ Slovenia Visa Application Center, Dublin",
                     "center_name": "Austria / Switzerland / Liechtenstein/ Slovenia Visa Application Center, Dublin",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Austria",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "vac_code": "AUT-DUB",
                     "vac_code": "AUT-DUB",
                     "category_name": "Other Visas",
                     "category_name": "Other Visas",
@@ -409,13 +443,13 @@
                     "subcategory_name": "All Visas ",
                     "subcategory_name": "All Visas ",
                     "subcategory_code": "TA"
                     "subcategory_code": "TA"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_IE_DK",
         "identifier": "VFS_IE_DK",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "ie_dk",
         "local_account_pool": "ie_dk",
         "need_proxy": true,
         "need_proxy": true,
@@ -439,6 +473,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "dnk",
             "mission_code": "dnk",
             "mission_name": "Denmark",
             "mission_name": "Denmark",
@@ -446,15 +490,9 @@
             "country_name": "Ireland",
             "country_name": "Ireland",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/irl/en/dnk/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.dub.dk.tourist",
+            "apt_configs": {
+                "slot.dub.dk.tourist": {
                     "center_name": "Denmark Visa Application Center, Dublin ",
                     "center_name": "Denmark Visa Application Center, Dublin ",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Denmark",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "vac_code": "DIDUB",
                     "vac_code": "DIDUB",
                     "category_name": "Schengen Visa",
                     "category_name": "Schengen Visa",
@@ -462,13 +500,13 @@
                     "subcategory_name": "Tourism",
                     "subcategory_name": "Tourism",
                     "subcategory_code": "TV"
                     "subcategory_code": "TV"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_IE_FI",
         "identifier": "VFS_IE_FI",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "ie_fi",
         "local_account_pool": "ie_fi",
         "need_proxy": true,
         "need_proxy": true,
@@ -492,6 +530,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "fin",
             "mission_code": "fin",
             "mission_name": "Finland",
             "mission_name": "Finland",
@@ -499,15 +547,9 @@
             "country_name": "Ireland",
             "country_name": "Ireland",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/irl/en/fin/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.dub.fi.tourist",
+            "apt_configs": {
+                "slot.dub.fi.tourist": {
                     "center_name": "Application Centre, Dublin",
                     "center_name": "Application Centre, Dublin",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Finland",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "vac_code": "Dubb",
                     "vac_code": "Dubb",
                     "category_name": "VISA",
                     "category_name": "VISA",
@@ -515,13 +557,13 @@
                     "subcategory_name": "Tourist Category",
                     "subcategory_name": "Tourist Category",
                     "subcategory_code": "Tourist Category"
                     "subcategory_code": "Tourist Category"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_IE_HU",
         "identifier": "VFS_IE_HU",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "ie_hu",
         "local_account_pool": "ie_hu",
         "need_proxy": true,
         "need_proxy": true,
@@ -545,6 +587,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "hun",
             "mission_code": "hun",
             "mission_name": "Hungary",
             "mission_name": "Hungary",
@@ -552,15 +604,9 @@
             "country_name": "Ireland",
             "country_name": "Ireland",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/irl/en/hun/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.dub.hu.tourist",
+            "apt_configs": {
+                "slot.dub.hu.tourist": {
                     "center_name": "Ireland Visa Application Center,Dublin",
                     "center_name": "Ireland Visa Application Center,Dublin",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Hungary",
                     "address": "Cunningham House, 130 Francis Street Dublin",
                     "address": "Cunningham House, 130 Francis Street Dublin",
                     "vac_code": "DUB",
                     "vac_code": "DUB",
                     "category_name": "Short Stay",
                     "category_name": "Short Stay",
@@ -568,13 +614,13 @@
                     "subcategory_name": "Schengen Visa",
                     "subcategory_name": "Schengen Visa",
                     "subcategory_code": "Schengen Visa"
                     "subcategory_code": "Schengen Visa"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "VFS_IE_IS",
         "identifier": "VFS_IE_IS",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "ie_is",
         "local_account_pool": "ie_is",
         "need_proxy": true,
         "need_proxy": true,
@@ -598,6 +644,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "mission_code": "isl",
             "mission_code": "isl",
             "mission_name": "Iceland",
             "mission_name": "Iceland",
@@ -605,15 +661,9 @@
             "country_name": "Ireland",
             "country_name": "Ireland",
             "culture_code": "en-US",
             "culture_code": "en-US",
             "language": "en",
             "language": "en",
-            "website": "https://visa.vfsglobal.com/irl/en/isl/login",
-            "appointment_types": [
-                {
-                    "weight": 10,
-                    "routing_key": "slot.dub.is.tourist",
+            "apt_configs": {
+                "slot.dub.is.tourist": {
                     "center_name": "Iceland Visa Application Center- Dublin",
                     "center_name": "Iceland Visa Application Center- Dublin",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Iceland",
                     "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
                     "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
                     "vac_code": "DUB",
                     "vac_code": "DUB",
                     "category_name": "C-Visa",
                     "category_name": "C-Visa",
@@ -621,13 +671,13 @@
                     "subcategory_name": "Tourism",
                     "subcategory_name": "Tourism",
                     "subcategory_code": "OTT"
                     "subcategory_code": "OTT"
                 }
                 }
-            ]
+            }
         }
         }
     },
     },
     {
     {
         "identifier": "BLS_IE_ES",
         "identifier": "BLS_IE_ES",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "ie_es",
         "local_account_pool": "ie_es",
         "need_proxy": true,
         "need_proxy": true,
@@ -651,23 +701,30 @@
             "plugin_bin": "bls_plugin.py",
             "plugin_bin": "bls_plugin.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "domain": "ireland.blsspainglobal.com",
             "domain": "ireland.blsspainglobal.com",
             "ocr_model": "data/ctc.pth",
             "ocr_model": "data/ctc.pth",
-            "query_selector": {
-                "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"
-            },
-            "city": "Dublin",
-            "country": "Spain",
-            "visa_type": "Tourist",
-            "routing_key": "slot.dub.es.tourist",
-            "website": "https://ireland.blsspainglobal.com/Global/bls/visatypeverification"
+            "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"
+                }
+            }
         }
         }
     },
     },
     {
     {
@@ -697,29 +754,36 @@
             "plugin_bin": "bls_plugin.py",
             "plugin_bin": "bls_plugin.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "domain": "uk.blsspainglobal.com",
             "domain": "uk.blsspainglobal.com",
-            "local_service_url": "http://127.0.0.1:8085",
-            "query_selector": {
-                "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"
-            },
-            "city": "London",
-            "country": "Spain",
-            "visa_type": "Tourist",
-            "routing_key": "slot.lon.es.tourist",
-            "website": "https://uk.blsspainglobal.com/Global/bls/visatypeverification"
+            "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",
         "identifier": "TLS_GB_FR",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "need_account": true,
         "local_account_pool": "gb_fr",
         "local_account_pool": "gb_fr",
         "need_proxy": true,
         "need_proxy": true,
@@ -737,36 +801,42 @@
             "random_min": 60,
             "random_min": 60,
             "random_max": 300
             "random_max": 300
         },
         },
+
         "plugin_config": {
         "plugin_config": {
             "lib_path": "plugins",
             "lib_path": "plugins",
             "plugin_name": "tls_plugin2",
             "plugin_name": "tls_plugin2",
             "plugin_bin": "tls_plugin2.py",
             "plugin_bin": "tls_plugin2.py",
             "plugin_proto": "IVSPlg"
             "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/country/gb/vac/gbLON2fr/",
         "free_config": {
         "free_config": {
-            "center": {
+            "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+            "apt_config": {
                 "code": "gbLON2fr",
                 "code": "gbLON2fr",
                 "country": "gb",
                 "country": "gb",
                 "mission": "fr",
                 "mission": "fr",
                 "city": "London"
                 "city": "London"
             },
             },
-            "city": "London",
-            "country": "France",
-            "visa_type": "Tourist",
-            "routing_key": "slot.lon.fr.tourist",
-            "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
             "interest_month": "02-2026",
             "interest_month": "02-2026",
             "target_labels": [
             "target_labels": [
                 "",
                 "",
                 "pta"
                 "pta"
-            ],
-            "website": "https://visas-fr.tlscontact.com/country/gb/vac/gbLON2fr/"
+            ]
         }
         }
     },
     },
     {
     {
         "identifier": "VISAMETRIC_IE_DE",
         "identifier": "VISAMETRIC_IE_DE",
         "debug": false,
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": false,
         "need_account": false,
         "local_account_pool": "",
         "local_account_pool": "",
         "need_proxy": true,
         "need_proxy": true,
@@ -790,14 +860,19 @@
             "plugin_bin": "de_plugin.py",
             "plugin_bin": "de_plugin.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
             "base_url": "https://ie-appointment.visametric.com",
             "base_url": "https://ie-appointment.visametric.com",
-            "consularid": 1,
-            "city": "Dublin",
-            "country": "Germany",
-            "visa_type": "Tourist",
-            "routing_key": "slot.dub.de.tourist",
-            "website": "https://ie-appointment.visametric.com/en"
+            "consularid": 1
         }
         }
     },
     },
     {
     {
@@ -827,13 +902,18 @@
             "plugin_bin": "ita_plugin.py",
             "plugin_bin": "ita_plugin.py",
             "plugin_proto": "IVSPlg"
             "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": {
         "free_config": {
-            "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525",
-            "city": "Dublin",
-            "country": "Italy",
-            "visa_type": "Tourist",
-            "routing_key": "slot.dub.it.tourist",
-            "website": "https://prenotami.esteri.it/Home"
+            "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525"
         }
         }
     }
     }
 ]
 ]

+ 9 - 0
data/account_states.json

@@ -0,0 +1,9 @@
+{
+    "ie_nl::ie_nl_vxzo9t@gmail-app.com": 1769637414.02752,
+    "ie_nl::ie_nl_y118ln@gmail-app.com": 1769637659.231531,
+    "ie_nl::ie_nl_yk3mah@gmail-app.com": 1769637843.617249,
+    "ie_nl::ie_nl_x0rh02@gmail-app.com": 1769637865.6890712,
+    "ie_nl::ie_nl_cluyap@gmail-app.com": 1769637902.6951838,
+    "ie_nl::ie_nl_cx2v7z@gmail-app.com": 1769638247.758345,
+    "ie_nl::ie_nl_quqc36@gmail-app.com": 1769638336.8477702
+}

+ 3 - 0
data/proxy_states.json

@@ -0,0 +1,3 @@
+{
+    "isp_proxy::100011": 1769627541.849858
+}

+ 33 - 6
gco.py

@@ -4,12 +4,12 @@ import time
 import json
 import json
 import random
 import random
 import threading
 import threading
+from datetime import datetime, timezone
 from typing import List, Dict, Tuple, Any, Optional, Callable
 from typing import List, Dict, Tuple, Any, Optional, Callable
 from concurrent.futures import wait
 from concurrent.futures import wait
 
 
 # 导入所有依赖
 # 导入所有依赖
 from vs_types import GroupConfig, QueryWaitMode, VSPlgConfig, VSQueryResult, Task 
 from vs_types import GroupConfig, QueryWaitMode, VSPlgConfig, VSQueryResult, Task 
-from vs_plg import IVSPlg 
 from vs_plg_factory import VSPlgFactory 
 from vs_plg_factory import VSPlgFactory 
 from toolkit.account_manager import AccountManager 
 from toolkit.account_manager import AccountManager 
 from toolkit.proxy_manager import ProxyManager 
 from toolkit.proxy_manager import ProxyManager 
@@ -135,15 +135,36 @@ class GCO:
                     continue
                     continue
                 
                 
                 # === 执行查询 ===
                 # === 执行查询 ===
+                apt_type = None
                 try:
                 try:
-                    # 这里的 task 充当了“哨兵”的角色
-                    result = task.instance.query()
+                    apt_types = self.m_cfg.appointment_types
+                    if not apt_types:
+                        self._log(f"No matching appointment configuration found.")
+                        continue
+                    
+                    weights = [float(item.weight) for item in apt_types]
+                    apt_type = random.choices(apt_types, weights=weights, k=1)[0]
                     
                     
+                    VSCloudApi.Instance().slot_refresh_start(
+                        apt_type.routing_key,
+                        country=apt_type.country,
+                        city=apt_type.city,
+                        visa_type=apt_type.visa_type
+                    )
+                    # 这里的 task 充当了“哨兵”的角色
+                    result = task.instance.query(apt_type)
+                    VSCloudApi.Instance().slot_refresh_success(
+                        apt_type.routing_key
+                    )
                     if result.success:
                     if result.success:
                         self._log(f"🔥 Slot Found by [{task.instance.get_group_id()}]! Triggering BATCH BOOKING for {len(tasks_to_process)} workers.")
                         self._log(f"🔥 Slot Found by [{task.instance.get_group_id()}]! Triggering BATCH BOOKING for {len(tasks_to_process)} workers.")
-                        
+                       
+                        query_payload = result.to_snapshot_payload()
+                        query_payload["website"] = self.m_cfg.website
+                        query_payload["snapshot_source"] = 'worker'
+                        query_payload["snapshot_at"] = datetime.now(timezone.utc).isoformat()
+                        VSCloudApi.Instance().slot_snapshot_report(query_payload)
                         # === [核心修改]:一人发现,全员出击 ===
                         # === [核心修改]:一人发现,全员出击 ===
-                        
                         # 1. 准备并发任务
                         # 1. 准备并发任务
                         # 我们使用刚刚快照的 tasks_to_process,或者重新获取一次全量列表
                         # 我们使用刚刚快照的 tasks_to_process,或者重新获取一次全量列表
                         # 重点:所有实例 (w.instance) 都使用同一份查询结果 (result) 去抢
                         # 重点:所有实例 (w.instance) 都使用同一份查询结果 (result) 去抢
@@ -176,6 +197,11 @@ class GCO:
 
 
                 except Exception as e:
                 except Exception as e:
                     self._log(f"Exception during query: {e}")
                     self._log(f"Exception during query: {e}")
+                    if apt_type:
+                        VSCloudApi.Instance().slot_refresh_fail(
+                            apt_type.routing_key,
+                            error=str(e)
+                        )
 
 
                 # === 计算下次运行时间 (仅针对当前 query 的 task,除非触发了 batch) ===
                 # === 计算下次运行时间 (仅针对当前 query 的 task,除非触发了 batch) ===
                 if not batch_booking_triggered:
                 if not batch_booking_triggered:
@@ -495,7 +521,8 @@ class GCO:
 
 
         # 如果没有绑定本地任务,尝试从云端 Pop
         # 如果没有绑定本地任务,尝试从云端 Pop
         if not task_data:
         if not task_data:
-            booking_routing_key = f'auto.{query_result.routing_key}' if query_result.routing_key else "default"
+            apt_type = query_result.apt_type
+            booking_routing_key = f'auto.{apt_type.routing_key}' if apt_type.routing_key else "default"
             task_data = VSCloudApi.Instance().get_vas_task_pop(booking_routing_key)
             task_data = VSCloudApi.Instance().get_vas_task_pop(booking_routing_key)
             
             
             if not task_data:
             if not task_data:

+ 13 - 16
plugins/bls_plugin.py

@@ -18,13 +18,12 @@ from bs4 import BeautifulSoup
 # DrissionPage 核心
 # DrissionPage 核心
 from DrissionPage import ChromiumPage, ChromiumOptions
 from DrissionPage import ChromiumPage, ChromiumOptions
 
 
-from cryptography.hazmat.primitives import serialization, hashes
-from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives import hashes
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.backends import default_backend
 
 
 # 框架依赖
 # 框架依赖
 from vs_plg import IVSPlg 
 from vs_plg import IVSPlg 
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, DateAvailability, TimeSlot, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, DateAvailability, TimeSlot, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.ocr_engine import PyTorchEngine
 from toolkit.ocr_engine import PyTorchEngine
 
 
@@ -198,8 +197,11 @@ class BlsPlugin(IVSPlg):
     # =========================================================================
     # =========================================================================
     # 2. 查询流程 (Query)
     # 2. 查询流程 (Query)
     # =========================================================================
     # =========================================================================
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         res = VSQueryResult()
+        res.apt_type = apt_type
+        
+        apt_config = self.free_config.get("apt_configs", {}).get(apt_type.routing_key)
         domain = self.free_config.get("domain")
         domain = self.free_config.get("domain")
 
 
         # 2.1 签证类型验证
         # 2.1 签证类型验证
@@ -231,12 +233,8 @@ class BlsPlugin(IVSPlg):
         self._check_resp_is_session_expired_or_invalid('APPLICATION PROCESS', resp)
         self._check_resp_is_session_expired_or_invalid('APPLICATION PROCESS', resp)
         
         
         # 这里需要极其复杂的 JS 变量提取 (JS Arrays -> Match Name -> Get ID)
         # 这里需要极其复杂的 JS 变量提取 (JS Arrays -> Match Name -> Get ID)
-        vt_payload = self._construct_visatype_payload(vt_resp.text, BeautifulSoup(vt_resp.text, 'html.parser'))
+        vt_payload = self._construct_visatype_payload(apt_config, vt_resp.text, BeautifulSoup(vt_resp.text, 'html.parser'))
         
         
-        res.city = self.free_config.get('city', '')
-        res.country = self.free_config.get('country', '')
-        res.visa_type = self.free_config.get('visa_type', '')
-        res.routing_key = self.free_config.get('routing_key', '')
         vt_res = self._perform_request('POST', f"https://{domain}/Global/bls/VisaType", data=vt_payload)
         vt_res = self._perform_request('POST', f"https://{domain}/Global/bls/VisaType", data=vt_payload)
         if not vt_res.json()['success']:
         if not vt_res.json()['success']:
             if not vt_res.json()['available']:
             if not vt_res.json()['available']:
@@ -588,7 +586,7 @@ class BlsPlugin(IVSPlg):
             if match: return match.group(1)
             if match: return match.group(1)
         return ""
         return ""
 
 
-    def _construct_visatype_payload(self, html: str, soup: BeautifulSoup) -> Optional[Dict]:
+    def _construct_visatype_payload(self, apt_config, html: str, soup: BeautifulSoup) -> Optional[Dict]:
         """
         """
         构造 VisaType 提交参数 (对应原代码 parse_visatype_form)
         构造 VisaType 提交参数 (对应原代码 parse_visatype_form)
         """
         """
@@ -608,12 +606,11 @@ class BlsPlugin(IVSPlg):
             return []
             return []
         
         
         # 读取配置
         # 读取配置
-        query_selector = self.free_config.get("query_selector", {})
-        cfg_jur = query_selector.get("jurisdiction")
-        cfg_loc = query_selector.get("location")
-        cfg_type = query_selector.get("visa_type")
-        cfg_subtype = query_selector.get("visa_subtype")
-        cfg_cat = query_selector.get("appointment_category")
+        cfg_jur = apt_config.get("jurisdiction")
+        cfg_loc = apt_config.get("location")
+        cfg_type = apt_config.get("visa_type")
+        cfg_subtype = apt_config.get("visa_subtype")
+        cfg_cat = apt_config.get("appointment_category")
         
         
         jur_value = None
         jur_value = None
         loc_value = None
         loc_value = None

+ 3 - 7
plugins/de_plugin.py

@@ -13,7 +13,7 @@ from bs4 import BeautifulSoup
 
 
 
 
 from vs_plg import IVSPlg 
 from vs_plg import IVSPlg 
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, TimeSlot, DateAvailability, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, TimeSlot, DateAvailability, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from toolkit.vs_cloud_api import VSCloudApi 
 from toolkit.vs_cloud_api import VSCloudApi 
 from toolkit.ocr_engine import DddOcrEngine
 from toolkit.ocr_engine import DddOcrEngine
 
 
@@ -139,12 +139,12 @@ class DePlugin(IVSPlg):
         self.session_create_time = time.time()
         self.session_create_time = time.time()
         self._log("Session created successfully.")
         self._log("Session created successfully.")
 
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         """
         """
         查询可用日期 (/getdate)
         查询可用日期 (/getdate)
         """
         """
         res = VSQueryResult()
         res = VSQueryResult()
-
+        res.apt_type = apt_type
         # 构造 Payload (参考 get_slot_day) 这里的 ID 需要根据实际情况配置
         # 构造 Payload (参考 get_slot_day) 这里的 ID 需要根据实际情况配置
         consular_id = self.free_config.get("consularid", "1") # 1=Ireland?
         consular_id = self.free_config.get("consularid", "1") # 1=Ireland?
         max_retries = self.free_config.get("slot_query_max_retries", 2)
         max_retries = self.free_config.get("slot_query_max_retries", 2)
@@ -182,10 +182,6 @@ class DePlugin(IVSPlg):
         j = resp.json()
         j = resp.json()
         dates = j.get("getDateEnable", [])
         dates = j.get("getDateEnable", [])
         
         
-        res.city = self.free_config.get('city', '')
-        res.country = self.free_config.get('country', '')
-        res.visa_type = self.free_config.get('visa_type', '')
-        res.routing_key = self.free_config.get('routing_key', '')
         if dates:
         if dates:
             res.success = True
             res.success = True
             res.availability_status = AvailabilityStatus.Available
             res.availability_status = AvailabilityStatus.Available

+ 2 - 7
plugins/de_plugin2.py

@@ -15,7 +15,7 @@ from urllib.parse import urljoin, urlparse, urlencode
 from DrissionPage import ChromiumPage, ChromiumOptions
 from DrissionPage import ChromiumPage, ChromiumOptions
 
 
 from vs_plg import IVSPlg
 from vs_plg import IVSPlg
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.vs_cloud_api import VSCloudApi
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from toolkit.proxy_tunnel import ProxyTunnel
 from toolkit.proxy_tunnel import ProxyTunnel
@@ -249,7 +249,7 @@ class DePlugin2(IVSPlg):
         m = re.search(r'name="csrf-token" content="([^"]+)"', html)
         m = re.search(r'name="csrf-token" content="([^"]+)"', html)
         if m: self.csrf_token = m.group(1)
         if m: self.csrf_token = m.group(1)
 
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         res = VSQueryResult()
         res.success = False
         res.success = False
         
         
@@ -277,11 +277,6 @@ class DePlugin2(IVSPlg):
         j = resp.json()
         j = resp.json()
         dates = j.get("getDateEnable", [])
         dates = j.get("getDateEnable", [])
         
         
-        res.city = self.free_config.get('city', '')
-        res.country = self.free_config.get('country', '')
-        res.visa_type = self.free_config.get('visa_type', '')
-        res.routing_key = self.free_config.get('routing_key', '')
-        
         if dates:
         if dates:
             res.success = True
             res.success = True
             res.availability_status = AvailabilityStatus.Available
             res.availability_status = AvailabilityStatus.Available

+ 2 - 2
plugins/ita_plugin.py

@@ -14,7 +14,7 @@ from urllib.parse import urlencode, urlparse
 from DrissionPage import ChromiumPage, ChromiumOptions
 from DrissionPage import ChromiumPage, ChromiumOptions
 
 
 from vs_plg import IVSPlg
 from vs_plg import IVSPlg
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from toolkit.proxy_tunnel import ProxyTunnel
 from toolkit.proxy_tunnel import ProxyTunnel
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.vs_cloud_api import VSCloudApi
 
 
@@ -240,7 +240,7 @@ class ItaPlugin(IVSPlg):
     # -------------------------------------------------------------
     # -------------------------------------------------------------
     # 2. Query Availability
     # 2. Query Availability
     # -------------------------------------------------------------
     # -------------------------------------------------------------
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         res = VSQueryResult()
         res.success = False
         res.success = False
         res.availability_status = AvailabilityStatus.NoneAvailable
         res.availability_status = AvailabilityStatus.NoneAvailable

+ 20 - 23
plugins/tls_plugin.py

@@ -12,7 +12,7 @@ from curl_cffi import requests, const
 from bs4 import BeautifulSoup
 from bs4 import BeautifulSoup
 
 
 from vs_plg import IVSPlg
 from vs_plg import IVSPlg
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.vs_cloud_api import VSCloudApi
 
 
 class TlsPlugin(IVSPlg):
 class TlsPlugin(IVSPlg):
@@ -75,9 +75,9 @@ class TlsPlugin(IVSPlg):
             http_version=const.CurlHttpVersion.V2TLS
             http_version=const.CurlHttpVersion.V2TLS
         )
         )
 
 
-        embassy = self.free_config.get('center', {})
-        if not embassy:
-            raise NotFoundError(message="center not found in free config")
+        apt_config = self.free_config.get('apt_config', {})
+        if not apt_config:
+            raise NotFoundError(message="apt_config not found in free config")
 
 
         # 2. 解决 Cloudflare 5s 盾
         # 2. 解决 Cloudflare 5s 盾
         self._solve_cloudflare5S_challenge()
         self._solve_cloudflare5S_challenge()
@@ -85,15 +85,15 @@ class TlsPlugin(IVSPlg):
         # 3. 获取登录页面参数 (OIDC)
         # 3. 获取登录页面参数 (OIDC)
         login_page = "https://visas-fr.tlscontact.com/en-us/login"
         login_page = "https://visas-fr.tlscontact.com/en-us/login"
         params = {
         params = {
-            "issuerId": embassy["code"],
-            "country": embassy["country"], 
-            "vac": embassy["code"],
-            "redirect": f"/en-us/country/{embassy['country']}/vac/{embassy['code']}"
+            "issuerId": apt_config["code"],
+            "country": apt_config["country"], 
+            "vac": apt_config["code"],
+            "redirect": f"/en-us/country/{apt_config['country']}/vac/{apt_config['code']}"
         }
         }
         headers = {
         headers = {
             'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
             'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
             'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
             'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
-            'Referer': f'https://visas-fr.tlscontact.com/en-us/country/{embassy["country"]}/vac/{embassy["code"]}',
+            'Referer': f'https://visas-fr.tlscontact.com/en-us/country/{apt_config["country"]}/vac/{apt_config["code"]}',
             'User-Agent': self.user_agent,
             'User-Agent': self.user_agent,
         }
         }
         resp = self._perform_request("GET", login_page, headers=headers, params=params)
         resp = self._perform_request("GET", login_page, headers=headers, params=params)
@@ -138,7 +138,7 @@ class TlsPlugin(IVSPlg):
         groups = self._parse_travel_groups(resp.text)
         groups = self._parse_travel_groups(resp.text)
             
             
         # 选择匹配城市的 Group
         # 选择匹配城市的 Group
-        target_city = embassy['city'].lower()
+        target_city = apt_config['city'].lower()
         for g in groups:
         for g in groups:
             if g['location'].lower() == target_city:
             if g['location'].lower() == target_city:
                 self.travel_group = g
                 self.travel_group = g
@@ -149,23 +149,24 @@ class TlsPlugin(IVSPlg):
         self.session_create_time = time.time()
         self.session_create_time = time.time()
         self._log(f"Session created successfully. Group: {self.travel_group['group_number']}")
         self._log(f"Session created successfully. Group: {self.travel_group['group_number']}")
 
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         res = VSQueryResult()
         res.success = False
         res.success = False
-        embassy = self.free_config.get('center', {})
+        res.apt_type = apt_type
+        apt_config = self.free_config.get('apt_config', {})
         group_num = self.travel_group['group_number']
         group_num = self.travel_group['group_number']
         interest_month = self.free_config.get("interest_month", time.strftime("%m-%Y"))
         interest_month = self.free_config.get("interest_month", time.strftime("%m-%Y"))
         max_retries = self.free_config.get("max_retries", 2)
         max_retries = self.free_config.get("max_retries", 2)
         
         
         url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking'
         url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking'
         params = {
         params = {
-            'location': embassy["code"],
+            'location': apt_config["code"],
             'month': interest_month,
             'month': interest_month,
         }
         }
         headers = {
         headers = {
             'accept': '*/*',
             'accept': '*/*',
             'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
             'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
-            'referer': f'{url}?location={embassy["code"]}',
+            'referer': f'{url}?location={apt_config["code"]}',
             'user-agent': self.user_agent,
             'user-agent': self.user_agent,
         }
         }
 
 
@@ -194,10 +195,6 @@ class TlsPlugin(IVSPlg):
         target_labels = self.free_config.get("target_labels", ["", "pta"])
         target_labels = self.free_config.get("target_labels", ["", "pta"])
         available = [s for s in all_slots if s.get("label") in target_labels]
         available = [s for s in all_slots if s.get("label") in target_labels]
 
 
-        res.city = self.free_config.get('city', '')
-        res.country = self.free_config.get('country', '')
-        res.visa_type = self.free_config.get('visa_type', '')
-        res.routing_key = self.free_config.get('routing_key', '')
         if available:
         if available:
             res.success = True
             res.success = True
             res.availability_status = AvailabilityStatus.Available
             res.availability_status = AvailabilityStatus.Available
@@ -231,7 +228,7 @@ class TlsPlugin(IVSPlg):
         res.success = False
         res.success = False
         
         
         # 1. 基础信息提取
         # 1. 基础信息提取
-        embassy = self.free_config.get('center', {})
+        apt_config = self.free_config.get('apt_config', {})
         group_num = self.travel_group['group_number']
         group_num = self.travel_group['group_number']
         
         
         available_dates = [da.date for da in slot_info.availability]
         available_dates = [da.date for da in slot_info.availability]
@@ -265,7 +262,7 @@ class TlsPlugin(IVSPlg):
 
 
         # 2. 解决 ReCaptcha V3
         # 2. 解决 ReCaptcha V3
         # 动作必须是 "book"
         # 动作必须是 "book"
-        page_url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking?location={embassy["code"]}&month={selected_date[:7]}'
+        page_url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking?location={apt_config["code"]}&month={selected_date[:7]}'
         
         
         api_token = self.free_config.get("capsolver_key", "")
         api_token = self.free_config.get("capsolver_key", "")
         rc_params = {
         rc_params = {
@@ -286,7 +283,7 @@ class TlsPlugin(IVSPlg):
             '1_formGroupId': str(group_num),      # 修正:加了 form 前缀
             '1_formGroupId': str(group_num),      # 修正:加了 form 前缀
             '1_lang': 'en-us',
             '1_lang': 'en-us',
             '1_process': 'APPOINTMENT',
             '1_process': 'APPOINTMENT',
-            '1_location': embassy["code"],        # 例如 gbLON2fr
+            '1_location': apt_config["code"],        # 例如 gbLON2fr
             '1_date': selected_date,
             '1_date': selected_date,
             '1_time': selected_time,
             '1_time': selected_time,
             '1_appointmentLabel': selected_label,   # 修正:单数 Label,值为字符串 "pta" 或 "regular"
             '1_appointmentLabel': selected_label,   # 修正:单数 Label,值为字符串 "pta" 或 "regular"
@@ -390,8 +387,8 @@ class TlsPlugin(IVSPlg):
         解决 Cloudflare 5s 盾
         解决 Cloudflare 5s 盾
         """
         """
         self._log(f"Solving Cloudflare 5s...")
         self._log(f"Solving Cloudflare 5s...")
-        embassy = self.free_config.get('center', {})
-        website_url = f'https://visas-fr.tlscontact.com/en-us/country/{embassy["country"]}'
+        apt_config = self.free_config.get('apt_config', {})
+        website_url = f'https://visas-fr.tlscontact.com/en-us/country/{apt_config["country"]}'
         
         
         # 1. 格式化代理字符串, 这里的接口要求格式通常是: host:port:user:pass (根据你的脚本示例)
         # 1. 格式化代理字符串, 这里的接口要求格式通常是: host:port:user:pass (根据你的脚本示例)
         p = self.config.proxy
         p = self.config.proxy

+ 16 - 14
plugins/tls_plugin2.py

@@ -13,10 +13,9 @@ from urllib.parse import urljoin, urlparse, urlencode
 from DrissionPage import ChromiumPage, ChromiumOptions
 from DrissionPage import ChromiumPage, ChromiumOptions
 
 
 from vs_plg import IVSPlg
 from vs_plg import IVSPlg
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from toolkit.proxy_tunnel import ProxyTunnel
 from toolkit.proxy_tunnel import ProxyTunnel
-from toolkit.vs_cloud_api import VSCloudApi
 
 
 
 
 class BrowserResponse:
 class BrowserResponse:
@@ -177,13 +176,16 @@ class TlsPlugin2(IVSPlg):
         try:
         try:
             self.page = ChromiumPage(co)
             self.page = ChromiumPage(co)
             
             
-            embassy = self.free_config.get('center', {})
-            if not embassy: raise NotFoundError("center config missing")
+            apt_config = self.free_config.get('apt_config', {})
+            if not apt_config:
+                raise NotFoundError("apt_config config missing")
 
 
             login_url = "https://visas-fr.tlscontact.com/en-us/login"
             login_url = "https://visas-fr.tlscontact.com/en-us/login"
             params = {
             params = {
-                "issuerId": embassy["code"], "country": embassy["country"], "vac": embassy["code"],
-                "redirect": f"/en-us/country/{embassy['country']}/vac/{embassy['code']}"
+                "issuerId": apt_config["code"],
+                "country": apt_config["country"],
+                "vac": apt_config["code"],
+                "redirect": f"/en-us/country/{apt_config['country']}/vac/{apt_config['code']}"
             }
             }
             full_login_url = f"{login_url}?{urlencode(params)}"
             full_login_url = f"{login_url}?{urlencode(params)}"
             
             
@@ -253,7 +255,7 @@ class TlsPlugin2(IVSPlg):
             self._check_page_is_session_expired_or_invalid("My travel group", html)
             self._check_page_is_session_expired_or_invalid("My travel group", html)
             groups = self._parse_travel_groups(html)
             groups = self._parse_travel_groups(html)
             
             
-            target_city = embassy['city'].lower()
+            target_city = apt_config['city'].lower()
             for g in groups:
             for g in groups:
                 if g['location'].lower() == target_city:
                 if g['location'].lower() == target_city:
                     self.travel_group = g
                     self.travel_group = g
@@ -270,17 +272,17 @@ class TlsPlugin2(IVSPlg):
             self.cleanup()
             self.cleanup()
             raise e
             raise e
 
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         res = VSQueryResult()
         res.success = False
         res.success = False
-        
-        embassy = self.free_config.get('center', {})
+        res.apt_type = apt_type
+        apt_config = self.free_config.get('apt_config', {})
         group_num = self.travel_group['group_number']
         group_num = self.travel_group['group_number']
         interest_month = self.free_config.get("interest_month", time.strftime("%m-%Y"))
         interest_month = self.free_config.get("interest_month", time.strftime("%m-%Y"))
         
         
         url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking'
         url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking'
         params = {
         params = {
-            'location': embassy["code"],
+            'location': apt_config["code"],
             'month': interest_month,
             'month': interest_month,
         }
         }
         
         
@@ -325,7 +327,7 @@ class TlsPlugin2(IVSPlg):
         res = VSBookResult()
         res = VSBookResult()
         res.success = False
         res.success = False
         
         
-        embassy = self.free_config.get('center', {})
+        apt_config = self.free_config.get('apt_config', {})
         group_num = self.travel_group['group_number']
         group_num = self.travel_group['group_number']
         
         
         available_dates = [da.date for da in slot_info.availability]
         available_dates = [da.date for da in slot_info.availability]
@@ -360,7 +362,7 @@ class TlsPlugin2(IVSPlg):
              raise NotFoundError(message="No suitable slot found")
              raise NotFoundError(message="No suitable slot found")
 
 
         # 2. 解决 ReCaptcha V3 (Action: book)
         # 2. 解决 ReCaptcha V3 (Action: book)
-        page_url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking?location={embassy["code"]}&month={selected_date[:7]}'
+        page_url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking?location={apt_config["code"]}&month={selected_date[:7]}'
         
         
         api_token = self.free_config.get("capsolver_key", "")
         api_token = self.free_config.get("capsolver_key", "")
         rc_params = {
         rc_params = {
@@ -390,7 +392,7 @@ class TlsPlugin2(IVSPlg):
         formData.append('1_formGroupId', '{group_num}');
         formData.append('1_formGroupId', '{group_num}');
         formData.append('1_lang', 'en-us');
         formData.append('1_lang', 'en-us');
         formData.append('1_process', 'APPOINTMENT');
         formData.append('1_process', 'APPOINTMENT');
-        formData.append('1_location', '{embassy["code"]}');
+        formData.append('1_location', '{apt_config["code"]}');
         formData.append('1_date', '{selected_date}');
         formData.append('1_date', '{selected_date}');
         formData.append('1_time', '{selected_time.time}');
         formData.append('1_time', '{selected_time.time}');
         formData.append('1_appointmentLabel', '{selected_label}');
         formData.append('1_appointmentLabel', '{selected_label}');

+ 10 - 18
plugins/vfs_plugin.py

@@ -15,7 +15,7 @@ from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.backends import default_backend
 
 
 from vs_plg import IVSPlg 
 from vs_plg import IVSPlg 
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, DateAvailability, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, DateAvailability, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from toolkit.vs_cloud_api import VSCloudApi 
 from toolkit.vs_cloud_api import VSCloudApi 
 
 
 # ----------------- 静态常量与辅助数据 -----------------
 # ----------------- 静态常量与辅助数据 -----------------
@@ -204,21 +204,15 @@ class VfsPlugin(IVSPlg):
         self.session_create_time = time.time()
         self.session_create_time = time.time()
         self._log("Session created successfully.")
         self._log("Session created successfully.")
 
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         """查询可预约 Slot"""
         """查询可预约 Slot"""
-        result = VSQueryResult() 
-        appt_types = self.free_config.get("appointment_types", [])
-        if not appt_types:
-            raise NotFoundError(message="No matching appointment configuration found.")
-        apt_config = random.choice(appt_types)
+        result = VSQueryResult()
+        apt_config = self.free_config.get("app_configs", {}).get(apt_type.routing_key)
         self._fetch_configurations(apt_config)
         self._fetch_configurations(apt_config)
         earliest_date = self._query_earliest_slot(apt_config)
         earliest_date = self._query_earliest_slot(apt_config)
         result.success = False
         result.success = False
         result.availability_status = AvailabilityStatus.NoneAvailable
         result.availability_status = AvailabilityStatus.NoneAvailable
-        result.visa_type = apt_config.get("visa_type", "")
-        result.city = apt_config.get("city", "")
-        result.country = apt_config.get("country", "")
-        result.routing_key = apt_config.get("routing_key", "")
+        result.apt_type = apt_type
         if earliest_date:
         if earliest_date:
             result.success = True
             result.success = True
 
 
@@ -244,16 +238,11 @@ class VfsPlugin(IVSPlg):
         user_inputs['alias_email'] = get_alias_email(user_email, new_domain="gmail-app.com")
         user_inputs['alias_email'] = get_alias_email(user_email, new_domain="gmail-app.com")
         
         
         res = VSBookResult()
         res = VSBookResult()
-        slot_routing_key = slot_info.routing_key
+        app_type = slot_info.apt_type
         
         
         from_date = slot_info.earliest_date if slot_info.earliest_date else datetime.now().strftime("%Y-%m-%d")
         from_date = slot_info.earliest_date if slot_info.earliest_date else datetime.now().strftime("%Y-%m-%d")
         
         
-        apt_config = None
-        appt_types = self.free_config.get("appointment_types", [])
-        for apt in appt_types:
-            if apt.get("routing_key") == slot_routing_key:
-                apt_config = apt
-                break
+        apt_config = self.free_config.get("app_configs", {}).get(app_type.routing_key)
         
         
         if not apt_config:
         if not apt_config:
             raise NotFoundError(message="Book: Config missing.")
             raise NotFoundError(message="Book: Config missing.")
@@ -316,6 +305,7 @@ class VfsPlugin(IVSPlg):
         if is_waitlist:
         if is_waitlist:
             if self._confirm_waitlist(apt_config, final_urn):
             if self._confirm_waitlist(apt_config, final_urn):
                 res.success = True
                 res.success = True
+                res.account = self.config.account.username
                 res.urn = final_urn
                 res.urn = final_urn
                 return res
                 return res
             raise BizLogicError(message='confirm waitlist failed')
             raise BizLogicError(message='confirm waitlist failed')
@@ -427,6 +417,8 @@ class VfsPlugin(IVSPlg):
     def _log(self, message):
     def _log(self, message):
         if self.logger:
         if self.logger:
             self.logger(f'[VfsPlugin] [{self.group_id}] {message}')
             self.logger(f'[VfsPlugin] [{self.group_id}] {message}')
+        else:
+            print(f'[VfsPlugin] [{self.group_id}] {message}')
     
     
     def _get_proxy_url(self):
     def _get_proxy_url(self):
             # 构造代理
             # 构造代理

+ 8 - 21
plugins/vfs_plugin2.py

@@ -22,7 +22,7 @@ from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.backends import default_backend
 
 
 from vs_plg import IVSPlg 
 from vs_plg import IVSPlg 
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, DateAvailability, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, DateAvailability, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.proxy_tunnel import ProxyTunnel
 from toolkit.proxy_tunnel import ProxyTunnel
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
@@ -394,15 +394,10 @@ class VfsPlugin2(IVSPlg):
             self.cleanup()
             self.cleanup()
             raise e
             raise e
 
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         """查询可预约 Slot"""
         """查询可预约 Slot"""
-        result = VSQueryResult() 
-        apt_types = self.free_config.get("appointment_types", [])
-        if not apt_types:
-            raise NotFoundError(message="No matching appointment configuration found.")
-        
-        weights = [float(item.get("weight", 1)) for item in apt_types]
-        apt_config = random.choices(apt_types, weights=weights, k=1)[0]
+        result = VSQueryResult()
+        apt_config = self.free_config.get("app_configs", {}).get(apt_type.routing_key)
         
         
         try:
         try:
             self._fetch_configurations(apt_config)
             self._fetch_configurations(apt_config)
@@ -410,10 +405,7 @@ class VfsPlugin2(IVSPlg):
             
             
             result.success = False
             result.success = False
             result.availability_status = AvailabilityStatus.NoneAvailable
             result.availability_status = AvailabilityStatus.NoneAvailable
-            result.visa_type = apt_config.get("visa_type", "")
-            result.city = apt_config.get("city", "")
-            result.country = apt_config.get("country", "")
-            result.routing_key = apt_config.get("routing_key", "")
+            result.apt_type = apt_config
             if earliest_date:
             if earliest_date:
                 result.success = True
                 result.success = True
                 if "WaitList" in earliest_date:
                 if "WaitList" in earliest_date:
@@ -933,17 +925,11 @@ class VfsPlugin2(IVSPlg):
         user_inputs['alias_email'] = get_alias_email(user_email, new_domain="gmail-app.com")
         user_inputs['alias_email'] = get_alias_email(user_email, new_domain="gmail-app.com")
         
         
         res = VSBookResult()
         res = VSBookResult()
-        slot_routing_key = slot_info.routing_key
+        app_type = slot_info.apt_type
         # 如果没有 earliest_date,默认从今天开始
         # 如果没有 earliest_date,默认从今天开始
         from_date = slot_info.earliest_date if slot_info.earliest_date else datetime.now().strftime("%Y-%m-%d")
         from_date = slot_info.earliest_date if slot_info.earliest_date else datetime.now().strftime("%Y-%m-%d")
         
         
-        # 2. 查找对应的配置
-        apt_config = None
-        appt_types = self.free_config.get("appointment_types", [])
-        for apt in appt_types:
-            if apt.get("routing_key") == slot_routing_key:
-                apt_config = apt
-                break
+        apt_config = self.free_config.get("app_configs", {}).get(app_type.routing_key)
         
         
         if not apt_config:
         if not apt_config:
             raise NotFoundError(message="Book: Config missing for this routing key.")
             raise NotFoundError(message="Book: Config missing for this routing key.")
@@ -1003,6 +989,7 @@ class VfsPlugin2(IVSPlg):
             if self._confirm_waitlist(apt_config, final_urn):
             if self._confirm_waitlist(apt_config, final_urn):
                 res.success = True
                 res.success = True
                 res.urn = final_urn
                 res.urn = final_urn
+                res.account = self.config.account.username
                 self._log("Waitlist confirmed.")
                 self._log("Waitlist confirmed.")
                 return res
                 return res
             raise BizLogicError(message='Confirm waitlist failed')
             raise BizLogicError(message='Confirm waitlist failed')

+ 78 - 1
toolkit/vs_cloud_api.py

@@ -3,6 +3,7 @@ import requests
 import json
 import json
 import time
 import time
 import urllib.parse
 import urllib.parse
+from datetime import datetime
 from typing import Dict, Any, Optional
 from typing import Dict, Any, Optional
 from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from vs_log_macros import VSC_ERROR, VSC_INFO, VSC_WARN, VSC_DEBUG
 from vs_log_macros import VSC_ERROR, VSC_INFO, VSC_WARN, VSC_DEBUG
@@ -40,7 +41,7 @@ class VSCloudApi:
         2. 发送实际请求
         2. 发送实际请求
         """
         """
         resp = self.session.request(method, url, headers=headers, data=data, json=json_data, params=params)
         resp = self.session.request(method, url, headers=headers, data=data, json=json_data, params=params)
-        VSC_INFO('vs_cloud', f'[perform request] {method} {url} {data} {json_data} {params} {resp.text}')
+        VSC_DEBUG('vs_cloud', f'[perform request] {method} {url} {data} {json_data} {params} {resp.text}')
         if resp.status_code == 200:
         if resp.status_code == 200:
             return resp
             return resp
         elif resp.status_code == 401:
         elif resp.status_code == 401:
@@ -216,6 +217,82 @@ class VSCloudApi:
             return result.get("data", {})
             return result.get("data", {})
         else:
         else:
             raise BizLogicError(message=f"Create http session biz error: {result.get('message')}")
             raise BizLogicError(message=f"Create http session biz error: {result.get('message')}")
+        
+    def slot_refresh_start(
+        self,
+        routing_key: str,
+        country: str = "",
+        city: str = "",
+        visa_type: str = "Tourist",
+        snapshot_source: str = "worker",
+    ):
+        url = f"https://visafly.top/api/slot_refresh/start"
+        payload = {
+            "routing_key": routing_key,
+            "country": country,
+            "city": city,
+            "visa_type": visa_type,
+            "snapshot_source": snapshot_source,
+        }
+        headers = self._get_headers()
+        resp = self._perform_request('POST', url, headers=headers, json_data=payload)
+        result = resp.json()
+        if result.get("code") == 0:
+            return result.get("data", {})
+        else:
+            raise BizLogicError(message=f"Slot refresh start biz error: {result.get('message')}")
+
+    def slot_refresh_success(
+        self,
+        routing_key: str,
+        snapshot_source: str = "worker",
+    ):
+        url = 'https://visafly.top/api/slot_refresh/success'
+        payload = {
+            "routing_key": routing_key,
+            "snapshot_source": snapshot_source,
+        }
+        headers = self._get_headers()
+        resp = self._perform_request('POST', url, headers=headers, json_data=payload)
+        result = resp.json()
+        if result.get("code") == 0:
+            return result.get("data", {})
+        else:
+            raise BizLogicError(message=f"Slot refresh success biz error: {result.get('message')}")
+
+    def slot_refresh_fail(
+        self,
+        routing_key: str,
+        error: str,
+        snapshot_source: str = "worker",
+    ):
+        url = 'https://visafly.top/api/slot_refresh/fail'
+        payload = {
+            "routing_key": routing_key,
+            "snapshot_source": snapshot_source,
+            "error": error,
+        }
+        headers = self._get_headers()
+        resp = self._perform_request('POST', url, headers=headers, json_data=payload)
+        result = resp.json()
+        if result.get("code") == 0:
+            return result.get("data", {})
+        else:
+            raise BizLogicError(message=f"Slot refresh fail biz error: {result.get('message')}")
+
+    def slot_snapshot_report(
+        self,
+        query_payload: Dict[str, Any] = {}
+    ):
+        url = "https://visafly.top/api/slots/report"
+        
+        headers = self._get_headers()
+        resp = self._perform_request("POST", url, headers=headers, json_data=query_payload)
+        result = resp.json()
+        if result.get("code") == 0:
+            return result.get("data", {})
+        else:
+            raise BizLogicError(message=f"Slot refresh fail biz error: {result.get('message')}")
 
 
     def fetch_mail_content(
     def fetch_mail_content(
         self,
         self,

+ 2 - 2
vs_plg.py

@@ -1,7 +1,7 @@
 # vs_plg.py
 # vs_plg.py
 from typing import Callable
 from typing import Callable
 from abc import ABC, abstractmethod
 from abc import ABC, abstractmethod
-from vs_types import VSPlgConfig, VSQueryResult, VSBookResult
+from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult
 
 
 # ================== 接口类 ==================
 # ================== 接口类 ==================
 class IVSPlg(ABC):
 class IVSPlg(ABC):
@@ -30,7 +30,7 @@ class IVSPlg(ABC):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         """
         """
         @brief 查询可用的签证预约信息, 抛异常则查询失败
         @brief 查询可用的签证预约信息, 抛异常则查询失败
         @return VSQueryResult 查询结果
         @return VSQueryResult 查询结果

+ 36 - 6
vs_types.py

@@ -97,6 +97,16 @@ class PluginConfig(BaseModel):
         """
         """
         return self.model_dump()
         return self.model_dump()
 
 
+class AppointmentType(BaseModel):
+    weight: int = 0
+
+    routing_key: str = ''
+    country: str = ''
+    city: str = ''
+    visa_type: str = ''
+
+    def normalized(self) -> dict:
+        return self.model_dump()
 
 
 class GroupConfig(BaseModel):
 class GroupConfig(BaseModel):
     debug: bool = False
     debug: bool = False
@@ -124,7 +134,10 @@ class GroupConfig(BaseModel):
     session_max_life: int = 15
     session_max_life: int = 15
     query_wait: QueryWaitConfig = Field(default_factory=QueryWaitConfig)
     query_wait: QueryWaitConfig = Field(default_factory=QueryWaitConfig)
     plugin_config: PluginConfig = Field(default_factory=PluginConfig)
     plugin_config: PluginConfig = Field(default_factory=PluginConfig)
+    
+    appointment_types: List[AppointmentType] = Field(default_factory=list)
 
 
+    website: str = ""
     free_config: Dict[str, Any] = Field(default_factory=dict)
     free_config: Dict[str, Any] = Field(default_factory=dict)
 
 
     @classmethod
     @classmethod
@@ -181,15 +194,32 @@ class DateAvailability(BaseModel):
     times: List[TimeSlot] = Field(default_factory=list)
     times: List[TimeSlot] = Field(default_factory=list)
 
 
 class VSQueryResult(BaseModel):
 class VSQueryResult(BaseModel):
+    success: bool = False
+    apt_type: AppointmentType = Field(default_factory=AppointmentType)
+    
     availability_status: AvailabilityStatus = AvailabilityStatus.NoneAvailable
     availability_status: AvailabilityStatus = AvailabilityStatus.NoneAvailable
     earliest_date: str = ""
     earliest_date: str = ""
-    routing_key: str = ""
-    visa_type: str = ""
-    city: str = ""
-    country: str = ""
     availability: List[DateAvailability] = Field(default_factory=list)
     availability: List[DateAvailability] = Field(default_factory=list)
-    success: bool = False
-
+    
+    def to_snapshot_payload(self) -> dict:
+        """
+        直接用于 slot_snapshot/report
+        """
+        return {
+            "routing_key": self.apt_type.routing_key,
+            "country": self.apt_type.country,
+            "city": self.apt_type.city,
+            "visa_type": self.apt_type.visa_type,
+            "availability_status": self.availability_status.value,
+            "earliest_date": (
+                self.earliest_date.isoformat()
+                if self.earliest_date
+                else None
+            ),
+            "availability": [a.model_dump() for a in self.availability],
+            "snapshot_source": "worker",
+        }
+   
 class VSBookResult(BaseModel):
 class VSBookResult(BaseModel):
     success: bool = False
     success: bool = False
     session_id: str = ""
     session_id: str = ""