PKTN?::tg/__init__.py""" Tg TUI client for telegram """ __version__ = '0.0.1' PKYTNLS# tg/main.pyimport logging import logging.handlers import os import threading from curses import wrapper from functools import partial from telegram.client import Telegram from tg.controllers import Controller from tg.models import Model from tg.views import View logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s', handlers=[ logging.handlers.RotatingFileHandler( './tg.log', backupCount=1, maxBytes=1024*256 ), ] ) logger = logging.getLogger(__name__) API_ID = os.getenv('API_ID') API_HASH = os.getenv('API_HASH') PHONE = os.getenv('PHONE') if PHONE is None: raise Exception('Environment variables did not provided') def run(tg, stdscr): # run this function in thread? view = View(stdscr) model = Model(tg) controller = Controller(model, view) controller.tg = tg tg.add_message_handler(controller.update_handler) t = threading.Thread( target=controller.run, ) t.start() t.join() def main(): logger.debug('#' * 64) tg = Telegram( api_id=API_ID, api_hash=API_HASH, phone=PHONE, database_encryption_key='changeme1234', ) tg.login() # model = Model(tg) # print(model.get_me()) # print(model.get_user(246785877)) # print(model.chats.get_chat(77769955)) # return wrapper(partial(run, tg)) if __name__ == '__main__': main() PKJTN tg/tg.pngPNG  IHDRLqgAMA asRGB cHRMz&u0`:pQ<bKGD pHYsHHFk>IDATxy|k&¾,a'PdW lC`ΩEm-PO+Vq(* "( ¾NHY'~jY=GCr}t2?.ceeeT˺Rff222T)--?wuʕz?J(2eǖ/_^nƿ\.UPA%KTTTJ.RJ\r W n|HEDDlٲP*V *xDDDl?2Jӧuy;wN?(第,۱iѮXVUf͚Vڍ.W%2(G8{9qN8GٳYqr/7-5jPzTn]թSGժUx̱ctQ]/ǎu ?~d"""nu~Sԩ^z*[,(pes!qa*TXQ5Ճ B 𛴴4gڵKЁtA8p@!TXQ7V&Mn<ռys&P.--߿_;wԮ]n#Go;f͚jٲ5jhlRm۶UժU)P,++l߾][lQwޭ'N؎uEjٲծ];jՊۃB (4m6mڴFyNNNVvvh@iӦ7JvLLt*UPwD3gΘ]l޼2\.5j:]vj߾ڷo5jP?@kmۦkjӦM7&~f͚QLLvOlaj1N2֭QY /r\ljpk׮ 6oUbbu%۱Ǫ\uYݺu=B rU~z]VקYYYc!+<<\m۶U޽u}[nPB A,==$&&jժUZvc;[ Svt}k׮ٳ*WL E aV^UV믿֎;o;"rjӦw>}{*S 1[lѪUj*} 8 v޽ջwou]%J`@P@9t萹^KBX2eԹsg[\@PeW^5_|VX+WСC#P7V>}wފ`EjѣfŊZdV\2n]w:t6mJ?P盔-YDK.͛/ojԨX `QG^j+-]TK,ӧmG"*W={j4hP]t,]T ._|=XΝ;kĈ5jjԨA/P@1?,_\ .Ԋ+k;Uvm5رcfѢEZp֯_|ۑPnڷojܸq뮻(PHj( ꣏>ҦMlq\رz!1B5\@P6.^h>#[8@HV||~a\mPG\b>S-\PlG+ntx=Zʕ\P@Rvv/pB}ǺzHP"""Իw.]r Q|_w+W؎AB z衇4n8u]nr $Q'Nw}W:d;:uhܸqJHH03!B $dee%KW_՗_~beo=n8EEEQ8mڴɼ[zwt%q $+WNV||z%EHjsimf;f͚iњ8qիG(j`1_~^}U}'͵ =aaaѣM2jA-55|;wve;4iG}T&MR*U(@PڴiyW;ڵkTR4hԻwo5C4{ァڲe8/jѢLI&L2kAB ޽;Ctq>T\9?^?7oN(CΝ>F@4c 1(JVVK/iΝ]wݥУ>(5AN>mϟ_~Y/^ʗ/~XO>ׯO`U6m2sՂ w4@nӧs:8(.??|7ڎbs~i 2Dnr (&'',X@gqҸqc=2e"""(B 222믿?:q8^N'xB*TX) 59_͛7O/_Bʕ+GyD3fPZ(|B >l̙o̴J*#GgQӦM)B kvi~_k…x<pCxxbݢE 5P(;w_|Q?(v[?S @Qb ( mǎ楗^HҀ4{luЁb P( l}]ێ\/ֳfRLL @P֭[s=ŋ \. :TfR֭)nB =j~_gi7 n=Co&MP99s(;;v)Q&L{NjբX 5.]d^z%͛7O0J.\3fPŊ)$QHzy /(55vVٲeӟTկTlY5(@5of͚ӧOێ@ШZ~_'PR(@P!c>>l;AqzCrQCBƍSO=ovSN]R;vǛΝ;S7[n5bb&Ԁå^xAsUVV88^ɒ%5uT͞=[*T`b 8p=:w8J*駟s,Yb 8pE3fh7o^zIja(Ԁ۷L>](Gzye˖k!8 pWYf֭[SP_}ڷoӧ4Z01Ƽۚ1cΜ9c;(5k^P\\R)))׺ulGEtw_VN(@b7d._lOnn4A.))I]tQ||p. 01y]tveUTo~=rL @֭[͔)Sh; :hر#p,صk̙3MLL eyfuEӧO7LƄPK,1?=j; vښ;wz!@bB SNx3h 4!ɓ>|bccǙB |ꫯ-Z߶KEzX%@زe2e6nh; p۷uw  5`QffyꩧLǎ)@RRRtj̙&++`jL4I{ƍ^ϴ 5gf̙[niP,TϞ=5e 3&Ԁ}fҤIڿ(a6loիjOP~fOn~4ÇO>2eIOOgjjVXat1Q@U^y2| 5#W\1'N4<eթS4x`=ʕ+LaB u?ڎB\zѣj˘P^efΜi~P@@8vz)Sk׮1M 5%۷o7qqqںu(7~[:t`Z xj<yMǎ) ڵK;w֬Yaj>lyYvBܹz-u]L"bB [oeڴiCAiÆ j׮Ν˄ ("&@!?L0A}(^1h R j(@!^ڌ?^NW7|STĒo̬YL޽)Ξ=|PӧO7L݀`B cرcn:QSNzԨQ#m0n?6ڵLqFo^ ,`L3}tC˶]ZZƌxsU5p,~d׮]fھ}(EzԶm[ÄW^yPgҥ1 5.~ﶣW^QTTj< 5B޾}ÙJPG)::RƒoO>tԉ2 P{QΝ3CHP#$噙3gCʕ+t=ZSL1999k$|#8qŒ9Rׯ:v쨅 A,GHaBW_;R(99Yw}VX!Bof͚e飳gڎ8.\P2X|?Eَ w}W˗g 8B Gۿ2dve; @Hiڴ>hтR b7kSNi ۧΝ;O?eǢPq1_4Tjj8!+--MC̙3W Gb7%##L0A~(#GXǠP1v1c7T%(հB +]fz!}綣 _ .Tҥ); 5.55 8P֭Э[7-YD˗Tï(gϚx@[lҪU+XBjբTo(G>}h5j+WQFj͂_޽tڕ2 9t萺v;v05_Ps6m2ݻw׉'lGÝ>}ZݻwWbb">GO}7gϞ:(.]R޽j*J5|B Ytyf; BLFF ˗S3jҥKÕe; BTff EQjx… ͰaÔm; B\NNF>R Pë}3vXڎHrss5j(۔jx^{+//v<&L7|R P+^u4RR P^}U|Q2g??SQlj+bNJ@0h3gBF4=u'|Rӟx3"sQP3ӧO(^zI/]s PQh}P[8y3g0$ 5$]rO{hP~teJ5(ԡڵk&66V)))Aa۶m0`^Jqkov _^CUvv6:QCT~~?~/_n; V\'*??R(!>1CO?B?9s؎8G͙3)u>!7cǎU~~(cn-X@#F!BBӯ_?eggێ8Nɒ%l2ՋR"(!bΝ[n|(c/_^k֬Q6m(!BN¡dpB3j(g r;hرR0jIJJ2]f; իչsgJPȑ#s:{(~FJLLTz(jHOO7 L̙3߿\T!(xرc}vQΝ;5fy<JP'ҥKmP˗//K1rv `Æ 9s |>5v f…>|8K:۷}JKK@)SԼysJu`wz6leMeddhȑvSA?OsN1۷kɶc_(W^1o?kL{-۱cԩ233mG$"""6mڰ" E׮]3;vݻmGdZl7tҔjKXmѴi(dΝzꩧliL-͈#l,XQF1BmMvt%Q *(%%E 4TK,//ό=2 +RSS<~Fg}V}dڵzm9,[vvrJٓ~BϛmӶpڵkk˖-R X3a4:y&Ol;FȠPgَ |'Rd?`ɷ:tȴkN鶣QQQڲe4iobBCGLWGaCjoov !hݺu`;GRRRLΝc; UdImܸQm۶eP} ;;}ھ}(B\tt6mڤJfΜIvڥg}v GbBe~o; HnVZ=z0" ]rŴiFǎ~N:ڶm*VH|{s4t =Ӷc8 j/Yj۷x>*˥˗_~LBiiiuLk*[,X/)ѣGc8bZzիK ˥/B{fJ] bviӦviܸlGP%t!թS'~ń_L@_W1 $'׮]3uեKlGAJt1EEE:v;y7)*ۭիN:Rrss,;wNǏi.]һᆱQn+'={؎ ִiSuM?OԵkWիWOᷜ|]phOaۗhرC.+`]/_no;PÆ #V|߾}wѫgھ4 dXB}P<`VXa;Dddz!M0AnW߈w3gl_.x>-[F.ݻw-[*P phҤI3f*T7߇6SN_|aGs\ڵk7o:`o?2 [TM-[(99cQ%aÆ˗k֬Y @3_rBnj׮tQ@nzꥉ'jС*U6[o'؎8Ru )S, '.e7Ԯ][3f_F 2-I7xCnw@ޕ+W{َqS9СIII*UJ R\\|*׿6>#kN)))p3wM64iƏJ*[1Ƙkɒ%N:kBKyWlG+WN ouV״i\T%r^uUVv+ԄŋN: D(!!AƍSTTTP[yɓm'22R'N+5~7(WvmWҁJHHp9LK҄ Ժuk1[oe;̄c7o}َ/ S=Ccŵ`3f1i޼v%W>` ʕ+M߾}mEGG8`Zj<iҤ9b; 8_}z'H1B+WԎ;OBLKRXXGpێpC@LO>mի5k֨{~V k8V.]ǫtbX)~벬,h"_(Sծ][3fvZWBB28PpBegg}ٷ'˖-TT) k~zqf͚7KNNv%$$(Aɓ#!ߝ_>}ԭ[C~D8p&Mx@aaah2׮] :uΘۄߧL|Of /] pQѣiPs7#Fhʕڽ{f̘^:%ڡmGB?}$]@4i$1B<vm޼v |w:riР_k2 PԨQCӦM֭[vZׄ \„?c>C|-Jc9 nz쩄 2D%J@jժێΝ;k>y>riԨu8 wu&N~X5k֤DCG5 4I.KǎS:u|z%~!e8RDDčݫ3g(Ӹnƍ#!?_>ESLL̙'O>pr(G'Ou2AB 9r{1k׎;޽Yf@r:qOtBS@rݻ|M:uJwQQCyxb~ j{`TN͘1Cʕ+]H4 lΝȰy.\0֭ixo)U 8_aaahaպpႩRO^}V?chkҤI. 5dM0'gKY U啐dܹ5c eD/ONr助Zrss}VϞ=5qD :ThĵkLY RJ*[_}?LPvm?^'OVƍ)d4@rJ 6'zҥ>Rndɒ۷5tPS7,g}?/O hB?&NURaQFFRm˗[{R@ppٳ'8+VBj*kOlwuƎ &~h8aʕ^\^C}AӤI 0UBBz%EcrʱGUzs+|{9sɓ\{vQt[lLA[+)*Tm޼Yɮӧ*UDF`4\aիW~>]?`,..N#FPdd$!j*]].III|I:u4n8M2E 6DbB  .h˖-СC>O 5˽pRJiРASF%55߿v rbbP\zt }Xe7w_ŚP 6~/_^FR\\vJyOiڵz*Ϻb꯿ZPLn[]tQ||ƏҥKSP);;[k׮U~9Uu.`Gݺu#h„ 0'|kʕ _m@!,YR}U||p4PDG5gΜ@}7E.ԗ/_6;v}Zh~X&LPj(рp, ʕ+|EXB*??(Wxݛ x˽x~z=EE.k֬}bbbcǪL2iGPo͚5/]kf͚1b&OVZQxvYUV 4޼yʕ++>>^{D-[P~(scǪL2i™B@jNII}=K54rHM-[xUBBz%E5III'B1F۶mS׮]oqw,Hd3fʖ-K=)))/+jĈOmR=j@Ppٳ4x`,Y" o8=·=,33Ӕ+WNyyy֭cS)ȑ#aÆc%K*--MJNmF$. M999ڹs:tpˏc_:vI&i̘1*_<%@@`7nZBk.WB9RSNU)j tݩ߶Pܹv~]?`,..NÇWҥ)1 bj&o]Ə)SaÆhoz,)rNKK3NJ*A)..NWXXE@@h;zMٲeo喅zΝ-hkĉZ*%@PP={m0ʗ/QF)..N]vDzjv*|޽{ 7NQQQia8S^PVZɓոqcJ4INNeƷ,mJ,}*>>^CUxx8EcT uZZ9qѼys=#0aUF(}Usmm7-Իwoqʕ^zrQ 5Iמ={o햅z\.vI&i0 d9s?~v b׮]/G5jȑ#裏u֔h!oÆ # S DGJHHА!CTD 4KRRȭ:2BLӦM5fM8QգDM$&&ڎ ܪ#nvXŊMjj/Pll8` c*U$J*:Ope"&&FqqqSJ(P{L .˦bŊ?x?o*V#F?ڶmKBvYnر~B vճgO%$$h*Y$EB f8@'[ƎSAh 5٭ AQT) 4̶ml TP@`V||}QU\ >1ue 5 *hȑ2e:t@c7[cNKK3.\BV^4i$ 2DJH$%%َ @9sFW^5QQQ7ޛP=zvFYk㕐FQD(c;-ZǏ!cqqq߿(`ɥK ǏߺP;vv> 8qVJqFcl~ e˖Ր!C^zrQ QĵktESreBro;VeʔDCPUreIj5jhȑzGպukJ48L^^III@8vڵk'{ĉs@W5i$ߟvءk׮َ H|}P:uv.iӦ3f&NzQ lذvA7F>{\`EDDbccc4~wPY111S\\*UDh; {և*V#FSv(͞={lDP)n[={T\\FH4@,c; rB}y۹ԩqiԩjР%o4Bjlb+U cP(Ledd2eʸ%{~ъפITJJ4@(ٳ*S?'H UPA#GԔ)SԡCJ4PN>mN8a; tY5nXLۭ.](>>^ǏWҥ)"ٰa4@P]Ə5jԈ ($%Vɒ%շo_kС H&11vA[f$mڴѤI4n8U\ |i&1:\._l;W\9=Zqqqڵ+%S{ѕ+WlRSS%@QBBƍ(4/]Pf͚ף>&MP~GPWy_GJHH1uj_4kL&L#<իSeee۷ێ QLddK." 7oVNN؍%ߙ&;;vpcǪL2h@@b7v횲M8iQF 9R&MR6m(G W\Q8(,ۭ={*!!AC Q%(A bB .;V'NTz(s%s!18˗)n/""B0pDcl r=ܣ'j*W%III#pTgdd @TXQ#Fc=vQh;z¯]f;0#F(22" pd8ĵk(@SƍSAh~_Zji,ظq%߀(QBSLѯ~+ըQ" EjDŽ:o:t@|l;x34ET\96lEŁd|v ˾UK5oޜ2 @4˗Wnn(vvmFյrJ4n͔i>&wxx@PU֬Yh4ZXXj õpB5mڔ2 @@2.wXX@{uRHX|,@ٳV\)M \pTVMQ8XJPsR"7nL9|w0djՊ2 @a7o&Nh;($ 5 cB Jddzi;(cYf{D< .؎ :u؎ 4a5pi.wDD@@:{(%""BH9m6y<nb @5)))cKtҶs)--M֭֭[i;A`#@2In׎;X @`4*]4{x<4irss)8 5bB ƍC @={؎ DEEQx_v p 7nT~~BNO>cR @@2!wDD@ӟ^zٳjHbbBLҥ厊*k֬QNxbJ5|Et1 Ƅ *TkݦE^zi̘1UJ\3$B JOO@ٻw\O65kִp0uY6l.5ŴcӺuk1sɕe5ъu}rQ(_<裶cArcilBVկ_? 8Pԛ7o6111qaaaܹFzHu^Iҕ+WLJo; em%}P?p0@#F(66V111kaV2}@x*UBm1QQQ `5lP߯p 67g@RFFƍ7P\.Wڵm[:|͛>}F7 .4dl[`2ŋz5rHUVMW_5gϞ\CQB 33SK.Ք)STvmuռf߾}kpcǎSNَ vBM<֭[3gYfjٲ9sYv17ڎje׮]zխ[75h@SL1K,1999k"jB d;vL 5jhȑ歷2iiikpj{]_cӺuk"""ԵkW 8P#FPZ|SBێ ٳG͚5^:--͔/_vFvK.h;qkj(k׮iҥzUzu 0x<5xٵkΝ;m}rrrl2_vpM6)//v `7Ғ%KlGE 5j(ŋێC( J*+ AgϞ=ڷo(A*UTB׏}fJ EÔܹsȑ#c-F c5x((֭Ӆ X ^d;H*n 5xmG`4@Q umŲo(>cINN$I7u121##Ô+WN7o+S.\RJ 4۷4kv VZZ vw2e\uֵRFFk1 @hРM˴tB-z 3}@Puc 5'|"þ(2 5@Ѳe[7 5ɓ'e1 (^C &`ŋmGeeggێX[l)jG Erorҷ,ʕsծ]v~Z7o։'G D(4h2erof71)5@S'mif;P@\r߿v TB̈́g`7PRRmI,ڵZVV 1 hlذv}-ԭ[V%l_5}@%%%َ%KERJnwD8Ζ,Y"òo( 5@ѪU+,Yvi /jc@;z9} `]B ~o3n P @,^vxjkrnmϞ=ڷo6(V6mqwrʹ5jdz 1[xj;w*##v TL?Yn.\oD 5ǣe˖َ ˾撒lGڶm[sSՇ7ǎ}]ʔ) .TR>ڵk|˳԰aC:t@ 4Ν;۾.zmL0 } \o!$H })g|LA@`4@R uPgee+''@ۼyڷo>jgJҕ+W |M'6mؾ>pŋێܹsiC:n P%KoL2۾np B @ԩJ.]bT%}7@(IMM5$R [N.\`7޺|Z }G˖-Exx""˻Zje1X TP۫lٲE?-PKo1VXl@p$@Խ{bb޽{۾~pt^v #Ggڎ>}PCJcӱ@Pn݊9U\E6}2} (E׮]YR1 T9wImٲv  5@.Kxb'<ټy I۷o?G uǎUR%88uU1@UTQ6my]\=z|cl޼Y'N`5a7@ѷo_bퟖP%}7cRp$ 5@Bݯ_?O8 Q^zyuw&M|N1J*ϫlٲ^իB ˳@Ν;+jeM/lINNL^+<'e$))v-^+Խ{VDD'hҥoh;(**J޼K uTT/x֯_o;x@ ӧ"""vF $ 08˾83gmwVX\H ޲h"ؘN.K>W?W uz\[Nvݻ}bbbTvmNZ%i{F @PآB>j. 5`f1ɶcO.c0??ԪUKgϞ3gΨJ*R ٳǴhv !f͚:y\^>jzy<-[v ($ 2-P_ }VIII#:ח|KRZZZrrr|@H([Ο?RJ@PԩTTRpʔ)rʹzgBDzzV^m;JvvٶmB\~|R%jIz衇|@b7`l18_vSC*<VD .:ʕ+~,8'O*%%v (0 5zJ* jem/ B 6_wR}ݙ3gLڵӋPѡCmڴK.*U*l өSTZPרQեK_~ )))):~8N7RXħeZqX doAlGy>|\.V'P 5n/Pn]Wǎ}~!*V^tQ:đ#GLΝΟ)%))v!sΪU'>/˾_؎/طo֭տM>R>lΝ;g;BͲo.nvew'NHygϞҊ5n8ǛWgro6{_ a 'b (==؟w߭۷S(l)] ׯB]\9׀zdl˗/7C Qff>ݻթS'͝;RD`ː!CTLޯٯZoN.\XKC*++;++KO<nRSSG0w@zT/>?aÆ);;ۧ_磏>RaJ5Brr<BPJԷo_]%K bw`Y`7nrss9_s55JLL@9rJ,ޒB-Iƍe eggS믿nƍ<~l=6l._IJJ@JըQ#+ NիWێ^}U|k>mVk׮T &lhذvjk[).gXm_W3uTeǏG5kϧXoN>mN8a;4i$\./$VG177j׮Ǐ[a~ߛ~vٳyլY '|b8'n9rDuֵʄZ4hm}yp'O*%%v/lJڵӊ+T4cLK $M0GYx!e֬Yf̙cѹsj&77b Pv[233MZjI'С6m^?xgo~1 {т ԠAOUSR%J*ɓ uddkȑ6#cL }c~eysǎtROU{LcZ-ӒB-Sc8ۇ1fڴi3g(rE 4HӧO7999kx`C tI녺sή-Z؎@ c&M_~v0h޼y{uJ5-))v!UVС-L $=###^Z$/xC=C rss͸qpBQ|nԩ)22JVV)_rrrlG"J,'NjժfĄFX1}wQFDW^yE;vԎ;M;JJJ e_ 6, ʴ Z{1.]]nwb eeeQtȑ#gϞ:|(x4k,K'O{-pirviӦ}ڎA-##CW# ݻtڕ2#|ڵk>R"(aj޼y@N.?aٷ{nӳgO}ŋ4=ZڵkkP*UhܸqcQҥK&Ol;PGvZӫW/]tvHzթS'رRj6uTEFFtZ =םttSbEy<Q8T%t!թS'^Zj׮6l>3|?7<e:@eff*!!A&###F7DSF 2-IttN_l23tPeffڎ;xձcGmݺRX q ,h u.]\;wիGC*++v޽{եK͟?ߑߓNd;ԩSNKAT%闿b 1>0cƌQNN((LM:UÆ 3/_XD8،3lG(*C Qflg[fرZ-Z;*))RN:eNs c~_?^ Mnn.Oɓ'J*A9tu6M6U^^(T\.N8Zj0csڎswkjԨQ~:QLLټyDڿׯA; Fi;c 1n񘄄4n*))Iw}> ,}v18̘1cLKA^%g~sss͸qkَv% 6LӦM3kKIIQnn$,,Lկl(o-Z`J E|rZ6#Go; 1FեK߿?fÆ #pcǪiӦA=P%R@!edd믿ㆫWO>A&%%E111zw)>d;qtZrHnѢkĈc@OmG$]tSVA*==]Ǐף>j]F2NMGVf͂~:-)߷k.ӺukێAr:q"""Pۿ,{yeeerVVyꩧLll\b)effj3fIKK ˽x3ڵˌ9Ҹn#J2s1>>w#??TŘ5"#  2/^t:_=iC!YvA'tt>˗/ƍ˶ Preu;{ƍm*W|~-uZ*VzmAŋqoLFtqQݻ믿vk̄?rBxQffx >ܤ:j2h"4"q`;vԩkԨQc GSNJIIqL7oԸqԱcǐ\ܒoI:riѢlGATRz5m4xg}fh; ={^zAXT!7 Mf;pl=:t.] cfΜi; SOdBtB-I殻ٳgmGQn]{ԩSmU\rA-!9e˺}Y1?~\^|Ecdjq)3؎ H׿2-Z!ڵk;wڎW^zUf̀}oUVَ EGGk֭!u $~َ/TLLꫀ^⋔iE?)˴o/_n;p ˥\TDyh"3rHَ  4H~iBw^ӦM؎'?ϟ͛[l23tP(رC7zf[H/Yfӧێl͚5jժ̾}M4,YbNPdO=e_PKzzi޼N:e; p0?^3g{No?EU^=޽[KP e˖uKcxo*::Z{6}Yݳg۷~P0c޽[Q@]4l0uQ.oXϞ=kf͚^{[Ϟ=_RB#[n5111x<Un] 2DzRN u/k׮O?TVX^i^QD e˖P~3cHԩ[SjTfM)SFՅ {nmٲE{a x OL&._l'P1$rIDAT5kێVUVM{U (?¡d7QbEׯk1_|2} Lo!??{JLLzD'PMLLrssmG WrrڶmK|F֭]ӧO駟L;viժ>l; E cEEEQo .]-_~2]x]Ç|nر0`eX]@gΜ1-ZPjj(˗׮]TV- u0.5jy1wpVeUjo-X@bmBLWDGG[gb&MRLL 1]M]***^ziƍ2ڰaN'A]MjW_ٱ:q)Dhho߮&MՈ+լYfԩSMEf̘ALNkȀUV 2DK.%kA]C;fo1=jԨ TNp廆Ԯ]ۚ;wN)sSNLOb'yĴʷ iٲ!cY̙CL 0zLB&N.+.իrssMO`X.]qF.vQEEEvtaSR~}+""vA\vQ5-Z$iz ΝKL0څJMM5=&MRBB1¸l۶ %K 4Ho,^j7p)K.OMOPbbbi&ժUvqꫯN:7=@5 G}v͚5.\Ct:5o>^۷o7=oUNNi@P{]w8`z ѰaCmٲEĴ3$""ZrLO3Zb1ajӱcG+##CkW`Y^{5]{Ĵ6=p=z[obڋЉ'^z)??kN|ˆi/GPCta{ڳg)jٲrrrT~}b5OQQBS5i҄$^iԨ;~.nݺziyZli_^k6=p ZnZnML<5~]v֚5kTV-SzjuЁ/U]t,Y"S#b U5~S߾}+V:~~~ZxnFbӧpBөs*11" d-X@>>>꧘:t(1?DP$%%Yϗ4=CNLԸ`CfϞ-6,envb2E;Yfɲ},;v,E!qƌcM> Y3f{%qjToM:j-˲4uTM0ԢR,۶Mo9s=~xIJ, ziTAKf7NȲ,⋺i\Ubȑ#uYSt:ѣGӸd5̢Ed9sN5rHbUFZx}ө vm4 A*e'%%@~~~ZpLLJԨk׮ RSxb 0F#Qmz-{Ȑ!*))1=^(((H˗/׍7HLZԨVv?/իW+..F!QnjW2=^~ZnbcciT+5>~S7nwyGZ"Q;\uUVnn*SZn\b5FiܸۛӱcGegg+22F!Qիgeggn0=￯uӨQ5j\ppf 8~iݺu !Qjo-^X#F0=n*99Y˗/W`` 1 #jk͛7Oͤ_1 cxm\Œ3zH9NM>]w! j˗۷vJJJLO WffJL%p)~#G+V(>> rcW4mToZnMLåP26mX7oVMOam۶Unn.1 DP%5lV>}LO!{Vnn"""i$.+88ZrMO@ =z֮]b.K233iӦ+,KJOO|}}i4JtR{ԨQ*..6= @>|8! @Pí؉***2=UaÆZr:uDLmp;ﯭ[*իWq4 Jۉ6nܨKԷo_pK5Rppl2=裦RRRœᶸ 7{l{*//7=GӧO i5߷ #GQn]-^X=z jx"{РAOLOմiSbPc4jȑ#MO[GLã(VFFꫯt:5yd-\ "Q ~z{:v)^)<<\-RBB! DPã}駟Ubbb|r]y4<Wњ7onm޼YIIIx+''#񂃃ŋ駟4=c9N=ZbBBBix<|ë|uAS/ BP8p6lLOZh b^+:|G}TeYRRR{JPëZ5j?nz [ ќ9sDHkz;vbccdhтW7^˖--[6=%''+//DPk֬YVZZMp9AAAJOOWffDL7 {챇۷h"mۖ~jibz q㏉iWpB +VcƌѣGMOQ5kJHnM999ԈΝ;kj֬1 | 22ڰaRSSt:M6NS% 5p6nh~***2=JEFFj޼y iqB \ݻ[8p)UfȐ!*(( DPN:˭ŋ+,,J ѫKZ4p \B{ԨQڸq)k׮T i8.A&M 6hڴi7=k&K 5PEve'''k۶mh͛7O4P8Htte^\eYJIIQ~~>1 T!Njyf{ȑڷo)5iD޽;! T1NjХK+??_wu,?@ͳ,KcǎΝ;ipB T{̘1MO^iӦJKKS޽ iqB Tx@>~Sib~P5(//3f>3Si޼ԳgOB!5[nֶm裏$pP%Ǝ۷@ 0?Nٳঢk 0s֭[cF|}}M4`'Ԁ رc=n8m޼vW_}UW_}5!  5bbbP^~Oo5=~Xƍ?! x)4=3:t9p=#JIIQ`` ! x99N_~e=3:q9Zji„ zFHDPGj*--5=ctw'P i!oƞ4i̙gϚ@q82d&O+jh׮]?-[wX<-ܢTiӆjl׮])S`NpoSO=XB!\ݻwۓ'O&n匿O<'.A k)|InݚP)5K_SLQzzΜ9czPjUf޽Ih"R|}}5|pUViUP Sj*..6=5tPW˖- iUPm9bKz饗ߛ"!!!;㏫A4jAPvN̙_ڿ9֠As=z衇JHV5S^^n/\PSLݻMx-Zh„ 7n i5P***UVצML8=JLLeY4EP0j֭iӴh"p8p|Auڕ` A % %\g}f⋚;wN8az*99Y&LUW]EDpi5QZZj/^XӧO֭[MT;jر1b inYfi:}9JPbbƎ޽{A o^zI;w4=pZj;SwuԩCHp[5өuffJKKM cn!c<A ;v^dfΜm۶^-**J#GѣU^="G!x]vs՜9sts+jذaJNNV\\ cBYYj*effjݺu:sIQzd%%%)((j^y4{l۷pk1b5k֌Uj^˶m;''GҥKuQӓ-ԩSGIIIx-$={ްa233|r:u$p)ݻFϏj%%%vVV233~zF8NS4h.2"~qQ;++Ks{'~ :vdz뭪_>  _K,Ѳe˴ep8ܹ[nE7&P EEEڵkzj^-9uErADE"}5kdMO_t:չsgr-:t6lHD%  =z^r|M*++3= PBB+<<*BP@5)..{=eeei͚5:pIDݺuէO%&&ꦛnRHH Հk.;++KW֦Mx*DO]v `Cu)++K֭ɓ'MOfխ[7OC QFha5VRRboذA֭;C{EiF ӧz쩀" ";/#|װaCubj/:t޶m~o>"^pEj߾XիWx\'Ok.8O׶;v쨎;*::Z:tPxx8 C5~GO?۵{nڵK{Qaa!p)eI&RttԮ];EGG0@*WVVf۷\dϽ{}l#jժ0v SݺuU~}5hkժE(A ct ;vL:uJJJTZZ'O̙3:vΜ9'OT%%%:uoڏWPPy?_@.j׮-]ve P``y.44`]|}}c~^$e{%tEXtdate:create2017-06-12T03:31:02+08:00܋%tEXtdate:modify2016-04-16T15:27:24+08:00 TtEXtsvg:base-urifile:///home/db/svg_info/svg/37/5c/375c71349b295fbe2dcdca9206f20a06.svg|/IENDB`PKԴHH tg/utils.pyimport logging import os logger = logging.getLogger(__name__) def notify(msg, subtitle='New message', title='Telegram'): msg = '-message {!r}'.format(msg) subtitle = '-subtitle {!r}'.format(subtitle) title = '-title {!r}'.format(title) sound = '-sound default' icon_path = os.path.join(os.path.dirname(__file__), 'tg.png') icon = f'-appIcon {icon_path}' cmd = '/usr/local/bin/terminal-notifier' logger.debug('####: %s', f'{cmd} {icon} {sound} {title} {subtitle} {msg}') os.system( f'{cmd} {icon} {sound} {title} {subtitle} {msg}' ) PK3TN@tg/controllers/__init__.pyimport logging import os import threading from utils import notify logger = logging.getLogger(__name__) class Controller: """ # MVC # Model is data from telegram # Controller handles keyboad events # View is terminal vindow """ def __init__(self, model, view): self.model = model self.view = view self.lock = threading.Lock() def run(self): try: self.handle_chats() except Exception as e: logger.exception('Error happened in main loop') def handle_msgs(self): # set width to 0.25, move window to left # refresh everything self.view.chats.resize(0.2) self.view.msgs.resize(0.2) self.refresh_chats() while True: key = self.view.get_key(self.view.chats.h, self.view.chats.w) logger.info('Pressed key: %s', key) if key == 'q': return 'QUIT' elif key == ']': if self.model.next_chat(): self.refresh_chats() elif key == '[': if self.model.prev_chat(): self.refresh_chats() elif key == 'J': if self.model.jump_next_msg(): self.refresh_msgs() elif key == 'K': if self.model.jump_prev_msg(): self.refresh_msgs() elif key in ('j', '^B'): if self.model.next_msg(): self.refresh_msgs() elif key in ('k', '^C'): if self.model.prev_msg(): self.refresh_msgs() elif key == 'G': if self.model.jump_bottom(): self.refresh_msgs() elif key == '/': # search pass elif key == 'gg': # move to the top pass elif key == 'e': # edit msg pass elif key == 'r': # reply to this msg # print to status line pass elif key == 'I': # open vim or emacs to write long messages pass elif key == 'i': # write new message msg = self.view.get_input() if msg: chat_id = self.model.get_current_chat_id() self.model.msgs.tg.send_message( chat_id=chat_id, text=msg, ) self.view.draw_status(f'Sent: {msg}') elif key in ('h', '^D'): return 'BACK' def handle_chats(self): # set width to 0.5, move window to center? # refresh everything self.view.chats.resize(0.5) self.view.msgs.resize(0.5) self.refresh_chats() while True: key = self.view.get_key(self.view.chats.h, self.view.chats.w) logger.info('Pressed key: %s', key) if key == 'q': return elif key in ('l', '^E'): rc = self.handle_msgs() if rc == 'QUIT': return self.view.chats.resize(0.5) self.view.msgs.resize(0.5) self.refresh_chats() elif key in ('j', '^B'): is_changed = self.model.next_chat() if is_changed: self.refresh_chats() elif key in ('k', '^C'): is_changed = self.model.prev_chat() if is_changed: self.refresh_chats() def refresh_chats(self): self.view.draw_chats( self.model.current_chat, self.model.get_chats(limit=self.view.chats.h) ) self.refresh_msgs() self.view.draw_status() def refresh_msgs(self): self.view.msgs.users = self.model.users msgs = self.model.get_current_msgs(limit=self.view.msgs.h) self.view.draw_msgs(self.model.get_current_msg(), msgs) def update_handler(self, update): logger.debug('===============Received: %s', update) _type = update['@type'] if _type == 'updateNewMessage': logger.debug('Updating... new message') # with self.lock: chat_id = update['message']['chat_id'] self.model.msgs.msgs[chat_id].append(update['message']) # msgs = self.model.get_current_msgs() self.refresh_msgs() if not update['disable_notification']: try: notify(update['message']['content']['text']['text']) except Exception: logger.exception('Error happened on notify: %s', update) # message_content = update['message']['content'].get('text', {}) # we need this because of different message types: photos, files, etc. # message_text = message_content.get('text', '').lower() # if message_text == 'ping': # chat_id = update['message']['chat_id'] # # print(f'Ping has been received from {chat_id}') # self.tg.send_message( # chat_id=chat_id, # text='pong', # ) PKTN" 2++tg/models/__init__.pyimport logging from collections import defaultdict logger = logging.getLogger(__name__) class Model: def __init__(self, tg): self.chats = ChatModel(tg) self.msgs = MsgModel(tg) self.users = UserModel(tg) self.current_chat = 0 def get_me(self): return self.users.get_me() def get_user(self, user_id): return self.users.get_user(user_id) def get_current_chat_id(self): return self.chats.chat_ids[self.current_chat] def get_current_msg(self): return self.msgs.current_msgs[self.get_current_chat_id()] def jump_bottom(self): chat_id = self.chats.chat_ids[self.current_chat] return self.msgs.jump_bottom(chat_id) def next_chat(self): if self.current_chat < len(self.chats.chats): self.current_chat += 1 return True return False def prev_chat(self): if self.current_chat > 0: self.current_chat -= 1 return True return False def next_msg(self): chat_id = self.chats.chat_ids[self.current_chat] return self.msgs.next_msg(chat_id) def prev_msg(self): chat_id = self.chats.chat_ids[self.current_chat] return self.msgs.prev_msg(chat_id) def jump_next_msg(self): chat_id = self.chats.chat_ids[self.current_chat] return self.msgs.jump_next_msg(chat_id) def jump_prev_msg(self): chat_id = self.chats.chat_ids[self.current_chat] return self.msgs.jump_prev_msg(chat_id) def get_chats(self, offset=0, limit=10): return self.chats.get_chats(offset=offset, limit=limit) def get_current_msgs(self, offset=0, limit=10): chat_id = self.chats.chat_ids[self.current_chat] return self.msgs.get_msgs( chat_id, offset=offset, limit=limit ) def send_msg(self, chat_id, msg): result = self.users.tg.send_message( chat_id=chat_id, text=msg, ) result.wait() if result.error: logger.info(f'send message error: {result.error_info}') else: logger.info(f'message has been sent: {result.update}') class ChatModel: def __init__(self, tg): self.tg = tg self.chats = [] # Dict[int, list] self.chat_ids = [] def get_chats(self, offset=0, limit=10): if offset + limit < len(self.chats): # return data from cache return self.chats[offset:limit] self.get_chat_ids( offset=len(self.chats), limit=len(self.chats) + limit ) for i in range(3): for chat_id in self.chat_ids: chat = self.get_chat(chat_id) self.chats.append(chat) logger.debug( '#### %s: %s, %s', chat_id, chat, i) if len(self.chats) >= offset + limit: break return self.chats[offset:limit] def get_chat_ids(self, offset=0, limit=10): for i in range(3): if len(self.chats): result = self.tg.get_chats( offset_chat_id=self.chats[-1]['id'], limit=limit ) else: result = self.tg.get_chats( offset_order=2 ** 63 - 1, offset_chat_id=offset, limit=limit ) result.wait() if result.error: logger.error(f'get chat ids error: {result.error_info}') return {} for chat_id in result.update['chat_ids']: self.chat_ids.append(chat_id) if len(self.chat_ids) >= offset + limit: break return self.chat_ids[offset:limit] def get_chat(self, chat_id): result = self.tg.get_chat(chat_id) result.wait() if result.error: logger.error(f'get chat error: {result.error_info}') return {} return result.update class MsgModel: def __init__(self, tg): self.tg = tg self.msgs = defaultdict(list) # Dict[int, list] self.current_msgs = defaultdict(int) def next_msg(self, chat_id): if self.current_msgs[chat_id] > 0: self.current_msgs[chat_id] -= 1 return True return False def jump_bottom(self, chat_id): if self.current_msgs[chat_id] == 0: return False self.current_msgs[chat_id] = 0 return True def jump_next_msg(self, chat_id): if self.current_msgs[chat_id] - 10 > 0: self.current_msgs[chat_id] -= 10 else: self.current_msgs[chat_id] = 0 return True def jump_prev_msg(self, chat_id): if self.current_msgs[chat_id] + 10 < len(self.msgs[chat_id]): self.current_msgs[chat_id] += 10 return True return False def prev_msg(self, chat_id): if self.current_msgs[chat_id] < len(self.msgs[chat_id]): self.current_msgs[chat_id] += 1 return True return False def get_msgs(self, chat_id, offset=0, limit=10): if offset + limit < len(self.msgs[chat_id]): return sorted(self.msgs[chat_id], key=lambda d: d['id'])[::-1][offset:limit] for i in range(3): if len(self.msgs[chat_id]): result = self.tg.get_chat_history( chat_id, from_message_id=self.msgs[chat_id][-1]['id'], limit=len(self.msgs[chat_id]) + limit ) else: result = self.tg.get_chat_history( chat_id, offset=len(self.msgs[chat_id]), limit=len(self.msgs[chat_id]) + limit ) result.wait() for msg in result.update['messages']: self.msgs[chat_id].append(msg) if len(self.msgs[chat_id]) >= offset + limit: break return sorted(self.msgs[chat_id], key=lambda d: d['id'])[::-1][offset:limit] class UserModel: def __init__(self, tg): self.tg = tg self.me = None self.users = {} def get_me(self): if self.me: return self.me result = self.tg.get_me() result.wait() if result.error: logger.error(f'get chat ids error: {result.error_info}') return {} self.me = result.update return self.me def get_user(self, user_id): if user_id in self.users: return self.users[user_id] result = self.tg.call_method('getUser', {'user_id': user_id}) result.wait() if result.error: logger.error(f'get chat ids error: {result.error_info}') return {} self.users[user_id] = result.update return result.update PK5TN|K((tg/views/__init__.pyimport curses import logging import math import re from datetime import datetime logger = logging.getLogger(__name__) class View: def __init__(self, stdscr): curses.start_color() curses.noecho() curses.cbreak() stdscr.keypad(True) curses.curs_set(0) curses.start_color() curses.use_default_colors() # default curses.init_pair(1, -1, -1) curses.init_pair(2, curses.COLOR_CYAN, -1) curses.init_pair(3, curses.COLOR_BLUE, -1) curses.init_pair(4, curses.COLOR_MAGENTA, -1) curses.init_pair(5, curses.COLOR_YELLOW, -1) curses.init_pair(6, curses.COLOR_BLACK, -1) # selection curses.init_pair(7, -1, curses.COLOR_BLACK) curses.init_pair(8, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(9, curses.COLOR_BLUE, curses.COLOR_BLACK) curses.init_pair(10, curses.COLOR_MAGENTA, curses.COLOR_BLACK) self.stdscr = stdscr self.chats = ChatView(stdscr) self.msgs = MsgView(stdscr) self.status = StatusView(stdscr) self.max_read = 2048 def draw_chats(self, current, chats): self.chats.draw(current, chats) def draw_status(self, msg=None): self.status.draw(msg) def draw_msgs(self, current, msgs): self.msgs.draw(current, msgs) def get_key(self, y, x): # return self.stdscr.getkey() ch = self.stdscr.getch(y, x) logger.info('raw ch without unctrl: %s', ch) try: return curses.unctrl(ch).decode() except UnicodeDecodeError: logger.warning('cant uncrtl: %s', ch) return 'UNKNOWN' def get_key_input(self, y, x): ch = self.msgs.win.getch(y, x) logger.info('raw ch without unctrl in msgs: %s', ch) return ch # return curses.unctrl(ch).decode() def get_input(self): return self.status.get_input() class StatusView: def __init__(self, stdscr): self.h = 1 self.w = curses.COLS self.y = curses.LINES - 1 self.x = 0 self.win = stdscr.subwin(self.h, self.w, self.y, self.x) def resize(self): self.w = curses.COLS self.y = curses.LINES - 1 self.win.resize(self.h, self.w) self.win.wmove(self.y, self.x) def draw(self, msg): # msg = '-' * (self.w - 1) # msg = '>' if not msg: msg = 'Status' self.win.addstr(0, 0, msg[:self.w]) self.win.refresh() def get_input(self): curses.curs_set(1) self.win.clear() buff = '' while True: key = self.win.get_wch(0, min(len(buff), self.w-1)) key = ord(key) logger.info('Pressed in send msg: "%s"', key) # try: logger.info('Trying to chr: %s', chr(key)) # except ValueError: # logger.exception() if key == 10: # return logger.info('Sending msg: %s', buff) break elif key == 127: # del if buff: buff = buff[:-1] elif key == 7: # ^G cancel logger.info('Not Sending msg: %s', buff) buff = None break elif chr(key).isprintable(): buff += chr(key) if len(buff) >= self.w: start = len(buff) - self.w buff_wrapped = buff[start+1:] else: buff_wrapped = (buff + ' ' * (self.w - len(buff) - 1)) self.win.addstr(0, 0, buff_wrapped) self.win.move(0, min(len(buff), self.w-1)) curses.curs_set(0) return buff class ChatView: def __init__(self, stdscr, p=0.5): self.h = 0 self.w = 0 self.win = stdscr.subwin(self.h, self.w, 0, 0) # self.win.scrollok(True) # self.win.idlok(True) def resize(self, p=0.25): self.h = curses.LINES - 1 self.w = int((curses.COLS - 1) * p) self.win.resize(self.h, self.w) def draw(self, current, chats): self.win.clear() # self.win.vline(0, self.w-1, curses.ACS_VLINE, self.h) for i, chat in enumerate(chats): # msg = f' {get_date(chat)} {chat["title"]} [{chat["unread_count"]}]: {get_last_msg(chat)}' date, title, unread, last_msg = get_date( chat), chat["title"], chat["unread_count"], get_last_msg(chat) # msg = emoji_pattern.sub(r'', msg)[:self.w-1] # last_msg = emoji_pattern.sub(r'', msg)[:self.w-2] + ' ' last_msg = emoji_pattern.sub(r'', last_msg) # msg = msg[:self.w-1] # if len(msg) < self.w: # msg += ' ' * (self.w - len(msg) - 1) if i == current: colors = [8, 9, 7, 10] else: colors = [2, 3, 1, 4] offset = 0 j = 0 # for color, e in zip(colors, msg.split(' ', maxsplit=3)): for color, e in zip(colors, [' ' + date, title]): attr = curses.color_pair(color) if offset > self.w: break j += 1 if j < 4: e = e + ' ' self.win.addstr(i, offset, e[:self.w-offset-1], attr) offset += len(e) if offset >= self.w: continue attr = curses.color_pair(colors[-2]) msg = last_msg[:self.w-offset-1] # msg = msg[:self.w-1] if len(msg) < self.w: msg += ' ' * (self.w - offset - len(msg) - 1) self.win.addstr(i, offset, msg, attr) if unread: attr = curses.color_pair(colors[-1]) unread = ' ' + str(unread) + ' ' self.win.addstr(i, self.w - len(unread) - 1, unread, attr) # if i == current: # # attr = curses.A_REVERSE | curses.color_pair(1) # attr = curses.A_REVERSE # self.win.addstr(i, 0, msg, attr) # continue # self.win.addstr(i, 0, msg) self.win.refresh() class MsgView: def __init__(self, stdscr, p=0.5): self.stdscr = stdscr # self.h = curses.LINES - 1 # self.w = curses.COLS - int((curses.COLS - 1) * p) # self.x = curses.COLS - self.w self.h = 0 self.w = 0 # self.x = curses.COLS - (curses.COLS - int((curses.COLS - 1) * p)) self.x = 0 # self.win = stdscr.subwin(self.h, self.w, 0, self.x) self.win = None self.lines = 0 def resize(self, p=0.5): self.h = curses.LINES - 1 self.w = curses.COLS - int((curses.COLS - 1) * p) self.x = curses.COLS - self.w # if self.win is None: self.win = self.stdscr.subwin(self.h, self.w, 0, self.x) # self.win.scrollok(True) # self.win.idlok(True) # else: # self.win.resize(self.h, self.w) # self.win.mvwin(0, self.x) def draw(self, current, msgs): # logger.info('Dwaring msgs') self.win.clear() count = self.h for i, msg in enumerate(msgs): # s = self._parse_msg(msg) dt, user_id, msg = self._parse_msg(msg) user_id = self._get_user_by_id(user_id) msg = msg.replace('\n', ' ') s = ' '.join([' ' + dt, user_id, msg]) # s = s.replace('\n', ' ') # if len(s) < self.w: # s += ' ' * (self.w - len(s) - 1) offset = math.ceil((len(s) - 1) / self.w) count -= offset if count <= 0: # logger.warning('Reched end of lines') break if i == current: colors = [7, 8, 9, 7] else: colors = [1, 2, 3, 1] offset = 0 j = 0 for color, e in zip(colors, s.split(' ', maxsplit=3)): attr = curses.color_pair(color) j += 1 if j < 4: e = e + ' ' # logger.info('####: %s', (e, offset, count)) self.win.addstr(count, offset, e, attr) offset += len(e) self.win.refresh() def _get_user_by_id(self, user_id): if user_id == 0: return '' user = self.users.get_user(user_id) if user["first_name"] and user["last_name"]: return f'{user["first_name"]} {user["last_name"]}'[:20] if user["first_name"]: return f'{user["first_name"]}'[:20] if user.get('username'): return '@' + user['username'] return 'Unknown?' def _parse_msg(self, msg): dt = datetime.fromtimestamp( msg['date']).strftime("%H:%M:%S") _type = msg['@type'] if _type == 'message': return dt, msg['sender_user_id'], parse_content(msg['content']) logger.debug('Unknown message type: %s', msg) return dt, msg['sender_user_id'], 'unknown msg type: ' + str(msg['content']) def get_last_msg(chat): content = chat['last_message']['content'] _type = content['@type'] if _type == 'messageText': return content['text']['text'] elif _type == 'messageVoiceNote': return '[voice msg]' else: logger.error(chat) return f'[unknown type {_type}]' def get_date(chat): dt = datetime.fromtimestamp(chat['last_message']['date']) if datetime.today().date() == dt.date(): return dt.strftime("%H:%M") return dt.strftime("%d/%b/%y") def parse_content(content): _type = content['@type'] if _type == 'messageText': return content['text']['text'] elif _type == 'messageVoiceNote': return '[voice msg]' else: logger.debug('Unknown content: %s', content) return f'[unknown content type {_type}]' emoji_pattern = re.compile( "[" "\U0001F600-\U0001F64F" # emoticons "\U0001F300-\U0001F5FF" # symbols & pictographs "\U0001F680-\U0001F6FF" # transport & map symbols "\U0001F1E0-\U0001F1FF" # flags (iOS) "\U00002702-\U000027B0" "\U000024C2-\U0001F251" "]+", flags=re.UNICODE ) PK!Hv !#tg-0.0.1.dist-info/entry_points.txtN+I/N.,()ʲM̳\\PK!HPOtg-0.0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!H9Rtg-0.0.1.dist-info/METADATAN0D Kz@%T$[gEl{-߳J9s[͎z0x-/sZ'uQ-XΙ8kZqX6DSum^uՓɣ!I'6ܲS6GQU ZJKn /x #$եXC"Nđ*xO}b5pP`qmJR~ i_?'9\c".rX*"g E݈SCPK!Hз tg-0.0.1.dist-info/RECORDmIs@{~ fi، DMlE׏*S9x}{x"U7S%fb"[ԫ{]O0of@b/QPz#{# I+23+k_n ؓ`'km,?(7AUafJ)Wsכ&OJ!Y^侉AFƭ^LbċBc}\ G]y*iIumE[IKuFhK=WحE,Dϸ @Ao,_j5tNf{oWA]qӜruu|1B 2|j#/NB ?>5F/Ϗ@t}+89$`V*C>cu-dXon]fxhڍJشc]|qdc *0/JԴHH Ltg/utils.pyPK3TN@tg/controllers/__init__.pyPKTN" 2++tg/models/__init__.pyPK5TN|K(( tg/views/__init__.pyPK!Hv !#tg-0.0.1.dist-info/entry_points.txtPK!HPOAtg-0.0.1.dist-info/WHEELPK!H9Rtg-0.0.1.dist-info/METADATAPK!Hз  tg-0.0.1.dist-info/RECORDPK B