From c5a563f04760f83534c07f822240aa93db4bcea3 Mon Sep 17 00:00:00 2001 From: William Bruno Date: Sat, 21 Mar 2026 08:22:24 -0400 Subject: [PATCH] icloud --- __pycache__/server.cpython-314.pyc | Bin 72761 -> 74436 bytes icloud.py | 31 +++++- pyproject.toml | 2 + server.py | 53 +++++---- uv.lock | 171 +++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+), 28 deletions(-) diff --git a/__pycache__/server.cpython-314.pyc b/__pycache__/server.cpython-314.pyc index d4b7bd11078bc7bc431ec7ec56c45690a1de06b0..5148778c35278e0d9832a4337dbf66a43ac715c0 100644 GIT binary patch delta 16497 zcma)D31CxI)_ylz(W~RChg^_bxUDx-xJkWQP_qK@LOguS>JuD^LLw_5uTOGl1dT&0XxTHV*9khv zqo~(A41&RtEF?32Ouf-z5=@R1A%#6F>r)+RLYjqZRX0zgPgUr*4rdTGo%2(@E0c4^ zwwj%BQzfW4$3^5?1dEg4ze?!@zE!Y7eX4qkBU8v^dDZn@kt5_da)n$+o{-1* z@%8x*n?Ua+b{%`x)R#FX36mV%8WBN9nO|DDw-Z0DV&uCyfXQIZdNd1;=u^P!E3#;BO)PH7qv^jmsrMQ)}Uv z4#Ks|6NTpGdSMZ3S_(8RwN*1}RKdx(X~0b%$6d_08NfA<<1PVih;v$iZ5_wHj%CgS zZq_(%3*%-3H)kC8ddAHKZr(WV4UC%)T-!MAjf`stZowG0MYxHv3xQo!85uIdQpPI= zUP+Xiodn$S*0d;hIW3XpTJM`F&EUMmUhy7wLX(|@_QrLM zexFzH^ZhGjDLZGZYOf^<9N561-D>pK1J_8^XzygVyk2H$)M zO^8b&Q>iU(J*l7%$5oRO`j@z})KW+c;`o>&=F7v(2vca0%A}}Zk92{mg0#|u@tJgw zYCo}2hkCkM4M}zhUxGQ96tqItMp4Wpx<{P{MQ%^ZqyMWe3^6%4(3<*U_&O|d4T1xq z9w4l7x!gXVUvLW^*Q#z`o6E&7z{G_J4FKbnY(geNEBsxG0r>oG?r!eCIG)vmDQ)l< z{Ya?QkU~7rfUVUw;D!}JsHi&_mbJSDw_O^Rt@Qgld2A6AdzEH|60$(h3T--lAt8SH zShM)+vGC=JE=4AS2Z z>Rz}`?B-5?o6FliX4%V{3wA|AGUM{AkV1v8qHD3fl&qnx`c`|?uKdkd?(&ZJAroN@ z{i{A=C?7Lah#6|BVRb3%beC8mGZf~(O64PlToR(jWXty&Du}fsgpe3BRFG+KC)42h zoN>{)x2`hZvT`N62E;GE>xBAL`3R;9M6YHA? zD^kh0#n5#RWC~(!u*Us_dpkjY)1XX0BIO9Vm}nT38AjwljwraKWO(=}1|GyUhOM^W zy)116X*>eT9ydNV($y&iWDDJvQcphaKcA9Jh=g8BT}fT3=G+#}XN2tRXSpQ0M zkbqM^YjMpefRv^_xj6OMipXSQSF#oT%0rp@ty$zry3Oi?_=04%r#^{g4j>#vcnaaS z2v5*ZrfKTqNdAUBBU~wWFcwntMnBM}Z)E0>C+MZjDNs#8R!4}8_8FuI)4iAV?H7@frUrHs}vx{T3U zMuR3$hc3h(^4NnO>41vS21ctH4f`A5$1~c<=mbWa7@f#y*eF1rBxkDFNKg?r3D_Ke zAxlrkCx3Ao=X5&RMgg9&sicTn?ZrwzG2^$8{ zI-|R#fQDYkFVtm??r)s1d1xtuHs#Rsc{6i!qx`(64A?TDjHm=1O31ZEbHe5!Qd_}g zIrXTeu`rsmNX*$%0Xi1bf&6^>&0RVZY$qaHI45i*TJo4Msk4k)Z8Hs%qG{!jRsgC> zoE0av`v*oc%_ z(VW$g69>U;F&}->UM-HC8f4I~?US?TMl;u<0+4y$WkNHs`pNU7`RbUW#h~alm*q6W z=q+Y_>A)2KB-&e8qniXW>+w0-E0%3}MrtXv#V;^Mx5fc0WS*1YTsV%EIhwCw95V}r z8f-0gP@r*w*lf%mDzIhdf$i7gpHcqE2>G1t`= z890!IdTbp-U zYa3GA2GlmXw{+2sDlXm*VNZPF+v=jX)x}=~G2po~cWw#UPv+*y`fNZQ)sVS1*->pK z(iLQGf$V5yAb$lM~?OV${qZ3MA^VG$smQf4dtIS7M|pTU#S{Ltu=+6@jtB_dd68CiIU_l6r_gQ?)oGk9{_%uxV z?jGk~fV6+o&PmhRo~untfAqY*|HVn06h@R`tMDgK@E=f0j9U;qT>&8+M?amGYVKu{`Cz*kl|?+c6+hVPCDW&o zfA+7Q{(w|fh9drf*{8tJ&QJ1n2f$vpdfPlM+@YK3yjeGqivB&bVoAxw#_)q!O5zVf zu)Qt_wL=Vud)jCWc(!^T1b%PMG4z+so=-IY!kYeXe1@OSaS%IXm`#TJ6Kg(IlWF~m zrlk@HgI6}Mu#eTo480YLkA`w!wjgvZhHl}sp0AZ8ttlKAfStP-#WL%FT7F*ewDU)> znYL1cWHF&h3k~#_f{y-aw?28f*-55G7K7166|z|C4b=KT ztSpU-B&FD%L1d!Ywpmcw{X+eaNKHGL(;j03pj3>78GG0QH%-|8-2!_ zLj`*J%U)f`DQ$t%Tc>;~Eeam@I{3F2(w$#kf9LYXX5zk5$|aS@Fcn z+PKadIrFj#ek)ERkM~xO)!im^yF1}Iz)WiuM~ZcHzW@hjN;oL-@VB}*=?e>lCG{i7}FS4bpL4{u-l_&}v+oP}` zJ_hUy+rK0+v0Y3}gQ+6+oCHeZ$cHcfWt1|Nt>Z6jR)i+5jXwoBK^#_WE&N;Ql%dkO zZtEUAlJDy!#q{)^J8$bWWRm&R%>V zy)S*pK6}tUd&pipXsG6QE~k!s5ljIE@2n}WHIbu} zNo}(1=#+GzUozHk@YM#1(GNBpSDs6Fyi`S)-{BT>&Cv|>PWv^9$(bH#XDRHx?u_1=y4yUpEci*h03^vo~%tegXxW_4b0oIBjgLCcFDLw=N+w_$;UgoG#O~2b0;Ja{wrCVliG=7x5gd7E_|4^73#MHo0by zeRQ#F8tkVwyB?d$Bt^`QLc$8()8)U_(`*L?>EPZVogQ3Ss1|d&u)4?T zBP(Wt$O|i$nVCq*IOS%cRYqmdo7^+V0lM9t3E7`3j zNfEZ>(Uo(^Z|Oe(u_Y4lq(STsIfEXi?BIaGxXL*$|M={myK{Ze{rMw-+Ryu3J za~+8!s*yHn;e2pC*`q2g1JU!-sc&wo%rYtz>%aMyT&BzZ6FRNuF065xeY8xZZb+gZ z+)_*mX?o{$QY^MP(0Top+e~A+v~=b93_7LjPnMEVaSEbacM2 zjaX=(&myhkOX+Ffq)^JZSd2|pT;#T&2tooPoQPw-}#z@mRrYwuh5vxNhu>1Wh4`lkh! zNkYj@DP(s|aifiUDR#=jLhhy62B44WNrO^$%#erlObO&UHY*+I|_z6 zOB9VU@h2-u!&L3b$#FoR;v|R?CFDF6hk{P26^*&Er?jNekZ?*DhqPIOXjNjWP1Tev zJyl_9(n#M>DL}b5G;-j)kt_we4LXW_5)jdk>EQ3u@qUYtCnM$b$eJ|z&Mi7Btev84 zBu>prIH#e+ZKV2Bx=<;pB>~vC!&X!;o_5{`g~y-L;^P(s76b=?3hN;LjmAJOkI%ib z6Ao|L8LkU<6sv`s#bB9vBj%Uj1eB{2Pdjn!Hu1fH5L3ZTh212&h+iP*`zse`3iFgV z;J{_`VPZvLg*WIL4bfR(9acgJ=kmF`Jgnky4D4TVo57p^DC)shdl%Ad?86vmAsc;u zUG>!WaoES>u;0iHOLY2#b4gjf^Y%6DZ5Ya_8O*5}mICiWlBHL_FMV(NQ0C0R%$dV- z9W7gL2tmrQlGCK`T(W1`u4O|R6@wWS!?DPXyPT^+uA0*rcgpstcd3U=_Cb?Uo*;BNuXehOKFtvDCgIq1A$?FyNZP>eEDBsaI znC}?YAzROBEYG&T*m<~fsJa>c4^%hz%J!-Es)wxQgVypP>$E}Zv@_Nj11;AN8?Zn! zr!nso_H5X-VaPId&@y${sG~`@8H|{g%4srow(sfO)j4FIJZPRgoTj5yx0SIp^X2Wb z=;)odl@T31bX!^J+0;1$t?uD$l#&A~jp{Ic&^mo27r2||4cj=qY12Y>j6t`SCDYsu zZ3^ZxUGx_lGPKNb%K3}fS!@?wOn<*2B@Gv579oy1F=6{>sf3Pfz~G?#_6@aO95NjM zY-cNYd;)||w;;0nqt@y&w?ZfMPzK7g9XQjLC77)yu?W3#jSrs}0*~e{Qv9vgO*)Enjvu-q9R725i-H-W<2(52an3}xO(w=1Oo?$+`JV@57zM#Qf34Tz2)bbp&=xr*5v2l zV+}w!*5zvJbO(d*Izb&W*I~_ZtNeVITX1>Sw(%LjfCZ1WhDXsjba)MBQ+Ky}+g+l5 zL2(qIiNk7f&j?9i-EblxRc$t^m||)BCji{v0D4#;6L5yhrz;$7t96Wn>wB=>9`_s1AQGz}56)8rzO)nf;{z0V7Jb1|>f7kIJX1E~L>;}X2SMGk6=x;?$mV+$tKzR582Sn4hJ!m3t z(Ub?X8mFQXM-kYxLPyEpge92grTB;@}V6>RfO?xi*!EmD(v`72&!~ttaj`0syFO*~eJu+; zI?Fm)(?X&Qu2BKvv>;AbDghDdOs*vkgJ%3><2lwOc8;Z|YkzgEx*3>F>@6PNLr?!| zCVggLc?HaI5AtEr#r3975|)&dv6x%j1zW|PbZpgXkI%C>OJ ztmH4CsEY{LsBjEKR&f5ykL~l}c(l7qJnlqQ!cw1q4Uc0YEbSKBc-)ePW4%GYxEzbS zAZegmuqTM)I>eMki$)BJ1>)UpZG5+c z6YT&$9rPVHr!id4V>_J4HYYN5=!!?`%p1|zLr{S2bv}j*2c5XvsXn8g{Wp62k!M5J z%q{WJQ?-I2UBQ5^;CxR0V1CDI`Zv>GPampZF<8H1sDAZe{pvII-qZOVr*k^CG@eh( z-cz-^>Nm;*>OS@9w8>kP=hCtt@je@WCa>;~X>})429~ZkQ}6y`nj2Cwa)&ZX2Qx}f zXH41>cR`=NbJaP$xmS5kZ|M!CoYSZ6ybj@}VU;3NyCoh%LPN&R)#vmXy(#;r?6(b+ z&mXYVozti8Y#fe}nY3GE5Ox~VdOEju4kgz3SOje@vQx!DQ{t-#>j|O5=dBX^ZS_oqc~!)@fbofVy-<#c3vg z!4A9bye8L?PF}SUN2=^qd$}W>{`}FEKWvrUM|Jzqy=-|rggd{bnlWR8Z7 z*01)Z=0q3CQ9p7V2MTj=QlP{&dis6`d6fQS|8qaA_5*ZfpICbb{je`%9-AYIfV(@0 z)q>P7HH*Dn-JR@BC2NE~ANZp>FkfZ^oyU9`ZGOxFFGlQo?8zUd!_&0uaZ!g&b)tuW zG27iWs(^N!0;37v(x7Of2Fri5fc!V@`%M*jhyKrRR{tpdLG(_|qc31q6O+ zfFsr7V6^?kYyA8zaEFAxOza}jL`mZ`gc6>1@Ke#w5`)4tdf`b^Dsw{1p|sd*xH7;5 zWTy<;19(Q}KHw)`(7^-iD=;(x8()Mh%=d?7o!+2uwK(Nf`s0J(_xtxBjF&9Nz|^iB zcL(z>3fh9ui!gxjM+A&s?XqypXxPVM!Z4=H1rU8JJAF?uCh`HTd;Xmo9Np|n2BN~RfDTIpfAq+I z4<@n;6)E@;IJ`zNVke+1xH$&vto)sWeA55;@6w3vEl7rH2`ty)7?1B(FAvu)5?s7l z_Q+{9f0lmm`%>~HHNWsT9VB(J)PmjY6+3j>t5WEg2~{yQ6lq4qOZnGvLkhp z7%6Qyk}iSWS;vvhM$F5ru{QXF_m#v;o@r2-Ef(Gj47yi)R8m@fGz$v!uRK~LQFyR@ z$LRgXCMSxsJlNgVhS#mr=(%G*Z^x0cUQ_RbJ1lRzwawqv<@UADu-X-uxn2GU)*PK) zkRH{S*P`aEo)56Hj{t%=xZtI5ahAP$yiTH(_9wo4gG!>6Q18IZq>0{^f07;=aA)xc zv680{ehVPBQ4Cu6L%;|-Sh#Zg87;kiP)Dlh*1^2#{L`5K8H4~>etC4w{5fR6MFp;t{GXBY zF2Z6Eu%7Ps+0^{s@EKPs{_k|lTV+X~pzKHSc|SrQ9e8V2=mIh?B49*wnQ6s6#|xP7 zA_5D|doc4+d}Oz}80PRV;Ug|){4u1^;{0)>wj=d2QfH8g#_{5n?@c5G%;*I(oQ7T2 zlgx^j_0KCP?Nx-=5MD<(0RZoiwB5r04?g|_A7f$JhNuYMc^88p<}2CKi_VHif5Fd< zL#=Si@X6G2CKEQRHD{WLy?@J@1T~J2-V5;V3O#dSzCPlY;E(}d7hvm4Ds8%GlH~>= zbgaKd#ry1 zi?A`omQyx1-bdb6l&}qfBHWL_h6?K-R?|bk`}WLWD=tf)3vz<$(W!x3W7_an2iefS z{jcv!L(D9XV^!>EgPpaY?65wv>1}tp176qY>k?W_k^w8g{}RcVn#I@D!`-%c#~uh?*#fL z@`;wjmVBb1x&N9iH^iETOQ`o>pOKM%_xmjpNd?@lePikg%1GAaKB@ScH*S=hM8|f; z-&~-!hX9_Uwhye}fa^cl8ILm@f*-N}=m#I<%q~Q$jP>WQA;YNaK><$#gj1HV!@G7T ze({UF{t_3yjsQdEDZ1#xqPZ0)Rms~?6q9E|>Ijx+<6}%bj+7?YP1RxAH3$xbdivsr zskL__$xhs4i>|A$6;I~SuuO}qyk*>nc@Q3>>BAl?dvo<8@%`A(ZZZIxfTR0I5f2?{L*V9q7_Ga_JwbLfhXQdQAk2f6;6 z9vCr$h2I!yo`n%dAH1d&?1uAI@%3nw3r3%d0u4C0m{INkibZemDlyD&Onj6s{U{gW z;yXSv^)_g^0yC`wxT<=5HCX>JU3jS&g3Wc8ex_*7gR1_qdl!BV z&u-$cM;)3`)*=Mvk8z!b1JGzF?TW0;%smD$+Y$uvbrtITa#n~nVmC6G+ZaTO`4hHw zFgLUfc;EyfPVjfZCArUw`)d9a=48^M5!KiQ&N#iKsAe>dVE2yjlObls`4VmptR&D* z(UYTt8CNpeBEA_Cg1B6Qk*(rKG_{=2rmynkhanWZ)W7blUrWd(YWdF{tW|xIIvzxG z@H*M$;}lhABT|kvF2Kiy0O9z^sV?4hx?Ipz*t=t+XHI05z(L0&9D;h;fyo!*>+lk} zg+0P?ks>S_x|=dq?mjHqcJ(wI&}mE@)g_f zB~2wP4xWQ$rJ@DLh9GDH))^gv*q9uAb1WSWs=e^*K9}p0o)SXRNktF*sxK!w8c~nE zS^}|U&s~H})_#H3i~$iFxpRrSO)CECWfT%pq52xi@NR4xj`->Y@Y`FbtW*k@e)7wH z(ZX&k;KzsT$5B_d0UB>9$or<}q=b}#+W(f2-DF|UPARcZ8yl+8fi-H8=!A%b=aGmR zM`75)EFy!8yKj0@WaRb*WvK9csOtOmkb)-&>LKgr_(rToUOs}ba|!$6E1tO^Bip2K z*$D!1V%oLh^1!Cj*f}e^P{jGfmtoFH2;~UTc8Jrk0(mzgsF)#;+KLp;)X}EmS)q8? z$2#9?xrWN0Pt!2G!8+@;9l7vMt^!Xkhx zOv0WIHKZWK3NOa2OAxL@XaTUtUwHz$FjE@>TPIk2HE~k%*JJ7p0Bi*xor5@|c!ZCk z@xf{^OV0rVnJxYx6!~@+GbLQrvfuZxAN%moyhsSGKxz)c3n-}&srM0Bu(u9)VP)jC z)gXT@)E-v)!4r*|cfi^-YYL>J>)>4qEQgq}B(NvVq6Kj1krukDF75^ItU%i!R{ z3o8g-dZrtRSz$!l)YEDtS@0suMkC2qFt=OU^RAH?dyW`M3Q6k;8%d!d9kXU2m=P=h z;dGWw-7R=KgS-_vRZ)fXB(vu>6PZb}dY&=Ca*>UtaxhP99lPU#cOLn?o)HsSMyB?_ zD+2d~63~os2u+xIDMA}U2Z9eFh_D9XHiV4`ELvcmpRKB_TX9B;P9Dc|SXtHOZ}0B( zRP)=wCQt(x9044b5<=eL68_Fv-{s1mxi*#DPx3!jNJ;X}l#e<1`b=#kwh<2Cvr31W txZuY&?CawtrXA@dJ>xbrr?3pk%>#1tmqF~7qf=5G2Kj3Sl_M|q{{c%tmYM(n delta 15330 zcma)j3w%>W_Wzx{)1+;ZrfJePP1AQu)0UQ&M|rfRg%nGH02NuFmNr7PZOY9p0zb6) z2H#~^11M7PiLxpd1^ul4Ku{h6ONIRvuIS?D=g)O@b*uOnAFKO6XKtF7iq$~AIrq%W znKLtI&YYP$J^Z%fwf%~iQ8Cd`9DGhKf5RVjpex2&RH)#%b=(rpOKLa)e`PhqLj>ZH z2{MmdkUOL_g`i-0l!CHrUhy1xjouS2M6*OiO^hd2i1io*LpZZhFhXW!jmZ-y#Cgnu znekOM@g9p{@gxWd>{(r7_1FZPgIgRmdrat{o|tKQvW8{{%?jX@A}4;iW^uAtJ647| zb6i-wU9fuz{u?8|f}bL!K($ddc8^1Fu)MmORF6||deVe6Pr8uq$q+IaUtg2y$r7?W z*+MpZj;^5(5LcY1P$=|_7DjuDgd)Z_*3e_bW%iT_WezT$b8_DJ1kP(&teU-wcKtn3 zAxv7Vq6V3nPEL+05~eJkGW#}3qoKHXl9L+~W{e>N8iS16BaO}zDi=@5CyOiFrVf-` zTseEg06q8%rS7F&ugB5Upjrhz7ZwuVnJ^$(n6^YIOoy)rzBTZju_Q&fW{FIwUF;gv zLzuZFR;XKI7G|+lS)o<7#jykZDa>ZvMBpY3;m%>)WZ>F|aJ|Uw^risYF@!yrWljaI za|m}H&t z&%L-b(z1n&n-AQA#kL6d4|KL7Gxp9hxt;S9SK&Xf!|QtfsJLE6N_xIkjU}YCCtq`d zka6@!ZEkW7B#Y|y#?>u>#^xXzVOCZuUjV&Dj6w>$f zS&;oDLl$*JkLqMHsvtw%E;V0`MW!K4NALiM#)gK*wzh!KDEJy~=xA$dXy9uwaR$OQ z07Dg=iA;n=@V%r4`0gI=9_~(#XZ2u86MQ2d8NHy=(Cs=SX@&+ksUoI*ayF!jsvuO@ z5fl~8jY6YKE-ID=0xdkYjENtic`wJLfv^J!$u-%3jmqg&n3%mxsZ&Ayh#)%+Y}cmZ4rejf7W z{x(6ySxFx@FLp&# z<(FZ(;kMC=OoWxx5+Cj|e2jG2t*px)j9*s3>|zO|LA|WYFy9C|WLPrEt#pRP@tfVn zgCHK^RwM?y3t16GZfA;oX}NjKFUD?UML>v{MgL{7U1@-~!)UsGQ?-?-5yI_A4625# zh-!B;)!MAB6WM6$U^E*Af1nM96l_e7CE4(etS3#dWs@LDr0H3+L zEWUu+90jU-k+GB3Io6U4`mQ6Z^FbYR8!i*iYGpE*G;|aEJqU9V9zb{q;X#Cr2wey$ zk>7x@2_b|)5jG>N2dHyh)lu_XA+2VFiQkC=5Kwy12mro$9d}t~QdW_E4pDM>n5U`B z7*F+1BiY`Q;tUd!O*f`BOmRU<-7cjxP;81Zp*CPMFY)V!4mm_SttgMvMHZ8WCum)I z8?1M4q&Ii&L=jIQJc;lW!qW&l0P0*s)HLuNZ4Hg>?Jlh-Z)yqh+cC9!APfI%0)kq| zk~Q#QYoMrV;(f53i3#&M+S+_A&E5{$oe@KJ(ibyIL7z`DZtP_9_;*&pKLb^a*mV9` z$nhF{gJ=uiL8o&Ib;$${~ z{%DDg;gTHvu@Y_I?9P6pM4Pyjw6EeA%@c}}P2gHIEru2naMx$Sa}+)Yvb?$kE|Bfj1Lh=g z^=>Thjb>@1S-BWS=QBE%(FKe)FdFM4UL&J38ErzkJ&MtBH?jvEdoVLv&uECGAyYJ? zEsTy~bONJey%3X1P4U{0(_#qZvUG?_LFv3C&O6(i1aTb1qpV4zk~n%XZxpf9(>deq z5RTT5g8VYCLn_@u24e7b1Lm`(4n)jeC+3AFKv;_P&}3Ihe0n4m!qTv$Oxo-!%g&0V zWJgjU9L1868sa2fvR#q95SyZu`q9O5@2Fw9&7e;L?aVdD=SA|nBeL`9g4}GneOjEw zTM$Vugk%>eE%T11&*hfI7e!KvS;2fr8AE@7l#)nFDN8AUl(BTisIu&Fk(BX}lFOt` z7^a#{YDjunB=1DXi{m|MSk6T3)VRq5`B33Y#gUyN}!OIyDQ=fLDF=5j`Vm6J)J)}#_LIlY_-}Q^rE{Wx@HI` zRmwDD2+N7`;pzdp~keCLUr#LgAn{(^)>%rjb3t{v$-r6g2rPIQK>iC&g@G`vW zbj~%09@t@_-36mE*(xaqhsPUWj=Bho=h@EmhN1fN_o!7g+50*gE9aAdpV;}ObAl!#+Ww6EPYZsM1o)7SRJhJ31zBW;Pb0gp8Z(GI`5Y>&% z&AcxdWQ&}rZVJF!)g*}8>Qzm?c8r`{S{4uRZ=m2eQHr`z5PYref*3_}Cnn}$$RNu3 zV6zmsgjd2;{7Ab&QN7G3GzcASVlJ%B>6-AuTXko`^n{;@+V zL)Ar9M>`xH_&@=yOjJvc!6Az0q2hnKv7BtCUjm}yEtx~*Fr}6Lwr1Zd{>s`9+%%0m zM%69ZlCn~%u(DVRg2BRHHu0qvFWFAzt*CQm>kB!<>tg!>mrNx7!M?$XN`GvvgWz;V z+x;_`|6-ntNCEc5Qw7}Z_%oca$NEN?~AH9c{^86|5IhBmjXum*3LL>n>s9m*0noAvkgQ; z|JegNFK7=;+rw&7lWnAmP!W4;qgdMY4LMs>w2#{8!a>Dx9@>+mkS#R#k|Zq*mXOj<8aa624$2YDCW`$C-S0p}a+-1`35c5RyU{ z4Ma!34W_}lo=q4}3RqvX3fB&AzN{^l{wO373niZ<)8dW~NFg<@w3A{w=j}LJxw465 z)2CND=;R9;`7}PB9$h)QGjVWv0Y?ZEbO$IP?HSQMh{_c~{~DjPv9zF7CLmZ5&}Z;A zphU%u0e_pc?_{oP6P8po2ikl*9{r0kQybfwd@U`$=4!TRF$e0z;?q(w9YOqb6bvNeCJQIeEax~BT1w-PH`kT8|b6*#9FKJsGZbWG)GeuNT*fkAmNys z)Q-{|%SS=SI!9Y)8kNVjq}HuJ9wkRQ+FV<#J?nYr4N31nts%t>n$ z(&;k9Obw>Gb#+PdljE#)7WpZI3Y0r#Q3B^wk{swJ7%KLOK}2_fj^)zUp6In%3Q|H} z=uE;XK$os7oxGHQYv9s3uW^8K#Yv|j_duDU$13;;6E%F^5dcbzqt2ALPU(1r86vt< z>7WFUN2W=3jn-Mm7I9aCWbW*=<+tTavw*FdwsX|owD}oIJ0R@W(L>3seHMaVs zKu--p@JfG^j|ZQKz8=r}hPmudZks%_7itJ<(3;nA{W6m|=A0p|yXu+R9kqQK6}=f1 z{c_-4G&s7=&m`|i?sJy)I?MW%!0T6W#-z`aY|x*XrtzMs5se%;=u?%%UBO`ZA~XW=`#o zMXrG}+BRow&)bsMm+0JTmXM|7N+v9D6YpFr#rsR>n|Inx;duWnj+GA~kA88dEy)e!@Ld%P@;@ci zc^8HSOYd4cbv7Ey9Q0rqhYP+o0pcVtGUp;pL3!l>5N1; zL`9+E2k4BAg?4oDybSw8EAWj?zNL*#H-TW*?D;gb@rKS}0XLQ&5_Rvu$3@t6-AJK^ z(qeQ!QWk_Hgxvt5PCBk@Ky^eT(-`+GpfO|dHq0cSH+43jj%7>;4)jmFnUM&!Sl6pa z*%4AGrA|Lvts=cTIEtdSp`odzF&Kn*UDcTVdTeRb4FSHjQE2e3Y7*tJQ*xOl=XD*5 zUWlN;Z2FF7e{)#BpkxgXQ2Qpgj;XK&^PQseH@Wkxk!0&=JyI1=Q4ke|vr1`$5ka;- zzO<=Q_9f~0W|L9nE`tKQWP=7cl0j6`?rrgO`lHccb@LxRuiu9aLL;5WkEIhHbC3_| z!pAa_nc1QuEXKyL6Z2taaTMDoyz!WZRM2-G+na^8^D6;HstB zB7`eC1~pjmcw#=Yl`0e%o<4&e!Zt0~e$?i6lv4=WWYdd}Yl(+`_IM9_n;;DYn=o?e za5^%oBk4Gsp5N{Q9dCQuMw7eO-_QW^d8VwYiG@3o+;C@s20y4JyG#8cI#mjYe9fu( z`T1b_tPS^I8x(`3_`!zYnwcf-8KZVoky&)sj>EsNCl}E1JE13g7VX?m+>24g#W*Tz zHW6TJI~El)&Gol-w6GT{sRIdb(~2k4Ji~U3n; zpf&D7o`2FyuI>5x$z&q+Im;gI^Q9>3clG%VwEpSbHts9w z>3#ZM5(B{o#OLyA;?n{}yJn`+4bK{7b9Hpbf6VmF>1KLhu~~Jk>Dn>H3cBREL_YW=~$4Re?$<(0^dT7gFv4K4nlz#jsJOD{Q1ram?> z+&-2!?EL+$K2Za58w82+-Av#3Q&RHaY|}&TK`+Y8_s2iYNbx~17JM62X8Vh8R9tkN zw8NNuRzKweZF*sUrzJ7e($&&u$?3J^^jY$HEqTx0a6o(7J?D(Y+h_2eHh9m)SvOZ~ znsCACdM0^i^6up?w(M#-<1FiQR`)uq&p4-d&G^`ny?yQ0wYxnp*6yl3;~4+2=bR&Z zw`+g(+1$#%Ix6AO>FRS9_d1KuI7=R$anYQ3&YIetbGN9n%;fgIjf^P9wD(` zr?Ol6|R55pCP~3kpJv0XAP4sn!&_hOq$rGfJo5h?9P66 z{OQt}rxWTPR-BJ>J)3bRuHdx3;H%M`aW46u9SYo0l{tMfIh;eLPgERsO*K!SOv_$c z`ujZ6{q)dF=-bO*vC;p$6pDWkdbBR=m6(@jcJ}<9#EgjZ8Sw0Q?!>$z3ydUx_>Vmv zavv>v`Sssd`zE%aW%uBMR{qM3bbF7zidndd9j}EQqH%~r4D+j??g2fRslmiiZ?0w<)sqrYNVUFFc?#mh1n=|e!PN6M?ptJmy_0uHx3`e&etygo;0|~!S$8?1{ zH0ISAWGlVy)rsVF`pB!xexFu5Y5G1KnrZt!AZzGTD^ry}#KQ?RB~QlbaqWKy(rC19N?1CLFf;1neL1}5^aN0s_!e-Ue`|HRK`v3L^$Umv?wKc^U z8$cg3-+(StRN%xOX%XH{O|Qcu+%xg@XxUs030;~Yr?u~)y$lAA=pdwMu^T9tLJTj+ zL7qinCo#DKKw7uhX(xT`&z0o)p1=LMOrE$G(+?sHlWU=44y2W_c#w4px<;3ZKLP3t zksQEB7PhfERFH|qXt+9d9VjPn^t^XqJ^6hD^%uJCEgUGvVH^GPTccdKimx$v;D{xV zms*qEnhH*NrH@A=;cta{BVide92!GT(dt88zptXRP?4D$4#%*{JQ;ZE$i!YT6=q@; z=&z+=z3*@xIYY(6?^mEP^Zx{Z80Sm)5oJQ44vOwTehA?QOqBCeP%es*1^{JcF%$Xr zZ4VK9ro59xa`r%SIIF1kwXO8?fj0gkFj>{?C>bRlqdSi0Xu|uUztNYEd>qG05>`p% zmBOy0lVzXC=s%7=V#8zzh?x!;mq-W2xa-<{Y?|z!GJ5e?GH$+pK6ZZs=H)-bN?|pC zXj_Uc$3mS2vVIwT?|2&I?D_V1o=nw@+U%n_Cre_bNgeEHYQk4CV`@`WHb{>s%J*XutLG!E?Ee6QXjr%jVBPnJQ`2OZWj#-v zTA-8l6Z+HnGQ*XtA|Ik2v9TD&2gSf$3AQN1{!Nx&$j;xScBZm+irR_Gd~Lo}?fhiX z!{Yic#Y}hsXnsFyESog9r=${oGb(ju{J?KT9$G2?C{m9hU>LE6zP;N)SG@18$L`>_ zA)keY7|`=CBD@43Dw!ANUq!|~WH9dnM=Eb1=S>7J3Ydh{Aq4bS{9$UnP-yrRQy#+S zE`*JA!G%ekBX+X14R{=LZ%5d{Bmlq}X-BK{76@&W--)RTq@F-ZiPV!wVI0HnKdWAPsmnTiOs^KdL^n1y@g2VA2I;!sGbsF7gr^a90f=(gN%I(@@bBQG zHt27Kpa-r(@~OyV>nC%?F=l+mHG@q!HjQ{K-S&YKj+g)YgE|t^6Z2t=9&IM&vtlxb zdOxc+hnFVUJmYId=5rG1iO;Nx%pm_ZJ@Q!!*+)P5tXQ=g#IAAB^3Uyb;^%3kiZ*<1 zBX`i%pHCq7(7m7M8kkFNz{=MnY@lC$KIb z?7&o36Wdt*V%jiQ%+f0$XHY*dZ&J}AAOEL^H1@=O@wvPcb`M|$2;B(m_yc8&=J0m3 z87>z28wTFD;?*cofp?AgKO(8Fm^;0u0-n&_!P!7#a2aHV=QMbh)+!8xnWmzmJrES& z)$U3>BPwD;(>bJW)+DCD6Il33gr^XmMtBV&6J_NfxDiGp;0}Nvhk)LXZOhqin7dxPWjBYx)GK&k@+XzJwG3r^u@in7xUZ?;`~COhe3% z1yIc0{{<5;l#+}Q-MO@P#7*7}aGF8y`r1jK`8rxJd0M=uCfdOF*mAJF!NoB7{`1p1 z?i&jiWxp(g`wIPdY2lI_9{bs&fyypDFIz67uV0#?G;6K>dGzN?mq~xmx8KyuWJMg^ z^Mf^{i6LnvyW~7ORl{v*owUl0bmx}c%X);qaXA$h#}6-Wj>bt28+gf9=pEl>q();` z4BE~X)EHXGg_n)z=#lSI$RqTV@ABAHY6xC*Sb`OcLW-IF4k$51%1(H!bH!ZkHm|5| zXlRCut_^VPg-1}WK4E#Fncdry4!RU`=GRPR2V?BUAO9SRt^r8B`jzZ$n2xZW?)l!A z%5H>xE!{i2yv9P54dh!6QM&uLjh6l(sD6g*{w?&SAIhZ*t*HJk!jynz#*Gs50ocM4 zxmXSiF@qkMm*BqQ4)a#=#1jrXOcd!k}aq+~o*%z`>`~ zF02!Vq5^)T7f8t9{g9EZQ(#vx%aOH}_Wzg(k+S9ADeQ(qsMbuX6mUy1o38tJ-WX;I z5F0dLULU}SE#OmO%pRu~|Lum5H0Gxbs@Zy|@$Xx=;ODg|b^K&_9P$nWmt5w(;b1ay z0@D!o*ywGgWppl57)9}4g#IoklR8;rHX?(qD~FL{3l8%n58>mz0I*0xND*j-_t$NH zJb&a5U=kCD-iQTX(ke7;z4;bXBOwQS-3QmlE0CKl@N$X zVMbAdmsZ8-@X0H_YiwwMVZ#9*G`8|`NdYua9$`ypn}%eo(bd3n=v@tYLUkD){O7@X zC%O0(Y^4KW#8DS>@Y+an-q4#=;??aob!^yfLupZ@E*}k-Peb`~sdKU2l9fVv=(qtR zg$FT%)sofF;BWH_FvGEiI7on*aX}NkLEWpdp4B9tOGtT8&liOnbtJ>WP>PaCf3T@Prt;8L2M-$h$L4%H%-$3Wg!iL~Y=y1FrcA{vjqJwpg0$#j* z9C{&|+~PK&me};)*5gxoXh94~CPn)?V#r2$XFQGy&Q!RE%?5-`txZ@vJNhigbevzj z1*rrCD?&tLX=Kx79-1-B|U>F5wiC#QQKQmqGvvHy$O)Az$=&;Ul_Z7Ov-0pT>#QOD+`y?@R<9+i&oZjlGU{STrY3dq@mxaQ zB~F)My3FZ}rhbHPtRbJ8jO|o$mN8$aM%fa-ws5ur(NO%A22#Hdu7R<+y|lKB>?gIk ziq|TlfP701FYFa>DJo6ya5y0bF>9(0evZIv`a;!_B0JDWW8sau>Zl zo~Q|5ISlfnp(arifc3XgfLn str: + payload = { + "ts": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"), + "level": record.levelname, + "logger": record.name, + "message": record.getMessage(), + } + if record.exc_info: + payload["exc_info"] = self.formatException(record.exc_info) + return json.dumps(payload, ensure_ascii=True) + + +handler = logging.StreamHandler() +handler.setFormatter(JsonFormatter()) +root_logger = logging.getLogger() +root_logger.handlers = [handler] +root_logger.setLevel(logging.INFO) +logger = logging.getLogger("ios-api") class FindMyMonitor: - def __init__(self, username, password, queue: asyncio.Queue, token_file="icloud_token.txt"): - self.username = username - self.password = password + def __init__(self, queue: asyncio.Queue, token_file="icloud_token.txt"): + self.username = os.getenv("APPLE_ID") + self.password = os.getenv("APPLE_PW") self.token_file = token_file self.queue = queue self.api = None @@ -72,6 +94,7 @@ class FindMyMonitor: self.start() while self.running: + logger.info("Starting iCloud FMF loop") try: lat, lon, ts = await self.get_location() print(f"[{ts}] Location: {lat}, {lon}") diff --git a/pyproject.toml b/pyproject.toml index ef070ca..b5f9d44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,9 @@ requires-python = ">=3.14" dependencies = [ "fastapi==0.135.1", "pydantic==2.12.5", + "pyicloud>=2.4.1", "pymobiledevice3==9.0.0", + "python-dotenv>=1.2.2", "python-socketio==5.16.1", "typing==3.10.0.0", "uvicorn==0.41.0", diff --git a/server.py b/server.py index 8c1a669..9d92e69 100644 --- a/server.py +++ b/server.py @@ -157,12 +157,14 @@ class LocationSimulationState: self.queue_data: Dict = {} self.queue_status: Optional[asyncio.Event] = asyncio.Event() self.queue_state: str = "STOPPED" - self.test_mode: bool = False + self.test_mode: bool = True self.simulation_task: Optional[asyncio.Task] = None self.sio: socketio.AsyncServer = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*") self.tunnel: Optional[RemoteServiceDiscoveryService] = None self.fmf_queue: asyncio.Queue = asyncio.Queue self.fmf_location: Optional[iCloudLocationData] = None + self.icloud_monitor = FindMyMonitor(self.fmf_queue) + self.icloud_monitor_task = None class TunneldRunnerSio: @@ -205,6 +207,7 @@ class TunneldRunnerSio: @asynccontextmanager async def lifespan(app: FastAPI): self._tunneld_core.start() + await start_icloud_monitor() yield logger.info("Closing tunneld tasks...") await empty_simulation_queue() @@ -384,16 +387,17 @@ class TunneldRunnerSio: - async def start_icloud_monitor() + async def start_icloud_monitor(): """Start Apple iCloud Find My Monitor to retreive actual reported device location""" - monitor = FindMyMonitor(apple_id, apple_pw, self.context.fmf_queue) - monitor_task = asyncio.create_task(monitor.run_monitor(interval=30)) + self.context.icloud_monitor_task = asyncio.create_task(self.context.icloud_monitor.run_monitor(interval=30)) while True: updated_location = await self.context.fmf_queue.get() - if self.context.fmf_location !== updated_location: + if self.context.fmf_location != updated_location: self.context.fmf_location = update_location self.context.sio.emit("fmf_update", updated_location, namespace="/",) + async def end_icloud_monitor(): + self.context.icloud_monitor.end() async def pause_simulation_queue(): """Pauses asyncio.Queue playback""" @@ -444,7 +448,7 @@ class TunneldRunnerSio: async def end_simulation_queue() -> bool: """Ends asyncio.Queue playback and closes tunnel""" - logger.info("End location simulation request from %s", sid) + logger.info("End location simulation request") try: if self.context.test_mode: q = self.context.queue @@ -503,6 +507,7 @@ class TunneldRunnerSio: "test_mode": self.context.test_mode, "simulation_task": self.context.simulation_task.get_name() if self.context.simulation_task else None, "tunnel": self.context.tunnel.service.address[0] if self.context.tunnel else None, + "fmf_location": self.context.fmf_location, } return data @@ -636,6 +641,7 @@ class TunneldRunnerSio: 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) @@ -644,28 +650,27 @@ class TunneldRunnerSio: 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) - await self.context.queue.put((loc_id, latitude, longitude, delay)) - if delay == 0: - start_time = datetime.now(timezone.utc).isoformat() - else: - now_time = datetime.now(timezone.utc) - new_time = now_time + timedelta(seconds=delay) - start_time = new_time.isoformat() + accrued_delay = 0 + if self.context.queue_data: + accrued_delay = sum(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": loc_id, - "latitude": latitude, - "longitude": longitude, - "delay": delay, - "start": start_time - } + "loc_id": loc_id, + "latitude": latitude, + "longitude": longitude, + "delay": delay, + "start": start_time } - self.context.queue_list.append(location_item) 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) @@ -1040,8 +1045,9 @@ class LocationSimulationTestQueue(LocationSimulationBase): async def play_queue(self, disable_sleep: bool = False, timing_randomness_range: int = 0) -> None: while True: - while self.context.queue_state == "PAUSED": + 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() @@ -1051,6 +1057,7 @@ class LocationSimulationTestQueue(LocationSimulationBase): 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_time") if self.context.set_location_enabled: if delay > 0 and not disable_sleep: @@ -1075,9 +1082,7 @@ class LocationSimulationTestQueue(LocationSimulationBase): await self.set(latitude, longitude) self.context.latitude = latitude self.context.longitude = longitude - self.context.loc_id = loc_id await self.context.sio.emit( - "simulation_status", { "status": self.context.simulation_active, diff --git a/uv.lock b/uv.lock index 00b54ba..4064283 100644 --- a/uv.lock +++ b/uv.lock @@ -95,7 +95,9 @@ source = { virtual = "." } dependencies = [ { name = "fastapi" }, { name = "pydantic" }, + { name = "pyicloud" }, { name = "pymobiledevice3" }, + { name = "python-dotenv" }, { name = "python-socketio" }, { name = "typing" }, { name = "uvicorn" }, @@ -105,7 +107,9 @@ dependencies = [ requires-dist = [ { name = "fastapi", specifier = "==0.135.1" }, { name = "pydantic", specifier = "==2.12.5" }, + { name = "pyicloud", specifier = ">=2.4.1" }, { name = "pymobiledevice3", specifier = "==9.0.0" }, + { name = "python-dotenv", specifier = ">=1.2.2" }, { name = "python-socketio", specifier = "==5.16.1" }, { name = "typing", specifier = "==3.10.0.0" }, { name = "uvicorn", specifier = "==0.41.0" }, @@ -412,6 +416,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" }, ] +[[package]] +name = "fido2" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/3c/c65377e48c144afca6b02c69f10c0fe936db556096a4e2c9798e2aa72db6/fido2-2.1.1.tar.gz", hash = "sha256:f1379f845870cc7fc64c7f07323c3ce41e8c96c37054e79e0acd5630b3fec5ac", size = 4455940, upload-time = "2026-01-19T11:08:34.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/ab/d0fa89cc4b982800dd88daa799612f11642bf9393851715d9eaeba3cfcac/fido2-2.1.1-py3-none-any.whl", hash = "sha256:f85c16c8084abf6530b6c6ec3a0cf8575943321842e06916686943a8b784182c", size = 226945, upload-time = "2026-01-19T11:08:29.675Z" }, +] + [[package]] name = "gpxpy" version = "1.6.2" @@ -541,6 +557,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, ] +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, +] + [[package]] name = "jedi" version = "0.19.2" @@ -553,6 +602,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, ] +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + [[package]] name = "jinxed" version = "1.3.0" @@ -565,6 +623,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/e3/0e0014d6ab159d48189e92044ace13b1e1fe9aa3024ba9f4e8cf172aa7c2/jinxed-1.3.0-py2.py3-none-any.whl", hash = "sha256:b993189f39dc2d7504d802152671535b06d380b26d78070559551cbf92df4fc5", size = 33085, upload-time = "2024-07-31T22:39:17.426Z" }, ] +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "keyrings-alt" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/7b/e3bf53326e0753bee11813337b1391179582ba5c6851b13e0d9502d15a50/keyrings_alt-5.0.2.tar.gz", hash = "sha256:8f097ebe9dc8b185106502b8cdb066c926d2180e13b4689fd4771a3eab7d69fb", size = 29229, upload-time = "2024-08-14T01:09:28.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/0d/9c59313ab43d0858a9a665e80763bd830dc78d5f379afc3815e123c486c2/keyrings.alt-5.0.2-py3-none-any.whl", hash = "sha256:6be74693192f3f37bbb752bfac9b86e6177076b17d2ac12a390f1d6abff8ac7c", size = 17930, upload-time = "2024-08-14T01:09:26.785Z" }, +] + [[package]] name = "la-panic" version = "0.5.0" @@ -631,6 +719,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + [[package]] name = "opack2" version = "0.0.1" @@ -897,6 +994,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/17/58ee1f079114ea360601ccd331fd73032701abfe9278495e3dec0c35ef3f/pygnuutils-0.1.1-py3-none-any.whl", hash = "sha256:3b690540cc13f2c763250ee5cc647e9c81055d1002b1bcf7ac07ea6d259a21c5", size = 46351, upload-time = "2023-05-12T12:56:58.211Z" }, ] +[[package]] +name = "pyicloud" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "click" }, + { name = "fido2" }, + { name = "keyring" }, + { name = "keyrings-alt" }, + { name = "requests" }, + { name = "srp" }, + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/f0/48aeab9d92690b6a7cf31200159c369441cdae1ac9d93d6bf69c096bf001/pyicloud-2.4.1.tar.gz", hash = "sha256:9c13bc46e08cabd87c4d4418133b5a303f30c3ef0478b6d1b13aa74c2e5334f6", size = 141404, upload-time = "2026-02-21T17:08:18.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/d2/875b873d54d3bc7dad37b50fd255b357f8adaa105af5bb2b02443cafc783/pyicloud-2.4.1-py3-none-any.whl", hash = "sha256:a767ada7cc2961428f8c2d0ce327102ae7666e3835610945409247fcf9d85e68", size = 66974, upload-time = "2026-02-21T17:08:16.196Z" }, +] + [[package]] name = "pyimg4" version = "0.8.8" @@ -1009,6 +1125,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + [[package]] name = "python-engineio" version = "4.13.1" @@ -1071,6 +1196,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + [[package]] name = "qh3" version = "1.6.0" @@ -1168,6 +1302,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/b6/049c75d399ccf6e25abea0652b85bf7e7e101e0300aa9c1d284ad7061c0b/runs-1.3.0-py3-none-any.whl", hash = "sha256:e71a551cfa8da9ef882cac1d5a108bda78c9edee5b8d87e37c1003da5b6a7bed", size = 6406, upload-time = "2026-02-03T15:59:59.96Z" }, ] +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + [[package]] name = "setuptools" version = "82.0.0" @@ -1207,6 +1354,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "srp" +version = "1.0.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/fb/9210875dd162d3977580407b1c5ce6e779e770b8197a0de76819144a9755/srp-1.0.22.tar.gz", hash = "sha256:f330d0ec7387e2ac8577487b164963155d4a031bca6e2024f1b0930eb92baa5d", size = 22472, upload-time = "2024-11-01T21:52:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/75/5352c3ebd26e7d119042ae8de07354435a19c77fa2b44058fa97a1416783/srp-1.0.22-py3-none-any.whl", hash = "sha256:35aa8af053285a35683eb37182dcb2e46dbd85c7075d28e139f200d6bf16ea43", size = 25347, upload-time = "2024-11-01T21:52:53.021Z" }, +] + [[package]] name = "srptools" version = "1.0.1" @@ -1353,6 +1512,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + [[package]] name = "urllib3" version = "2.6.3"