From ee1b6e8607e6797d769238d1de0c87614553dc2f Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 26 Jan 2025 13:51:46 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=B9=D0=BA=D0=B5=D1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__pycache__/__init__.cpython-313.pyc | Bin 158 -> 0 bytes app/__pycache__/app.cpython-313.pyc | Bin 3872 -> 0 bytes telegram/__pycache__/__init__.cpython-313.pyc | Bin 163 -> 0 bytes telegram/__pycache__/client.cpython-313.pyc | Bin 2681 -> 0 bytes widgets/__pycache__/__init__.cpython-313.pyc | Bin 162 -> 0 bytes widgets/__pycache__/chat.cpython-313.pyc | Bin 1930 -> 0 bytes widgets/__pycache__/dialog.cpython-313.pyc | Bin 2238 -> 0 bytes widgets/__pycache__/message.cpython-313.pyc | Bin 2141 -> 0 bytes 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/__pycache__/__init__.cpython-313.pyc delete mode 100644 app/__pycache__/app.cpython-313.pyc delete mode 100644 telegram/__pycache__/__init__.cpython-313.pyc delete mode 100644 telegram/__pycache__/client.cpython-313.pyc delete mode 100644 widgets/__pycache__/__init__.cpython-313.pyc delete mode 100644 widgets/__pycache__/chat.cpython-313.pyc delete mode 100644 widgets/__pycache__/dialog.cpython-313.pyc delete mode 100644 widgets/__pycache__/message.cpython-313.pyc diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 4a6205a883569d21007a2602af23c70299368b97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmey&%ge<81Y#5W(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iYepzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE`Pb?_VkB`sH%PfhH*DI*J g#bJ}1pHiBWYFESxG!0~1F^KVznURsPh#ANN0P}k$#{d8T diff --git a/app/__pycache__/app.cpython-313.pyc b/app/__pycache__/app.cpython-313.pyc deleted file mode 100644 index 53fddff829e3b45b48bf927f0640ca857485d6f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3872 zcmb7HO>7&-6`mn?$>rZtBK1Sc`k_Tj7HyeKTK~IoP0KB8s$mVSevMW#3_QpY7Z@nws6{}jZVET!?<(?GE^WZ-xAA#ouY@n+2v9c z;k4)o`{vEOH^Z4X-}}*q&*w(a-uv0>`H&N#Puaj~e1qBeB{0{Jgd{wPQkdYBjo2b= zY@4*FIKriP!W(1zq$A}d&Xhoel#94hZsIoPxk*pTOS~x`@ufr}ru@X8>L4AdPSR<} z`N=>kNP-6MnCwb*lWv1|PWGgFNw2{Rlc7|Ygj0Q_FM>j77)h=^B)QLV`|Rd9$+o;F z=Dp7bF&h=9m7=7>WEK};cN=wN77KDAM+Mf; zXVm$uc}+eVOKe1u;mFg=I{cqLF`fV8gRsW;@2mJ&6cJ=j3fp|QjWmPqyY8Q)2oUvI9 z+gObOjy>kac_2seH8g8U&@`Sw-v{}=#!LjwTJS`(4ohuTu(%mCWdPe6o4o~<8B}bp z$)gpVjyWl>R>1I7kmW+Dpvf``!Wz=WNH-AboX?bUMbPkT4P?n#16nIK%Bf0mj=E%+ z$*8Ixylk#~btEq{60Z4N%tx`z9)3YzWc#-t8 zpX_QedfYmfA0%oN$m7dsP4K*tYx(gW~057_50>4P;fI~jzII zLY-+r`yZC!H?4xn80g1r#?>irMl%CurU}RZh-t7DF%7`>f&mbIQ}E0z>OT0LnOsPCkoJ$v$JBVkT;9B@X7rwn-R1X|BZ2x-8AJ z-;x}#vcnp0_9FVaO>!QxmmF{d2HCbY^W!0@F_0nf&JRxq^{axu5*f1bRe$vGoUsnqpiMIjgE zatU&p!IDn+DHd54%1a96N|^nq5gVkyc!y>hYnUl2X7NSbWjf+uLq7V zJMV@E-WmM)pgwk}7Cu}LpQwgUe0a7NPS?X{tKqZy_s-YCFD#39g`T?5Ulsb_o~{YQ zbs<(2Vjl~;KkXS<{`TEq`1+AIjx3+J8wg#GzY(to#;Sp_<%zXm?+xx2e{1eHg_Xjs zS$*_SHFy}bynS_Vr0R{_?D~gy^nnZY4u0-J!3q4O%5ZRHV8V;v!4q!o=JDOYZgD`C z(xUKfJTMp*sb`y!#B?wl0HI|L^8X=h%r#^8X0ntxCbfXHgsx#&+jJMH=|-jmYCQPc z*NAPP*@Us>C{NbQqwQ!Mv53W54$eDgi=ZJmX9T6A&)A319R6yJJTf&5SUb{G+=^rJ zH0((Y%&O!FD!;WU$x03g5;ekfZqj4bIjJwufhUWezqpno)Lv9WC7* zEMEsUvl3^<3U{ucHqtfO1`DYdLWQin1a%bDGh_&+8YPjLu01VM+S}^WErM~jiaqt? zX5uol_MWP^rzR%qLP8f3YyPfRFI4@}ntwz$Gxk$vM!Pz=AHR$Da=!@f$1!Z26S60J z*L2L!$~+dcWSP2Us2p-otpo3s*4urHwGRe z=pKn6+CV_a4r`-yte*-RRIl26xkMOz2zzPj(Ug}oxObdA7OUTkE6{*7-Aoi}i+8a| zK-ke(L2NF+P{={jPzlScgr&?D7i48CVZn}i8)|VD=gmAKT2qZ=!2ETy&W@XPwo#B% zhgN<;DXE)v>=@&9w0j-xT1V*`O0T2II-0zP a2JWHl_t4YpDEg4wfk%HD{Q^N}Z1^9?s9&l8 diff --git a/telegram/__pycache__/__init__.cpython-313.pyc b/telegram/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index f890a5cc6786001b08ede4c62e8e9df92dad2018..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmey&%ge<81PT-T(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~i8epzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE0bM)imGxIV_;^XxSDsOSv dy?*Sy>EXd}L;1WGrF^vH7fK6rR~XZyd)?LVh5mi31^t)q)Wsq@)ogY7dYU3&og5f@-z3H_pPwPG>f$ zBK1(w3V}qGa)_#4+LTHa>7|z*s;U>RJz?BpYlT!*J>>>*5z4La&Du@?SL&%F?VC40 zZ)V>6-p^KAT7m@H>h0C+Z!#f&;h@%(DpNTP%o<6OB%LNxl+h`PNmDYDqhv2RK$21q zNy?*=zndvyPJubKKBs22SYQ)3#AGjg!E&rDGZ)@+Y%Aw_nzala_tYiVc6i!9#;4UY zN#z8L*T`kUXp%4~NtukdG9@VkRg-WV(~?a|wTE1xERfV-tS1AkDXYhVUgz@{zRS$T zMT>2Mx+GoG5${vs!^z60K-S153Y}<4I@>;qE6`HJsn4g#OznmKqM$Q)&I>IiU(#w* zGkT5N=?{?6>Y!$`D9MtTl<<@#Zn2ok@On>6`zC8MGZ{me&6qcH8OLG_74d=z>%(&v z=VsQ5DW1YDXU@~iMcc45o{s&z$>+Vug1@CE%TYE8%{v`mHd%Z=zhK49W!ud&lgH1q z{1q$h@_5z;;#RRU+$Fm{i2Khvvbf^uhGFMy*D$=khE_jPTgN;3VIUuqM_q&K7w=vy zcAdJR+-ffCq1EP_%_aR%K|i#f|8coAcILs@nc~>g@A`BF1W39L01VJBpTJkS0OTNv z0&oBe@GrKRDuBD^Yeqi{&OHz7%XCdns3ABdOLUS@$K*M>K@%~FHG`O^a@S<;24!Iw zaePITwc|n{ozLaKNZu=rm98!$@O(dzP0+d=ZeP86^J*#FUkLXX!viIKpr{XQS3`(;?*>ZG`^6+OlrAzNT+5WAp0M**<*xj>@bL#$L3sx;tWMwwE;?EQAM(;h~Z~ zRMdyISubo#?+zpk6@lrm!1$QPNlyCjxTa{5o}{g${RLcea!P6(o};K5v?w_T&jIYa zgCr&GAr}=c#R&)Q%pOAWr)L8GzTGsX)TGZ#=Q;^#hEuqdQwD+h+#Eqqf^d#ePj&1C z+l?u{ONDILVn)U`oqU#KtS(x)jMr=|n2Xg}h<$W7HUPv6Rd*VW%^`F8Badb{<{S*+ zszWwsEw_H^Kx344Z4v(b9vmSYvwPo-P`Nky_4Jq1rQYL(-s3-w-p79O8KF%hpAVHo zdrP7ILa2XTdl(viBE#hU-pM}?pDaekzrTE!mRh5Q*64cM!`6cvmkW{c`}+9ygUI+c ze-&=H);oESex*!Ecj&nGtvn%p*8vkh(+TMp6_|UpXR=SZw_gYTC7-|uu6zV!cXCeA zMoIV*<`>as>WQ~DL!fAZRD(2J!x2_RS=^C84tz=;#H73-dlF<+C1);JUZ9#yW11Iy zTi|#$H$3$rgGH_H9 zqrwZpEjLkLsFjev@rjVXgNCt?&n!8}hYaI`CDW;{L=0ojX54k`oR!NP27|~TtOp5Z z1vY|2JipU0AZ5CC+Av+0+3zp87B`FyA}$xHlVLmwg5YvY1=q=zB&qMvEuE?dwlt~^ z|E($N8&5;3>c3iSPs7f7s|GKr;3FFUConauKq#T5HZcW1SNYNI!nyG2Yh_-y%T7t^WWcW*}1l diff --git a/widgets/__pycache__/__init__.cpython-313.pyc b/widgets/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 60dd34e3c0a50fe566aa73c2073eee422a938b38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmey&%ge<81V$74(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iOepzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE`FV9R#PX!9b$7kkcmc+;F j6;$5hu*uC&Da}c>D`Ewj2(qyl#Q4a}$jDg43}gWS$xJAn diff --git a/widgets/__pycache__/chat.cpython-313.pyc b/widgets/__pycache__/chat.cpython-313.pyc deleted file mode 100644 index 401b80beae9d60a33e4bec18954c5f3a2e54d178..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1930 zcmZ`4TWl0n^xS#u%x-sETBI!t3CkmdiNZ?QswB42Vh9woF${~wu1==2)9sY$4&Ir? z$B(5SiqS@lZ9>IFKFeP*f;B#p@`Zo1^+zW)Cir3cSFHwO{P5g6yA_C>wr9@ko_n6R z>zPan5avGmuqufFzcQu;a+6peL}C_n(8&oHB9!D9Eu0XBL@EwRRN}mNLLN%cL=Jjk z7wA$Jbh(`9L!S(&QcY;eDkC)#$X=8Jx4c?lx{-R?rh#LbE*d0WbShOl zu$X-d+M2|=i^QuKPZ=oDfeJdIqApNL&*)NF*2Vn_u)AASVuMmPD7Bm@q)JdAdNK>+ z>bNvcsG_H`KvlgBaZ*oH)b|tGwgVhU%Qb!9_9M08 z_~wXfSFA)cL-xTOfYomOG@=1A3&Z%0;Z&jouV5_0t^PoX^pRNiQlTX42HiUPN~%VMWcnr!>^wPpvC^E<5vsxAuiFNoy`333AE3qfxzCQz9iQLZ=svi->0m54Hg{}3+vqMVZz|l8p=&#$_Uwi7Z=Ks_vww4Dy8MsN zUYx16!EQ3G4a(w;{m&NUrQIr$OOGZ|x>QIF3dyAb1tmA-)Zk|2W`}}u%%1P7Ttqmk zrP(sGCkU50Wf&7)rS391Z5XfBO}DA(FpN=$`hn}z?3#ya#V{(KWf-&zqllyt&vW@I z(rw7o920KAMGs7Tx(zc3s54RzY#&c%n$Lh`yRKniG^~@n!q4!fqHN4KVomVb`*!yo z_(|&jT@vK&m-H)zxtG@f$pw9}a4jZp9SKjK{(gvy!?81AjW)(8=hgs@3PZvjc7O~1 zP>GzmlaS@UH9*8Sliya<)Tkq{-v}`41I<^4Pejz!l3MIP5>|lv@w~7AqxP0IK111S y9p|u>QEc<$$3lHNF75!`k1{rVbe)Xbjt;7Id@RZI2~;x999$ z6%&(E6GDRo5^W?T5)>cct)h?`pp`!HI9o!Zn-UUz;*D5L3Gu;iX7>uIM(1+h%zX3B zH{ZPp-|=oELt_C=_)EM$#VL zwzZ<3_mA;qa3-4H4D%1*Mh6h)6oh$&!-B%&kRlDBS2#szL5JnT92S*O3&LR1aYs`y zm?RVA8m#|7CQ|NEGVP9l;xd`Yhy3`Mqz-AI^J_tD<;Kx|rlz7lG=ScRq`ccWAShf9 zJbw|T`963c-Y9rd-qdVcw>`OF*xEr;FQj=-w5t^zd$Ot;Wy4X`wCIIw-5kOR@P`|y zNP_Sps>-&js!g+=f;Qs4mKoM?=8#p=Gup7>SXi?&Z)5AQo_Fj_(E#D-re4HaNp-45 z<|Fp_cBa5yrn7PcFQcv#{!4Y%LWqFUUIF6K2%3}QW0EU3pUFlD9=$D zG#UCf_|Ff5whg5qZ~GV5y8T?BlC9YhmjriU!Epkx0U6c)do`;G#?3}sTmMoGg*DBYNpUzm|60n`nx z?4+U0!5-86VoHse79^%VFfCJfo8{2ikPDQ;O(3E0tWw3Yb+0}^dN8k}cAtZ|j{xcM z6(q=bBcM_RT-9bes0fHtd;eT6Mnb;=W0+-8tK^b3sInoeN1+-+X-VUb5p(vg7dr z?CiXgT>qGN}iU6lRZPACC-w0wH4WPYLyQJ-<7E7AKb4jhbT!7D1WDqQ6JXJj!olA1qn)8 zW%VHYq^K1PSk((&bC9ZktUL?*Y|2|B=oh)}lsqP%N{l5)bI%=lBW?!$CHPSF_3cPU zDXEsVlCG*=R8>n>p=#1`ovMCV)y!Zep{hd$wjI+b>tzd;B~>k0c~xcHh-n$%mq2(c zfO#DaK5VMyIM{%2bsNm%0Waru(**YBFeOd~zSkM}Fa;SC6sGnn5RZG-}s5p>MS-*wVmIsgCw diff --git a/widgets/__pycache__/message.cpython-313.pyc b/widgets/__pycache__/message.cpython-313.pyc deleted file mode 100644 index 8d066b63ef0c394c7ed126957dff943805a3ea77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2141 zcmah~|8EpU6rb6>y}diG^sEJrLhM0_vV1#2C>2r&L@<%o#<*6S96H(F?Hx<@Zp+Lb z3O@lqm^OeH5*yK&@C&~f6N5$+6Cv?G*t0d9laiSDQ-32?13&nKZ{~KdVA}AK`|O*U zH*em|d+&2gU0o>z^7ZvbWjKb=pJEV+7Ai|eK$%4*GVusHi7CzrT^UhMs#KM_Iubjn zQ7wnEXe%;feaO^KX@hEeKaE%7hQ25U2IktRHEFw`BwzDtzU9z_DRI5%T8!C@CrS>pCSAJ(%Nnas*_0;;acYiF2-mCa+i=Zcq-w+c zV$62SRHTw-1iT<%jexX(E&!e+M0zKLC19Lu%SC=Pf6k)$(_YoiTjw0#qZZ4*MZGh2 z(P#OJ1K``PU7=Q$_;n}$q0AJ^hcWD#I#0WW`%L)6d!jJKA*>(Z$wjn~=$g@+iR_Ki z?V%u%Z6?NA8(zAaZmsLPmRbT6RKx;95f3CE_bSZJqH*;0)2pFTq4T7J;c5+!;}tET zu-nqHRKRCEnqB^G4^`^~f?@|7afknjD@sU;sf;TtT68>v1zypjQ%a_~ijU686=#!>T7g4J>``Os_v0De{dUsDJTbYdyG6T)bz^xN=nH|$wYfX3KP~%W*YkvB8V`OGz zKHcAXdH3{*#^}uGe0rd@cWC;J2kFht^yc~W?$*`=?Umos{eL{OW7ktV4y>|+u7`M@ z=@^mc0r)L_DwP~`O`+2>gD@vVK{-?q_m)!V)bYx_5cNv*J%?toBzqwL!LavoKw(?Z zE)?}m3C=7lIn)m)iM7LOKPuxPwTulcDyNfEt}>rWsxq+55QCj4%!Xd{s#6}bd3Qu9 z;lw7eVXpuzLN*q<)?X_u^lbQ2y>a~J=;xzzS!1qe$FJ!fk5#mGW25j%;a|29Y%i}l zmcZAx45{BFLHRLtOw)hCI;c>7k|qUV8u2k9uNx^rvx1I+W>W-;qEvTO}Mhk6oD$bL>zCvxk?fw{UKg1C#YDuI@#`H+V7(YZ?9-{4l Qf5) Date: Sun, 26 Jan 2025 19:18:23 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=83=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20+=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F=D1=8E?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20+=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C?= =?UTF-8?q?=20=D0=B2=D1=81=D1=91=20=D1=81=D1=82=D0=B0=D0=B1=D0=B8=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++ app/__init__.py | 0 main.py | 2 +- screens/auth_screen.py | 56 --------------- screens/chat_screen.py | 64 ----------------- {app => src}/app.py | 15 +--- src/screens.py | 149 +++++++++++++++++++++++++++++++++++++++ {tcss => src}/style.tcss | 0 src/widgets.py | 101 ++++++++++++++++++++++++++ telegram/__init__.py | 0 telegram/client.py | 48 ------------- widgets/__init__.py | 0 widgets/chat.py | 38 ---------- widgets/dialog.py | 33 --------- widgets/message.py | 37 ---------- 15 files changed, 259 insertions(+), 289 deletions(-) create mode 100644 .gitignore delete mode 100644 app/__init__.py delete mode 100644 screens/auth_screen.py delete mode 100644 screens/chat_screen.py rename {app => src}/app.py (61%) create mode 100644 src/screens.py rename {tcss => src}/style.tcss (100%) create mode 100644 src/widgets.py delete mode 100644 telegram/__init__.py delete mode 100644 telegram/client.py delete mode 100644 widgets/__init__.py delete mode 100644 widgets/chat.py delete mode 100644 widgets/dialog.py delete mode 100644 widgets/message.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe0f395 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +test.py +*.session +*.session-journal +__pycache__ +*/__pycache__ \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/main.py b/main.py index d438f0f..9d0ccd7 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -from app.app import TelegramTUI +from src.app import TelegramTUI if __name__ == "__main__": tg = TelegramTUI() diff --git a/screens/auth_screen.py b/screens/auth_screen.py deleted file mode 100644 index 4406c4c..0000000 --- a/screens/auth_screen.py +++ /dev/null @@ -1,56 +0,0 @@ -from textual.screen import Screen -from textual.widgets import Label, Input -from textual.containers import Vertical -from telethon import TelegramClient -from telethon.errors import SessionPasswordNeededError - -class AuthScreen(Screen): - """Класс логина в аккаунт""" - - def __init__( - self, - name = None, - id = None, - classes = None, - telegram_client: TelegramClient | None = None - ): - super().__init__(name, id, classes) - self.client = telegram_client - - def on_mount(self): - self.ac = self.query_one("#auth_container") - - def compose(self): - with Vertical(id="auth_container"): - yield Label("Добро пожаловать в Telegram TUI") - yield Input(placeholder="Номер телефона", id="phone") - yield Input(placeholder="Код", id="code", disabled=True) - yield Input( - placeholder="Пароль", - id="password", - password=True, - disabled=True - ) - - async def on_input_submitted(self, event: Input.Submitted) -> None: - if event.input.id == "phone": - self.phone = event.value - self.ac.query_one("#phone").disabled = True - self.ac.query_one("#code").disabled = False - await self.client.send_code_request(phone=self.phone) - elif event.input.id == "code": - try: - self.code = event.value - self.ac.query_one("#code").disabled = True - await self.client.sign_in(phone=self.phone, code=self.code) - self.app.pop_screen() - self.app.push_screen("chats") - except SessionPasswordNeededError: - self.ac.query_one("#code").disabled = True - self.ac.query_one("#password").disabled = False - elif event.input.id == "password": - self.password = event.value - await self.client.sign_in(password=self.password) - await self.client.start() - self.app.pop_screen() - self.app.push_screen("chats") diff --git a/screens/chat_screen.py b/screens/chat_screen.py deleted file mode 100644 index 6e58992..0000000 --- a/screens/chat_screen.py +++ /dev/null @@ -1,64 +0,0 @@ -from textual.screen import Screen -from textual.widgets import Footer, Static -from textual.containers import Horizontal, VerticalScroll -from telethon import TelegramClient, events -from widgets.dialog import Dialog -from widgets.chat import Chat - -class ChatScreen(Screen): - """Класс экрана чатов, он же основной экран приложения""" - - def __init__( - self, - name = None, - id = None, - classes = None, - telegram_client: TelegramClient | None = None - ): - super().__init__(name, id, classes) - self.telegram_client = telegram_client - self.telegram_client.on(events.NewMessage())(self.update_chat_list) - - async def on_mount(self): - self.chat_container = self\ - .query_one("#main_container")\ - .query_one("#chats")\ - .query_one("#chat_container") - - self.limit = 100 - for i in range(self.limit): - chat = Chat(id=f"chat-{i + 1}", notify_func=self.notify) - self.chat_container.mount(chat) - #self.mount_chats(limit=25) - - await self.update_chat_list() - - def mount_chats(self, limit: int): - self.limit = limit - chats_amount = len(self.chat_container.query(Chat)) - if limit > chats_amount: - for i in range(limit - chats_amount): - chat = Chat(id=f"chat-{i + 1 + (limit - chats_amount)}") - self.chat_container.mount(chat) - elif not (limit == chats_amount): - for i in range(chats_amount - limit): - self.chat_container.query(Chat).last().remove() - - async def update_chat_list(self): - dialogs = await self.telegram_client.get_dialogs(limit=self.limit) - - for i in range(len(dialogs)): - chat = self.chat_container.query_one(f"#chat-{i + 1}") - chat.username = str(dialogs[i].name) - chat.msg = str(dialogs[i].message.message) - chat.peer_id = dialogs[i].id - #self.notify("Новое сообщение") #колхоз дебаг - - def compose(self): - yield Footer() - with Horizontal(id="main_container"): - with Horizontal(id="chats"): - yield VerticalScroll(Static(id="chat_container")) - #TODO: сделать кнопку чтобы прогрузить больше чатов - - yield Dialog() diff --git a/app/app.py b/src/app.py similarity index 61% rename from app/app.py rename to src/app.py index 3ddbf85..1b51c91 100644 --- a/app/app.py +++ b/src/app.py @@ -1,21 +1,12 @@ from telethon import TelegramClient, events -from telethon.errors import SessionPasswordNeededError -from textual.app import App, ComposeResult -from textual.containers import Horizontal, VerticalScroll, Vertical -from textual.widgets import Static, Footer, Label, Input, Button -from textual.screen import Screen -from textual.events import Event -from widgets.chat import Chat -from widgets.dialog import Dialog +from textual.app import App from tokens import api_id, api_hash -from screens.auth_screen import AuthScreen -from screens.chat_screen import ChatScreen +from src.screens import AuthScreen, ChatScreen class TelegramTUI(App): """Класс приложения""" - CSS_PATH = "../tcss/style.tcss" - #SCREENS = {"chats": ChatScreen} + CSS_PATH = "style.tcss" async def on_mount(self) -> None: self.telegram_client = TelegramClient("user", api_id, api_hash) diff --git a/src/screens.py b/src/screens.py new file mode 100644 index 0000000..1c23140 --- /dev/null +++ b/src/screens.py @@ -0,0 +1,149 @@ +from textual.screen import Screen +from textual.widgets import Label, Input, Footer, Static +from textual.containers import Vertical, Horizontal, VerticalScroll +from telethon.errors import SessionPasswordNeededError +from telethon import TelegramClient, events, utils +from src.widgets import Dialog, Chat + +class AuthScreen(Screen): + """Класс логина в аккаунт""" + + def __init__( + self, + name = None, + id = None, + classes = None, + telegram_client: TelegramClient | None = None + ): + super().__init__(name, id, classes) + self.client = telegram_client + + def on_mount(self): + self.ac = self.query_one("#auth_container") + + def compose(self): + with Vertical(id="auth_container"): + yield Label("Добро пожаловать в Telegram TUI") + yield Input(placeholder="Номер телефона", id="phone") + yield Input(placeholder="Код", id="code", disabled=True) + yield Input( + placeholder="Пароль", + id="password", + password=True, + disabled=True + ) + + async def on_input_submitted(self, event: Input.Submitted) -> None: + if event.input.id == "phone": + self.phone = event.value + self.ac.query_one("#phone").disabled = True + self.ac.query_one("#code").disabled = False + await self.client.send_code_request(phone=self.phone) + elif event.input.id == "code": + try: + self.code = event.value + self.ac.query_one("#code").disabled = True + await self.client.sign_in(phone=self.phone, code=self.code) + self.app.pop_screen() + self.app.push_screen("chats") + except SessionPasswordNeededError: + self.ac.query_one("#code").disabled = True + self.ac.query_one("#password").disabled = False + elif event.input.id == "password": + self.password = event.value + await self.client.sign_in(password=self.password) + await self.client.start() + self.app.pop_screen() + self.app.push_screen("chats") + self.app.notify("") + +class ChatScreen(Screen): + """Класс экрана чатов, он же основной экран приложения""" + + def __init__( + self, + name = None, + id = None, + classes = None, + telegram_client: TelegramClient | None = None + ): + super().__init__(name, id, classes) + self.telegram_client = telegram_client + + async def on_mount(self): + self.limit = 30 + + self.chat_container = self\ + .query_one("#main_container")\ + .query_one("#chats")\ + .query_one("#chat_container") + + print("Первоначальная загрузка виджетов чатов...") + self.mount_chats( + len( + await self.telegram_client.get_dialogs( + limit=self.limit, archived=False + ) + ) + ) + print("Первоначальная загрузка виджетов чата завершена") + + self.is_chat_update_blocked = False + await self.update_chat_list() + + print("Первоначальная загрузка чатов завершена") + + for event in ( + events.NewMessage(), + events.MessageDeleted(), + events.MessageEdited() + ): + self.telegram_client.on(event)(self.update_chat_list) + + def mount_chats(self, limit: int): + print("Загрузка виджетов чатов...") + + chats_amount = len(self.chat_container.query(Chat)) + + if limit > chats_amount: + for i in range(limit - chats_amount): + chat = Chat(id=f"chat-{i + chats_amount + 1}") + self.chat_container.mount(chat) + elif limit < chats_amount: + for i in range(chats_amount - limit): + self.chat_container.query(Chat).last().remove() + + print("Виджеты чатов загружены") + + async def update_chat_list(self, event = None): + print("Запрос обновления чатов") + if not self.is_chat_update_blocked: + self.is_chat_update_blocked = True + dialogs = await self.telegram_client.get_dialogs( + limit=self.limit, archived=False + ) + print("Получены диалоги") + limit = len(dialogs) + #limit = 30 + self.mount_chats(limit) + + for i in range(limit): + chat = self.chat_container.query_one(f"#chat-{i + 1}") + chat.username = str(dialogs[i].name) + chat.msg = str(dialogs[i].message.message) + chat.peer_id = dialogs[i].id + #self.notify("Новое сообщение") #колхоз дебаг + + self.is_chat_update_blocked = False + print("Чаты обновлены") + else: + print("Обновление чатов невозможно: уже выполняется") + + def compose(self): + yield Footer() + with Horizontal(id="main_container"): + with Horizontal(id="chats"): + yield VerticalScroll(Static(id="chat_container")) + #TODO: сделать кнопку чтобы прогрузить больше чатов + + yield Dialog() diff --git a/tcss/style.tcss b/src/style.tcss similarity index 100% rename from tcss/style.tcss rename to src/style.tcss diff --git a/src/widgets.py b/src/widgets.py new file mode 100644 index 0000000..8f44bc0 --- /dev/null +++ b/src/widgets.py @@ -0,0 +1,101 @@ +from textual.containers import Horizontal, Vertical, Container, VerticalScroll +from textual.widget import Widget +from textual.reactive import Reactive +from textual.widgets import Input, Button, Label + +class Chat(Widget): + """Класс виджета чата для панели чатов""" + + username = Reactive(" ", recompose=True) + msg = Reactive(" ", recompose=True) + peer_id = Reactive(0) + + def __init__( + self, + name: str | None = None, + notify_func = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False + ): + super().__init__( + name=str(name), + id=id, + classes=classes, + disabled=disabled + ) + self.notify = notify_func + + def _on_click(self): + self.msg = str(self.peer_id) + self.notify("нажат чат") + + def compose(self): + with Horizontal(): + yield Label(f"┌───┐\n│ {self.username[:1]} │\n└───┘") + with Vertical(): + yield Label(self.username, id="name") + yield Label(self.msg, id="last_msg") + +class Dialog(Widget): + """Класс окна диалога""" + + def __init__(self, id=None, classes=None, disabled=False): + super().__init__(id=id, classes=classes, disabled=disabled) + + def compose(self): + with Vertical(): + with VerticalScroll(id="dialog"): + yield Message(message="привет, я ыплыжлп", is_me=True) + yield Message(message="о, дщытрапшщцрущ", is_me=False) + yield Message(message="ДАТОУШЩАРШЩУРЩША!!!!", is_me=False) + # должно быть примерно + # is_me = message.from_id == client.get_peer_id("me") + + # но я могу ошибаться, я это фиш если что + + #TODO: сделать кнопку чтобы прогрузить больше сообщений, + #но при этом чтобы при перезаходе в чат оставались + #прогруженными только 10 сообщений, + #а остальные декомпоузились + + with Horizontal(id="input_place"): + yield Input(placeholder="Сообщение", id="msg_input") + yield Button(label="➤", id="send", variant="primary") + + def on_button_pressed(self, event): # self добавил + self.app.notify("Нажато отправить") + +class Message(Widget): + """Класс виджета сообщений для окна диалога""" + + def __init__( + self, + name=None, + message=None, + is_me=None, + id=None, + classes=None, + disabled=False + ): + super().__init__(name=name, id=id, classes=classes, disabled=disabled) + self.message = message + self.is_me = is_me + + def on_mount(self): + container = self.query_one(Container) + label = container.query_one(Label) + if self.is_me: + self.styles.padding = (0, 0, 0, 15) + label.styles.text_align = "right" + container.styles.align_horizontal = "right" + label.styles.border = ("solid", "#4287f5") + else: + self.styles.padding = (0, 15, 0, 0) + label.styles.text_align = "left" + container.styles.align_horizontal = "left" + label.styles.border = ("solid", "#ffffff") + + def compose(self): + with Container(): + yield Label(str(self.message)) diff --git a/telegram/__init__.py b/telegram/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegram/client.py b/telegram/client.py deleted file mode 100644 index 1010434..0000000 --- a/telegram/client.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -ЭТОТ ФАЙЛ БОЛЬШЕ НЕ ИСПОЛЬЗУЕТСЯ -СКОРО УДАЛИМ -""" - -from telethon import TelegramClient, events, utils - -class TelegramClientWrapper: - """Обёртка для метода TelegramClient из Telethon""" - - def __init__(self, api_id, api_hash, message_handler): - self.message_handler = message_handler - self.client = TelegramClient('user', api_id, api_hash) - self.client.on(events.NewMessage())(self.local_message_handler) - - async def local_message_handler(self, event): - await self.message_handler() - - async def connect(self): - await self.client.connect() - - async def start(self): - await self.client.start() - - async def disconnect(self): - await self.client.disconnect() - - async def get_dialogs(self, limit=None): - await self.client.get_dialogs(limit=limit) - dialogs_list = [] - async for dialog in self.client.iter_dialogs(limit=limit): - dialogs_list.append(dialog) - #return [self._map_dialog(d) for d in dialogs_list] - return dialogs_list - -#ого: - """def _map_dialog(self, dialog): - return DialogInfo( - id=dialog.id, - name=utils.get_display_name(dialog.entity), - message=dialog.message - )""" - -"""class DialogInfo: - def __init__(self, id, name, message): - self.id = id - self.name = name - self.message = message""" diff --git a/widgets/__init__.py b/widgets/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/widgets/chat.py b/widgets/chat.py deleted file mode 100644 index 13b9a7c..0000000 --- a/widgets/chat.py +++ /dev/null @@ -1,38 +0,0 @@ -from textual.widgets import Label -from textual.containers import Horizontal, Vertical -from textual.widget import Widget -from textual.reactive import Reactive - -class Chat(Widget): - """Класс виджета чата для панели чатов""" - - username = Reactive(" ", recompose=True) - msg = Reactive(" ", recompose=True) - peer_id = Reactive(0) - - def __init__( - self, - name: str | None = None, - notify_func = None, - id: str | None = None, - classes: str | None = None, - disabled: bool = False - ): - super().__init__( - name=str(name), - id=id, - classes=classes, - disabled=disabled - ) - self.notify = notify_func - - def _on_click(self): - self.msg = str(self.peer_id) - self.notify("нажат чат") - - def compose(self): - with Horizontal(): - yield Label(f"┌───┐\n│ {self.username[:1]} │\n└───┘") - with Vertical(): - yield Label(self.username, id="name") - yield Label(self.msg, id="last_msg") diff --git a/widgets/dialog.py b/widgets/dialog.py deleted file mode 100644 index 05a9fe0..0000000 --- a/widgets/dialog.py +++ /dev/null @@ -1,33 +0,0 @@ -from textual.widgets import Input, Button, Label -from textual.containers import Horizontal, VerticalScroll, Vertical -from textual.widget import Widget -from widgets.message import Message - -class Dialog(Widget): - """Класс окна диалога""" - - def __init__(self, id=None, classes=None, disabled=False): - super().__init__(id=id, classes=classes, disabled=disabled) - - def compose(self): - with Vertical(): - with VerticalScroll(id="dialog"): - yield Message(message="привет, я ыплыжлп", is_me=True) - yield Message(message="о, дщытрапшщцрущ", is_me=False) - yield Message(message="ДАТОУШЩАРШЩУРЩША!!!!", is_me=False) - # должно быть примерно - # is_me = message.from_id == client.get_peer_id("me") - - # но я могу ошибаться, я это фиш если что - - #TODO: сделать кнопку чтобы прогрузить больше сообщений, - #но при этом чтобы при перезаходе в чат оставались - #прогруженными только 10 сообщений, - #а остальные декомпоузились - - with Horizontal(id="input_place"): - yield Input(placeholder="Сообщение", id="msg_input") - yield Button(label="➤", id="send", variant="primary") - - def on_button_pressed(self, event): # self добавил - self.app.notify("Нажато отправить") diff --git a/widgets/message.py b/widgets/message.py deleted file mode 100644 index 88468e0..0000000 --- a/widgets/message.py +++ /dev/null @@ -1,37 +0,0 @@ -from textual.widgets import Label -from textual.containers import Container -from textual.widget import Widget - -class Message(Widget): - """Класс виджета сообщений для окна диалога""" - - def __init__( - self, - name=None, - message=None, - is_me=None, - id=None, - classes=None, - disabled=False - ): - super().__init__(name=name, id=id, classes=classes, disabled=disabled) - self.message = message - self.is_me = is_me - - def on_mount(self): - container = self.query_one(Container) - label = container.query_one(Label) - if self.is_me: - self.styles.padding = (0, 0, 0, 15) - label.styles.text_align = "right" - container.styles.align_horizontal = "right" - label.styles.border = ("solid", "#4287f5") - else: - self.styles.padding = (0, 15, 0, 0) - label.styles.text_align = "left" - container.styles.align_horizontal = "left" - label.styles.border = ("solid", "#ffffff") - - def compose(self): - with Container(): - yield Label(str(self.message))