u z-B6!6lvGX;Jw7pA85L$H%HI7`wQ8-?q0 zj}}G)LGknce7sH`iV)u}hQ2k5foc&HdVcW%o=eLwa^S%Ldu$U1qTN70-t&4DO>T*E zfT@!fn^m8ICR$ulf`34Z3)BlOF4rLE3D@ e{FFI4CuoDdo&&*36gswOs;g zv3#H({cpP1)fd&O#e;W6VA-a=z^*C-zic`!|N}Y-xazX~X5M>PK6nqt=$=@I=4R+a7 zGNg%Y)o|-J<9ovN4!4^B=Ud*HKK{YWN;*;|tAiN!2n@6#=_HIi_m $LyPv?l>(K)9+h!zp|G=p4-I?MC!LPE&Wj%I`chPY z-l>rh4o?_5-gFn80u>nDp|-v|ZTmoe4f*4u6Yxo5EX6%L+7`RhPNC%FORdS?4PG_I zhB2Re^x<8Gg#h7f!5>g#39I{moay)k`qz~zO^lB;*!DV2r_;DRZ7?+(mr5F&Y^v$j zP8ECGwkm+54AQ-M_T6l5CB@W}oRq_q-VB)$RI*+SEKwN}rwQ{$-e-4btx6t0GAJ;r z9DnCJd;7BK2;RMixvRVP&9e`-m m(l<=#=4Rm;N#T zyo{<**VeYAfeAFNxyOxe@6xaa7m%{{scQB3gU<+g*U!9#oU-a9u<6cu6lzm*)SYZ} z!pPe~>u_2IR?D2oGd9!J9PG!)7aTg Bk52WJ1FS?SPUd|@z{&I z+2d@? 57EzyEnCV=+8UsSGF Eqs2#d{s yQvfu6xblrFJ;70vp8|KlH3 zJnJ9-&p#fPWEwc2 5HZHXe9rJeh<*ERDAdjsED^T zJ9pfAZN?Cnf@5aISxH-57#;&ea;EDk7iwgw9J~3VPn}rMi)`JE2a1L)ZDBeJ=G&0g z&(t8rEp0y3C|Mgtvv%C)^*03Pz4t3zBzGMF*2ib;;=X(GQ)j*|HM{-lwoa-7oLXJh ze>Pi%7=iyZw^I1eW~&cP*KTZ7-+d3Aty&+pch~D3E?B7d57mw);*IKzyfxYQY7cBz zEKkS9l2Ze3*fUbsqUW29Wtp>i3qPG)dgYeEW=6_RSBrDS@~86JxA28AKiT~;J~)ks z1g>+D86G-l{KgW68HTvlNf8J}=qVCFFurJPbZ>yvgg)rp-NR9DWAD;b38Lw1u0Icta4$WRn9HJCP7eS#cf&ncYrZ~S-^l`An5ItQu#vzfFwd~rct(M1WM-08m zj?T6xY2BJ5+EJzE=n3zrE-3nAwi{==lxD3jIijzPnd)-x(TRDRUY9mMaMG8nie-PW z^zo0ZJv9}6pJcW+VS?#sMS&Rj-cS|=_f%HlBv$ci3kPJ3-v^s3h}D1r fPBLeKF250mXvLs_; zYl6-WU>h}%kh_5e_79K0gaKU5)2%rDczH?|WUG7b``tO;!Gd|;KUB>1!v=}UjokWc z;@q3z{tSy%ik)v3qg0=h^pOY!i^;KoqN0g`8PaaE&}52Gjq@yG)@cvev6$k!yNx@? z6vmKaDmE9b2-}hD8}V?@NfOZ2>I3W#cEHJdhvZ|I`3Br=e+hp>WIR~CAa8R$VGR2F z^(G0xB);DK@GLKgOIR}rEZfSvgy-bk8R_ F_Wv~@qze#|Br>YZzUpRG;viD%k+iZRX zdO=wQzhdz>>_a2h7bk8p-zB_F`h#SQTf$lc?RPf3)1IYWuG0oN -1r2qpruesnVq=uajk8O_ z_65%VEXMQ+6(a5ShIivjqku9;7}@ufLw5F0BSmS2OKa_!=m6X80GDkx`;+P*R)jD6 zUL>FTHe!vF?QGjWZM$9EA$ZLuZJgmI{ep}ZwxB2PwAn2+*9A$iE0aO3)rhfLs &lYw#)2V)rS>n`N zAwie7rUQ$N$4Ds>o{5b><%F`s6AkaN_9k-LNU%E1Ca5L6Ax9I-yNkLyw%!N{eE#-^ zLFoO~4IOyQ;@xVf?t;wDJ7;iTKm$BpE&$WPL}J}1sh~YKW`vm3Tipc%qVl^HRWiV` zUUs@+dnZ~)j_}aw$!7y&wu&>ES8z19CLmCqNrpe0w8#n7KNaiFVSu^?r?q1yV6EjT zYYzsC&s++8?aje|Y &GN+toT&3Utqj3cixu zeTTGBa!g=ne+Ak#izn(h*?G*er%ro5*sfw3=W}=1pSa+Vu7fNDZBg0G3w?}|^rjVi zlTSR{QZk-pI$G7e{Z%_C=uzC6;O(V!hYRO^{{-CnXI5ya{I?5s6N8fuNBIFo?DM|2 zwwFt9;;ukvnu|cVPoPryAqbegn!#iF-TO1R1FoiU{|Gb>WQRICf0+YhWmO@Wkc?3^ zup$bXjz^({VCvb!!g=Rpr-HP@Hyk*QX~gu5voP@$r!`D*a^_6?N(v5{n^4mwC@2Sn zREj;PPCFK}6(2Np5JHnlgN5K}oLLX9cT@f6ZKc;&IaJ8Mk2`#UUg7Ae; _uNss%suG(Coh-aqWCynEiW$6ANtRcM@PUp0W`> zqZE4Y>zcgpNQF&Krvse^DZQ!3twl%T$hkNR-P}3&$n@xKJ#W63sE%jQ*n!}5Yr)Tx zI5}yu^~G(j>L$MNZ^(Kv#@p)<9)oPcgy$7JpbrG}J%QlzL#MOFZX72PXa+H$w<#~} zlhr!6vN1v`hr_neWN>KC1P8S%2YO%@F5?zfIUS$va 6DJt2HD?iZHHz~ZpY+m(l#i^b)4-Q9Dj-WBY7OsZ&)OE zCIFSw6~&tkxLO9c32RVE{N7FY%nMgIyXJ_iBBnPSg>p1l$(&tA(s;Spr(LGgk~SH& zZp8&T$@4+3Eb~PW?88$afHqh%QnKJA dY4kiHNzN@jV<4B z6rN7Ps7zldgI=63QqA#VA~zup%C6ss5pF eUJEh=62M&D5X+_KzF*Yi3iCLK|849*S2NpMby z)atpEo$d6sEy`VCPz0T#x8i#(M8>CQgE{IeD ?TKgZc_dtE2hqdq>=Dz$2so^ zN`GI~T>hC0egO%QP{FR|`^KuuSJ6t9%Ux?dE(paov1rsSvOL=NN>Lj3oM^b<(rAqZ z!{y-|v?NPnO=`F9I{dkXSvz{0glOQbBi&BUz;Ry_3raYDWslGBP!lWs*#Y#|s1ls< z?@peNXKFUb W zK}@H9?;6$k?=>5 %?-!TF}x;vK1TK4)~Hu1 zKD?nu-G}di%L*cgKN}fHM=N@5$(
|MjTR{nazg!R$h%a2-X>B4>o>tbg2 euy}qpjh7>`dtN!|?%?EJNK7qOwN>*cd zEV~3_fQ3~L#WL5&>M11fSS`+bkqZx! NI;mT9!H0CaTUuqyx;b)UxwUq=GXn?xDyxf@}X-Z*%vCV9*Vq z^VVVyu;VSL3%|dG 6tmekKT+ 6$vi5$AwYd{l*Ze z?!RRWR8Rh75L_R6WfZ{MFF2y^U{_FT%uqSmHA(mjX88Z)ao2Z(gZ;?Y5dW)%03~5v z_P#&&=P-Q*>Ahu;_IEn59v>pUmu`Gg }1CgK7e(!X^n3lTf>#`fHh%{ zfO?ZG;1Y!UA*ctWHU^7bPoU-$c}NAJh*H1PVUEWw zl9MzxUhaf_xe%A-Avwuy1H*;sUIzOVu%m2tY$WwCTh*yn*A}PqV%$|3*&VyqGR(l9 zpC7jnUoD!uyoF{3*!^>lSOI=|?LmB&ae#&iPYSsP^3eeU0IPctJ+8IajPfINv$#5( z29gu{rw^*#yY6`bO%9$KBzT~;=w&xNEcJM(Yp$!C+H$%yBls}fz^@w2z?VIka8CjZ zpyJWSN}OGlo`kmBgB?+AE3YgG2=iVGc6&MSw%~qku*3Lriq4JkAUgj4D%&0oqrM{q zprj_;pGg4jzNzVJv;=Fi_6aOYs50t7{z|MgY5X{BDFH@{hUs~tF64V{TFA;MrgdSZ ztd4C3Y>&lxBneA{d zBMD8Q7i;8(sydn;m(9B 7Y)yyI +;2=R>u`GX>Zwhsa zxF)?dSKV>MujJ8rF)po6=7Vs@T2jgBTHNtHWIjFE$SGf}#cfw@aqZ27QdQ>@b)PxF zZ??oeN{qLW0wF>Kz`r16`D0HZ7zp))m~(vB>T0}!wI>FnomSE*+Vd*yhsQMOmKjc; z)8T4-l1{U39AWT)u qfuiS!<{aXi*mBvAj(mbiHV=G2v2PENMi=e?fTd{AjimL zANyj;(ZGCdo`KCWA?O)>rtc%W8_7bC!k;10#zA67Rj!jHq@jJ@62Le*M(bKrr0= zuCGUxKMVeAFzP0q!EbBw0jeSB7(b|?e?!3EhIJ!aC2VRU?y+E{y8EVxs~`i~Reu}G zRbGm<*`~9JBpSuFt`QxJ?TIKi;7ow_j-aOEnhDvBY+`68qM{U;vR&ws*6otEe?Ax- zv5Mr7P263(O%p9cl$J9=@h01 IOYR&M&a=2+Kd;oXLNl8SXzSr>mG#!*w` zZ}a}A`44{mJ qL2a8>~pnmRYm1gec;JO>Z>m!As);nXp zJ< vAK-zA8c5gd;dv*QmK~3e z_+W6p0}lGQj1S7Z2SX5s`mSfYg27V{KGt6aIIrpQyT?uqH|GD&*n7^+JPQLjn|liH zH^SgKM^iz1;E|W&0PH^Rxty}24ilmDa0HoSbB#Xb_6j-iYpTU-%8ZvzbIs0yz}`=m zhOo53DGPfP1MN{yPTqCw_H-*T_}a&p+z>~{NKyAcuJd>)4q!s~R{2+@9$y5B8vQxn z+6Sqlfi`D=*iCB=loQvu=iN1T2MgBS{R6NF-a1mvRyPJM5*8xuts!o)jD_M@FE&Z* zsPE!95;|OnbVlBe91U4QpYG{t-j`;;I6Ch5Hcf`SJfywMBYMk-b<9AFjK%Cr?#l nmast)Bm$Fu;~Wu@j7n>yrY}xr0O_`~Ae>=V7d*4%Q-OPFQ@M zd+kt{=BfXQQusV72G0EUhIckVKr_%G69W|w9Xn;>o-@EfcPebLUv{?bP1M&`D1Uag zBDG+QbKzN{*XnWnI*Rxf-ffp+X2x0^m9(05!18OGfzBWN?gp(dBtXE1MFg5Gg7%`9 zBY`hb^?SVf!GL@gjZJ8S<=wyMm a= z4I@rl2}`!n-#VNO9DsZ|ne1ZLHY&Z^j*KQ|xa`rWk>jn>?HkYwt}feCDo8(z{5joK zRTMk*3Q@>f!FJ-FJBJcIg3^s?tk8<6(o(^JQ$|N^;3j%0zb1AfVa!s-#*M-DC@1=v zU(rK^3+5t7nxt4D()HBH3(0q-X=UcY;)fT4FTCQk30Ie&{T^3 49)-K-3T`9W$A1X?9j z(7IRwzq4gwO@y;5RsSFquVborQh7Q;{C_thULIF|$Iz?Myo%GS9)-sh~~xT4W! z0>_vKxw{}I>2^VlcB7J+P1r;XoCP8{nbeNTSnB)p?!b`1aRsB1r`qkq+&9bV!uD-j zM|1a`1yvEaT2B{NX%;{5sqcF3gYtyBHh53`T!G$@2;c*q1PdL0Tv&alv_LDT1v*m8 zY1_lVrq12wL@Iq|L@DL|jI*JOfoeO~W;sD%BwX;5<%%0)A=R=2aH6qsXzd22zj3 zEB8_&oQ|tiitSVli{f;0Mm#DVEqa5UDtCC_W5|g> zj@KoPelfVKfaqfbr6wF)G9Z71gBtJ~B5Ge%k>?+z4(LgSWr{YKuBH;wv66P&U4)u% zr^W_!2Y?oT8opdJ8v8$Y ?82ZX-Y;#mI z6W2R)H_aXBh+4Dmca6ykp`V~{82J1GxX!SR(zlBl9A-!`jMZayOQ*A cb4oxX z;3z*I&3o;D+%*2jvZuGl58^s-)G&BeLHKE`mb;5ra_H~T<9vA@bh^T}yBU%_3rN~W zm*w6;wrRMJ1z&Fu_tKRXj`(r~#Kl`?$Y9Q%bxl0 9~$5UY!}sVOnAdhy|!+xl*?o2 z>`|Y`fnkADZGEdq9pm_+ffFskZ<-JXr~3zMwBZF1iw$2_p8p$bw*^|I%tF~t_pv_H zaC$@|Spjl9!^L*32_V$ovx9jH=^-d?b(3R!wp_tM&CQwHmbqSJV8 %a8yxRvFCBV*u zRRZ4%JBYkh-(==fezC!KB}Y$nir)syd4MXCMG y_tGzC3{7>o75psU OO#7r8p@uK`9^kZ 82@XVY+uDA1QoiifinY@RJd5O|Ao-K%$qi^V^9y{iXrO5Ok?!Q|++ml) zlbHuvJDQ9vw{XI gezD zNz7t(&!g&MY9#7`^5 IH6KMoj{(s)C2@F|Vnoe_arH6N8Wj{=F;ezo7EFds*=ArMnO$&)1(eX}9W02<9&4 z0GOv-_`hH>{4N>b11UIvWB=bx3!c0N -K1t73@z$I6{}W2G*#o|X*cspggCh_Ll8J 7E2ys`dxDm2+VX| f|D3B~#H zxK@J`nwD-bjYj2^I&KuVH80CaXY96FG#N?nw4k|`M11Vx0CtYLwTG>(jn87)s=3dr zNZ=EkCLBL5i1%?^1v#n#R`|VttPH6iI1T@Bhy8dsb0x$kti8DBfKFVRHt1J_Kc7q% zJ6^28!-twi4|mVr<@Vd7uRy|aR~Nu#g?afOwb)2Kjwb4{C-A%^5!+~^ZX|u;P8gul z&s=9XPDpn$+@M5&jk` gvK z KJ(9OetBSw63)1PP_rr5q#hofHIqr67ZezY#67V%tTL88XU@m6e#42!2<_Y z>rvS>e^>_jdse9fz9QiE9>C~tVPLBUEwMu=EIoHh2jg%~mV(3hOdmrl^c8v79hQ C0AKd+p_|bFefDdC-~OOykBe`0ds3IH 5eGl z%*YI7cRe87er^@AxoNi(U*b!Vmd{~kM&Q6lEZXq98PM$@M9x<6iw9@>dQ4ms>i}Q# z&!$_&mmpvm3xl%jM+wU$D=~2Z&rkT%qO&Z@FwmD`nbS_(pMnK|FED}XVEM7rDR V9Ii!`Lu7N1M#ePbS}St5=q9}frfFu5Th-!k4nqV74yJK%JD{%K z!~qzM|L*Bp-<*^Pbb9kSh>v~Zp_TgN(_9<00A~jzJL=BPtwQn1s;$)oR3`0XN*c_v z?f_qn-nv^|8P8vi`MP;ZH)H0z0 zztvduI9uCWIWIqDIDGAGBhO>F=IM;arsWCdP~Xu0wrGdFBY~?3`cfKDyw^Sg5f4E= zV5YJ3X*7xR<%w&Pme=2|-Ng{Gg;>bj!;Z 7-t&xom9LlczPRZrJySge>@bWazrxP=8{Q4v=pUx6^L75tdzE7As z=!!J;FCcN=qD^l0);oP;4Fyf@8pCne>J0o;Rd^);=msYN3&v=an21Sg^T{j%T4s;; zsg^m~u_t<_O|~aaA53Nir{Zq3nr|fnoc;4$qz{Tpbr5X>``tZHtLj9X5b r7;;e?-6g0Y>;t_iQ`&m&HkLUPN=&%K{KZYVMkYI~yzDWS%a=5)% zGwbd0?8e&mgrlVzQrnE((xAIiu1_)3W8bjbZ^Nzp{mPAl2FHt{jpv7(@Ob(8r{H|E zaQ*>u4U5&VD&OY^Zw=y*Mi7$+iN&JTYPT{*i#jYjC#PA#SSfSv)2ckP!9guUf54fP zuEZPD;mUJA7cg2S$>r7@EB1O>ZqhdJsO@vdIvP3-arn5bE?dr;=Ki<~?d`<`0KzGQ zKi~)<7&Whp`$nM_L@X^$Mo2cJ4y!IF_g!op&rkU5lsHo2@bbLfo3}9Anj<9Z(g%jo z*|9&(*N%21u=K3D3WpEqIn% Mo?RG(biu&8`mdB>4O%uKtr@tmPC ze}1Y6{f6DXT8_vr-|sB(S#eUw#ZX@?Vm2T9JCs_m`$9Fu&Z(Gp&I#Ts!h$Yzfx>)~ zaqVf$I|Qr(n+FyfjAryHwUu#165Qb{q2bB2u&lgk=FnHUYxcKEU4ev?t}fmQ@!%i* zbLfdU#+?e27Ql%nQ9of3)^cd CbM7u}dKiS>@yayz+Y>>_o>%*ib{aPCR!fC4JbODrvZSy>Jx#~%a_YJjWp-``V} z2mBCo!aw%I%w%1?GddGJk6N8)vtxKv1I;gjuXA{eQa4iom=2$Y|Jw`eApm}vN9|qj z8YDid*nZAB6hF}Q)h>7909gkI(Ech%lFme$3_zv$%93`=kR6Rr!FYk$DejWn6Vun7 zPB;P?Fpe|* G&$ 4d^w2Z=w8qIfEvKEyKv)jw{xK&erv=AZ z)2<8-m%zf^kUdU@xPAo1!(vGngHjyUS-*O7V`!tgqh~u19<8tlHe5Xr;yUJgqOBsv zW0#EzHxIeRD?wlu`AQH7r`Fa6{NyEJo?lrfQFX6ICde7jgL$7+HzorFyIod3!(%r; z_)hpwqcwO$b3xmX;2dSGwLX`Ve!h@ro2@k* odlA}1WRYF8K zpK+8)_be#Ekhdn`Ml3Ya+*;~Tj;zyX#lq#qcjq5|z6Amv@ClgnnE-XP)q|6;1x0 I5#p|tF_ni7Z*3^lYz4}o zbMCL{Wg!t@PlkO&@`b?QWUI*d!$4`7)?$AYgUzP&Hc{z0L$mI5Rd0UH^EZN5tokfq zs^5N1?EVva)^`O7p8~V{n%o7?K?}9c`k&V~@;#bHm!4cVlGVvo?Fjv~P6qIxF7ZNO zN5DW>og|Iqpebc(r@LrQ3_-u+!gY;dTf&1<^Zmhez?5tUTjLQ)()(r03%f#BQbNI8 zdxzx=6wpb|V5=F!BSo$0OlKFF#J3pkos;N4vv-$sAwK2B6M7!cRBXc2qXv0bSl170 zgb1s>r*aS;nYj^jr3O+Hpn>U%1IIP57p^@-qD{eqGjgm03qsXSIC0Cu)Vg2;UFK@V zSY1wyWyBZ3F~o9X>*uL8a{V?xmDzb*99mmLuNnYVEOYY*?|X2YCl9tF-jD8nd-j)& zy>Q}}D==pW=x26W9Oq)(O_t#Fr9lUrB$o9RiC#J9QF)$ECx-(uBFumo#VxV)+TG4* zWpc+PJ4k$92{dllA*hgWIn9_2m zx)CVkHyoB}J!ic?>x-J%p>9pR%MBNhVAPk3claOltdF}his!}H 54IsG(+-4fH z`|Fa?Bhce5&M&ba8*>iGku}ROv@nIA7kZsAT%xYUZkVWxQZ#Twzwh<<% E(#q^RJzDG&Jl? zPRGGWT)1Q@rW= }H(s-*!7Ut-2@fIr3;~TaGkA;Nx4 S5rB<3=kKJhOz=kiM8q!ZHCg~&POyQ-O8C7Ol~3hBPgZ9>k4UMyWw0WMyB9o zxC-~ejW}WB-VzwxkKY``m?V}LV0U5L) `@LPCj2gU%mjSj=E<6 literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 000000000..847c688f7 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "discord-cdn", + "version": "1.0.0", + "description": "cdn for discord clone", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/discord-open-source/discord-cdn.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/discord-open-source/discord-cdn/issues" + }, + "homepage": "https://github.com/discord-open-source/discord-cdn#readme", + "dependencies": { + "body-parser": "^1.19.0", + "btoa": "^1.2.1", + "cheerio": "^1.0.0-rc.5", + "express": "^4.17.1", + "express-async-errors": "^3.1.1", + "lambert-db": "^1.0.5", + "missing-native-js-functions": "^1.0.8", + "multer": "^1.4.2", + "node-fetch": "^2.6.1" + }, + "devDependencies": { + "@types/btoa": "^1.2.3", + "@types/express": "^4.17.9", + "@types/multer": "^1.4.5", + "@types/node": "^14.14.16", + "@types/node-fetch": "^2.5.7" + } +} diff --git a/src/Server.ts b/src/Server.ts new file mode 100644 index 000000000..7d93c4448 --- /dev/null +++ b/src/Server.ts @@ -0,0 +1,93 @@ +import express, { Application, Router, Request, Response, NextFunction } from "express"; +import { MongoDatabase, Database } from "lambert-db"; +import { Server as HTTPServer } from "http"; +import { traverseDirectory } from "./Util"; +import bodyParser from "body-parser"; +import "express-async-errors"; + +const log = console.log; +console.log = (content) => { + log(`[${new Date().toTimeString().split(" ")[0]}]`, content); +}; + +export type ServerOptions = { + db: string; + port: number; + host: string; +}; + +declare global { + namespace Express { + interface Request { + server: Server; + } + } +} + +export class Server { + app: Application; + http: HTTPServer; + db: Database; + routes: Router[]; + options: ServerOptions; + + constructor(options: Partial = { port: 3000, host: "0.0.0.0" }) { + this.app = express(); + this.db = new MongoDatabase(options?.db); + this.options = options as ServerOptions; + } + + async init() { + await this.db.init(); + + console.log("[Database] connected..."); + await new Promise((res, rej) => { + this.http = this.app.listen(this.options.port, this.options.host, () => res(null)); + }); + this.routes = await this.registerRoutes(__dirname + "/routes/"); + } + + async registerRoutes(root: string) { + this.app.use((req, res, next) => { + req.server = this; + next(); + }); + const routes = await traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root)); + this.app.use((err: string | Error, req: Request, res: Response, next: NextFunction) => { + res.status(400).send(err); + next(err); + }); + return routes; + } + + registerRoute(root: string, file: string): any { + if (root.endsWith("/") || root.endsWith("\\")) root = root.slice(0, -1); // removes slash at the end of the root dir + let path = file.replace(root, ""); // remove root from path and + path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path + if (path.endsWith("/index")) path = path.slice(0, -6); // delete index from path + + try { + var router = require(file); + if (router.router) router = router.router; + if (router.default) router = router.default; + if (!router || router?.prototype?.constructor?.name !== "router") + throw `File doesn't export any default router`; + this.app.use(path, router); + console.log(`[Routes] ${path} registerd`); + + return router; + } catch (error) { + console.error(new Error(`[Server] ¯\\_(ツ)_/¯ Failed to register route ${path}: ${error}`)); + } + } + + async destroy() { + await this.db.destroy(); + await new Promise((res, rej) => { + this.http.close((err) => { + if (err) return rej(err); + return res(""); + }); + }); + } +} diff --git a/src/Util.ts b/src/Util.ts new file mode 100644 index 000000000..291372c19 --- /dev/null +++ b/src/Util.ts @@ -0,0 +1,38 @@ +import fs from "fs/promises"; +import "missing-native-js-functions"; + +export interface traverseDirectoryOptions { + dirname: string; + filter?: RegExp; + excludeDirs?: RegExp; + recursive?: boolean; +} + +const DEFAULT_EXCLUDE_DIR = /^\./; +const DEFAULT_FILTER = /^([^\.].*)\.js$/; + +export async function traverseDirectory ( + options: traverseDirectoryOptions, + action: (path: string) => T +): Promise { + if (!options.filter) options.filter = DEFAULT_FILTER; + if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR; + + const routes = await fs.readdir(options.dirname); + const promises = []>routes.map(async (file) => { + const path = options.dirname + file; + const stat = await fs.lstat(path); + if (path.match( options.excludeDirs)) return; + + if (stat.isFile() && path.match( options.filter)) { + return action(path); + } else if (options.recursive && stat.isDirectory()) { + return traverseDirectory({ ...options, dirname: path + "/" }, action); + } + }); + const result = await Promise.all(promises); + + const t = <(T | undefined)[]>result.flat(); + + return t.filter((x) => x != undefined); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..d80259681 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,14 @@ +import { Server } from "./Server"; + +const server = new Server(); +server + .init() + .then(() => { + console.log("[Server] started on :" + server.options.port); + }) + .catch((e) => console.error("[Server] Error starting: ", e)); + +//// server +//// .destroy() +//// .then(() => console.log("[Server] closed.")) +//// .catch((e) => console.log("[Server] Error closing: ", e)); diff --git a/src/routes/attachments.ts.disabled b/src/routes/attachments.ts.disabled new file mode 100644 index 000000000..db1a7efc5 --- /dev/null +++ b/src/routes/attachments.ts.disabled @@ -0,0 +1,19 @@ +import { Router } from "express"; +import multer from "multer"; +const multer_ = multer(); + +const router = Router(); +router.post("/:file", multer_.single("attachment"), async (req, res) => { + const { buffer } = req.file; + + res.set("Content-Type", "image/png"); + res.send(buffer); +}); +router.get("/:hash/:file", async (req, res) => { + res.send(`${req.params.hash}/${req.params.file}`); +}); +router.delete("/:hash/:file", async (req, res) => { + res.send("remove"); +}); + +export default router; diff --git a/src/routes/external.ts b/src/routes/external.ts new file mode 100644 index 000000000..14980b058 --- /dev/null +++ b/src/routes/external.ts @@ -0,0 +1,83 @@ +import bodyParser from "body-parser"; +import { Router } from "express"; +import fetch from "node-fetch"; +import cheerio from "cheerio"; +import btoa from "btoa"; +import { URL } from "url"; + +const router = Router(); + +type crawled = { + title: string; + type: string; + description: string; + url: string; + image_url: string; +}; + +const DEFAULT_FETCH_OPTIONS: any = { + redirect: "follow", + follow: 1, + headers: { + "user-agent": "Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)", + }, + size: 1024 * 1024 * 8, + compress: true, + method: "GET", +}; + +router.post("/", bodyParser.json(), async (req, res) => { + if (!req.body) throw new Error("Invalid Body (url missing) \nExample: url:https://discord.com"); + + const { db } = req.server; + const { url } = req.body; + + const ID = btoa(url); + + const cache = await db.data.crawler({ id: ID }).get(); + if (cache) return res.send(cache); + + try { + const request = await fetch(url, DEFAULT_FETCH_OPTIONS); + + const text = await request.text(); + const ツ: any = cheerio.load(text); + + const ogTitle = ツ('meta[property="og:title"]').attr("content"); + const ogDescription = ツ('meta[property="og:description"]').attr("content"); + const ogImage = ツ('meta[property="og:image"]').attr("content"); + const ogUrl = ツ('meta[property="og:url"]').attr("content"); + const ogType = ツ('meta[property="og:type"]').attr("content"); + + const filename = new URL(url).host.split(".")[0]; + + const ImageResponse = await fetch(ogImage, DEFAULT_FETCH_OPTIONS); + const ImageType = ImageResponse.headers.get("content-type"); + const ImageExtension = ImageType?.split("/")[1]; + const ImageResponseBuffer = (await ImageResponse.buffer()).toString("base64"); + const cachedImage = `/external/${ID}/${filename}.${ImageExtension}`; + + await db.data.externals.push({ image: ImageResponseBuffer, id: ID, type: ImageType }); + + const new_cache_entry = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; + await db.data.crawler.push(new_cache_entry); + + res.send(new_cache_entry); + } catch (error) { + console.log(error); + + throw new Error("Couldn't fetch website"); + } +}); + +router.get("/:id/:filename", async (req, res) => { + const { db } = req.server; + const { id, filename } = req.params; + const { image, type } = await db.data.externals({ id: id }).get(); + const imageBuffer = Buffer.from(image, "base64"); + + res.set("Content-Type", type); + res.send(imageBuffer); +}); + +export default router; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..26be7b1b2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,69 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + "lib": ["ES2015"] /* Specify library files to be included in the compilation. */, + "allowJs": true /* Allow javascript files to be compiled. */, + "checkJs": true /* Report errors in .js files. */, + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true /* Generates corresponding '.d.ts' file. */, + "declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */, + "inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */, + // "sourceMap": true /* Generates corresponding '.map' file. */, + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist/" /* Redirect output structure to the directory. */, + "rootDir": "./src/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + "types": ["node"] /* Type declaration files to be included in compilation. */, + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} From 4ad77092d07159ebcf5ada40e49aa17fade1fe04 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:07:11 +0100 Subject: [PATCH 005/908] Update README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index c7095453f..3439a95d8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,27 @@ # discord-cdn cdn for discord clone + +## Endpoints: + +### /external +#### POST +``` +Content-Type: application/json + +body: +{"url": URL} // "https://discord.com" +``` +##### Returns: +Content-Type: application/json +```ts +{ + "id": string, // "aHR0cHM6Ly9kaXNjb3JkLmNvbQ==" + "ogTitle": string, // "Discord | Your Place to Talk and Hang Out" + "ogDescription": string, // "Discord is the easiest way to talk over voice, video, and text. Talk, chat, hang out, and stay close with your friends and communities." + "cachedImage": string, // "/external/aHR0cHM6Ly9kaXNjb3JkLmNvbQ==/discord.png" + "ogUrl": string, // "https://discord.com/" + "ogType": string // "website" +} +``` +### /external/:id/:filename +#### GET From 738d78ca5a5d21f6dbbc7e7ebe72ccd883d388c0 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:14:04 +0100 Subject: [PATCH 006/908] Update README.md --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3439a95d8..ede9cb5c2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ cdn for discord clone ## Endpoints: -### /external +### `/external` #### POST ``` Content-Type: application/json @@ -23,5 +23,17 @@ Content-Type: application/json "ogType": string // "website" } ``` -### /external/:id/:filename +### `/external/ / ` #### GET +``` +url-params: + :id // aHR0cHM6Ly9kaXNjb3JkLmNvbQ== + :filename // discord.png + +/external/aHR0cHM6Ly9kaXNjb3JkLmNvbQ==/discord.png +``` +##### Returns: +``` +Content-Type: image/ +Image +``` From 4edda3503dc1c075b1a50cf94c6e2a5ba6016d90 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:17:45 +0100 Subject: [PATCH 007/908] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ede9cb5c2..c6a5f2f91 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ Content-Type: application/json url-params: :id // aHR0cHM6Ly9kaXNjb3JkLmNvbQ== :filename // discord.png - +``` +``` /external/aHR0cHM6Ly9kaXNjb3JkLmNvbQ==/discord.png ``` ##### Returns: From 316847094ea9c5d7d6e0e23aea0313b305cc8659 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:18:05 +0100 Subject: [PATCH 008/908] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6a5f2f91..68cc50da4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# discord-cdn +# Discord-CDN cdn for discord clone ## Endpoints: From abf416728ec671a013ff1eec5e656c0f2417af6d Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 11:55:08 +0100 Subject: [PATCH 009/908] updated crawler type and implemented it --- src/routes/external.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/routes/external.ts b/src/routes/external.ts index 14980b058..be680003c 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -8,11 +8,12 @@ import { URL } from "url"; const router = Router(); type crawled = { - title: string; - type: string; - description: string; - url: string; - image_url: string; + id: string; + ogTitle: string; + ogType: string; + ogDescription: string; + ogUrl: string; + cachedImage: string; }; const DEFAULT_FETCH_OPTIONS: any = { @@ -59,7 +60,7 @@ router.post("/", bodyParser.json(), async (req, res) => { await db.data.externals.push({ image: ImageResponseBuffer, id: ID, type: ImageType }); - const new_cache_entry = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; + const new_cache_entry: crawled = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; await db.data.crawler.push(new_cache_entry); res.send(new_cache_entry); From 631a1d80a09287995492e461834e6a555b7625dd Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:44:05 +0100 Subject: [PATCH 010/908] finished cdn (POST, GET, DELETE) --- package-lock.json | Bin 59210 -> 59731 bytes package.json | 6 +- src/Snowflake.js | 145 +++++++++++++++++++++++++++++ src/routes/attachments.ts | 50 ++++++++++ src/routes/attachments.ts.disabled | 19 ---- 5 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 src/Snowflake.js create mode 100644 src/routes/attachments.ts delete mode 100644 src/routes/attachments.ts.disabled diff --git a/package-lock.json b/package-lock.json index 9d7a1f511d23e32c87fbecec6194d1039168b5ae..9b5a53109bd9137b7bc58ccdb5c55e70f9f32443 100644 GIT binary patch delta 282 zcmX?gj`{K><_&LzC)WwGPyX;AaPkCB_Q{S9jYCrdEeqYk_0uxa+`Zf^!o5A*T|J^) zE5pLmqAH3)w9TA-JiXkVlA{9BDnc{#wVm=@v$R8<-6MT-iYyDGN+a}*9CJ#F%gT$~ z3kwW z^xE+I8}{!EU|%u{)M7>y<$ hr&pN=nWv_2KJ~DH6##m`WrqL& delta 51 zcmV-30L=f>(gVuQ1F-B9v-cGGfRn$Z6_Skwvk8|EOp_m$3X?v6D3eO#5R+RJBa@7l JUbEZdcm<2o7svnr diff --git a/package.json b/package.json index 847c688f7..036623568 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,15 @@ "lambert-db": "^1.0.5", "missing-native-js-functions": "^1.0.8", "multer": "^1.4.2", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "uuid": "^8.3.2" }, "devDependencies": { "@types/btoa": "^1.2.3", "@types/express": "^4.17.9", "@types/multer": "^1.4.5", "@types/node": "^14.14.16", - "@types/node-fetch": "^2.5.7" + "@types/node-fetch": "^2.5.7", + "@types/uuid": "^8.3.0" } } diff --git a/src/Snowflake.js b/src/Snowflake.js new file mode 100644 index 000000000..feb5eb415 --- /dev/null +++ b/src/Snowflake.js @@ -0,0 +1,145 @@ +// @ts-nocheck + +// github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js +"use strict"; + +// Discord epoch (2015-01-01T00:00:00.000Z) +const EPOCH = 1420070400000; +let INCREMENT = 0; + +/** + * A container for useful snowflake-related methods. + */ +class SnowflakeUtil { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z + * ``` + * If we have a snowflake '266241948824764416' we can represent it as binary: + * + * 64 22 17 12 0 + * 000000111011000111100001101001000101000000 00001 00000 000000000000 + * number of ms since Discord epoch worker pid increment + * ``` + * @typedef {string} Snowflake + */ + + /** + * Transforms a snowflake from a decimal string to a bit string. + * @param {Snowflake} num Snowflake to be transformed + * @returns {string} + * @private + */ + static idToBinary(num) { + let bin = ""; + let high = parseInt(num.slice(0, -10)) || 0; + let low = parseInt(num.slice(-10)); + while (low > 0 || high > 0) { + bin = String(low & 1) + bin; + low = Math.floor(low / 2); + if (high > 0) { + low += 5000000000 * (high % 2); + high = Math.floor(high / 2); + } + } + return bin; + } + + /** + * Transforms a snowflake from a bit string to a decimal string. + * @param {string} num Bit string to be transformed + * @returns {Snowflake} + * @private + */ + static binaryToID(num) { + let dec = ""; + + while (num.length > 50) { + const high = parseInt(num.slice(0, -32), 2); + const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); + + dec = (low % 10).toString() + dec; + num = + Math.floor(high / 10).toString(2) + + Math.floor(low / 10) + .toString(2) + .padStart(32, "0"); + } + + num = parseInt(num, 2); + while (num > 0) { + dec = (num % 10).toString() + dec; + num = Math.floor(num / 10); + } + + return dec; + } + + /** + * Generates a Discord snowflake. + * This hardcodes the worker ID as 1 and the process ID as 0. + * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate + * @returns {Snowflake} The generated snowflake + */ + static generate(timestamp = Date.now()) { + if (timestamp instanceof Date) timestamp = timestamp.getTime(); + if (typeof timestamp !== "number" || isNaN(timestamp)) { + throw new TypeError( + `"timestamp" argument must be a number (received ${isNaN(timestamp) ? "NaN" : typeof timestamp})` + ); + } + if (INCREMENT >= 4095) INCREMENT = 0; + const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, "0")}0000100000${(INCREMENT++) + .toString(2) + .padStart(12, "0")}`; + return SnowflakeUtil.binaryToID(BINARY); + } + + /** + * A deconstructed snowflake. + * @typedef {Object} DeconstructedSnowflake + * @property {number} timestamp Timestamp the snowflake was created + * @property {Date} date Date the snowflake was created + * @property {number} workerID Worker ID in the snowflake + * @property {number} processID Process ID in the snowflake + * @property {number} increment Increment in the snowflake + * @property {string} binary Binary representation of the snowflake + */ + + /** + * Deconstructs a Discord snowflake. + * @param {Snowflake} snowflake Snowflake to deconstruct + * @returns {DeconstructedSnowflake} Deconstructed snowflake + */ + static deconstruct(snowflake) { + const BINARY = SnowflakeUtil.idToBinary(snowflake).toString(2).padStart(64, "0"); + const res = { + timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH, + workerID: parseInt(BINARY.substring(42, 47), 2), + processID: parseInt(BINARY.substring(47, 52), 2), + increment: parseInt(BINARY.substring(52, 64), 2), + binary: BINARY, + }; + Object.defineProperty(res, "date", { + get: function get() { + return new Date(this.timestamp); + }, + enumerable: true, + }); + return res; + } + + /** + * Discord's epoch value (2015-01-01T00:00:00.000Z). + * @type {number} + * @readonly + */ + static get EPOCH() { + return EPOCH; + } +} + +module.exports = SnowflakeUtil; diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts new file mode 100644 index 000000000..7d09e4029 --- /dev/null +++ b/src/routes/attachments.ts @@ -0,0 +1,50 @@ +import { Router } from "express"; +import multer from "multer"; +import Snowflake from "../Snowflake"; + +const multer_ = multer(); +const router = Router(); + +type Attachment = { + filename: string; + file: string; + id: string; + type: string; +}; + +router.post("/:filename", multer_.single("attachment"), async (req, res) => { + const { buffer, mimetype } = req.file; + const { filename } = req.params; + const { db } = req.server; + + const File: Attachment = { + filename, + file: buffer.toString("base64"), + id: Snowflake.generate(), + type: mimetype, + }; + + if (!(await db.data.attachments.push(File))) throw new Error("Error uploading file"); + + return res.status(201).send({ success: true, message: "attachment uploaded", id: File.id, filename }); +}); + +router.get("/:hash/:filename", async (req, res) => { + const { db } = req.server; + const { hash, filename } = req.params; + + const File: Attachment = await db.data.attachments({ id: hash, filename: filename }).get(); + + res.set("Content-Type", File.type); + return res.send(Buffer.from(File.file, "base64")); +}); + +router.delete("/:hash/:filename", async (req, res) => { + const { hash, filename } = req.params; + const { db } = req.server; + + await db.data.attachments({ id: hash, filename: filename }).delete(); + return res.send({ success: true, message: "attachment deleted" }); +}); + +export default router; diff --git a/src/routes/attachments.ts.disabled b/src/routes/attachments.ts.disabled deleted file mode 100644 index db1a7efc5..000000000 --- a/src/routes/attachments.ts.disabled +++ /dev/null @@ -1,19 +0,0 @@ -import { Router } from "express"; -import multer from "multer"; -const multer_ = multer(); - -const router = Router(); -router.post("/:file", multer_.single("attachment"), async (req, res) => { - const { buffer } = req.file; - - res.set("Content-Type", "image/png"); - res.send(buffer); -}); -router.get("/:hash/:file", async (req, res) => { - res.send(`${req.params.hash}/${req.params.file}`); -}); -router.delete("/:hash/:file", async (req, res) => { - res.send("remove"); -}); - -export default router; From f1d63918efe1559137e14ee1252bf19152d4925e Mon Sep 17 00:00:00 2001 From: xnaclyDate: Thu, 31 Dec 2020 13:54:40 +0100 Subject: [PATCH 011/908] Update README.md --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 68cc50da4..ec28d6d5b 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,64 @@ cdn for discord clone ## Endpoints: +### `/attachments/ ` +#### POST +``` +Content-Type: form-data +attachment: File +``` +##### Returns: +``` +{ + "success": boolean, // true + "message": string, // "attachment uploaded" + "id": snowflake, // "794183329158135808" + "filename": string // "lakdoiauej.png" +} +``` +### `/attachments/ / ` +#### GET +``` +requests image from database with given and +``` +##### Returns: +``` +Content-Type: image/ +Image +``` +### `/attachments/ / ` +#### DELETE +``` +deletes database entry +``` +##### Returns: +``` +Content-Type: application/json + +{ + "success": true, + "message": "attachment deleted" +} +``` + +
+ +_(endpoints for crawler):_ ### `/external` #### POST ``` +requests crawling of `og:`metadata and the download of the `og:image` property +-------- Content-Type: application/json body: {"url": URL} // "https://discord.com" ``` ##### Returns: +``` Content-Type: application/json -```ts + { "id": string, // "aHR0cHM6Ly9kaXNjb3JkLmNvbQ==" "ogTitle": string, // "Discord | Your Place to Talk and Hang Out" @@ -25,6 +71,7 @@ Content-Type: application/json ``` ### `/external// ` #### GET +- requests cached crawled image ``` url-params: :id // aHR0cHM6Ly9kaXNjb3JkLmNvbQ== From b9d40cbca0c91fd3f42d42a47496e0f16e5ebe38 Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:55:28 +0100 Subject: [PATCH 012/908] Update README.md --- README.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ec28d6d5b..491db0c04 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ cdn for discord clone ## Endpoints: -### `/attachments/ ` -#### POST +### POST `/attachments/ ` ``` Content-Type: form-data @@ -18,8 +17,7 @@ attachment: File "filename": string // "lakdoiauej.png" } ``` -### `/attachments/ / ` -#### GET +### GET `/attachments/ / ` ``` requests image from database with given and ``` @@ -28,8 +26,7 @@ requests image from database with given and Content-Type: image/ Image ``` -### `/attachments/ / ` -#### DELETE +### DELETE `/attachments/ / ` ``` deletes database entry ``` @@ -46,8 +43,8 @@ Content-Type: application/json
_(endpoints for crawler):_ -### `/external` -#### POST +### POST `/external` + ``` requests crawling of `og:`metadata and the download of the `og:image` property -------- @@ -69,8 +66,7 @@ Content-Type: application/json "ogType": string // "website" } ``` -### `/external// ` -#### GET +### GET `/external/ / ` - requests cached crawled image ``` url-params: From f971d83785ff0bf31568aaa3ba955c4df012216e Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:56:35 +0100 Subject: [PATCH 013/908] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 491db0c04..9747e1758 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ cdn for discord clone ``` Content-Type: form-data -attachment: File +attachment: File (binary-data) ``` ##### Returns: ``` From 2e4e7b826d3a5b2eeaa7e12c188dbf2393221093 Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:57:48 +0100 Subject: [PATCH 014/908] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 9747e1758..d34bd264c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # Discord-CDN cdn for discord clone +## Run localy: +``` +npm i +node dist/ +``` + ## Endpoints: ### POST `/attachments/ ` ``` From 7b8b8b649e6f3879dd59d1c26ea65cad160b6d1b Mon Sep 17 00:00:00 2001 From: xnacly