From cf696d05db6379b4ee061237dd9204b263296065 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 30 Jun 2026 02:05:28 +0300 Subject: [PATCH] Update: 2026-06-30 02:05:28 --- .../executionHistory/executionHistory.bin | Bin 867540 -> 905733 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 17 bytes .../.gradle/8.13/fileHashes/fileHashes.bin | Bin 72465 -> 72665 bytes .../.gradle/8.13/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../8.13/fileHashes/resourceHashesCache.bin | Bin 20299 -> 21591 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .../buildOutputCleanup/outputFiles.bin | Bin 22313 -> 22367 bytes android_bot/.gradle/file-system.probe | Bin 8 -> 8 bytes .../service/ScraperAccessibilityService.kt | 312 ++++++++++++++++-- 9 files changed, 292 insertions(+), 20 deletions(-) diff --git a/android_bot/.gradle/8.13/executionHistory/executionHistory.bin b/android_bot/.gradle/8.13/executionHistory/executionHistory.bin index 469cd254934524f748d36fa90bf2e7ac7f7caa2e..66eeb691f58df8008b2c0f363287e9c8928a6f2f 100644 GIT binary patch delta 7449 zcmb_g2UHZv)}Efw-GejOAXz{VP|0Bc!3C8?jHrOJ3aBt7QNoZ#41jAu$p%Wq0E+91 zh$J0F1rc*X6frBTikLtIBniIm>d{r)=X>uz|D02EZr{3f>)zY-eO1+UJ?883#-__5 z+zj~%rVIwJV>yGN^_D^X_)2~ZhPH^7ZR!{d9dlaVu!EE>NI9WcxK(kBveGT4H(vTq z$pT^V3u=DXQQ4rQf{=|#HWt~~VjUIE(i{Uz@1>!E{^k~;{zCJmev5qqmV|{z1;tnf z3ke6tgDu6X`E`2B%NX2eGm{m?C(AiO)7VuXOACNL8?9uYp`;cz3Z_v~o1pa=i;XZC zGlmKn3|xk6T&iXNWR8=6ZI3!}3~cFRbcISg=G7zO8yqwZW1puy3;C!4l*Ac)6pQ*< ztk!yZ-AU3a&HmAT#X29u!^eovtwBxM>}wJ~sz81NO0YkWk7C^os1Tp*$zZJZ%w8Pe z?JWun9UBts?-Sr_5gBb65xp$TFC-?6AYufW@t0&W_e$^f9r>tV>#Xb#Dn>cT-HMI` z6VlN!VwH4M-ixS=eXhj@p z7K12RsJKag*yq4Un|@tg-f_#34O-N31(4N=R&r9;mKC&tg&)xw67ldy)Q(Fj1$i>a zUXU@s(*#Og=rA@kH10yjqOi9VwB5v2wt4YUko>iKUl*!J8`gHA`Vx;wH!5IL^8l-E z)Pa@~x={s{q9-|KR%5i6t;sl&(>ahdf18+!d8Z6g0E)X&Rb6{gaHue3i73R#CrB6( z!5}&tZSRJ)m~*Q6tD7b|_d9070hJgy(v6y{cyt^rUE6S9w*1Y+!jkC*rgZOBedIW( zGU1y6;~o@KvN~RFsrLAnz4oj7@PN>L2DH&dT_C)LtAi03Usjbt{1rCmRdwYt`Kbo% z%OUU2;fJ2-o8K{3bk&JU=8~Qq zKXf{zZ7AvA{>=J7RnERyr0QhB^FHelF3+rg^TmD_xX6LoMYshAP}Km&CAbn1uvfH& zXSGG=#lHB7O|dC0z_<^smlL&XMrer}H`F~)7%=%{&rc%P2vGinT1s7nuQXl**#|~^ zLe;@jCWe9rE=F+a9vsP{I^@A2@(+{ffZ8Wi5j^>X4pADLExzWFpW&ak>b&;K)RIG# z=QhD30YLguJr1-7CjF?HiexCYD2=PDy8?gbTyMeZL88YO@HkmWI-?lKxc)#Z;=n%Qm}t@^}Pqux1d(uJ3Fmv8{pR(7tmoHe^5Ay>w6 z^)HJB!YlNwt_YweRZwIQf27@6H(&Kn%#`J8cr0ad8$I^w0BXjCq5wyBg4LhV;pF16 zop4{}SXID+My-YeM)zjKtpAe2AC!d->1}ZQWibBbpHY(^;-|YR$Z^VpKCL#n=1#X|wAu7AOFK*KrH903Yuw(Q6>KkiLepyQS z02=N1j1HBzeup|$nw%YW*98aZ1 z4gWT+>C5({8!FV>kC(0#X^p=3f)3}|NHQ(bbT~{a!0kxRo}^8$UpCuhyzyQi3B3Vz zCYcv4E?{D#IVWJj6PcKqM66_DmJ+d-iRnv3Jr-sv5xrQLu0%{_VaDFj16lk{q2Z!P zkx``3n?X!k>i*_PcI2*}54(JZnr|5KwOm`j8W{!pEsux{_Y(#)2(CZ&~d_;6SNVIr2g$P$S;Is#y+J@6D}Q* zs&-l*r|H7!f_5O3jVVu2b$>NV^hY-9x~YDZ?$!GYI;_3ENb*-7)PfkZ_wG{DWBl&qSlpQE$#3_cMTt~?l zNsBBD8zm8qFicfxd~ZJ*uJJPP&^*VK+HHjcbOZxT;EiD;C59U@OwSMwCo@Tlhzj)! z|7LOvi;$!g?`NNO&;6x)1-qdUPTqiFCY(|@xjc>uB;rgQ8zT{8aZFw5(#zD_>nc2! zebgN1XZjhvPfxy!35sw`TVi-0#|+}2Cjm~<=H2GCSA@y6(kJHdqh=diRV7u*Fm-y24taq6CY>0q$FnGtH+CZRZWFm=PG?|Fr@3TKwAjhT#*Bj^ zS)8ozJsEvNN*e>PNo#YE!o~S(Mm;=x;WFj2O|Tfumm*W8T_s3i28Lf;r7iKLLZTef zLFMS}Nn4k%{f@#O87bb*R zYg?Nnu9{n?Ur>0So)ky6eDB-{VrXk%6DQ*M@YtqP#!>mb(~f^J&KhL=O;TxRT$`Ge zV@FoA+Gk%q=**)ln#4ge;26!X;Zq1Ry?mY-oEh zN-P>!z>|lm$vvo=kd;?9aZJ%ftD`ouQ)W`#Af1OP$SrsN%kyKSQH>>ME4%XC!3VIX zoQJ7SpTz0Ne(L6s(9!3yaPst22JP}x6q5Q)Y;Xfv(BPHm`lwuJanIRQw5UrmH^~>9 zX-m*BfU1ek)2WfmbWzN+u_?db=FXp*`C^eyqzMhzBIDY24Zf9&pw00l|Nh8mB zk%rz;OM-ljyqA!?))#4)%NcDv=Lz;Z)^*{WV;Ma0$&zC+8J35e$ zE+eahfSm_w0iJ~Nriw+<*weDfo(2qtr~c2*{=4t**3ybSbKW;@-YAD@h41W$!5T&> z2)l%-fs5y`QR2JRSgsUvVhzZ>jSL4jlzHaj%QvtjZDRRB7n_F8S5p*Mg)%m_DS=H^PX5o3Ez#e_pUE15MLaDXCWhc zYPM&^dlmEa?-n)IC5bIJZ=jak(I~2Ho>tak4-xONj(DdWw$({|?FBEJDNfJE7hbdd z$)#!c`zD_IyZ~8c*GI&gbDQRb6N5D%G?6?lPdv%N#Et>n88X9e?_oWX+I-NM(Y9bt zeEP#UB-IS0M{?uEda>LaLkND?)R!eDh^kxruBLgTx4zpVEU*J-;)uS4WR^Y+TY%4b9+9735THg6A(EvVlwdaM9Pww|^rPtiFX4%J8zuBCe&a zIh?oId;F}v$t%_sJcvVXP}uwh#NWcD!PIK}BvJ3W;_$9FCC+niY3Nf+r?{I-NrdR4-T=sK!0StrY}sS^FvqAj`O};`e<1ZWf-` zk$R|2@Inj3tMey;95p@%JeA?g;`^XJ1L~&%`x;zUV!B33Vk*m5s0u$%aJ&S5A%8OFEd)IqWJCT=cux|53Gk}`RdAus)k zAZHAp4W`!OvYh?U$g>t7%{c(Yq*`2;a~FyyYH>}#mgf`fdh!`4$@A^mr$|r0Q=YFa zRY2JY-pV5R@J2uS@6!AF#W09B?DeU_=_1pTY^QaK@ojF~bs0BPIk0m~a^-?j zFT>vqT5r_GTHb@swrU~AsQbJHdDTj3?YCD7Y3-Ij+ViVhc$C#ym_oW1au_(b;08eN zHW@u#9lP83D1+RFvmOfAVF#jjs9&bulgm}mzB!4yus?mSH_~U%yU33gi=y4a7HtHl z75I7_I47vOjgRC!g!wk##x*&6p(tI48%$fqJTNb8%5h^Uh54S$ZF`zYPueq&WyEc( zc-v$$aH{f`PBE$CDbG#b(<>GkKK7l_KIO^?Qq@qVTjIORz3!P%_xvcYUtfSiMgGy5 zIwlmTHj*uYYCqezj>egmn!YN8gY>lzkrF1012x6b7D4su@OJj(QU&|u29*8J{Wv4}g7A#Xc#ha-Jz zw`%Oms+h>d!CO_U5x-70dEWe{rt`xN)}>E-_($oV(a1F#HG~PjS@w=&YA=g*fH+e4 zzu73YgGt1pRlY?@M22^sh#HKAcACYan{_f?tw|TR!qoETH zgJ3pt8rZ!@t;HK#@iI1H($o9b8lBxSt1?nbnEIz(K<*W$D$ux&FBLzw;x>zZ0H2 zml*uT?7ulO8b=Z=tAewRL`gNJAR}=IrsRA z_d$EkmzS3@Jk3OpDH>!WvV%XO(JNsbh{O0Lg!~%zVVC=sKC#(5brx1I*bN}3SNU6Tz$a~~7&5@KO;mAX2ln6QU z0n|c{OoCd-k*$>Yj~rPG?IB0rh9cz1{cz~t9GOf$Lyk0~J*`zoQhpUh&WRL8`WZn= z9E{KVlrurmuz$m#7dsWR=SDWHue6P-+h8U3{}TrIg+VJY&dtRq-L+~~uPrxL3wWLn zd%tB+MnTbw0sqT6mW7*o&t2X;v<+H(Wl)l12g4F3DmcYMa^Sa6Gdk>HHjhWT`t7fu zKmDwK!P$phw4WkCF>9D&i+Y_yQF`g&pjSH@%;RWgrE3kDwUC+=KxU=O2$^+>vizpL zA+k+53l8s7Sg`*Y>B%*8SDoAJqlO9}j8ZqA>!zGLHP&i`e%@Udpu6%?Vn^8nQZ*|% z$o$6p?kAlWqIa6u8;yX>I$OuYl!C7z*>tG(Gip6G{Xy^gXHD3yj3;6G=L(lTViSW^ zU*X%w{>RhF{}HT%zi|JLU>*Dk{70}3{{H>nfOYV9ZV6a__{jXtfuLa~KSC`8YdX|Iu(l^r=l_2?(7cEC5UjKa z!TJOa{Tr-+e5S!ld-~5{?WFwvd$11PLSIr7em(9C-Z@)g@3&wbynS}U1b+tBRv6G= zunyi*Q{bL>Du=>T1c!U`nzG$MaqG?4DV0mAX|Q&}(m=4T-AI)kf)#oP5Ui#B<(>RF ziGSTh-5kSz&9Wgqjk(9$aq{M!SnuB$)m3j@JV@1}I~MGB^R+ZC{?=vNuGwQus(#qO KsbdgR$LwFTr@qYq delta 4045 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 b54L<@bW%>@KF9kR=PA!Hog{34!Ta^UUkxDJ diff --git a/android_bot/.gradle/8.13/executionHistory/executionHistory.lock b/android_bot/.gradle/8.13/executionHistory/executionHistory.lock index 4ac5b306503e9dd0d6d9d21ee41a756ef4f0e7da..1b3f1fd988e5ec8753963d86c815e0dacfbf7d1b 100644 GIT binary patch literal 17 VcmZSHW4!avD({VX3}C=m1OPl(1up;q literal 17 UcmZSHW4!avD({VX3=q%?06b|0K>z>% diff --git a/android_bot/.gradle/8.13/fileHashes/fileHashes.bin b/android_bot/.gradle/8.13/fileHashes/fileHashes.bin index 14f3301c8a10f51925d122c187e413e5729626fe..80921cf57ff0a668244886ead1d2de6d017d0099 100644 GIT binary patch delta 3592 zcmZ`+30MKZbUV-hqbgS zXBK8Hb7AptgQ!P|uMF+;)yNAoFOjV@{}nShkI~078#}2Z>vRARcQBc$*5 z)_IG{oO|6NljI*^$g?^DSY~eLJU!;P;K1&o8qpJ)<(u@9a}bncfiP;%`hm--N%R7f z9CHbXJjDisnM~qIsBQoZM0V83PdVBmveP52`p8}D1CY*Pf-9miG;$WSitN!qQ#>6f zP{2csIP}2=u^lp<5Zgmqeb6kn^Z9~Imy}6GtBC$G*?|`i$6W04tvDAnjfavqKZ?Se zGy(1*rq#1TN4LonckB~Y77lz)TS{TF5k~VkhzB+<%9QnQejCN@!bMlhryfQAi6fe^ z8&6JCL;+2J&G8#Fy43uKp1i1h)cIcB=no1bqAxoPdR8#4HZAO|ud6YcGmX|5_u)$U z*BJZ;n2|+lOezwU#SRL;b!I4pd|*v}YjxE4&o&m&5;G2do*B$hGjKp|EUQ@+po z@zsV#-dultC>U2mBnQt1p>TSib)7>!|fMwc#0hdo)`v&r#%r;2+4F zN`K9w_vbxd7O=N}Bx2?4pmYL2?8O`lZR9cYH{f@YX*RVWBc*)OYQEZ%xZ(I+M3o>$ zlawxK&yna*Gmp265=vk7E{=4W_cZ~B~Fi$vuJhK${r~qlSWR)mGJi0V|R_O z$NLsEmGN~cBPE=A8t9;e;}IoCWp#zDR=d~i-1z6?tOGmh-}Jwk8cjLU0lxsjoFS~T z&e1E|#^RY5rOtDytJgzC;f(Ih+)1}0I>;{=_`pYDCBtI^;lk1Vze_L+>TDO7iZ z&?6L-2c*YPBzM@GivC0CMQVp;O78Po+hdY$F0Y+tpE2u%F_b|QM9}(eCo(UJ?KPk2bR_r&r>1e>KmKv z1s{8JBhc~kKpg16+VpAu0?H z)^OI3%eyM`w&oA@=0_ViAF`q{f`wrXjIdIDZd7X5haODghSAYcsp$JgiU)Q(%gg5> zyG9`zW z2rNQnMqBqlvePg)?EpxlM%%z8G{U<%r9k!ZMtQu7ZPe+HGjs){k6eUsb<6nP)1r+#9d{NXxWbb=;(SPdiMH4 z`_F7?F47h6MjjfT61geHw7T`KW9Hl9kXsrXea=cYpo2NbiqMR_GCe_8sSPe9a;OVp z2MLxeZEwHMEW4LE_wi|2>2}0Oi$`FiEaI+`LrZY_FpifPR#rT64{8ZaMjrG5@KYHQ zZ523EmdLqVfIra|mpQ2QtXP5VE>|$bDB*ngNHqA(hJUGW0mw_aDiA76!Kv zLtJ~7g-yX+ggE+dLQS3cUqO{_=ypzS^6F|`+xt~*_tI)zkutbgqQfSW!e2?e(4b2a zj|LqQDH^<%+$9F_LcAAcON;%MexS|k{-*cN^m@I-c3=Le;!~CS0LMvvRMywBHSXmhVa9#}Inhhxg^7tD6`EB`uD-ou5wpMOt;X2y UcF_*FE`eME9VA=HPc{4h0o;I;SO5S3 delta 3384 zcmZ`*3p`ZY8lP=ANTG&1V^A~lIOWVB^pdDXUZoO6DMjxjlFp^5b2K-3kA(>d9ioSW zCW)d4>GZfc=}8kRg;b&(b-U@Vz1H4y&*8LxznQ)E{=V=3ee3`I*IK)|Qdx3Vxy*(` zOqAJjteCQB{W3x}-|(x3Y}KT&Q7XO+Mv?$TwEra2w5j*ju;a-X=yFK}bK=aH@EHga zxPTw0i^enJEa1Hgy%VE%)v}zpL;}9N246xWUk~^v4BFZmg&`bO&mMReTEo9d@W&^A z+96$AU-;IB{NWuZZ`;0ncNc>}nSTwRf<0r(ek!C(qYeS`3Zcos$!{#Ivkgp3++pR_Y#Rf zzF(p7oN{l*?u>CG6ZCnUntt|Uu+G}N#kJeCgQPgZiIg3A#lzn)o{L03_3`$J!>>v1 ziSAfwAEzSVWEkK>I-65_VP!|?BbjM>oBPlQs{l*NJ{erj;ezg8kYv=#z}QSK2v4V< zNZXq%`pN1rl7dNRcw0`Vj&}G@`F!?TJ{GK!d^GrG&eBxzgRxslaS$A&k_)Z|bduTJ| zYD$s-FJ-Xg5F#2@4&C=wOE-WYS9RQHb)@YWjP!M;Yr@_s*QAbg5z1#yQOv_rJ>S z$g=%t99)R90CZB^CBT;9&SoIgbRSUKZm3pmeXMQS%6CIGRG=|fl8fB{a@(}1rQ+P0 z>?5{H50#>#O5uto;9j5x0IidF(hZnU`bj}S^77cBSha(5cMsi`e!db71&xC8kn2Ip zH6G+=;YN$$Fh zRP82++2baAJRcRQuHL-Mv{x@?zo#OS)H$vC43x4n8_O^S$+g^e z&q?DnyTgnQJuA7ggruD063q#)a&RpLsFsC2W_2&Z(*o5J>SG zj3}zJvAgB%UwYHCZau^y#Ye3d_BGi1VGAHiGb-xZnrU9I%H@&mGxT++2xAhCr@Ejs zUc>UeZ|D9d345@OK6y;Agmj)LmR|u$K5sC8s+U98*VfmQ10OcG92lgqD0lU4V3m$n zYX&(Jg)$OQu*C#@bKDSTt*jyb18`wxp1TRW~@A>`}UdFGjWIZ>@%~= zXm^E3Bax(Xa#_ z6OYstGp4nDN}hMg3+V~h)5aC==-DIVtKzvQe;?9&5cMzO{)VvM|3Tqk-Cw70klHyL zqE7deRQufMQ`Qt_-d6Z4wzCRl71^$OhM}1u|)`6!UtO<$n^59?lS$^hTt9wE!Jt!Yz zOe&ENoR8whBFJx%1`uiV4u+=AbA05LzhzeR`@VCD){ZI8ZS-U@@(fqlbr$>YS571% zHt%h#^OhAs{e#;5J3_?=4k%_xDojkBYe=4;l6p!e0~Kdp10V|UX43$7E`7H6-ue0+ zmdjS?jJ3{Q?sSJLNT|?N1EQMVEgBFOeP|&Y&;3l5uEV8$E0Z1w?Qv8dIj@SXbKM3i*e0V6?3WLC9Nv1rdY~ASqz#W2@=_0if5WzgcOG*KiwwiJ zN57ilWT4qQL`5`$H)1Yl%dwqjVxkurgp?ZX+-~z6ejt$wb>Rc86I}46SeMEF6a_B2 z!R^S!V*J4Eebt3-xBOopIZv&gv8z#9#@Muxc^zkt?_0_P)qZ8mUVIG=Bt@I6>*($P zB97w;tpX>HNAs?)6XrV^ZtHoe_wT6LMs7`11tJWb&5^mDaGpY#$s}?PQ+By6n?qEU z^L=zGb4C*^4W`KDv@~0v;BwS6$Jcy#pLx@Lzd`2(=jSUZm>}s3HUYoZCuU07975m# zTg4uap-s<^thp&JZ|xgS%cihJTV}5aJpn)K5FO2^GrBh9Bxci}*b N|7jM6SKdUz{||y}v01~A|&1pqYK1kV5f literal 17 VcmZQpDUVz8=j>v01~6cL4*)ej1wH@( diff --git a/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin b/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin index e4bcf8e859e7d26c5c1ddb3f16a722ac777ca2fa..e4f3e863340dff9cb410022c28c1bc3d1f411190 100644 GIT binary patch delta 1728 zcmX>-kMa5n#tkMCjM0-#B@6^wOv2CD?)TPZ0E1xW$*~d^0^PSQlV!i-C zWfFVWLrt!pd{rXAVdp<7)hE|VDhM2mE&C|L=eG$W7_2&Zs-%U((Zbp1r)DOLOMUGI)C z`9f{{2p2DZF7@m5hSN~-%*j`!Ed~6uWO{cg)Hd(QsWOWEoFa1HjBj&5#Dexuu9dOSNbX!+nc)@<5o7T0hgqL| z$D4Wa<6BPf>ou#kO{VLMIwB<9V zw)$wP&Y&qLcRiWfcm37RNt-cMRIUuNdGPh6yLfHE@$0{DfYJ||>Icta5K=o3JT?rm}4FL82n$0 zJi6s28Dax{X$NPJSzD#rdgQ)_W!Sx?59F{gusTWx&_2_f!Tf23b z+OEy-Kb^A9GGT(w{HQwx7eU1gTF~_r?_jO9Z8<*q(?iuKPv6&Ks@UOIX_#Y_^yp7@ ze(NX6BfW4Hh~$UWbwA|e)bHFrd{c$?3e}Kn5gYf6wn?%Hm{*HjBg~Z8pS0=Z}*G8tA2GGQ^o$OCvu#+?$2IHOf>j%ErJuSLKbd~r`;7@*?Un! PPfjZf%zSx352OSDFkDuz delta 123 zcmcb14FEo11+xGE literal 17 VcmZSn@TkOm3HOb23}C>l2mn2!1o{8~ diff --git a/android_bot/.gradle/buildOutputCleanup/outputFiles.bin b/android_bot/.gradle/buildOutputCleanup/outputFiles.bin index b2e93cb52b120267d46d6973af1da5264d7069ba..5e180febfece1fe53830a75f28022bd839b95798 100644 GIT binary patch delta 103 zcmZ3vj`99F#tkMCjMpcdN_-IL()|5Q*u3Hm0~mx`ZMXHCJU{^|s`(!Z q&UY~yhb<2_fQtKWu9aWJ!z5z4QSpcPMuP{;n;kt~@J-GSHUj{wx+Sv! delta 41 zcmV+^0M`HCt^uj80kAX}0o0Q<81S<_8DIvp%^#W!0u%AEA@~=uFen7GK}hHhWsneJ diff --git a/android_bot/.gradle/file-system.probe b/android_bot/.gradle/file-system.probe index 964ca61fac2c1fb9478ecdd8c41ad0937e3a7a08..4a66b2903d6f99d41812d0fcebb74d752519ec37 100644 GIT binary patch literal 8 PcmZQzV4N?SyrKaB26h5L literal 8 PcmZQzV4N>1_vJ4D2D}2r diff --git a/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt b/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt index 1e54437a..2403efa1 100644 --- a/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt +++ b/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt @@ -15,6 +15,7 @@ import org.json.JSONObject enum class BotState { IDLE, LAUNCHING_APP, + NAVIGATING_HOME, SEARCHING_START, SEARCHING_END, READING_PRICE, @@ -33,6 +34,22 @@ class ScraperAccessibilityService : AccessibilityService() { private var currentState = BotState.IDLE private var currentTask: JSONObject? = null private var currentAppName: String = "" + private var taxiFHomeClickTime = 0L + private var taxiFLocationClickTime = 0L + private var taxiFSuggestionClickTime = 0L + private var taxiFPickupDone = false + private var taxiFDestinationDone = false + private val taxiFCollectedPrices = mutableListOf>() + private var taxiFPriceScrollAttempts = 0 + + companion object { + private val TAXIF_EXCLUDED_TEXTS = setOf( + "JOD", "ECO", "TaxiF", "SUV", "EV", "Mini", "Female", "Van", + "Airport", "Courier", "~", "تحديد نقطة الانطلاق", "الآن", "نقدًا", + "سيارة خاصة", "تاكسي", "جيب", "صغيرة", "كهرباء", "سائقة", + "عائلية", "توصيل", "خدمة", "✈︎", "📦", "🇯🇴" + ) + } override fun onServiceConnected() { super.onServiceConnected() @@ -71,15 +88,15 @@ class ScraperAccessibilityService : AccessibilityService() { Log.i(TAG, "Received Task: $taskId for App: $currentAppName") + taxiFPickupDone = false + taxiFDestinationDone = false currentState = BotState.LAUNCHING_APP // Launch the App val success = AppLauncher.launchApp(this, currentAppName) if (success) { - // We wait for the AccessibilityEvent to tell us the app is opened, - // but we can preemptively change state to SEARCHING_START - currentState = BotState.SEARCHING_START - Log.i(TAG, "State -> SEARCHING_START") + currentState = if (currentAppName == "taxif" || currentAppName == "com.taxif.passenger") BotState.NAVIGATING_HOME else BotState.SEARCHING_START + Log.i(TAG, "State -> ${currentState}") } else { // Failed to launch (app not installed) Log.e(TAG, "Failed to launch app. Returning to IDLE.") @@ -471,37 +488,283 @@ class ScraperAccessibilityService : AccessibilityService() { val task = currentTask ?: return Log.d(TAG, "TaxiF Automation event. State: $currentState") when (currentState) { - BotState.SEARCHING_START -> { - val pickupEdit = findEditableNode(rootNode) - if (pickupEdit != null) { - val startLoc = task.optString("start_location", "Amman") - val arguments = android.os.Bundle().apply { - putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc) + BotState.NAVIGATING_HOME -> { + if (hasJustClickedHome()) return + val rihlaNode = findNodeByText(rootNode, "رحلة") + if (rihlaNode != null) { + var clickable: android.view.accessibility.AccessibilityNodeInfo? = rihlaNode + while (clickable != null && !clickable.isClickable) { + clickable = clickable.parent } - pickupEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) - Log.i(TAG, "TaxiF: Entered start: $startLoc") + if (clickable != null) { + clickable.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK) + Log.i(TAG, "TaxiF: Clicked 'رحلة' tab, entering trip flow") + } else { + rihlaNode.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK) + Log.i(TAG, "TaxiF: Clicked 'رحلة' node directly") + } + taxiFHomeClickTime = System.currentTimeMillis() + return + } + currentState = BotState.SEARCHING_START + Log.i(TAG, "TaxiF: Home screen navigated, state -> SEARCHING_START") + } + BotState.SEARCHING_START -> { + if (detectTaxiFPriceScreen(rootNode)) { + if (hasJustClickedLocation() || hasJustClickedSuggestion()) return + if (!taxiFPickupDone && shouldEditLocation(rootNode, task, isPickup = true)) { + clickLocationRow(rootNode, isPickup = true) + taxiFLocationClickTime = System.currentTimeMillis() + return + } + taxiFPickupDone = true currentState = BotState.SEARCHING_END + return + } + if (!taxiFPickupDone && handleTaxiFSearchScreen(rootNode, task.optString("start_location", "Amman"))) { + taxiFPickupDone = true + Log.i(TAG, "TaxiF: Handled pickup search, waiting for price screen") } } BotState.SEARCHING_END -> { - val destEdit = findEditableNode(rootNode) - if (destEdit != null) { - val endLoc = task.optString("end_location", "Airport") - val arguments = android.os.Bundle().apply { - putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc) + if (detectTaxiFPriceScreen(rootNode)) { + if (hasJustClickedLocation() || hasJustClickedSuggestion()) return + if (!taxiFDestinationDone && shouldEditLocation(rootNode, task, isPickup = false)) { + clickLocationRow(rootNode, isPickup = false) + taxiFLocationClickTime = System.currentTimeMillis() + return } - destEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) - Log.i(TAG, "TaxiF: Entered end: $endLoc") + taxiFDestinationDone = true currentState = BotState.READING_PRICE + taxiFCollectedPrices.clear() + taxiFPriceScrollAttempts = 0 + return + } + if (!taxiFDestinationDone && handleTaxiFSearchScreen(rootNode, task.optString("end_location", "Airport"))) { + taxiFDestinationDone = true + Log.i(TAG, "TaxiF: Handled dest search, waiting for price screen") } } BotState.READING_PRICE -> { - searchPriceByCurrency(rootNode) + if (!hasJustClickedSuggestion()) { + collectAndScrollPrices(rootNode) + } } else -> {} } } + private fun hasJustClickedHome(): Boolean { + return (System.currentTimeMillis() - taxiFHomeClickTime) < 1500 + } + + private fun hasJustClickedLocation(): Boolean { + return (System.currentTimeMillis() - taxiFLocationClickTime) < 1500 + } + + private fun hasJustClickedSuggestion(): Boolean { + return (System.currentTimeMillis() - taxiFSuggestionClickTime) < 1500 + } + + private fun getLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?): List { + val texts = mutableListOf() + collectLocationTexts(node, texts) + return texts + } + + private fun collectLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?, texts: MutableList) { + if (node == null) return + val text = node.text?.toString()?.trim() ?: "" + val className = node.className?.toString() ?: "" + val isEditText = className == "android.widget.EditText" || node.isEditable + if (text.isNotBlank() && text.length > 2 && !isEditText && TAXIF_EXCLUDED_TEXTS.none { text.contains(it) }) { + texts.add(text) + } + for (i in 0 until node.childCount) { + collectLocationTexts(node.getChild(i), texts) + } + } + + private fun shouldEditLocation(rootNode: android.view.accessibility.AccessibilityNodeInfo, task: JSONObject, isPickup: Boolean): Boolean { + val locationTexts = getLocationTexts(rootNode) + val taskLoc = if (isPickup) task.optString("start_location", "") else task.optString("end_location", "") + if (taskLoc.isEmpty()) return false + val matchFound = locationTexts.any { text -> + text.contains(taskLoc, ignoreCase = true) || taskLoc.contains(text, ignoreCase = true) + } + Log.d(TAG, "TaxiF: Location texts=$locationTexts, taskLoc=$taskLoc, matchFound=$matchFound") + return !matchFound + } + + private fun clickLocationRow(rootNode: android.view.accessibility.AccessibilityNodeInfo?, isPickup: Boolean) { + if (rootNode == null) return + val locationNodes = getLocationTextNodes(rootNode) + val targetIndex: Int + if (isPickup) { + targetIndex = 0 + } else { + val destIdx = locationNodes.indexOfFirst { it.text?.toString() == "عنوان الإنزال" } + targetIndex = if (destIdx >= 0) destIdx else minOf(1, locationNodes.size - 1) + } + if (targetIndex < locationNodes.size) { + val locNode = locationNodes[targetIndex] + var clickable: android.view.accessibility.AccessibilityNodeInfo? = locNode + while (clickable != null && !clickable.isClickable) { + clickable = clickable.parent + } + if (clickable != null) { + clickable.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK) + Log.i(TAG, "TaxiF: Clicked ${if (isPickup) "pickup" else "destination"} row: '${locNode.text}'") + } else { + locNode.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK) + Log.i(TAG, "TaxiF: Clicked ${if (isPickup) "pickup" else "destination"} node directly: '${locNode.text}'") + } + } + } + + private fun getLocationTextNodes(node: android.view.accessibility.AccessibilityNodeInfo?, results: MutableList = mutableListOf()): List { + if (node == null) return results + val text = node.text?.toString()?.trim() ?: "" + val className = node.className?.toString() ?: "" + val isEditText = className == "android.widget.EditText" || node.isEditable + if (text.isNotBlank() && text.length > 2 && !isEditText && TAXIF_EXCLUDED_TEXTS.none { text.contains(it) }) { + results.add(node) + } + for (i in 0 until node.childCount) { + getLocationTextNodes(node.getChild(i), results) + } + return results + } + + private fun handleTaxiFSearchScreen(rootNode: android.view.accessibility.AccessibilityNodeInfo, location: String): Boolean { + val editTexts = findAllEditTexts(rootNode) + if (editTexts.isEmpty()) return false + val editField = editTexts[0] + val currentText = editField.text?.toString() ?: "" + if (currentText != location) { + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, location) + } + editField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "TaxiF: Typed '$location' in search field") + } + if (hasJustClickedSuggestion()) return false + val firstWord = location.split(" ").firstOrNull() ?: location + if (clickFirstSuggestion(rootNode, firstWord)) { + taxiFSuggestionClickTime = System.currentTimeMillis() + Log.i(TAG, "TaxiF: Clicked suggestion matching '$firstWord'") + return true + } + return false + } + + private fun clickFirstSuggestion(rootNode: android.view.accessibility.AccessibilityNodeInfo?, target: String): Boolean { + if (rootNode == null) return false + val recycler = findRecyclerView(rootNode) ?: return false + for (i in 0 until recycler.childCount) { + val child = recycler.getChild(i) ?: continue + if (child.isClickable && suggestionContainsText(child, target)) { + child.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK) + return true + } + } + return false + } + + private fun suggestionContainsText(node: android.view.accessibility.AccessibilityNodeInfo?, target: String): Boolean { + if (node == null) return false + val text = node.text?.toString() ?: "" + if (text.contains(target, ignoreCase = true)) return true + for (i in 0 until node.childCount) { + if (suggestionContainsText(node.getChild(i), target)) return true + } + return false + } + + private fun findRecyclerView(node: android.view.accessibility.AccessibilityNodeInfo?): android.view.accessibility.AccessibilityNodeInfo? { + if (node == null) return null + val className = node.className?.toString() ?: "" + if (className.contains("RecyclerView") || className.contains("ListView") || className.contains("Recycler")) { + return node + } + for (i in 0 until node.childCount) { + val result = findRecyclerView(node.getChild(i)) + if (result != null) return result + } + return null + } + + private fun detectTaxiFPriceScreen(node: android.view.accessibility.AccessibilityNodeInfo?): Boolean { + val hasConfirmBtn = findNodeByText(node, "تحديد نقطة الانطلاق") != null + if (hasConfirmBtn) return true + val hasJOD = findNodeByText(node, "JOD") != null + val hasRideOption = findNodeByText(node, "ECO") != null || + findNodeByText(node, "TaxiF") != null || + findNodeByText(node, "سيارة خاصة") != null || + findNodeByText(node, "Airport") != null || + findNodeByText(node, "توصيل المطار") != null + return hasJOD && hasRideOption + } + + private fun collectAndScrollPrices(rootNode: android.view.accessibility.AccessibilityNodeInfo) { + findAllJODPrices(rootNode, taxiFCollectedPrices) + if (taxiFPriceScrollAttempts < 3) { + val recycler = findHorizontalRecyclerView(rootNode) + if (recycler != null) { + recycler.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) + taxiFPriceScrollAttempts++ + Log.i(TAG, "TaxiF: Scrolled price list, attempt $taxiFPriceScrollAttempts") + } + return + } + val prices = taxiFCollectedPrices.distinct() + taxiFCollectedPrices.clear() + taxiFPriceScrollAttempts = 0 + if (prices.isNotEmpty()) { + prices.forEach { (price, label) -> + Log.i(TAG, "TaxiF: Found price option: ${label.take(50)} = $price JOD") + } + val cheapest = prices.minByOrNull { it.first } ?: prices.first() + Log.i(TAG, "TaxiF: Submitting cheapest price: ${cheapest.second.take(50)} = ${cheapest.first} JOD") + submitPriceToServer("${cheapest.first} JOD") + } else { + Log.w(TAG, "TaxiF: No JOD prices found, falling back to generic search") + searchPriceByCurrency(rootNode) + } + currentState = BotState.IDLE + } + + private fun findHorizontalRecyclerView(node: android.view.accessibility.AccessibilityNodeInfo?): android.view.accessibility.AccessibilityNodeInfo? { + if (node == null) return null + val className = node.className?.toString() ?: "" + if (className.contains("RecyclerView") || className.contains("HorizontalScrollView")) { + return node + } + for (i in 0 until node.childCount) { + val result = findHorizontalRecyclerView(node.getChild(i)) + if (result != null) return result + } + return null + } + + private fun findAllJODPrices(node: android.view.accessibility.AccessibilityNodeInfo?, prices: MutableList>) { + if (node == null) return + val text = node.text?.toString() ?: "" + if (text.contains("JOD")) { + val converted = arabicToWestern(text) + val match = Regex("""(\d+\.?\d*)""").find(converted) + if (match != null) { + val price = match.value.toDoubleOrNull() + if (price != null && price > 0) { + prices.add(price to text.trim()) + } + } + } + for (i in 0 until node.childCount) { + findAllJODPrices(node.getChild(i), prices) + } + } + private fun handleJeenyAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) { val task = currentTask ?: return Log.d(TAG, "Jeeny Automation event. State: $currentState") @@ -644,6 +907,15 @@ class ScraperAccessibilityService : AccessibilityService() { return list } + private fun arabicToWestern(text: String): String { + val mapping = mapOf( + '٠' to '0', '١' to '1', '٢' to '2', '٣' to '3', '٤' to '4', + '٥' to '5', '٦' to '6', '٧' to '7', '٨' to '8', '٩' to '9', + '٫' to '.' + ) + return text.map { mapping[it] ?: it }.joinToString("") + } + override fun onInterrupt() { Log.w(TAG, "Accessibility Service Interrupted") }