소스 검색

feat: update

jerry 3 달 전
부모
커밋
985ef8c247
15개의 변경된 파일493개의 추가작업 그리고 301개의 파일을 삭제
  1. 258 178
      config/groups.json
  2. 9 0
      data/account_states.json
  3. 3 0
      data/proxy_states.json
  4. 33 6
      gco.py
  5. 13 16
      plugins/bls_plugin.py
  6. 3 7
      plugins/de_plugin.py
  7. 2 7
      plugins/de_plugin2.py
  8. 2 2
      plugins/ita_plugin.py
  9. 20 23
      plugins/tls_plugin.py
  10. 16 14
      plugins/tls_plugin2.py
  11. 10 18
      plugins/vfs_plugin.py
  12. 8 21
      plugins/vfs_plugin2.py
  13. 78 1
      toolkit/vs_cloud_api.py
  14. 2 2
      vs_plg.py
  15. 36 6
      vs_types.py

+ 258 - 178
config/groups.json

@@ -22,10 +22,20 @@
         },
         "plugin_config": {
             "lib_path": "plugins",
-            "plugin_name": "vfs_plugin2",
-            "plugin_bin": "vfs_plugin2.py",
+            "plugin_name": "vfs_plugin",
+            "plugin_bin": "vfs_plugin.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.nl.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Netherlands"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/irl/en/nld/login",
         "free_config": {
             "mission_code": "nld",
             "mission_name": "Netherlands",
@@ -33,15 +43,9 @@
             "country_name": "Ireland",
             "culture_code": "en-US",
             "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",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Netherlands",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8  D08 H48R",
                     "vac_code": "NTDB",
                     "category_name": "All Short stay Categories",
@@ -49,13 +53,13 @@
                     "subcategory_name": "Tourist",
                     "subcategory_code": "To"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_SG_FR",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "sg_fr",
         "need_proxy": true,
@@ -79,6 +83,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.sin.fr.tourist",
+                "city": "Singapore",
+                "visa_type": "Tourist",
+                "country": "France"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/sgp/en/fra/login",
         "free_config": {
             "mission_code": "fra",
             "mission_name": "France",
@@ -86,15 +100,9 @@
             "country_name": "Singapore",
             "culture_code": "en-US",
             "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",
-                    "city": "Singapore",
-                    "visa_type": "Tourist",
-                    "country": "France",
                     "address": "79 Anson Road #15-01 Singapore 079906",
                     "vac_code": "FRSN",
                     "category_name": "Short Stay",
@@ -102,7 +110,7 @@
                     "subcategory_name": "Short Stay Tourist, Family Visit",
                     "subcategory_code": "Six"
                 }
-            ]
+            }
         }
     },
     {
@@ -132,6 +140,23 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.syd.fr.tourist",
+                "city": "Sydney",
+                "visa_type": "Tourist",
+                "country": "France"
+            },
+            {
+                "weight": 10,
+                "routing_key": "slot.mel.fr.tourist",
+                "city": "Melbourne",
+                "visa_type": "Tourist",
+                "country": "France"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/aus/en/fra/login",
         "free_config": {
             "mission_code": "fra",
             "mission_name": "France",
@@ -139,15 +164,9 @@
             "country_name": "Australia",
             "culture_code": "en-US",
             "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",
-                    "city": "Sydney",
-                    "visa_type": "Tourist",
-                    "country": "France",
                     "address": "France Visa Application Center,Level 6, 88 Pitt Street,Sydney NSW 2000",
                     "vac_code": "SYD",
                     "category_name": "VISA",
@@ -155,13 +174,8 @@
                     "subcategory_name": "Short Stay Schengen Visa",
                     "subcategory_code": "ShortStaySchengenVisa"
                 },
-                {
-                    "weight": 10,
-                    "routing_key": "slot.mel.fr.tourist",
+                "slot.mel.fr.tourist": {
                     "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",
                     "vac_code": "MEL",
                     "category_name": "VISA",
@@ -169,13 +183,13 @@
                     "subcategory_name": "Short Stay Schengen Visa",
                     "subcategory_code": "ShortStaySchengenVisa"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_GB_IT",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "gb_it",
         "need_proxy": true,
@@ -199,22 +213,33 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 80,
+                "routing_key": "slot.lon.it.tourist",
+                "city": "London",
+                "visa_type": "Tourist",
+                "country": "Italy"
+            },
+            {
+                "weight": 20,
+                "routing_key": "slot.man.it.tourist",
+                "city": "Manchester",
+                "visa_type": "Tourist",
+                "country": "Italy"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
         "free_config": {
             "mission_code": "ita",
             "mission_name": "Italy",
             "country_code": "gbr",
             "country_name": "United Kingdom",
             "culture_code": "en-US",
-            "website": "https://visa.vfsglobal.com/gbr/en/ita/login",
             "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",
-                    "city": "London",
-                    "visa_type": "Tourist",
-                    "country": "Italy",
                     "address": "Ground floor, 8- 20  Pocock St London SE1 0BW , United Kingdom",
                     "vac_code": "ILON",
                     "category_name": "Italy UK VisaCategory",
@@ -222,13 +247,8 @@
                     "subcategory_name": "Tourist/ Business/ EU Family",
                     "subcategory_code": "TBE"
                 },
-                {
-                    "weight": 20,
-                    "routing_key": "slot.man.it.tourist",
+                "slot.man.it.tourist": {
                     "center_name": "Italy Visa Application Centre, Manchester",
-                    "city": "Manchester",
-                    "visa_type": "Tourist",
-                    "country": "Italy",
                     "address": "50 Devonshire Street North, M12 6JH",
                     "vac_code": "IMAN",
                     "category_name": "Italy UK VisaCategory",
@@ -236,13 +256,13 @@
                     "subcategory_name": "Tourist/ Business/ EU Family",
                     "subcategory_code": "TBE"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_GB_NL",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "gb_nl",
         "need_proxy": true,
@@ -266,6 +286,23 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 90,
+                "routing_key": "slot.lon.nl.tourist",
+                "city": "London",
+                "visa_type": "Tourist",
+                "country": "Netherlands"
+            },
+            {
+                "weight": 10,
+                "routing_key": "slot.man.it.tourist",
+                "city": "Manchester",
+                "visa_type": "Tourist",
+                "country": "Netherlands"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/gbr/en/nld/login",
         "free_config": {
             "mission_code": "nld",
             "mission_name": "Netherland",
@@ -273,15 +310,9 @@
             "country_name": "United Kingdom",
             "culture_code": "en-US",
             "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",
-                    "city": "London",
-                    "visa_type": "Tourist",
-                    "country": "Netherlands",
                     "address": "66 Wilson Street, EC2A 2BT",
                     "vac_code": "NAKN",
                     "category_name": "Schengen Visa",
@@ -289,13 +320,8 @@
                     "subcategory_name": "Tourism",
                     "subcategory_code": "TA"
                 },
-                {
-                    "weight": 10,
-                    "routing_key": "slot.man.it.tourist",
+                "slot.man.it.tourist": {
                     "center_name": "Netherlands Visa application centre - Manchester",
-                    "city": "Manchester",
-                    "visa_type": "Tourist",
-                    "country": "Netherlands",
                     "address": "50 Devonshire Street North, M12 6JH",
                     "vac_code": "NAKT",
                     "category_name": "Schengen Visa",
@@ -303,13 +329,13 @@
                     "subcategory_name": "Tourism",
                     "subcategory_code": "TA"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_GB_NO",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "gb_no",
         "need_proxy": true,
@@ -333,6 +359,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.lon.no.tourist",
+                "city": "London",
+                "visa_type": "Tourist",
+                "country": "Norway"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/gbr/en/nor/login",
         "free_config": {
             "mission_code": "nor",
             "mission_name": "Norway",
@@ -340,15 +376,9 @@
             "country_name": "United Kingdom",
             "culture_code": "en-US",
             "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",
-                    "city": "London",
-                    "visa_type": "Tourist",
-                    "country": "Norway",
                     "address": "66 Wilson street, EC2A 2BT",
                     "vac_code": "NLON",
                     "category_name": "Schengen Visa C",
@@ -362,7 +392,7 @@
     {
         "identifier": "VFS_IE_AT",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "ie_at",
         "need_proxy": true,
@@ -386,6 +416,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.at.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Austria"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/irl/en/aut/login",
         "free_config": {
             "mission_code": "aut",
             "mission_name": "Austria",
@@ -393,15 +433,9 @@
             "country_name": "Ireland",
             "culture_code": "en-US",
             "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",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Austria",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "vac_code": "AUT-DUB",
                     "category_name": "Other Visas",
@@ -409,13 +443,13 @@
                     "subcategory_name": "All Visas ",
                     "subcategory_code": "TA"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_IE_DK",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "ie_dk",
         "need_proxy": true,
@@ -439,6 +473,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.dk.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Denmark"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/irl/en/dnk/login",
         "free_config": {
             "mission_code": "dnk",
             "mission_name": "Denmark",
@@ -446,15 +490,9 @@
             "country_name": "Ireland",
             "culture_code": "en-US",
             "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 ",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Denmark",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "vac_code": "DIDUB",
                     "category_name": "Schengen Visa",
@@ -462,13 +500,13 @@
                     "subcategory_name": "Tourism",
                     "subcategory_code": "TV"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_IE_FI",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "ie_fi",
         "need_proxy": true,
@@ -492,6 +530,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.fi.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Finland"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/irl/en/fin/login",
         "free_config": {
             "mission_code": "fin",
             "mission_name": "Finland",
@@ -499,15 +547,9 @@
             "country_name": "Ireland",
             "culture_code": "en-US",
             "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",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Finland",
                     "address": "Cunningham House, 130 Francis Street, Dublin 8 D08 H48R",
                     "vac_code": "Dubb",
                     "category_name": "VISA",
@@ -515,13 +557,13 @@
                     "subcategory_name": "Tourist Category",
                     "subcategory_code": "Tourist Category"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_IE_HU",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "ie_hu",
         "need_proxy": true,
@@ -545,6 +587,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.hu.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Hungary"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/irl/en/hun/login",
         "free_config": {
             "mission_code": "hun",
             "mission_name": "Hungary",
@@ -552,15 +604,9 @@
             "country_name": "Ireland",
             "culture_code": "en-US",
             "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",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Hungary",
                     "address": "Cunningham House, 130 Francis Street Dublin",
                     "vac_code": "DUB",
                     "category_name": "Short Stay",
@@ -568,13 +614,13 @@
                     "subcategory_name": "Schengen Visa",
                     "subcategory_code": "Schengen Visa"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "VFS_IE_IS",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "ie_is",
         "need_proxy": true,
@@ -598,6 +644,16 @@
             "plugin_bin": "vfs_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.is.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Iceland"
+            }
+        ],
+        "website": "https://visa.vfsglobal.com/irl/en/isl/login",
         "free_config": {
             "mission_code": "isl",
             "mission_name": "Iceland",
@@ -605,15 +661,9 @@
             "country_name": "Ireland",
             "culture_code": "en-US",
             "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",
-                    "city": "Dublin",
-                    "visa_type": "Tourist",
-                    "country": "Iceland",
                     "address": "Cunningham House, 130 Francis Street, Dublin, Ireland- DO8 H48R",
                     "vac_code": "DUB",
                     "category_name": "C-Visa",
@@ -621,13 +671,13 @@
                     "subcategory_name": "Tourism",
                     "subcategory_code": "OTT"
                 }
-            ]
+            }
         }
     },
     {
         "identifier": "BLS_IE_ES",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "ie_es",
         "need_proxy": true,
@@ -651,23 +701,30 @@
             "plugin_bin": "bls_plugin.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.es.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Spain"
+            }
+        ],
+        "website": "https://ireland.blsspainglobal.com/Global/bls/visatypeverification",
         "free_config": {
             "domain": "ireland.blsspainglobal.com",
             "ocr_model": "data/ctc.pth",
-            "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_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.lon.es.tourist",
+                "city": "London",
+                "visa_type": "Tourist",
+                "country": "Spain"
+            }
+        ],
+        "website": "https://uk.blsspainglobal.com/Global/bls/visatypeverification",
         "free_config": {
             "domain": "uk.blsspainglobal.com",
-            "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",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "gb_fr",
         "need_proxy": true,
@@ -737,36 +801,42 @@
             "random_min": 60,
             "random_max": 300
         },
+
         "plugin_config": {
             "lib_path": "plugins",
             "plugin_name": "tls_plugin2",
             "plugin_bin": "tls_plugin2.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.lon.fr.tourist",
+                "city": "London",
+                "visa_type": "Tourist",
+                "country": "France"
+            }
+        ],
+        "website": "https://visas-fr.tlscontact.com/country/gb/vac/gbLON2fr/",
         "free_config": {
-            "center": {
+            "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
+            "apt_config": {
                 "code": "gbLON2fr",
                 "country": "gb",
                 "mission": "fr",
                 "city": "London"
             },
-            "city": "London",
-            "country": "France",
-            "visa_type": "Tourist",
-            "routing_key": "slot.lon.fr.tourist",
-            "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
             "interest_month": "02-2026",
             "target_labels": [
                 "",
                 "pta"
-            ],
-            "website": "https://visas-fr.tlscontact.com/country/gb/vac/gbLON2fr/"
+            ]
         }
     },
     {
         "identifier": "VISAMETRIC_IE_DE",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": false,
         "local_account_pool": "",
         "need_proxy": true,
@@ -790,14 +860,19 @@
             "plugin_bin": "de_plugin.py",
             "plugin_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.de.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Germany"
+            }
+        ],
+        "website": "https://ie-appointment.visametric.com/en",
         "free_config": {
             "base_url": "https://ie-appointment.visametric.com",
-            "consularid": 1,
-            "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_proto": "IVSPlg"
         },
+        "appointment_types": [
+            {
+                "weight": 10,
+                "routing_key": "slot.dub.it.tourist",
+                "city": "Dublin",
+                "visa_type": "Tourist",
+                "country": "Italy"
+            }
+        ],
+        "website": "https://prenotami.esteri.it/Home",
         "free_config": {
-            "capsolver_key": "03db1d1ff2f4a33e84ef1da99bd83336bed3710153525",
-            "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 random
 import threading
+from datetime import datetime, timezone
 from typing import List, Dict, Tuple, Any, Optional, Callable
 from concurrent.futures import wait
 
 # 导入所有依赖
 from vs_types import GroupConfig, QueryWaitMode, VSPlgConfig, VSQueryResult, Task 
-from vs_plg import IVSPlg 
 from vs_plg_factory import VSPlgFactory 
 from toolkit.account_manager import AccountManager 
 from toolkit.proxy_manager import ProxyManager 
@@ -135,15 +135,36 @@ class GCO:
                     continue
                 
                 # === 执行查询 ===
+                apt_type = None
                 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:
                         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. 准备并发任务
                         # 我们使用刚刚快照的 tasks_to_process,或者重新获取一次全量列表
                         # 重点:所有实例 (w.instance) 都使用同一份查询结果 (result) 去抢
@@ -176,6 +197,11 @@ class GCO:
 
                 except Exception as 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) ===
                 if not batch_booking_triggered:
@@ -495,7 +521,8 @@ class GCO:
 
         # 如果没有绑定本地任务,尝试从云端 Pop
         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)
             
             if not task_data:

+ 13 - 16
plugins/bls_plugin.py

@@ -18,13 +18,12 @@ from bs4 import BeautifulSoup
 # DrissionPage 核心
 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 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.ocr_engine import PyTorchEngine
 
@@ -198,8 +197,11 @@ class BlsPlugin(IVSPlg):
     # =========================================================================
     # 2. 查询流程 (Query)
     # =========================================================================
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> 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")
 
         # 2.1 签证类型验证
@@ -231,12 +233,8 @@ class BlsPlugin(IVSPlg):
         self._check_resp_is_session_expired_or_invalid('APPLICATION PROCESS', resp)
         
         # 这里需要极其复杂的 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)
         if not vt_res.json()['success']:
             if not vt_res.json()['available']:
@@ -588,7 +586,7 @@ class BlsPlugin(IVSPlg):
             if match: return match.group(1)
         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)
         """
@@ -608,12 +606,11 @@ class BlsPlugin(IVSPlg):
             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
         loc_value = None

+ 3 - 7
plugins/de_plugin.py

@@ -13,7 +13,7 @@ from bs4 import BeautifulSoup
 
 
 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.ocr_engine import DddOcrEngine
 
@@ -139,12 +139,12 @@ class DePlugin(IVSPlg):
         self.session_create_time = time.time()
         self._log("Session created successfully.")
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         """
         查询可用日期 (/getdate)
         """
         res = VSQueryResult()
-
+        res.apt_type = apt_type
         # 构造 Payload (参考 get_slot_day) 这里的 ID 需要根据实际情况配置
         consular_id = self.free_config.get("consularid", "1") # 1=Ireland?
         max_retries = self.free_config.get("slot_query_max_retries", 2)
@@ -182,10 +182,6 @@ class DePlugin(IVSPlg):
         j = resp.json()
         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:
             res.success = True
             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 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 utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from toolkit.proxy_tunnel import ProxyTunnel
@@ -249,7 +249,7 @@ class DePlugin2(IVSPlg):
         m = re.search(r'name="csrf-token" content="([^"]+)"', html)
         if m: self.csrf_token = m.group(1)
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         res.success = False
         
@@ -277,11 +277,6 @@ class DePlugin2(IVSPlg):
         j = resp.json()
         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:
             res.success = True
             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 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.vs_cloud_api import VSCloudApi
 
@@ -240,7 +240,7 @@ class ItaPlugin(IVSPlg):
     # -------------------------------------------------------------
     # 2. Query Availability
     # -------------------------------------------------------------
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         res.success = False
         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 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
 
 class TlsPlugin(IVSPlg):
@@ -75,9 +75,9 @@ class TlsPlugin(IVSPlg):
             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 盾
         self._solve_cloudflare5S_challenge()
@@ -85,15 +85,15 @@ class TlsPlugin(IVSPlg):
         # 3. 获取登录页面参数 (OIDC)
         login_page = "https://visas-fr.tlscontact.com/en-us/login"
         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 = {
             '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',
-            '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,
         }
         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)
             
         # 选择匹配城市的 Group
-        target_city = embassy['city'].lower()
+        target_city = apt_config['city'].lower()
         for g in groups:
             if g['location'].lower() == target_city:
                 self.travel_group = g
@@ -149,23 +149,24 @@ class TlsPlugin(IVSPlg):
         self.session_create_time = time.time()
         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.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']
         interest_month = self.free_config.get("interest_month", time.strftime("%m-%Y"))
         max_retries = self.free_config.get("max_retries", 2)
         
         url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking'
         params = {
-            'location': embassy["code"],
+            'location': apt_config["code"],
             'month': interest_month,
         }
         headers = {
             'accept': '*/*',
             '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,
         }
 
@@ -194,10 +195,6 @@ class TlsPlugin(IVSPlg):
         target_labels = self.free_config.get("target_labels", ["", "pta"])
         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:
             res.success = True
             res.availability_status = AvailabilityStatus.Available
@@ -231,7 +228,7 @@ class TlsPlugin(IVSPlg):
         res.success = False
         
         # 1. 基础信息提取
-        embassy = self.free_config.get('center', {})
+        apt_config = self.free_config.get('apt_config', {})
         group_num = self.travel_group['group_number']
         
         available_dates = [da.date for da in slot_info.availability]
@@ -265,7 +262,7 @@ class TlsPlugin(IVSPlg):
 
         # 2. 解决 ReCaptcha V3
         # 动作必须是 "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", "")
         rc_params = {
@@ -286,7 +283,7 @@ class TlsPlugin(IVSPlg):
             '1_formGroupId': str(group_num),      # 修正:加了 form 前缀
             '1_lang': 'en-us',
             '1_process': 'APPOINTMENT',
-            '1_location': embassy["code"],        # 例如 gbLON2fr
+            '1_location': apt_config["code"],        # 例如 gbLON2fr
             '1_date': selected_date,
             '1_time': selected_time,
             '1_appointmentLabel': selected_label,   # 修正:单数 Label,值为字符串 "pta" 或 "regular"
@@ -390,8 +387,8 @@ class TlsPlugin(IVSPlg):
         解决 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 (根据你的脚本示例)
         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 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 toolkit.proxy_tunnel import ProxyTunnel
-from toolkit.vs_cloud_api import VSCloudApi
 
 
 class BrowserResponse:
@@ -177,13 +176,16 @@ class TlsPlugin2(IVSPlg):
         try:
             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"
             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)}"
             
@@ -253,7 +255,7 @@ class TlsPlugin2(IVSPlg):
             self._check_page_is_session_expired_or_invalid("My travel group", html)
             groups = self._parse_travel_groups(html)
             
-            target_city = embassy['city'].lower()
+            target_city = apt_config['city'].lower()
             for g in groups:
                 if g['location'].lower() == target_city:
                     self.travel_group = g
@@ -270,17 +272,17 @@ class TlsPlugin2(IVSPlg):
             self.cleanup()
             raise e
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         res = VSQueryResult()
         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']
         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'
         params = {
-            'location': embassy["code"],
+            'location': apt_config["code"],
             'month': interest_month,
         }
         
@@ -325,7 +327,7 @@ class TlsPlugin2(IVSPlg):
         res = VSBookResult()
         res.success = False
         
-        embassy = self.free_config.get('center', {})
+        apt_config = self.free_config.get('apt_config', {})
         group_num = self.travel_group['group_number']
         
         available_dates = [da.date for da in slot_info.availability]
@@ -360,7 +362,7 @@ class TlsPlugin2(IVSPlg):
              raise NotFoundError(message="No suitable slot found")
 
         # 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", "")
         rc_params = {
@@ -390,7 +392,7 @@ class TlsPlugin2(IVSPlg):
         formData.append('1_formGroupId', '{group_num}');
         formData.append('1_lang', 'en-us');
         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_time', '{selected_time.time}');
         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 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 
 
 # ----------------- 静态常量与辅助数据 -----------------
@@ -204,21 +204,15 @@ class VfsPlugin(IVSPlg):
         self.session_create_time = time.time()
         self._log("Session created successfully.")
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         """查询可预约 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)
         earliest_date = self._query_earliest_slot(apt_config)
         result.success = False
         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:
             result.success = True
 
@@ -244,16 +238,11 @@ class VfsPlugin(IVSPlg):
         user_inputs['alias_email'] = get_alias_email(user_email, new_domain="gmail-app.com")
         
         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")
         
-        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:
             raise NotFoundError(message="Book: Config missing.")
@@ -316,6 +305,7 @@ class VfsPlugin(IVSPlg):
         if is_waitlist:
             if self._confirm_waitlist(apt_config, final_urn):
                 res.success = True
+                res.account = self.config.account.username
                 res.urn = final_urn
                 return res
             raise BizLogicError(message='confirm waitlist failed')
@@ -427,6 +417,8 @@ class VfsPlugin(IVSPlg):
     def _log(self, message):
         if self.logger:
             self.logger(f'[VfsPlugin] [{self.group_id}] {message}')
+        else:
+            print(f'[VfsPlugin] [{self.group_id}] {message}')
     
     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 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.proxy_tunnel import ProxyTunnel
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
@@ -394,15 +394,10 @@ class VfsPlugin2(IVSPlg):
             self.cleanup()
             raise e
 
-    def query(self) -> VSQueryResult:
+    def query(self, apt_type: AppointmentType) -> VSQueryResult:
         """查询可预约 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:
             self._fetch_configurations(apt_config)
@@ -410,10 +405,7 @@ class VfsPlugin2(IVSPlg):
             
             result.success = False
             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:
                 result.success = True
                 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")
         
         res = VSBookResult()
-        slot_routing_key = slot_info.routing_key
+        app_type = slot_info.apt_type
         # 如果没有 earliest_date,默认从今天开始
         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:
             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):
                 res.success = True
                 res.urn = final_urn
+                res.account = self.config.account.username
                 self._log("Waitlist confirmed.")
                 return res
             raise BizLogicError(message='Confirm waitlist failed')

+ 78 - 1
toolkit/vs_cloud_api.py

@@ -3,6 +3,7 @@ import requests
 import json
 import time
 import urllib.parse
+from datetime import datetime
 from typing import Dict, Any, Optional
 from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from vs_log_macros import VSC_ERROR, VSC_INFO, VSC_WARN, VSC_DEBUG
@@ -40,7 +41,7 @@ class VSCloudApi:
         2. 发送实际请求
         """
         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:
             return resp
         elif resp.status_code == 401:
@@ -216,6 +217,82 @@ class VSCloudApi:
             return result.get("data", {})
         else:
             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(
         self,

+ 2 - 2
vs_plg.py

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

+ 36 - 6
vs_types.py

@@ -97,6 +97,16 @@ class PluginConfig(BaseModel):
         """
         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):
     debug: bool = False
@@ -124,7 +134,10 @@ class GroupConfig(BaseModel):
     session_max_life: int = 15
     query_wait: QueryWaitConfig = Field(default_factory=QueryWaitConfig)
     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)
 
     @classmethod
@@ -181,15 +194,32 @@ class DateAvailability(BaseModel):
     times: List[TimeSlot] = Field(default_factory=list)
 
 class VSQueryResult(BaseModel):
+    success: bool = False
+    apt_type: AppointmentType = Field(default_factory=AppointmentType)
+    
     availability_status: AvailabilityStatus = AvailabilityStatus.NoneAvailable
     earliest_date: str = ""
-    routing_key: str = ""
-    visa_type: str = ""
-    city: str = ""
-    country: str = ""
     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):
     success: bool = False
     session_id: str = ""