From b9e273561c7b51d46883814dbd4ce51080341e21 Mon Sep 17 00:00:00 2001 From: rking32 Date: Tue, 5 May 2020 12:55:35 +0530 Subject: [PATCH] update webss(add chrome driver) and memes(reacts, hmm, lol, ...) + carbon + autopic(DB supported) --- .gitignore | 3 + README.md | 3 +- app.json | 11 +- config.env.sample | 6 +- resources/font.ttf | Bin 0 -> 51424 bytes userge/config.py | 18 ++- userge/core/client.py | 59 +++++++--- userge/plugins/fun/autopic.py | 115 +++++++++++++++++++ userge/plugins/fun/carbon.py | 48 ++++++++ userge/plugins/fun/kang.py | 13 ++- userge/plugins/fun/memes.py | 150 ++++++++++++------------- userge/plugins/{utils => fun}/quote.py | 21 +++- userge/plugins/misc/upload.py | 99 +++++++++------- userge/plugins/tools/timeout.py | 37 +++++- userge/plugins/tools/updater.py | 2 +- userge/plugins/utils/webss.py | 93 ++++++++------- userge/plugins/utils/welcome.py | 9 +- userge/utils/exceptions.py | 8 +- userge/utils/progress.py | 6 +- userge/versions.py | 2 +- 20 files changed, 480 insertions(+), 223 deletions(-) create mode 100644 resources/font.ttf create mode 100644 userge/plugins/fun/autopic.py create mode 100644 userge/plugins/fun/carbon.py rename userge/plugins/{utils => fun}/quote.py (61%) diff --git a/.gitignore b/.gitignore index 5c6490e0c..2a3874b40 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,6 @@ gen unknown_errors.txt logs/ bin/ +resources/base_profile_pic.jpg +resources/mdfy_profile_pic.jpg +pictest.py diff --git a/README.md b/README.md index ae9732e8d..0da9a22e4 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,8 @@ async def testing(message: Message): * [@uaudIth](https://t.me/uaudIth) * [@K_E_N_W_A_Y](https://t.me/K_E_N_W_A_Y) * [@nawwasl](https://t.me/nawwasl) -* [@THARUKA](https://t.me/TharukaN97) +* [@TharukaN97](https://t.me/TharukaN97) +* [@Supun97](https://t.me/Supun97) * [@gotstc](https://t.me/gotstc) ### Copyright & License 👮 diff --git a/app.json b/app.json index 1044cecdf..5e4a9e51f 100644 --- a/app.json +++ b/app.json @@ -43,11 +43,6 @@ "description": "Your Languge ( ex: if english => 'en' )", "required": false }, - "SCREENSHOT_API": { - "description": "get API key from 'https://screenshotlayer.com'", - "required": false - - }, "CURRENCY_API": { "description": "get API key from 'https://free.currencyconverterapi.com'", "required": false @@ -97,6 +92,12 @@ "buildpacks": [ { "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git" + }, { + "url": "https://github.com/opendoor-labs/heroku-buildpack-p7zip" + }, { + "url": "https://github.com/heroku/heroku-buildpack-google-chrome" + }, { + "url": "https://github.com/heroku/heroku-buildpack-chromedriver" }, { "url": "https://github.com/heroku/heroku-buildpack-apt.git" }, { diff --git a/config.env.sample b/config.env.sample index d2bd1faa8..2f5abea2b 100644 --- a/config.env.sample +++ b/config.env.sample @@ -44,18 +44,18 @@ DOWN_PATH = "downloads/" PREFERRED_LANGUAGE = "" -# get API key from 'https://screenshotlayer.com' -SCREENSHOT_API = "" - # get API Key from 'https://free.currencyconverterapi.com/' CURRENCY_API = "" + # add default city for weather WEATHER_DEFCITY = "" + # Weather API get it from 'https://openweathermap.org/' OPEN_WEATHER_MAP = "" + # GDrive Folder ID G_DRIVE_PARENT_ID = "" diff --git a/resources/font.ttf b/resources/font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..68d0b290782565a302a65dce57490a23b53367d0 GIT binary patch literal 51424 zcmd44cVHV;wm*LFj4io$N$ys$C0ALtEXlnW*>OuxNOfYzapELS;xtmIp#%sau%QK( zwsb=8q3p6imZdGrgJda>4$CgPEDQTsh~?ks-kFg`ae;l`_s1_Vo{=$v}okqw(ipZ>JtQbMk~g^=(&Mm7zMtkzt-7teoy`?YK&h(OLcSAX)^WwF)Y zk#G@k33=)Jo8#H}_v^MbjK4dcqg^0AhmT>nSNezN#NUo51H>?Xe^a}FLy~`FCwiED zgTHgo3j4>Nr2MXW_7VRCdLjLmgyLeKumGQDNN2i~l#(CV1EHD_b_zL@kbCP1=^*mI zhUUf=QcsLOhDd!7e?wTWuVp{e?}Vc`83FxU_8H%F;n3saYtlpSBtyagF$?K9Zo!dD zEaV$S0s#HLyE{5I6qEwLJp}T@8P#gaomgJTlxG3&X1E+`bV5` zO?Ke87S|hb49Vxq@cdgiVo9uEMmrCXMxh?(Kj3(VbPFk@LFmM_0XV%zLg`=8&&Tln zBRKZqK-+>vK9}KKLUQOq42+v}2tA}*MBjxAa1@eq!Ai1d9j+k5`)kroULF6DK1~|QXMph`e*2cxQ(V(8<@*+q8sMKz zPLe|OJ)7Q7;)LIjIC_$_(=Pz$1KigFIP4jmg-Bepaeok>PvCfm^a}0x9{8e9L^P5V zx(DBXL-N^p^UvM*d>M}W`8{^}KFJa8!L^UH3j*f#IO*YI$;R`PL$u>Lj4vNsHonSX zCv)g((gj?*=`OT?CJtq6+4!msdI-Ozqn~Vi)d$9xjRV?b{hjm>6YxD7UxurEV16y= zdpG$vjt|G*rI<55{%nj+J%BSG>r=iL7+lsi&J1VPzW;n7e)}uNp5e{LnVlb!&#KRi z=7jY)*x88RF*dAke2iFM|G}T}e`a*a+f>eOGB@Zm=1@3|)U&w}vgGG~i}O|-9XOVg zndCdrlbv*tgBX+OaUZ8gM&F<@&|Ej8bDArk;{hid*U#eIi}SrWV~*HZ@iS=A|M?<( z&pu<`*cd8jF%|p}htJ>>Ix3%6l4kxHyuf+q-N2dAvhp3{DH;j7X6IG%c?9=@26!7k z=lmhyDSY?=o@M-~J~Q4$oAg}pBRiUjoy>x40x#g`pcp^Gq(5jxSW9LJt8hLBT&~0! zV#i!gx!PCrpOKfF{^^0ge{XZX?-AP2iIl={MnCe0L5GHvd_qmfyo<3gb}` zM!ezrT-z$(H{iwd7uD zZiQCB3d46}IDUzP-8+uo*!f2MX2SV;93Mj-o{7)%aj^Dx;vOAI;%%{e(WbB$NQ5F^*>vaWp~RZ^n5Ij(q%nopfL<*u6w3 zl77<;Lykq`yU#K2Cke&4MbYoZf8f`VBwPsPV?n7%dc-e5WA!*N58?_EFWv+gm>2X+ zl#g_-=Lj9fH_(D4orF-P|9c^2Ltzc<0SZXNs8 zhl@X4^x=XJ=YH7vVe^MoAH4a&vJV#j_ioL(nscPlKrjFA{%M2=1`LDC##jB1!eWsA zDQESs>TBsq5q4oH(ZEUxBjF^1M3N{HO~6l>r+AV;5=jztx}F$FGD#t+Bn`8fK{81e z#@qx;!2)fVL#)I`a!DS^Ck3Psc7vT169*}QCF3Mz#6{f1L&`}7sU%gTn$&Ocz( z(6mjYnY55r(nh?{(;Z|6>4d!OCOx2*nUD>$VL{C$^I+pGAPdQ9WD!|RmXM`n8Cg#H zfX+^`3-oj*xqw_kE+P{$k}Y&6F1L}>$;D(0dXIg-jc%u-bPMQ{kcVj|J%gS}chfy|FFlX{zLoBP zR^CC*BHPK?u({49d&zm^9I}sGgx@bDmqC9VARkj3Eh6h_9?ho(WEAvW2;D&*qvLz; z3%P^=%9a0XL~jp~4`@0aq36+?=+pFF`n3=N9>^EUh2_Fl;jr*q;eAmM%fwdkO!0{L zviMDiCL}eaFr+`^<&e)plS3Or`$Kny9tb@e`ex`yq2Fr~G-ge;W|ro3%?+9lv@zOd z?Huh^?FHIfw9jfk(Vh&82}=*Nht-F5hxLbD6ZTTr*Wsz*jo~Z84~9Pw{&M)Y5it>| z5#15%BMwF!jrbyxM3zQ&MD|B+jXW56XXG1^UqmHE8KXL*)@t-AxBxEKuCTvQ$DdA6v35l(V7bG4_{3ywoG%M-cr2CS-(536@b^CO$>-GA1 z`djti7^)2y7+y{eNv=*FNj{SNQu1fXz7%uHX({_tev|S|sv~tc^+4)Nso$n$rZuK* zO1m@d_4K%ONBVI3HR+#ZBxEelxF+NEOiiXcb2#(*%r~<_vK(3Kvkqk)%lgciWGpuh z8?Q3HW&GY$Xj);q-t@lNY(C9=$o!@y*-~lgw_ISk&vM-2%TCC)W_M(7&pw!aefF{J zzvZOncym_gT#$21&as>?tb#S!T4wFGj#_WBzHIf`GHqVlu;}E!bZ0Si!#vQwy65mlmE`c&PA^!mo?WMIA+Jif$-+ z-5zEyw{N%KXaAr$uDG*!wD_9hqs8w#h{Nbu?Kt9i*71hpZzZH8v!t@*w30JR?k;(= zrAM5Cv((w?+~eHuyvg~5^9|>hWyxjMvih=FWy57>m)%|VX4z-1C|AC# z+O^zumFpqbC+-Nh+r7vATlcq~I8V3dJkM{-h4R|+(emrdpRN!pVk+zv>njdd9I5!M zGNRI2>8;#T`B>%am0wnwt2(N7RUN5%y_!^)RWGf+q56sHH>>|vlUmbSv#I9Xn!9UB zZ9;8!ZE5Xr?Y`PqYd@;f*HzaotvihWj@SLQesTT2`p4>Dt^cSYrlG81Uc;FUhZ-Je z)Hkkfe7|Wy(|t{EfpeJVBJ^AMP9h{>B79v_yh)5V#ph^kTARn_F?bAG`mHaDep~7r zq1$X1T$p?L<+&GLV7p8>I<{3|Jn~6H~{z}F^})3 zV`2%~$%UjRp%$0bYV+vzhIqT+Dsy{WPMzM6q_x^~_)eds(ZS;y z8ev9Av%6+j?YXfy=OR_cKUt{uU%bz{!3xbu442l6n*+m*eiUB-bG^c(x8`!(?$&fA8~yW zxW|mfOUWspXO{?to`u`z%-KGtIlnnSzuEUO|Ik#xK9mR#ImYVo+nnv0)pmQeo$gV6 z;A3q;yWy}=3!vjj4sh03%r+|<1-Hj)vtkO_BxhZWdI?z&l5=KLd|IjBl z{U&ZsvUB}u#d*ahSG_4YE#9&;vcQy=P^ixltr^w`eS3S{!uH*@DT#W|>77O8i4Ak= zJl@*HsrhMPX6r(8W3&)mqlrshW=If1_J-_^Yfbcx&%gTK^pd1Z>k97*SP6Eai|pXE zrJ}60#W4vHpeLbgokT}K3HNr7?;|HLzcGZM=T=udurX+K@k#moZf?m-cRrt;3)H0T{R##Qjw69AZjA)M-Oy5Xf$R8Xmn3q}a*toHzF$2$<$#J>@ zwsa)pG8gC@#OKsmKJ)I{h5wJA(R%K=t*ni(@w=guJ7L`rgU#)+S>0}Dsa}^c+pLAH!~rgk?o2hRDpvxl}L4msu@l zHrODHk9Xd^d*ci1ylqYUI(K!_AAHXYEL%1}8wb~n_y(w}#e0eGclAw8YzzVQcNjz1 zg-%PnMys`zx;@U{wWm5_6LgJ9?ZR?>l-QV)%Ekis(w&e^NlebU65TGw99qm6q)n1m zG|`>DYyM@ao#BpbNSt?pqpa{_n$Scym*~65bolO2aQv2Di*^wU^I)=_ zrHs)ni58bhr*-PV{1&rS=Spm}*7VM;U;peY)Ofi#d+Y86?xnK}vvOu{>k(;&x9Q!G z9rx{WDt-F^@E|o!mUvU5Q*_4TKdpsc<@G)P>ls%(=k+GKFfjwZ4{4U~!&ZjRxYP%D zH0T5Gg2j?(ViK-Y=|OQvuAIe2~tO2*#z8zHZ*LW7**-k^-!1ZMY>NI^?KQuio74s2+zYJ zPls(y+`K`XM#Gqi6AJi|i5Nz|TB$Qh`Xy+3W%ZM{`+TdnKgTKcFVH)iz*nh3w=I@J$+=>tR20x89og5 z2GUP&0-0iW~Q`+Bw zjc(tCRXs6tyW$$!iwX*hYP%YA?l~2X$9JdIn>4n$b+n327P}!_B6H5XdnAs6?%exF&Kw| zJM3g!Kp|Pq^g3J1QQtmJM84pD9*GW%$eII$fG}BAx`|iN>LfG?u)Z_0?(m^V|}}p+cP>ufk>#<%&cIW zWIF;F>>I5f8kMO{ST+U-8z)4I+fDBYp~BAmPIra{cFux^lydL6-il(;D-^pejf+C^ z^j%JF?yT@VGjB;`=KHj@rnR}w_qekyB*9iuo*N(9jJc6;p}-|hp-_uOCnEwgJBM)( zIFsQg(2kyjmo|aH#hNof5y;7R@9N72- zyq?YN3kXkFnLPyJ6Vwkc2Ph>OGDEm#&Y2N9|Dr|Zb5_nNA6+-QPG5UYZt1^Ygv~owKIW{BPKBHvu$Y!Z6Mra3t2Q&}tIA%s!zVzNi z58f_pZ*B&?bd3L-{vC7+36TR5Oo|b>*ufS0MQ`)w)V21u)#Z3+oH64!^vAI=S9g9= zQhv8f5PhMw+`!;6LMTKV(1W0TMxk_g$*+5E_+>9h?O|btOv|DM_i$PkLCfqmM$5x} z%a^S<+`W5k_u3u3LiE@_@yxSAJv$U0Sc>N)z7j2o{6SGzy6DJ}h421;_AR%}d6^n~ ze@0MtiSH|#=)0BRA0_3mhnfEkEg#B!2WES?7=l2{!_IV1Qbu;k3%lCqoY5809_b7( zsT|E;w~C6se>b%h*l5kz2Uh_Oq=dlw&G^aW;>sHgs_~`J^L*#iH+`qmiyH)YOY_+8 zrFOf=_mjWFM#p#?KrcFNyy5RR-neif?DV>blYfUOK`s$?{Bg;S4}~31k9YZ=k_>s~ zE2*A$d6>xgJ08DrQ(c4@A5q6~>lR+4BjPzsW=Z}H9VuQuV8256mu{7@~I(PdQI^I_SCd62GaukFGBy~5g89y0nCnq$kT;-xL>8yt?jSv zNb8z&WZ>2%z7O7RTu`%bZeCUK!B=LUwy@{I*q$y|k-H4(5lMGPA(jB%2JibV2|iWO z;#an3rxz6^Bt%9>8cWwC&k}OV+GCPK>jhhd1459^y`5f<{v;qi&$vipBGw&)2OOk< zcxTdzdoVORq1k-Tiov_gVq-;Y(?CP@aKjtriCu#g%NPG*L7%H|?Duu8^qh-56*V;n zD{5L;|LXDQe#|#&Bc$1ey23P@x%RxE@Ql2<-js|TrBGZSW+d9y_->`0rRk;&-wHg# z=Kz0MOiat9gW?B>;JO=T{ZyqH{$XQG*8FvQzz zm^O`tIk#>FLQAp!5@CiXCOk7DrJF%A8%pU%p%6(GUFES6X~I8}lg-K8pMc#492m_? za!u0v2sG&FGYcQg^LigIyR@C=XZwDD$#LUdrMq^>^Fj{c8Sp0N#d!$b1VWM=eHy%G z#0Mw?u$MF*@V@=myaj{1l%6%W(>upouFJ?aXGMGU=_6&{W?FGReZ{f34I?#UUZO^b zj!n1tZl)c@={cFce)?SsAS>gJcKEn6j^+)?th%zIs>V;VGR#l}#Uw`rSlXPTi@7Xv|E~Au-L$H2NlU5F z>J?fSx6}>SzM=n9e&3>f^VT_XeUD$>;HhZ4)Kgi7A;XwKaw1l9lEo3&oarP}70~v2 zTZ>xEc?p&oRrI@5Q&@AP&4l~2$NxbuMcoKS95Dnaoie+C@Et?Yz^!>*Ql`j!wCvW9 z#(8z_?w-t?nTw3&QBeltYG-|KkLR>|R-7T`Xw4`qu$Yt-WT^g2AF((QEKwOu<-@1o!N*7M{( zyGJ;JdnV}i_<4($%sbq*W3X*-YZo2%T_HWAJl77`C-5BC?KXs!ZB7UlEq!b5#*z7l z56|1QdG7TOJ@oIBk3M>m&jFK1FF+naI{KxNi~-0`3Mp!6zM;u-@3OX$9XT#TzUz(h zr1s^-%a+g<-%~qwJYF>;+8tBjYpnGGH|8rmh4v-g&SOl}M4$3q2Kt`k8>fklbfl@l zcPSf(?(vJMS#%>33~LmQ8AP9z+Z9s7&{Gi6Nz|-&6zhySU9zLNRo7TqU0rS{TQp~5 zVo6eBVr|;c&Q+OiOW#8UmDTwV_j8<>zCWbI^tdcw5xt=H5MN|!is>pI{{&AMVg$6N z(e&#Zt1BCIt;LRH9hxm3EjRW*oL^m8@KB${ow;h~P+DzbVp2)s#yN|a3S@9;Ed2sG zMK-UJm&qJ+1nWSUbhA6lSlF6=T6$WACClEHwu8>gaa5#-*&G#S@D_X=RSp8?TasCk znLSbE(KEYw62ed{WQ~1b7Y-m~qKANBx^h1~AZ&nF!1M^B`&{qJaeb}T7SHBRYl~;B zNsHS~v!>2%X)7vC%CQz^$GZyKW^Gy${fT(Hh=v73#VCWo<=z(To2?V4!8uXu8z9b7#!DwkvwBh`qAs7ab$f zyLbPsH957F#`(Tx2fJqT&Bne;$b$_~h-^7AAcD%hjZi7z2dSvhNFE0>=NJt$k(9$6 z62XRCfa*xgPi>36|K5iV{m{x-~x+){w=yc*Ew;Qw{b;V_t%xt1`Nf_mc=$ zk@ry}C&l#S;FVsE&$a6_b-K)7us?d?_x#kZ%hKtX3lA-tGJY0(i*@{28_GO2Mr)iK z88F})2Z0~zgobC3nC!J!nRS+=N32HF{K@ybcmG7Ij*l%pGIr0sKOUjCcEt_1uH3w7 zMf>`w&akG!mgcr5d-KBM$7$_bZ~5N6>uxIEd*>_O`DZ_W{=62meZl#sIoKG}U33v> zkH>kbhB;7@NeyiXQ<-UUZeQ})7hTj+TVv0vPSV>emuobkqOh#7uFjU2uG6<>)AJ)W z>D5Iulfx|aj<(q`mh`qH9b5?kl@Pu3AM{1YVQ3&^5nUj=Q0YVy?N2`GxP%V!)2_lN z>64C&FO^OhABx(ZsqzjX{;`Wf^gf;0_&JD)`8{2m9MU4E45`r2Gf>%fu(P}WtV+A9 z#N0t2JasH>Pd`l`ZmL|O_awJu`ko9PLN;%ZMZ#KIfLbcpK@>zz3*LKASPRRO(=GiK zU5_#4^q^$fGzyQU@}%0@!Xj@`A^jE4q3IkAB_$0G4wrmF_alb`J(ptw2Qe<rXV0FN-Me>x(p=TBdv}AZ-|a#te4-PQPsIHpCM#CDN}|%V8bg@Sxj9t`Pl<%8 zqTDV%3Oxi}kfSxT3>|a#65}$Pqin@d*_!tDq}k?>obq-t z&6H{ki4Kj?d>t-4?c17^NedfkXOyoEJ`Wp{@e0)K{*C2QLM`z&Sb&Ip*hQWKbee>H zt<7yuG+7eTVj~1`@ltyBi{g5E@*q8$8k6D3&MYeJ-^0d_`F}Q=hbv~~vK$8UpUhgw zEf`rWiD7O}%Q3Ykg{SIs^fjf~G3Ac#MbB5I^9baU1)Rfa7nVvC%iZI_osJI3+6T$FSMR!$h zt~$`$ueiK{h%dcgaeftet0#r$ClNKgjS9J_;N7bbjsouhhX?(u?w^*a6g8;Nxks;p z^S0vKD{vY)98irKj-=9=jFVKlUJdg)TUKU~Goh$7CEuJ;>`Ewds^EWRDRw0qvhg+C z;}Cf)vw0f{%syAOl5mQ6R<~hSiE0(}kAj7^g2P6%qlRwfsT?pKBJv4xCQ63>e443Y zaYIe^{5draqtRgk@L$Hbx*Hm79Y)_JELcykgFZDv0&w{O#SjTS%HYEyU=<70AHB|T zX~l5ggY>rTy>vb8@%`|i;V=#HJ#@}zh}iL1_FqmilM{T@2tHzQc`$<V)=)~A<5@b zN8-gm1ScpczfYlU86)9Zg|L8}(I|$x(g|-!VS1W|Af_DJ$;8h0okCS!~ zs6;7RC5^Q38=0`BJj-D*8!%YKMGzFF)D}7D?C0=jW~exs*&b!ge8dW#=>&V zU>uD2FEk;nAlQ&B5rm`z;@F}^XR=yp&+8>6NBc%&>MFgyzp{MLp*=J<8q&Y@$kN_c z-#8LOk-qI!sq~{E7jq^!ZbGHl0RM^2mOtBIn<#|*`GGt^@a2VMkfugCmRz5f3X0K{Ia9*b<>^ah-$Er3j7SzN^dGuyJLv zEw|HEwR2u>Ud}`5mVGoS?t!$7+RjZ^l&xg>rgH<23!Z6vC09HrD%X!en? zCUh6P#q%FfuGs~ZLFTUjR2jW(uQmmPuj+OKZOU{ zIDj_b(N4rertKi`T29)N!al>VE2Qzs=9Aj3#YqUl0WeQa(vs4O%er z=+h(_CD9TXub|}8uNAQ{d6r3d+dpEGzIYLKOAL#xDv?I1=*U*a!iaEa5iqv5Ea@AZ zZEQ|#9B6D@)+QYNvS9e3q4czwP4#EhS93q&Tb_?bApn6@g?xvbWl%t=EEB-W7c3g2 za_}FlE!1nkH1{C2!r~^-1>x411Z%W5JU+X#z2m~RoV2ut*x0<}x_Co*cE)Ly=EBpH zq8+g=I?HZ#XJy4?#T!?R{Y^;eTrg*?skk~ODm%5QH5(RxbV+@Fo*}uoB(9k0HN@3k z6YhWn$VNRs@%(f)Nk!(}SLqDisS-=23e``OVWOV}h+pZ;ZU0eU{L?#aUrfquPuG`E z{8R29m)Ce-@_*L2NNh80H)e7!nx+pzl*CNRSnTEPyMI>u6II2ueGw)oj_LYk4N@KO zMI^fE=lv_h`j4MOIj6G!wEY`UDV6DZmaG;E%DCN%xJe$?rUZH=c`=Bl8CbpYBuP?1 zc5UQN00jTNIeRzTiDPrH#5eZkeO^MR;NzsF~8r#`t+^vcf=-) z@K{`si_l2CmdK_9iz9*zqznr<}iX)c&p|q5sA5% zl&NV#RyAFoiLs5a{G{jJ!d%GD`Nx~3jQvAgj^|S45gognJvaU?bf^zw8VXN`NTQcD z<>Mn|!TpVVV8%*tH?teL|Aw()u}kFMPsN`#cclvD8Gz8O!f@<$4o9*Do)ON&7{kIz zWOipzrdH-7Rj}uBRqpoj2{Ly=Hg|Q9pq1p=a~M~a*EPXt=E(qwa8#LHl}@HXN#3Q( z&`*->rMLK*LTTr=pKQlJLBZ|JSGZxac5d=BgVN4xydA{JrocxwL4#1S@Tr{){!4xy zP~h+3@N=hxKT%f(qhp&mQ4_P}^u3_Dl62y8pfNUAnan$x0=rZ7U~t=)tH^LNY~NCm zB}kjo_MZ@2&&Cyt(hybk$m5TY!^PV4vcnro>FdwD|{yD+`OzC(18!x?`H zH3Xfor+M$WZv_#o_*Ni2W|6`g= z<|Rq`(WX`IWhgL+oWH}h0@VhQ_w2VDeUE7CqG^ZbjQuvo|Hywc`i_<2nJ%Ug{TXmo zG>AT@B*TBHkhCf*Zu3(zkAZNQw@n38MZy7?viGEdnd7J00L<3_lSi>n>8BJ8@OLqn zld1|;0~J}QATJD}1~#5{)DGSzY0{}P@@l_mc2IJD zgTl#EX6c3Z{39dD!xup_llevo8V&c3)V`6ojWYL0=saC=j%aN4*~~wZ=#TZiAARTA zYwF%B$q8k&XfO)7d`y192W)CA-sjoMOn$FK|5C0ZlgHPg#Z=LC)FC+45%s4}oKOYQ zkMR7MBzs;HmU5dIvgc>`QVj02R&f4_6Vn*K^b@qK#O-@|zk{;^iL$ytW(PCja;b4R z*0{8>+{Arv>+l z(Rcx`!8S0N5z4Cat)4QEvTncv&lwhWq#)Gf8Cx=I=(0p<6~TYem6XEQzG(iO9r>lU z;>H>_hhxpNYzaF0GGAK2>gx*VG1Nchv9*2^XHW*t%~RGWPb^PcY$-RYm-;d{yL`rJ z<Cl$JSJb9a8P0KNmnTZxLc8X z2Ov*)^fias<&#usCI(uh<_XoBBT91<%Qoo;eo+ElAmPX2@lIt7np!0JyE(1Q8 zo2l`^ta%Kc$+4j!`>(JMET0q|I&{e;w!XdqK9>o9{K>gNOOs&pBX4CQGQuXQ3CL+s z*Eixpz6OWa^zg+HL91{?jz{oXoCps1)D=0xf5|;(^8Z_526F4s&|t9sSpDqe708L| z)v1$|BVS&Un<_6@oxC!cewL16@4z}%1*c4o6U@2ERu+j+Ho+K~Pn?*54ptyYH1#bV zL(Eghybg{D&r`8Vr>SbH`}@7MwG->A+gm$3TgTp-q_&#P!(lNKRx31bP=1p2RK1$1 z5*?LOlUK9;iL$S|l>55iU_V7p@Tr-Ko#5TRbK=CLPHi%POE%46u~4$6)%{ZJj-bBn z<4o%Bo}Vu{J;3iDmi_T*`j{ZsR88>w&*QKs@2b)et4}GQM=@8(`1!S=YF(I4R$BCO zSs(ho@h=mTwUew5Wh5Gy62drG0GHLi8?gs~(t8x%DB&xn|9E65I7q_>+flJAFCyL| zYrU@axj}{TnGvh=BDj{T_Rx0ksa5iUIXH+pn3%g}e6OaC1c@;v0ul4o{D|>?gOC4D z#a#e%!st`V0Z%&occ|nQUpI$*GIreKRwI0_DOS!wtU$0#zJ9Kh>y$}X(NU$)gwYi0 zv!S;_QMs9|Xi3PEY1aWf!$GG{Tn!*JaeZ}i>9nf?*nCJhb^}M$2EsC)2FFuu&dITz z-?X@iS)Y^QE0}{#KJdGQ_dZApGuw|s@vIfY7ORD)9f6DF<}{Ox!9I0kCvTZgFR$dE=IO6p4tyX4}PxhyQ4HzcH)0d#ymO+{8r}-L}0-o8M zZf;Q|%WFvyp*Cn}*F==p%c``YBzL;iFPK(Ip_Zrfc!;D2PJ*u`8Khdd3MOW3>|ej? z;QSR>#p*8{5lB6lCzIEj0H|csPFz<*S8xC()$$tIg7wXuu*!z_3pFH15$lmOJzG&L zX=hYcG7YW8l0u<~t*$L8Ve4xh4qUVD(GOY=;a_VL-%o<+U#8<>15N0YU)#SK=u~a3 zx4(bFx}SWe-$9wuc-<&gK{iSKVs$lABwrN|r8X4F=qJS2*z(?aLGd&ho-hjejhKsE z8LxuGVzzW*{X;xLw=)8e#vWGU%+v~7gU5@d>K(RI&i|k@#mA4c)-(OO-v%FE+7|$K z@K$UmPPDKXbo1CvJPu4zhfqM{Fm_3(LGXK^tda=(axnMvb6M9$Htv(WPDRC{CCTN) zkRgu!!*~IC0<~-x%(>;uZUI?HI<36mr{zU9XoSv-<3aq?7 z)!R?tE3sQ-w_dd(Tim1;v%G)ZoOL#l>EJ*)nD=&O0(UtU+DBihi8PRDjLhD zUi}5y>lv?=*LuTUQLpw6vb6(Cze55dDQFe=*rO^@1h|dkHS$_A?kUKt$l3Y=wn2q| ziy76@vPCM$>Q!dG#cIev7&>j79uu&0GrTq}t5>#ZVO<+^sAMtd>9H_Nbyj*`vDf?T zk|(#7)Yj!>TldV2nS-Th$X^lLu<7Djy4~Eq8bAf?iP5Vv;s|`g7T!-Tdjt0@~jEZgD@@dED|Gl`g3cu1?q&HR3H-0=B!TM%K)`7fqqgo zGX#1%PE<4K@9fEQ)H4Yk33|GpuYE*a)x`b=aj<$R(t0OdvV-qaR>9}?Tj49{HP#+xL5@f=4~Z3Y7%<*!={(zZ2-_B+MV_39hl<`UoA*33&bjy4R`(fp<*4K8bWabrDSxZL4g+Os#I$YF@z zvHz^I_E*%^Ug@nYsj9*%S)RK9&r`@$wqFRUTbUPQ39O?E-Z4ZKG@o+wkh7IJm}D1` ze*er%vSKFWZ#3I;MX48*Ry?Yj!3t_+k#e2_*uPp?nMetb@ftzd_eA!_roTk8`8mkvM~<9KiI*f` zr$mVAxU`)FneP;NE6vLYWOy{ok_Y2~f&<9!D+36ekfgV=dORpT&G(8ChYxl?s* ztTYw}Fcz#za{955O#JDFfo|uH@J|lHwQ@dP83Ps%WAzr255ape)!Ke~HE-gnX+}Zz zF~;tlZmB=?E~}qo`w1XZXgFoz5ej!%>U+}t1(d}~YXhtOO51vbsX8>XH|3O-fDK{Y z?y0)AZf1x@StlsX&2v9@ZomN3&do14OZ?Q_h-EUv$#WwLpij(=L>rTjh}nvnL-W&- zCT-!(Pe+)-RRLZn@CT(mmS9hV*ZD3>LDEXva;YjHsMzjHCtnDXQ3CdJs3mOAq9_(6 z@o3qSGI>V_EanUNf5+tQ9c%$`>YX0ORZyWAL8%8}i)v-RocB%gL!?g0o(TH$PYEN| zkAKo`JQKyN#h`vjqU)sGfkS+h)NPA)1%}ivt8c)%CuK}n6hvyZ9Z426xdL$zW}E(# zb^&jU9(KHi=EiLj<}AR3JpoK;u(g$KX|ZS#Z|-m_o%dq%jMl?#%%73g0yII=B3C_e zjlm=ZSV3z6>L&z#90}_4!#{=U zl&C2^D2Ge6@)kP5iB1(FQrMK!AK#x2YhBseGDt1dpG~r&h@7(KGkDKB-$_-O-sDS* z#E77hJ;(syBWT3y<=BsZM>|z4FL-l1-;W9@$lKfruPR2NBvZhhPr)vfVoBKjnJE}* zQc;_FL!H|dVDip7D)Y|Yw_L#Dd078$dvY8(xMOeeh_ceD%cZEYWOqL$4EbTm{gG=^ z_3F+XkaA^8SH9xeDkdkTF?tR#c&&ia4>{f!wD(^JSAlXb#DxKy0R9Gj3igNsLm3xt zd#HO6%^}F0P^=188&)gt5y@7TOmFK3`C-$UReuav3_oRB`QB2Q5g3zf_+`?5R`9Z? zx37Y_Gu`%9R|mQ?ovG#fLERPbCiX9+y%<}R4N6ux?d5j1uthf2C)@*jkM{C`7T!_j zh{3^zIj}o0%cp7>8YgEJgrldZCcq|m-_b1LzhK)CrJABr%5R**60C49VRs2OMn z51=bs87R_yzDlMGOM)ahx`Mqs>@*xY3Qzj8G)^hi^MNdCYNWi(R;jj-*HFNY66Vqy zg=ZM|Ow3?SlD!htCJpa%Ir$FxA-;Qt%8OI!iz5mH`!$46A}>B0 zUTv+6lQIIfKqzciYEn{a(gpnQJO#XP6~0scFZM$g+1tGYFW(m?c#n@rAplYE2KI7Z zuRv7Q&_?;;ad{y$0bT@KZ>hqWs)}09QDD$B)Vm&+%V5T~$h9nN-dUXCSChe0rE~-E zK2~-m76QNmsa+W!9R)nT_W*JTh}o*9gN`D%_7A0dEVFp7A2+U(g+ck903gb#VybG{ zKN$3a0AIfXddhGleOzgf`C#7(cW~T|!DBh8atn;YM(o5|`nn4Z3q^w@@=iLAzyq_W(f4Y&f>xLUu?NH3jwkXgPm$bk9?AlmKB{Gsf}v% z3{*5EMkYsRh3XSNL6T(+Zl# z#@y4WbMeT?V&5xe6*~GaWiX%{5tGH3w+gRKd&Grn3dw*PvVPy1Hu&g-sW={q{+6Cno`IBe$(6*~siWBFN6x?gYs zF3Yz0EgQi#g@d`qLO z03*>P-(OFC+s0>S;$0hOKD>UU9@P=(2H#r`b%*kpfy+;Tv*mrr**N@VlD)L`onN>;N{NL)p@IRB* zwtee+rja2p@dJC8iPTpf$)V+e&Ad4sRbza<$2}PD$$biX_RrY&n#uC_Q1|m9ykqX2 zxY$xDR`kT=tJ8`1FG*6>Vqv?mvrG;gt?eDzM@Ox>`J>rlgJXXG(Y(Xe`R=^UmT1R| zH2bQYipZL3-+LDcn$q%hi+mrwc#I~twpRNPW0HCIf3wdwG=R_nUBII-HmuH&R&?=~ zyp$pb#NH%@7f`VEWEMKmPP3OS@s4J94OrV`~1Z7s4K2}}8_RT&4AB?S=fOVe8_N2r- z=?y6K&}#T*rEZTqk!Rfa3piBX*?-TN5m%$tB$tI4QhP=!^XeS&x6Ey$!?6H&v$C z$WYqs?@xujWwyC(ZGDyA7JoyRnXnb|*qg~hd_|E_wHGp4InLpOqgD1JE9JUO0a(fE zxfE-WttpoBwJ*UoV6>3JGKN!G$k+>wtx+^`x<;J;*NhHy*JQ#J(D%DaBh%BgG|c4n zo$IaKRwZO`(&1)8W{hMs_$;)>_vj;!U{~MYG*+?wOgX)v27<4%`DtpInnZq@GB7%f zK9|vF`8x(OS#>vs>BBb5#R5;1An`SydXX;~bVL+;qoaCiAH9qE>|^BVQBQAIj%0^~~k zcqRQz5Li|KZ;2@Lc&2-fF)T{(r>8gy(r`S?1JT^Qz z-8WF-0xj@yX8UCS8Zs1ECrUKVsUK{h2l25X;CX7kyS+``nfjR3VJda#vJHm$Md_}` zmn~`dYkpecXv3;n`p@k0N|T{AGb*pjlIfYd`Zt4%_H_68UK)Wy)kmCnW@U9b!+dR4v?l(bWXf;=#Q`Y9q zHdYtS$!@#)SsIs8x!2)5x+Br|&(ycIDUP8zGcLcfvEF{o{L}cHah~NhI{q4y0GnQ} zVov(*Bupv7M)-lUI>$Ifa#$qqVFRx&pWf9=BU$qJSwewjCvXV&$VN z1gClFjAeaZt+w^5WK*@pQu1`~yr`%qXM3WC{@eG$!eP30;h}m{V`al^Vc6IIeO1xr zMOk?v+B)jZp>6e^_(Ut_p%K{Z3)DC6lj^SP1~#%q1;jBb2$^j!XhYM7$MP@oqmcMAk^thxBuy< z%KoR(mj7V?Q#9}n#!RwX`MX5wPTg<3d)!KQ3BTpp9+~27vX*1J{K_n5fd0c~N#HUX zRZ`bx(C6ee=Jz@+jnzfEj5M7Mxuud>@cev#O?@Xs)EGO;9&4EwW?b6VW{fUKiH@(1 zDtFyB6yW3k*ZuC=fvj+eSu}isJS%B&mj0q`#p9UG-WXbvBs$I znAr>2rt~_~pr>ZQYW#&MCeEE2VvNX)%S?{T$iLV%H(IN$Np|E7HsZxh%?V|^zhA+x z%0fg1wj&~IvXnyxfW7>SZ}zQ6r5KlY*g}&TY4kY=x}3}sx7Ab}Z_6$-)#Z$OdP{5F zBf3QDJ|BYaK}S<;N=T@vHD%VsWu=EZoVVB3?mF9656Q#v`v={Lci6+115dc594y}Q z(v~tZLW{OoU0I{Fu({cHyi=lKw#M*jc`kzVE8QyV(#L|;CdVsRSb}*-XPg&mh`J-{s33c35 z&VMKRcQBSczCWCgP1HNiza_UUCSK2sy2gI9DWo7foiXJ)v^UTar z?jG}3xo%X})o7US~;%QD~>eF2|z>mZz8GUV7Fsm+RPBm*$qFt~gL% zT~>Ma*_CBAuqQAcMdJ*7X%ZF1phg17fO4n1gv1Civ5)GLz=Bvi_6t-&hXofzSkzc zLPNGOUgGV*qfptm!3YFwTbXmFcM@KuZSiSm+q3eg(iP`=T zJ@g6wUR##=_P^U!;Zx--z3cWcfA6I9{%Zb)O7k#EUs&dX~)`TewSZjO}S(_BJaVlm`klkUt4g4|`LG&o)hY+_J>f2zHxpTWOJl}!vht)*CN<$s;z zTzuqhvUiHV2ip@-8)|WBEpXvnsB>j)LNC$x?73OFj&euc=QeA-K(^!G{{8#y!^6D4 zAjV$dImGS%lc>GW`_rNNpBtbD3QWG?54Ow%buuecjz70e8h;kF$fvQ~33vwM$lm!0 z^OEhM>4C$b`h@4 z?Ip=tT{U}W7vo6+Sig}feBy(93>@g`?b%NLbbEf$`Q5@QeVDG&!MoK`V27Kk%+qqf zz(BpKg3N$@{-rfuqf_Tt6BnkSkE?bn<`wP{erYj}Ef?0rMHrkVg~@?Y zVAu3-h5FiD5HXV%EQb{FAJ;`(p^j<###HM1=hj91T$+f35Ly7641=AM;Bo2`3~rBy zxhWda0_O`uVig0I(ii-%sNBmQN#q~c-;kQ_+&M`zlajKF+xmBOxmM4MuZzm6otM{} z-z#4xbtm=OwR^;Gx!z#PQH_!lIUJ`ilzH-;&YE$ye~2La6EV|o-+0F%!3@HcTCZz zQIn@8CFOln^rK*s`jzzdDF&O>6=GEh-vtGJt1I5jwjsx>e|X`Jj=!XX(Nq$>MPKis zH#SFw<3(0Cc{j#|E#zf?)J}bWY)!Q2r1x6A_#()Mz1j+}q5Tm9J<9YAI7o`4(s(&| zs*0Afd!rB&`U~a|adhlE#_#=Y_rm9TulZH)3vbfTeOu^7j26d#z|C)f7Q+xpfOX}E zJ7G_?2)UgglhYrerO&EDw@m)pj(D4yd#GXPrji>2K6Pt zEQ5ASj1dNYlTNpa}R9hI)V&1J+RBUyd3^sV< z!U$#b{ z@kPHw)t2fiPEWH+PE}O0FM4YK)bDT|bQM%q<)_VbcPoAuJ^qufE0h1@Ux^vOBbRtM z*TH**AW_8!{hl-P`Vh3jOI*Pu!5*~m=aiA9MM;*NppHfgp>g3cAymJ`cbno*OTKV= zT$pJ|aPK0;&^R%a`N(w3B>uH{FZinmJ!A~aO-9jw1(C<%PwZ>AKLAx5q5eXjkQr*w zi}x0U*h^y?Gt*-iCgc{C#-}%=#fL9c1*bBi^d(w>N2e|i)5lgNg(FGN!&dAUD#fh~ zOVXpl<9OuCCPY>hy}u&1()V0^W@L?L+1oKy8Q6j_5XD-MkR#&7Y*oJOG`;hs#$=rw z-D2NI6*{WAe6KD}ONf<s&qh4k2bdo6!_|g_v838ncj^0k5y&u{B73{DOC}A%v`9 zA!PNvkog)d{!5}-PgYE9h&r_FF@}dlC&$$pOY9|%jPS77tg)`whpHlDU;6`?U$Ws|fUm@5s4DM56&V^RFR4~!sH(=sFPxbq zNvHFeOy>Vq;;(c~0ehuQhzA*i*wPQGR<-;c=G8fc6sN=Vnp!xFmcuOWcDbz)TAoosQpl{08lq3n)$kzgi5{}5% zBAFv7_lFR(7$bilN>W9Ke61lyu|vMrl2LJud>uxz#rNgwaMg7rv4#lp_fe|rXle|} zmA{W6Wue}t4I?`?4GpdyHRi6&GnP6W<;I5f{hJ2*HXCR34UhD#9qJ$2Y|LFfFg(1W zerSFFz@~xCqeJTl?JGB|%PTZ)8ya10oHej{VAIxteq-x~^`pk_zI6jeb3^~u0etA) zv_W;*HPAn_Wt|K^0KU=~xZ5b*Q^~tB;p=$=n>G(^SP!~%IGob2XrV}k>u=IHXZ6r} zBl_KE9NpB{Kd`QE(^}((RmS%2=9Wp&vroi$+qP}?Q?Lch%@T&QH>?`n*0*VZwJbV7+v6r*uQ1QRzEmiJf)O|19t5*1nEmd1%mzINTt5!>Z7!Z4!1u#|S/ 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 literal 0 HcmV?d00001 diff --git a/userge/config.py b/userge/config.py index e00072330..29d280f9f 100644 --- a/userge/config.py +++ b/userge/config.py @@ -39,9 +39,7 @@ class Config: - """ - Configs to setup Userge. - """ + """Configs to setup Userge""" API_ID = int(os.environ.get("API_ID", 12345)) @@ -75,6 +73,10 @@ class Config: G_DRIVE_IS_TD = bool(os.environ.get("G_DRIVE_IS_TD", False)) + GOOGLE_CHROME_DRIVER = os.environ.get("GOOGLE_CHROME_DRIVER", None) + + GOOGLE_CHROME_BIN = os.environ.get("GOOGLE_CHROME_BIN", None) + LOG_CHANNEL_ID = int(os.environ.get("LOG_CHANNEL_ID", 0)) UPSTREAM_REPO = os.environ.get("UPSTREAM_REPO", "https://github.com/UsergeTeam/Userge") @@ -91,6 +93,8 @@ class Config: WELCOME_DELETE_TIMEOUT = 120 + AUTOPIC_TIMEOUT = 60 + ALLOWED_CHATS = Filters.chat([]) CMD_TRIGGER = os.environ.get("CMD_TRIGGER", '.') @@ -112,26 +116,19 @@ class Config: if Config.HEROKU_API_KEY: _LOG.info("Checking Heroku App...") - for heroku_app in heroku3.from_key(Config.HEROKU_API_KEY).apps(): if heroku_app and Config.HEROKU_APP_NAME and \ heroku_app.name == Config.HEROKU_APP_NAME: - _LOG.info("Heroku App : %s Found...", heroku_app.name) - Config.HEROKU_APP = heroku_app Config.HEROKU_GIT_URL = heroku_app.git_url.replace( "https://", "https://api:" + Config.HEROKU_API_KEY + "@") - if not os.path.isdir(os.path.join(os.getcwd(), '.git')): tmp_heroku_git_path = os.path.join(os.getcwd(), 'tmp_heroku_git') - _LOG.info("Cloning Heroku GIT...") - Repo.clone_from(Config.HEROKU_GIT_URL, tmp_heroku_git_path) shutil.move(os.path.join(tmp_heroku_git_path, '.git'), os.getcwd()) shutil.rmtree(tmp_heroku_git_path) - break if not os.path.exists('bin'): @@ -145,7 +142,6 @@ class Config: "bin/cmrudl"} _LOG.info("Checking BINs...") - for binary, path in _BINS.items(): if not os.path.exists(path): _LOG.debug("Downloading %s...", binary) diff --git a/userge/core/client.py b/userge/core/client.py index 136b5dd02..1b85ebc34 100644 --- a/userge/core/client.py +++ b/userge/core/client.py @@ -41,6 +41,7 @@ class Userge(RawClient): def __init__(self) -> None: self._help_dict: Dict[str, Dict[str, str]] = {} self._imported: List[ModuleType] = [] + self._tasks: List[Callable[[Any], Any]] = [] self._channel = self.getCLogger(__name__) _LOG.info(_LOG_STR, "Setting Userge Configs") super().__init__(Config.HU_STRING_SESSION, @@ -60,8 +61,7 @@ def getCLogger(self, name: str) -> CLogger: def conversation(self, chat_id: Union[str, int], - *, - timeout: Union[int, float] = 10, + *, timeout: Union[int, float] = 10, limit: int = 10) -> Conv: """\nThis returns new conversation object. @@ -87,8 +87,7 @@ async def send_read_acknowledge(self, chat_id: Union[int, str], message: Union[List[RawMessage], Optional[RawMessage]] = None, - *, - max_id: Optional[int] = None, + *, max_id: Optional[int] = None, clear_mentions: bool = False) -> bool: """\nMarks messages as read and optionally clears mentions. @@ -135,7 +134,7 @@ async def send_read_acknowledge(self, return await self.read_history(chat_id=chat_id, max_id=max_id) return False - async def get_user_dict(self, user_id: int) -> Dict[str, str]: + async def get_user_dict(self, user_id: Union[int, str]) -> Dict[str, str]: """This will return user `Dict` which contains `id`(chat id), `fname`(first name), `lname`(last name), `flname`(full name), `uname`(username) and `mention`. @@ -307,14 +306,15 @@ def on_cmd(self, filter_my_trigger = Filters.create(lambda _, query: \ query.text.startswith(trigger) if trigger else True) sudo_filter = Filters.create(lambda _, query: \ - query.from_user and query.from_user.id in Config.SUDO_USERS and \ - (query.text.startswith(Config.SUDO_TRIGGER) if trigger else True)) + (query.from_user + and query.from_user.id in Config.SUDO_USERS + and (query.text.startswith(Config.SUDO_TRIGGER) if trigger else True))) sudo_cmd_filter = Filters.create(lambda _, __: \ cname.lstrip(trigger) in Config.ALLOWED_COMMANDS) if filter_me: - filters_ = filters_ & ( - ((Filters.outgoing | Filters.me) & filter_my_trigger) | \ - (Filters.incoming & sudo_filter & sudo_cmd_filter)) + filters_ = (filters_ + & (((Filters.outgoing | Filters.me) & filter_my_trigger) + | (Filters.incoming & sudo_filter & sudo_cmd_filter))) return self._build_decorator(log=f"On {pattern}", filters=filters_, group=group, **kwargs) @@ -341,6 +341,11 @@ def on_left_member(self, filters=Filters.left_chat_member & leaving_chats, group=group) + def add_task(self, func: Callable[[Any], Any]) -> Callable[[Any], Any]: + """add tasks""" + self._tasks.append(func) + return func + def get_help(self, key: str = '', all_cmds: bool = False) -> Tuple[Union[str, List[str]], Union[bool, str]]: @@ -349,8 +354,10 @@ def get_help(self, """ if not key and not all_cmds: return sorted(list(self._help_dict)), True # names of all modules - if not key.startswith(Config.CMD_TRIGGER) and key in self._help_dict and \ - (len(self._help_dict[key]) > 1 or list(self._help_dict[key])[0].lstrip(Config.CMD_TRIGGER) != key): + if (not key.startswith(Config.CMD_TRIGGER) + and key in self._help_dict + and (len(self._help_dict[key]) > 1 + or list(self._help_dict[key])[0].lstrip(Config.CMD_TRIGGER) != key)): return sorted(list(self._help_dict[key])), False # all commands for that module dict_ = {x: y for _, i in self._help_dict.items() for x, y in i.items()} @@ -443,9 +450,9 @@ def _build_decorator(self, log: str, filters: Filters, group: int, - **kwargs: Union[str, bool, Dict[ - str, Union[str, List[str], Dict[ - str, str]]]]) -> Callable[[_PYROFUNC], _PYROFUNC]: + **kwargs: Union[str, bool, + Dict[str, Union[str, List[str], Dict[str, str]]]] + ) -> Callable[[_PYROFUNC], _PYROFUNC]: def _decorator(func: _PYROFUNC) -> _PYROFUNC: async def _template(_: RawClient, __: RawMessage) -> None: await func(Message(_, __, **kwargs)) @@ -490,7 +497,7 @@ async def reload_plugins(self) -> int: _LOG.info(_LOG_STR, f"Reloaded {len(reloaded)} Plugins => {reloaded}") return len(reloaded) - async def restart(self) -> None: + async def restart(self, update_req: bool = False) -> None: """Restart the Userge""" _LOG.info(_LOG_STR, "Restarting Userge") await self.stop() @@ -500,13 +507,29 @@ async def restart(self) -> None: os.close(handler.fd) except Exception as c_e: _LOG.error(_LOG_STR, c_e) + if update_req: + os.system("pip3 install -r requirements.txt") os.execl(sys.executable, sys.executable, '-m', 'userge') sys.exit() def begin(self) -> None: """This will start the Userge""" - _LOG.info(_LOG_STR, "Starting Userge") nest_asyncio.apply() Conv.init(self) - self.run() + loop = asyncio.get_event_loop() + run = loop.run_until_complete + _LOG.info(_LOG_STR, "Starting Userge") + run(self.start()) + running_tasks: List[asyncio.Task] = [] + for task in self._tasks: + running_tasks.append(loop.create_task(task())) + _LOG.info(_LOG_STR, "Idling Userge") + run(Userge.idle()) _LOG.info(_LOG_STR, "Exiting Userge") + for task in running_tasks: + task.cancel() + run(self.stop()) + for task in asyncio.all_tasks(): + task.cancel() + run(loop.shutdown_asyncgens()) + loop.close() diff --git a/userge/plugins/fun/autopic.py b/userge/plugins/fun/autopic.py new file mode 100644 index 000000000..730109ff7 --- /dev/null +++ b/userge/plugins/fun/autopic.py @@ -0,0 +1,115 @@ +# Copyright (C) 2020 by UsergeTeam@Github, < https://github.com/UsergeTeam >. +# +# This file is part of < https://github.com/UsergeTeam/Userge > project, +# and is released under the "GNU v3.0 License Agreement". +# Please see < https://github.com/uaudith/Userge/blob/master/LICENSE > +# +# All rights reserved. + + +import os +import base64 +import asyncio +import datetime +import textwrap +from shutil import copyfile + +import aiofiles +from PIL import Image, ImageFont, ImageDraw + +from userge import userge, Message, Config, get_collection + +SAVED_SETTINGS = get_collection("CONFIGS") + +__tmp__ = SAVED_SETTINGS.find_one({'_id': 'UPDATE_PIC'}) + +UPDATE_PIC = False +BASE_PIC = "resources/base_profile_pic.jpg" +MDFY_PIC = "resources/mdfy_profile_pic.jpg" +if __tmp__: + UPDATE_PIC = __tmp__['on'] + if not os.path.exists(BASE_PIC): + with open(BASE_PIC, "wb") as media_file_: + media_file_.write(base64.b64decode(__tmp__['media'])) + +del __tmp__ + +LOG = userge.getLogger(__name__) + + +@userge.on_cmd("autopic", about={ + 'header': "set profile picture", + 'usage': "{tr}autopic\n{tr}autopic [image path]\nset timeout using {tr}sappto"}) +async def autopic(message: Message): + global UPDATE_PIC + await message.edit('`processing...`') + if UPDATE_PIC: + if isinstance(UPDATE_PIC, asyncio.Task): + UPDATE_PIC.cancel() + UPDATE_PIC = False + SAVED_SETTINGS.update_one({'_id': 'UPDATE_PIC'}, + {"$set": {'on': False}}, upsert=True) + await message.edit('auto profile picture updation has been **stopped**', + del_in=5, log=__name__) + return + image_path = message.input_str + store = False + if os.path.exists(BASE_PIC) and not image_path: + pass + elif not image_path: + profile_photo = await userge.get_profile_photos("me", limit=1) + if not profile_photo: + await message.err("sorry, couldn't find any picture!") + return + await userge.download_media(profile_photo[0], file_name=BASE_PIC) + store = True + else: + if not os.path.exists(image_path): + await message.err("input path not found!") + return + if os.path.exists(BASE_PIC): + os.remove(BASE_PIC) + copyfile(image_path, BASE_PIC) + store = True + data_dict = {'on': True} + if store: + async with aiofiles.open(BASE_PIC, "rb") as media_file: + media = base64.b64encode(await media_file.read()) + data_dict['media'] = media + SAVED_SETTINGS.update_one({'_id': 'UPDATE_PIC'}, + {"$set": data_dict}, upsert=True) + await message.edit( + 'auto profile picture updation has been **started**', del_in=3, log=__name__) + UPDATE_PIC = asyncio.create_task(apic_worker()) + + +@userge.add_task +async def apic_worker(): + user_dict = await userge.get_user_dict('me') + user = '@' + user_dict['uname'] if user_dict['uname'] else user_dict['flname'] + while UPDATE_PIC: + img = Image.open(BASE_PIC) + i_width, i_height = img.size + s_font = ImageFont.truetype("resources/font.ttf", int((35 / 640)*i_width)) + l_font = ImageFont.truetype("resources/font.ttf", int((50 / 640)*i_width)) + draw = ImageDraw.Draw(img) + current_h, pad = 10, 0 + for user in textwrap.wrap(user, width=20): + u_width, u_height = draw.textsize(user, font=l_font) + draw.text(xy=((i_width - u_width) / 2, int((current_h / 640)*i_width)), text=user, + font=l_font, fill=(255, 255, 255)) + current_h += u_height + pad + tim = datetime.datetime.now( + tz=datetime.timezone(datetime.timedelta(minutes=30, hours=5))) + date_time = (f"DATE: {tim.day}.{tim.month}.{tim.year}\n" + f"TIME: {tim.hour}:{tim.minute}:{tim.second}\n" + "UTC+5:30") + d_width, d_height = draw.textsize(date_time, font=s_font) + draw.multiline_text( + xy=((i_width - d_width) / 2, i_height - d_height - int((20 / 640)*i_width)), + text=date_time, fill=(255, 255, 255), font=s_font, align="center") + img.convert('RGB').save(MDFY_PIC) + await userge.set_profile_photo(MDFY_PIC) + os.remove(MDFY_PIC) + LOG.info("profile photo has been updated!") + await asyncio.sleep(Config.AUTOPIC_TIMEOUT) diff --git a/userge/plugins/fun/carbon.py b/userge/plugins/fun/carbon.py new file mode 100644 index 000000000..0cb89257e --- /dev/null +++ b/userge/plugins/fun/carbon.py @@ -0,0 +1,48 @@ +# Copyright (C) 2020 by UsergeTeam@Github, < https://github.com/UsergeTeam >. +# +# This file is part of < https://github.com/UsergeTeam/Userge > project, +# and is released under the "GNU v3.0 License Agreement". +# Please see < https://github.com/uaudith/Userge/blob/master/LICENSE > +# +# All rights reserved. + + +import random +import asyncio + +from pyrogram.errors.exceptions.bad_request_400 import YouBlockedUser + +from userge import userge, Message + + +@userge.on_cmd("carbon", about={ + 'header': "create a carbon", + 'usage': "{tr}carbon [text | reply to msg]"}) +async def carbon_(message: Message): + replied = message.reply_to_message + if replied: + text = replied.text + else: + text = message.input_str + if not text: + await message.err("input not found!") + return + await message.edit("`creating a carbon...`") + async with userge.conversation("CarbonNowShBot", timeout=30) as conv: + try: + await conv.send_message(text) + except YouBlockedUser: + await message.edit('first **unblock** @CarbonNowShBot') + return + response = await conv.get_response(mark_read=True) + await response.click(x=random.randint(0, 2), y=random.randint(0, 8)) + response = await conv.get_response(mark_read=True) + while not response.media: + response = await conv.get_response(mark_read=True) + caption = "\n".join(response.caption.split("\n")[0:2]) + file_id = response.document.file_id + await message.delete() + await userge.send_document(chat_id=message.chat.id, + document=file_id, + caption='`' + caption + '`', + reply_to_message_id=replied.message_id if replied else None) diff --git a/userge/plugins/fun/kang.py b/userge/plugins/fun/kang.py index 03d24bc62..f2e201442 100644 --- a/userge/plugins/fun/kang.py +++ b/userge/plugins/fun/kang.py @@ -15,6 +15,7 @@ from PIL import Image from pyrogram.api.functions.messages import GetStickerSet from pyrogram.api.types import InputStickerSetShortName +from pyrogram.errors.exceptions.bad_request_400 import YouBlockedUser from userge import userge, Message, Config, pool @@ -85,7 +86,11 @@ def get_response(): if (" A Telegram user has created " "the Sticker Set.") not in htmlstr: async with userge.conversation('Stickers') as conv: - await conv.send_message('/addsticker') + try: + await conv.send_message('/addsticker') + except YouBlockedUser: + await message.edit('first **unblock** @Stickers') + return await conv.get_response(mark_read=True) await conv.send_message(packname) msg = await conv.get_response(mark_read=True) @@ -134,7 +139,11 @@ def get_response(): else: await message.edit("`Brewing a new Pack...`") async with userge.conversation('Stickers') as conv: - await conv.send_message(cmd) + try: + await conv.send_message(cmd) + except YouBlockedUser: + await message.edit('first **unblock** @Stickers') + return await conv.get_response(mark_read=True) await conv.send_message(packnick) await conv.get_response(mark_read=True) diff --git a/userge/plugins/fun/memes.py b/userge/plugins/fun/memes.py index 91897fa88..2797ec6a3 100644 --- a/userge/plugins/fun/memes.py +++ b/userge/plugins/fun/memes.py @@ -257,11 +257,32 @@ "( ^_^)o自自o(^_^ )", "ಠ‿ಠ", "ヽ(´▽`)/", "ᵒᴥᵒ#", "( ͡° ͜ʖ ͡°)", "┬─┬ ノ( ゜-゜ノ)", "ヽ(´ー`)ノ", "☜(⌒▽⌒)☞", "ε=ε=ε=┌(;*´Д`)ノ", "(╬ ಠ益ಠ)", "┬─┬⃰͡ (ᵔᵕᵔ͜ )", "┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻", r"¯\_(ツ)_/¯", "ʕᵔᴥᵔʔ", "(`・ω・´)", "ʕ•ᴥ•ʔ", "ლ(`ー´ლ)", "ʕʘ̅͜ʘ̅ʔ", "( ゚Д゚)", r"¯\(°_o)/¯", "(。◕‿◕。)", - "(ノಠ ∩ಠ)ノ彡( \\o°o)\\", "“ヽ(´▽`)ノ”",) + "(ノಠ ∩ಠ)ノ彡( \\o°o)\\", "“ヽ(´▽`)ノ”", "( ͡° ͜ʖ ͡°)", "¯\_(ツ)_/¯", "( ͡°( ͡° ͜ʖ( ͡° ͜ʖ ͡°)ʖ ͡°) ͡°)", + "ʕ•ᴥ•ʔ", "(▀̿Ĺ̯▀̿ ̿)", "(ง ͠° ͟ل͜ ͡°)ง", "༼ つ ◕_◕ ༽つ", "ಠ_ಠ", "(☞ ͡° ͜ʖ ͡°)☞", + "¯\_༼ ି ~ ି ༽_/¯", "c༼ ͡° ͜ʖ ͡° ༽⊃") +HAPPY = ("( ͡° ͜ʖ ͡°)", "(ʘ‿ʘ)", "(✿´‿`)", "=͟͟͞͞٩(๑☉ᴗ☉)੭ु⁾⁾", "(*⌒▽⌒*)θ~♪", + "°˖✧◝(⁰▿⁰)◜✧˖°", "✌(-‿-)✌", "⌒°(❛ᴗ❛)°⌒", "(゚<|\(・ω・)/|>゚)", "ヾ(o✪‿✪o)シ") -@userge.on_cmd(r"(?:[kK]ek|:/)$", - about={'header': "Check yourself, hint: `:/`"}, name='kek',trigger='') +THINKING = ("(҂⌣̀_⌣́)", "(;¬_¬)", "(-。-;", "┌[ O ʖ̯ O ]┐", "〳 ͡° Ĺ̯ ͡° 〵") + +WAVING = ("(ノ^∇^)", "(;-_-)/", "@(o・ェ・)@ノ", "ヾ(^-^)ノ", "ヾ(◍’౪`◍)ノ゙♡", "(ό‿ὸ)ノ", "(ヾ(´・ω・`)") + +WTF = ("༎ຶ‿༎ຶ", "(‿ˠ‿)", "╰U╯☜(◉ɷ◉ )", "(;´༎ຶ益༎ຶ`)♡", "╭∩╮(︶ε︶*)chu", "( ^◡^)っ (‿|‿)") + +LOVE = ("乂❤‿❤乂", "(。♥‿♥。)", "( ͡~ ͜ʖ ͡°)", "໒( ♥ ◡ ♥ )७", "༼♥ل͜♥༽") + +CONFUSED = ("(・_・ヾ", "「(゚ペ)", "﴾͡๏̯͡๏﴿", "( ̄■ ̄;)!?", "▐ ˵ ͠° (oo) °͠ ˵ ▐", "(-_-)ゞ゛") + +DEAD = ("(✖╭╮✖)", "✖‿✖", "(+_+)", "(✖﹏✖)", "∑(✘Д✘๑)") + +SAD = ("(@´_`@)", "⊙︿⊙", "(▰˘︹˘▰)", "●︿●", "( ´_ノ` )", "彡(-_-;)彡") + +DOG = ("-ᄒᴥᄒ-", "◖⚆ᴥ⚆◗") + + +@userge.on_cmd(r"(?:Kek|:/)$", + about={'header': "Check yourself, hint: `:/`"}, name='Kek',trigger='') async def kek_(message: Message): """kek""" kek = ["/", "\\"] @@ -270,8 +291,8 @@ async def kek_(message: Message): await message.edit(":" + kek[i % 2]) -@userge.on_cmd(r"(?:[lL]ol|-_-)$", - about={'header': "Check yourself, hint: `-_-`"}, name='lol',trigger='') +@userge.on_cmd(r"(?:Lol|-_-)$", + about={'header': "Check yourself, hint: `-_-`"}, name='Lol',trigger='') async def lol_(message: Message): """lol""" lol = "-_ " @@ -282,8 +303,8 @@ async def lol_(message: Message): await message.edit(lol, parse_mode="html") -@userge.on_cmd(r"(?:[fF]un|;_;)$", - about={'header': "Check yourself, hint: `;_;`"}, name="fun", trigger='') +@userge.on_cmd(r"(?:Fun|;_;)$", + about={'header': "Check yourself, hint: `;_;`"}, name="Fun", trigger='') async def fun_(message: Message): """fun""" fun = ";_ " @@ -294,7 +315,7 @@ async def fun_(message: Message): await message.edit(fun, parse_mode="html") -@userge.on_cmd("[oO]of$", about={'header': "Ooooof"}, name='oof', trigger='') +@userge.on_cmd("Oof$", about={'header': "Ooooof"}, name='Oof', trigger='') async def Oof_(message: Message): """Oof""" Oof = "Oo " @@ -303,7 +324,7 @@ async def Oof_(message: Message): await message.edit(Oof) -@userge.on_cmd("[hH]mm$", about={'header': "Hmmmmm"}, name='hmm', trigger='') +@userge.on_cmd("Hmm$", about={'header': "Hmmmmm"}, name='Hmm', trigger='') async def Hmm_(message: Message): """Hmm""" Hmm = "Hm " @@ -312,43 +333,33 @@ async def Hmm_(message: Message): await message.edit(Hmm) -@userge.on_cmd("fp$", about={'header': "Facepalm :P"}) -async def facepalm_(message: Message): - """facepalm_""" +async def check_and_send(message: Message, *args, **kwargs): replied = message.reply_to_message if replied: await asyncio.gather( message.delete(), - replied.reply("🤦‍♂") + replied.reply(*args, **kwargs) ) else: - await message.edit("🤦‍♂") + await message.edit(*args, **kwargs) + + +@userge.on_cmd("fp$", about={'header': "Facepalm :P"}) +async def facepalm_(message: Message): + """facepalm_""" + await check_and_send(message, "🤦‍♂") @userge.on_cmd("cry$", about={'header': "y u du dis, i cri"}) async def cry_(message: Message): """cry""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply(choice(CRI), parse_mode="html") - ) - else: - await message.edit(choice(CRI), parse_mode="html") + await check_and_send(message, choice(CRI), parse_mode="html") @userge.on_cmd("insult$", about={'header': "Check yourself ;)"}) async def insult_(message: Message): """insult""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply(choice(INSULT_STRINGS), parse_mode="html") - ) - else: - await message.edit(choice(INSULT_STRINGS), parse_mode="html") + await check_and_send(message, choice(INSULT_STRINGS), parse_mode="html") @userge.on_cmd("hi", about={ @@ -382,82 +393,65 @@ async def hi_(message: Message): await message.edit(pay) -@userge.on_cmd("react$", about={'header': "Make your userbot react to everything"}) +@userge.on_cmd("react", about={ + 'header': "Make your userbot react to everything", + 'types': ['happy', 'thinking', 'waving', 'wtf', 'love', 'confused', 'dead', 'sad', 'dog'], + 'usage': "{tr}react [type]", + 'examples': ["{tr}react", "{tr}react dead"]}) async def react_(message: Message): """react""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply(choice(FACEREACTS), parse_mode="html") - ) + type_ = message.input_str + if "happy" in type_: + out = choice(HAPPY) + elif "thinking" in type_: + out = choice(THINKING) + elif "waving" in type_: + out = choice(WAVING) + elif "wtf" in type_: + out = choice(WTF) + elif "love" in type_: + out = choice(LOVE) + elif "confused" in type_: + out = choice(CONFUSED) + elif "dead" in type_: + out = choice(DEAD) + elif "sad" in type_: + out = choice(SAD) + elif "dog" in type_: + out = choice(DOG) else: - await message.edit(choice(FACEREACTS), parse_mode="html") + out = choice(FACEREACTS) + await check_and_send(message, out, parse_mode="html") @userge.on_cmd("shg$", about={'header': "Shrug at it !!"}) async def shrugger(message: Message): """shrugger""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply(choice(SHGS), parse_mode="html") - ) - else: - await message.edit(choice(SHGS), parse_mode="html") + await check_and_send(message, choice(SHGS), parse_mode="html") @userge.on_cmd("chase$", about={'header': "You better start running"}) async def chase_(message: Message): """chase""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply(choice(CHASE_STR), parse_mode="html") - ) - else: - await message.edit(choice(CHASE_STR), parse_mode="html") + await check_and_send(message, choice(CHASE_STR), parse_mode="html") @userge.on_cmd("run$", about={'header': "Let Me Run, run, RUNNN!"}) async def run_(message: Message): """run""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply(choice(RUNS_STR), parse_mode="html") - ) - else: - await message.edit(choice(RUNS_STR), parse_mode="html") + await check_and_send(message, choice(RUNS_STR), parse_mode="html") @userge.on_cmd("metoo$", about={'header': "Haha yes"}) async def metoo_(message: Message): """metoo""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply(choice(METOOSTR), parse_mode="html") - ) - else: - await message.edit(choice(METOOSTR), parse_mode="html") + await check_and_send(message, choice(METOOSTR), parse_mode="html") @userge.on_cmd("10iq$", about={'header': "You retard !!"}, name="10iq") async def iqless(message: Message): """iqless""" - replied = message.reply_to_message - if replied: - await asyncio.gather( - message.delete(), - replied.reply("♿") - ) - else: - await message.edit("♿") + await check_and_send(message, "♿") @userge.on_cmd("moon$", about={'header': "kensar moon animation"}) diff --git a/userge/plugins/utils/quote.py b/userge/plugins/fun/quote.py similarity index 61% rename from userge/plugins/utils/quote.py rename to userge/plugins/fun/quote.py index dd77dc2ff..d57d3c236 100644 --- a/userge/plugins/utils/quote.py +++ b/userge/plugins/fun/quote.py @@ -9,6 +9,8 @@ import asyncio +from pyrogram.errors.exceptions.bad_request_400 import YouBlockedUser + from userge import userge, Message @@ -21,12 +23,19 @@ async def quotecmd(message: Message): args = message.input_str replied = message.reply_to_message async with userge.conversation('QuotLyBot') as conv: - if replied: - await userge.forward_messages(chat_id=conv.chat_id, - from_chat_id=message.chat.id, - message_ids=replied.message_id) - else: - await conv.send_message(args) + try: + if replied: + await userge.forward_messages(chat_id=conv.chat_id, + from_chat_id=message.chat.id, + message_ids=replied.message_id) + else: + if not args: + await message.err('input not found!') + return + await conv.send_message(args) + except YouBlockedUser: + await message.edit('first **unblock** @QuotLyBot') + return quote = await conv.get_response(mark_read=True) await userge.forward_messages(chat_id=message.chat.id, from_chat_id=conv.chat_id, diff --git a/userge/plugins/misc/upload.py b/userge/plugins/misc/upload.py index ecb597662..f715fcd38 100644 --- a/userge/plugins/misc/upload.py +++ b/userge/plugins/misc/upload.py @@ -70,19 +70,24 @@ async def doc_upload(chat_id, path): c_time = time.time() thumb = await get_thumb() await userge.send_chat_action(chat_id, "upload_document") - msg = await userge.send_document( - chat_id=chat_id, - document=str(path), - thumb=thumb, - caption=path.name, - parse_mode="html", - disable_notification=True, - progress=progress, - progress_args=( - "uploading", userge, message, c_time + try: + msg = await userge.send_document( + chat_id=chat_id, + document=str(path), + thumb=thumb, + caption=path.name, + parse_mode="html", + disable_notification=True, + progress=progress, + progress_args=( + "uploading", userge, message, c_time, str(path.name) + ) ) - ) - await finalize(chat_id, message, msg, start_t) + except Exception as u_e: + await message.edit(u_e) + raise u_e + else: + await finalize(chat_id, message, msg, start_t) async def vid_upload(chat_id, path): @@ -94,21 +99,26 @@ async def vid_upload(chat_id, path): start_t = datetime.now() c_time = time.time() await userge.send_chat_action(chat_id, "upload_video") - msg = await userge.send_video( - chat_id=chat_id, - video=strpath, - duration=metadata.get("duration").seconds, - thumb=thumb, - caption=path.name, - parse_mode="html", - disable_notification=True, - progress=progress, - progress_args=( - "uploading", userge, message, c_time + try: + msg = await userge.send_video( + chat_id=chat_id, + video=strpath, + duration=metadata.get("duration").seconds, + thumb=thumb, + caption=path.name, + parse_mode="html", + disable_notification=True, + progress=progress, + progress_args=( + "uploading", userge, message, c_time, str(path.name) + ) ) - ) - await remove_thumb(thumb) - await finalize(chat_id, message, msg, start_t) + except Exception as u_e: + await message.edit(u_e) + raise u_e + else: + await remove_thumb(thumb) + await finalize(chat_id, message, msg, start_t) async def audio_upload(chat_id, path): @@ -126,22 +136,27 @@ async def audio_upload(chat_id, path): if metadata.has("artist"): artist = metadata.get("artist") await userge.send_chat_action(chat_id, "upload_audio") - msg = await userge.send_audio( - chat_id=chat_id, - audio=strpath, - thumb=thumb, - caption=path.name, - title=title, - performer=artist, - duration=metadata.get("duration").seconds, - parse_mode="html", - disable_notification=True, - progress=progress, - progress_args=( - "uploading", userge, message, c_time + try: + msg = await userge.send_audio( + chat_id=chat_id, + audio=strpath, + thumb=thumb, + caption=path.name, + title=title, + performer=artist, + duration=metadata.get("duration").seconds, + parse_mode="html", + disable_notification=True, + progress=progress, + progress_args=( + "uploading", userge, message, c_time, str(path.name) + ) ) - ) - await finalize(chat_id, message, msg, start_t) + except Exception as u_e: + await message.edit(u_e) + raise u_e + else: + await finalize(chat_id, message, msg, start_t) async def get_thumb(path: str = ''): @@ -172,4 +187,4 @@ async def finalize(chat_id: int, message: Message, msg: Message, start_t: int): else: end_t = datetime.now() ms = (end_t - start_t).seconds - await message.edit(f"Uploaded in {ms} seconds") \ No newline at end of file + await message.edit(f"Uploaded in {ms} seconds") diff --git a/userge/plugins/tools/timeout.py b/userge/plugins/tools/timeout.py index 85ead6f0c..f8c6df885 100644 --- a/userge/plugins/tools/timeout.py +++ b/userge/plugins/tools/timeout.py @@ -13,6 +13,7 @@ __tmp_msg__ = SAVED_SETTINGS.find_one({'_id': 'MSG_DELETE_TIMEOUT'}) __tmp_wel__ = SAVED_SETTINGS.find_one({'_id': 'WELCOME_DELETE_TIMEOUT'}) +__tmp_pp__ = SAVED_SETTINGS.find_one({'_id': 'AUTOPIC_TIMEOUT'}) if __tmp_msg__: Config.MSG_DELETE_TIMEOUT = __tmp_msg__['data'] @@ -20,7 +21,10 @@ if __tmp_wel__: Config.WELCOME_DELETE_TIMEOUT = __tmp_wel__['data'] -del __tmp_msg__, __tmp_wel__ +if __tmp_pp__: + Config.AUTOPIC_TIMEOUT = __tmp_pp__['data'] + +del __tmp_msg__, __tmp_wel__, __tmp_pp__ @userge.on_cmd("sdelto (\\d+)", about={ @@ -46,7 +50,7 @@ async def view_delete_timeout(message: Message): """view delete timeout""" if Config.MSG_DELETE_TIMEOUT: await message.edit( - f"`Currently messages will be deleted after {Config.MSG_DELETE_TIMEOUT} seconds!`", + f"`Messages will be deleted after {Config.MSG_DELETE_TIMEOUT} seconds!`", del_in=5) else: await message.edit(f"`Auto message deletion disabled!`", del_in=3) @@ -75,7 +79,34 @@ async def view_welcome_timeout(message: Message): """view welcome/left timeout""" if Config.WELCOME_DELETE_TIMEOUT: await message.edit( - f"`Currently welcome/left messages will be deleted after {Config.WELCOME_DELETE_TIMEOUT} seconds!`", + f"`Welcome/Left messages will be deleted after " + f"{Config.WELCOME_DELETE_TIMEOUT} seconds!`", del_in=5) else: await message.edit(f"`Auto welcome/left message deletion disabled!`", del_in=3) + + +@userge.on_cmd("sapicto (\\d+)", about={ + 'header': "Set auto profile picture timeout", + 'usage': "{tr}sapicto [timeout in seconds]", + 'examples': "{tr}sapicto 60"}) +async def set_app_timeout(message: Message): + """set auto profile picture timeout""" + t_o = int(message.matches[0].group(1)) + if t_o < 15: + await message.err("too short! (min > 15sec)") + return + await message.edit("`Setting auto profile picture timeout...`") + Config.AUTOPIC_TIMEOUT = t_o + SAVED_SETTINGS.update_one( + {'_id': 'AUTOPIC_TIMEOUT'}, {"$set": {'data': t_o}}, upsert=True) + await message.edit( + f"`Set auto profile picture timeout as {t_o} seconds!`", del_in=3) + + +@userge.on_cmd("vapicto", about={'header': "View auto profile picture timeout"}) +async def view_app_timeout(message: Message): + """view profile picture timeout""" + await message.edit( + f"`Profile picture will be updated after {Config.AUTOPIC_TIMEOUT} seconds!`", + del_in=5) diff --git a/userge/plugins/tools/updater.py b/userge/plugins/tools/updater.py index d8ab8d0e5..db33f8685 100644 --- a/userge/plugins/tools/updater.py +++ b/userge/plugins/tools/updater.py @@ -91,7 +91,7 @@ async def check_update(message: Message): await message.edit( '**Userge Successfully Updated!**\n' '`Now restarting... Wait for a while!`', del_in=3) - asyncio.get_event_loop().create_task(userge.restart()) + asyncio.get_event_loop().create_task(userge.restart(update_req=True)) return if not Config.HEROKU_GIT_URL: await message.err("please set heroku things...") diff --git a/userge/plugins/utils/webss.py b/userge/plugins/utils/webss.py index f0d4c138e..242552f85 100644 --- a/userge/plugins/utils/webss.py +++ b/userge/plugins/utils/webss.py @@ -8,49 +8,62 @@ import os -from time import time -import requests -from userge import userge, Message, Config +from re import match +from asyncio import sleep + +import aiofiles +from selenium import webdriver -CHANNEL = userge.getCLogger(__name__) +from userge import userge, Message, Config @userge.on_cmd("webss", about={'header': "Get snapshot of a website"}) async def webss(message: Message): - if Config.SCREENSHOT_API is None: - await message.edit( - "Damn!\nI forgot to get the api from (here)[https://screenshotlayer.com]", - del_in=0) + link_match = match(r'\bhttps?://.*\.\S+', message.input_str) + if not link_match: + await message.err("`I need a valid link to take screenshots from.`") return - await message.edit("`Processing`") - suc, data = await getimg(message.input_str) - if suc: - await message.edit('Uploading..') - await userge.send_chat_action(message.chat.id, "upload_photo") - - msg = await userge.send_document(message.chat.id, data, caption=message.input_str) - await CHANNEL.fwd_msg(msg) - - await message.delete() - await userge.send_chat_action(message.chat.id, "cancel") - if os.path.isfile(data): - os.remove(data) - else: - await message.err(data, del_in=6) - - -async def getimg(url): - requrl = "https://api.screenshotlayer.com/api/capture" - requrl += "?access_key={}&url={}&fullpage={}&viewport={}" - response = requests.get( - requrl.format(Config.SCREENSHOT_API, url, '1', "2560x1440"), - stream=True - ) - if 'image' in response.headers["content-type"]: - fname = f"screenshot_{time()}.png" - with open(fname, "wb") as file: - for chunk in response.iter_content(chunk_size=128): - file.write(chunk) - return True, fname - else: - return False, response.text + link = link_match.group() + if Config.GOOGLE_CHROME_BIN is None: + await message.err("need to install Google Chrome. Module Stopping") + return + await message.edit("`Processing ...`") + chrome_options = webdriver.ChromeOptions() + chrome_options.binary_location = Config.GOOGLE_CHROME_BIN + chrome_options.add_argument('--ignore-certificate-errors') + chrome_options.add_argument("--test-type") + chrome_options.add_argument("--headless") + chrome_options.add_argument('--no-sandbox') + chrome_options.add_argument('--disable-dev-shm-usage') + chrome_options.add_argument("--no-sandbox") + chrome_options.add_argument('--disable-gpu') + driver = webdriver.Chrome(chrome_options=chrome_options) + driver.get(link) + height = driver.execute_script( + "return Math.max(document.body.scrollHeight, document.body.offsetHeight, " + "document.documentElement.clientHeight, document.documentElement.scrollHeight, " + "document.documentElement.offsetHeight);") + width = driver.execute_script( + "return Math.max(document.body.scrollWidth, document.body.offsetWidth, " + "document.documentElement.clientWidth, document.documentElement.scrollWidth, " + "document.documentElement.offsetWidth);") + driver.set_window_size(width + 125, height + 125) + wait_for = height / 1000 + await message.edit(f"`Generating screenshot of the page...`" + f"\n`Height of page = {height}px`" + f"\n`Width of page = {width}px`" + f"\n`Waiting ({int(wait_for)}s) for the page to load.`") + await sleep(int(wait_for)) + im_png = driver.get_screenshot_as_png() + driver.close() + message_id = message.message_id + if message.reply_to_message: + message_id = message.reply_to_message.message_id + file_path = os.path.join(Config.DOWN_PATH, "webss.png") + async with aiofiles.open(file_path, 'wb') as out_file: + await out_file.write(im_png) + await userge.send_document(chat_id=message.chat.id, + document=file_path, + caption=link, + reply_to_message_id=message_id) + os.remove(file_path) diff --git a/userge/plugins/utils/welcome.py b/userge/plugins/utils/welcome.py index 573e1ffcb..a11dcd50f 100644 --- a/userge/plugins/utils/welcome.py +++ b/userge/plugins/utils/welcome.py @@ -12,6 +12,7 @@ import base64 import asyncio +import aiofiles from hachoir.metadata import extractMetadata from hachoir.parser import createParser from pyrogram import Message as RawMessage @@ -206,8 +207,8 @@ async def raw_set(message: Message, name, collection, chats): "trying to download", userge, message, c_time ) ) - with open(tmp_path, "rb") as media_file: - media = base64.b64encode(media_file.read()) + async with aiofiles.open(tmp_path, "rb") as media_file: + media = base64.b64encode(await media_file.read()) file_name = os.path.basename(tmp_path) os.remove(tmp_path) @@ -299,8 +300,8 @@ async def raw_say(message: Message, name, collection): file_name = found['name'] media = found['media'] tmp_media_path = os.path.join(Config.DOWN_PATH, file_name) - with open(tmp_media_path, "wb") as media_file: - media_file.write(base64.b64decode(media)) + async with aiofiles.open(tmp_media_path, "wb") as media_file: + await media_file.write(base64.b64decode(media)) file_id, file_ref = await send_proper_type(message, caption, file_type, tmp_media_path) collection.update_one({'_id': message.chat.id}, {"$set": {'fid': file_id, 'fref': file_ref}}, diff --git a/userge/utils/exceptions.py b/userge/utils/exceptions.py index 13b9e0b89..d54d94ea9 100644 --- a/userge/utils/exceptions.py +++ b/userge/utils/exceptions.py @@ -9,11 +9,7 @@ from asyncio.exceptions import CancelledError class StopConversation(CancelledError): - """ - Exception to raise if conversation terminated. - """ + """Exception to raise if conversation terminated""" class ProcessCanceled(Exception): - """ - Custom Exception to terminate threads. - """ + """Custom Exception to terminate threads""" diff --git a/userge/utils/progress.py b/userge/utils/progress.py index 6cf28444a..7294531b0 100644 --- a/userge/utils/progress.py +++ b/userge/utils/progress.py @@ -20,7 +20,8 @@ async def progress(current: int, ud_type: str, client: 'userge.Userge', message: 'userge.Message', - start: int) -> None: + start: int, + file_name: str = '') -> None: if message.process_is_canceled: await client.stop_transmission() now = time.time() @@ -30,7 +31,7 @@ async def progress(current: int, speed = current / diff time_to_completion = time_formatter(int((total - current) / speed)) progress_str = \ - "__{}__\n" + \ + "__{}__ : `{}`\n" + \ "```[{}{}]```\n" + \ "**Progress** : `{}%`\n" + \ "**Completed** : `{}`\n" + \ @@ -39,6 +40,7 @@ async def progress(current: int, "**ETA** : `{}`" progress_str = progress_str.format( ud_type, + file_name, ''.join(["█" for i in range(floor(percentage / 5))]), ''.join(["░" for i in range(20 - floor(percentage / 5))]), round(percentage, 2), diff --git a/userge/versions.py b/userge/versions.py index ddaa7d262..678ecb212 100644 --- a/userge/versions.py +++ b/userge/versions.py @@ -14,7 +14,7 @@ __version_mjaor__ = 0 __version_minor__ = 1 __version_micro__ = 4 -__version_beta__ = 4 +__version_beta__ = 5 __version__ = "{}.{}.{}".format(__version_mjaor__, __version_minor__,