From 342a616ff9750f63f0180327d9fe5ff9bff46603 Mon Sep 17 00:00:00 2001 From: m5rcel { Marcel } Date: Thu, 9 Oct 2025 20:24:27 +0200 Subject: [PATCH] Uploading m5rcode Ubuntu Port to the repo --- README.md | 185 ++++++++ commands/__pycache__/cmd_cd.cpython-313.pyc | Bin 0 -> 2418 bytes .../__pycache__/cmd_credits.cpython-313.pyc | Bin 0 -> 3651 bytes commands/__pycache__/cmd_exit.cpython-313.pyc | Bin 0 -> 3954 bytes .../__pycache__/cmd_fastfetch.cpython-313.pyc | Bin 0 -> 7533 bytes commands/__pycache__/cmd_nano.cpython-313.pyc | Bin 0 -> 1509 bytes commands/__pycache__/cmd_new.cpython-313.pyc | Bin 0 -> 1523 bytes commands/__pycache__/cmd_run.cpython-313.pyc | Bin 0 -> 3071 bytes commands/__pycache__/cmd_wdir.cpython-313.pyc | Bin 0 -> 7629 bytes commands/cmd_cd.py | 46 ++ commands/cmd_credits.py | 47 +++ commands/cmd_dir.py | 163 ++++++++ commands/cmd_exit.py | 72 ++++ commands/cmd_fastfetch.py | 121 ++++++ commands/cmd_nano.py | 15 + commands/cmd_new.py | 16 + commands/cmd_run.py | 48 +++ commands/cmd_wdir.py | 113 +++++ files/m5rcel.m5r | 46 ++ files/maze.m5r | 288 +++++++++++++ files/snake.m5r | 195 +++++++++ files/test.m5r | 111 +++++ files/testing.m5r | 1 + files/testing.pyjs.m5r | 1 + m5r_interpreter.py | 124 ++++++ m5rshell.py | 395 ++++++++++++++++++ requirements.txt | 8 + utils/downloader.py | 315 ++++++++++++++ utils/updater.py | 52 +++ version.txt | 1 + 30 files changed, 2363 insertions(+) create mode 100644 README.md create mode 100644 commands/__pycache__/cmd_cd.cpython-313.pyc create mode 100644 commands/__pycache__/cmd_credits.cpython-313.pyc create mode 100644 commands/__pycache__/cmd_exit.cpython-313.pyc create mode 100644 commands/__pycache__/cmd_fastfetch.cpython-313.pyc create mode 100644 commands/__pycache__/cmd_nano.cpython-313.pyc create mode 100644 commands/__pycache__/cmd_new.cpython-313.pyc create mode 100644 commands/__pycache__/cmd_run.cpython-313.pyc create mode 100644 commands/__pycache__/cmd_wdir.cpython-313.pyc create mode 100644 commands/cmd_cd.py create mode 100644 commands/cmd_credits.py create mode 100644 commands/cmd_dir.py create mode 100644 commands/cmd_exit.py create mode 100644 commands/cmd_fastfetch.py create mode 100644 commands/cmd_nano.py create mode 100644 commands/cmd_new.py create mode 100644 commands/cmd_run.py create mode 100644 commands/cmd_wdir.py create mode 100644 files/m5rcel.m5r create mode 100644 files/maze.m5r create mode 100644 files/snake.m5r create mode 100644 files/test.m5r create mode 100644 files/testing.m5r create mode 100644 files/testing.pyjs.m5r create mode 100644 m5r_interpreter.py create mode 100644 m5rshell.py create mode 100644 requirements.txt create mode 100644 utils/downloader.py create mode 100644 utils/updater.py create mode 100644 version.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..95d77d3 --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# m5rcode – The Unofficial Polyglot Programming Language + +![Polyglot](https://img.shields.io/badge/language-Python%2FJS%2FPHP%2FC%23%2FC++-purple.svg) +![Status](https://img.shields.io/badge/status-experimental-orange.svg) + +**m5rcode** is an experimental **polyglot programming language** written with a blend of **Python, JavaScript, PHP, C#, and C++**. +It uses obfuscation and cross-language embedding to allow developers to write multi-language scripts in a single file (`.m5r`). +The project includes a custom **REPL shell** (`m5rshell`) and an **interpreter** for `.m5r` files. + +--- + +## ✨ Features + +- **Polyglot Language Core** + - Mix **Python, JavaScript, PHP, CSS, C#, and C++** in a single `.m5r` file. + - Interpreter extracts, executes, and blends code blocks. + - Supports obfuscation for added challenge and uniqueness. + +- **m5rshell – The REPL Shell** + - Interactive REPL for testing code snippets. + - Commands: + - `new`, `nano`, `run`, `fastfetch`, `credits`, `exit`, `cd` + - Developer-friendly CLI for creating & running `.m5r` scripts. + +- **.m5r File Runner (Interpreter)** + - Executes `.m5r` polyglot files directly. + - Efficient, multi-language-aware execution engine. + - Provides fast output even for obfuscated code. + +--- + +## πŸ”§ Requirements + +- Python **3.8+** + +--- + +## πŸ“¦ Installation + +Clone this repository: + +```bash +git clone https://github.com/m4rcel-lol/m5rcode.git +cd m5rcode +``` + +--- + +## ⚑ Quick Start + +### Run the REPL shell +```bash +python3 m5rshell.py +``` +--- + +## πŸ“ Example + +Here’s a `hello.m5r` script that prints **Hello world** in all supported languages: + +```m5r + + + + (char)c))); + } +} +?> + +int main() { + int arr[] = {72,101,108,108,111,32,119,111,114,108,100}; + for(int i = 0; i < 11; i++) std::cout << (char)arr[i]; + std::cout << std::endl; + return 0; +} +?> + + +``` + +--- + +## πŸ“‚ Project Structure + +``` +m5rcode/ +β”œβ”€ m5rshell.py # The REPL shell +β”œβ”€ m5r_interpreter.py # The .m5r polyglot interpreter +β”œβ”€ files/ # Sample m5rcode scripts +β”œβ”€ utils/ # Handling everything +β”œβ”€ commands # Commands handling +β”œβ”€ version.txt # Version of m5rcode showing on fastfetch command +β”œβ”€ requirements.txt # Modules u need to install for m5rcode to work. +└─ README.md +``` + +--- + +## 🀝 Contributing + +Contributions are welcome! +If you want to add support for more languages, open an issue or PR. + +--- + +## πŸ‘₯ Credits + +- **Creator:** [m5rcel](https://github.com/m4rcel-lol) +- **Contributors:** The m5rcode community + +--- + +## πŸ“œ Where can I install m5rcode from? + +You can install m5rcode from it's official website **pythonjs.cfd** hover over and copy paste to your browser. diff --git a/commands/__pycache__/cmd_cd.cpython-313.pyc b/commands/__pycache__/cmd_cd.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76b10e8df010a90623828d57a8e61f6ab1ba783d GIT binary patch literal 2418 zcmbtV|4-XS6hAu-Bw&&>l!Z1xJfKs4|wVtfMlhwQCDe5bD$M;0^W5J@XR1x zxeZ-&RMh>3C=*c%F`wyBkt#=(nW`#f4ht*bD*!1TK$@qZi>FTl$PjoW&K<2o z-plf3J)IUc`HdS}NDeCE##Q~5zsJG~m=2`Ku03X|cmPQ3wWyvZ>Z`(@B}oldu_Zxj z$0mu9k+^t|EcQsc5>1Z0yAD=Gd~?V99Ej2rkU)ugkat#1DTW}c5Yf7Um<7?CNA+0M z#I%Cg1*qRrBvXL8ZX&laucT6VF`>GE>P!*NDQ1*GjG?4vp$9ueUi*pwm08qKIVY|S zf}Fzxeb7Izri9y>U6J&SaI9=7n07-qV8$2~U4SB`*oRnn_rbyH zAr_4XEii8O;BLZqaIf|kI)5prES*)OYTxH+J0m$eV@HpSGrWJ;esGE7IKlmA0BH2& z%|o)$a?tAN+1U9`I!Px&H9gfp(_z|{m|ME4LBhSrCEby29)zUQA^H2E1_Se;Y1~mU zueukYs+o`w4RR$u{RFvH1BZ*cAgOoc#VBP}7EJ|tH06#!+#BRFW=_~_8^J{lHSH22 zR!LyvEM}YspyY4P-pk%wlh<$OZ*TC0j)849_(vUR39XLj##bkElUuAWJDwRYvTYXI zwiO7j*5~S1*&MqzwSGN+ef>uM#>UjMna!C(;N!B3Zt|5~OiRPE_pJ}Gf$T_TWUaf< z*s+bnEM=Cm_cQmO9{;i9hmL~3zZ8xX!+ln`FXzDlF&z)(ht|*M&u_$^4Q~!VJGXhR z5FUQ)DTN}%P>&Vr*@zc=N3Gt`Vra|?jg=x@#mIma8F+l<@9hw`eC2H&2(%XcotD4z zX}s7Kx4Pm*|Dfd`++vS?*S&f=clt#;Zw2|6>{N*jzHV&F4rPY2W0|qF)?#C))!12T zKb5_jx%z10^^QU0!{_l|Mt&N3e&$8{1uJ;rSN0-&4<|3m?0GVhP5aF!qV=Q)kuHdP zP=g?(b$KyGWL6NqUKCT+6TcwL;s$4?R87%zL4e1Jhb}y!7D2#oZ>o|Ynz&=$Tr_c` z6*hq#)qYP%y0<+M!63oE1@CCLJNBpVefK4*a+924kfaEaN77R|6w{(zL>2oUe6Z^i nc}6Qx&%r)?K$@IE`0A~TqNrEE`x`j%7Zar}QSShmZNq;6xaFt; literal 0 HcmV?d00001 diff --git a/commands/__pycache__/cmd_credits.cpython-313.pyc b/commands/__pycache__/cmd_credits.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f398ceea334bf3f6dec863ae56430ed62712b4e GIT binary patch literal 3651 zcmbVPYitwQ6~6Y2Ux^>Vb`nU~WXSTU5sBG?Ss-lLJWK+Fr1E665JN|fJ&A`MkG*%s z1-z?5|5Qd>i9`b7R9oTgqODg&?V^noDphJ%T512zDD4u>rmD23vRw{z(RAqwAfufNI|u|YEi5{VWD94Bk=YH6wXzh4BK4KsC`6BA0$lK3y!zVo#d!Q+ z@3H<%`!fasw*oY!!v|&HrjSRtYXQ_i1$B z8Z4qDA!aj;Uh9CFJLn?9U@Eq<7EG~La3;-C*fvXtY=(EJhCEc;%@eZkbv|>!MAc{e z?KPhff3enHh^%aln?MjFmOf^Ysdzm$2*u0@o0|geMRCwlYcT?~p?M*?4H~MaXT69Q z9imzLVQYibi*Se&H#ER;G-&uP9Op~ncsGE+5O7>uz-f9ZoR&>-nzw-CekmOPrZ}E0 z;CK&P!#0_Yx0)InqP~fjSYIy)vMrpo7c#Wk-$rPpe!3XMQL%PY*K^MLO^F!Ej}0wS zAImn*7C@sP!+EuH_E_fzF&LVXY~c8Lgc5Lq&w)P9&LYlABiX@O;yjYd8n7M0Rnmx;D}aeuqYvsmbI-qv;dbwPk;{63hmlGv=P3b0WdH zHpp!f8zRA0^_hEO(76gi&4@u;-cMUqOM63nx09LY8aWyw^Z=ne_%8Q5u8G~@L$bTU zg3a%Wju)OqO7=8p>3MFy+4CZ<8Ez%Ot+m+ZcL61PIX7f;kn>cj7w#dO?A!EQ_)NP@ zF1K(#5_7I)T|Dhu_FvWioc;fd*6&|3TAkHsHD=?^D)RUGXnj0qQRtQNC#S%rPsmq> zO_36^3a>Ifla*D&UV91)-Z3GOVuppE3Q{&F;DS4MO-*Oz%SvBzCRNz^W1PJ#BvocK ztEi$p%j^ZwoRqzm5kQACnU%6Qkx3MGaEg#(X0n)31)LG(gv2ON1i%*s|4c$rXP^p5 zGoq~IM0o28EyIE$&dM+`Bjp7-DHNIjTuz96@FEvlS$L`$C6mA^GjN(o3sR2h59P@V z_|2W$bv{?;cMZo-EINF8lEn^UriH05%-8w78ozy`KK@OCI`P9uee&n^$zRp^uS0%J zDill}G{Zg^J3TTwi3zen4_z1-H(aqO8=d3_#>Nc$h3MGW#9M~r+`ve5d~(3Bjl`nS zaf5>G7`7Z1!9z`0kW&JlgmMImiku{>;1fZCV2}dCpd>-A78r(aj3CuD@zL{!r#kW0 z=;_HQ3`5%tS`nlf!!@0~%3l#vYT9TPWm&-W0W~Ae8jgAiV+86QUIjpbuVK=J85B-j zF{oT3Wl-6?YS3~bBN#N!N}rG1+Y4`(E>#XJ=9jKNysjM?(LAHtnY88> z8-=6=sq~Y|(Z%S}#KQ^g(6Hu-Y9rH{JJ~37dEs*Dq894^Q}oXhf1J?z$28A5Z6cw$ zryHfx3+d8Sxv;orDf}?3y*8+MhP3EK&3)-|I}CR_`iLq^#{*dUS!{z9d-8HJOh@rb(9XxO%;b% z+dA%w^Wt2*Ox=(EcH+Yc?bX9tAOZ{RosZi?dV5G~+g~|c*}K@f)cvqqJ2;>P2aC~_ zj;>N$>78=0Y%eD&w(=FNJzN}JZQJp|{_^?dj=tr#aB*ZM&{;~BVk__3MFt zP`l2sN>KOrF49Zxhi=_};!$4r4}ay3f-#*v&w}4;%omX_Pkw$<_s0I_WU&Vh?XVzlzSq9#-^$tP4u z{OP9t06m*C{)JJf$oa%RdnFchvbUZMB5|>)dCVw0w&Qt>XR< D36kZn literal 0 HcmV?d00001 diff --git a/commands/__pycache__/cmd_exit.cpython-313.pyc b/commands/__pycache__/cmd_exit.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f7eee5ca9c515059e3d436dd3b0c7afeedd41f7 GIT binary patch literal 3954 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 literal 0 HcmV?d00001 diff --git a/commands/__pycache__/cmd_new.cpython-313.pyc b/commands/__pycache__/cmd_new.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e32c57d5e3d65f5490c2790a8777bc4cd3f9f7b GIT binary patch literal 1523 zcmZt`U2oe|^jv?WN$MnB=*AcyyB$HqVr$r%YG@jjXjg*R6hurVP>?Jyc3PLjF4wL? z^EB-Nt%#{qqH1^|JWc!z_yvvV)ap`RfS0|YMW_-_+-t{aRpnSZ_k7>;b=^;;lEC1j zPySpvMgV_tCIV?d^sh2v2Q<*oT_F4v?xGI`B4D@(S3nalfhNrgp*0bgkt#pqOj$)# zdfz2hEmVb=-4Z|_vttK72ZA&pf`*8w373FKJRXrXiNuy=HAa(VtF_=Zn}%aP;~CY& zz&%7^Vf>NI}e@GRZ5360n7 zhUFMd%j3mhjA_2G;x?@U3stik!Qq-#p%(VRE7Y2%?pUqqwGA5Ab=$FhU8gUcbZR;h zvO>YT&d>um%)EGz!MzObUO33)dzt*9GTDjsl-C}8a5Q;gx3pc_y|;a@d-1o);v?yC zqR(rsoo;8034&&S!=yV<?tO7V_2~{$Y{mIETeb5ukUjq9gA3Qqii4)(5 z(&W+ir(s4ooAVT0h2h$=o9hCG%YfNee&=2BGSrb8E4RlM3316S+@8QjgII>SfvvA> z&-bP&YPBZ{1vu(nB(~|AP0yFY0^A=VUB*F3`ytyw)c>s2XHi=+Pb-Ub8BvM zu9N#w{Xsn#&-KQ0N6NYHbG!NNe5cj*pKScJ(fwlY`g?n?&iAtO`^v(hl08bD`!2Vu zZmS(_KQ-0;_)tl0l{QOzlW*=T`C}2%SN@KtzBRX2H&;8K4P(4~qnEz%G(K}IGlQq8 zssB8l^Dl2_=ZoU6#pHr8rlO$qvO30J5`Tm2B_(ye>6+^ePAj_p<+{-rXfnE9w~6OB zY{zn3T_=3J!?$a?;rqnCzwTR}uKx@q%T0K52!9g+;{TZ-m9OEsAj-4< zCS!6jG~_m_6byyNYi`3OM$-sJ5xT$5V+=#m`0X&$Ir0Wm@YVFrGxU#$5c(Ywf51z} Ml7foQfuX?de=D6Nga7~l literal 0 HcmV?d00001 diff --git a/commands/__pycache__/cmd_run.cpython-313.pyc b/commands/__pycache__/cmd_run.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecb85ec95b05c4425ab6d43fcef63c6d9bfa62b6 GIT binary patch literal 3071 zcmahLOKcm*b@oS6GXIijSX#B6zp1LV!|W3NW>VzPa9axHntiw(_HL}fNY?fh~5Cw8Ia8O zAGSde&-B*a{#Gg;DqXcP(=U3uh&BU!E~fCBn-y54^erVW&0la6>;J40f|o78BN zqzDLjC1%uzkj`EY=3A%IjxkUY%=G+7c05l(=z7>uJ%#eEw=JR=n^@)Jg>f7ch)2~G zv#eQ5#IukwRawz6VGWb;#SGB+9~qh+;|Oo6`5dNK5Km6at9quOnpC1FiX=L=WE9jf zke5~SHZT-*EZZ=QIhHLbGA`-y;tKJ|vZia6ER(Lb>EaI21MU?E>t9Q5i)iwT?9s6(+Wn1Qg1@(#T zf*_b~1End0J(A6caMlzi!SK(I6grIaYc4y5P@3Llk2F=o9!Wc{omB%$%;7Tq5V@~# zqrl`y7ttb0Ll88^;in`{;wRW-z~O4c@g}M>>7YYRh8?U8AxDJNL7wjRo`a1yLq}&* zY%5F${)f&H=TsX?uIKfV+{t6@eFu{~eGvLx$a%YC37t##IXuaJ2Wvynp+oAFj!51K zjzv13;*&$|T?b2Xjyw?ohv?ATP#2(nz~>~5hz3#mgtMP~$-&wXZuVUh##r#q$u`x& zBtLwC30`-lTuH}p&L%*IlmVN!|Sa`WBIdVWm6KzTQ)2%uitqBT@%_CT|~#x>6S(cWMK|oJA_2|62Cgf4WS%!9Hmb?a@;5bJ`xY;{mrkH z!q3Lg5K3Ihp_W`nEK2_0mpZ3nU5QoqMT`ynJ#qDjs2i4;GfKJ=UlmGLZuGm*A)q#9 z4Mo!zSACN=&lgvs@sabfo9CxihZBZ)WyM-DbP*WT#z9fjMQcejMOs-!S|#Iz>3pUj z(L{O${`bER(~F=!^aa#~W+4EK&|&?-Nrku3h9IKLkX%RL)FL{|<(L>lyO>~FSS!X@ z!p6@K&QupoTH8bs)1p8+CKpv*T`uA&a{NHWrR8`sZ)7uhb1L3qp-E=a{Cxx!c&C5z z)0u7Q&W#Um+(~byKb`rv2}KnBw11ExOnG{hgL)3Px)e39S}JjwrCd&1Ca$7wmaEV+ zxNKP={%odbm9Q!sC97Dn2ydy&7D4cE4jRwuXww11jbp>2x~yhPR%Riuwml+(nXkNd zbv7RB#k8d2BQOz{x~!R&NrWQSbPH3{5-xf10^yCKsuLco8HKP|CGMQ2E17(rFcNXy zxHv!m#x?Av+#X9U6sey`I05}kxe7}IXYk52r3q!Mfnq#ub)?fQEflelRZWv{V7*2w zbeWa{dV%tmqGC)!^kBlUrfD46nh{q?&ujWyF*l~A95{^ zbhZ{cyBnHVbA1u+u8zDvvNJHc{q}G0Z>>+P-SFg^`>%mWL*}W z2kS?VRm+v~+U!#S`7be#JN+HvUe8eV&G+A|hx;~XcY-JD-M!VzmCN;^7wbL8{@Ec! z{A<&Xy(k!2e`o!jyW2gZ_A?b9%~sk2yIr!rdEYXGYPz7(?+9j^d-evjP`m?dY+i zI1zKxA4N3A;73uXEEf!=l&82?mVaEz=vL-H>HWn|dP_dSuzOGM1K= zGZvJBg_5P3vixg=X}5304E<$;=|rQ+q_O`q^d-v+?E@V;R|%Pbp!ECHXc5Or(pvk5t)N9f!P@5bJSt~g+51tzo27J`4}U9 K2^FAm@qYmu#$x9H literal 0 HcmV?d00001 diff --git a/commands/__pycache__/cmd_wdir.cpython-313.pyc b/commands/__pycache__/cmd_wdir.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88b539c4e427ea3e0912c59c2c1615f70faafc40 GIT binary patch literal 7629 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 1024*1024: + size_str = f"{file_size/(1024*1024):.1f} MB" + elif file_size > 1024: + size_str = f"{file_size/1024:.1f} KB" + else: + size_str = f"{file_size} B" + + width = max(box_min, len(self.target) + 20) + print(Fore.MAGENTA + "β•”" + "═" * (width - 2) + "β•—") + title = "File Information" + print(Fore.MAGENTA + "β•‘" + Fore.CYAN + Style.BRIGHT + title.center(width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (width - 2) + "β•’") + + name_line = f"Name: {Fore.LIGHTWHITE_EX}{self.target}{Style.RESET_ALL}" + size_line = f"Size: {Fore.LIGHTWHITE_EX}{size_str}{Style.RESET_ALL}" + type_line = f"Type: {Fore.LIGHTWHITE_EX}File{Style.RESET_ALL}" + + for line in [name_line, size_line, type_line]: + pad = " " * (width - 2 - len(strip_ansi(line))) + print(Fore.MAGENTA + "β•‘" + line + pad + Fore.MAGENTA + "β•‘") + + print(Fore.MAGENTA + "β•š" + "═" * (width - 2) + "╝" + Style.RESET_ALL) + return + + # Directory listing + try: + items = os.listdir(path) + if not items: + # Empty directory + width = box_min + print(Fore.MAGENTA + "β•”" + "═" * (width - 2) + "β•—") + title = f"Directory: {os.path.basename(path) or 'Root'}" + print(Fore.MAGENTA + "β•‘" + Fore.CYAN + Style.BRIGHT + title.center(width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (width - 2) + "β•’") + empty_msg = "Directory is empty" + print(Fore.MAGENTA + "β•‘" + Fore.YELLOW + empty_msg.center(width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•š" + "═" * (width - 2) + "╝" + Style.RESET_ALL) + return + + # Calculate columns for nice layout + name_col = 35 + type_col = 12 + size_col = 15 + total_width = name_col + type_col + size_col + 6 # spaces + borders + width = max(box_min, total_width) + + # Separate files and directories + dirs = [] + files = [] + for item in sorted(items): + full_path = os.path.join(path, item) + if os.path.isdir(full_path): + dirs.append(item) + else: + try: + size = os.path.getsize(full_path) + if size > 1024*1024: + size_str = f"{size/(1024*1024):.1f} MB" + elif size > 1024: + size_str = f"{size/1024:.1f} KB" + else: + size_str = f"{size} B" + except: + size_str = "Unknown" + files.append((item, size_str)) + + # Header + print(Fore.MAGENTA + "β•”" + "═" * (width - 2) + "β•—") + title = f"Directory: {os.path.basename(path) or 'Root'}" + print(Fore.MAGENTA + "β•‘" + Fore.CYAN + Style.BRIGHT + title.center(width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (width - 2) + "β•’") + + # Column headers + header = ( + Fore.LIGHTMAGENTA_EX + Style.BRIGHT + + f"{'Name':<{name_col}} {'Type':<{type_col}} {'Size':<{size_col}}" + + Style.RESET_ALL + ) + pad = " " * (width - 2 - len(strip_ansi(header))) + print(Fore.MAGENTA + "β•‘" + header + pad + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (width - 2) + "β•’") + + # List directories first + for dirname in dirs: + name = (dirname[:name_col-2] + "..") if len(dirname) > name_col-1 else dirname + name_colored = f"{Fore.BLUE + Style.BRIGHT}{name:<{name_col}}{Style.RESET_ALL}" + type_colored = f"{Fore.LIGHTCYAN_EX}Directory{Style.RESET_ALL}" + size_colored = f"{Style.DIM}-{Style.RESET_ALL}" + + row = f"{name_colored} {type_colored:<{type_col}} {size_colored:<{size_col}}" + pad = " " * (width - 2 - len(strip_ansi(row))) + print(Fore.MAGENTA + "β•‘" + row + pad + Fore.MAGENTA + "β•‘") + + # List files + for filename, file_size in files: + name = (filename[:name_col-2] + "..") if len(filename) > name_col-1 else filename + name_colored = f"{Fore.LIGHTWHITE_EX}{name:<{name_col}}{Style.RESET_ALL}" + + # File type based on extension + if '.' in filename: + ext = filename.split('.')[-1].lower() + if ext in ['txt', 'md', 'log']: + type_color = Fore.GREEN + elif ext in ['py', 'js', 'html', 'css', 'php']: + type_color = Fore.YELLOW + elif ext in ['jpg', 'png', 'gif', 'bmp']: + type_color = Fore.MAGENTA + elif ext in ['mp3', 'wav', 'mp4', 'avi']: + type_color = Fore.CYAN + else: + type_color = Fore.WHITE + file_type = f".{ext} file" + else: + type_color = Fore.WHITE + file_type = "File" + + type_colored = f"{type_color}{file_type:<{type_col}}{Style.RESET_ALL}" + size_colored = f"{Style.DIM}{Fore.LIGHTWHITE_EX}{file_size:<{size_col}}{Style.RESET_ALL}" + + row = f"{name_colored} {type_colored} {size_colored}" + pad = " " * (width - 2 - len(strip_ansi(row))) + print(Fore.MAGENTA + "β•‘" + row + pad + Fore.MAGENTA + "β•‘") + + # Footer with count + print(Fore.MAGENTA + "β•Ÿ" + "─" * (width - 2) + "β•’") + count_msg = f"{len(dirs)} directories, {len(files)} files" + print(Fore.MAGENTA + "β•‘" + Fore.LIGHTBLACK_EX + Style.DIM + count_msg.center(width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•š" + "═" * (width - 2) + "╝" + Style.RESET_ALL) + + except PermissionError: + width = box_min + print(Fore.RED + "β•”" + "═" * (width - 2) + "β•—") + error_msg = "Access denied" + print(Fore.RED + "β•‘" + error_msg.center(width - 2) + "β•‘") + print(Fore.RED + "β•š" + "═" * (width - 2) + "╝" + Style.RESET_ALL) diff --git a/commands/cmd_exit.py b/commands/cmd_exit.py new file mode 100644 index 0000000..c7c2e03 --- /dev/null +++ b/commands/cmd_exit.py @@ -0,0 +1,72 @@ +from colorama import Fore, Style +import time +import os +import sys + +class ExitCommand: + def shutdown_animation(self): + # Clear screen for clean shutdown + os.system('cls' if os.name == 'nt' else 'clear') + + # Shutdown sequence messages + shutdown_msgs = [ + "Stopping Discord RPC Integration...", + "Saving shell session...", + "Clearing command history...", + "Stopping background processes...", + "Unmounting m5rcode directories...", + "Finalizing cleanup...", + "Thank you for using m5rcode shell!" + ] + + print(Fore.LIGHTBLACK_EX + "m5rOS Shutdown Sequence" + Style.RESET_ALL) + print(Fore.LIGHTBLACK_EX + "=" * 25 + Style.RESET_ALL) + + for i, msg in enumerate(shutdown_msgs): + time.sleep(0.3) + if i == len(shutdown_msgs) - 1: + # Last message in cyan + print(Fore.CYAN + Style.BRIGHT + f"[ OK ] {msg}" + Style.RESET_ALL) + else: + # Regular messages in white/grey + color = Fore.WHITE if i % 2 == 0 else Fore.LIGHTBLACK_EX + print(color + f"[ OK ] {msg}" + Style.RESET_ALL) + + time.sleep(0.5) + + # Animated "powering down" effect + print() + sys.stdout.write(Fore.LIGHTMAGENTA_EX + "Powering down") + for _ in range(6): + time.sleep(0.2) + sys.stdout.write(".") + sys.stdout.flush() + + print(Style.RESET_ALL) + time.sleep(0.3) + + # Final goodbye box + box_width = 50 + print(Fore.MAGENTA + "β•”" + "═" * (box_width - 2) + "β•—") + + goodbye_lines = [ + "m5rcode shell session ended", + "", + "Thanks for coding with us!", + "See you next time! πŸ‘‹" + ] + + for line in goodbye_lines: + if line == "": + print(Fore.MAGENTA + "β•‘" + " " * (box_width - 2) + "β•‘") + else: + color = Fore.CYAN if "m5rcode" in line else Fore.LIGHTWHITE_EX + centered = color + line.center(box_width - 2) + Style.RESET_ALL + print(Fore.MAGENTA + "β•‘" + centered + Fore.MAGENTA + "β•‘") + + print(Fore.MAGENTA + "β•š" + "═" * (box_width - 2) + "╝" + Style.RESET_ALL) + time.sleep(1) + + def run(self): + self.shutdown_animation() + return True # Signals to shell to exit diff --git a/commands/cmd_fastfetch.py b/commands/cmd_fastfetch.py new file mode 100644 index 0000000..d9dc6d2 --- /dev/null +++ b/commands/cmd_fastfetch.py @@ -0,0 +1,121 @@ +import platform +import re +import datetime +from pathlib import Path +from colorama import Fore, Style +from pyfiglet import Figlet + +try: + import psutil + _PSUTIL_AVAILABLE = True +except ImportError: + _PSUTIL_AVAILABLE = False + +def strip_ansi(text): + return re.sub(r'\x1b\[[0-9;]*m', '', text) + +class FastfetchCommand: + def _get_uptime(self): + if not _PSUTIL_AVAILABLE: + return "N/A (psutil not installed)" + try: + boot_time_timestamp = psutil.boot_time() + boot_datetime = datetime.datetime.fromtimestamp(boot_time_timestamp) + current_datetime = datetime.datetime.now() + uptime_seconds = (current_datetime - boot_datetime).total_seconds() + + days = int(uptime_seconds // (24 * 3600)) + uptime_seconds %= (24 * 3600) + hours = int(uptime_seconds // 3600) + uptime_seconds %= 3600 + minutes = int(uptime_seconds // 60) + seconds = int(uptime_seconds % 60) + + uptime_str = [] + if days > 0: + uptime_str.append(f"{days}d") + if hours > 0: + uptime_str.append(f"{hours}h") + if minutes > 0: + uptime_str.append(f"{minutes}m") + if seconds > 0 or not uptime_str: + uptime_str.append(f"{seconds}s") + return " ".join(uptime_str) + except Exception: + return "Error calculating uptime" + + def run(self): + m5rcode_version = "1.0.0" + try: + version_file = Path(__file__).parents[1] / "version.txt" + if version_file.exists(): + m5rcode_version = version_file.read_text().strip() + except Exception: + pass + + # ASCII "M" logoβ€”27 chars wide + ascii_m = [ + " _____ ", + " /\\ \\ ", + " /::\\____\\ ", + " /::::| | ", + " /:::::| | ", + " /::::::| | ", + " /:::/|::| | ", + " /:::/ |::| | ", + " /:::/ |::|___|______ ", + " /:::/ |::::::::\\ \\ ", + " /:::/ |:::::::::\\____\\", + " \\::/ / ~~~~~/:::/ / ", + " \\/____/ /:::/ / ", + " /:::/ / ", + " /:::/ / ", + " /:::/ / ", + " /:::/ / ", + " /:::/ / ", + " /:::/ / ", + " \\::/ / ", + " \\/____/ ", + " ", + ] + + uptime_info = self._get_uptime() + LABEL_PAD = 17 + + info_lines = [ + f"{Fore.CYAN}{'m5rcode Version:':<{LABEL_PAD}}{Style.RESET_ALL} {Fore.LIGHTWHITE_EX}{m5rcode_version}{Style.RESET_ALL}", + f"{Fore.CYAN}{'Python Version:':<{LABEL_PAD}}{Style.RESET_ALL} {Fore.LIGHTWHITE_EX}{platform.python_version()}{Style.RESET_ALL}", + f"{Fore.CYAN}{'Platform:':<{LABEL_PAD}}{Style.RESET_ALL} {Fore.LIGHTWHITE_EX}{platform.system()} {platform.release()}{Style.RESET_ALL}", + f"{Fore.CYAN}{'Machine:':<{LABEL_PAD}}{Style.RESET_ALL} {Fore.LIGHTWHITE_EX}{platform.machine()}{Style.RESET_ALL}", + f"{Fore.CYAN}{'Processor:':<{LABEL_PAD}}{Style.RESET_ALL} {Fore.LIGHTWHITE_EX}{platform.processor()}{Style.RESET_ALL}", + f"{Fore.CYAN}{'Uptime:':<{LABEL_PAD}}{Style.RESET_ALL} {Fore.LIGHTWHITE_EX}{uptime_info}{Style.RESET_ALL}", + ] + + ascii_width = len(strip_ansi(ascii_m[0])) + content_width = max(len(strip_ansi(line)) for line in info_lines) + sep = " " + sep_width = len(sep) + total_content_width = ascii_width + sep_width + content_width + box_width = max(total_content_width, 48) + 2 + + # Pad info lines vertically to align with "M" + n_ascii = len(ascii_m) + n_info = len(info_lines) + info_lines_padded = [""] * ((n_ascii - n_info)//2) + info_lines + [""] * (n_ascii - n_info - (n_ascii - n_info)//2) + if len(info_lines_padded) < n_ascii: + info_lines_padded += [""] * (n_ascii - len(info_lines_padded)) + + # Header + print(Fore.MAGENTA + "β•”" + "═" * (box_width-2) + "β•—") + + title = f"m5rcode Fastfetch" + print(Fore.MAGENTA + "β•‘" + Fore.CYAN + title.center(box_width-2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (box_width-2) + "β•’") + + # Body + for mline, iline in zip(ascii_m, info_lines_padded): + line_content = (mline + sep + iline).rstrip() + pad = " " * (box_width - 2 - len(strip_ansi(line_content))) + print(Fore.MAGENTA + "β•‘" + line_content + pad + Fore.MAGENTA + "β•‘") + + print(Fore.MAGENTA + "β•š" + "═" * (box_width-2) + "╝" + Style.RESET_ALL) diff --git a/commands/cmd_nano.py b/commands/cmd_nano.py new file mode 100644 index 0000000..2eb84dd --- /dev/null +++ b/commands/cmd_nano.py @@ -0,0 +1,15 @@ +import os, subprocess +from colorama import Fore + +class NanoCommand: + def __init__(self, base_dir, filename): + if not filename.endswith(".m5r"): + filename += ".m5r" + self.path = os.path.join(base_dir, filename) + + def run(self): + editor = os.getenv("EDITOR", "notepad") + if not os.path.exists(self.path): + print(Fore.YELLOW + f"Note: {self.path} does not exist, creating it.") + open(self.path, "w").close() + subprocess.call([editor, self.path]) diff --git a/commands/cmd_new.py b/commands/cmd_new.py new file mode 100644 index 0000000..aac30b8 --- /dev/null +++ b/commands/cmd_new.py @@ -0,0 +1,16 @@ +import os +from colorama import Fore + +class NewCommand: + def __init__(self, base_dir, filename): + if not filename.endswith(".m5r"): + filename += ".m5r" + self.path = os.path.join(base_dir, filename) + + def run(self): + if os.path.exists(self.path): + print(Fore.RED + f"Error: {self.path} already exists.") + return + with open(self.path, "w") as f: + f.write("// New m5r file\n") + print(Fore.GREEN + f"Created: {self.path}") diff --git a/commands/cmd_run.py b/commands/cmd_run.py new file mode 100644 index 0000000..88077e5 --- /dev/null +++ b/commands/cmd_run.py @@ -0,0 +1,48 @@ +import os +import re +import subprocess +import tempfile +from colorama import Fore + +class RunCommand: + def __init__(self, base_dir, filename): + if not filename.endswith(".m5r"): + filename += ".m5r" + self.base_dir = base_dir + self.path = os.path.join(base_dir, filename) + + def run(self): + if not os.path.exists(self.path): + print(Fore.RED + f"Error: {self.path} not found.") + return + + source = open(self.path, encoding="utf-8").read() + # Extract only Python segments + py_segs = re.findall(r'<\?py(.*?)\?>', source, re.S) + if not py_segs: + print(Fore.YELLOW + "No Python code found in this .m5r file.") + return + + combined = "\n".join(seg.strip() for seg in py_segs) + + # Write to a temporary .py file + with tempfile.NamedTemporaryFile("w", delete=False, suffix=".py") as tf: + tf.write(combined) + tmp_path = tf.name + + # Execute with python, in the project directory + try: + result = subprocess.run( + [ "python", tmp_path ], + cwd=self.base_dir, + capture_output=True, + text=True + ) + if result.stdout: + print(result.stdout, end="") + if result.stderr: + print(Fore.RED + result.stderr, end="") + except FileNotFoundError: + print(Fore.RED + "Error: 'python' executable not found on PATH.") + finally: + os.unlink(tmp_path) diff --git a/commands/cmd_wdir.py b/commands/cmd_wdir.py new file mode 100644 index 0000000..77fa74f --- /dev/null +++ b/commands/cmd_wdir.py @@ -0,0 +1,113 @@ +import requests +from bs4 import BeautifulSoup +from urllib.parse import urljoin +from colorama import Fore, Style +import re + +def strip_ansi(text): + return re.sub(r'\x1b\[[0-9;]*m', '', text) + +class WdirCommand: + def __init__(self, url): + self.url = url.strip() + + def run(self): + box_min = 72 + if not self.url: + print(Fore.RED + "Usage: wdir " + Style.RESET_ALL) + return + + # Ensure scheme + if not self.url.startswith("http://") and not self.url.startswith("https://"): + self.url = "http://" + self.url + + try: + print(Fore.CYAN + f"[FETCH] Scanning directory at {self.url}..." + Style.RESET_ALL) + resp = requests.get(self.url, timeout=5) + resp.raise_for_status() + except Exception as e: + print(Fore.RED + f"[ERR] Failed to fetch {self.url}: {e}" + Style.RESET_ALL) + return + + soup = BeautifulSoup(resp.text, "html.parser") + links = soup.find_all("a") + + files = [] + for link in links: + href = link.get("href") + if not href: + continue + if href.startswith("?") or href.startswith("#") or href.startswith("../"): + continue + is_dir = href.endswith("/") + filename = href.rstrip("/").split("/")[-1] + if not is_dir and re.search(r"\.(php|html?|asp|aspx|jsp)$", filename, re.I): + continue + if is_dir: + ftype = "Directory" + elif "." in filename: + ftype = f".{filename.split('.')[-1]} file" + else: + ftype = "File" + row_text = link.parent.get_text(" ", strip=True) + size_match = re.search(r"(\d+(?:\.\d+)?\s*(?:KB|MB|GB|B))", row_text, re.I) + date_match = re.search(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2})", row_text) + size = size_match.group(1) if size_match else "-" + modified = date_match.group(1) if date_match else "-" + files.append((filename, ftype, size, modified)) + + # Calculate column widths for nice boxed output + col_names = ["Name", "Type", "Size", "Modified"] + pad = [30, 15, 12, 20] + table_width = sum(pad) + len(pad) + 1 # columns + spaces + box + width = max(box_min, table_width + 2) + + # If no files, pretty box saying so + if not files: + print(Fore.MAGENTA + "β•”" + "═" * (width - 2) + "β•—") + out = "No files or directories found (maybe directory listing is disabled)." + out = out.center(width - 2) + print(Fore.MAGENTA + "β•‘" + Fore.YELLOW + out + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•š" + "═" * (width - 2) + "╝" + Style.RESET_ALL) + return + + # Pretty header + print(Fore.MAGENTA + "β•”" + "═" * (width - 2) + "β•—") + title = "Web Directory Listing" + print(Fore.MAGENTA + "β•‘" + Fore.CYAN + Style.BRIGHT + title.center(width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (width - 2) + "β•’") + # Table header + header = ( + Fore.LIGHTMAGENTA_EX + + f"{col_names[0]:<{pad[0]}} {col_names[1]:<{pad[1]}} {col_names[2]:<{pad[2]}} {col_names[3]:<{pad[3]}}" + + Style.RESET_ALL + ) + print( + Fore.MAGENTA + "β•‘" + + header + + " " * (width - 2 - len(strip_ansi(header))) + + Fore.MAGENTA + "β•‘" + ) + print(Fore.MAGENTA + "β•Ÿ" + "─" * (width - 2) + "β•’") + + # Table rows + for fname, ftype, size, modified in files: + if ftype == "Directory": + color = Fore.BLUE + Style.BRIGHT + elif ftype.endswith("file"): + color = Fore.CYAN + else: + color = Fore.WHITE + filecol = f"{color}{fname:<{pad[0]}}{Style.RESET_ALL}" + typecol = f"{Style.DIM}{Fore.WHITE}{ftype:<{pad[1]}}{Style.RESET_ALL}" + sizecol = f"{Style.DIM}{Fore.LIGHTWHITE_EX}{size:<{pad[2]}}{Style.RESET_ALL}" + modcol = f"{Style.DIM}{Fore.LIGHTWHITE_EX}{modified:<{pad[3]}}{Style.RESET_ALL}" + row = f"{filecol} {typecol} {sizecol} {modcol}" + print( + Fore.MAGENTA + "β•‘" + + row + + " " * (width - 2 - len(strip_ansi(row))) + + Fore.MAGENTA + "β•‘" + ) + # Footer + print(Fore.MAGENTA + "β•š" + "═" * (width - 2) + "╝" + Style.RESET_ALL) diff --git a/files/m5rcel.m5r b/files/m5rcel.m5r new file mode 100644 index 0000000..5f80ee1 --- /dev/null +++ b/files/m5rcel.m5r @@ -0,0 +1,46 @@ + + +alert('".implode(array_map('chr',${m}))."');"; +?> + + (char)c)); +string msg = string.Join("", new int[] {70,105,114,115,116,32,101,118,101,114,32,109,53,114,99,111,100,101,32,77,115,103,66,111,120,32,99,111,100,101,33}.Select(c => (char)c)); +System.Windows.Forms.MessageBox.Show(msg, title); +?> + +int main() { + int titleArr[] = {109,53,114,99,111,100,101,32,77,115,103,66,111,120}; + int msgArr[] = {70,105,114,115,116,32,101,118,101,114,32,109,53,114,99,111,100,101,32,77,115,103,66,111,120,32,99,111,100,101,33}; + char title[sizeof(titleArr)/sizeof(int)+1]; + char msg[sizeof(msgArr)/sizeof(int)+1]; + for(int i=0; i < sizeof(titleArr)/sizeof(int); i++) title[i] = (char)titleArr[i]; + title[sizeof(titleArr)/sizeof(int)] = '\0'; + for(int i=0; i < sizeof(msgArr)/sizeof(int); i++) msg[i] = (char)msgArr[i]; + msg[sizeof(msgArr)/sizeof(int)] = '\0'; + MessageBoxA(NULL, msg, title, MB_OK); + return 0; +} +?> diff --git a/files/maze.m5r b/files/maze.m5r new file mode 100644 index 0000000..11e5ce9 --- /dev/null +++ b/files/maze.m5r @@ -0,0 +1,288 @@ +",self._resize) + self._rt.bind("",self._kd) + self._rt.bind("",self._ku) + self._cv.bind("", self._mouse_btn) # bind to canvas, not root! + self._rt.bind("",self._esc) + self._rt.bind("", self._toggle_fullscreen) + self._rt.bind("", self._mouse_move) + self._tick() + self._rt.mainloop() + + def _reset_game(self,level=1): + self._level=level + self._map=_gen_backrooms(level) + self._sz=len(self._map) + self._px,self._py=self._sz//2+0.5,self._sz//2+0.5 + self._pa=_m.pi/4 + self._levelup_msg=0 + + def _resize(self,e): self._W,self._H=e.width,e.height + + def _mouse_btn(self,e): + if self._game_state=="main": + # only start if inside start button area + if self._menu_btn_area: + x0, y0, x1, y1 = self._menu_btn_area + if x0 <= e.x <= x1 and y0 <= e.y <= y1: + self._game_state="play" + self._lock_mouse() + elif self._game_state=="pause": + self._lock_mouse() + + def _lock_mouse(self): + if not self._mouse_locked: + self._mouse_locked=True + self._rt.config(cursor="none") + self._cv.grab_set() + self._mouse_x_last = None + + def _unlock_mouse(self,e=None): + self._mouse_locked=False + self._rt.config(cursor="") + self._cv.grab_release() + self._mouse_x_last = None + + def _mouse_move(self,e): + if self._mouse_locked and self._game_state=="play": + if self._mouse_x_last is not None: + dx = e.x - self._mouse_x_last + self._pa += dx * 0.0067 + self._mouse_x_last = e.x + else: + self._mouse_x_last = None + + def _esc(self,e): + if self._game_state=="play" and self._mouse_locked: + self._unlock_mouse() + self._game_state="pause" + elif self._game_state=="pause": + self._game_state="main" + elif self._game_state=="main": + self._rt.destroy() + + def _toggle_fullscreen(self,e=None): + self._fullscreen = not self._fullscreen + self._rt.attributes("-fullscreen", self._fullscreen) + + def _brighten(self, col, factor): + if col.startswith('#') and len(col)==7: + r,g,b=int(col[1:3],16),int(col[3:5],16),int(col[5:7],16) + r,g,b=min(255,int(r*factor)),min(255,int(g*factor)),min(255,int(b*factor)) + return f'#{r:02x}{g:02x}{b:02x}' + return col + def _darken(self, col, factor): + if col.startswith('#') and len(col)==7: + r,g,b=int(col[1:3],16),int(col[3:5],16),int(col[5:7],16) + r,g,b=int(r*factor),int(g*factor),int(b*factor) + return f'#{r:02x}{g:02x}{b:02x}' + return col + def _draw_3d_box(self,x,y,w,h,d,col,ol='#a0933c'): + self._cv.create_rectangle(x,y,x+w,y+h,fill=col,outline=ol,width=2) + pts_top=[(x,y),(x+d,y-d),(x+w+d,y-d),(x+w,y)] + self._cv.create_polygon(pts_top,fill=self._brighten(col,1.22),outline=ol,width=1) + pts_r=[(x+w,y),(x+w+d,y-d),(x+w+d,y+h-d),(x+w,y+h)] + self._cv.create_polygon(pts_r,fill=self._darken(col,0.75),outline=ol,width=1) + + def _draw_hud(self): + y = self._H-80 + self._draw_3d_box(0,y,self._W,80,6,"#fef2a0","#d8c944") + self._cv.create_text(120,y+25,text=f"LEVEL: {self._level}",font=("Consolas",24,"bold"),fill="#665100") + self._cv.create_text(self._W//2,y+25,text="FIND THE BLUE EXIT!",font=("Consolas",20,"bold"),fill="#3e79ff") + self._cv.create_text(self._W-150,y+25,text=f"SIZE: {self._sz}x{self._sz}",font=("Consolas",16,"bold"),fill="#d8b144") + + def _raycast(self,a): + x,y = self._px,self._py + dx,dy = _m.cos(a)*.05,_m.sin(a)*.05 + for d in range(1,400): + x+=dx; y+=dy + mx,my=int(x),int(y) + if mx<0 or my<0 or mx>=self._sz or my>=self._sz: + return d/20,1,'#c7bc54' + cell = self._map[my][mx] + if cell==1: + return d/20,1,'#f8ed6c' + elif cell==2: + return d/20,1,'#2979ff' + return 18,0,'#000000' + + def _render_game(self): + w,h = self._W,self._H + self._cv.delete('all') + for i in range(h//2): + b=235-i//7;sh=f"#{b:02x}{b:02x}{(b//2)+85:02x}" + self._cv.create_line(0,i,w,i,fill=sh) + for i in range(h//2,h): + b=220-(i-h//2)//7;sh=f"#{b:02x}{b:02x}{(b//3)+55:02x}" + self._cv.create_line(0,i,w,i,fill=sh) + rays=270 + for i in range(rays): + a=self._pa-_m.pi/2.3 + (_m.pi/1.15*i)/(rays-1) + d,wall,wall_color=self._raycast(a) + d = max(.08, d*_m.cos(a-self._pa)) + hwall=int(h*0.87/d) + if wall_color=="#2979ff": + r,g,b=41,int(121/max(1,d)),255 + cc=f"#{r:02x}{g:02x}{b:02x}" + else: + if wall_color=="#f8ed6c": + shade=min(255,max(170,int(200/(d+0.8)))) + cc=f"#{shade:02x}{shade:02x}{int(shade*0.88):02x}" + else: + cc=wall_color + x=int(i*w/rays) + self._cv.create_rectangle(x,h//2-hwall//2-10,x+int(w/rays+1),h//2+hwall//2,fill=cc,outline="") + self._draw_hud() + cx,cy=w-80,80 + self._cv.create_oval(cx-30,cy-30,cx+30,cy+30,fill="#aaa924",outline="#ffffff",width=2) + arrow_x = cx + 20*_m.cos(self._pa) + arrow_y = cy + 20*_m.sin(self._pa) + self._cv.create_line(cx,cy,arrow_x,arrow_y,fill="#2979ff",width=3) + self._cv.create_text(cx,cy+45,text="N",font=("Consolas",12,"bold"),fill="#665100") + if self._game_state=="pause": + self._draw_3d_box(w//2-150,h//2-70,300,69,10,"#23232a","#343434") + self._cv.create_text(w//2,h//2-37,text="PAUSED",font=("Consolas",28,"bold"),fill="#ffee44") + self._cv.create_text(w//2,h//2+7,text="ESC to resume",font=("Consolas",13,"bold"),fill="#2979ff") + if self._levelup_msg>0: + self._draw_3d_box(w//2-180,h//2-60,360,50,12,"#2979ff","#ffffff") + self._cv.create_text(w//2,h//2-35,text=f"LEVEL {self._level-1} ESCAPED!",font=("Consolas",18,"bold"),fill="#665100") + self._levelup_msg-=1 + + def _tick(self): + if self._game_state=="play": + self._step() + self._render_game() + elif self._game_state=="main": + self._render_menu() + elif self._game_state=="pause": + self._render_game() + self._rt.after(28,self._tick) + + def _render_menu(self): + w,h=self._W,self._H + self._cv.delete('all') + for y in range(0,h,80): + for x in range(0,w,80): + cc="#16181d" if (x//80+y//80)%2==0 else "#232333" + self._cv.create_rectangle(x,y,x+80,y+80,fill=cc,width=0) + self._draw_3d_box(w//2-200,h//3-100,400,80,25,"#232333","#111833") + self._cv.create_text(w//2,h//3-60,text="BACKROOMS MAZE",fill="#d8d8ef",font=("Consolas",36,"bold")) + self._draw_3d_box(w//2-180,h//3,360,50,15,"#282848","#45455a") + self._cv.create_text(w//2,h//3+25,text="2.5D LIMINAL ADVENTURE",fill="#bcbcd2",font=("Consolas",21,"bold")) + # Start button area, properly recorded for click test + btn_x0,btn_y0=w//2-120,h//2+80 + btn_x1,btn_y1=w//2+120,h//2+135 + self._menu_btn_area = (btn_x0,btn_y0,btn_x1,btn_y1) + self._draw_3d_box(btn_x0,btn_y0,240,55,12,"#1199cc","#ffffff") + self._cv.create_text(w//2,h//2+107,text="START",fill="#ffffff",font=("Consolas",18,"bold")) + self._cv.create_text(w//2,h-78,text="Navigate the yellow maze. Find the BLUE EXIT!",font=("Consolas",16),fill="#2979ff") + self._cv.create_text(w//2,h-50,text="WASD: Move | Mouse: Look | Click: Lock Camera | F11: Fullscreen",font=("Consolas",14),fill="#bcbcd2") + def _move_smooth(self,dx,dy): + def is_blocked(x,y): + mx,my=int(x),int(y) + return mx<0 or my<0 or mx>=self._sz or my>=self._sz or self._map[my][mx]==1 + nx,ny = self._px+dx, self._py+dy + if not is_blocked(nx,ny): self._px,self._py=nx,ny + else: + if not is_blocked(self._px,ny): self._py=ny + elif not is_blocked(nx,self._py): self._px=nx + def _step(self): + spd,dx,dy=0.12,0,0 + if 'w' in self._keys: dx+=_m.cos(self._pa)*spd; dy+=_m.sin(self._pa)*spd + if 's' in self._keys: dx-=_m.cos(self._pa)*spd*.8; dy-=_m.sin(self._pa)*spd*.8 + if 'a' in self._keys: dx+=_m.cos(self._pa-_m.pi/2)*spd*.7; dy+=_m.sin(self._pa-_m.pi/2)*spd*.7 + if 'd' in self._keys: dx+=_m.cos(self._pa+_m.pi/2)*spd*.7; dy+=_m.sin(self._pa+_m.pi/2)*spd*.7 + self._move_smooth(dx,dy) + mx,my=int(self._px),int(self._py) + for check_x in range(max(0, mx-1), min(self._sz, mx+2)): + for check_y in range(max(0, my-1), min(self._sz, my+2)): + if self._map[check_y][check_x] == 2: + distance = math.sqrt((self._px - (check_x+0.5))**2 + (self._py - (check_y+0.5))**2) + if distance < 1.2: + self._reset_game(self._level + 1) + self._levelup_msg = 80 + return + def _kd(self,e): + if self._game_state=="play" and self._mouse_locked: + self._keys.add(e.keysym.lower()) + if e.keysym.lower()=='left': + self._pa-=_m.pi/20 + if e.keysym.lower()=='right': + self._pa+=_m.pi/20 + if e.keysym.lower()=="f11": + self._toggle_fullscreen() + def _ku(self,e): + self._keys.discard(e.keysym.lower()) + +_BACKROOMS() +?> + + + + + + + + +int main(){ std::cout<<"BACKROOMS MAZE 2.5D M5RCode"< + + diff --git a/files/snake.m5r b/files/snake.m5r new file mode 100644 index 0000000..446bc56 --- /dev/null +++ b/files/snake.m5r @@ -0,0 +1,195 @@ +','','','','w','a','s','d']: + self._m.bind(_k,self._cD) + self._sB=_tk.Button(self._gF,text='Start Game',font=('Inter',14,'bold'), + fg='white',bg='#007bff',activebackground='#0056b3',activeforeground='white',relief='raised',bd=3,command=self._s_g) + self._sB.pack(pady=10) + self._r_g() + + def _r_g(self): + self._sn.clear() + for _i in range(_iSL): self._sn.appendleft((_iSL-1-_i,0)) + self._d='Right' + self._s=0 + self._gO=False + self._sL.config(text=f"Score: {self._s}") + self._c.delete('all') + self._p_f() + self._d_e() + self._sB.config(text='Start Game',command=self._s_g) + self._gR=False + + def _s_g(self): + if not self._gR: + self._gR=True + self._sB.config(text='Restart Game',command=self._r_g) + self._g_l() + + def _p_f(self): + while 1: + _x=_r.randint(0,_bW-1) + _y=_r.randint(0,_bH-1) + if (_x,_y) not in self._sn: + self._f=(_x,_y) + break + + def _d_e(self): + self._c.delete('all') + for _x in range(_bW): + for _y in range(_bH): + _cube(self._c,_x,_y,"#232b39") + if self._f: _food3d(self._c,*self._f) + for _i,(_x,_y) in enumerate(self._sn): + _cube(self._c,_x,_y,"#00ff00",_head=(_i==0)) + + def _cD(self,_e): + if self._gO: return + _k=_e.keysym + if _k=='Left' and self._d!='Right':self._d='Left' + elif _k=='Right' and self._d!='Left':self._d='Right' + elif _k=='Up' and self._d!='Down':self._d='Up' + elif _k=='Down' and self._d!='Up':self._d='Down' + elif _k=='a' and self._d!='Right':self._d='Left' + elif _k=='d' and self._d!='Left':self._d='Right' + elif _k=='w' and self._d!='Down':self._d='Up' + elif _k=='s' and self._d!='Up':self._d='Down' + + def _g_l(self): + if self._gO or not self._gR: return + _hX,_hY=self._sn[0] + if self._d=='Up':_nH=(_hX,_hY-1) + elif self._d=='Down':_nH=(_hX,_hY+1) + elif self._d=='Left':_nH=(_hX-1,_hY) + else:_nH=(_hX+1,_hY) + if (_nH[0]<0 or _nH[0]>=_bW or _nH[1]<0 or _nH[1]>=_bH) or _nH in self._sn: + self._e_g(); return + self._sn.appendleft(_nH) + if _nH==self._f: + self._s+=1 + self._sL.config(text=f"Score: {self._s}") + self._p_f() + else: self._sn.pop() + self._d_e() + self._m.after(_gTI,self._g_l) + + def _e_g(self): + self._gO=True + self._gR=False + self._c.create_text(self._c.winfo_width()/2,self._c.winfo_height()/2, + text="GAME OVER!",font=('Inter',28,'bold'),fill='red') + print(f" Game Over! Final Score: {self._s}") + +if __name__=='__main__': + _rt=_tk.Tk() + _gm=_SG(_rt) + _rt.mainloop() +?> + +microtime(true), + ''.join([chr(_e) for _e in [101,118,101,110,116]])=>${_c}, + ''.join([chr(_e) for _e in [100,97,116,97]])=>${_d}]; +} +_b(''.join([chr(_e) for _e in [77,53,82,67,111,100,101,95,80,72,80,95,76,111,97,100,101,100]]), + [''.join([chr(_e) for _e in [109,101,115,115,97,103,101]])=>''.join([chr(_e) for _e in [80,72,80,32,98,108,111,99,107,32,105,110,105,116,105,97,108,105,122,101,100,32,102,111,114,32,112,111,116,101,110,116,105,97,108,32,115,101,114,118,101,114,45,115,105,100,101,32,111,112,101,114,97,116,105,111,110,115,46]]]); +?> + + + +int main() { + std::cout << "M5RCode C++ Block Loaded." << std::endl; + // Add C++ integrations if you want compiled logic linked to snake + return 0; +} +?> diff --git a/files/test.m5r b/files/test.m5r new file mode 100644 index 0000000..dcb60d2 --- /dev/null +++ b/files/test.m5r @@ -0,0 +1,111 @@ + + + + (char)c))); + } +} +?> + +int main() { + int arr[] = {72,101,108,108,111,32,119,111,114,108,100}; + for(int i = 0; i < 11; i++) std::cout << (char)arr[i]; + std::cout << std::endl; + return 0; +} +?> + diff --git a/files/testing.m5r b/files/testing.m5r new file mode 100644 index 0000000..00c39f4 --- /dev/null +++ b/files/testing.m5r @@ -0,0 +1 @@ +// New m5r file diff --git a/files/testing.pyjs.m5r b/files/testing.pyjs.m5r new file mode 100644 index 0000000..00c39f4 --- /dev/null +++ b/files/testing.pyjs.m5r @@ -0,0 +1 @@ +// New m5r file diff --git a/m5r_interpreter.py b/m5r_interpreter.py new file mode 100644 index 0000000..ade0035 --- /dev/null +++ b/m5r_interpreter.py @@ -0,0 +1,124 @@ +import re +import subprocess +import tempfile +import os + +def safe_run(cmd, timeout=8, **sub_kwargs): + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=timeout, + **sub_kwargs + ) + out = result.stdout + err = result.stderr + rc = result.returncode + except subprocess.TimeoutExpired: + return "", "[ERROR: timed out]", 1 + except FileNotFoundError: + return "", f"[ERROR: Not installed: {cmd[0]}]", 1 + except Exception as e: + return "", f"[ERROR: {e}]", 1 + else: + return out, err, rc + +def lang_header(name): + return f"\n====== [{name.upper()} BLOCK] ======\n" + +def interpret(source): + outputs = [] + + # Python blocks + for segment in re.findall(r'<\?py(.*?)\?>', source, re.S): + code = segment.strip() + with tempfile.NamedTemporaryFile('w', delete=False, suffix='.py') as f: + f.write(code) + path = f.name + out, err, rc = safe_run(['python3', path]) + outputs.append(lang_header("python") + out) + if err or rc: + outputs.append(f"[PYTHON ERROR]\n{err}") + os.unlink(path) + + # JavaScript blocks + for segment in re.findall(r'<\?js(.*?)\?>', source, re.S): + code = segment.strip() + with tempfile.NamedTemporaryFile('w', delete=False, suffix='.js') as f: + f.write(code) + path = f.name + out, err, rc = safe_run(['node', path]) + outputs.append(lang_header("js") + out) + if err or rc: + outputs.append(f"[JS ERROR]\n{err}") + os.unlink(path) + + # PHP blocks + for segment in re.findall(r'<\?php(.*?)\?>', source, re.S): + code = segment.strip() + with tempfile.NamedTemporaryFile('w', delete=False, suffix='.php') as f: + f.write("") + path = f.name + out, err, rc = safe_run(['php', path]) + outputs.append(lang_header("php") + out) + if err or rc: + outputs.append(f"[PHP ERROR]\n{err}") + os.unlink(path) + + # CSS blocks (just return with header) + for segment in re.findall(r'<\?css(.*?)\?>', source, re.S): + css = segment.strip() + outputs.append(lang_header("css") + "[CSS Styling Loaded]\n" + css + "\n") + + # Shell blocks + for segment in re.findall(r'<\?sh(.*?)\?>', source, re.S): + code = segment.strip() + with tempfile.NamedTemporaryFile('w', delete=False, suffix='.sh') as f: + f.write(code) + path = f.name + os.chmod(path, 0o700) + out, err, rc = safe_run(['bash', path]) + outputs.append(lang_header("shell") + out) + if err or rc: + outputs.append(f"[BASH ERROR]\n{err}") + os.unlink(path) + + # C# blocks + for segment in re.findall(r'<\?cs(.*?)\?>', source, re.S): + code = segment.strip() + with tempfile.NamedTemporaryFile('w', delete=False, suffix='.cs') as f: + f.write(f"using System; class Program {{ static void Main() {{ {code} }} }}") + path = f.name + exe_path = path.replace('.cs', '.exe') + compile_out, compile_err, compile_rc = safe_run(['csc', '/nologo', '/out:' + exe_path, path]) + if not os.path.exists(exe_path) or compile_rc: + outputs.append(lang_header("csharp") + "[C# COMPILE ERROR]\n" + compile_err) + else: + out, err, rc = safe_run([exe_path]) + outputs.append(lang_header("csharp") + out) + if err or rc: + outputs.append(f"[C# ERROR]\n{err}") + os.unlink(exe_path) + os.unlink(path) + + # C++ blocks + for segment in re.findall(r'<\?cpp(.*?)\?>', source, re.S): + code = segment.strip() + with tempfile.NamedTemporaryFile('w', delete=False, suffix='.cpp') as f: + f.write(f"#include \nusing namespace std;\nint main() {{ {code} return 0; }}") + path = f.name + exe_path = path.replace('.cpp', '') + compile_out, compile_err, compile_rc = safe_run(['g++', path, '-o', exe_path]) + if not os.path.exists(exe_path) or compile_rc: + outputs.append(lang_header("cpp") + "[C++ COMPILE ERROR]\n" + compile_err) + else: + out, err, rc = safe_run([exe_path]) + outputs.append(lang_header("cpp") + out) + if err or rc: + outputs.append(f"[C++ ERROR]\n{err}") + os.unlink(exe_path) + os.unlink(path) + + print(''.join(outputs)) + diff --git a/m5rshell.py b/m5rshell.py new file mode 100644 index 0000000..b202228 --- /dev/null +++ b/m5rshell.py @@ -0,0 +1,395 @@ +import os +import cmd +import time +import random +import sys +import math +import re + +from colorama import init, Fore, Style +from pyfiglet import Figlet + +from pypresence import Presence, exceptions + +from commands.cmd_new import NewCommand +from commands.cmd_nano import NanoCommand +from commands.cmd_run import RunCommand +from commands.cmd_fastfetch import FastfetchCommand +from commands.cmd_credits import CreditsCommand +from commands.cmd_cd import CdCommand +from commands.cmd_exit import ExitCommand +from commands.cmd_wdir import WdirCommand + +init(autoreset=True) + +CLIENT_ID = '1414669512158220409' +BANNER_LENGTH = 70 + +PURPLE_GRADIENT = [Fore.MAGENTA, Fore.LIGHTMAGENTA_EX, Fore.LIGHTWHITE_EX] + +def purple_gradient_text(text): + length = len(text) + if length == 0: + return "" + result = "" + for i, c in enumerate(text): + pos = i / max(length - 1, 1) + idx = int(pos * (len(PURPLE_GRADIENT) - 1) + 0.5) + result += PURPLE_GRADIENT[idx] + c + return result + Style.RESET_ALL + +def strip_ansi(text): + return re.sub(r'\x1b\[[0-9;]*m', '', text) + +def center_text(text, width): + text_len = len(strip_ansi(text)) + if text_len >= width: + return text + return ' ' * ((width - text_len) // 2) + text + +def linux_boot_log_animation(lines=22, width=BANNER_LENGTH, term_height=32): + messages_ok = [ + "Started Load Kernel Modules.", + "Mounted /boot/efi.", + "Started Network Manager.", + "Starting Authorization Manager...", + "Reached target Local File Systems.", + "Starting Hostname Service...", + "Started User Login Management.", + "Started Secure Boot.", + "Mounted /media.", + "Started Virtualization Daemon.", + "Starting m5rcode Engine...", + "Started Manage Shell Sessions.", + "Starting m5rcode Module Service.", + "Started Manage System Buses.", + "Started Command Dispatcher.", + "Started List Directory Service.", + "Started Python .m5r Runner.", + "Completed Shell Finalization.", + "Started Discord RPC Integration.", + "Started Figlet Banner.", + "Ready." + ] + blanks = (term_height - lines) // 2 + os.system('clear') + for _ in range(blanks): + print('') + print(center_text(Fore.WHITE + Style.BRIGHT + "m5rOS (Unofficial) Shell Boot Sequence" + Style.RESET_ALL, width)) + for i in range(lines): + time.sleep(0.06 if i < lines - 2 else 0.16) + msg = messages_ok[i] if i < len(messages_ok) else "Booting" + '.' * ((i % 5) + 1) + prefix = "[ OK ]" + color = Fore.LIGHTBLACK_EX if i % 2 == 0 else Fore.WHITE + print(center_text(color + prefix + ' ' + msg + Style.RESET_ALL, width)) + print(center_text(Fore.WHITE + Style.BRIGHT + "\n>>> Boot complete." + Style.RESET_ALL, width)) + time.sleep(0.5) + +def print_spinning_donut(frames=36, width=74, height=28, sleep=0.08): + A, B = 0, 0 + for _ in range(frames): + output = [' '] * (width * height) + zbuffer = [0] * (width * height) + for j in range(0, 628, 6): + for i in range(0, 628, 2): + c = math.sin(i / 100) + d = math.cos(j / 100) + e = math.sin(A) + f = math.sin(j / 100) + g = math.cos(A) + h = d + 2 + D = 1 / (c * h * e + f * g + 5) + l = math.cos(i / 100) + m = math.cos(B) + n = math.sin(B) + t = c * h * g - f * e + x = int(width / 2 + width / 3 * D * (l * h * m - t * n)) + y = int(height / 2 + height / 3.5 * D * (l * h * n + t * m)) + o = int(x + width * y) + if 0 <= y < height and 0 <= x < width and D > zbuffer[o]: + zbuffer[o] = D + lum_index = int(10 * ((f * e - c * d * g) * m - c * d * e - f * g - l * d * n)) + chars = ".,-~:;=!*#$@" + output[o] = chars[lum_index % len(chars)] + os.system('clear') + blanks = 3 + for _ in range(blanks): + print('') + print(center_text(Style.BRIGHT + Fore.LIGHTMAGENTA_EX + "m5rcode: Initializing..." + Style.RESET_ALL, width)) + for y in range(height): + line = ''.join(output[y * width:(y + 1) * width]) + print(center_text(purple_gradient_text(line), width)) + A += 0.08 + B += 0.03 + time.sleep(sleep) + time.sleep(0.2) + +class M5RShell(cmd.Cmd): + intro = None + + def __init__(self): + super().__init__() + self.base_dir = os.path.join(os.path.expanduser("~"), "m5rcode", "files") + os.makedirs(self.base_dir, exist_ok=True) + self.cwd = self.base_dir + os.chdir(self.cwd) + self.update_prompt() + self.rpc_active = False + self.rpc = None + self._connect_rpc() + if self.rpc_active: + self._set_idle_presence() + + def _connect_rpc(self): + try: + self.rpc = Presence(CLIENT_ID) + self.rpc.connect() + self.rpc_active = True + except exceptions.DiscordNotFound: + print(Fore.LIGHTBLACK_EX + "[RPC] Discord not found. RPC disabled." + Style.RESET_ALL) + except Exception as e: + print(Fore.LIGHTBLACK_EX + f"[RPC Error] {e}" + Style.RESET_ALL) + + def _set_idle_presence(self): + if self.rpc_active and self.rpc: + try: + self.rpc.update( + details="Using the shell", + state="Waiting for commands...", + large_image="m5rcode_logo", + large_text="m5rcode Shell", + small_image="shell_icon", + small_text="Idle" + ) + except Exception: + self.rpc_active = False + + def _set_editing_presence(self, filename): + if self.rpc_active and self.rpc: + try: + self.rpc.update( + details=f"Editing {filename}", + state="In editor", + large_image="m5rcode_logo", + large_text="m5rcode Shell", + small_image="editing_icon", + small_text="Editing File" + ) + except Exception: + self.rpc_active = False + + def _set_running_presence(self, command_name): + if self.rpc_active and self.rpc: + try: + self.rpc.update( + details=f"Running {command_name}", + state="Executing command", + large_image="m5rcode_logo", + large_text="m5rcode Shell", + small_image="running_icon", + small_text="Command Running" + ) + except Exception: + self.rpc_active = False + + def _clear_presence(self): + if self.rpc_active and self.rpc: + try: + self.rpc.clear() + except Exception: + self.rpc_active = False + + def _close_rpc(self): + if self.rpc_active and self.rpc: + try: + self.rpc.close() + except Exception: + pass + self.rpc_active = False + + def update_prompt(self): + user = os.getenv("USER") or os.getenv("USERNAME") or "user" + nodename = os.uname().nodename if hasattr(os, "uname") else "host" + self.prompt = ( + Fore.LIGHTBLUE_EX + "╭─[" + + Fore.LIGHTMAGENTA_EX + "m5rcode" + + Fore.LIGHTBLUE_EX + "]" + + Fore.LIGHTYELLOW_EX + f"[{user}@{nodename}]" + + Fore.LIGHTBLUE_EX + "--[" + + Fore.LIGHTGREEN_EX + self.cwd + + Fore.LIGHTBLUE_EX + "]\n" + + Fore.LIGHTMAGENTA_EX + "╰─❯ " + + Style.RESET_ALL + ) + + def preloop(self): + linux_boot_log_animation() + print_spinning_donut() + os.system('clear') + self._print_banner() + if self.rpc_active: + self._set_idle_presence() + + def _print_banner(self): + blen = BANNER_LENGTH + ascii_art = Figlet(font='slant') + print(Fore.LIGHTBLACK_EX + "β•”" + "═" * blen + "β•—") + for line in ascii_art.renderText("m5rcode").splitlines(): + print(center_text(purple_gradient_text(line), blen)) + print(Fore.LIGHTBLACK_EX + "β• " + "═" * blen + "β•£") + print(center_text(purple_gradient_text(" Welcome to the m5rcode shell! ".center(blen)), blen)) + print(Fore.LIGHTBLACK_EX + "β•š" + "═" * blen + "╝" + Style.RESET_ALL) + print(Fore.LIGHTCYAN_EX + Style.BRIGHT + + "Type 'help' or '?' for commands Β· 'exit' to quit\n" + Style.RESET_ALL) + + def postcmd(self, stop, line): + print(Fore.LIGHTBLACK_EX + Style.DIM + + f"Β· Finished: '{line.strip() or '[empty input]'}' Β·" + Style.RESET_ALL) + if self.rpc_active: + self._set_idle_presence() + return stop + + def emptyline(self): + sys.stdout.write(Fore.LIGHTBLACK_EX + "Β· waiting Β·\n" + Style.RESET_ALL) + + def default(self, line): + print( + Fore.LIGHTRED_EX + Style.BRIGHT + + "⚠ Unknown command:" + + Fore.LIGHTYELLOW_EX + f" '{line}'" + Style.RESET_ALL + ) + print(Fore.LIGHTMAGENTA_EX + "Type 'help' or '?' to see available commands." + Style.RESET_ALL) + + # Help command with sections and commands + def do_help(self, arg): + blen = BANNER_LENGTH + inner_width = blen - 2 + def c(s): return "β•‘" + s + "β•‘" + if arg: + super().do_help(arg) + else: + print(Fore.LIGHTCYAN_EX + "β•”" + "═" * inner_width + "β•—") + fig = Figlet(font='standard') + for line in fig.renderText("M5R HELP").splitlines(): + if line.strip() == "": continue + raw = line[:inner_width] + pad = (inner_width - len(strip_ansi(raw))) // 2 + out = " " * pad + purple_gradient_text(raw) + out = out + " " * (inner_width - len(strip_ansi(out))) + print(Fore.LIGHTCYAN_EX + c(out)) + print(Fore.LIGHTCYAN_EX + "β•Ÿ" + "─" * inner_width + "β•’" + Style.RESET_ALL) + sections = [ + (" FILE/PROJECT ", [ + ("new", "Create a new .m5r file"), + ("nano", "Edit a file with your editor"), + ("run", "Run a .m5r script (executes only Python blocks)") + ]), + (" INFORMATION ", [ + ("fastfetch", "Show language & system info"), + ("credits", "Show project credits"), + ]), + (" NAVIGATION & UTILITY ", [ + ("cd", "Change directory within m5rcode/files"), + ("dir", "List files in the current directory"), + ("wdir", "List files hosted at a website directory"), + ("clear", "Clear the shell output"), + ("exit", "Exit the m5rcode shell"), + ("help", "Display this help message"), + ("?", "Alias for 'help'") + ]) + ] + for idx, (header, cmds) in enumerate(sections): + header_line = purple_gradient_text(header.center(inner_width)) + print(Fore.LIGHTCYAN_EX + c(header_line)) + for command, desc in cmds: + print(self._print_command_help(command, desc, inner_width, boxed=True)) + if idx < len(sections) - 1: + print(Fore.LIGHTCYAN_EX + "β•Ÿ" + "─" * inner_width + "β•’" + Style.RESET_ALL) + print(Fore.LIGHTCYAN_EX + "β•š" + "═" * inner_width + "╝" + Style.RESET_ALL) + foot = Fore.LIGHTBLACK_EX + Style.DIM + "For details: " + Style.NORMAL + Fore.LIGHTCYAN_EX + "help " + Style.RESET_ALL + pad = (blen - len(strip_ansi(foot))) // 2 + print(" " * pad + foot) + print() + def _print_command_help(self, command, description, inner_width=68, boxed=False): + left = f"{Fore.LIGHTGREEN_EX}{command:<10}{Style.RESET_ALL}" + arr = f"{Fore.LIGHTCYAN_EX}β†’{Style.RESET_ALL}" + desc = f"{Fore.LIGHTWHITE_EX}{description}{Style.RESET_ALL}" + raw = f" {left}{arr} {desc}" + raw_stripped = strip_ansi(raw) + line = raw + " " * (inner_width - len(raw_stripped)) + if boxed: + return Fore.LIGHTCYAN_EX + "β•‘" + line + "β•‘" + Style.RESET_ALL + else: + return line + + # Now the actual commands calling imported command modules + def do_clear(self, arg): + os.system('cls' if os.name == 'nt' else 'clear') + self._print_banner() + self.update_prompt() + + def do_new(self, arg): + if self.rpc_active: + self._set_running_presence("new file") + NewCommand(self.cwd, arg.strip()).run() + + def do_nano(self, arg): + filename = arg.strip() + if self.rpc_active: + self._set_editing_presence(filename) + NanoCommand(self.cwd, filename).run() + + def do_run(self, arg): + if self.rpc_active: + self._set_running_presence(f"script {arg.strip()}") + RunCommand(self.cwd, arg.strip()).run() + + def do_fastfetch(self, arg): + if self.rpc_active: + self._set_running_presence("fastfetch") + FastfetchCommand().run() + + def do_credits(self, arg): + if self.rpc_active: + self._set_running_presence("credits") + CreditsCommand().run() + + def do_cd(self, arg): + if self.rpc_active: + self._set_running_presence(f"changing directory to {arg.strip()}") + CdCommand(self.base_dir, [self], arg).run() + os.chdir(self.cwd) + self.update_prompt() + + def do_dir(self, arg): + if self.rpc_active: + self._set_running_presence("dir") + try: + files = os.listdir(self.cwd) + if not files: + print(Fore.YELLOW + "Directory is empty." + Style.RESET_ALL) + return + print(Fore.GREEN + "\nFiles in directory:" + Style.RESET_ALL) + for f in files: + path = os.path.join(self.cwd, f) + if os.path.isdir(path): + print(f" {Fore.CYAN}{f}/ {Style.RESET_ALL}") + else: + print(f" {Fore.WHITE}{f}{Style.RESET_ALL}") + except Exception as e: + print(Fore.RED + f"[ERR] {e}" + Style.RESET_ALL) + + def do_wdir(self, arg): + if self.rpc_active: + self._set_running_presence("wdir") + WdirCommand(arg).run() + + def do_exit(self, arg): + self._clear_presence() + self._close_rpc() + return ExitCommand().run() + + +if __name__ == "__main__": + M5RShell().cmdloop() + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9fae444 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +rich +requests +colorama +pypresence +discord +psutil +pyfiglet +beautifulsoup4 diff --git a/utils/downloader.py b/utils/downloader.py new file mode 100644 index 0000000..152bdc1 --- /dev/null +++ b/utils/downloader.py @@ -0,0 +1,315 @@ +import requests +import zipfile +import tarfile +import io +import os +import shutil +from pathlib import Path +from colorama import Fore, Style +import time +import tempfile +import json +import base64 + +class GitHubDownloader: + def __init__(self): + self.session = requests.Session() + self.session.headers.update({ + 'User-Agent': 'm5rcode-shell/1.0 (GitHub Download Utility)', + 'Accept': 'application/vnd.github.v3+json' + }) + self.repo_owner = "m4rcel-lol" + self.repo_name = "m5rcode" + self.files_folder = "files" + self.base_api_url = f"https://api.github.com/repos/{self.repo_owner}/{self.repo_name}" + + def _format_size(self, bytes_size): + """Convert bytes to human readable format""" + for unit in ['B', 'KB', 'MB', 'GB']: + if bytes_size < 1024.0: + return f"{bytes_size:.1f} {unit}" + bytes_size /= 1024.0 + return f"{bytes_size:.1f} TB" + + def _print_progress_bar(self, current, total, width=40): + """Print a styled progress bar""" + if total <= 0: + return + + percent = current / total + filled = int(width * percent) + bar = 'β–ˆ' * filled + 'β–‘' * (width - filled) + + print(f"\r{Fore.CYAN}[{bar}] {percent*100:.1f}% " + f"({self._format_size(current)}/{self._format_size(total)}){Style.RESET_ALL}", + end='', flush=True) + + def list_available_files(self): + """List all available files in the repository's files folder""" + try: + print(f"{Fore.CYAN}[FETCH] Loading available files from m5rcode repository...{Style.RESET_ALL}") + + url = f"{self.base_api_url}/contents/{self.files_folder}" + resp = self.session.get(url, timeout=10) + resp.raise_for_status() + + contents = resp.json() + files = [] + folders = [] + + for item in contents: + if item['type'] == 'file': + files.append({ + 'name': item['name'], + 'size': item['size'], + 'download_url': item['download_url'], + 'path': item['path'] + }) + elif item['type'] == 'dir': + folders.append(item['name']) + + # Display in a nice box format + box_width = 70 + print(Fore.MAGENTA + "β•”" + "═" * (box_width - 2) + "β•—") + title = "Available Files in m5rcode/files" + print(Fore.MAGENTA + "β•‘" + Fore.CYAN + Style.BRIGHT + title.center(box_width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (box_width - 2) + "β•’") + + if folders: + print(Fore.MAGENTA + "β•‘" + Fore.YELLOW + " Folders:".ljust(box_width - 2) + Fore.MAGENTA + "β•‘") + for folder in folders: + folder_line = f" πŸ“ {folder}" + print(Fore.MAGENTA + "β•‘" + Fore.BLUE + folder_line.ljust(box_width - 2) + Fore.MAGENTA + "β•‘") + print(Fore.MAGENTA + "β•Ÿ" + "─" * (box_width - 2) + "β•’") + + if files: + print(Fore.MAGENTA + "β•‘" + Fore.YELLOW + " Files:".ljust(box_width - 2) + Fore.MAGENTA + "β•‘") + for file_info in files: + size_str = self._format_size(file_info['size']) + file_line = f" πŸ“„ {file_info['name']} ({size_str})" + if len(file_line) > box_width - 3: + file_line = file_line[:box_width-6] + "..." + print(Fore.MAGENTA + "β•‘" + Fore.LIGHTWHITE_EX + file_line.ljust(box_width - 2) + Fore.MAGENTA + "β•‘") + + print(Fore.MAGENTA + "β•š" + "═" * (box_width - 2) + "╝" + Style.RESET_ALL) + print(f"{Fore.GREEN}[SUCCESS] Found {len(files)} files and {len(folders)} folders{Style.RESET_ALL}") + + return {'files': files, 'folders': folders} + + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + print(f"{Fore.RED}[ERROR] Repository or folder not found{Style.RESET_ALL}") + else: + print(f"{Fore.RED}[ERROR] HTTP {e.response.status_code}: {e.response.reason}{Style.RESET_ALL}") + return None + except Exception as e: + print(f"{Fore.RED}[ERROR] Failed to fetch file list: {str(e)}{Style.RESET_ALL}") + return None + + def download_file(self, filename, target_dir=".", show_progress=True): + """Download a specific file from the repository's files folder""" + try: + target_dir = Path(target_dir) + target_dir.mkdir(parents=True, exist_ok=True) + + # Get file info from GitHub API + url = f"{self.base_api_url}/contents/{self.files_folder}/{filename}" + resp = self.session.get(url, timeout=10) + resp.raise_for_status() + + file_info = resp.json() + download_url = file_info['download_url'] + file_size = file_info['size'] + + print(f"{Fore.CYAN}[DOWNLOAD] Downloading {filename} from m5rcode repository{Style.RESET_ALL}") + print(f"{Fore.LIGHTBLACK_EX}File size: {self._format_size(file_size)}{Style.RESET_ALL}") + + # Download the file + start_time = time.time() + resp = self.session.get(download_url, stream=True, timeout=30) + resp.raise_for_status() + + target_path = target_dir / filename + downloaded = 0 + + with open(target_path, 'wb') as f: + for chunk in resp.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + downloaded += len(chunk) + if show_progress and file_size > 0: + self._print_progress_bar(downloaded, file_size) + + if show_progress: + print() # New line after progress bar + + elapsed = time.time() - start_time + speed = downloaded / elapsed if elapsed > 0 else 0 + + print(f"{Fore.GREEN}[SUCCESS] Downloaded {filename} ({self._format_size(downloaded)}) " + f"in {elapsed:.1f}s ({self._format_size(speed)}/s){Style.RESET_ALL}") + + return target_path + + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + print(f"{Fore.RED}[ERROR] File '{filename}' not found in repository{Style.RESET_ALL}") + else: + print(f"{Fore.RED}[ERROR] HTTP {e.response.status_code}: {e.response.reason}{Style.RESET_ALL}") + return None + except Exception as e: + print(f"{Fore.RED}[ERROR] Download failed: {str(e)}{Style.RESET_ALL}") + return None + + def download_folder(self, folder_name, target_dir=".", recursive=True): + """Download all files from a specific folder in the repository""" + try: + target_dir = Path(target_dir) / folder_name + target_dir.mkdir(parents=True, exist_ok=True) + + print(f"{Fore.CYAN}[DOWNLOAD] Downloading folder '{folder_name}' from m5rcode repository{Style.RESET_ALL}") + + # Get folder contents + url = f"{self.base_api_url}/contents/{self.files_folder}/{folder_name}" + resp = self.session.get(url, timeout=10) + resp.raise_for_status() + + contents = resp.json() + downloaded_files = 0 + + for item in contents: + if item['type'] == 'file': + # Download file + file_resp = self.session.get(item['download_url'], timeout=30) + file_resp.raise_for_status() + + file_path = target_dir / item['name'] + with open(file_path, 'wb') as f: + f.write(file_resp.content) + + downloaded_files += 1 + print(f"{Fore.GREEN}[SUCCESS] Downloaded {item['name']} ({self._format_size(item['size'])}){Style.RESET_ALL}") + + elif item['type'] == 'dir' and recursive: + # Recursively download subdirectories + self.download_folder(f"{folder_name}/{item['name']}", Path(target_dir).parent, recursive) + + print(f"{Fore.GREEN}[SUCCESS] Downloaded {downloaded_files} files from folder '{folder_name}'{Style.RESET_ALL}") + return True + + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + print(f"{Fore.RED}[ERROR] Folder '{folder_name}' not found in repository{Style.RESET_ALL}") + else: + print(f"{Fore.RED}[ERROR] HTTP {e.response.status_code}: {e.response.reason}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}[ERROR] Folder download failed: {str(e)}{Style.RESET_ALL}") + return False + + def download_all_files(self, target_dir="m5rcode_files"): + """Download all files from the repository's files folder""" + try: + target_dir = Path(target_dir) + target_dir.mkdir(parents=True, exist_ok=True) + + print(f"{Fore.CYAN}[DOWNLOAD] Downloading all files from m5rcode repository{Style.RESET_ALL}") + + # Get all contents + file_list = self.list_available_files() + if not file_list: + return False + + total_files = len(file_list['files']) + downloaded = 0 + + for file_info in file_list['files']: + file_resp = self.session.get(file_info['download_url'], timeout=30) + file_resp.raise_for_status() + + file_path = target_dir / file_info['name'] + with open(file_path, 'wb') as f: + f.write(file_resp.content) + + downloaded += 1 + print(f"{Fore.GREEN}[{downloaded}/{total_files}] Downloaded {file_info['name']} ({self._format_size(file_info['size'])}){Style.RESET_ALL}") + + # Download folders + for folder_name in file_list['folders']: + self.download_folder(folder_name, target_dir, recursive=True) + + print(f"{Fore.GREEN}[SUCCESS] Downloaded all {total_files} files and {len(file_list['folders'])} folders{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}[ERROR] Bulk download failed: {str(e)}{Style.RESET_ALL}") + return False + + def search_files(self, pattern): + """Search for files matching a pattern in the repository""" + try: + file_list = self.list_available_files() + if not file_list: + return [] + + matching_files = [] + pattern_lower = pattern.lower() + + for file_info in file_list['files']: + if pattern_lower in file_info['name'].lower(): + matching_files.append(file_info) + + if matching_files: + print(f"{Fore.GREEN}[SEARCH] Found {len(matching_files)} files matching '{pattern}'{Style.RESET_ALL}") + for file_info in matching_files: + print(f" πŸ“„ {file_info['name']} ({self._format_size(file_info['size'])})") + else: + print(f"{Fore.YELLOW}[SEARCH] No files found matching '{pattern}'{Style.RESET_ALL}") + + return matching_files + + except Exception as e: + print(f"{Fore.RED}[ERROR] Search failed: {str(e)}{Style.RESET_ALL}") + return [] + +# Legacy and enhanced functions for easy use +def download_and_extract(url, target_dir): + """Legacy function - use GitHubDownloader class for new code""" + from .downloads import DownloadUtil + util = DownloadUtil() + return util.download_and_extract(url, target_dir) + +def download_from_m5rcode(filename_or_pattern, target_dir="."): + """Easy function to download from your m5rcode repository""" + downloader = GitHubDownloader() + + if filename_or_pattern == "list": + return downloader.list_available_files() + elif filename_or_pattern == "all": + return downloader.download_all_files(target_dir) + elif "*" in filename_or_pattern or "?" in filename_or_pattern: + # Simple pattern matching + matches = downloader.search_files(filename_or_pattern.replace("*", "")) + if matches: + for match in matches: + downloader.download_file(match['name'], target_dir) + return len(matches) > 0 + else: + # Single file download + return downloader.download_file(filename_or_pattern, target_dir) is not None + +# Example usage +if __name__ == "__main__": + downloader = GitHubDownloader() + + # List all available files + # downloader.list_available_files() + + # Download a specific file + # downloader.download_file("example.m5r", "downloads/") + + # Download all files + # downloader.download_all_files("my_m5rcode_files/") + + # Search for files + # downloader.search_files("test") diff --git a/utils/updater.py b/utils/updater.py new file mode 100644 index 0000000..a765e26 --- /dev/null +++ b/utils/updater.py @@ -0,0 +1,52 @@ +import os +import requests +from utils.downloader import download_and_extract +from pathlib import Path + +# URLs for raw version and zipped repo +VERSION_URL = "https://raw.githubusercontent.com/m4rcel-lol/m5rcode/main/version.txt" +REPO_ZIP_URL = "https://github.com/m4rcel-lol/m5rcode/archive/refs/heads/main.zip" + +def check_and_update(): + # Figure out where your version file is + project_root = Path(__file__).resolve().parents[2] + local_version_file = project_root / "version.txt" + + try: + # 1. Get remote version number (plain text!) + remote_ver = requests.get(VERSION_URL, timeout=6).text.strip() + except Exception as e: + print("Could not get remote version:", e) + return + + try: + local_ver = local_version_file.read_text().strip() + except Exception: + local_ver = None + + if remote_ver != local_ver: + print(f"Updating: {local_ver or 'unknown'} β†’ {remote_ver}") + # 2. Download/extract ZIP to temp location + import tempfile + with tempfile.TemporaryDirectory() as tmpdir: + download_and_extract(REPO_ZIP_URL, tmpdir) + # Find the extracted subfolder (GitHub zips always contain one top-level folder) + zipped_root = Path(tmpdir) / "m5rcode-main" + if not zipped_root.exists(): + zipped_root = next(Path(tmpdir).iterdir()) + # 3. Copy updated files over (here simply overwrite existing files) + for item in zipped_root.iterdir(): + dest = project_root / item.name + if item.is_dir(): + # Recursively copy dir + import shutil + if dest.exists(): + shutil.rmtree(dest) + shutil.copytree(item, dest) + else: + dest.write_bytes(item.read_bytes()) + # 4. Write new local version + local_version_file.write_text(remote_ver) + print("Update complete!") + else: + print("Already up to date.") diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..7f50de0 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.2.2 Pre-release