refractor

This commit is contained in:
2026-04-14 09:53:52 -04:00
parent 02d9e06077
commit e667af9201
13 changed files with 947 additions and 1629 deletions

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.system.id="java-source" type="PYTHON_MODULE" version="4">
<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />

View File

@@ -1,25 +1,25 @@
#LWP-Cookies-2.0
Set-Cookie3: dslang=US-EN; path="/"; domain=.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
Set-Cookie3: site=USA; path="/"; domain=.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
Set-Cookie3: acn01=VXsJ1FolypnjIirlSDusIR9ovu+CG7pXMkEM9wALeBAzs3+u; path="/"; domain=.apple.com; path_spec; secure; expires="2027-04-05 02:36:14Z"; HttpOnly=None; version=0
Set-Cookie3: aasp=9968286574C1B31BE2158A19285C92934F9213370AF67665FD3DC55BCB20C2B39E8728B5D8B4952B5F8B8B09F4D1C6826058791E379DE064B11DD79D0F7F1F87A27809C8C1CF291EEC649A8495D06747AA26022E3C56113F2E43528B8F75ECA9A7FA5EADDBFA510B80AF6C0198C74B431BA8BA5E76EA6411; path="/"; domain=.idmsa.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
Set-Cookie3: acn01="NNc+TvoQfp2pQiuHIAAqUBtXR1S4XhqZJWcHCAARBs+41xk/"; path="/"; domain=.apple.com; path_spec; secure; expires="2027-04-14 09:01:23Z"; HttpOnly=None; version=0
Set-Cookie3: aasp=DE9DF160D458CB33DB61E598CA924CC24441BCA91527DC0B793F5576DC6E8DF8BDBF5D9424A9243220F7D62057DD673EE28B6F754666DDF1949F07205B0E0BC0066D0CEDEA32E34965B3EA5AC66D2F90BEAC5E289B72B338B05E5D57B8DDD402BBC24F218664008D4D70C62F90DF6411A599DFC6DE59BA38; path="/"; domain=.idmsa.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
Set-Cookie3: DES580750186337023c50d1415a6e6ca44a2="HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX"; path="/"; domain=.idmsa.apple.com; path_spec; secure; expires="2026-04-21 03:09:12Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-UNIQUE-CLIENT-ID="\"BA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; version=0
Set-Cookie3: X-APPLE-WEBAUTH-LOGIN="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGnRzaIRDmdzLmljbG91ZC5hdXRovQCklMXNjS27UT7gDS-gYQ89116WefHaUfyFRHCo9vj0gIvottHkbhRRXrty62DAgu_MJaMZUjxNBFe0CwGCcMtgC4Lg6NaNaHBlBlsy-OAjkFv1sJqr_nax-L7L3P6u9vXYy9jjrvy5Bl47wGJmZgnMbee6RQ~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-VALIDATE="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGnRzaIRDmdzLmljbG91ZC5hdXRovQCklMXNjS27UT7gDS-gYQ89116WefHaUfyFRHCo9vj0gIvottHkbhRRXrty62DAgu_MJaMZUjxNBFe0CwGCcMtgC4Lg6NaNaHBlBlsy-OAjkCBWl1oaHvC06H_6zbXfJv5b7ws2aI9JUCBGIrlYvdtyArGvxA~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; version=0
Set-Cookie3: X-APPLE-WEBAUTH-USER="\"v=1:s=1:d=157320350\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-LOGIN="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGneOZkRDmdzLmljbG91ZC5hdXRovQCaguVTZqS1WtbK-2fXiMlSEneJRuCgMGur1kfGcotuhS8Kvhra-S8EJ_LDYfFgbF8KYm2Dwug7ev54fe7_F-ui_zK0AqM9w7i8HvwWtWOA6QeYJtzFKgl7BVt_c5I4FrExr468XbgzmKtoXQxDTJu_wOLbbA~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-VALIDATE="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGneOZkRDmdzLmljbG91ZC5hdXRovQCaguVTZqS1WtbK-2fXiMlSEneJRuCgMGur1kfGcotuhS8Kvhra-S8EJ_LDYfFgbF8KYm2Dwug7ev54fe7_F-ui_zK0AqM9w7i8HvwWtWOA6amlkIysG_APwQqmrfcDi-L_p8u1i1A8tqFrDUUfJs21Y1v8oA~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; version=0
Set-Cookie3: X-APPLE-WEBAUTH-USER="\"v=1:s=1:d=157320350\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X_APPLE_WEB_KB-FHMLYL_TPMN_3A8D3KIPPI0C_EC="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGm_OLQRDmdzLmljbG91ZC5hdXRovQD38nYoxQenHW9WggeFKkoDa8I8zeKoOshv6I4dsZQalR2itry1r6kUZe9d_BZan1W-oKlImTrYi_-Vt5Q4YEJWJITWeqN8QChxvbTXB0o8sQ-wAIzBL1J5sQIRBqMadrtP5U0wslkRg0u0AguK20CM4TGoGg~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-21 00:32:52Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-DS-WEB-SESSION-TOKEN="\"AQGj4lLXV42fukxli9in3ovKoBpXDePY1U3TMHAECRIIVd+zcdKkZDdg0hx6yPg+bVVZDVKN5iqJGKZsbIGATe6G/ce1fuxw+aON29I3TuOIuWrLNQyy1Idjn6SzX8RWY5MlFmoM+oVFt4MqdtvYJgGSfGv1IAe8CfTWPyQhqlk4qGgczVCCx74ReWAz7mh0GQk3aFwre6RK3QpJV1sgFGLh5uu0Jw2ThzgGfsYlORVDtz/90OjWuGMO6fr8BeXAu5Oh3mgryYTM39Et/Pc/8GuW9beXXI1WLul1eyQ/rK3xd8iZcEwYSFeBYfjNRYFAS3raJkIC0DJkKd93baYUNhEGJHXNtb6uzrueFmQT+svVA8VXY79IG9noP0S+0Ie3oDs0rZVwz5dHZhIzCEepxFz+WtAuIrkUXOFk71wkytU+oAdA90qfPP/DzeXbKGFbHgiBkoxSxsKsGpikC3U0ne6bPWcJZIsLh1vlcpNutXJGkAqm4VXYBGWbaWqr2UJyDBxJQClRngaKR/VB9U+BFvK5NylHsU0gzv7U8goPnQBr62ZAEmD3TDTCxuqIbbgaRxYRJD4smOVemXktlhwV8FvcH1r/UonCGal9YedLDLzL9bxMPg9vZVKnaAMDKwsJstLDFzY4EB2IS1Ga/XJSfO85MFPe6SeNOU2v2how1RyjoTeKhvACva16kcmT/RpUFR9W8qruLadc8sGGGXZ/LrpwG76hZBBrHef1jrsZw2lagAhtT7p69Sr7V9WQgZKoRq8ixSllm5VX1TJqoHkizS6lOBCOeasVT8g=\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-HSA-TRUST="\"28a33818a1dce9a0eecde38e7c8fcc6f080b70bc9feb505599fb2855903a4792_HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-07-04 02:49:06Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Events="\"S2V5QXBwbDoBAAAA8QQzAADzWe4VyeK8B8xw8IwuUAj3/m/Tnx3zxoa+V62xZNyypQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Documents="\"S2V5QXBwbDoBAAAAAgQzAABmoTT+n0/L3H+DLCJo23ecPgEOXkHSr8swBUva2awlRw==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Photos="\"S2V5QXBwbDoBAAAAAwQzAACZIqX0p5ijwnU4kYUFAd6s41Ki8Ll00cb5+m/HALArxA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Cloudkit="\"S2V5QXBwbDoBAAAABAQzAABlviOyJnRiHxxWxB8ItTVk+oDDvc7OUX1eJkqX0bYdXA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Safari="\"S2V5QXBwbDoBAAAAFgQzAACET30lpsAp5WwxJGxde2g5ajvsMowlnYgU2tfOoebjgA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Mail="\"S2V5QXBwbDoBAAAABwQzAACaoZ2L80C9Pl1HRn65m+rka24bPiCumxBL/I07rSfkGQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Notes="\"S2V5QXBwbDoBAAAACQQzAAA5vanrsd/XLqXzVPYQGtt68uOMqCgEcW8J8pisVkU8xw==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-News="\"S2V5QXBwbDoBAAAACwQzAADVWJymh0KvnbnDDHZBntjVHLnfcTr/Q0ES5n8RjkStNg==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Sharing="\"S2V5QXBwbDoBAAAADAQzAADpgr9x39VeWGIvzow7K8TzwvGLETRpkbcQH4Q8gKed2w==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-05 02:49:09Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-TOKEN="\"v=2:t=BA==BST_IAAAAAAABLwIAAAAAGnRztkRDmdzLmljbG91ZC5hdXRovQDHdf4C78ZKILT8BBTIOy2E0pByyyFr6yZauanzkyx8PQqrJBuXKi3q6Ms5vECA80g_yAB1WH-0ep0fExYB60bSXj-tV6WHYlFPew3HCW9LTJGs4uea0vlYHya2rKBlXbmkU3-WnL6eHN_U6MuYKO1zefaf8A~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-19 02:54:17Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-FMIP="\"BA==BST_IAAAAAAABLwIAAAAAGnRztkRDmdzLmljbG91ZC5hdXRovQDHdf4C78ZKILT8BBTIOy2E0pByyyFr6yZauanzkyx8PQqrJBuXKi3q6Ms5vECA80g_yAB1WH-0ep0fExYB60bSXj-tV6WHYlFPew3HCW9LTFFwE029MTNgDRSz7RG3UgEKnrs8iiy5GqIVN5vkstidJ8ogxA~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; HttpOnly=None; version=0
Set-Cookie3: xr_3n2093n1a="f16+Jo6W6qPJmRkZzBixaQfuGOyKO+kJ4iSEZyG94A=="; path="/"; domain=p144-fmipweb.icloud.com; path_spec; secure; discard; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-DS-WEB-SESSION-TOKEN="\"AQFfzSjK/WZszw0Fsqj3EgUNWNY4t+SKa9A0tAZvx+6URTf4eVhwhvkiDp7K92hEHTDNkvBVpeLWCOciQyzVLPcDhnss5IUImi6rDXxU6diem5bTWiznw50LWVBDnC9GnFB1ER6z1jqvw6u/2pDoO7slxsylU2j5fDp6mPvMe2iz83UhRRDLqUV5RjHOZ3Y93rnGSskislOzvvd/wuWBL9aXdC22oeGCs8lALzT2LDTJE/fJfw4H5ttKDoh5sA5bPgO3UjBtvAOMurTwdlYHmyaZ5pPrRDKSl9sgcbzkxwwvbvCVUbbsLt7rUolhsJmY33Pu51A7ubiG6hzIEg8U/mAQH7A9Ill4D/HbpR0Q4/uW3Sw9BkdI2MopLsLLJudBW66IBfYSGY53Dzr33IXjyi8bn+f8drCoxv+Jqsut5Yn/MI8Ooc9vpkON2INj+MDPCbr9LP2dh2hjOjvyHWJvDIYuIa/1ols9fx7GAT37dp8iuJYq7UYUT4GVaayt5xRH/a+hE8IpM+6gMAVgXMYBYi40eAmvcCG1+Pd4NKTQGRio+Z53bfcpMq4IjV5ZpwmcfWHMsCs6UNuFzDUhz3Oh0O9zK1hubuxgP4NvpJWZTGeDxm3Ak4nphbELvoFeRzqzX+wHLIKfr8H/ruK8LCrLeJvt4QAExQH9N6pjBDot0M/WpG0aZ0ZbOubqhfmX5KftG2QKJwDfLouyLeWp07TvyAsWXkS+o5Bc2ararFO8R5+OHURIIx9O47UUHkA0a7C1iYhdbxf8MMhOjYt6J8buormR4LDZEp2q5Xk=\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-HSA-TRUST="\"28a33818a1dce9a0eecde38e7c8fcc6f080b70bc9feb505599fb2855903a4792_HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-07-13 12:56:57Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Events="\"S2V5QXBwbDoBAAAA8QRQAABCqtxc6DiNgfM7sfluuTm75zY6t8HcSbX5byVn83L6Yg==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Documents="\"S2V5QXBwbDoBAAAAAgRQAAC9HDQLy/UwkOt+Z+OPhwCJxnyB1byDPGL1UYWgHtJEgg==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Photos="\"S2V5QXBwbDoBAAAAAwRQAACxKufqce7i/IW850g8JkJ73J0Wyxpjpfh5nAk7eioyvw==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Cloudkit="\"S2V5QXBwbDoBAAAABARQAABnmo+VJgPhrzid+Xl/01UA02nJiwkk9q0MBvy6QH758g==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Safari="\"S2V5QXBwbDoBAAAAFgRQAAC9tTzH5+GZyvSdFF1r+Zmvj/kJmQNWTYNLCmNG8tX6Cw==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Mail="\"S2V5QXBwbDoBAAAABwRQAABywaR34zLT3fm0XcBUYCbETV16EkpVL6/O4hB9EHwDcQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Notes="\"S2V5QXBwbDoBAAAACQRQAADqrnrINYzUlz7QoxZO+ami+5QY9Qtys3wAuCPjM0x7wQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-News="\"S2V5QXBwbDoBAAAACwRQAAAYQEni07N+B3hrogj/ZlPdWE3ibkkBCYJcDgadmwRzOw==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Sharing="\"S2V5QXBwbDoBAAAADARQAABGcfw9nr8NjONRvLxWcwgMoaBXj7xode3Sf7ajDnV2IQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-14 12:57:00Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-TOKEN="\"v=2:t=BA==BST_IAAAAAAABLwIAAAAAGneP2oRDmdzLmljbG91ZC5hdXRovQCgEswrYhaJx11ybTgoBMzcGPh9PUsXyVTJCpHvMijnTIkcF0JOFbgPAg8KKzEH9Rd9ebBJmHwfJP5LUVHG1YaUgB5xL9LX2AQYoGwnJcaj2mCD7LQxw4WCZSVJf9TauwpgZlhHpR15qHwnpiIC3Qg8Rue57g~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-28 13:21:46Z"; HttpOnly=None; version=0
Set-Cookie3: X-APPLE-WEBAUTH-FMIP="\"BA==BST_IAAAAAAABLwIAAAAAGneP2oRDmdzLmljbG91ZC5hdXRovQCgEswrYhaJx11ybTgoBMzcGPh9PUsXyVTJCpHvMijnTIkcF0JOFbgPAg8KKzEH9Rd9ebBJmHwfJP5LUVHG1YaUgB5xL9LX2AQYoGwnJcaj2h-pjNdL9bRPA229-vtr8LU4Syp2u2FO2pDcJ6aasJg2FHdtQg~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; HttpOnly=None; version=0
Set-Cookie3: xr_3n2093n1a="B50g9gcKmoM+KF+j8hPX6wKXv22QcYsBKEONZTiNR4g="; path="/"; domain=p144-fmipweb.icloud.com; path_spec; secure; discard; HttpOnly=None; version=0

View File

@@ -1 +1 @@
{"client_id": "a803c3ce-2586-11f1-a724-8f6777a1d2b5", "session_id": "9968286574C1B31BE2158A19285C92934F9213370AF67665FD3DC55BCB20C2B39E8728B5D8B4952B5F8B8B09F4D1C6826058791E379DE064B11DD79D0F7F1F87A27809C8C1CF291EEC649A8495D06747AA26022E3C56113F2E43528B8F75ECA9A7FA5EADDBFA510B80AF6C0198C74B431BA8BA5E76EA6411", "auth_attributes": "Xmk4qpyEj50xx+3lPjCdsfTj8wLQuUJ9ZlW4t+MjKa0jiHZ9YmQQJfUGKqW0Jl3jpyX0m4eYPaS7oSPeWR3jUx9jjrdLW3fuUjZrksYarxCZGX8/V1ArrgSzl8lsbD9MSNDxtBDEyJZSlnp4syp2/Tqs2jM/dE3641Pdyh4ScxQi9S1c4HtHnn0ryjT3XcH034UwQnGtnLnJS8FXtWAjFqPUv0I9C06BVPLkI7jw+I/efCr0sMYUsPgclZbSVuPU7+ioZPJeeV5xJm/dGQpoAAt4ED/Fik8=", "scnt": "AAAA-jk5NjgyODY1NzRDMUIzMUJFMjE1OEExOTI4NUM5MjkzNEY5MjEzMzcwQUY2NzY2NUZEM0RDNTVCQ0IyMEMyQjM5RTg3MjhCNUQ4QjQ5NTJCNUY4QjhCMDlGNEQxQzY4MjYwNTg3OTFFMzc5REUwNjRCMTFERDc5RDBGN0YxRjg3QTI3ODA5QzhDMUNGMjkxRUVDNjQ5QTg0OTVEMDY3NDdBQTI2MDIyRTNDNTYxMTNGMkU0MzUyOEI4Rjc1RUNBOUE3RkE1RUFEREJGQTUxMEI4MEFGNkMwMTk4Qzc0QjQzMUJBOEJBNUU3NkVBNjQxMXwyAAABnVuNNE9ZApXl4PMBXcfJlCl96p_1cCfBRhHOdT0BkG04tbOjDgF3V8vlM9CkAAt4ECMQx8n6Pfm37fS8OCpOsUoTL7pc1A-DXplPGubA4CDTzbZfkw", "account_country": "USA", "session_token": "YaPd8Iuy2R5+ZsjKRO52duQIvtCutVY0Mf8UTjNowXeyq6E8O+VBbYFMEh1oq/bQIlTmu0zYRc/79K6Qt8jTYpuQArQNhVDqCkUNpEgc0wyKA3L15xFP11t7/nDDlzL1U0hU2JEpiXcNpr8oXF0oJxDf+p9zRA+ryHL/jybhiiaVX48zUB6cYc8m++GA+Oid2r45sftOPRUkYpgIF2UwIq0pF9xat810EDnaDTVXYe2geQ2Vt9BNJiAJLWNA3WPfifMpAZzN5b2vHg8Nsh3e/Nm/+GQvjrtcBr5vT3RwlslOfWIqJbtoLL/np16f+szHM6e5ZOxejMdOifG5PxfPshr+qMzu/HRX7Ex8r6975KaiW98MBrvbVITsgsL/YmutmdCAFfATXYxu2JOfnOHhVPDANNRLh8N1ZTSJtGY3fXP3DxbNlCjmCA3JAMWMiUkhlxDcA4xPubDiiU5ZNJ6x1ckkAdjtQkOvjESt9N3ydMf89sz9WDqdps0ll1sYXINjLM1OagoBSxzE0y0szkX34YkCki0j3f25OH+utNCNIDZV9fWTEYiINOY1POSX07ZPbzw2AKfVqJP3DduVL4rzjXTIcGtTBkcr04c0Lz43knqcPkVQI1QNckxiFL1owcZEdlxoPKIOL8ojXRK7b6NAJyk+t5vLpDWrt97bHegGaJoe7swSYP75rSxkVdh8vAczd/Mir4OmB/uerwtEZq9KUiKWvC52mYYolF/XYazJKAVGx1FpywM8DL5mHlbcJzTUR2NBWRD/b0/m73Jxs32kLButuWtnKXiHk/OqC1JZfA8+LNdNaK6KtJcdEul3go7LCwuxQZ4o8o2QzN8SqgM9UUvcyMf/3LwDoZgRdPmKNRN6GV5a3J9I+WF+Jz98W0Klbn5PJa6y0Zc9aaUy1H6Ho6pdzYeLdMMhwJ5RaJqvgnHxDFYU2UQKiFt3w26gFANk1AALeBBE/iN7", "trust_eligible": "true", "grant_code": "c3254a0d3cdf4487fa423d13cca72142f.0.przwz.C6giZgZ9fLof99ebfJFxDA", "trust_token": "HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX"}
{"client_id": "a803c3ce-2586-11f1-a724-8f6777a1d2b5", "session_id": "DE9DF160D458CB33DB61E598CA924CC24441BCA91527DC0B793F5576DC6E8DF8BDBF5D9424A9243220F7D62057DD673EE28B6F754666DDF1949F07205B0E0BC0066D0CEDEA32E34965B3EA5AC66D2F90BEAC5E289B72B338B05E5D57B8DDD402BBC24F218664008D4D70C62F90DF6411A599DFC6DE59BA38", "auth_attributes": "zahj2u9cli9LAxaDkM/LZ+cVDLo7WY7x9DXKo/GGGAb+VYFvMJT/jxRGAHy2AaUN3MbqtRLU0i7jA2oeXhQ1iMaUEntaTv9AWTsTfylbAnoJ1Vo+euKP22hW6V/IbTfClCFMHGxZul3ooBdwJwz+otrAo3VPF3yUptZPvP5+7JOegMRDA1awse9Ci0QtcOfI8p6tyj5aV1xjcpLbt29wdmV+Ny/Lm25iFpDKnf/XIgvT1MUXIpGot1NfW3rqWViq/M/pq8DK0i8/RuLDevFYABEGz8Qt3PE=", "scnt": "AAAA-kRFOURGMTYwRDQ1OENCMzNEQjYxRTU5OENBOTI0Q0MyNDQ0MUJDQTkxNTI3REMwQjc5M0Y1NTc2REM2RThERjhCREJGNUQ5NDI0QTkyNDMyMjBGN0Q2MjA1N0RENjczRUUyOEI2Rjc1NDY2NkRERjE5NDlGMDcyMDVCMEUwQkMwMDY2RDBDRURFQTMyRTM0OTY1QjNFQTVBQzY2RDJGOTBCRUFDNUUyODlCNzJCMzM4QjA1RTVENTdCOERERDQwMkJCQzI0RjIxODY2NDAwOEQ0RDcwQzYyRjkwREY2NDExQTU5OURGQzZERTU5QkEzOHwyAAABnYtHDOAIdGLN7q_QgI9AB0GH5OFan9jwoX6JhEYkfqH0K-ydWfBkwqF6Le3-ABEGz6vfU7IlwYBHKE9RVBG8MeYu5XjWFmHIzPcRjW3ImaknBIL7cA", "account_country": "USA", "session_token": "cD1YUhA3Q+BFVazN3KBXOKbVHASxhLLJyWAqVySi3kNzZY4W8ikOiA+sWjs3ROhJcgQh2aErAhxbB3MFVq0h1rdhwbfe6pDtiC1AO7gjuDjCjEJyfZXr4Z5YpeI0ETekbJnNtFrOvxQ5eMdBpXEf9GGOD8FSkBNkl1XYZWdBbsFPKrmGMPEBWcv07pD1XzkONFiwQs71dLPTEwbImkKMjYtTUckv61hjDV9+ktZrYRmRjEhclp3nXkB4a49d88pNVTt9+Dm2MGIuxPvjKogTmE6rLhhxQavVjJrc16zSayrXv30qNiKJfckwSCfcliqWEgICB/I56ZALxbAmvMBWOdB9YPYfvle81KfI1UGrOiXko3G7IzPAA/7voNZqStR0z8FiTiua3GxorxDmUFHezp1kpLgpa+yzNyBobbLP3vbSRC6083rN0XbXM/ad7Tmm2G8FDyzM4XOrE+P2X5mtQcXxlba7ygKq51nP4vM1YrfQYc6ZRgRBJK+vA21i3igPA8awj9zrg4KF5GvMGjwSZ6Tp3wG6MSws909+80bvdp5x22b88k/sjzk2MLNjborCRPz9xC9H/ZAeqjPp6F7S/AqwwEa6RQ34ykiZVLA5dv5bNFteEQZNVO6FmoDBecw3BscsBt6MAHA50mUjq6dvzeZxOOOlpGtgJyOWybyfK3VoevZ03utoYg12dTvLMwGaIyELPhOP6mJFDa2bJxmeDPwkkt+bwk3EVumRudUAhOcIbxXJ52Zt4HuvgzDE/6c+bg9Jbv29cw5njr2P+8VYJO4ghFqmnan+QLnFiwP7Ypacc+Jl0YbEB1bBa/paAnD0wlzYzqS8qi+GQpD5Ro4BFkbS7hMPIvZL1+n4xC3sepc3AAq+0IP6PerQiUn6ORK88JYuRnnMUuwEJcpDtzL/CONusB75h9R4KFIQZl9GykYQ3ZLj+3JerAgvzcFOR0D1OQARBs/I9osU", "trust_eligible": "true", "grant_code": "c610a05475fb544cab5128ff1b920b9a6.0.srzwz.U70T2OR3TxSYAjcY4DHZ9w", "trust_token": "HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX"}

Binary file not shown.

View File

@@ -1,44 +0,0 @@
from typing import List
from sqlalchemy import select
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from .db_models import Base, Location, Route, Waypoint
DATABASE_URL = "sqlite+aiosqlite:///.locations.db"
engine = create_async_engine(DATABASE_URL, echo=True)
async_session_local = async_sessionmaker(engine, expire_on_commit=False)
async def init_db() -> None:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def create_location(name: str, address: str, latitude: float, longitude: float, is_favorite: bool = False):
async with async_session_local() as session:
new_location = Location(name=name, address=address, latitude=latitude, longitude=longitude, is_favorite=is_favorite)
session.add(new_location)
await session.commit()
return new_location
async def get_locations():
async with async_session_local() as session:
result = await session.execute(select(Location))
return result.scalars().all()
async def create_route(name: str, origin_id: int, destination_id: int, waypoints_data: List[dict]):
async with async_session_local() as session:
new_route = Route(name=name, origin_id=origin_id, destination_id=destination_id)
for wp in waypoints_data:
new_route.waypoints.append(Waypoint(order=wp['order'], description=wp['description']))
session.add(new_route)
await session.commit()
return new_route
async def get_routes():
async with async_session() as session:
# Use joinedload for efficient loading of relationships
from sqlalchemy.orm import joinedload
result = await session.execute(
select(Route).options(joinedload(Route.waypoints))
)
return result.scalars().unique().all()

View File

@@ -1,42 +0,0 @@
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.ext.asyncio import AsyncAttrs
from typing import List
class Base(AsyncAttrs, DeclarativeBase):
pass
class Location(Base):
__tablename__ = "locations"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
address: Mapped[str] = mapped_column(String(255))
latitude: Mapped[float]
longitude: Mapped[float]
is_favorite: Mapped[bool] = mapped_column(default=False)
routes: Mapped[List["Route"]] = relationship(back_populates="destination", cascade="all, delete-orphan")
class Route(Base):
__tablename__ = "routes"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
origin_id: Mapped[int] = mapped_column(ForeignKey("locations.id"))
destination_id: Mapped[int] = mapped_column(ForeignKey("locations.id"))
destination: Mapped["Location"] = relationship(back_populates="routes")
waypoints: Mapped[List["Waypoint"]] = relationship(back_populates="route", cascade="all, delete-orphan")
class Waypoint(Base):
__tablename__ = "waypoint"
id: Mapped[int] = mapped_column(primary_key=True)
order: Mapped[int]
description: Mapped[str]
route_id: Mapped[int] = mapped_column(ForeignKey("route.id"))
route: Mapped["Route"] = relationship(back_populates="waypoints")

View File

@@ -6,15 +6,21 @@ from geopy.adapters import AioHTTPAdapter
from geopy.extra.rate_limiter import AsyncRateLimiter
logger = logging.getLogger("ios-api")
CACHE_LOOKUP_SQL = "SELECT address FROM location_cache WHERE lat_lon = ?"
CACHE_UPSERT_SQL = "INSERT OR REPLACE INTO location_cache VALUES (?, ?)"
class AsyncReverseGeocoder:
def __init__(self, db_path="geocache.db", user_agent="pymd3_vue_location_sim/0.1.0 (iam@williambr.uno)"):
def __init__(
self,
db_path: str = "geocache.db",
user_agent: str = "pymd3_vue_location_sim/0.1.0 (iam@williambr.uno)",
):
self.db_path = db_path
self.user_agent = user_agent
self._init_db()
def _init_db(self):
def _init_db(self) -> None:
"""Initializes the SQLite database."""
with sqlite3.connect(self.db_path) as conn:
cursor = conn.cursor()
@@ -26,19 +32,29 @@ class AsyncReverseGeocoder:
''')
conn.commit()
async def get_address(self, lat, lon):
"""Reverse geocode with caching."""
key = f"{lat:.5f},{lon:.5f}"
logger.info("Checking location_cache for %s", key)
# Check Cache
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute(
"SELECT address FROM location_cache WHERE lat_lon = ?", (key,))
row = cursor.fetchone()
if row:
return json.loads(row[0])
@staticmethod
def _cache_key(lat: float, lon: float) -> str:
return f"{lat:.5f},{lon:.5f}"
def _read_cached_address(self, key: str) -> dict | None:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute(CACHE_LOOKUP_SQL, (key,))
row = cursor.fetchone()
return json.loads(row[0]) if row else None
def _store_cached_address(self, key: str, address_data: dict) -> None:
with sqlite3.connect(self.db_path) as conn:
conn.execute(CACHE_UPSERT_SQL, (key, json.dumps(address_data)))
conn.commit()
async def get_address(self, lat: float, lon: float) -> dict | None:
"""Reverse geocode with SQLite cache."""
key = self._cache_key(lat, lon)
logger.info("Checking location_cache for %s", key)
cached_address = self._read_cached_address(key)
if cached_address is not None:
return cached_address
# Fetch New Data
async with Nominatim(
user_agent=self.user_agent,
adapter_factory=AioHTTPAdapter
@@ -49,15 +65,10 @@ class AsyncReverseGeocoder:
location = await reverse(key)
if location:
logger.info("Nominatim response: %s", location)
address_data = location.raw['address']
# Save to Cache
conn.execute(
"INSERT OR REPLACE INTO location_cache VALUES (?, ?)",
(key, json.dumps(address_data))
)
conn.commit()
address_data = location.raw.get("address", {})
self._store_cached_address(key, address_data)
return address_data
return None
except Exception as e:
print(f"Error: {e}")
except Exception:
logger.exception("Reverse geocoding failed for key=%s", key)
return None

View File

@@ -2,6 +2,7 @@ import asyncio
import logging
import os
import sys
import textwrap
from collections.abc import Callable
import click
@@ -16,10 +17,17 @@ from pyicloud.exceptions import (
PyiCloudPasswordException,
)
from .models import iCloudReturnData
from .models import ICloudReturnData
load_dotenv()
logger = logging.getLogger("ios-api")
COOKIE_DIRECTORY = "./cookies"
ENV_APPLE_ID = "APPLE_ID"
ENV_APPLE_PW = "APPLE_PW"
ENV_SELECTED_DEVICE_ID = "SELECTED_DEVICE_ID"
ENV_SELECTED_DEVICE_NAME = "SELECTED_DEVICE_NAME"
ENV_AUTH_INIT_TIMEOUT = "ICLOUD_AUTH_INIT_TIMEOUT_SECONDS"
BACKOFF_SCHEDULE = (15, 30, 60, 120, 300)
AUTH_EXCEPTIONS = (
PyiCloudAuthRequiredException,
PyiCloudFailedLoginException,
@@ -38,11 +46,11 @@ class FindMyMonitor:
get_client_sids: Callable[[], list[str]] | None = None,
code_timeout_seconds: int = 180,
):
self.username = os.getenv("APPLE_ID")
self.password = os.getenv("APPLE_PW")
self.username = os.getenv(ENV_APPLE_ID)
self.password = os.getenv(ENV_APPLE_PW)
self.token_file = token_file
self.selected_device_id = os.getenv("SELECTED_DEVICE_ID")
self.selected_device_name = os.getenv("SELECTED_DEVICE_NAME")
self.selected_device_id = os.getenv(ENV_SELECTED_DEVICE_ID)
self.selected_device_name = os.getenv(ENV_SELECTED_DEVICE_NAME)
self.selected_device = None
self.queue = queue
self.api = None
@@ -52,13 +60,33 @@ class FindMyMonitor:
self.get_client_sids = get_client_sids
self.code_timeout_seconds = code_timeout_seconds
self.auth_init_timeout_seconds = int(
os.getenv("ICLOUD_AUTH_INIT_TIMEOUT_SECONDS", "0")
os.getenv(ENV_AUTH_INIT_TIMEOUT, "0")
)
self._logged_candidates = False
self._no_location_streak = 0
self._auth_error_streak = 0
self._fetch_lock = asyncio.Lock()
async def _create_api_session(self, has_token: bool) -> bool:
try:
init_args = [self.username]
if not has_token:
init_args.append(self.password)
init_task = asyncio.to_thread(
PyiCloudService, *init_args, cookie_directory=COOKIE_DIRECTORY
)
if self.auth_init_timeout_seconds > 0:
self.api = await asyncio.wait_for(
init_task, timeout=self.auth_init_timeout_seconds
)
else:
self.api = await init_task
return True
except Exception:
source = "cookies" if has_token else "credentials"
logger.exception("Failed to initialize iCloud session from %s", source)
return False
async def _request_code_from_vue(self, prompt: str) -> str | None:
if self.sio is None or self.get_client_sids is None:
logger.warning("2FA request skipped: Socket.IO context not configured")
@@ -91,7 +119,7 @@ class FindMyMonitor:
logger.warning("Invalid 2FA code payload from sid=%s", sid)
return None
async def authenticate(self):
async def authenticate(self) -> bool:
"""Authenticates with iCloud, handling 2FA and token storage."""
if not self.username:
logger.warning("APPLE_ID is not configured; skipping iCloud monitor authentication")
@@ -103,42 +131,15 @@ class FindMyMonitor:
)
return False
if os.path.exists(self.token_file):
print("Loading stored session...")
try:
init_task = asyncio.to_thread(
PyiCloudService, self.username, cookie_directory="./cookies"
logger.info(
"Initializing iCloud session from %s",
"stored cookies" if has_token else "credentials",
)
if self.auth_init_timeout_seconds > 0:
self.api = await asyncio.wait_for(
init_task, timeout=self.auth_init_timeout_seconds
)
else:
self.api = await init_task
except Exception as e:
logger.exception("Failed to initialize iCloud session from cookies: %s", e)
return False
else:
print("No stored session. Authenticating...")
try:
init_task = asyncio.to_thread(
PyiCloudService,
self.username,
self.password,
cookie_directory="./cookies",
)
if self.auth_init_timeout_seconds > 0:
self.api = await asyncio.wait_for(
init_task, timeout=self.auth_init_timeout_seconds
)
else:
self.api = await init_task
except Exception as e:
logger.exception("Failed to initialize iCloud session with credentials: %s", e)
if not await self._create_api_session(has_token=has_token):
return False
if self.api.requires_2fa:
print("Two-factor authentication required.")
logger.info("Two-factor authentication required.")
code = await self._request_code_from_vue("Enter the 6-digit Apple verification code")
if code is None:
if sys.stdin and sys.stdin.isatty():
@@ -154,29 +155,28 @@ class FindMyMonitor:
"2FA required but no interactive terminal or Vue responder is available; deferring authentication"
)
return False
# Verify the code
result = await asyncio.to_thread(self.api.validate_2fa_code, code)
print(f"2FA validation result: {result}")
logger.info("2FA validation result: %s", result)
if not result:
print("Failed to verify 2FA code")
logger.warning("Failed to verify 2FA code")
return False
# Trust the session
await asyncio.to_thread(self.api.trust_session)
if self.api.requires_2sa:
import textwrap
print(textwrap.dedent("""
logger.info(textwrap.dedent("""
Two-step authentication required.
Please select a device to receive a SMS verification code:
"""))
# List available devices for 2SA
for i, device in enumerate(self.api.trusted_devices):
print(
f" {i + 1}: {device.get('deviceName', 'Unknown device')} ({device.get('phoneNumber', 'Unknown number')})")
logger.info(
" %s: %s (%s)",
i + 1,
device.get("deviceName", "Unknown device"),
device.get("phoneNumber", "Unknown number"),
)
# Prompt the user for their choice
if not (sys.stdin and sys.stdin.isatty()):
logger.warning(
"2SA required but no interactive terminal is available; deferring authentication"
@@ -185,19 +185,18 @@ class FindMyMonitor:
device_index = await asyncio.to_thread(click.prompt, "Please select a device number", type=int) - 1
device = self.api.trusted_devices[device_index]
if not await asyncio.to_thread(self.api.send_verification_code, device):
print("Failed to send verification code")
logger.warning("Failed to send verification code")
return False
# Prompt the user to enter the verification code they received
code = await asyncio.to_thread(click.prompt, "Please enter verification code", type=int)
if not await asyncio.to_thread(self.api.validate_verification_code, device, code):
print("Failed to verify verification code")
logger.warning("Failed to verify verification code")
return False
print("Successfully authenticated.")
logger.info("Successfully authenticated.")
return True
async def get_location(self):
async def get_location(self) -> ICloudReturnData | None:
"""Fetches the latest latitude and longitude."""
if not self.api:
await self.authenticate()
@@ -259,7 +258,7 @@ class FindMyMonitor:
"deviceStatus": status['deviceStatus'],
"name": status['name']
}
response = iCloudReturnData(**data)
response = ICloudReturnData(**data)
return response
logger.info("Location payload is None for device=%s", self.selected_device.name)
return None
@@ -279,14 +278,12 @@ class FindMyMonitor:
self.running = False
def _no_location_backoff_seconds(self, base_interval: int) -> int:
schedule = [15, 30, 60, 120, 300]
idx = min(self._no_location_streak - 1, len(schedule) - 1)
return max(base_interval, schedule[idx])
idx = min(self._no_location_streak - 1, len(BACKOFF_SCHEDULE) - 1)
return max(base_interval, BACKOFF_SCHEDULE[idx])
def _auth_backoff_seconds(self, base_interval: int) -> int:
schedule = [15, 30, 60, 120, 300]
idx = min(self._auth_error_streak - 1, len(schedule) - 1)
return max(base_interval, schedule[idx])
idx = min(self._auth_error_streak - 1, len(BACKOFF_SCHEDULE) - 1)
return max(base_interval, BACKOFF_SCHEDULE[idx])
async def refresh_location(self):
"""Fetch one location update while serializing iCloud API access."""
@@ -308,7 +305,12 @@ class FindMyMonitor:
if device_data is not None:
self._no_location_streak = 0
self._auth_error_streak = 0
print(f"{device_data.timeStamp} - Location: {device_data.latitude}, {device_data.longitude}")
logger.info(
"%s - Location: %s, %s",
device_data.timeStamp,
device_data.latitude,
device_data.longitude,
)
await self.queue.put(device_data)
else:
self._no_location_streak += 1

View File

@@ -1,213 +0,0 @@
from pymobiledevice3.services.dvt.instruments.location_simulation_base import (
LocationSimulationBase,
)
from pymobiledevice3.services.dvt.instruments.location_simulation import (
LocationSimulation,
)
class LocationSimulationQueue(LocationSimulation):
def __init__(self, dvt, context: LocationSimulationState):
super().__init__(dvt)
self.context = context
async def play_queue(
self, disable_sleep: bool = False, timing_randomness_range: int = 0
) -> None:
while True:
if self.context.queue_state == "PAUSED":
await asyncio.sleep(0.1)
continue
if self.context.queue_state == "SHUTDOWN":
break
loc_id = await self.context.queue.get()
if loc_id is None:
self.context.queue.task_done()
break
location_item = self.context.queue_data.get(loc_id)
if location_item is None:
logger.warning(
"Simulation queue item missing for loc_id=%s; skipping stale entry",
loc_id,
)
self.context.queue.task_done()
continue
new_latitude = location_item.get("latitude")
new_longitude = location_item.get("longitude")
new_delay = location_item.get("delay")
new_delay = 0 if new_delay is None else new_delay
new_start = location_item.get("start")
current_location_item = self.context.queue_data.get(self.context.loc_id)
current_latitude = (
current_location_item.get("latitude")
if isinstance(current_location_item, dict)
else self.context.latitude
)
current_longitude = (
current_location_item.get("longitude")
if isinstance(current_location_item, dict)
else self.context.longitude
)
current_start = (
current_location_item.get("start")
if isinstance(current_location_item, dict)
else None
)
if self.context.set_location_enabled:
if new_delay > 0 and not disable_sleep:
countdown_delay = int(round(float(new_delay)))
if timing_randomness_range > 0:
new_delay = new_delay + random.uniform(
-timing_randomness_range, timing_randomness_range
)
countdown_delay = int(round(float(new_delay)))
for i in range(max(0, countdown_delay), 0, -1):
self.context.next_move = i
await self.context.sio.emit(
"simulation_status",
{
"status": self.context.simulation_active,
"loc_id": self.context.loc_id,
"latitude": current_latitude,
"longitude": current_longitude,
"start": current_start,
"next_move": i,
},
namespace="/",
)
await asyncio.sleep(1)
self.context.queue_data[loc_id]["start"] = self.context.queue_data[self.context.loc_id]["end"] = datetime.now(timezone.utc).isoformat()
await self.set(new_latitude, new_longitude)
self.context.loc_id = loc_id
self.context.latitude = new_latitude
self.context.longitude = new_longitude
await self.context.sio.emit(
"simulation_status",
{
"status": self.context.simulation_active,
"loc_id": self.context.loc_id,
"latitude": self.context.latitude,
"longitude": self.context.longitude,
"start": new_start,
"next_move": None,
},
namespace="/",
)
logger.info(
"Set simulated location to %s, %s after %ss delay",
new_latitude,
new_longitude,
new_delay,
)
self.context.queue.task_done()
class LocationSimulationTestQueue(LocationSimulationBase):
def __init__(self, context: LocationSimulationState):
super().__init__()
self.context = context
def __enter__(self):
return self
def __exit__(self):
return self
async def set(self, latitude: float, longitude: float) -> None:
await asyncio.sleep(0.1)
logger.info("Simulated location set to %s, %s", latitude, longitude)
async def clear(self) -> None:
q = self.context.queue
self.context.set_location_enabled = False
self.context.queue_state = "SHUTDOWN"
while not q.empty():
try:
item = q.get_nowait()
q.task_done()
logger.info("Discarding item from queue: %s", item)
except asyncio.QueueEmpty:
break
await q.put(None)
if self.context.simulation_task is not None and not self.context.simulation_task.done():
try:
await asyncio.wait_for(self.context.simulation_task, timeout=5)
except TimeoutError:
self.context.simulation_task.cancel()
with suppress(asyncio.CancelledError):
await self.context.simulation_task
self.context.simulation_active = False
self.context.queue_state = "SHUTDOWN"
async def play_queue(
self, disable_sleep: bool = False, timing_randomness_range: int = 0
) -> None:
while True:
if self.context.queue_state == "PAUSED":
await asyncio.sleep(0.1)
continue
if self.context.queue_state == "SHUTDOWN":
break
loc_id = await self.context.queue.get()
if loc_id is None:
self.context.queue.task_done()
break
location_item = self.context.queue_data.get(loc_id)
if location_item is None:
logger.warning(
"Test simulation queue item missing for loc_id=%s; skipping stale entry",
loc_id,
)
self.context.queue.task_done()
continue
latitude = location_item.get("latitude")
longitude = location_item.get("longitude")
delay = location_item.get("delay")
delay = 0 if delay is None else delay
start_time = location_item.get("start")
if self.context.set_location_enabled:
if delay > 0 and not disable_sleep:
countdown_delay = int(round(float(delay)))
if timing_randomness_range > 0:
delay = delay + random.uniform(
-timing_randomness_range, timing_randomness_range
)
countdown_delay = int(round(float(delay)))
for i in range(max(0, countdown_delay), 0, -1):
self.context.next_move = i
await self.context.sio.emit(
"simulation_status",
{
"status": self.context.simulation_active,
"loc_id": self.context.loc_id,
"latitude": self.context.latitude,
"longitude": self.context.longitude,
"next_move": i,
},
namespace="/",
)
await asyncio.sleep(1)
await self.set(latitude, longitude)
self.context.latitude = latitude
self.context.longitude = longitude
await self.context.sio.emit(
"simulation_status",
{
"status": self.context.simulation_active,
"loc_id": self.context.loc_id,
"latitude": self.context.latitude,
"longitude": self.context.longitude,
"next_move": None,
},
namespace="/",
)
logger.info(
"Set simulated location to %s, %s after %ss delay",
latitude,
longitude,
delay,
)
self.context.queue.task_done()

View File

@@ -1,59 +1,63 @@
from typing import Optional, Dict
from __future__ import annotations
from typing import Optional
from pydantic import BaseModel
class SimulationStatusData(BaseModel):
class Coordinate(BaseModel):
latitude: float
longitude: float
class ScheduledCoordinate(Coordinate):
delay: int = 0
start: Optional[str] = None
end: Optional[str] = None
class SimulationStatusData(Coordinate):
start: float
end: Optional[float]
next_move: Optional[float]
end: Optional[float] = None
next_move: Optional[float] = None
class SimulationStatus(BaseModel):
status: bool
data: Optional[SimulationStatusData]
data: Optional[SimulationStatusData] = None
class SimulationRequestData(BaseModel):
latitude: float
longitude: float
delay: int = 0
start: Optional[str] = None
end: Optional[str] = None
class SimulationRequestData(ScheduledCoordinate):
pass
class SimulationRequest(BaseModel):
status: bool
data: Optional[SimulationRequestData]
data: Optional[SimulationRequestData] = None
class SimulationRequestResponseData(BaseModel):
class SimulationRequestResponseData(ScheduledCoordinate):
loc_id: str
latitude: float
longitude: float
delay: int = 0
start: Optional[str] = None
end: Optional[str] = None
class SimulationQueueList(BaseModel):
data: Optional[SimulationRequestResponseData]
data: Optional[SimulationRequestResponseData] = None
class SimulationRequestResponse(BaseModel):
status: bool
data: Optional[SimulationRequestResponseData]
data: Optional[SimulationRequestResponseData] = None
class SimulationQueueDict(BaseModel):
location_id: Dict[str, SimulationRequestResponseData]
location_id: dict[str, SimulationRequestResponseData]
class iCloudLocationData(BaseModel):
latitude: float
longitude: float
class ICloudLocationData(Coordinate):
timestamp: str
class iCloudReturnData(BaseModel):
latitude: float
longitude: float
class ICloudReturnData(Coordinate):
timeStamp: int
altitude: float
horizontalAccuracy: float
@@ -63,11 +67,16 @@ class iCloudReturnData(BaseModel):
deviceStatus: int
name: str
class LatLng(BaseModel):
latitude: float
longitude: float
class LatLng(Coordinate):
pass
class ORSRequest(BaseModel):
geometry_simplify: bool
coordinates: List[List[float]]
coordinates: list[list[float]]
# Backward compatibility aliases for existing imports.
iCloudLocationData = ICloudLocationData
iCloudReturnData = ICloudReturnData

View File

@@ -1,379 +0,0 @@
""" Tunnel Functions"""
@self._app.get("/start-tunnel")
async def start_tunnel(
udid: Optional[str] = self.context.udid,
ip: Optional[str] = None,
connection_type: Optional[str] = None,
) -> fastapi.Response:
udid_tunnels = [
t.tunnel
for t in self._tunneld_core.tunnel_tasks.values()
if t.udid == udid and t.tunnel is not None
]
if len(udid_tunnels) > 0:
self.context.udid = udid
data = {
"interface": udid_tunnels[0].interface,
"port": udid_tunnels[0].port,
"address": udid_tunnels[0].address,
}
return generate_http_response(data)
queue = asyncio.Queue()
created_task = False
try:
if not created_task and connection_type in ("usbmux", None):
task_identifier = f"usbmux-{udid}"
try:
async with await create_using_usbmux(udid) as lockdown:
service = await CoreDeviceTunnelProxy.create(lockdown)
task = asyncio.create_task(
self._tunneld_core.start_tunnel_task(
task_identifier,
service,
protocol=TunnelProtocol.TCP,
queue=queue,
),
name=f"start-tunnel-task-{task_identifier}",
)
self._tunneld_core.tunnel_tasks[task_identifier] = TunnelTask(
task=task, udid=udid
)
created_task = True
except ConnectionFailedError, InvalidServiceError, MuxException:
pass
if connection_type in ("usb", None):
for rsd in await get_rsds(udid=udid):
rsd_ip = rsd.service.address[0]
if ip is not None and rsd_ip != ip:
await rsd.close()
continue
task = asyncio.create_task(
self._tunneld_core.start_tunnel_task(
rsd_ip,
await create_core_device_tunnel_service_using_rsd(rsd),
queue=queue,
),
name=f"start-tunnel-usb-{rsd_ip}",
)
self._tunneld_core.tunnel_tasks[rsd_ip] = TunnelTask(
task=task, udid=rsd.udid
)
created_task = True
if not created_task and connection_type in ("wifi", None):
for remotepairing in await get_remote_pairing_tunnel_services(
udid=udid
):
remotepairing_ip = remotepairing.hostname
if ip is not None and remotepairing_ip != ip:
await remotepairing.close()
continue
task = asyncio.create_task(
self._tunneld_core.start_tunnel_task(
remotepairing_ip, remotepairing, queue=queue
),
name=f"start-tunnel-wifi-{remotepairing_ip}",
)
self._tunneld_core.tunnel_tasks[remotepairing_ip] = TunnelTask(
task=task, udid=remotepairing.remote_identifier
)
created_task = True
except Exception as e:
return fastapi.Response(
status_code=501,
content=json.dumps(
{
"error": {
"exception": e.__class__.__name__,
"traceback": traceback.format_exc(),
}
}
),
)
if not created_task:
return fastapi.Response(
status_code=501, content=json.dumps({"error": "task not created"})
)
tunnel: Optional[TunnelResult] = await queue.get()
if tunnel is not None:
self.context.udid = udid
data = {
"interface": tunnel.interface,
"port": tunnel.port,
"address": tunnel.address,
}
return generate_http_response(data)
else:
return fastapi.Response(
status_code=404,
content=json.dumps(
{"error": "something went wrong during tunnel creation"}
),
)
@self._app.get("/restart-tunneld")
async def restart() -> fastapi.Response:
"""Restart Tunneld"""
self._tunneld_core.clear()
await asyncio.sleep(2)
self._tunneld_core.start()
data = {
"operation": "restart-tunneld",
"data": True,
"message": "Restarting tunneld...",
}
return generate_http_response(data)
@self._app.get("/shutdown")
async def shutdown() -> fastapi.Response:
"""Shutdown Tunneld"""
os.kill(os.getpid(), signal.SIGINT)
data = {
"operation": "shutdown",
"data": True,
"message": "Server shutting down...",
}
return generate_http_response(data)
@self._app.get("/clear-tunnels")
async def clear_tunnels() -> fastapi.Response:
"""Clear all tunnels"""
self._tunneld_core.clear()
data = {
"operation": "clear_tunnels",
"data": True,
"message": "Cleared tunnels...",
}
return generate_http_response(data)
@self._app.get("/cancel")
async def cancel_tunnel(udid: str) -> fastapi.Response:
"""Cancel a tunnel"""
self._tunneld_core.cancel(udid=udid)
data = {
"operation": "cancel",
"udid": udid,
"data": True,
"message": f"tunnel {udid} Canceled ...",
}
return generate_http_response(data)
"""Simulation Functions"""
@self._app.get("/start-simulation")
async def app_start_simulation() -> fastapi.Response:
logger.info("Simulation Start Requested ")
if (
self.context.simulation_task is None
or self.context.simulation_task.done()
):
await start_icloud_monitor()
self.context.simulation_active = True
self.context.simulation_task = asyncio.create_task(
start_simulation_queue(),
name="location-simulation-worker",
)
data = {"status": "started", "message": "Simulation started"}
else:
data = {"status": "error", "message": "Simulation already running"}
return generate_http_response(data)
@self._app.get("/start-icloud-monitor")
async def app_start_icloud_monitor() -> fastapi.Response:
await start_icloud_monitor()
data = {
"status": "started",
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
"icloud_monitor_running": is_icloud_monitor_running(),
}
return generate_http_response(data)
@self._app.get("/stop-icloud-monitor")
async def app_stop_icloud_monitor() -> fastapi.Response:
await end_icloud_monitor()
data = {
"status": "stopped",
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
"icloud_monitor_running": is_icloud_monitor_running(),
}
return generate_http_response(data)
@self._app.get("/icloud-monitor-status")
async def app_icloud_monitor_status() -> fastapi.Response:
data = {
"status": "ok",
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
"icloud_monitor_running": is_icloud_monitor_running(),
}
return generate_http_response(data)
@self._app.post("/add-location")
async def app_add_location(data: SimulationRequestData) -> fastapi.Response:
"""Add a location to the simulation queue"""
logger.info("Request to add new location to queue")
loc_id = str(uuid.uuid4())
latitude = (
data.get("latitude")
if isinstance(data, dict)
else getattr(data, "latitude", None)
)
longitude = (
data.get("longitude")
if isinstance(data, dict)
else getattr(data, "longitude", None)
)
delay = (
data.get("delay", 0)
if isinstance(data, dict)
else getattr(data, "delay", 0)
)
try:
delay = parse_delay_seconds(delay)
except ValueError as e:
return generate_http_response(
{"status": "error", "message": str(e)},
status_code=400,
)
if latitude is not None and longitude is not None:
logger.info(
"Adding location %s (%s, %s) with %s delay to the queue",
loc_id,
latitude,
longitude,
delay,
)
accrued_delay = 0
if self.context.queue_data:
accrued_delay = sum(
parse_delay_seconds(item.get("delay", 0))
for item in self.context.queue_data.values()
)
now_time = datetime.now(timezone.utc)
new_time = (
now_time
+ timedelta(seconds=accrued_delay)
+ timedelta(seconds=delay)
)
start_time = new_time.isoformat()
location_item = {
"loc_id": loc_id,
"latitude": latitude,
"longitude": longitude,
"delay": delay,
"start": start_time,
"status": "queued",
}
resp = {
"status": "added",
"message": f"Location {loc_id} added to the queue",
"item": location_item,
}
await self.context.queue.put(loc_id)
add_item(loc_id, location_item)
logger.info("Location %s added to the queue", loc_id)
else:
resp = {"status": "error", "message": "Invalid location data"}
return generate_http_response(resp)
@self._app.get("/clear-queue")
async def app_clear_queue() -> fastapi.Response:
"""Clear the simulation queue"""
logger.info("Simulation Start Requested ")
await empty_simulation_queue()
data = {"status": "cleared", "message": "Simulation cleared"}
return generate_http_response(data)
@self._app.get("/pause-queue")
async def app_pause_queue() -> fastapi.Response:
"""Pause the simulation queue"""
await pause_simulation_queue()
data = {"status": "paused", "message": "Simulation paused"}
return generate_http_response(data)
@self._app.get("/resume-queue")
async def app_resume_queue() -> fastapi.Response:
"""Resume the simulation queue"""
await resume_simulation_queue()
data = {"status": "resumed", "message": "Simulation resumed"}
return generate_http_response(data)
@self._app.get("/end-simulation")
async def app_end_simulation() -> fastapi.Response:
"""End the simulation queue"""
logger.info("End location simulation request")
end_task = asyncio.create_task(
end_simulation_queue(), name="end-simulation-worker"
)
result = await end_task
data = {"status": result, "message": "Simulation ended"}
return generate_http_response(data)
"""Status Functions"""
@self._app.get("/")
async def list_tunnels() -> dict[str, list[dict]]:
"""Retrieve the available tunnels and format them as {UUID: TUNNEL_ADDRESS}"""
tunnels = {}
for ip, active_tunnel in self._tunneld_core.tunnel_tasks.items():
if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
continue
if active_tunnel.udid not in tunnels:
tunnels[active_tunnel.udid] = []
tunnels[active_tunnel.udid].append(
{
"tunnel-address": active_tunnel.tunnel.address,
"tunnel-port": active_tunnel.tunnel.port,
"interface": ip,
}
)
return tunnels
@self._app.get("/device-info")
async def device_info():
"""Get device information"""
tunnels = {}
for ip, active_tunnel in self._tunneld_core.tunnel_tasks.items():
if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
continue
if active_tunnel.udid not in tunnels:
tunnels[active_tunnel.udid] = {}
try:
lockdown = await create_using_usbmux(
serial=active_tunnel.udid, autopair=False
)
tunnels[active_tunnel.udid] = iterate_multidim(lockdown.all_values)
except Exception as e:
logger.error(
f"Failed to create lockdown session for device {active_tunnel.udid}: {e}"
)
continue
return tunnels
@self._app.get("/device-name")
async def rsd_info():
"""Get rsd information"""
device_name = await get_device_name()
return generate_http_response(device_name)
@self._app.get("/rsd-info")
async def rsd_info():
"""Get rsd information"""
rsd_info = {}
if self.context.tunnel is None:
await get_tun()
if self.context.tunnel is not None:
rsd_info = self.context.tunnel.peer_info
return generate_http_response(rsd_info)
@self._app.get("/hello")
async def hello() -> fastapi.Response:
data = {"message": "Hello, I'm alive"}
return generate_http_response(data)
@self._app.get("/context-status")
async def app_context_status() -> fastapi.Response:
data = get_status()
return generate_http_response(data)

View File

@@ -1,425 +0,0 @@
""" Socket.IO Functions"""
async def sio_send_status(sid):
"""Send Current Status"""
await self.context.sio.emit(
"status", jsonable_encoder(get_status()), namespace="/", to=sid
)
"""Socket.IO Connection Events"""
@self.context.sio.event
async def connect(sid, environ):
"""Client connection event handler."""
self.context.connected_clients.add(sid)
logger.info("Client connected: %s", sid)
await sio_send_status(sid)
return "%s connected" % sid
@self.context.sio.event
async def disconnect(sid):
"""Client disconnection event handler."""
self.context.connected_clients.discard(sid)
logger.info("Client disconnected: %s", sid)
""" Socket.IO Request Events """
@self.context.sio.event
async def request_update(sid):
status_update = jsonable_encoder(get_status())
logger.info("Update request from %s sending %s", sid, status_update)
return status_update
@self.context.sio.event
async def message(sid, data):
logger.info("Received message from %s: %s", sid, data)
return True, "Message received"
# await self.context.sio.emit("message", f"Received message from {sid}: {data}", namespace="/")
""" Device Control"""
@self.context.sio.event
async def device_control(sid, data):
"""Device Control"""
command = (
data.get("command")
if isinstance(data, dict)
else getattr(data, "command", None)
)
delay = (
data.get("delay")
if isinstance(data, dict)
else getattr(data, "delay", None)
)
if delay is None:
delay = 5
match command:
case "shutdown":
""" Shutdown the device"""
logger.info(
"Shutdown command received from %s with delay %s", sid, delay
)
await device_shutdown(delay)
return {
"command": "shutdown",
"status": "success",
"message": f"Device shutdown initiated with {delay} seconds delay",
}
case "reboot":
""" Reboot the device"""
logger.info(
"Reboot command received from %s with delay %s", sid, delay
)
await device_reboot(delay)
return {
"command": "reboot",
"status": "success",
"message": f"Device reboot initiated with {delay} seconds delay",
}
case _:
return {
"command": command,
"status": "error",
"message": f"Invalid command: {command}",
}
@self.context.sio.event
async def simulation_control(sid, data):
"""Simulation Control"""
command = (
data.get("command")
if isinstance(data, dict)
else getattr(data, "command", None)
)
logger.info(
"Simulation Control command: %s requested from %s", command, sid
)
try:
match command:
case "add":
""" Add a location to the simulation queue"""
loc_id = str(uuid.uuid4())
latitude = (
data.get("latitude")
if isinstance(data, dict)
else getattr(data, "latitude", None)
)
longitude = (
data.get("longitude")
if isinstance(data, dict)
else getattr(data, "longitude", None)
)
delay = (
data.get("delay", 0)
if isinstance(data, dict)
else getattr(data, "delay", 0)
)
try:
delay = parse_delay_seconds(delay)
except ValueError as e:
return {
"command": command,
"status": "error",
"message": str(e),
}
if latitude is not None and longitude is not None:
logger.info(
"Adding location %s (%s, %s) with %s delay to the queue",
loc_id,
latitude,
longitude,
delay,
)
accrued_delay = 0
if self.context.queue_data:
accrued_delay = sum(
parse_delay_seconds(item.get("delay", 0))
for item in self.context.queue_data.values()
)
now_time = datetime.now(timezone.utc)
new_time = (
now_time
+ timedelta(seconds=accrued_delay)
+ timedelta(seconds=delay)
)
start_time = new_time.isoformat()
coords = f"{latitude}, {longitude}"
rev_geocode = self.context.reverse_geocode(coords)
if rev_geocode:
address = rev_geocode.address
else:
address = f"{latitude}, {longitude}"
location_item = {
"loc_id": loc_id,
"latitude": latitude,
"longitude": longitude,
"delay": delay,
"start": start_time,
"address": address,
}
ack = {
"command": command,
"status": "added",
"message": f"Location {loc_id} added to the queue",
"item": location_item,
}
await self.context.queue.put(loc_id)
add_item(loc_id, location_item)
logger.info("Location %s added to the queue", loc_id)
return ack
else:
logger.warning(
"Invalid location data received from %s: %s", sid, data
)
return {
"command": command,
"status": "error",
"message": "Invalid location data",
"data": location_item,
}
case "clear":
""" Clear the simulation queue"""
await empty_simulation_queue()
return {
"command": command,
"status": "cleared",
"message": "Simulation cleared",
}
case "pause":
""" Pause the simulation queue"""
await pause_simulation_queue()
return {
"command": command,
"status": "paused",
"message": "Simulation paused",
}
case "resume":
""" Resume the simulation queue"""
await resume_simulation_queue()
return {
"command": command,
"status": "resumed",
"message": "Simulation resumed",
}
case "end":
""" End the simulation queue"""
logger.info("End location simulation request from %s", sid)
end_task = asyncio.create_task(
end_simulation_queue(), name="end-simulation-worker"
)
result = await end_task
simstatus = not result
return {
"command": command,
"status": simstatus,
"message": "Simulation ended",
}
case "start":
""" Start the simulation queue"""
logger.info("Start location simulation request from %s", sid)
if (
self.context.simulation_task is None
or self.context.simulation_task.done()
):
await start_icloud_monitor()
self.context.simulation_active = True
self.context.queue_state = "RUNNING"
self.context.simulation_task = asyncio.create_task(
start_simulation_queue(),
name="location-simulation-worker",
)
return {
"command": command,
"status": self.context.queue_state,
"message": "Simulation started",
}
else:
return {
"command": command,
"status": "error",
"message": "Simulation already running",
}
case _:
logger.warning(
"Invalid command received from %s: %s", sid, command
)
return {"status": "error", "message": "Invalid command"}
finally:
await sio_send_status(sid)
@self.context.sio.event
async def icloud_monitor_control(sid, data):
command = (
data.get("command")
if isinstance(data, dict)
else getattr(data, "command", None)
)
logger.info(
"iCloud Monitor control command: %s requested from %s", command, sid
)
try:
match command:
case "start":
await start_icloud_monitor()
return {
"command": command,
"status": "running",
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
"icloud_monitor_running": is_icloud_monitor_running(),
}
case "stop":
await end_icloud_monitor()
return {
"command": command,
"status": "stopped",
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
"icloud_monitor_running": is_icloud_monitor_running(),
}
case "status":
return {
"command": command,
"status": "ok",
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
"icloud_monitor_running": is_icloud_monitor_running(),
}
case _:
return {
"command": command,
"status": "error",
"message": "Invalid command",
}
finally:
await sio_send_status(sid)
""" Tunnel Control """
@self.context.sio.event
async def tunneld_control(sid, data):
command = (
data.get("command")
if isinstance(data, dict)
else getattr(data, "command", None)
)
logger.info("Tunneld Control command: %s requested from %s", command, sid)
match command:
case "start":
"""Start Tunneld"""
logger.info("Start tunneld request from %s: %s", sid, data)
try:
self._tunneld_core.start()
logger.info("Tunneld started successfully")
return {
"status": "running",
"message": "Tunneld started successfully",
}
except Exception as e:
logger.error("Error starting tunneld: %s", e)
return {
"command": command,
"status": "error",
"message": f"Error starting tunneld: {e}",
}
case "start-watcher":
""" Start Tunneld Watcher """
logger.info("Start tunneld watcher request from %s: %s", sid, data)
await start_tunnel_watcher()
return {
"status": "running",
"message": "Tunneld watcher started successfully",
}
case "end-watcher":
""" End Tunneld Watcher """
logger.info("End tunneld watcher request from %s: %s", sid, data)
await end_tunnel_watcher()
return {
"status": "stopped",
"message": "Tunneld watcher stopped successfully",
}
case "shutdown":
"""Shutdown Tunneld"""
logger.info("Shutdown tunneld request from %s: %s", sid, data)
try:
os.kill(os.getpid(), signal.SIGINT)
return {
"command": command,
"status": "Success",
"message": "Server shutting down...",
}
except Exception as e:
logger.error("Error shutting down tunneld: %s", e)
return {
"command": command,
"status": "error",
"message": f"Error shutting down tunneld: {e}",
}
case "clear":
"""Clear all tunnels"""
logger.info("Clearing tunnels...")
try:
self._tunneld_core.clear()
return {
"command": command,
"status": "Success",
"message": "Cleared tunnels...",
}
except Exception as e:
logger.error("Error clearing tunnels: %s", e)
return {
"command": command,
"status": "error",
"message": f"Error clearing tunnels: {e}",
}
case "cancel":
"""Cancel a tunnel"""
logger.info("Canceling tunnel request from %s: %s", sid, data)
try:
udid = (
data.get("udid")
if isinstance(data, dict)
else getattr(data, "udid", self.context.udid)
)
if udid is None:
udid = self.context.udid
self._tunneld_core.cancel(udid=udid)
return {
"command": command,
"status": "Success",
"udid": udid,
"message": f"tunnel {udid} Canceled ...",
}
except Exception as e:
logger.error("Error canceling tunnel: %s", e)
return {
"command": command,
"status": "error",
"message": f"Error canceling tunnel: {e}",
}
case _:
return {
"command": command,
"status": "error",
"message": f"Unknown operation: {command}",
}

File diff suppressed because it is too large Load Diff