From 84abb27f183c2c87e1e5f7d36f8d2d59dc33b598 Mon Sep 17 00:00:00 2001 From: danogentili Date: Mon, 26 Dec 2016 20:23:46 +0300 Subject: [PATCH] Killed fread with fire, improved peer handling, written basic update handling features, written static class for serializing and deserializing MadelineProto easily, fixed lots of bugs, more stuff kek --- .gitignore | 1 + README.md | 11 +- docs/index.md | 11 +- enc.tar.xz.enc | Bin 32848 -> 33840 bytes src/danog/MadelineProto/API.php | 7 + src/danog/MadelineProto/Connection.php | 17 +- src/danog/MadelineProto/MTProto.php | 89 +++---- .../MTProtoTools/CallHandler.php | 6 +- .../MTProtoTools/MessageHandler.php | 10 +- .../MTProtoTools/PeerHandler.php | 126 +++++++++- .../MTProtoTools/ResponseHandler.php | 8 - .../MTProtoTools/UpdateHandler.php | 234 ++++++++++++++++-- src/danog/MadelineProto/Serialization.php | 31 +++ src/danog/MadelineProto/TL/TL.php | 42 ++-- src/danog/MadelineProto/Tools.php | 2 +- src/danog/MadelineProto/Wrappers/Login.php | 17 +- .../MadelineProto/Wrappers/PeerHandler.php | 53 +--- .../Wrappers/SettingsManager.php | 25 ++ testing.php | 35 +-- 19 files changed, 537 insertions(+), 188 deletions(-) create mode 100644 src/danog/MadelineProto/Serialization.php create mode 100644 src/danog/MadelineProto/Wrappers/SettingsManager.php diff --git a/.gitignore b/.gitignore index c83e06eb..2bc60f1f 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,4 @@ session.mad *.madeline enc.tar.xz a +web_data.php diff --git a/README.md b/README.md index de142967..e80892e2 100644 --- a/README.md +++ b/README.md @@ -215,14 +215,15 @@ var_dump($authorization); var_dump($MadelineProto->resolve_username('@Palmas2012')); // Always use this method to resolve usernames, but you won't need to call this to get info about peers, as get_peer and get_input_peer will call it for you if needed -$mention = $MadelineProto->get_peer('@veetaw'); // Returns an object of type User or Chat -$mention = $MadelineProto->constructor2inputpeer($mention); // Converts an object of type User or Chat to an object of type inputPeer - $message = "I've installed MadelineProto!"; + +$mention = $MadelineProto->get_info('@@NonSonoGioTech'); // Returns the following array: ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id] +$mention = $mention['inputType']; // Selects only the inputType object + foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) { - $peer = $MadelineProto->get_input_peer($peer); + $peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object $sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]); - var_dump($sentMessage); + \danog\MadelineProto\Logger::log($sentMessage); } // The above works with bots too diff --git a/docs/index.md b/docs/index.md index eb478f79..c4f47217 100644 --- a/docs/index.md +++ b/docs/index.md @@ -219,14 +219,15 @@ var_dump($authorization); var_dump($MadelineProto->resolve_username('@Palmas2012')); // Always use this method to resolve usernames, but you won't need to call this to get info about peers, as get_peer and get_input_peer will call it for you if needed -$mention = $MadelineProto->get_peer('@veetaw'); // Returns an object of type User or Chat -$mention = $MadelineProto->constructor2inputpeer($mention); // Converts an object of type User or Chat to an object of type inputPeer - $message = "I've installed MadelineProto!"; + +$mention = $MadelineProto->get_info('@@NonSonoGioTech'); // Returns the following array: ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id] +$mention = $mention['inputType']; // Selects only the inputType object + foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) { - $peer = $MadelineProto->get_input_peer($peer); + $peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object $sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]); - var_dump($sentMessage); + \danog\MadelineProto\Logger::log($sentMessage); } // The above works with bots too diff --git a/enc.tar.xz.enc b/enc.tar.xz.enc index 9ce6d889d22fc1302dbbb618aced32a34b0db05a..21db546c56a9210488e07b5e9a6a0590550f029f 100644 GIT binary patch literal 33840 zcmV(nK=Qx3q~uDAc$gtd!=|tIX$3Ld+WDj)rOm4;qH?L)><+cCM@#m_dhNj}!Pydd zPF}nUz~Xze3VYzl$U+hN&s}RjL@dq?EIuw>5$MnTvfk2IKTH3qY9?NsDPHCIH_D{_Ct={P5B}&=ix0y8 zYcpUq9icFKT&2G`)D2>M0;+%Ks__P^1CbOh6N_7IX7Y%Op6)2@<)z=}0j`ZYjq$e3 z8#K0mn`9&XHcolA?7Wt}cNT)Ct;C+JqbLnYnfWitTd7Ovwrp+49P{F)%7JLtfm4vi zigwV3ES?YC!p8^o1`gIN1avyVEV#}$+jpD}oW8J@ac2^o`>nquCL0{M^0wsiQLg~{ zt-6^nG?wx$GYRcIz)wdxj)4WFr6g$tGEN6=(#hsY4~e5qncbc|$*NVDu#;E)mcS}F z)3~Lhto78IAo_*Y*Y5-GLBVU9uvRt-jPYQ{&OWb9Qh^yu?T>PEHMe$gLm&2P6m#Z{ zlUoIC*n`q`UdTo{kjSD z_P>EX9F8;}@(S9m{MN9+xh8SbWq{~l<&X;sG@(QEa6%PeN##bKB&vrKih^}aM&r83 zf@W{P`X@*8y$mnMv>JXo6BE3m&=9B7fL>bP<;UKbea9TQ4@aLj`aF|I;uHZ<-&o9T z_7XhNpU@6CuqqP5QpzmPT;_gG>S028#U)2)6hB zpUEuKhDTuWraO;fKBKidHE>(hTq=6^a`J%%zD+-0fck>Nq-=T!{}*iaSVCbECa-Q6oWN zOkyO8q65DPOHcY+;-Qkg#o>1+o?^5D5Ahcu!{ZsH9uYdLM*(vl^w9*T&I-SSa-4J>7r zEsuk!Ozr11s`OpxP5iN5lb_gjl<`f%cnO&BX7wMS;q12M?z6S5-t;6U3^Zu0XQo(w z(za^FHfHM%OW?UE4_?Z~RXe?qAxf6s4LYA&Gs02Cr!}dw&rA51 z&D4{82EJ2fWkhtr2xvrXd8eau{sRpZD#2Npal`3n4B^_LiYqdoDW;X*g+QcHV4LP; zai&OQj|thdfOae}D8OA09dfdUA zH7b^@X&WY-llWB%UKkn}ONLob8s=YzyyBIIM&lYR4Ylk_w>oA0_h#ct2av5bd|8Z- zZE;UWq7Rrx0GsbRSA_yPxVMnR4=AH1&FT!9Ux%WxmoSv^d8uLR!%&_$;|GT$Jz4>) zstpcCMNc2IU0W7L?E{y{sH~dQX zHF>&>Fgj>WFb#3AREJ*>`J*2D5J($1Tugdc;!oVI9Bw3z@m9H1XgTIV3VIKQ=;ON9 z5m>*?)QDeEsobK8ZJbQID6@I8k`9z9-|CfWnsn!v6}0{hVM&YRr?%Jfewzjr(@=!i zAcL9%;&!O)#829g*y)gT$MSw$#sTEp$tANUbx9Vy{5E&`i1owKn&dDGjTMT!mKX=j z#=cxR38@z*&u#v>hoK5p}Ynl zWyW(iSt(Ie=iP}Ou3zOW;~!^J@0dq5^yZaApD$Zcf&K=!XR zCc!2@s6y5pUJQ5k%%t5tV4x1`9-8F2aY-Pv$F>jIAsT&_x}fevuP$_@RuH`X%3_^{ ztI7XEP5}$Z;M=de`X&%1gx_&wu|_*TemR4(4exxmq|zhAvs0sO0!2E$@*xOlJK->O z6rq>HOILWL$HjOd#`#qy64<(>%pB| zohc1Ze8~O!$lI642m0C_fc|YyDSQ%cQ)U#wN6d3pcWLFoF9oExzNmgZ;0Z^%D9Z!!|3o zIwzm|njt29t0n>96N7KoSF+#`X5dJmC86M}h@mdq6ZDBcV3$6((!9kgKwXR9$VaoP zYXL+bEMQ$#dH!ai{@dI}M;)Q-lu8*;LcwM5G3&yNA}pc!p@{XpU;Wm69$*u*BZrtk zMG|&&Oac%H=@9^#djQN^y8;cdww)o>{q=|{0Xh@Z_}=rf@pb)?U5AHaa=2Hvw%gV9 zzR?GxLpcHPv%=3_A0a!0f9|Q@`v@IZI8T0(m%g$F3BvMv*hVvdR*CQKYnx>C)O_26 zala1pcCw%i7GmqVS??DnZczV%z|6NB0i!GyY_z>h>LZsF>8n;BVTpdU_dXpn^ZK)) zYOdza3qr(O6HlP?)WQO@x)Sg(5+ktai$0ivgS(pkGN}_<)qtl3Bg5F~W3~|1&ZPn> z#O4q`HjVt1LHL1IIvO;4p|xf+#G9_gkVjhikY^SR-eaDgMc11oj=WlcwBDdoAn5wT z9u5Kxx{08f$zag{a_wsWo}#n%R7XbYBwgn0vNe<^)o^o-CNUa#yPiONvKQEKGd%n0 zQa8=4umd*B7+zwaqIvi|`ALunzo*!y8AHTuFwNtwp!OL8$El74!hA(>_*7Lbb2B+h z##<}$PF1qYq(c8?=}_?GGWl3EZ%Hka0hIQzj|BI)qcP`CO?F6)jCx6!|K!nlIT0eS zg2fpW^tb6;+8KRo>RHsk=U+4!0+nM`{JcR2&kXE_#x|cxs~sWjuEP1=*;4tBZ-VlJ z0R}!ePMToosQ5Pj1rIn9x@9MfYAU3izM(SfAU>D{2q=`Nf|JhUwQ`jR!ESp3R`nNp zj9-09%jT=ux;N$jCr1}K?&Ev!*7)`G%XLlCkNi z&ng*f%Ui9fs?1nOsJPJrlg>+MxJZ~ednqY>ifL%<{JEm)uxO}@Tq|lSjOsMw6*d{y zX%V<8N}1q_Qeb<)v3Wyv_MG}YmIOB>Vp;tfn0;#NXyHCdJ)ewBddBFvzGDybKYaBa zEl^z#REq7*a(=G0ts9aO9`6)?jIeb7%SLQYe8(M>f^I5~e%~B`U0JLjeJw2@RpLP8 znVGjubkmyK6^A5{ySa9oVUOO;JPkqefARb6B7PH~z*QITEPg;ujQVlM`xXmE`+J`h zzW(gkhmi(EWerS*#s~H&Hl$f}Cp|Lqo0O4)&~;KE$Rt2!_$ydLsQjOOHU^Png24X( zHGV1pHXEqtfAZ@SK8gB0Mf9G8$evnzT@o$Kb?D2VyEAJv6qt1g`t$|9{i_MBan3z2 zn4*HsTTe6gwt~#nMfB!gosR6{iDP4pPgSJr%*MEHVKH)cZ9Jx>XA2*f_vF>Su9PAi z1us$f#45)jg<3XoOF0dnmwLc6pW#EFelN+Op>QPCIcVZt+H9*=^;dG52op{5J1R5> z6`8B$gf1UfDr37I(s)}zct)x9*=<2oYXC&cRt9o{G_L%Wm>dRj5Mc*fL1h_Q!mz6*HE)|RdSkuW3`I)`fqq*KG07`k>IVV@2f(5?%iR5 zsg}!#CDaowdqpwBBDP7 zU;_4VmTc=Mo8ns9Z?cY$$Twwr2BLlmUbX60E9N%5$>U%~X=zNIxdGL%VZz6MXA1cb zw)mjTVgq2`Cr4$}^y(vjy4Sod|Nl#+bPN05rAW03aH4dg6-5VzMhF_=wx-Ob!-`kA&Ph z3fzY>TaraT3!Ntcq|-A&wh+Q+u* z0HR{hHK;g>a5tO1h;q|ZSNML(nvN_e%(?m(Yj{EVX;J2mt-gXu)(~cu-Vm(1R79xy zBZ&QD;4IIl*qKaqlDXX*&3Bc{3k>e8Y21f&NylwjUO)acT!WA#O6)H`FTs?8=$B-} z`x!t@St$v1V!lDUtC=q8B3iz18zL&p42O|sfMEbiFWSG>8OA9!|$XXJI*-N zoKa_j=FOJR51dw6=DxyeDgoeR6;_n&a1gFHhI8r1AAG$M4gA(6tW<;T*ML4|J$?WZ zwmt<>&UOA6*zl$8IcVoqjLOy27sS!X$iZ&{DR{ZtkC;NULvZ_FSJ4kNCr)l+w5lZ9zuy2{hHG25OB&%x zf6GYv7?MF<66Tst*d6kAXt#=RVwH6!If!(*MD3VtL&pwJ)2oFVIGaV&1P}0ArEy?2 z)7yRa=-I&`;rjLjHDz_Da%1I!*9fO9qyaCMmWUjJX_$2!hxjfkD!AQ5O1?a1m!w4u zHj*`I6sP2ji*aoA%v01HjxkBjG&nyKf67Ty6Z1&96Id1}u;N27kPucQ{nPVs(;NWk zM`4&`(P}i5670?Ip&)UbJ=@!ZbC`7XK)@)#8QEvMMmI+VN>t3YAL%-r7P)KPLMryM zHNOQLLT&kDw{PLh%3n7GE8t5;3)I&Lj>xPo&x(j&8@ev=k zKy8N=HqetA)r$oQ?gH6{AksqaT}=2n%8DT=T1G1QaU%+SZQK?wVzlcmB^|k92Er_u z1WA9cL+lH*FVEE2pJ91a9pHp)-|SET5XdSJ{0%h!qfzr;cX{8fhll7`%iYTT4YDMz05)j=7RJQq@i z$WHy+#Z&G{Rty)df5?`Zg9=eQ= zk9Vi`T%XR|jBQ}kAzAN1p2x(OK zFUe!iE_y&P4QwI54hQ547V>Q6ze|U)f_XzqGA(F(a<{K=G`nLqCP+jiJW;}B1#orF zRU+&dZ|OuCHEITejJ=nC5^Z-L9+~j8*f2}bets(44YlYYAPX)$7(>ef^74?~&>ck+ zTcOK`)e;38v*l1!J$5)&)JfC`u~p+`0%xzyEUeNvbsLqa51N}bSzQVb@$;%MaRbZu6@!c&|$#`f4#wFbVmtRWQBXTU$gejKghT!fPa!`Tbjc3Nkq%>ue?-N_Ud4$j0dia6x{pc6b2fg z0N6SFOEuzYuMpMn5+rFT8;aC67$8xu{Ir6aPPWHnpo_DJQcj-Yl90Lzcd81psgPLF z9{UOM15$CZ2sg7K(Rv`o$ZUnVC`uDD!}q-?sUV4BO9F6W9y6{Fx(Ah2PYFq1qCg-T zwV@+La=LPh?0~e}>@tvWZ6|#fo%DRg#c=qsBqKyjYuZIR%nc}`pc<}@u2kZj;c3C1 zESO`b{!<@5vkP+IYF#efcQbqG#i+~r^+ytAIkxa?!~B&146vT@s|p~I+XSWDkz&WI z8I>9hdMHb_PrHmc{v5mR9873|;i2;dXy3gY{sZ8cM0$gyRt3;(mXGxy4Vxd|764mW z2>-Sp>J^4#Kzt%iTVj2JY(KG9S=&Qd(j)eBYnqO#gqw{PKl&P|*%kkKhs_2K@xR@7 zU_?;uKMp7h98aMpVUa2Vq7xO(&InKulkfuNk<0jP!C0zdT^L7ChfAr+WMvj`)?_Ca z2}yJ(SYH2RjvhQJ>aI-CCfr_5A_I_dLwF2gYx$E${ex?;qOH6y>&HJaXZjWu3sC_w zo2Z?hZelAP>*E3{=U*Ub2CJliS#7_hi0Yj14JxP3iVbMj5cDFw#DtHk$ri<(Ak3L%-cD z!W6pHrp@;7GQfqX(c-*#;JFNZgBprXR&iBEb*H%D1hpaU>6M5O{4!~ir zuVb+2ikvU;1KMRoPt-(#3a}TdRI19@2PN;J4*H}OU$GCwn8bA8%GdGh4QRhHCh8xn z>bbEBoU@TN`-dSU*NnBzbn=t`ZPB{!n}QfH7R8;pAj0nl!vz(5s^whR#Z|qPOLGfT zPvq)1X#1jSHig;ehQF4mrg-M8g<$gm7}mVn)~k1CD9scS8SH>&x;J-lOaw>&BrY*+ zk49T`^KQ=(=1SxoD=yXBt1pLDTKyY0Ww}@y4dGB;k#Z3-=UM$n7lZNxLN~h1Ez;`nalQo z;psXn;Dp2g2FI}iV#v6-^2%GQ?2!fAyG5MNmgZC9iw2eCD(RBo?v}5ReSzMZ4kCT) z!#od*cp@_NeawB4Jv8>?vFU6IFmU(zU9dycQwSLc#INnw;AmLrTKIUlo(5G$Tr?1n zPE?Zl>_Mv%K8d7_vN-?TwJT1}UT1kI$$f*V9A#qp)tyd$No~GIrqH1T)5Ct>hPehs z52+aSth}7DB4p*DP566o;k)`5Pra^k-aK{1j zrpe#Tryh8k$_2Yqc26oM7oCJXk9)Y8tF=>vTOA!=X8JCY&qEnL8><;H^VP941oKQW zn7b_BpK8s-7D5}KuN;onB=Nm#gaR2)AOSk5L|WBGe03;-ok#py-ASqcK9#BH0a4zcEwgu%$09{A|78U}Y;WZjOg+-6 zy1#>4T?Y`Nw#_=4Q6((7Ps)M%Mhy&dX*F+0hZoXy;ybx}^2j$lYB(zJ%yuPhCI*pD zfPge7O%sGy{47ZOGgLwaD@nOOmi_`Dw4VdF{ z`wmLFDnzfQa5uz^Y6q+W_?A$EIzHroNLV;i>TCL}+_mB8o5}<1R{1lT9!c>MjH6w# zHnJa_4ZTb9FjwO3!F0V0K`;BXO7bw{j1x7l7Rk0dWej(lM)6jNr%_vZ!}ExHy!O)l zGiXPL&Hh3-wC2`DpF=u%va9ei1!Ry0?C5d~O2U{x za-S{}RR=UCDjODd4!RNExR(u2yMqU>uHNY%LW7NG^tpiU-8ay1Sv-~!dP!>iQIuvJ zGH9|CoD~?&4?+NQG3{tT0McM_C9&36(RwOp zs1yXl52b*DQUEMsg$>^lW)EE4fI%Zu6}}tOfz`?2EAF(a;La8yP_idvFZ(%Id8K{q zk-ZVwcdAhE2Do=U{>E3TdE;9IHv5#83xG!d(&CTf?-2#(GOCG^d~w~wumpyVYym8>I(zG*p9Q#}`=0ZK&Rqbrsm!p@JJ>|S6!DZ0nx z6XW@vH2_^WG2v!m1Z4=w8h)~5xNj1D}dY62vNk5O0#Y0lE|&2d!VB5e>7M%)D>_)fhEY4=;kUr#F4X ztzL5AD20Sk9Hqc!7oy%4;g%T!_5b=Qa97f%* zUqo;Ck|8x=$_RJJ2)_bxWVJ>8mwFo4KC*8sPN$03B``Nzo}!+1-sm- zjS!!E(Nd_-tKxwCZ(5W@a=bY-f5l>?lI)FhCYhd@+wlB4v1TLHT!gU*;%KDS{wR;0 z|3Mt48fhts<4@j!ITpgV`RB67BDwk0tq2_$A<39Lr z;z9zn-%uO;Rz+)or)!@X;MZc>CzhcNg$ixo2W!&QSb?6S){r<8EYH)Eljiu!1m8?o zh`8MEPnp#QTq);S#!v+k1O;6x?hZKd?(A3ELTFh3+WQ+*CGi$z^kqAS{u4rK~&BJa4|tAHje^&{YF5!Z$A-`k(E&SO-|o%_{_)XlFK>M- zP(#s|ndky2Zp9x>4(gxiQf$N&!`7{}HUg=tc&{$sYYN}E>SAAFBmk!ZTqc*xBIXHy zz!R>Q`D=+H(6oT}Cao6?&w8Fa8F>BmU$kK*@z8y>dY#hlS79 z1krxeSRhL;*7)#s1g6pcKzG$I%~6yxD%99l1=aM$uXf47ZOHScW5RGK&SD$r9lFC8 zvOx|1<*n+Ht82i-;8z0w5vAK|Dgh=7w9E{vY9X_n$Q_@do;|Rv$~v2)$5MV#F3KGH z?^McyvxwC4#|S$eF}&@vP@&ewbUyyuZ104JZgBvrU-ufCq5#oA!Rg)qnTqG}7EX{f2#dQ^*TSs(q%(*z*nQ33l5Bd^)l3(Brm0N~ zgs3FQbXH)pP1`r|oF79zNGn#Sz&D%>cFtuyO@gcEo9$FqFG9)|D(t0cbs^HhqqYAK z!jqUCrl9EK>#fVmUqDgKxT7*_bqRI7Hzh>U8Z-8*-I$CV3&_7zMtMk3M%5evuiLK=jNV^PfIzF_- zX!3+OxMXSqd4X9%R{X}uZ*ZtZj9kvNi61z_H#X}|m;lmVY@8_wb!|0#hI&V3osz%A z>7EORg1KO!oTn&%vYe8Pf!Y{&8FoFJo50!CZNMi+=xQ9mnrZ7z07BpU`rELPYM9N0 z0k3f>fZTFUhN;TAip-&Oi>P*Ew~J_0sYT>~HDXFS-+j+wsl;_k>DM8vfDvD*O&o>+ zOz1&Tqc4t>W9*Fh7Wbt#fRI>J<@a-0eP^d1wWvy}ocy8NeadZiL64_AB2E(nY~HU^ zCpW5=^Xt5-H9{g%Ap?&np=_O2_G*cyeNNX9Mlu9SP?9r#wz}(6n2)xi*~ov)piVB z=!ok2IKb45&hXi0INs?rjK?2C;_@GvWO|UIr#k?K-Mx&eoLM%ZCk|UfN?W-F9+Hv@ z{Td3eBwttn>`z#J;T=UXQrIp4O6edunGo00Un}RH!ybMxw3V&V zwL{b^d=pIuj+LE>K%Wu08G33E4ut4<5?c!r&i+9+Aw(^0NL!aU3LBQF_Z>PABqH^M zLy$BhF4wj|m0hlppQwIhZ^L+J-uQlK*dD<>&D(mF$Q0|>j$7&V#ehKM5Ujvt6N0H? zg|y@}#PP^Gk_*=_1aE88@H}?G%N8ElO6=*BnHy%rgSr=!IzglYU$4+xp+1GdAfW>E zi9g}-@K0zj*EEI_!m^El8M}4g^3MU^5?j4*{u$n#rzab#phJx(Kll3$PUcdEcH&&M&gMT{dHya$0t14( zJ~Ezp=X6F<*g)M%+ai%-jQZQ+Ph%HP1vRn)#!Drf z0V(j|cBHnW{~F%dK&GNt34)+Qf5`a?gyxY7(&#~6a^i|2G?%cxf&J?k(i~Vk#}68h z#P(h`-d$-(s>3^On_&nbJ>$6^H))(-2kD2JBZ=+2sa8HnGyQmU53@u9H7Ybcl8ELc>4 z3wO9dY`_XuK7$%s<$gbj3ZGCkU&%7EX`tjdy)C<_i?kx^8AQ#rC$8Nt;4J-6a~Chn z11V#Y6<{;h0xa%{Xx~g7{?L#)AABZYe%B&?hm#VKjF^K8!3PD@rn4$Iau{W+za5P$ ziGx*~gIp6OrZ}^oDo-LTdb}<3Gi$E>Xjji;M1Ms*uuMje2~!o_wfZ+Y&2^s z>^0}tsm-!FM)4;8$@5~^t>r8O@92FrafJQ&RvsXtSr6gLZOxulx7cb5j6*5N%^fX8 zkx;1%>=Q}HS5!Pv{93mqg=F?W{cgf?u?kbeQf17Ivb_Ir914)#)`%-s!K-)u+P6oH zu`B}Utk|m#Z38aVo^Me;psp&M^V(Z)?gL$!6rZ^m!@(8J|w4H(oFQlC4#7Z2DP)9KI(?hgIEujrEtACi%JxVw()p@hads!hmsf3sHRPr-~kpUL2KV@2vmWa zcDtiWu$0^CE?Rl)+;r{Id?Ti*l15)2B_rO9mZ;-g^DpDyVmR#BT?G%!JOPf+bZATc z+e!F8(~FGtPnrNM!x)6_x-L1?S9^xd-u6PXZ7^M}={ip{5WfARP5{MfA)Os&{tmWE zzBsA2w0T(%aP{`}09p#1j6Z4wV&eD|(7pz24g0MKkMp}?RfCs0mY=?EcLWU|^l7{- zUi$vb5tkKlUyi8|f0KvwgkfO-1|8hHTmGmIVC`i<@s4q*(o3 zWGC_8o=vYNqaj~$^BfL)(nokKBeQiuZ?0h`NA56`Fq2P6-(*V)`ebt|85&l>KbhEM zpaRiK+$W@JL%E$YkkfKG&2{Dh`ax{gnH9Zvjty~6@1LlZ;@qLZ%>_S9@NUo$ysuYT zAI;a|P#$P92puprsG9T^Cd}vtEMKUwB^~ zrXuwBxcx!sc7+XU7#?K8Y1q+PfxLNJQZAj79xaL)vWd{X9}|>l{S{oTX^+6lRnq{C zMD@r8RymPMGa~zXY)2YVoQViT!42vlpy%4o(H!Lb&SZ1R>yOYh~Ghr*z zDLVN3$V@+C|BTSAh6=%%gMb+})W5-D{*vCScgZNCNih5A_^PiW&RNBoM=;;pKumcB zZ>cpYcAW)457a#kz!IdeMOr=33r8TYT2p4;jc4VnP3cgNkz&BtV{Q%e6RKK+Wl|1& zO0q!QbRMeWH|?$ZH%m~KB{YLkI3dXe!0N>5d_OAguqgqd0-h;E^*R4BwK|05VkZNz z>!PDhZbdwoWy(XUhFtNVNVTi~+XZG2m6=2Nv(1c)m-#K5TQlR>XTyyS%rhOj8+w)R zydD5n1TL%0px3OhXbTuufME!IIs#JR;7#vq*zYh2*7ft8_HVu>m1N=l(k@DwUz942 z1rX%{$56CH)DL=;A5|5}qU*ZhQZ+3cpif;PoocDO=qLa^Wl3mhl= z3h|;EG993`hfqHw3=K*Dz%QdX3c+z zo#4rF#0E=PcIIfOspK>31&eTPbDqTufk#IzA_GF5D7};;_*{@4mZv*jbvwrV7y^l$ zDS$fyjhi(~df|}M%Y!MWcmN=|n||`uY3u4aebE^T22b^aN;m%%mz3VZi@^`R3qs=) z+DJXQ2Wzi6g;P~yaN!H3{zJqC((gD0LHvFgqa|9VEtPL%`9l@HIpLo_3m$=gps?FC$5LOQgl0SxVfC_r zTN3#CnF-gbu7WwYVtN;W%HgBEja0A|G4SDG?#6zslZY_dJ4D2cht6WLJ1*! zb$e|4J>6IDEkc7T#d#nhBu!*_#Ln%5SDw4Ro#((@w@s&8C>k~nb=*lwqQPdXcMAH= zS_$0CJE~I*jNk4i!|82WOGr5@v|6c8*6W8D|3o#9;AbW{ij(46ms-m6&C(t3ZoWnS zTL#v@CDE&ukx|BP(sY+}=SDotU9$5FfE-F`G-Lls9rSu$C(H_5+ZYK)9Uw#ha+`A- z<74~6e7xj9aCHiHboS9@Ff={Oobjv|0dTk#d^bvwOsH}_(TaRZ$oKSiI_ zl+Snae?)LW5X~Y=j4cwHkKW0=-K(8jUy4*aZ45VK3U0RspVj}V!7vO@Ufv#Ol7m(h zA|F!b*sBTK5cCuNr&vSO)*kdYuPQr;Exr9jUx{jrZFC0*6@>e^7LV$ww+55E0o|D_ zV+o2hx!j0xB1adp)lna;xau2H+Ch{xe~8Z??Ftt{4jbV&_rhA$f&NS_D$&%HMph|_ z5%Q{OTu%Tz<&2y!u9>kT@P=owknOD%qZjV5!}?-dy_mwhj&@;lmO(Skpn1D7L1f^oh|X zG{?`J|Imt_Ot@83a-+UMZ{@^LUpZVzJw8zb`vR7BsY9v;@e5}34iPqzH?f&H8P9?82RhKQXlwSGa_?$-UY2kU0@j_?mT2_H`HpceV z6$Hx%XQ#Cb67|@>QuW-N$D%AT{B#1>0kGh4co-a!;QXO4_24-ah{0RjPj~o%s3wdi zJa=K;9RU2OCyUpduNN9@$4B9R`{lPfML%4X3)Bi3Kdk#TT>{9Yt`7(ATGql{rglv0 zdWXc9j?@M&jlGsH0A1x%ox`~BTtFK_=EF6PmbJ7D51~UZI*S5@JtnhBRRkGE4i<6X zddu8gT6Hu2=-j0@NQlt%l*?Y$D-PRD3zn5Nl}H65V3lEAd)e`@DsSnXp?Til%=S~5 z0QJK^)J|b1nw1}s>F(ts3=n>EFqzdBCY^asA+KJ0#~#8Ei~Z4lp(8o^s)l@)t>$G- z%Zpox7W*nrs{@QE9h9PTnoInHTBA2W^vNliSOJSUjU`NGpAerI@V6Daeh3IkdMjP4 zT|9)+Ozs6QgtJE{fLZCMqL#|H2^%c1gfv-K>Xt7%N3pamG1{$wT@=wdBJ<0k1`~>?eaw?MX$9OceWE-(R#9n6O>>@Y+aC&o)Lm)n5c zET{k8(6myJl5C2#n739}wwlDp4FfD}w7ha$61~{Yl0GlBeh#TE+oUK&xn(1@wtgPeLd@s;Pue5$9(u7{>NP(ltzHZ$xp|{T`EQTs*zk0Qcp4ba>dYwZ zDCjot?L6r4#1O3CzIkuUZLe{ogv(MwXj6sX7uLsI5(xeCobgS}Z=0sde@+QHMYU(#}gusZ( zzw_Ss?^zdm@z@bkMtT;$M%vL845gFsaxsT-D_|>m7^O^wT~_LPInjAp{o)aCQ&kat zC$#doet^4Q2lW}!55&m9o!IC)UFxJAv3Em{TcqS27I^pGGPQkGp&{ge;A*K))N%H( zz|=pvLRk$!Fme{t3oUiMYU4=-oJf$i9UT4F2A22__@q?=0{WMPbTXA5SxqUC)?^Fd zpce7<9#eUvHJ37it>mh}Nv`400qi7d0T~McuP(M(!_-JM(V+@I-}S7-QtaBD0@(@Acq1+HP&g8^5@`Y*mpk$c9YXZ2y&%?9FSd#yu4~ z)LVAKH*NU&>yIA+1oeWDhIxc_JZE)bjOc zkL%%z4v>@4C|KvaW0IQ03W|WCN$rbXAvOL=GQ8; z`TUl=kwEFV*z1%Y^_ys6ud53CjAnCs%oW&Q2o^~QVzhWq7{oH}2*;_tFF=EXTf~K{ zz*KJ?&^WX(_!9rV2Ykv*@RU@&t{D(QhiejOQ%g%L*r<#>+9@wf&0f?0WGCJZc5j5M zZd~;t&lwvym-zfScJ7!ou!~g;z`UxmLDDc!_&B&Es#6W#=gsU3gOJ%6 zD3p3}NP2fNFZ^VNbq=#e+C5^B%E6##d2XIG;aZj9U-H9EGNeYPg^^#1aVvdcgjw0N z%X0C?ivu}F%~s8y{vg_ldh@<{Kk?8Ro;6fC?CyYQ8lU-(g@LjZ!m2KBD5-{YwURMg zy{n#PYJ;0+e-h1*PyyBdI98@u*;eA2{xlT*=d7ihQ4T(hvUUX4{;RXC>v5ZjAtnZ; z76efA9|fPxFwqHF1)`mT&x;Ym94w%_g1nL|&+Ff*-%70lyrMxaxD-wlZAL1248Z~O z=NM6E!@QAQPKQgZW;4B~em*M1@&N-4bc~lf~ygAzh z{6`Z9oFxQmf8atNEG95q1ha0Efrb+qj(#ZNb?R8u(7lJx4pBIHLtFVvNMqxaBpBEf z5izcAchdQ#O^LTO06an+>Vts`;yF?-`nLs0I0wTm!?=7 zG>7i2a6MIl&wAQXe&}c307+TcpeP!1psll$z&k!SEPf5BDEr->)phu_Xc|x!8Zt}R zHP`f?ON#1glQb#(zuxlXcB75il~{4u(me{^OnR79+B3C5Wec6=EG6Ge!-5rO&mq92|tu^fb=;6e7C%* zQTX0&4k<(ZUg<*&)ZYdevhz@>YR_aVZi;IpLRo#W^gK_OLa24laYIXOBuX&0d{-jy zvfMY_!GG?X4GN_{*@Vk+n>WC+lXhB6qr5JmX03dQs}<(ccD=&xjPWJlv=+J(du@_! zUE?xHv2&80!t4PqfE*cR+#`(JDNKy`5(Vhg~67)-L*|UHA z2w#R4?qs=Mfm`>(2{d#|Q4H+smAP2c|6^Ay7w`$oo-^LRdvP13Ue5+to`g94KLRVd zZXx8eQRV#u#2_Y zVP7g|)caDwp=1e$ha~|L1w#;(o@TcQ{MflNI*Nzf#h}aIMWqq?`q)%PJ+=Tvl*EIm z&eXsn&5}D$GXW;_W*6K+Xwr&gFioN5(U-NG=>RCtD=Y3{-?{bMz@^;W59)xh^8TjH zjn6|`!87cNUmY~FAp1Uc@qTKtWvi?;S{M0nI;XyM%9-h$@LVz<(8%5WUuyb*H@WGr z?!9}5_(KoKv}rU}Iw@}b{cw`3s726NQAQSlinSG9qwr6j4<{*wsUPDf{Jmy(SGir@ zE(kF)gAIy`t!W$Kv?;2c$~;2^_|#S115bCoQIr5YL_O?wzd{y33^)b{G{UGa0O7JD zfa0**kIxpy;o8{y9kU-iM_3j%nE1?Bp7b|a;w7-1EIxd+^XMp!%(`*EojVezI`_WG zV^d<=qw4LCFa)}?Jg=`hCx*VeGU4CN2AI`MP&0i5vSGm@0Jm8xw+j&%)9*Yz+%On5($Nmb`S{a8VPHjVPqUWQ0X= zF*rmtP=)WEe_uhsPOI5=!36~DOybnD04YG$zmYu>zUMZHM$(0cCnJk(GT$Cu*?wpC zbQ?|9z7;ysPO<^4&~`%gw@D1UR`LK@AynX*&!sq#NIgzwhl`$xYD+Ea_P$`8j<_t*s$H^)h$}E1{1dQ^1MNgn%a1Q%-BznsS5qz(PI{-4Ogw#749`c+c4oR5_Cg}ZvipBaA~Y6`$go0RTy0!Eqk8HjQx zaFBp62eRGvMjyC40)yMlN5LE%@iqGe?h_W_lw%ap+JGABVEGK_5aDBIKx7O= z=NSXcFooU^@AapF(DQMMTk$+Y8c^oZ!I+VWjfXM_UilT1e-3^#vq%)9X{nx&-&gwY zzdyv6s7pmh6px6|SSO_n8yBwRCzgCkhAiLWq0m5L_3|gPk7|YQ_u|x-$l0ZS28La0 zgDkCvW3g?u=WSjv|sO<>Mf4 z{+B~*1LyHyC)tk)n*@Db#8oP_9z@b)POe8g2`&?7;n|zjwCA~ClvgJ)d@`jQl{}kA zb}(j0(Jj$h?Bu5Wjx|dDXpEE=?36~f%YvQpCenVNZK;qy)cMk>B|<-BMf1^xD2zG9 zZt6C2DdnA$MNReHi@;)tKqpc8vHi_Z8zX_|=z{5CPxu`AaXQ5@GOL(2jRIwbS%aKv^-p`0nS(Ai5n++zYWGJq1LlVM4DSYJgm?QRNv^i0BVzWS(EU~ z9N==ayUFqQjq&)^Fkn^Qo+K2q&o^ku)*gbV@Bp8l3VN~wQ_5V;+Ls2R*awXbbH1=i zO^|qcVglBngD|%i^V)nUv66#^YMtt1zx%O!`odR+6`L(h<~>W=r|Zy6o$aZKjJfiX z?Y=+s%2%`oHpX0B4cp^QeDV8TQ2*9mWuSONMRhwkb4o`*72bsiu?@2l1a72QD_&9t zz=#{VqZPVN+(AJ+*cb6lz~y3{GOr6uy@QaOkd;{8jp3x<3@#hJ8nlC3I`J{@vtrM3 zLTPf5^Eq=$8W^ox$79uD;M&prqg`Gb^>K=9cC)6M$B-((TAw6 zJ$srH{DylP42M(~g~6iX)3gc-qPplzPF-*h{%Pf1DSag&F$Pe{U4JY0+SpyNR% z=YU)uoo)^aAD7`SDBx-_|CIM%wGt$fwMH?;z^iCmA|`WFxOfsee`5Da^6YlXhK|g5 zYW)8 z&Yi@sP{JiNf`7GR@ymZ?(8J=t5dcME85~+}OYQnT?xyqD>YG1H8xY9^iFW!yp7$iL z`l$xHcEa+Yl_o9E)mF^bRYAhBjD&sP#l^; zlP-CMANH{(1+_*E9b7)Mjm&_J28&lgprCi?2B7;zGulB1Gur}4QZ{1=19r&kk9}n@ zhT5iSJ5%r#p@06;uJgRB_BoxK&ybZ!ebCN5c88XY4Vtc)8sHNDoBLreU&(7!-TRwJ zbH2pZ@do!N8@`A&O}%Ltss^+ntBd)9XjGlaz4d@DG{0FN>~`<6OjslN>1Z!-1|>yB z0!e=5tjD|fmcf+xaPQAzk~K-|&5%m1>ssZ{BFkr)Znt{2B6+09=`wk7i08|?qsq~0 z#J@VBO5s#YVB!e&9vP;foQ zc95i}pBdJRV?WXyBE*S)+TILCwvhG`fk$h)DKW+D`2a-|$6PMFJjh);i=h@;l9r~X zbQ$fL;kQreK6McdkYEbJm8$to-X`aHD+pGc<_A3_h@@RF>_sB0IG)Wv>a zTiTp6B~LR-HxXPH5ZxZ;Pfh3}i*V4HQ908;uTpErf|6~&Q;dT%IDpL`pBF2KAKTIW z6(k1jXVo9GJ&tC_hOLgCAMBy0u6)fNkCX^!5th#OB)CtuaL|(zOGwxsiI00}m~CeJ z3t99~=%q(V9<)+;zyg&)COwu`pb&;)6;lN;UQd{1uHxM889t>z^}UTF0e)G}l{yd^ zl-qALU$R5Xf?;U7vpe0bV76b5gmxEQi;nOEZCMD$5zs)|vyQRFb=b|qK@ikQH|xjp zCbbc&M>KJ2aJ)@bfYGw$Ee|U-Jt!#hR_)_d$PjQx5;w8>Hz zQG#PzC@CcZr~}#ht!XVFO%SOPNrbkmsI{}{df&~oiK7kKUXR&%N|um!m#tBYt2-0E z($Lx54A4qU{U~Ql^1i7@TkKBFX&=H1Y>{^U>^2Z`}9b!a9Cm{$T2)+ge{DQQEx-WUeuG_u_|{oDw(XAA1WX_ zGQnjuPDqPZ<~rQRRLz6UBT_u#VsYfwvgL$raajUDUv0$YVW)IWatiV%-iH`w%@sz}XuhhF($xfVaWy8Q5I%0s zF-f94W8T5>wdxdCb+u5K}qgL>o12fBCxCH|LL4OU*MZ| zs`M6-i*C&N=b3Q}!{<8qO&CBnnLj)}oCzFD>1z#^K;i&=>YWUv@liR#6N1`NP=?!u zz#+v4gl?upfetUZ0D+SM(p?jPshLg;dx`D9Yt95I9A9Q%D5{65m2VgNCw~fZ`Y!2{ z(G_RQ>eMQVk*NDVbQusTJm#XsiChb-l?PKWSyv{c;r7df8$IFJqBR(4ln<`LLX50B4xQ+C4a&G0jYuA2ZxGHdBV zH&#|*D{~n51sWFOyZ}aCB8me+M|6^RDRMH&j+Si$h_6161ZCT$c=LaLAWy-6X7l@|WrNk3FzBGko&neCiz zW~YEZp|T@Bt`UoUaT6KpHWrWmQkOk!?z!FXxTPG@Mq-D2V_=U2AqQrkAcLJQ9JI5U zY(2&OOZzlwN#8rM9)h9j^^GU!;SdCNkdsB+b1L$;)xsRX&Z+pzQmqQUNe{t* z6?!LCVeafok%!IHKOXJIcr*dIARD?L1xJplCQjYIn)Sq@57RQ2;+c6$OAD+6=SU5N zT43ni;{js?vgM{CI6g9Q2Ki$b$-<|He|r+F;|`G zKM4vhlPz23`PT^9$F_h6U@jL`jnc%k65Ms{=7dH7?7GT}Nhxl%QOij6B_%}S1B{yn ze9*=A;Y{=SZcqkT7fr5E{9n#dcXP!M9R-o7iE3|XZU;0hDH{@#&}woJo>S*fTlAX% z0Xi)stvkxV6q7YuRCuF(TkAIJlLu5C;+MF1QSm*MRe8tJHWD} zLpF?VATBfq-m)&0+=}*OM@jCbQJm>dFb+15z_nFziz%G&!Y7vl6xv0h6@0x@<>%b| z{}gJ3z|F~B*?+NiZBOa=e$R*adTSgq9t&VKd<#+pZN{6xe8hA2lL4_FHf=#q2Q;xL zNpJ@p$jkFMs>Up!5-Y!* z+=Z3phC7V)IG1>G5$r=n^4@nw$H&l@+2{ZSM03q-=C$un1aJPii`ZbAWNyEdFDz@FywlDnvIuW>Hz%qIBs#$G zsFhNFY?DavR1F}TkBURCQ%W>8PxyX*Kb3&KD$Pz{6Ip_?vB|v#Q;$ zyK_Xt9IZPa5ypb%J@IfGy-0qOJkgaE*uiM9-fS>qmOY4Cf?)m4m{4VxqiR<*SOITN zH{#y)*QmG~gukB(VIMI!1SUoK9D$sZX=LJP8NoArP&y&~#1YF_a0=78SQh24*Q81# z9HU;1ZF*BwJSt^ioR=8OqAe!(_;tysm)s2q7z$*&=t(SRnFSE8q{ZI+AhQaaIdV%Of( zon(Vz4+mACnK7WCMZAy*N)|V#zH=TK;DX?svYedRs%P~bxYsEM-PmutsGrKe6bKBB z#BQ!mz?`|GT<9+o1iQPQV(z8YLgsBI6g&U)$!DH$%)$W%r+|qg>3CCqB~my&z{Pq# z&bXkg+6`i~FIUzIVX0^Pv*pO0al5}>7_JR`7;}mwJd?`wk)@)5no5u4`(;9iguk(y zt0(O9?LQj5Ef8_)(AInx&cWj>?OMW47?K|Kfxb76n<8bySj;Bk3jyXEaZOwX1U(Gl z>g#%62G9v12@ba+Yv-^AfZ{T`P)K`%-+yf)9b?6C3}8|WjMnU5fF(rUnm7xQj2s=m5<{Z(1E=TPyCbfg7;Litb#v0h0q8$j3CB2_gv6@h`f|*WV(aN8XGyqo1HrOyhiLt*RRtt}miTrA^Cz{={aIsX_6f z`#Pn2Q;S`C0Jg&`0+?tM*N91CiR1j3ekadly!^N6xZz)TFlWuZsdC@HD9MmU%nKhQ zHOEFxQl}slB3aJ7rqJ+kla3JB;k?d+GD&ssb#}*jCm8@9fPF84PYy|lg3%(oWd_Tw zz1I7TZv~f*IzAeQ59^S45+U{(Z3E=fG%nHUW&95^^k3;@%^omNvmQL4=TLrl z9VHwzY}cp8`1GWu$jF#4Hiy{1oEvyUWUR@QGp^b=PJfVqW+Okf*)G_Iw8&iokSQH@ zkC^^m=M(xM=teE=olI`Jl$h51t>Qh*i8l1?wUg@OxiJsPQH=hgVzip{HoCf zAR5}Ez@Jg}U(STvoXX+(edaUOwb{Zaa`5WW%t_KHiF(u__L=e2{Y5Y+8t~yDo9#^D zMrZj(EH7FkGQLg${@S715b_!d?sSN-u*J&}wE2yVs!UL9EJ<&6`llX+(LnH_G#~FTvSd2y`5}rsM3|Z(DMSZNEc8(xD1_8aA)ttqrlNC? zrW0l~y1uu*+Ub~HA(GOQ8}Fbf@H!{nBwV*2%%`7K_ZH~a`C1UbX`aW8zi%+i(4b^K z-PKy8MRZy)d&^gLwdH*X=ziYt{SMM#nvjQx%RN@54-fCx`rqgDQy9)_-L|=WD>VIz zJ)FXOvqwfBPNcAy1(sGh-E0|Ga|5nRlk_yZhL<_p7hB9y6xJBj5?~sUWXErJwCt*a zfjU}Z(PuR4xx@fJAFvyEWu1!Msc*7Sl$HH8w0~z5mF#GuT&ft@_8fG|gN4cNkSA9$-z@&J1`LAPANoJ6MygBuJFaZG~-wU z$l*Wskw}RY|JVZXqWzZoF~;{JfbphR!>4!4+w~1bV+w(vmnnX1Sn+_fBS&N5g0X%& zbGga042g>*?`YkcgCSn$@I{A1a&*s~yPHu*?GUmIh)$S=8{PNTTnkQ7tBSCCicg$W zs!L)wN+5?((hQrR6DluCl4z4h^;N3j1{Nfhg$l$XE`Cj+Tg{-p{URk`M$8NS)Wu>Va;dMim+T4O4Odg;F>Wp-AY0G{11`~zD#9J zePiEUO#yuOFK-$%j~0rdENKw4f#O3pmOK1Jgk*)Eto$z24$F5h;!%i;JJ;39GvZL! zJ#-eHNa%@SgLTDLa1E4Pv-S#Lo(6~~OiNcRIDDVSHjqm{z*=h;sOHVLeN^*{Cxs1Q zI+S35=dHQECmcE z4%?Fat!($7|FTgu0<|?rGz;5rv`P?FrTl0!?kujVXP%M=;2`m`(VoJXbgemt48Gj4 zO_=D1e^jinm?Rz9`!n{(DZx0)+{J?@v<|=k5j(&jy&OsFZ8(&T1|@o$hANRErKK*=RdGdEwVYwGq4SCMWVic0I~oeAV@7Y~{m0KrEHK47ulkIO z&Lf@tK+_w}ktLoKFCAK9rJz8vXixhWcr0E47{4lT75U>Fa=YiC*R9(0GZU{}9KfVy z4mfXQ!aM#g6_uBm>Z@ESdz=cCU?4BwC+g9f**72ByAHm{M*SS%GQUMY{MLEl^^TE&ZJ6yx;in5LF$RErMDeZ(WP(3NS2ShjFmpcn+xw`#31*eg zfQFs)&o4Q{wlI4NpD)7yKM!nbk{-6a63|>%d4X%daD*Z(dIbMD`3y>;Nb0%Bawb>1Oby$ty23B;Y)wt&)L0&j=S zy2qH!!a%Sm&Ak&2+|d-u@H3MMO$=Vf^;0#MnNUd9mN|u0q6fd2p^SLvLqD&mdKu&o zfc%{Vm@cKX|3f0=7hY0$ynQh;#SCZ~K!nbK@`avoYk=(sJ$P=OcJZ-P`Bu*;jMHdi zBeDxI{Tna0j*4YeZDUs0aN>-8rXWIt=2mX0K{hX(Ph4QObKy=iR@0&7|lfj43Q&h15j07^jhA}AIBq;T>37K8p$ysP0U?jLQ) zQ`&*|$$699lu2J$LfDNiUTx;H+^9EP+xO49I=um!YK&i{4;mFBh(wkdPJ<)jDuy25 zK7YN%&u6HE)7OO%(rLxgQg_1 zhPV82iK?W!63b3kz8780OxiK{Ds5*V$T2!qVK_|{M54)Uv7Tz2qa>5e2UJj1^We`< zg;snN1^=wHM<}=uMZDgAXw->dGbAjsS+~b5F0dKMUf*r{xpacMTDy(Q<1FpZ5Yq-t zY-$8f+1|6RofJ-U4(`6-b=3y>TV>FQTmR+{-gjM|3U(=7sf5uB& zf0OepvfI4SBwkqR2OpI^UTF06KE-~Z3kMd>U!z+GO|pbTZV9G!#W~IHN_X8eMzR~pN81agP)r1S0l^I_DJgNxp=E>0BS6%Dby}5 zg5(u3t0hH)Zgm+~AVVshfGmq5iHTbQxIZAgCuHx=4>tFJ;npnRI9fx6@wNV)3r_N6 zo+2qEvqx4ab;F79v4&6wkA+H4=XvTH#Xik}c7xFHSxpv7`o-SJK6Qtx|ARZ6_cFk; zU{|LdUPd)0S}cLC^CM6gxBbPjnn7&vz$>tN5K!VTM1el-}&jBp}1qwu#1InGfZ7t7rf6wf6NT20iT zXH^_igz}eu+F2*YoS0DUvRccNu82>fz%@-?2{_>5D|-u=H4j2wZuT8kleW&Sq52RT z4D_`;8T_df1FaX#dOtM5vC*D6!1UD2QIu%QOu5>*tOny0Gir42SSm^Iv1F{KksY9$ zD*=yzdhR3&Yl3r9<9B(Kl`>e!Dy{5{QR|=N!mwK%TVz5>sh+749GGVUHKQYz9n)hn+BzvUyh2=p4-oCBMglHSlR-p7^katiODfM=k|%5>&a-9EN1;Xrl4 zjN}2KjaQ;u?cdhtanV9?+vPlzcd~@>MfV8Csn+uvgR#POb*~j zkVMML8M*yv6>cw|GL~~c53?(S(jvf5hl-s#G)>~it3!lHny`JQdOS-$C;iKydz;Ze zujlvyr>t=hP>fJ`+fCJ`5?jCBSTi~;A8(2mR`)aMOD{Ya*dP9CF9ai2u#{S+S!Q_6 z#O-`bFr1@DN|Z`O{>=w3C`{c`ogqvx7gyHm8x88g$1_P=C(kiyAv4ugMkau%ID@=u z=j^!&y^hDtP2lKtE9XBS6s0HEsg3k(e``*r()<(wdFhj-1f3L@_+8yTx1D0FF#};P z!#V_5@0dutbwL!+qCM=W! zbIg`WitYP&&(P3bc5j-QVi7GmGM~Z9v(FF#Zo4sxd?3WIcM^Ks91%A3nTzm`rEBcG zzMLJk%;h}qL8bnVLEe^p08ZH!-+zrz+FJy5L#1dj2$AWA|9(yyhV!;+8;XgXg;&Y!|INTj*`{7)cXSOS&X(0$ zeBOUZrn&bjYLMfRB6Xj}F^M2~rOL=AMc7~fC=S;=k#U7c%eQXiWNOD+h{KE2I$?zSk90>Sd$LJI4eGH*mJUrA$mm zpo0xRf2Hfg8e=#t*4?+(P^;{t`e*1`W{jjwe+ROrYBojLaZ^iFAl%!^lbUk~dp#y^ zj#$Ir4Za~)clUpsC7C!XpnP3$!J@5#>!}cYL%Zu{=$l7uv8TKBQn(u`ZcnFyvH{!3 z6_IWp;eiv>r~x4@Bx~|NEf0>fUNs8K=F-qIB^8#2tXU2Aps`$l=rlD;gy6%+a{NHp zEZobvdKvidmp4VYFEL!3%Cp55kR`u!j~|p+T}Z9m_2p|x>f?Q~D~w2AzIofr{z zgk92>`2FVrc4&ouoZI1j_cDs6j1ob5j0vP0k1~bMP$i)&R1y!_&a-P9RIHqu+=qsi zjELKYr{#+yTM^eFG)-ob=lAfi3UBtEQlDlQ9M!Q%Po} zXh?*QWQLS)yYW8Ttk36y1`9twZ+vV&b;H4Iqdg96i})ADc!v`^kezNFV&ARU3jGdF zn;lD%H~_@5i%F-Quiw^FR3Xo=f9nJMTPZ72nf6tnB=eEq+sUs|3n8+Eo)IMbnjhc2 zb7TL*(}VaB*DRfQxKT1msh$s({AchJsGe1b1+H9-7|wrQGZyoRp}bX)I>DthlZ)V#C^eaU z0ANRUsI-J`t{jVt6M+l&ykW2qCIksX_=BD+q(fFlFF5T&%)orQoX3mVQvm4KZQMnF z%G(#9?~>_WX3^A^v$Z}C&d!c&uyI%r^L2?A(}NF|ga7*CP4Q|Stj%|b{cDmldn_hC zSJ|llC7LVgz8(o&`6mNSOFq{*1xTogn}ol z&Q`U{FW;}{Z>)aP8T`ZtsTc^h*+icZs?mD8+@4Yx`$K2igNwl_GE0SA$-s<@7ZMCl zno7Ppf!~}16jJl#i$sIvbcIHHk{!OC3rDDGXcwN?ktpvyU_(cst$AYYUG5Z#K@nt7 z1I;YY_964+=#U+Ls3gq#qWbY=( zQ`oftL*@%4KvUo4(L*edi(+TaW=H|Ry$@@ zHNt!Z$q z!edEGAoqrbdN5h5ZYlQ>CcOeW+pPPHXq@|fmmoEzOY?F*-u4hkcF+5K*WKNw4)*xD zdAg{wK--YJ7{KMlV}#;So6WUzXszPeN-(L*yK_TRC^vk;^rNsYVzt0V*3!FP(~_yj z!w<_gt1z}6gw|QzW*>$vbuRpMlhdap)9TWmlLQ|v%d`oS8D0ENqTC|$N>>0vRG ze|oVG5-24CR@z^f75+_EN|TiSLk-@66nb}Fb3FhH-fq(dGI#kEWP|2AZDbbl9%pfj zEPc&NovIJdj(~$wOVA$)8*!R- zIL>7DsfTbps zywZxX`-bo0Tch0q;e%}@!3=oaE$2vzM}LOp<>7s3W*omdoTpjKQQky%IT)n$qAwfg z>x+=N#xisrCn7PYbhK>B`w$2Y?kavAt|UZJq;GYt(EpJ7hhxW01@9Jc{bK5M>lHmtu18*LBPeNN zR_9NN7|`lkj$&d_MT!CagzHvzotj8(s?Q~n7}Z}3=a4b&v8SH#bG5Aw$Za z{vClB^s5tgG8@8~v5w}P2jQl$cZ zCXO^7I=D`@$x^=gUkgNtB|WmQGi*A&(&QyH0ZM>aCltG5&s{1BB|K z0{p--Lk0Pjf;qCu$-6jCbZL8GyL~31 zZ`IMhF}HcSq~b8D3B70eE7}FaZT4jhkVKkn5fq)xy^>mHa;G=u0^Y$~IVcm6$R&R8 zBw^mtNaWNdF-2I&Gn>;SNC=9>6TK`CA*zOYN6GFZunlUW8)XKOKF`Y4xI&ul&E~>M zgW+uXuM)lSc^$8OBX@O33e~SP!J!C@cB{RkLm7Kx%EpH{;P_{ds`dxJ{w!+(F?)zs zM8&STa?3YPbF>E_Dlvt*JpfP|;D(VXnJ@T*w-c6Jy_cJI3PYWCNNPhErjG#W$;w7) z$iJ~7)^)P}gM`^o+IIadT{}#=x_#gc`xnx2ZElF0+60W9aSQgsU^X_BB|og6mGHvY zvxTDw-M|Qx2P^Y~a4u2kqUyfunxPptH8(NB|JP@($3!Kz6iKaa)==K-t1562=iZAr zefn-9N6j(q;am#qAgmmm;Dw?a#7U2HhncWP^AxloV=skWY|6rDN}dGkZ!``56)MYT zqUHYD)VJeNNS*Z%eQ$LQ&nbDs0mk;d%B_+vdGBrI8_myM*N0a;6e(ctvnss1AI=+M z!2z~+LtxnKb~H3|Hwo)t?@X}AUfF(rk%3T{DYNByyC6?j?h0-LdS$CcI6gFk4BB;v zb^%V?o0ihClCG(4Ft}Sc^R$^#ya=x1b7l@kg5DYQboP0)A;{*4b)|V4;lW(srAP*u zBwpS8!FVU)W9NgLoKgHF0M8XUp!t$}PK9BO%uU~Cu^WkWLpC-$z%M|oS&V7oT(;Zp z$^w2`!aI{$K;ezjQ*=~)$hyinE0Q%#qWOSlnEe}&@i~Yqm(=K7`dAmfclJw^6b@ML zr~2$G+bQL~YjUC3m0$emghYt$UHD}ipUh$~pB`&N>2md2wBxgpr(~ET-d9S`NJ@C*&IaQ~+=#K0nTfEj z;!JzHw@uFeA%1=$O`H|sCv&(xICT*4k^qd54tRiEagwCJPD>T@(dQ$aBL=h7r6t{p zn`dc5n(EV#`ra}6(y4I1q7go$K7wR54wa;=CP^;&@L z52lk%rhiaspqSw%Lg{Zbw1D0UBtR*6*{)S+g$;Q`#hs)7KD+K2?7z+!CL$_wG(>7~ zjjXKrExv_C%JLDZt1<^o5iAisf*mH)1)_`GD1mrmzVOu3C3Z-Mh})l*F^BJWwn>pthX1iF+LlXPqJL7+wY#t zryBj1Od$2nr4KJph}p9Q9xcUm&2_gY<1`#(SQtoRa|D&mBLGp=3&Q@)Niih+^uLA_ zEZQ%Y#E#>p*7E5*W;{1@l6?Yq6@wz zb5ZPck^Vovk*95AF-?Fm2J`jqFL34w%EpAtfy`qf08~S_6?(ir4 zkh)(~)Gi7XNb1Zij#S{r!}590ti^tl44EfLKoNmN|Bq`kQX)LP3Bd15>irmCJpESW?Y?_r zK=tj@u^;;Y@6#fSh*)uvd|ZHtWU$CD7hPqJoVb3b*{rq>Aw`-PGdB}_3lCXgG=yD1 z2ol#I@VE0xh(_~_1AFmE<3l}!B)#6zn`r1DRjSoJ`L<7@7&H++7X1)ylny(%jjQBY zhWTFk7b9V9X`48h2rW|(UW*p1fkGUA(WV=^=7R*>N<#>wJ|p_yG^r&sH~S;$IhNUn z0yB7-jgCP^(Y;R{Sz9cwJc-j0%;7@2qd20e`HPLR`@-SLSXJRq6k>&!Ayo+>6 zEB?buQMt2amuJzq*CPka9zfUMyT`D{T?_ph5F*e+7*jKRtg;De4K~Q?&pqW*MXU5B z30avqRsLO!>*FxDX|mwG%cG(o_haMk&+=Tjai_xvCydKuUI4abPbyNEe408%qDP^w z&eAHhjH~{od8OjWvSsc@adujW5~w*e8rC*aPNDSw=D`wKUaLk{7&UxnF6boYHtIIg z36yC0v6-|$15LCsS~46qQ%vB4_GorcL7h&|;0d%E+968R+L4NWa7d$809tGX>Cl>L zV0l`ItDz^_5;jgCmdjHS6$6wI^_c}vKUvi3BX5r4JCLykw~V={+$IC_BUzavNRCT_ z;`TDz5AFbZZlWZ9RX}0JR!gg~9UULmrzG;H=CfX|dou3*=YrcT)Z{r3j&Aji_ zVTQ;nYM?OLbqx1qNZ~hfojn9?Zp1iH?~Yi=!@*ipfZKc6v2vKa^pd1;=Yt>G>{SA zyX7|`K(kY%(36o-l&yR==1|y&$ z=G3lOF`udO=3;x=vHbHQsp;TDrr>QF0{CvBNHxD%cB7qg^^K4$wz`mE23SueOi_wW zC30Vnu_!~j)fZuJ!Xnm#3Y-|EMkaIGpIzYfa_db!c!uiZ+6?U<^&%PT;0Q9$N!tU( z!XKU($6z`AO=V(li|RqHZjNY1%YA-dMt3N^2Up&{HQ@>%#_1)=GSsDypXPboOK27z z+o8*~3kDRrmDYLOKIT*3Lm@9}0_ATT?f`H~yabvg=zL@#4n4)_Xus(6N?W}W(K?o1scs-E%=5KNY_ z765|D6bK8Fl7chv|HfeFYOt`0ST!m0bH4hv0nq@aNYlJKCa+KcTIbRWt;?}+I{`A; zhK*o{X?t>z5N6?gwD@E;`f`qv_lxRf9&H6B=EeW#4@mXg{A&rE*xe1%5J=ZL{)}TR zp}aYq(eeG`a^qq@rE&MC&S=_3)rn9zcW;+busMvN&8KK>QK^Qgx;tNa+hmcJQZ@5k z36Jm^ifg)R!H`-c#;9LQ5n}R5>&c6wY%LHKf<nwmNi3XWtQ zWMl|sP2bV?=Cfgo)*S?0TLMD2W{$rIm{RYDwZWuon#erkGUZlhM!mX&Y*{Z(hY;Ta zn!h-c^sw9@%T`jO+gjU8!#|>RH4!n^4Yj?{p$3o2(LiLO?beav^ZmfJ0`Z^^)}p_*6sIW~O{P`r<`Wu5AQA!8sdElZ3za8Vekafx%KN z#xgbf9vBhWmLD1}nGC(>U#w&$*B&wed;&Zwn;kJr!JtIU`gO*t!+2thelC~FobM#j zq$`#k>|XfsM+EWv15feA1MOYV+Xx}O6-q0Hc!5&u=id1Bc1|vfuaVZHB3-5ADmgl^ zrSvg!7`8>KN7bu!b0kf@xLvQ9`LF3(jJUB9x&f)$nP5<7&*Qt;(al3IhJGJtCTEZt zBq9o?6Oa;X<5_s!0GC7z)d*!F=dA-@BY4GuvR!I@gnsok%!GBgvQ}kOUzs9%QpRX! zDQ2>KsDo*muAR?RF%|>;Tx=@9U`_0{ z3CfzK;gk7z!8JR9sEyMti&G8Tr`Xm7frmLTUrk@ts$J;Rm}IvQ5rEZ9+VAy3@Rn*4Z)6=)>GQ~e9e5O;`lEwer^Kdgg09AkO zNwWmd0UEW)`b|TE?u8=oV%x2V{uy}W( zddU-}a}_ITjv~bT1vHeBZ#dywJfKAK&>g(v`zbV2jWY!02B_mL4Zje03sIy^jmz?z zOv8x(ge_t+bg&$~Pb`gId}3JYN9K4ksvE-Ti&!Cm{pH=VGkyll8q(bma{DD!LWgsM zM(z+Hzfn*3`w|~8@Ei&J6$wjP#*tDpwFnpje;Y%mbMg+;d0)~OhIfi*laKALog>1y(yqo-tg)oZ z>P=JJBwg4@!1)FS@k7c?W&q1-q&o%md>g{sc%aD98rj;4+Oi*;e1k+?#`DBWGoYtE zNC`=|)MvJ8(4{MD;IlA%Le%27nO3rkuFw${>@Y_H;`EL9I-Qi1KR|UjD+||nbcYCL^N?alY3Cw9u{!&{M_TG{R-giRbd=oE zj6U1fUlVo>H3jaV7Vs?TsS$ZaKgt360|orc=N=DYN1B+$A-109s-H6wFGaQ9LaBO>i9`71wVT?2?(br}3mb-An@Mn32 zXE3PmG_qBJDqAn03ORf-SDN?}A2j#lhC=LeiC!LW$#508DUI*$rIzKkIob4jPh0)y zDud=>II-njT`@)v(zekViu^WL0()lt8}>_O=FPZ>s{#F?grq0OvgdJn$dG-y5mVnH z3q`62DP=xn%x3Qb>o>88%Hd7|W}N{I9i6jbQIpU9JshHh3ay>0{rQ-Cv|)e}o{|Th zF%&#RnW64(h)#1~-PR~NJA_UJcZAg=TDDUPb;TM+)CqKjzYOz*Td*v`*E44TIHd8@ zzO`KsX2k*BajQxG$ASOX*V8BS7_r#OXF<*eVJSD{z9$24Vo@r;fMbQO7n|RExRKnr z{UtD`tj`chMC`HQ}7L#54a?K}oiBF9r0d2#GEx;JgNsH)y8hb8WO_8SpjK8~N;%2Rpw< z%8LSlq@T&}u=bFd5z9ihoBFOIq=-{1)Kp7@&??%E_6O=^<|nWY&j-n+itT}WopxxM z`fi&{BVEwn<%mF@ubtbbNF`FryW*HF8@7)Zl4{4ORX!;K3arjD^&A40*t0&Mu%QpH((sB zpmEIzjCSRz6B+6R)HiQB!rmZeov0}Y)QKY~kfZ4GbN2;fMw*~Dxx>q{ zBe9udM_%YOVGnmjJ0j!ZX531ia{Y*=Q#Bw;cKn@P4sOYv&|AzNCfk}Q4x&XA~deB_vpuA0B8ZkAXCQ1 zv<**l_*H!(n%!TO1}O1j(GPUWCPSoOaKwCQ;lNi8OZEC_-$iejvX~xQh?L8ppwmO` z-s-zV%oG%-Dl-T#cJ$X67`zFnO(N6{3KpLj-tgh?AB>os^g{?#kcz)nDLhjsjEm0} z(%88(Xe(M$nk{2y_e~^g^G_6O_^U7|dSTdtG5E8i4D!ziZypUa6l&Y#tz~?*16iL= zax))laDIipE3bD=vdcAQic)i!YbSKTb)^nxj3S-kDnkvpVP=c7P%{_jTo#->%vImb zuaT*mtX`5~y=b6M-bbJ|zapwo+J1KIPkb)NqgV5glD2_s=4%OXtOKFY(FX|^~;)HQ})&{c;(=>+AGS7J$ zRxwru|AK!lG0v&kC7l||ylmyb0=~?>_b zF)Dt}UFUsYUFv`FU(n2JQg$&zQUM{=~QOpA@d_*!89!670cVt%*^5O(<1+`#~t_UT=ms<5&F`dtb+JPnaS24N}3M#!(#J!#uQweF774r9L;Z`4d@$!Y3(eo zn>iz<#YKcDzb4d>I?R9#9jVYWpdS(C=+8$8cs^jZq{0JYWae|Uj$E`pY(rCGse6fw zv=KpSPg}FI@oS0b&aCb0L+=X_daB4Z7mHgnpYNI7M|qLIJXW8KlE(Up)DX3`$FwKa zBAp9OOQTJO8O*%5H7XLy>ans~^puZ^r$8_3`+(_&>{iR)JEdx6_~Zo7 zyDzo0bXblOXS`VoN5JLGK!}Q)P0=~I%N6@2OZl!IYl_+pyoV@^j`iT%T+LX8r6mzkyAQrW@cZbO4#8{(+qLsO#`Vpu zIVF-?bQa%igm>os~pW`1GIg;CNH!aT3@knpzvKgh;HZG=lvqWc=^9hYe9Z*N_ zJNJ^(E^^rt!O)99q%DHpc8N2L0vGZM)#mu31M<>u*b5C}7+(0i1w*JOJ47Y?N-{tO zQnH`ueB@%oc^wke0;u!X)L$vzeu-x)fbP1-GQv8H?pos>$b0q_r<(F zRopoBmT?jguHr!3lZw`t6l4Pzdx(pVd2MWFZ&lijckAOmTqz(+YJSk@e-)|sk`beK z86Qor^g;BLd)L8O!q^5Q15}927B}&h!;7j{^snyJ>m(|m7wzdx6mz5Wu;kJ?beplP z%h*qFxgm2Gb(~vC1H*B}Ru(87d>3YheO|gHV>`ndgpjjgyITXb`3_OO;b|9%Caj9bG>|MNTMArh&p|2&vI1qP154$886LnL^VB~dcF z;}Y|2R1MtZ*a#ResDJ(j=0_VYm3{_A%Q8N0j-7HJ0vjU5BUWpbE1;8IW5#CpLT>HWw0VZApLMVQU4_0hbd*>>`)1JJEZhxDl4O3 zS~WzTvBod4o-BsZtHV z(Vex%rbD)75zdB5s>oxNTh8* z1p$TZ=dX)oP3AG>@1DCjH*2r7;+7nc=kI9S5d}lusREsFOKuVWk@DY{@9YOKEH+BU z2F;_UPV&&bI;NG}I?hHKuf z&2C&)un&Xa#<#z@7X#Dq8Td5cErQFOg1+My)ZLVw6pLmS;}@`eMhT9P)Xa_q^4&Sc z^~ZFp)AK9T1dPbZdtM?!IjKqiv(D>KCpy01GlUAkma%J#Kc}(%{%~lg0_As8QcA6^4iHI|coeK7PL@TPw9}Qob#M zexBl5HgJjOD6qHstMR8pn>}mMZ5YYh0=!5YzzMi?Lp+b@!c9Af7^Q4e48tB8<2VCj z9p->0W4*IW!wi|Jt7dL=)KXuS1}a&EX+%``nHiJuz^Y1Q+YsdM6mTn#aSY81CBTFD z1}ax<`Bs!N#F9vMXb29*3^vS$daE9A`nv-ii&+K|f)#}h3Jy+}KzA;jAF{`wK7pDc zJZ{kdrcKj%SXy9~BVIYOB7v)PEnU@B0DX~QKICibFJD~u)do|uMBE6QBN3u5rWbLy zN!GMh1X#A57Ar~hN~rLOE!#%I=Iy1C4Xottcmg4NmEhVuxrspC{!5?VWy91Mg^ruA zJ~~*s^ZOJ>j9{mE}swLZ`sm`Ei5^BeI+w z>ZQ-fyF^Gr+`6ADt%tAuOkahIa!+!CnhahjO0>6FOQVdZ-fQYSywPg+8E7mVdC%kG zfc_J6-ztu}0Z?1k*3PM!5=)Lyz>TI`BKe z!DM$Xbq77%!&=`Q>2{flG=hbq)VPd2wD1<=fZv4;HV=DO`-P5L?0N9O#fV)RCRNM0 z_v+h6p~S=IBB-)>d)dKFaLfPB^-_y9Gt8#@f#=FG!}e!Y4&x`n-L5>9xi_Atzee(F zwuskQc@sV9tX->|X1`>>&EIW@Xi{ANK!CkeOl_(E`(EU^nvC+6y=h(aL)QB%T z8ot}_4;_J7_KgR1$3g;neV^YEbJ?|^UzzxU;C}%MK)ll51XZ5ISFk{HIum)r zeUyR*PO)(47ML-TONEY_Mzk**@OkJ^Af8Xnb!ooa6@@W^M@{LByTzG6*3ifz+35+z zoC)xuulKUv2M|=XA@>npvg0Ft`j%mlNfzx?Wq0nn`Rp==M-6ik0Hosy-51`PweoiL ze;Q-uFglMlt=IO0e>xpZg@Hs;G&L*)<_;i@8=UdqHBDk{9)vJ*&eT8el`kvUL`-E_aSY%kwVCB&u5pip8$Xz4S>&qA{}Kw%9s`JB6RQWt*`?b@yK?@ zy;=xx8FwF@{y1s%+041MW>UJB-T=z?2}dZ}G47hIl}*2{HQ)=M{_u*Ll)Yl}aQtjLun<&XLou<1#fs33K4Yeh6_3wB~3 zS+qnVq_29rIdtJjMW&y1A#4f-KfAj!pW%B=1{*$RO{wY(hsRJ zs=ZHwkR)%>Qn${+VM>3A%dxr+q;p-mSlIVA6;O8N^?F^`bdtYO6&gFJcKlr7j^FAq z4@98kTNVoEaw)1e0>&feHLFHJ2pkO>Svxl^3%?ef8!p6D+z~cHtSeU7=$sF^|MAmB zx{yR!f6@Tn=~uSCMUV?IV%ru@T0c$L>dQ7k*Q+4<)g2k20oF$?>Oo*-S>*t?X4aHT z!C}EJNX|)0%{61soxD9ti^DuBP$g2y%(|U`H-Z<_ME*x^#ezU5V77iGt#ga6aRL5B zN?K+oJBWv7x1l47af);Om_62^h}A(6qMbS36AbA+c*9DNZIs-|rWjy+SfsFMJ;%m^}2i^Bcr|6H22 zoCU9oS1+kZg}CL8eAoUjIuB-$4i*RYzCNgsn+oy3NA{MOwy~L|ai2RJzm=`;%5p$X zXbetyf}$=!CjazKIZGRIPc?K7cBVj{Ztyz|CVfD72FcNSg-&~gBINZ!~NOXHACp_h4q5FNMeJAJUO zAZ3Io{+&B^iP~+pc(B8k7p__Z<7H=|2}NYeo6BV3K45dQB#&%%8)UYN(882zfLwq> zQ~sqDR##X+dd`eDc4}X0M)bl(Z|>-JRQ-oI7~{Ku+!GC@PvH!^K-fHX1js7N)!T|* zHCYMHBWwBE1am!c<_RA?gUQO5$HVp~r-?D;qSx^324CA^_EDK^vUrm3l8Y+YP~s~f|# zrtg~|kV5a3l%>?~_I1(n?=r5N4Iot9NnBtg?ThM3<0jJbz(*++HBC!T=PiSWw#AFO zyQGwpnaaA}0{ODvs9s#JZ3oRm9HDCuIx^sfcd3q^D+DAg-{;IZR|sU<Iuel$6%UUHINgm;7zRoG1ghMD~OjoUZhrk8b*Z4mRtxf3tDf|771FMG`j|i9Sq?YU?YJ|*F<1H)1ZARi!u|@({cnOzp?+yDMfc-8UrdWA zNmQp>TeX*!|65YMMf$q|UtT6e`*a7;nl(#ImRfmJ>RR3pxSlPdW(%YR^)oG4H1U2e zraa$$CGasQeMB-r)0e1gdBac_d$n2`wz!|);gRxSTIj(J$>u;=5LhX6#=cDMX2SBz zhsW2QB%+W9>Mh(yn)#);m$lP??qC|cvejHdp^AIM{gz7jBuT~FSg=nFo|_O_*jRKj z3P`pVRz*zoVW`lGfU#0PXdWwSqyxvxZ*EC+z6+r1XxrFtkc{B3pojqPE>P zXl1k`NDq!&b#wL%dJGnHVEKYTI7ZToo~Z~62OZs3N^Kp`g1U)Of;j;M^5Q{@*?*po zIm{MvJ+1T56)Yt$5hw8K+7bGGpe-PDi&qD5t9QsiT@&D20b>7?;({>DE3?X?%?C;0 zGjX=6D2)N(f@=87>VKnX0iEJ0 z7UR0_$zoX)hq{1Y0OVfl%Jy4>y|b_ZQN5ijqPNCAyAhT!zn!A)Rjr*?q0|t93YN3) zEnE3}bysZH_;7q~X;iZlz<*_ls?Gs_hPUvI^QhxX0+*eyzzKj{rN`Vz;PQ@aul3LMS%)! z4R079#!dx%e~g*Amt6iIMi<4)nh=^{voC|0;FpEV-~%yr2p3j&Gq$r07-$Ejta^=# z$uj`ZW4ci|l6rF%ucgAAkf@NC3ZMD3(_4&!Y7Lk#Lcu(5W0ei6iX8hedvobpCQ)L+ z!AUTKQ+(sn>q9;wx`*<$SZC@~w$hBy3np|UieKu=K;8mwCQVv=ifsk`K_EI&Qq`4E z6s}wUa4$n54jPe{*_5z!I>NS)GFWtETNg5yP(gT`b9$&W<+?1L%cYsSPgQQ~2Sb|m^Z%3ZrGG<*H9-Q{YY z)3ns^nDtpw^|=$YA|oktJ5HH`*@8sz1>^opA6YkW-N?`0po~xD7E8K~c1D>S)hEyq5D9YrvjpyT62k`r1^h<!w>M-xSjOzP5yP);igJ?Wd z!)>w2uqI`$)%ZoO|A^B9#JFT&%hho&L86iS(u`^TS}CYcWvso4DK7b_ZBT$+WfMca zI~iiMj<_?NI%dhC-VTK!xK+qHbeKTHP4qkGTJm@CB-)>RZRHnMe>>xE4!(+wkns7- zn4bv0g2lVuO=!P}Q zs9?M0BIwfMl-6OC>E5c)n5{1tYyeyyiU4}|F&WRt7sCD_AEi9_k;7*)*5xB|^!&+1 zR=YP^ggSqsu$9%=c{`EXHK5-^1Ug?CY_gNFGERZ-yx{E*8tDa$`#Ls=nHtk@*xkCW zAVx4^zCjst?`N=j7(lhK)=$#%Kw7WNXh4!v5Y(1)R5lxTxYnObo@k5;)gyr!?KoFH zrTu~GwPn+-?uI$?y>TBjXeRdyK+u0g8w+u=UHa46aDQUPN$24xVztFOY_<03E+Dt6 z&IV`Sj_XExuyAl#&}M_rr9A<>q|k-6!s>ULUyyzc>UO|E1Mu8{jB=9dNae!J88R4W z@`_uKJqiB1@~T~g3+R|E2D~pyU{EGyoH$y0@Onc@Qz=Jj4Hhdf)+lCS=o6lu^igrZ z)FS&(wC`5lz(b%!@H^~cl*CJdpdFF|k_f1aqRZ<<1FF7T_$|+Myb0jTzYkyK*wP2tcu0CB?*&hIU#A~CU#QlUnzJA=qWL;|HrU-IEd?aG=L zlI~j?$6`=X@;`z*t&auKEsHc8G)@T->Pqgp0a2HB*Gi|rAa(vwQ#6wS zHGE*+&LtBUAqY|48U)Cn-Po)COa#+Mh*egQV?bq32_#TKxXX4DckLowd7}~(i)m$f zo*mBOKl?ySmPITv2DlB4RQue9QZ%-7|OkiB#-i^*mVh#G1#{&!w&lmGF^$+7F0yM1A?q)wwnvpLCHQMOQ1fqlKdANxQtzh}?oyqk}snq6|ArJ-%Y;)}tDNkL`AR3j2JAb#418 zw&>hki{+i2yTQo2#WfR>g^P{p{z?#SmCjVH(TQ{1|I)pSEhp>s(w1f%C-^(!ie%J< zvlEvY#7(1R8r(xpQ^uGb1TFV z_2BFTq6?ivL`JR}_N2M-P3KNjni)Xdo#2FLLg02o>R0%HX}}ROVhCnJp6ep25qtfF%{bs5w#YWU?9XUvt_O{K9bn@&7$xmFTFvhn1#4K z*mdm&4xfDX4x#1VoBdAG1lKkyJ!O=%CWj9ZyHK$wD2ZzvMqC$C34thW?z~GC zbind_{(DVl;6e$DGuHC5G^5%{D9!1YiKX`RIV~%%nlB|9_;L%iTS4Pu^%+|BkvT*l zfBm}Dm#{yusA)%Pvr*X*U~^E8dY5Lc*EiOWt>ft8zBxvZsBAKMa2Es6m@v0(^P~7L zd1<%+tQ3P@x?Is7raCDDP1<%~6^m!G1gy{1pvdvy=$oT8$l!ZmNxTSPdGU^%A>Dka z3vTg>m?T>$8z$Pb2ZASV}E^>54SS<{0Qw@h~~iWYtMWKsdN z<%?h|%IZ?r*iUGRhBr>LRTE|5R!I>Q6z|f)*o`~ZX?%Q&uyMtDggqIeMIM9q`08XL zj`olu&!JyIR*ga{V0rqf_ z3F&mss4fO%eeMN~inHBIseP-v_Ytniy4K+q+mbz9e2~M9v_e_k0#usy2%EmzwT9@d zP=J9+NAR!n9dmf180xiUUM* z)WLa@t(E!&i90FjkwvzHL>Wy#7$|P{#AxpggjD;9a)6_~j>@f9&oWXe8<v5NH99tC|r&A5F|Jx_?Pc#>!H z0Z3IG^OePyi}LX0(UdD@^faq_$f|Gk(P%MYZWsgj>G?T1JL{z4^$D4jVuRj+gj^p6 z>dl7C9S6taLHR3JP8*)u+pOr6V-y%uru)pb2&9XITPq;507;2@50d~>`>4L#*SbLC(XjsJI5qFMt+RPR(P%8%LGF8u$khhzOit-is zY@A`^5T~FLFsZ2FP%INx3+xq9^JcZ$eR5ha_L8xBl7XWwmIUPYAz?2f&R-hSNevNJ zlKkknb~ibr9mknrj>J*Z1K2OnIk_Z);MUT>8u;~(yZ4xTx2GvWxp_rzDy6k= zH1r`Ec5Rv?vw2efnp!Pf+IQ8u0t05cX-TwA`cEucpIOzj#)oI5pr7sTZn+Dd{PPiTst9Glz${VfB_Tp!Vp<#C2YiiMd* z`MwvQ7r#T`X{BtF``$x|Bux}vFeG9{o6}~_BjErxPBlC*PMCQW<=Dp31OcV(m}PJh zsMzGhOT}bTnV`+HICCEb;?TM^-ids>^=+3(LX_>L$1qqDdSFA!G5Tp!Ix@sS<`mM8 zJcr-Jl7k1Nbux&V+lo^#oeCbzf?r%O)9E@r>ZEXLiYI9DyRIGVAIKA*ji}u{`-w@f z861#2Hl!4m?$Fz=I1MXa7-U!!Z3kHR z>aa>;>FE7u5+;JMR%(9uzG5vQb)u$@lq3J=Z9~oi`DVZ0VdX3e6q9xeC6rU;C2Ae3 z-kjUeX?QB21%Zm6nxYLeR49mv;?wTqhu|q)W>$k*md{hV?aLocJnseugF5qC zO;Y}0kpuu0qaiwJw8=>Fz0zH_peU$m$^fUUkZ|>S`L!~4hgjqlq1HGNc2)wqbTJAU z&W!AzcKzO%HxzUDI6nl`di$pzYBX;Aoj?UXA;^>(ka53d-Hg2elmh=9w%6;+^&%VlSByp9$0*gbdm}W1VmnDSH_sg(z9Icxc z%$V*JUshN3ZA)(Njwb~Ra+3V!`Y{vml8rA`2>kJ3+IMpn7b75D_xwXyAbhIHuAIxW zgA{So*@vW^4vXJn7kqcj*hyEaQ1|Uh`GVOF6v7F2u_2^AQrXw7yD_=UYr_RTZU}Yv z2jt1b>CBj+LY@_|ard{gb(&~dte9WriSVg>0;r8G-Q z#Aa<|*782*1@HTx9kqbyKIvtqz#*1X%`m9g2csl?Y+}p5C`^}m$1lcj8+IUN4^@g1(+~V zZW55aB442B*9FGE6Gx=r{vy3WulE$RJvwR@t~ z;hhkG#$=>&;-dawB+}>A3oz;JBvC&QJLTNGwp=?qXHpnfF9jAfl}H)Sv9QQhDLP37#6{Uq|CMMTGxXnd0HR^Yci*gT`|aVL zd?F3ls^jNl_gy4gvgM%1ldML}pZe3cX(-7=XV{(Ze;p>(77t)y5SA;3eFwH_8U%_j zNuu4suGG-eRHgOygJA#z zbo6xAI`0gD-1VOmv|{PO)Zi(jk^ul~dvXJXDEm8u2xN-)#O zRD9rzp(y0W(hB$y)mTCgnNw4AdaerOOZS7MF_b(kSb8Zp2v65!3`fIP%Sx(XBHbjYz2impt6#fc)kyt_$szfEYT`eRK9ITOPMfFM2R#_g>? zaudh>5EDiw z&dd7xGV%f3&@VxQ<*k^JX0T>m%=>(`7-)UJTK3avY!P=@0e{?t$S-@l8BsD5j+&9a zx!>sKiq9RSEhIL!?Yns##!M9KZb<-ot{M-+YUuWTGhE;!ERE$1A6 zHXQ{WuF34FcW9V=FA96E&^p|s)w8&Z?r06`fejc2RcqPYDj$4H3K!}f#Hh*gQ^C8t z6U`D3*C5Bt#tqhet>J^-n)7TP*KOry_N(^rJqFF0^C~~C2N;mQulZfb#2*bo=yd{Z zC?3>QjU>m0HD_@k)`34d!p*|DR0VoFTe^nKu#Fwn^;hF(s;oUJ8w}w(b4A~=l&Wz3lxo+=>kNFho00Lra#Fn zbY2J6Ex=Ma6E@kGd(K8Tk9Z;lc5ieGtmoilr)vsX3Mf>Ae7hh^HK|Y~Ag8#@C5wnT zE-<O6S)ZgsD8kbH-#*v!FUpXoPIA!+63K!}Y&(E;zwHCnIgpsE(h z$kz>(%qVo_e+l2mvR;-|&gC1QC-J~%F$r6nkN{wK383CU5bG_$c?e43Frk=qF9$gx zoVpnu?kX{Xe)44sne(Nj$ArL-02(lN>W;ZFDFvi( zRd%g4&l}{MKulRk0h7CTV{$h!OEZ{Ekiq81Dh%`~ufXE38WO=TGu_#;^fm8Pd(}hA zl357CPml#VAAXbs#Ei-de;*Y%Vl1HNeTs~$h5o>Z<`n*6^sW~Y-*9$Ca&DxF))-e& z7={IT(dyCiXEzcaA$XRHhuI7u62va=w4ON%`lG22aI!M%kG1QL3H9mRnRQW0dp-YX zMn%MKktr~m^ssHo5oP{1Jl;zdJ@*(ywg7)7f9DW>IpDGJz%GVTJ7oW*#G>D&){2Th zk#E|V$(2-)K|r#dN#JwQzwm!mbra!iMw!kius2#Z?`WZ|cF;NXUSMVRlz<7r(S0)- zly{B71WxZveg`KpnW}5?I+3DE!mW$vbekghw#zJmsR7JTltOG`UOwgyWiO~6um#R5L7lL5vv6E^i475O%tQRdIONPzku`Nv0ZF zf3O5*lkRi+I(gHW?{1ZiLUzB+Q}Nlr@{seEBj_(E4!RKJO##f{qn-T=1#mC%9anf> zL_IR%@}JSmpI|>hx7b=6^sQpaqaw^IUyZ#!>}RU`iE$^XEPxVCy*UEIq$i&lcL^3@ zTcXSbj*np-AGU$zHV&cgcsp4#*JX6_O}vd7!5l86RPL=`bhMM0y7f5vBj*Kg|9yV= zygw%mfc4kRtYqfsA`tVRs=Q_U1d-M(M9zIe^k@Mc$4$SkVygh<__bkAXii3e&jup4 zJ(}n)n^{7J8w`_LB+jZ~;$nZ=D0DV12c+MAWA4w~HLvlg{QXg%v!~jv*(4VwujqW3 zPM_u3JB4s<3|+l~TH-IyO8mjd8!}la%fd6N6d`@}A(RqDi2jL>YQP*`E!>7OyC}Dv z?!VAFpOv~~w^q*6wj`HG1&3LA9lNmjiqYI${~*&>flpUIwp@s({hCTUDATa*i0Ji0q9@vVb8<3-;y_M9_GlYgRNk;o5(V7 zeYOeB=3vw?*=$*=?*=E#VV67{G3Y`2hUh(cC*F@o_U?&g^84y9T0z8NWHyt1k7|KYko#e(vorBbH^`H)5j}{9%Wd-6dDOxCtcKId zKHDw+yKW{CJ|&@X23$#!5K30W>|Beh1mzDoeAza(O!EjOCpg^dqWn^Z(YPK$_&_Uo zKluzmOtMu#vEs&YhK>?5U^)V`)=h?^ode$PnpBA}fI(^60EiPx{l{XRW7S$wF_@3Q z^-GKYzw3Ahm`Np!=x1T)YF2bQdi2SdXvlAJMx|SL_c8op=wna`B5f5ddK4t?v()T! z-KRUkt%AU_cA+_THA0t7#UVAaJ4P|gWk}&zS$rAF)tNiDXT2bbkgMmLL!o zJlRowf5+B&DE^@UIsa#u8O|jEZYZc=oCM11Rc!yie83P1V|DtWgsKn|X-!NFk9~0M z(q(}BFyZ(V(ge8o*ij93?N4PsRwZ$mfNxf`(MFY z{xsaTL=0@$eTuuQ_>9VZPz6V)ho!pV;fW+~_g7euv6gdiz`c$RnMHC<9yJ2}ICQLFWFXSn*3m=|0-gL7#SYr?6?1!Rm}5F_X-$ez}XQ z^WI2#TxVqK;Rcs>P>TM{j&X1*m=H^7YCoB8k-nC;#&x%D91Rohc()UNi(6Sha=i%o zkETvSw-Yn|gt-W$?|wE8-)s)LRS7*k)`o6^fyY*okf%-HJu2=CZE(#h`i%(cWJ`ak2I1OT;vJ5ktQ$Oe@1wPPw^*-fGFSOg zE`7O}WyLHo$_~LnT{8E(l{tE0?H%&rcDMbK!#*d4>@q7sqq{olJwom(0KW*sd@O5( zX?&iO=!6lIL6WwZL(KVVz6py%Qa;Dlu+F;38UyKhZ2ZGNrMm4Xm!YFD}cBEfKo*ddwXaTV1j zrnN{+qbn(m?=cNFJJd)R{z#F_F$C=^+=PawiS%UBA1p;Gv{u7_ zA(|zxY}X9b%nZC;w3K8sn-MH*k3^icC4k{#dVO6P0M=Ym99LWDr?@308T+M0&e?^N z(^IB5oUK=0$N4}X#PDIjC{k;kh$RI_l1KUgbjUVJb&vV&p2?5S55ND4})>i z$`Y>fDLEq&9`m@04mM9ZyC!ti5P*F)^Mt4#Ga_ZA6+bWn$XV^NCmEl=4rW^45&rv6 zC%Ot$szMr=lKJ$jtsGc1TNj55ML4%gyn&&N7S z&%*t2lxM|Z@Lp2n3FS?A+(~KMWU0?x@C52nw2#SsZv(9nJw)L)h0^-|`^%bcR4n`g z*&Cc*ifcWNK-HuCHJITJC&qsyk|RmRu^-C>?Vy`@f)UyJiE6?q

kEYL!Aq19>19 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%0ATPlXr<#!F2A;c^ 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

r==o%3lM(jMEGsYWYIxOj1O@a| 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

vEn$<*+k0gqU*1WDhi<4#2H$ zF#S4&a_<)UKj6z7NQR#Flmx-K+9b0F?a7DKqUA~{2q{-*_yP2luC~bc&3{${Gq0zQ zpz4!C{2}eSV@2Acy?MDuAQsvN#CjN8+rbDEGzTwL?j2w8_Yun!b8U?mpfSBHsE74A z&ai<%XQ;g$_i>aGzzsoN%Ml^(zt{b1kXl2=neSM3ambld35NtGF3f+in%-l4Xf zFpDf=>z9+!TJO+`EnXKuRhBm^Mm|Iaf-*B*m zCndb3w_%a0^EUmt5wnE>w^vG(ZsK{Y4ktk>ebt%))18p0%PD=ys7hmq7Ue2NDVRfl zX(8zu3@{Q$vd$keW-4=DtlizoD8ZFFrWOKGq4R#dUgoUdE++B^!%@^UCKILJrxqO= zu7*-{c;}l!FQ4MV4gxAQOw)AcK>N7JVw8#@rgQ0yTJ{v3)8(fdRgbTevl6)3U;f1BtGw75r z7LoZ^?{H&UW=OV9&2Yecym+Sn&&C6^jq4r5*(GDm4}WqcZK1v|$B6i^rxn%6n|UrN z86?+)|F?jf3Mlsej-iM0S5e|nmwZSp8SeZhh9S)NJ;^_*Waq4lkApr^{8*b>iiNkx zj=e-8lpO<;8dY(v7ti0t-iMlims5@6$YI>^i@;BiE{NBkT91gkYRH?#z9LX3fcssp zJey)B&w{I(-7(kL*Y^~5o#SHzIWo=fn`c;|yRTXXsGn~T27H&h<<25{yN@nX&1{%H zYGC&9Y0={s9iVU#xW)!^mb=Ax=fw0%r7e$L%5qZ;)Yq{c$E?PZtaGm?cN zo#20T3{W|NK&mw#FlG|L_<=RiU!Moll! zB=(4)`^BNvd7u4?SN(qxy6LVX$^RhRD>SD*U?T`5C@Xty(pD#6F24e;=w*j1ZG2NE z|A=!1_*u}DDWVb7d*KtiUs?&DU0@v@59Ib=ePDllUbmWL{nY!&FZ^xC1sP>q4~=bt zDCV9p-MzkTvSUm$)GRB-C#gDCW76JolF$DJLsgH2xx^jG^;Jc-2Wfw%=3*E#>&79R z=9b~kS!f#|#)n=uLV$b0&&fD?jse2AbaDTh)~oz~d90dx2S<;x?XNVxJv3KFxCAN) zS&L>6XX_D6KH)5;G=GW|8WW0m7|H+1sclGs5FGwb=jTHu7K3(H>3u|KMS)5%%^$zm zG>J7dg+#EKP-u-B z(00-fVTEE~6;+Eg5F#!fTUTe)E6z*TISri^b;FO$SM?LSTc6c|9_yT_toGmqB$eTs2GdMoAH`KxdH^ay5Mjeq2k z70^8;tJf7sO;s5lGj8{OYI0^je`3kJ>k&iDRG754E7PtaD(Rd zQ`K|RjL4rt@-gAnhe9msDb>=khK0C8S8=Jnd-{<4gi|TBaWJm-!oA_9x?@(7p7N8yNWbVdi&kck)P(wPPU^EZ)Bpg$ C41z!a diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index 17f3dd57..dc7fea68 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -16,6 +16,7 @@ class API extends APIFactory { use \danog\MadelineProto\Wrappers\Login; use \danog\MadelineProto\Wrappers\PeerHandler; + use \danog\MadelineProto\Wrappers\SettingsManager; public $API; public $settings; @@ -23,6 +24,11 @@ class API extends APIFactory public function __construct($params = []) { + // Detect 64 bit + if (PHP_INT_SIZE < 8) { + throw new Exception('MadelineProto supports only 64 bit systems ATM'); + } + set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); $this->API = new MTProto($params); @@ -45,6 +51,7 @@ class API extends APIFactory public function __wakeup() { + set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); $this->APIFactory(); } diff --git a/src/danog/MadelineProto/Connection.php b/src/danog/MadelineProto/Connection.php index 2005e527..5e1905ed 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -162,8 +162,12 @@ class Connection extends Tools if (!(get_resource_type($this->sock) == 'file' || get_resource_type($this->sock) == 'stream')) { throw new Exception("Connection: couldn't connect to socket."); } + $packet = stream_get_contents($this->sock, $length); + if (strlen($packet) != $length) { + throw new \danog\MadelineProto\Exception("WARNING: Wrong length was read (should've read ".($length).", read ".strlen($packet).")!"); + } - return fread($this->sock, $length); + return $packet; break; case 'http': case 'https': @@ -193,19 +197,20 @@ class Connection extends Tools if ($in_seq_no != $this->in_seq_no) { throw new Exception('Incoming seq_no mismatch'); } + $payload = $this->fopen_and_write('php://memory', 'rw+b', substr($packet, 4, $packet_length - 12)); break; case 'tcp_intermediate': - $packet_length_data = $this->sock->read(4); + $packet_length_data = $this->read(4); if (strlen($packet_length_data) < 4) { throw new Exception('Nothing in the socket!'); } $packet_length = \danog\PHP\Struct::unpack('sock->read($packet_length); + $packet = $this->read($packet_length); $payload = $this->fopen_and_write('php://memory', 'rw+b', $packet); break; case 'tcp_abridged': - $packet_length_data = $this->sock->read(1); + $packet_length_data = $this->read(1); if (strlen($packet_length_data) < 1) { throw new Exception('Nothing in the socket!'); } @@ -213,10 +218,10 @@ class Connection extends Tools if ($packet_length < 127) { $packet_length <<= 2; } else { - $packet_length_data = $this->sock->read(3); + $packet_length_data = $this->read(3); $packet_length = \danog\PHP\Struct::unpack('sock->read($packet_length); + $packet = $this->read($packet_length); $payload = $this->fopen_and_write('php://memory', 'rw+b', $packet); break; case 'http': diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index a7bea56a..5e157141 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -32,14 +32,42 @@ class MTProto extends PrimeModule public $settings = []; public $config = ['expires' => -1]; public $ipv6 = false; - + public $should_serialize = true; + public function __construct($settings = []) { - // Detect 64 bit - if (PHP_INT_SIZE < 8) { - throw new Exception('MadelineProto supports only 64 bit systems ATM'); - } + // Parse settings + $this->parse_settings($settings); + // Setup logger + $this->setup_logger(); + + // Connect to servers + \danog\MadelineProto\Logger::log('Istantiating DataCenter...'); + $this->datacenter = new DataCenter($this->settings['connection'], $this->settings['connection_settings']); + + // Load rsa key + \danog\MadelineProto\Logger::log('Loading RSA key...'); + $this->key = new RSA($settings['authorization']['rsa_key']); + + // Istantiate TL class + \danog\MadelineProto\Logger::log('Translating tl schemas...'); + $this->tl = new TL\TL($this->settings['tl_schema']['src']); + + $this->switch_dc(2, true); + $this->get_config(); + } + + public function __wakeup() + { + $this->setup_logger(); + $this->datacenter->__construct($this->settings['connection'], $this->settings['connection_settings']); + $this->reset_session(); + if ($this->datacenter->authorized) { + $this->get_updates_difference(); + } + } + public function parse_settings($settings) { // Detect ipv6 $google = ''; try { @@ -161,9 +189,13 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB 'response' => 5, // How many times should I try to get a response of a query before throwing an exception ], 'msg_array_limit' => [ // How big should be the arrays containing the incoming and outgoing messages? - 'incoming' => 30, - 'outgoing' => 30, + 'incoming' => 100, + 'outgoing' => 100, ], + 'updates' => [ + 'updates_array_limit' => 1000, // How big should be the array containing the updates processed with the default example_update_handler callback + 'callback' => [$this, 'get_updates_update_handler'] // A callable function that will be called every time an update is received, must accept an array (for the update) as the only parameter + ] ]; foreach ($default_settings as $key => $param) { if (!isset($settings[$key])) { @@ -184,36 +216,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB unset($settings['connection_settings']['all']); } $this->settings = $settings; - - // Setup logger - $this->setup_logger(); - - // Connect to servers - \danog\MadelineProto\Logger::log('Istantiating DataCenter...'); - $this->datacenter = new DataCenter($this->settings['connection'], $this->settings['connection_settings']); - - // Load rsa key - \danog\MadelineProto\Logger::log('Loading RSA key...'); - $this->key = new RSA($settings['authorization']['rsa_key']); - - // Istantiate TL class - \danog\MadelineProto\Logger::log('Translating tl schemas...'); - $this->tl = new TL\TL($this->settings['tl_schema']['src']); - - $this->switch_dc(2, true); - $this->get_config(); } - - public function __wakeup() - { - $this->setup_logger(); - $this->datacenter->__construct($this->settings['connection'], $this->settings['connection_settings']); - $this->reset_session(); - if ($this->datacenter->authorized) { - $this->get_updates_state(); - } - } - public function setup_logger() { if (!\danog\MadelineProto\Logger::$constructed) { @@ -243,15 +246,14 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB if (!isset($this->datacenter->sockets[$new_dc])) { $this->datacenter->dc_connect($new_dc); $this->init_authorization(); - $this->config = $this->write_client_info('help.getConfig'); - $this->parse_config(); - + $this->get_config($this->write_client_info('help.getConfig')); $this->get_nearest_dc($allow_nearest_dc_switch); } if ( (isset($this->datacenter->sockets[$old_dc]->authorized) && $this->datacenter->sockets[$old_dc]->authorized) && !(isset($this->datacenter->sockets[$new_dc]->authorized) && $this->datacenter->sockets[$new_dc]->authorized && $this->datacenter->sockets[$new_dc]->authorization['user']['id'] === $this->datacenter->sockets[$old_dc]->authorization['user']['id']) ) { + $this->should_serialize = true; $this->datacenter->curdc = $old_dc; $exported_authorization = $this->method_call('auth.exportAuthorization', ['dc_id' => $new_dc]); $this->datacenter->curdc = $new_dc; @@ -273,6 +275,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB if ($this->datacenter->auth_key == null) { \danog\MadelineProto\Logger::log('Generating permanent authorization key...'); $this->datacenter->auth_key = $this->create_auth_key(-1); + $this->should_serialize = true; } \danog\MadelineProto\Logger::log('Generating temporary authorization key...'); $this->datacenter->temp_auth_key = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in']); @@ -306,21 +309,22 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB if ($nearest_dc['nearest_dc'] != $nearest_dc['this_dc'] && $allow_switch) { $this->switch_dc($nearest_dc['nearest_dc']); $this->settings['connection_settings']['default_dc'] = $nearest_dc['nearest_dc']; + $this->should_serialize = true; } } - public function get_config() + public function get_config($config = []) { if ($this->config['expires'] > time()) { return; } - $this->config = $this->method_call('help.getConfig'); + $this->config = empty($config) ? $this->method_call('help.getConfig') : $config; + $this->should_serialize = true; $this->parse_config(); } public function parse_config() { - \danog\MadelineProto\Logger::log('Received config!', $this->config); foreach ($this->config['dc_options'] as $dc) { $test = $this->config['test_mode'] ? 'test' : 'main'; $ipv6 = ($dc['ipv6'] ? 'ipv6' : 'ipv4'); @@ -329,5 +333,6 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB $this->settings['connection'][$test][$ipv6][$id] = $dc; } unset($this->config['dc_options']); + \danog\MadelineProto\Logger::log('Updated config!', $this->config); } } diff --git a/src/danog/MadelineProto/MTProtoTools/CallHandler.php b/src/danog/MadelineProto/MTProtoTools/CallHandler.php index 0cd23c5c..204e112a 100644 --- a/src/danog/MadelineProto/MTProtoTools/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/CallHandler.php @@ -25,6 +25,7 @@ trait CallHandler foreach (range(1, $this->settings['max_tries']['query']) as $count) { try { \danog\MadelineProto\Logger::log('Calling method (try number '.$count.' for '.$method.')...'); + $args = $this->tl->get_named_method_args($method, $args); $int_message_id = $this->send_message($this->tl->serialize_method($method, $args), $this->tl->content_related($method), $message_id); $this->datacenter->outgoing_messages[$int_message_id]['content'] = ['method' => $method, 'args' => $args]; @@ -35,7 +36,7 @@ trait CallHandler \danog\MadelineProto\Logger::log('Getting response (try number '.$res_count.' for '.$method.')...'); $this->recv_message(); $this->handle_messages(); - if (!isset($this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content'])) { + if (!isset($this->datacenter->outgoing_messages[$int_message_id]['response']) || !isset($this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content'])) { continue; } $server_answer = $this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content']; @@ -94,6 +95,7 @@ trait CallHandler } catch (\danog\MadelineProto\Exception $e) { \danog\MadelineProto\Logger::log('An error occurred while calling method '.$method.': '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Recreating connection and retrying to call method...'); $this->datacenter->close_and_reopen(); + sleep(1); // To avoid flooding continue; } if ($server_answer == null) { @@ -113,7 +115,7 @@ trait CallHandler foreach (range(1, $this->settings['max_tries']['query']) as $count) { try { - \danog\MadelineProto\Logger::log('Sending object (try number '.$count.' for '.$object.')...'); + \danog\MadelineProto\Logger::log($object == 'msgs_ack' ? 'ack '.$args['msg_ids'][0] : 'Sending object (try number '.$count.' for '.$object.')...'); $int_message_id = $this->send_message($this->tl->serialize_object(['type' => $object], $args), $this->tl->content_related($object)); $this->datacenter->outgoing_messages[$int_message_id]['content'] = ['method' => $object, 'args' => $args]; } catch (Exception $e) { diff --git a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php index f7692f31..3aadf1f4 100644 --- a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php @@ -55,7 +55,7 @@ trait MessageHandler { $payload = $this->datacenter->read_message(); if (fstat($payload)['size'] == 4) { - $error = \danog\PHP\Struct::unpack('datacenter->temp_auth_key != null) { \danog\MadelineProto\Logger::log('WARNING: Resetting auth key...'); @@ -68,13 +68,13 @@ trait MessageHandler } throw new \danog\MadelineProto\RPCErrorException($error, $error); } - $auth_key_id = fread($payload, 8); + $auth_key_id = stream_get_contents($payload, 8); if ($auth_key_id == $this->string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) { - list($message_id, $message_length) = \danog\PHP\Struct::unpack('check_message_id($message_id, false); - $message_data = fread($payload, $message_length); + $message_data = stream_get_contents($payload, $message_length); } elseif ($auth_key_id == $this->datacenter->temp_auth_key['id']) { - $message_key = fread($payload, 16); + $message_key = stream_get_contents($payload, 16); $encrypted_data = stream_get_contents($payload); list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->temp_auth_key['auth_key'], 'from server'); $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv); diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php index e29f642c..cdc46ecd 100644 --- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php @@ -24,7 +24,10 @@ trait PeerHandler foreach ($users as $key => $user) { switch ($user['_']) { case 'user': - $this->chats[$user['id']] = $user; + if (!isset($this->chats[$user['id']])) { + $this->chats[$user['id']] = $user; + $this->should_serialize = true; + } case 'userEmpty': break; default: @@ -40,17 +43,134 @@ trait PeerHandler switch ($chat['_']) { case 'chat': case 'chatEmpty': - $this->chats[-$chat['id']] = $chat; + if (!isset($this->chats[-$chat['id']])) { + $this->should_serialize = true; + $this->chats[-$chat['id']] = $chat; + } + case 'chatForbidden': case 'channelEmpty': break; case 'channel': - $this->chats[(int) ('-100'.$chat['id'])] = $chat; + if (!isset($this->chats[(int) ('-100'.$chat['id'])])) { + $this->should_serialize = true; + $this->chats[(int) ('-100'.$chat['id'])] = $chat; + } break; default: throw new \danog\MadelineProto\Exception('Invalid chat provided at key '.$key.': '.var_export($chat, true)); break; } } + $this->should_serialize = true; + } + + + public function get_info($id, $recursive = true) + { + if (is_array($id)) { + switch ($id['_']) { + case 'inputPeerSelf': + case 'inputPeerSelf': + $id = $this->datacenter->authorization['user']['id']; + break; + case 'user': + $id = $id['id']; + break; + case 'inputPeerUser': + case 'inputUser': + case 'peerUser': + $id = $id['user_id']; + break; + + case 'chat': + $id = -$id['id']; + break; + case 'inputPeerChat': + case 'peerChat': + $id = -$id['chat_id']; + break; + + case 'channel': + $id = '-100'.$id['id']; + break; + case 'inputPeerChannel': + case 'inputChannel': + case 'peerChannel': + $id = '-100'.$id['channel_id']; + break; + default: + throw new \danog\MadelineProto\Exception('Invalid constructor given ' . var_export($id, true)); + break; + } + + } + + if (preg_match('/^channel#/', $id)) $id = str_replace('channel#', '-100', $id); + if (preg_match('/^chat#/', $id)) $id = str_replace('chat#', '-', $id); + if (preg_match('/^user#/', $id)) $id = str_replace('user#', '', $id); + + if (is_numeric($id)) { + $id = (int)$id; + if (isset($this->chats[$id])) { + return $this->gen_all($this->chats[$id]); + } + debug_print_backtrace(); +// if ($recursive) { +// } + throw new \danog\MadelineProto\Exception("Couldn't find peer by provided chat id ".$id); + } + $id = str_replace('@', '', $id); + foreach ($this->chats as $chat) { + if (isset($chat['username']) && $chat['username'] == $id) { + return $this->gen_all($chat); + } + } + if ($recursive) { + $this->resolve_username($id); + + return $this->get_info($id, false); + } + throw new \danog\MadelineProto\Exception("Couldn't find peer by provided username ".$id); + } + + public function gen_all($constructor) { + switch ($constructor['_']) { + case 'user': + $inputPeer = $constructor['self'] ? ['_' => 'inputPeerSelf'] : ['_' => 'inputPeerUser', 'user_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']]; + $inputType = $constructor['self'] ? ['_' => 'inputUserSelf'] : ['_' => 'inputUser', 'user_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']]; + $Peer = ['_' => 'peerUser', 'user_id' => $constructor['id']]; + $id = $constructor['id']; + $bot_api_id = $constructor['id']; + break; + case 'chat': + $inputPeer = ['_' => 'inputPeerChat', 'chat_id' => $constructor['id']]; + $inputType = []; + $Peer = ['_' => 'peerChat', 'chat_id' => $constructor['id']]; + $id = $constructor['id']; + $bot_api_id = -$constructor['id']; + break; + case 'channel': + $inputPeer = ['_' => 'inputPeerChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']]; + $inputType = ['_' => 'inputChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']]; + $Peer = ['_' => 'peerChannel', 'channel_id' => $constructor['id']]; + $id = $constructor['id']; + $bot_api_id = (int)('-100'.$constructor['id']); + break; + default: + throw new \danog\MadelineProto\Exception('Invalid constructor given ' . var_export($constructor, true)); + break; + } + + return ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id]; + } + + public function resolve_username($username) + { + $res = $this->method_call('contacts.resolveUsername', ['username' => str_replace('@', '', $username)]); + if ($res['_'] == 'contacts.resolvedPeer') { + return $res; + } + throw new \danog\MadelineProto\Exception('resolve_username returned an unexpected constructor: '.var_export($username, true)); } } diff --git a/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php index 4d119012..9f0af088 100644 --- a/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php @@ -125,15 +125,9 @@ trait ResponseHandler $this->datacenter->temp_auth_key['server_salt'] = $response['server_salt']; $this->ack_incoming_message_id($current_msg_id); // Acknowledge that I received the server's response \danog\MadelineProto\Logger::log('new session created'); - \danog\MadelineProto\Logger::log($response); unset($this->datacenter->new_incoming[$current_msg_id]); - if ($this->datacenter->authorized) { - $this->get_updates_state(); - } break; case 'msg_container': - - \danog\MadelineProto\Logger::log($response['messages']); unset($this->datacenter->new_incoming[$current_msg_id]); foreach ($response['messages'] as $message) { $this->check_message_id($message['msg_id'], false, true); @@ -191,8 +185,6 @@ trait ResponseHandler if (isset($this->datacenter->incoming_messages[$response['answer_msg_id']])) { $this->ack_incoming_message_id($response['answer_msg_id']); - } else { - $this->object_call('msg_resend_req', ['msg_ids' => [$response['answer_msg_id']]]); } break; case 'msg_resend_req': diff --git a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php index 2bf9684b..38fce2f0 100644 --- a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php @@ -18,19 +18,82 @@ namespace danog\MadelineProto\MTProtoTools; trait UpdateHandler { public $updates_state = []; + public $channels_state = []; - public function update_state($data, $chat_id = 0) - { - if (!isset($this->updates_state[$chat_id])) { - $this->updates_state[$chat_id] = ['date' => 0, 'pts' => 0, 'seq' => 0]; + public $updates = []; + + public function get_updates_update_handler($update) { + if (count($this->updates) > $this->settings['updates']['updates_array_limit']) { + array_shift($this->updates); } - - $this->updates_state[$chat_id]['pts'] = (!isset($data['pts']) || $data['pts'] == 0) ? $this->updates_state[$chat_id]['pts'] : $data['pts']; - $this->updates_state[$chat_id]['seq'] = (!isset($data['seq']) || $data['seq'] == 0) ? $this->updates_state[$chat_id]['seq'] : $data['seq']; - $this->updates_state[$chat_id]['date'] = (!isset($data['date']) || $data['date'] < $this->updates_state[$chat_id]['date']) ? $this->updates_state[$chat_id]['date'] : $data['date']; + $this->updates[] = $update; + \danog\MadelineProto\Logger::log('Stored ', $update); } - public function get_updates_state() + public function get_updates($offset, $limit = null, $timeout = 0) { + sleep($timeout); + $this->get_updates_difference(); + $result = array_slice($this->updates, $offset, $limit, true); + $updates = []; + foreach ($result as $key => $value) { + $updates[] = ['update_id' => $key, 'update' => $value]; + unset($this->updates[$key]); + } + return $updates; + } + + public function &get_channel_state($channel, $pts = 0) { + if (!isset($this->channels_state[$channel])) { + $this->channels_state[$channel] = ['pts' => $pts, 'pop_pts' => [], 'pending_seq_updates' =>[]]; + } + return $this->channels_state[$channel]; + } + public function update_channel_state($channel, $data) + { + $this->get_channel_state($channel); + + $this->channels_state[$channel]['pts'] = (!isset($data['pts']) || $data['pts'] == 0) ? $this->get_channel_state($channel)['pts'] : $data['pts']; + } + + public function get_channel_difference($channel) + { + $this->get_channel_state($channel); + + $difference = $this->method_call('updates.getChannelDifference', ['channel' => $this->get_info('channel#'.$channel)['inputType'], 'filter' => ['_' => 'channelMessagesFilterEmpty'],'pts' => $this->get_channel_state($channel)['pts'], 'limit' => 30]); + switch ($difference['_']) { + case 'updates.channelDifferenceEmpty': + $this->update_channel_state($difference); + break; + case 'updates.difference': + $this->handle_update_messages($difference['new_messages'], $channel); + $this->handle_multiple_update($difference['other_updates']); + $this->update_channel_state($difference); + if (!$difference['final']) { + $this->get_channel_difference($channel); + } + break; + case 'updates.differenceTooLong': + unset($this->channels_state[$channel]); + \danog\MadelineProto\Logger::log('Got updates.differenceTooLong: ', $difference); + break; + default: + throw new \danog\MadelineProto\Exception('Unrecognized update difference received: '.var_export($difference)); + break; + } + } + public function update_state($data) + { + if (empty($this->updates_state)) { + $this->updates_state = ['date' => 0, 'pts' => 0, 'seq' => 0, 'pending_seq_updates' => []]; + } + + $this->updates_state['pts'] = (!isset($data['pts']) || $data['pts'] == 0) ? $this->updates_state['pts'] : $data['pts']; + $this->updates_state['seq'] = (!isset($data['seq']) || $data['seq'] == 0) ? $this->updates_state['seq'] : $data['seq']; + $this->updates_state['date'] = (!isset($data['date']) || $data['date'] < $this->updates_state['date']) ? $this->updates_state['date'] : $data['date']; + } + + + public function get_updates_difference() { if (empty($this->updates_state)) { return $this->update_state($this->method_call('updates.getState')); @@ -41,19 +104,15 @@ trait UpdateHandler $this->update_state($difference); break; case 'updates.difference': - $this->add_users($difference['users']); - $this->add_chats($difference['chats']); $this->handle_update_messages($difference['new_messages']); $this->handle_multiple_update($difference['other_updates']); $this->update_state($difference['state']); break; case 'updates.differenceSlice': - $this->add_users($difference['users']); - $this->add_chats($difference['chats']); $this->handle_update_messages($difference['new_messages']); $this->handle_multiple_update($difference['other_updates']); $this->update_state($difference['intermediate_state']); - $this->get_updates_state(); + $this->get_updates_difference(); break; default: throw new \danog\MadelineProto\Exception('Unrecognized update difference received: '.var_export($difference)); @@ -65,13 +124,22 @@ trait UpdateHandler { switch ($updates['_']) { case 'updatesTooLong': - $this->get_updates_state(); + $this->get_updates_difference(); break; case 'updateShortMessage': case 'updateShortChatMessage': - case 'updateShortSentMessage': - $update = ['_' => 'updateNewMessage']; - $this->handle_update_messages($update, ['date' => $updates['date']]); +// case 'updateShortSentMessage': + $fromID = isset($updates['from_id']) ? $updates['from_id'] : ($updates['out'] ? $this->datacenter->authorization['user']['id'] : $updates['user_id']); + $toID = isset($updates['chat_id']) + ? $updates['chat_id'] + : ($updates['out'] ? $updates['user_id'] : $this->datacenter->authorization['user']['id']); + + $message = $updates; + $message['_'] = 'message'; + $message['to_id'] = $toID; + $message['from_id'] = $this->get_info($fromID)['Peer']; + $update = ['_' => 'updateNewMessage', 'message' => $message, 'pts' => $updates['pts'], 'pts_count' => $updates['pts_count']]; + $this->handle_update($update, ['date' => $updates['date']]); break; case 'updateShort': $this->handle_update($updates['update'], ['date' => $updates['date']]); @@ -79,12 +147,13 @@ trait UpdateHandler case 'updatesCombined': $this->add_users($updates['users']); $this->add_chats($updates['chats']); - $this->handle_multiple_update($updates['updates']); + $this->handle_multiple_update($updates['updates'], ['date' => $updates['date'], 'seq' => $updates['seq'], 'seq_start' => $updates['seq_start']]); break; + case 'updates': $this->add_users($updates['users']); $this->add_chats($updates['chats']); - $this->handle_multiple_update($updates['updates']); + $this->handle_multiple_update($updates['updates'], ['date' => $updates['date'], 'seq' => $updates['seq']]); break; default: throw new \danog\MadelineProto\Exception('Unrecognized update received: '.var_export($updates)); @@ -92,22 +161,135 @@ trait UpdateHandler } } - public function handle_update($update) + public function handle_update($update, $options = []) { - var_dump($update); + $channel_id = false; + switch ($update['_']) { + case 'updateNewChannelMessage': + case 'updateEditChannelMessage': + $channel_id = $update['message']['to_id']['channel_id']; + break; + case 'updateDeleteChannelMessages': + $channel_id = $update['channel_id']; + break; + case 'updateChannelTooLong': + $channel_id = $update['channel_id']; + if (!isset($this->channels_state[$channel_id])) { + return false; + } + return $this->get_channel_difference($channel_id); + break; + } + + $this->save_update($update); + /* + switch ($update['_']) { + case 'updateNewMessage': + case 'updateEditMessage': + case 'updateNewChannelMessage': + case 'updateEditChannelMessage': + $message = $update['message']; + if (isset($message['from_id']) && !isset($this->chats[$message['from_id']]) || + isset($message['fwd_from']['from_id']) && !isset($this->chats[$message['fwd_from']['from_id']]) || + isset($message['fwd_from']['channel_id']) && !isset($this->chats[(int)('-100'.$message['fwd_from']['channel_id'])]) || + !isset($this->get_info($message['to_id'])['bot_api_info'])) { + + \danog\MadelineProto\Logger::log('Not enough data for message update'); + + if ($channel_id !== false && isset($this->chats[$channel_id])) { + $this->get_channel_difference($channel_id); + } else { + $this->get_updates_difference(); + } + return false; + + } + break; + default: + if ($channel_id !== false && isset($this->chats[$channel_id])) { + return false; + } + break; + } + $pop_pts = false; + if ($update['pts']) { + $new_pts = $cur_state['pts'] + (isset($update['pts_count']) ? $update['pts_count'] : 0); + if ($new_pts < $update['pts']) { + \danog\MadelineProto\Logger::log('Pts hole', $cur_state, $update, $this->get_info($channel_id)); + + $this->cur_state['pop_pts'][] = $update; + + if ($channel_id && isset($this->chats[$channel_id])) { + $this->get_channel_difference($channel_id); + } else { + $this->get_updates_difference(); + } + + return false; + } + if ($new_pts > $update['pts']) { + $cur_state['pts'] = $update['pts']; + $pop_pts = true; + } else if ($update['pts_count']) { + return false; + } + } else if (!$channel_id && isset($options['seq']) && $options['seq'] > 0) { + $seq = $options['seq']; + $seq_start = isset($options['seq_start']) ? $options['seq_start'] : $options['seq']; + + + if ($seq_start != $cur_state['seq'] + 1) { + if ($seq_start > $cur_state['seq']) { + \danog\MadelineProto\Logger::('Seq hole', $cur_state); + + if (!isset($cur_state['pending_seq_updates'][$seq_start])) { + $cur_state['pending_seq_updates'][$seq_start] = ['seq' => $seq, 'date': $options['date'], 'updates' => []]; + } + $cur_state['pending_seq_updates'][$seq_start][] = $update; + + if (!$cur_state.syncPending.seqAwaiting || + $cur_state.syncPending.seqAwaiting < $seq_start) { + $cur_state.syncPending.seqAwaiting = $seq_start + } + return false; + } + } + + + if (curState.seq != seq) { + curState.seq = seq + if (options.date && curState.date < options.date) { + curState.date = options.date + } + popSeq = true + } + } + + saveUpdate(update) + + if (popPts) { + popPendingPtsUpdate(channelID) + } + else if (popSeq) { + popPendingSeqUpdate() +}*/ } - public function handle_multiple_update($updates) + public function handle_multiple_update($updates, $options = []) { foreach ($updates as $update) { - $this->handle_update($update); + $this->handle_update($update, $options); } } - public function handle_update_messages($messages) + public function handle_update_messages($messages, $channel = false) { foreach ($messages as $message) { - $this->handle_update(['_' => 'updateNewMessage', 'message' => $message, 'pts' => $this->updates_state[0]['pts'], 'pts_count' => 0]); + $this->save_update(['_' => $channel == false ? 'updateNewMessage' : 'updateNewChannelMessage', 'message' => $message, 'pts' => $channel == false ? $this->updates_state['pts'] : $this->get_channel_state($channel)['pts'], 'pts_count' => 0]); } } + + public function save_update($update) { + $this->settings['updates']['callback']($update); + } } diff --git a/src/danog/MadelineProto/Serialization.php b/src/danog/MadelineProto/Serialization.php new file mode 100644 index 00000000..a3bd0e8b --- /dev/null +++ b/src/danog/MadelineProto/Serialization.php @@ -0,0 +1,31 @@ +. +*/ + +namespace danog\MadelineProto; + +/** + * Manages serialization of the MadelineProto instance + */ +class Serialization { + + public static function serialize($filename, $instance, $force = false) { + if ($instance->API->should_serialize || !(file_exists($filename) && !empty(file_get_contents($filename))) || $force) { + $instance->API->should_serialize = false; + return file_put_contents($filename, serialize($instance)); + } + return false; + } + + public static function deserialize($filename) { + return file_exists($filename) ? unserialize(file_get_contents($filename)) : false; + } +} \ No newline at end of file diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index 11c70808..c2531efb 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -87,7 +87,6 @@ class TL extends \danog\MadelineProto\Tools return \danog\PHP\Struct::pack('deserialize_bool(fread($bytes_io, 4)); + return $this->deserialize_bool(stream_get_contents($bytes_io, 4)); case 'int': - return \danog\PHP\Struct::unpack(' 254) { throw new Exception('Length is too big'); } if ($l == 254) { - $long_len = \danog\PHP\Struct::unpack('string2bin('\x00'))[0]; - $x = fread($bytes_io, $long_len); + $long_len = \danog\PHP\Struct::unpack('string2bin('\x00'))[0]; + $x = stream_get_contents($bytes_io, $long_len); $resto = $this->posmod(-$long_len, 4); if ($resto > 0) { - fread($bytes_io, $resto); + stream_get_contents($bytes_io, $resto); } } else { - $x = fread($bytes_io, $l); + $x = stream_get_contents($bytes_io, $l); $resto = $this->posmod(-($l + 1), 4); if ($resto > 0) { - fread($bytes_io, $resto); + stream_get_contents($bytes_io, $resto); } } if (!is_string($x)) { @@ -298,14 +297,14 @@ class TL extends \danog\MadelineProto\Tools case 'true': return true; case 'Vector t': - $id = \danog\PHP\Struct::unpack('constructors->find_by_id($id); if ($constructorData === false) { throw new Exception('Could not extract type: '.$type['type'].' with id '.$id); } switch ($constructorData['predicate']) { case 'gzip_packed': - return $this->deserialize(gzdecode($this->deserialize($bytes_io, ['type' => 'string']))); + return $this->deserialize($this->fopen_and_write('php://memory', 'rw+b', gzdecode($this->deserialize($bytes_io, ['type' => 'string'])))); case 'Vector t': case 'vector': break; @@ -313,7 +312,7 @@ class TL extends \danog\MadelineProto\Tools throw new Exception('Invalid vector constructor: '.$constructorData['predicate']); } case 'vector': - $count = \danog\PHP\Struct::unpack('deserialize($bytes_io, ['type' => $type['subtype']]); @@ -330,7 +329,7 @@ class TL extends \danog\MadelineProto\Tools } else { $constructorData = $this->constructors->find_by_predicate($type['type']); if ($constructorData === false) { - $id = \danog\PHP\Struct::unpack('constructors->find_by_id($id); if ($constructorData === false) { throw new Exception('Could not extract type: '.$type['type'].' with id '.$id); @@ -338,7 +337,7 @@ class TL extends \danog\MadelineProto\Tools } } if ($constructorData['predicate'] == 'gzip_packed') { - return $this->deserialize(gzdecode($this->deserialize($bytes_io, ['type' => 'string']))); + return $this->deserialize($this->fopen_and_write('php://memory', 'rw+b', gzdecode($this->deserialize($bytes_io, ['type' => 'string'])))); } $x = ['_' => $constructorData['predicate']]; foreach ($constructorData['params'] as $arg) { @@ -365,7 +364,6 @@ class TL extends \danog\MadelineProto\Tools if (isset($x['flags'])) { // I don't think we need this anymore unset($x['flags']); } - return $x; } diff --git a/src/danog/MadelineProto/Tools.php b/src/danog/MadelineProto/Tools.php index 41e5090d..c7ae7b46 100644 --- a/src/danog/MadelineProto/Tools.php +++ b/src/danog/MadelineProto/Tools.php @@ -35,7 +35,7 @@ class Tools { $pos = ftell($handle); fseek($handle, 0); - $content = fread($handle, fstat($handle)['size']); + $content = stream_get_contents($handle, fstat($handle)['size']); fseek($handle, $pos); return $content; diff --git a/src/danog/MadelineProto/Wrappers/Login.php b/src/danog/MadelineProto/Wrappers/Login.php index 3a3dde00..04c54156 100644 --- a/src/danog/MadelineProto/Wrappers/Login.php +++ b/src/danog/MadelineProto/Wrappers/Login.php @@ -24,8 +24,11 @@ trait Login } $this->API->datacenter->authorized = false; $this->API->datacenter->authorization = null; + $this->API->updates = []; \danog\MadelineProto\Logger::log('Logged out successfully!'); + $this->API->should_serialize = true; + return true; } @@ -45,7 +48,12 @@ trait Login ] ); $this->API->datacenter->authorized = true; - $this->API->get_updates_state(); + $this->API->get_updates_difference(); + + + $this->API->should_serialize = true; + + $this->API->updates = []; \danog\MadelineProto\Logger::log('Logged in successfully!'); return $this->API->datacenter->authorization; @@ -70,6 +78,9 @@ trait Login ); $this->API->datacenter->authorization['phone_number'] = $number; $this->API->datacenter->waiting_code = true; + $this->API->should_serialize = true; + $this->API->updates = []; + \danog\MadelineProto\Logger::log('Code sent successfully! Once you receive the code you should use the complete_phone_login function.'); return $this->API->datacenter->authorization; @@ -91,7 +102,9 @@ trait Login ); $this->API->datacenter->waiting_code = false; $this->API->datacenter->authorized = true; - $this->API->get_updates_state(); + $this->API->get_updates_difference(); + $this->API->should_serialize = true; + \danog\MadelineProto\Logger::log('Logged in successfully!'); return $this->API->datacenter->authorization; diff --git a/src/danog/MadelineProto/Wrappers/PeerHandler.php b/src/danog/MadelineProto/Wrappers/PeerHandler.php index 79b4352a..92b6d6fb 100644 --- a/src/danog/MadelineProto/Wrappers/PeerHandler.php +++ b/src/danog/MadelineProto/Wrappers/PeerHandler.php @@ -17,56 +17,15 @@ namespace danog\MadelineProto\Wrappers; */ trait PeerHandler { - public function get_peer($id, $recursive = true) - { - if (is_numeric($id)) { - if (isset($this->API->chats[$id])) { - return $this->API->chats[$id]; - } -// if ($recursive) { -// } - throw new \danog\MadelineProto\Exception("Couldn't find peer by provided chat id ".$id); - } - $id = str_replace('@', '', $id); - foreach ($this->API->chats as $chat) { - if (isset($chat['username']) && $chat['username'] == $id) { - return $chat; - } - } - if ($recursive) { - $this->resolve_username($id); - - return $this->get_peer($id, false); - } - throw new \danog\MadelineProto\Exception("Couldn't find peer by provided username ".$id); + public function get_info($id, $recursive = true) { + return $this->API->get_info($id, $recursive); } - public function get_input_peer($id) - { - return $this->constructor2inputpeer($this->get_peer($id)); + public function gen_all($constructor) { + return $this->API->gen_all($constructor); } - public function constructor2inputpeer($peer) - { - switch ($peer['_']) { - case 'user': - return $peer['self'] ? ['_' => 'inputPeerSelf'] : ['_' => 'inputPeerUser', 'user_id' => $peer['id'], 'access_hash' => $peer['access_hash']]; - case 'chat': - case 'chatEmpty': - return ['_' => 'inputPeerChat', 'chat_id' => $peer['id']]; - case 'channel': - return ['_' => 'inputPeerChannel', 'channel_id' => $peer['id'], 'access_hash' => $peer['access_hash']]; - default: - throw new \danog\MadelineProto\Exception('Invalid constructor given'); - } - } - - public function resolve_username($username) - { - $res = $this->API->method_call('contacts.resolveUsername', ['username' => str_replace('@', '', $username)]); - if ($res['_'] == 'contacts.resolvedPeer') { - return $res; - } - throw new \danog\MadelineProto\Exception('resolve_username returned an unexpected constructor: '.var_export($username, true)); + public function resolve_username($username) { + return $this->API->resolve_username($username); } } diff --git a/src/danog/MadelineProto/Wrappers/SettingsManager.php b/src/danog/MadelineProto/Wrappers/SettingsManager.php new file mode 100644 index 00000000..e8c53b32 --- /dev/null +++ b/src/danog/MadelineProto/Wrappers/SettingsManager.php @@ -0,0 +1,25 @@ +. +*/ + +namespace danog\MadelineProto\Wrappers; + +/** + * Manages changing API instance settings. + */ +trait SettingsManager { + public function get_settings() { + return $this->API->settings; + } + public function update_settings($settings) { + $this->API->parse_settings($settings); + } +} \ No newline at end of file diff --git a/testing.php b/testing.php index 85caad61..7ca35821 100755 --- a/testing.php +++ b/testing.php @@ -12,10 +12,16 @@ If not, see . */ require_once 'vendor/autoload.php'; +$settings = []; +if (file_exists('web_data.php')) { + require_once('web_data.php'); +} -if (file_exists('number.php') && !file_exists('session.madeline')) { +$MadelineProto = \danog\MadelineProto\Serialization::deserialize('session.madeline'); + +if (file_exists('number.php') && $MadelineProto === false) { include_once 'number.php'; - $MadelineProto = new \danog\MadelineProto\API(); + $MadelineProto = new \danog\MadelineProto\API($settings); $checkedPhone = $MadelineProto->auth->checkPhone(// auth.checkPhone becomes auth->checkPhone [ @@ -33,35 +39,36 @@ if (file_exists('number.php') && !file_exists('session.madeline')) { $authorization = $MadelineProto->complete_phone_login($code); \danog\MadelineProto\Logger::log($authorization); echo 'Serializing MadelineProto to session.madeline...'.PHP_EOL; - echo 'Wrote '.file_put_contents('session.madeline', serialize($MadelineProto)).' bytes'.PHP_EOL; + echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('session.madeline', $MadelineProto).' bytes'.PHP_EOL; } echo 'Deserializing MadelineProto from session.madeline...'.PHP_EOL; -$MadelineProto = unserialize(file_get_contents('session.madeline')); +$MadelineProto = \danog\MadelineProto\Serialization::deserialize('session.madeline'); -$message = (getenv('TRAVIS_COMMIT') == '') ? 'Message entities can be sent too (yay)' : ('Travis ci tests in progress: commit '.getenv('TRAVIS_COMMIT').', job '.getenv('TRAVIS_JOB_NUMBER').', PHP version: '.getenv('TRAVIS_PHP_VERSION')); +$message = (getenv('TRAVIS_COMMIT') == '') ? 'I iz works always (io laborare sembre) (yo lavorar siempre)' : ('Travis ci tests in progress: commit '.getenv('TRAVIS_COMMIT').', job '.getenv('TRAVIS_JOB_NUMBER').', PHP version: '.getenv('TRAVIS_PHP_VERSION')); $flutter = 'https://storage.pwrtelegram.xyz/pwrtelegrambot/document/file_6570.mp4'; -\danog\MadelineProto\Logger::log($MadelineProto->resolve_username('@Palmas2012')); // Always use this method to resolve usernames, but you won't need to call this to get info about peers, as get_peer and get_input_peer will call it for you if needed - -$mention = $MadelineProto->get_peer('@veetaw'); // Returns an object of type User or Chat -$mention = $MadelineProto->constructor2inputpeer($mention); // Converts an object of type User or Chat to an object of type inputPeer +$mention = $MadelineProto->get_info('@giuseppe_la_gaipa_2'); // Returns the following array: ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id] +$mention = $mention['inputType']; // Selects only the inputType object foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) { - $peer = $MadelineProto->get_input_peer($peer); // Returns directly an inputPeer object, basically does the same thing I've done manually above - $sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message.' & pony', 'entities' => [['_' => 'messageEntityUrl', 'offset' => strlen($message) + 1, 'length' => 6, 'url' => $flutter], ['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]); + $peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object + $sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]); \danog\MadelineProto\Logger::log($sentMessage); } +sleep(5); +$MadelineProto->API->get_updates_difference(); + echo 'Size of MadelineProto instance is '.strlen(serialize($MadelineProto)).' bytes'.PHP_EOL; if (file_exists('token.php')) { include_once 'token.php'; - $MadelineProto = new \danog\MadelineProto\API(); + $MadelineProto = new \danog\MadelineProto\API($settings); $authorization = $MadelineProto->bot_login($token); \danog\MadelineProto\Logger::log($authorization); } foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) { - $peer = $MadelineProto->get_input_peer($peer); - $sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message.' & pony', 'entities' => [['_' => 'messageEntityUrl', 'offset' => strlen($message) + 1, 'length' => 6, 'url' => $flutter], ['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]); + $peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object + $sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]); \danog\MadelineProto\Logger::log($sentMessage); }