From 0b0078328cab586883475e7d0de1dad689a4441b Mon Sep 17 00:00:00 2001 From: Rushil Perera Date: Fri, 8 Aug 2025 08:16:39 -0700 Subject: [PATCH] fix(aniplay): Migrates to Aniwatch only Removes Consumet and consolidates episode fetching to use Aniwatch as the sole provider. This simplifies the codebase and ensures a consistent data source for episodes and URLs. Also updates `wrangler` and sets `ENABLE_ANIFY` to false. --- bun.lockb | Bin 173010 -> 173772 bytes package.json | 2 +- src/controllers/auth/anilist/index.ts | 12 +- .../episodes/getByAniListId/aniwatch.ts | 10 +- .../episodes/getByAniListId/consumet.ts | 39 ----- .../episodes/getByAniListId/index.ts | 60 ++----- .../episodes/getEpisodeUrl/aniwatch.ts | 34 ++-- .../episodes/getEpisodeUrl/consumet.ts | 36 ---- .../episodes/getEpisodeUrl/index.spec.ts | 95 +---------- .../episodes/getEpisodeUrl/index.ts | 161 +++++------------- src/controllers/internal/new-episode/index.ts | 17 +- src/index.ts | 7 + wrangler.toml | 2 +- 13 files changed, 95 insertions(+), 380 deletions(-) delete mode 100644 src/controllers/episodes/getByAniListId/consumet.ts delete mode 100644 src/controllers/episodes/getEpisodeUrl/consumet.ts diff --git a/bun.lockb b/bun.lockb index 2604a0da8708fcfe97648fc308938e5ae5c09bd0..f2494453aa3e46bacbb87209ef5fa388ac5ebe9d 100755 GIT binary patch delta 29736 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

r3_3+PfSiw4T+OPU!v&Z64PJ;1|H)={lB-5 zlwZkbiu5#L41~M-^|)Swx^lf5If;^u{zIu~c(wl0?EaD{gjKGoojE|tas`rxIz80` zjuq8`DXOy`cp9?a3B58fJ5%l@w#Ko0N!t_A<|2GFg-l*W8I#*W5n1SH+e z8VlKmUfQdu8;#^yNQ$kWEsdNWB<)@ffTu}F-aRfUO{JuB9V=}BwjiD+_uMfml>_7m zNE^slNGnLQ94WsGbedtiq0@}Yhs2E0W(AIyrro55u;IUxtd% z6zmTlP7XlXsg&eO4SXRP_6ov$UQgvkFNRi;Mu{y9dD<3 z*W7f|FSZNM4v9T@&;Q=wuzbv zcKI#)Y}UjZn>uY0F0Fjv*8aKfHdm8Zy4xIUJhK)|G#)~TS4+Vl&`Z#ysNSop5twV|s5#X!)5TO{s zFwj@GnWIY8K@816sGS(Pk5Hr-YV4$h#v;^4ujy3I#zLKDeQvAFNT&Y^YK-M+ROfO!C%);wR<@Y zRM(5Y=pux9*Dz>;$*}43n^)YVe2kxGSh)Ni%D)ZWl=w?Pe}+`ahzjRbuk zjqW0lo?`!9LdaD(>*=emfMQxAVMW2n$x9s!jVgm|b-k+2fEEZ%2Ss^)^O$MlBm~&# zsV&`9D%^xoW20JL{6sgQps9vey9@fh8g(}m8iu$ELSaoWezm)h2l6RM>H$4!qS~Rc zN`=KsL)yTLk8Lc3`Dv=I0@;kn??uh*udd=DPoeK5m0$HbXmKw6nf61Eh?D9EofV|6 z0#=+(KUlKy5wtk3{zMzL$L#|#S-PNY1kR>`*kA^hDu>~8_EHx?qpro+WOx}FdC5J7 z98`Hd1pQEr;a#LafmvkV z5_uX3S^jDR)TYg!NYvf1D_ELwKavu=rd;-IxvW2${wE7F9n}VZ<)}*xFN38i^XIZ` zF%AFD4geeSx3t^JWj~k8w($Rp1*+FyIk8@s%4Mwr{*qrz8(%KF9&FIxYWuoe)+?~Q zw4=&pkAiLaH@lVKasI+Os15!`W;G~>q;a_Px1?az<}IX&X(oE2!O+4{vY}8|&x_yJ zLI{i0sI5^dHIpM!odJ#JAv^%Id}y@T)#5$jjhvDfj3T}DT1oZg1fx1$x_Qu=3R4{X zh{lYa;!R#WN;!@`_goz#e`4b^RK?jZc6QU@gbbE6RRjE)@ z$y2Jcp`E}F?H=o(9g}i8uS}#H>Pe&;9^ip@1HICAu~+#mZG^DS8ue|E=nLuy)YGA@w0@;QO@$_9pg~;+jaDHqcJ3Q! zlH)<;bsf;SNa3uLFFzzw(C?~IKLgR>d&vQCWzhkR{ zO2U)|zUlykq*T-cBijiD-8AYeAf>wqO^oX8B~4Fk-bGofC~Ed7ow8RHCq|;IiPJ=P z6|?}sFxXdJsiR8O38|zzZg*%jMR3Q${da+^>4=rS`d!2QOV=EkP|X-$bw`A#Mwqy` zuTASD=*Mc*=RwlV0t1DStr{(jLM5?c?V(Bjn+ll(jd}*zu@5*MEri8s)D>f-JFr+Q z-C$^~gorx6>d6R6^Nq&i3^W=KX$qTkE<4}aL!*I5UpRWH88n(3DxuH@JBiLhSiFWa z6eh;|tCPA&MVX4LX(Ke;{jo-junKQNBWEn#0PEq#N#i6<=wxW#(8N2wdKENkubyDk z-dhg}^@y|x?Yl|!swxy>u(i;r0GJH&Qg4Pv{Vi^S)SsbwiW+LEcIz&UA9^2ENrvWu zIB73&78;eH7RRYl4=E+eYv84BA#2jOYoSq(RT7N+z4f4ATFT4SzNb_%eQ|huLDL`( z9p>kyUJ8w>DS3_y(EP+WDxh|3+2XoE!(gMwXf(IVagz208Vv$8Wx%vSu~o3+rV2{MhC!n$iE;eW zcp)!IqrL`G8X+pLS8wU|FHQTOph+c=u{Y4jH0A&X%srv(Z0!$?;v^Ta4jT0t=C!+* zu2CP9K)V`s3krQtl>tqfH)Q=JG%8CfS-0h@!`_?fnB=7%2hA6np%B&ATTf1ie!>mmwXCUy!fsw_`$TDm zNwX*#8o6Jofa%bvp}OMaI48$Ro#50T4@VG(`zO84!1Y|B(7b_zlfV%}@tIr}v9ugIk$B*h*?C|HOX?#HDIdBgp6(v^Z{ zAtV)a6Co*a{S2kZ!3c$mruHEurPCdvq-%$e$f*|~6e9H#ew%qVM93Sh(XBI7EM&B= zZX!Zx142I`ge(Y^e2>)(Q@9BTN$JiZgnmLg<4h&C8$zfcxcu+j8-&nSaDl^>*r^Di ztyzBDa3L?tUuT43Djp%J@C^t_rb-Z!iVPSjwiTu(Ata?MLP$zyJ4)gDBP3Q$y%`}N zvDHyQUi{-xLcur{d5p$PMOqUG6QB=CGhzQs zFX7D0YFZB3JAmYSSr*IkgDgKn(uX9aD<9Y)DEtM@~(W_<6EkRuVN|=KoDn=?jsL zk}i_dlceBcjvj4G0hLNfX6E8#St2Lnyf@n zy>~%UZytc8jE5odqdGztSrUI-*8fe?oGd^(vU>)SvYeIWIhnt}p?r!cq>C&mcv05N zlVq?6@g#qf)0LHK@V8~YtR(7=%zu}DAgD$6At~;GoPZ<+AIf^UQgEKZQz+q<(oP>m(^(u`EBze0h@6eL_4X`7Eb{l+>l=43+=pXV?|5K%F;$jZjL~gCwaknp}CgUeuSKgB+ZaWSuZQ8m?-ch+d)#24!BVI zXj$(}Bt9g`pLdsal9VnMlJdvNJf!6Eh~R|d3u}2&w!U&SNeU+7LfMmKo+RlhvYx_k z`!qG|zk4?7T73S#r~B`o?O!*AVy~66S(JI%i~Vx~G2Nf`bTl#lyJwU71)DhWQ=X(K z+N;rrgJzJCi(VlHv z=K34<0qPDBjl$QsB;D=!uuj*PeKThqxAj>2JfP6m-!$H0UoVS|(M1C;8#_*M?pDLY zI8pbDdFG+&#|rDFzdG!h?l7j$Q(^Y`L4xVcaBhGQcQcwx5_aR7EEwI2<^~FhxTXk) zaUCRB-j3!53xjb@6;9!rCfMGI#^dZvTr-41T!#owccZzXLN>0$gsZq_3U2qJ@fdg_ zt|NrIxQ-Nj?niT@gcQXc#Z2AA@pH1H&$5oFj{zg z#Zb_F6wc)cQIDd9xmOK^?a(F&-0#ss$Th@W2cE&`7Y=@z)-}gW)$~yEwHM#Kg+U7z(Br!y~4Zt?L?1_X9x|PKE(CUGDh0hIPLGac_21%8)$< z7kBI~c-M67px7Mmzya!ym%JWted}ZL_53-X#_khp)`*@Mn7R0!qk~X-!$LTIF`Szr z7+s3yW(tXyqB{#^OSt*za)UxkquzC_54~B6cbtQzZu)+;5TVd{({e zbHm;z=PoaZ{yMVH+qY}t1KkUpDn%AW?mBt)kb9YS%n~e%qPf|^U|i=2r*LJ0?XS_? zTp<(Jc|sws^985hqPYb^_HWVJg=Oopeq_VPM{2z56}fI>ek|}P z+1RRk%7J+UwiG*!Z(6eQPQBgU65vi@nvoyX{s#9r(*gze6AyT!ttl`?bJKZl)Ep3+)wr~CBcj~RShtrSU zF8x@vVXVgl^G(fv-*+av5!Yq%Z#SN_t7YXk>l4RW2@CHX34SrC>d^O#g(dP69lb0V zT~gMtZ{JVaJ#Otk)#$AK@QrDU*S~FIx8dmi8C$scRnI*4Zz?#FS(Iw$=`!R>@bbA$ zR|fUk;+T76@T&L=iQ{uR`8!`0E3DF^o>*Ge?v;+KUb{c3JZ8<_hJH2TW23b7)p{2r zJbI1j@hyE?;uo{Jg%6w(i#_re^l#MQSlf4BOD@-a|JBav^@|I;-)XzK4qqV~rk+?< z*6{pGy^S69cXa4qEEvs63B8x`ad%WvQRUEEy;r8+sCvX=L!0@{-d@_BaI<=?3v&;t z{XSk@)#vyz7p*Whs)JjLEur-2QCX?W%NpLeZn2JLO2DS-Gx|lIeWX9M-Rjx{7tM)m z)8sciU#Se$}EY*ZW!T#_yY~ z8u#(I-m3E{r^-{d*%jz|oW9rj{faJkue{lKaCGwF9``nB+Ep)8yH#atSAXt`;$2xK zEn=VhJQ=_C#LHLPQj7do@Ig(s^d1?!dBpf(dre9{&dImzuq8V6^m^O3L+`0CbrwQP zzn*nX)_FDkExyEiU=h)C$*xbezt$YLxLUy1z4{j}{nV!U{yNp%T6bEPU-6!%)#|{N zHG=gYzFx84YRbA#+MsuPUtMrGGGgoL^(#wF{|IPI56+Y^S)*d>9Jx+H$U{S|hkt)V z_Zv^H{50+1t%oN9vO8RNj*cF9qkUb|!tpWt+$Xyv=6}7=yv_D&6AM)nn$($Akn_ul z!pqm+w_dnr%Ltx1?k7j5T&i|-X+zia=Iug{jA*rjS>4}# z+hblEou?+XEQ_j4Im<7My*l^k#>rYY=kDhhBwly!U3zMj@cf~rcHQX8xh!T=qmJYI z#MMi8G%7Lwm{D_l)RwIyujq!ge15+Aw95A)+kJiWDyM7W(aLjE$29CXZF}7@Gp=uT zUoy>OcbUqqFI%~nG5M<+{i(D*y5_ab6 z*WTg1$gqdUxEechj#YBV%;;CI{_5Y?_5MSrP2ind4>ueMYmu}u=A_`;-{asiS9(&Z zjL1fzGj5qNzZ(kse-Gz23+l(wg5G08?d8or_r0tgOOAZHW^h_{VAqNsaSgtfK726z zU4T}n#g~-kv!CvHnqqE}cW+G4%l`LH&#HH{(bUMR#}{Wi`rP8O7l}?(eED0orECX` zb1h_UO*ed4JMBf!pHxpXf)a|JJvGd8z3VzGzV!U8r7bSb%^0zMnzojEwbasie=OM1 zAk=A?b?!;MBlCUUkDFVOo?4T4t_`DG%Nou&e#P5mO1GyA3xdZo-ljNUc+(a|$L`i| z|MbDeoc?vU?#Nkd=3Vft^#Fs-=ht_Qm|SwJ%^D$MP6dZ)UJDb=KerL>N)_HFzK|dr z*Ad1%U#&aBV^)>OrvUv%+Yb#CyQ6?(hZnyl!zee&|g%_Fm>?EU%K_+_V6K@W5< zG(2;led5lSEvD6JaL)E*xBE+k?oTYVA!$Lm>r)m*EUMVvr|KcrGP?WcH5;>+bnjj~ zO?@x<{PWL#IdvylJ+!OtdE-FuajTx)aBs|OJHGk0E~P_Z{awbt%dN)4N7eSSt)9Ej z;okd#iH5Tx>VEt#K)tHrh$g_UXo_WXd_H7p*SZ)9_B+^{Kgx?a}mhfIBUOnqVOoSr8KxL5}b+~dE`^wj>9=9%wz z3@UAWG2Jwx$bZ<4@uuB_Le{<3$~{4*cPU08b=-xf&aK*aGdQ>Jmi|D$qOY5h7Bqdc zXKehOU0L?Ev_C(o(IZW@veCuF;c00r*9?h0bGlCZMVh6Fdmeqt?9lc>t{r{nM-wXx z#&(xA{A`c=)LLV|+|d5?;BEBj+etRRINZJIW3ayIzMPB&=dE{syz0{|?^TBfl@?9f zwYBH7;Tv}syVu;;HgDapwV&28sY2gk&<$FzHjM5mYk23o2VE;Jn?A~TtA72W1$zTO zRvf4Qz+$)VZ;u+kNw5Cp?eW<|&OgjGTD{WfhE7zj^YR{JF2!`~_Sn63$j0=Hjt}>W z6;|rIQ-(ONxQDz`SU{dM^-y_ z_G~@pbCq_?X=y*b%f~0Hw=MShqh_am*Oyz3*!xS(0N?d?t5Zj9o?z5jHDtRsy> zOiUW>c-(eLoNd(H89Q5z3mbFc`k8wz+R!&v_|c=)aiFZ-ryI5|S$TheT|4ce{(W98 ziHyshKG?$gL+GPXzgk^*@nlU`@7bQ3cg-rjd+vE~W`7I)4h6R7cC`NVIi|Dc+vMtI zuUg55$@;;vhD|@Xead_^;Mmd;xoc+_<1oI^H^n}4=X4}3mTG%WeZLuCy&(F=azmbAT-&xu2M$tSK| z-ZXw$cOA2!Y18epn+Lb8)AjMAmuoCjkJb9*xY+LcP2SI}&B$1T5sQzWUeUyCWbHO} zM7w(8BtBfmu2!dW_1JCx1DAw8?Y-DwwAq5=OH0je>*wXox*^yteDHGFj?0ga&TQvc zy<(Tz{>Qjm&l2nS>V|Ka@_BFz-D~3>#Wa&DOkaNBo5?bUbq95&UG1o>e8Ve=R+n9 z_e+^RT{fJB${sIU;pt~jIPD+aA?NDJ39~nxP0gM-PiMn|o=1nRIWR0mYgl@Tn|jl$ zc-ola(ms_xPjcz<_(}HH@w$_|rhdJ2Dr9rUO&@9ORC)n0`b$~EgNuWIE%5y0G&>`9 z#n3zNv%f`54YuuM(S3Er_v;t#u{mAZ`s=(&zXg@<%)RC4*XE0Haa8G$ZsF^NYC0CK zdo*tQP+{_#dep@y${G%sc*4lC|0<8GcRBecDN3A-Pb3~-BX_F`GvfqmNE?%JiOV#mmp zjh+0iOY1pH{F3TaS;L3ytd`y@J~bqDLEqX7@>f+j;JK}+X1zoAEjvxReL37KukgsW zrM?c;7e30-RG(+Dqj<62N8@^j%zO=v>s7ei=X1^eVvVI=u=cf~ zVE87SJ1ca4gU!$zY=)qn7u0XD8G36dB)<*k3WeR!c0sdw7tUQ065mA&{oWZ0XQBNn zSiX-IEZ!Rmquz&emxWW%PC|1o4(F~4nZ?nwD`-G<+pA3ak zXuk`gpYhb=v!Sr@b2#@zD1r77TK6yE+#f>z7qssS+E*IRJr_EcqJ5=kAGDW(`YYP^ z747>P&b=0PL)!(-=36-TR!IDY_I*S9puL}M$wkk$;0$Mv;=;KPvrj=g$r&u2vcTR8H7b; z5Th!CFlVQTI7x)F0SHT$X#irF0f?JKSTQF<5RQf*rWk^-VONQ`LPWDFAZoFRRY2rc z0r8v&Tjo<0gm+aCi>iXKXHSTDOhiOA5OvwSY9QuT15rvuJr-&NBE$&9Mk5eTtb~Y< zM0BqXq5;dV4q|O}5QfGeoLOgM5HZFe_7l;FsZBuWnSe+(0pZ4W6S0d38&eRCS)wV3 zex@MK648WNnt`w|12M`BgcmzS#7QEY%|ZCEOmh&!%t72F!k0N&fN;cwVpA+YG-FqZ zxI#oTOA!8SB4!-RwFL2;h(P931B7=C5Q}PnXu+Nk@tBARD-f;NJSz}$tw59#5zIoZ zL4;U?*k}zRl$8+ik%;a#Ai`O`4T!ZiAPj4Qh+v&-f{3XJVm}dWnYtDTy;>lWYk`Pj zyNTFEgiUP_?O9@N5dCU{I7>uFW@!t;!WP6RTM*Ig6cHzhaJB={nPu967-k3JCJ|kk zlRXGWdk|CXL3C$ViMT>UvpOJpvWay-GOW0i^ za+`u^+ZV)pB37_KKM;?JSmg&|6?;v@Tt5&Un}JxvmNf$r(hP*I21GuK z(t!9##C9UqGtM8xTKusLOYjG=k!>L&#vg=f0Eo>jE&zmH0Epv6Y+*)$Aa)Uv5eQ-% zJ4{5sKoE7CgV@dnHwR(S9K^3g>}0krK%68Zrv-@JtdNLdEkHDB31TnHZVAG%C5VSa z>}PJRKwKeWb}JAE*KWm$~6sEslI-bKEz6!G3mAEvO0b%61SAZ z4S)gC=^Hb8vz0z@N=G^4W&_2&ob+y=FQvgJ1so-%_bE%IvQ?>aI(nINv&^M2?igpR zO@~VFuhH9!zXJ4`BqygAsFY{vlVuJ=t7=Dy@R=fW1_*Z$8I@|P%+c$vL2{L*$y^n1 z!7?ZR3ZbPl98@7PDSbO2H9Z`hF5+g&nT-(shA@2unWOHYHvp-vvt$l0B&lA>>6G_u zO~BodxjAw=Q*gWJp#*)HOwzlE^h|+1b7hWthklcy&pb$~s|7&M+3B+YlAgp^0;6Sa z2_yw;02HRrQb@{c1<=EP`Yew`fWp>5OPQod3fcgsGPhFZYJ#gGbE{;o7C7bU?`n4U zI7W4i9A%5BIDkHDWzG&^daI5;^wtM{RQ3S9c}JghGFJy-dKZ8?emywqo4P;+Ii2#7 zt^+vb#Zh`;7e886J&+{`(P$i%NR=bP^lJ@$jzN+gC*TsoG#bCi>FOhV0%0nUB&BNr z97LD~zH!c_73$h>3&qRM}GipNkO)P-YK+UQi^%Mc^pD3D8sK zew8^-aP*!crKeYlDc$4abj7SV%MP62Hu=zdmrVg$=YBvlKm*YGne?tEeKAX~;nJG_ z9e50E05-BkCpr7DWCY1wkb6i3$g7a|=?(M&`T>1`cpw2t0(t?lKsSJ1QELHQSFtLm zxE@WCQRG0N1<(qhwN7iBmU3$#5NHmFOBRyeldZ+(o#LXkFJXpOC_Q6)1H1*E08fEO zz*XQ1a1*!=(8{G=xdz+pp-MC_P5m3DDbKnZRIxJl|Mg9FPTIfmRV; zr>9j*D|0CuRlu2Rdmz{npr>6yKrrwaz5f7s1w04-0A2tufoH%Q;5Bd;cm(_o+ykBh zPk=kXL*PDe6SxiB0xH0^FAxCG`ZnZLEmXz`7y%|gb)YIx8KA{p5vT;{0ct=Wr~%jj zmVh;2#WDTUTpGQW90T+Q5&&A%;;Xli96$^89qWsRzuqhuQ^*0XhOa-~h}*a$4eDfu3x}S@_YGNFD~z&4_L^ml5RzxC4GbW1uPE z0yG0Wfciiapb?+}0s&9JAMgSi0Iq;H&=Bwe+yH050N8+P!~)UeN86)NdNrC}m;QuE za>yi0fZM<&-~@00I0)nclYr^K3}6hf23QNs1=;{+KrLW13K1LXEP0rWzCB|r~Q z^KAY(F07X>!k>|z$}IwZ1&#m(z-c+W2y%8Akk=zGM|sWxb%446dAV4i7chv%5ub*a z$-~+CR705N-g00mumm8xG^G*%8aEnpTY%Oktu0!g1v~T4b5$#7Cn5Y3un-V{nZOJn z56A`j1Bn1_zs5jj16jZ*U?eaCp#I1Nh5 z1PD+>k|HS)C8kW2c{G6AlPU)o3yhO79&!RO5ugfEW2OVsfT_R~U@}01wh7n>tOS+= z)Zhj9R{0WF#gHcPY=8lC*py40dBYV5(xh4stOD|Z)xcU{4X_TNBI(0w7jd!LEnv<7 zr-6&WdEgvy7PtTu0`~#(YS)0Pz$M^Upa}R4xC~qYt^+rKI{X7!(jUdTIQ;jPF`T)6Ra?#{Z$n8`D zbYz`;KDlvSSsx1<6mJMr0jdFHm)2!XEiUBS$*+?EE1-rPCgbEc%Nmb@9tqImCXYj& zh#VR%I9ho2fE}O=Pg8`+>yQT`FXaN%1IPoB_tHAzLT--S6S*tW$%T>IBiBW`l7U^Nc^9A?K=YypWOpDQhy!}bavbDXpbwD9Vei-%fqp;| z&>t88;KhwOm$`O^0})OJ$iO)E{xY^S;}OgO7Ry)$NyB**awCurtOiyAD*>v@3dps< z8el6xE_^@m6R;WB1W?}f02Q?k*Z?Sbh$n2J4x<1WQ8N6D@HT)F?*O*T`X0#Lz)oNn zuou_|Q2xWfA>be&x(_tq2*Pt9iKhlo+%X{bGA{V6HiIKq?QDLPyUyvjHFj~`X?vYJ zW6Zg;7caQc6&ky^v6Ppbjb)mA_V91F*PNO9Z9$0+*TdDNabuSzY|=|SKHc$_Yh$e( z1AOPO>DO^r`aRI$q$F4f_N?|h&cahUI5_(}U$6aGe$PT&h>5Xh z11Ya^8u6gjnq6Z?{ITRm3gx`w5zl(PG8mh`{71}Od$tdGt(6mw^Y_L+T`{K5kRK_I z+p||>T{#o^Wz5alRl>HL|45;nntWpU*9%=AJzS~7xw*KyG(oQ!*J0C(VO=?Axvh?s zO>*TUaZ(Boq-b4-bwdiie;qdYJyI$sHMiLD`DWq1C3rYOwkYMAI_wZqcq(T*$98CE z@vOt%Ha}AQbBvt#U#!dJ^KyOurN&B|{<+nmc>AlEbN5tEb>5@9Y^BbrHCuSDhntI! ziz^mVL+QCqufcVi1yru;jTm<{(+&5QhOFTS^n-FTbYaZ83!(3pJQVY~Qr<4kEEXwh zC}%_$51cc`M1QqCQeZNX4R+`y*N{K%!d|@OqBsv0@QMqs(8R@qO?|~hHdan^Rt{R^ zxyHywJ**u3tUc4mU(a1n1B*>u+`Tb_U%ue1JeBjL*XE>~Hrz6-9nZPDxKnv5<-}=w zySoXTYwz_u1`Wf8m)hB^%B!4Qt!j}tuSSz>M;)%No%A?Q zIotZ&J>Q;VQ;*;O5o6`STE9Wfl+&=gXnSD`Ltz}aDBZG1%uHgI3zW50+iRnfbu3a$b4E0p^w7 z9_=6WBZG3hZ9C0{2G%x4YktHm_GK4Ptt4bu4%1y9l-NDhzuv+hiIn4alVX>D`)$VV zz#lQnQR?nSqdMK))2`Q#80CoG!e!NLjyXPQ`y(cjMSbEdxmY#;!b&-c+|7Sw-H~IB zYG4MDQ$T%m*pg3NV{7G5^MkdEH@uqQ?vU$)O*&Jeb-LiMhwki7%ytY{653^KWN1|edgTxm#tVHB<_~`KXaB%lmpP4y*;$0 zpjwlD$cWWO>rFWlT_?u9!`X`Kjw7ZCOd_TwtNev)jBhyozi^iP&mpYK7p}F{->X11 zpgU9sJ4^`=hq4=AP?J?*toRFR(2zNma(%6pK$3ybqTf*6fQe@a0 z&VDI{uU8K2K9|$tS+$+NRFE49x2haY?vQ`uQzzrWd5Ce7e8Zz~=JJ)Zuu_gGKkaed zrR&9tLy&?T9J;O&i~EXEvS1@1td!%>^DSrH9Nx*h71E&*)M|gWnGz`HsefMNThFnv z#zrhk?3N66|0{-3Ie7g*v*Eiv+q&o>frr!)Us^NwZ(M6GglWHVmY&Ly?q4p4@vSmqC-;-b$|V;?!o1aJ2mCipGkv#ogxtAB2{*eVlWv4zS}?nN0M zeMTMH{9G&ut;deLwRHIK-&=bja`s~0!t%br&jOxXTXv? zcDe1@a*lVmR?dcB*>UB%d8fO?BZWKW5T^gq_UsjhhA5}WH<-3Swb5?&QL#2+?d5ZI%06wq=p=9&on$*q8!!U@zS$8!&9ntMhZ`9pbXexp0~t%3wL#ROCIUJ z=sEnx=hud_=@s}sRsmh4bEr2buK)Fn@c}cKl$T#yW`xQ~bu?lAI(#j7q@F*h zO5&803+7L?XPc-E<+aS6>%|)B@)7uUdz3EUm|V$LUEUpERX-$F@+kbW?yOBk^tf`K zy!zXWC9&yuIk`set}5lk`85-tCpg481|vrHEJwSug%!~%)Zzkcpcb}zV%;mNrT(p1 z36_=rB+!Lhcs=ODl0nGtiH^ncD5uNaXp7^jI@p6HAuo*)+2ApAmXa-lSn10@)tCco zPX?v_BG(0@ZzjhC_Vf?dS-;>VV#qT}^Fp-o=UVcwIxs&yDnc&mMJyYn$Gcno^8_1$ zS7)#gl#}?2=gwc(Z&{!kCDAU0>cC$AXus4OCY8h(6c(U{api>j-`WJN`D5B$59F1n zk|~>lPHUo^z8^U{Y2S@4+ct>J5O;R}T(A1z$%#BJ(!UZ zZ&FcAE$;3td2CYlAemt3R$hn`yE8eDh`q*zrHCuQypCx z+f@1w3ZAXc>{vGQ=ixtkr7`;kOL(u11amj$J*=1EA!s!`AOEBOi1F&Co=bQRKGe&_ z3(uRhEWixYpSFkxv}~a<9EI}F7tD+wwRX%6IV`7!!?~(u7mRUZc%@}JCVYK+E=!sV zdfDRltXYpDhgXQ`IBzFbC<^dI0rgpw2`b~l20)Zk52SOa8-xECw$ubSg4i+Qx42ma zW}i&;tG@nKv?Qh&ffyUKq3P~P9X^}(-YUhwr>N$RVK0!ESK31M&T5%~8E?Uwn(`i; zD@!pY2VY(--92DIswIz12TakofA0XQCKZJ{0W48MvTm*XyNR&v-|pP%cBi9K+a_|q zTF55NYPzcAe?^gXaB#ir&$MACQd<1(tlFK!rotLL?iw@P&jZJ^8&G+LHeglF(Ra#! zsF=EJYsb=2=cz36f^ewVgtRp0-95|Mt;i2y{{D!Co?76s0`(I$*;@Jk6$?yvKCO|n zZvd>*9UImMxv@L2Ze8xP3Gp_BXA>?Ku%Z0#i@RUXZgTW6oGaE)d~6~=z2GHVW;z~J z?i>r=y^ivawUqyPA(o462%g-F4Q?M*T7&nt zbD~F;F6n)T(^kvXIW^wJtqFebaCL9u>VZ*nMIXyyw!n{%)fGv$tPyX`tef#(I~_uK zzB0QU!dtOVt9S#}vl+kK1Y2Y>i9QxHH+F|tK> z!CDRh=jP8}SBoW3_MLVCe5DE%ptFW8_+lOpDvTEM_U!X2-iR$|!Q=I{mVC`W)1z9z z>hYGm&7bMm7O13sE8hCgbVe(_E-_m?P>lnv_-dAT-hn4?RC)mYUo8ATtVX>u(t4$2 zvZx^5+Vam_BWF3%*_~x)@O5nIAE1F0ADuaikITps{dcng>Ga>aU?rQkZtz1#;xe($ z^c#{lW$Bxc?wppAks8ll_vNkazN-ny@Iyg*xPEua!1m_xacob0-mwBmRv5zDx+_nP zkVgqRi?hB7o{*X*j!|HFF|bh{E!y)CYtSokU?chYmRMaR^X7bg)mmxN6(Bv|Wj_l6Y`ChH+Z1FU{>i5*Bhd#U49X+r- emABn#(}O=}%zKtN#yK;a431 diff --git a/package.json b/package.json index 028fbb1..f3368f0 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "prettier-plugin-toml": "^2.0.4", "ts-morph": "^22.0.0", "typescript": "^5.8.3", - "wrangler": "^4.13.0", + "wrangler": "^4.28.1", "zx": "8.1.5" }, "lint-staged": { diff --git a/src/controllers/auth/anilist/index.ts b/src/controllers/auth/anilist/index.ts index e3bd194..a6b7e56 100644 --- a/src/controllers/auth/anilist/index.ts +++ b/src/controllers/auth/anilist/index.ts @@ -4,11 +4,10 @@ import { streamSSE } from "hono/streaming"; import { fetchEpisodes } from "~/controllers/episodes/getByAniListId"; import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode"; -import { sleep } from "~/libs/sleep"; import { associateDeviceIdWithUsername } from "~/models/token"; import { setWatchStatus } from "~/models/watchStatus"; import type { Env } from "~/types/env"; -import { Episode, EpisodesResponseSchema } from "~/types/episode"; +import { EpisodesResponseSchema } from "~/types/episode"; import { ErrorResponse, ErrorResponseSchema } from "~/types/schema"; import { Title } from "~/types/title"; @@ -186,13 +185,8 @@ app.openapi(route, async (c) => { continue; } - await fetchEpisodes( - media.id, - { ...env(c, "workerd"), ENABLE_ANIFY: "false" }, - true, - ).then(({ result: episodesResult }) => { - const episodes = episodesResult?.episodes; - if (!episodes) { + await fetchEpisodes(media.id, true).then(({ episodes }) => { + if (episodes.length === 0) { return; } diff --git a/src/controllers/episodes/getByAniListId/aniwatch.ts b/src/controllers/episodes/getByAniListId/aniwatch.ts index fdfc4b6..094b93b 100644 --- a/src/controllers/episodes/getByAniListId/aniwatch.ts +++ b/src/controllers/episodes/getByAniListId/aniwatch.ts @@ -97,12 +97,12 @@ async function fetchEpisodes( .then( (res) => res.json() as Promise<{ - success: boolean; + status: number; data: AniwatchEpisodesResponse; }>, ) - .then(({ success, data }) => { - if (!success || data.totalEpisodes === 0) { + .then(({ status, data }) => { + if (status >= 300 || data.totalEpisodes === 0) { console.error( `Error trying to load episodes from aniwatch; aniListId: ${aniListId}, totalEpisodes: ${data.totalEpisodes}`, ); @@ -164,12 +164,12 @@ function getAniwatchId( } const json = (await res.value.json()) as { - success: boolean; + status: number; data: AniwatchSearchResponse; }; const currentValue = await current; return { - success: currentValue.success || json.success, + success: currentValue.success || json.status === 200, data: { ...currentValue.data, animes: [ diff --git a/src/controllers/episodes/getByAniListId/consumet.ts b/src/controllers/episodes/getByAniListId/consumet.ts deleted file mode 100644 index 4801295..0000000 --- a/src/controllers/episodes/getByAniListId/consumet.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { aniList } from "~/consumet"; - -import { Episode, EpisodesResponse } from "./episode"; - -export async function getEpisodesFromConsumet( - aniListId: number, -): Promise { - try { - const episodes: Episode[] = await aniList - .fetchEpisodesListById(aniListId.toString()) - .then((episodes) => - episodes.map( - ({ id, number, title, image: img, description }): Episode => ({ - id, - number, - title, - img, - description, - rating: undefined, - updatedAt: 0, - }), - ), - ); - if (!episodes || episodes.length === 0) { - return null; - } - - return { providerId: "consumet", episodes }; - } catch (error: any) { - if (!error.message.includes("failed with status code")) { - console.error( - `Error trying to load episodes from consumet; aniListId: ${aniListId}`, - ); - console.error(error); - } - } - - return null; -} diff --git a/src/controllers/episodes/getByAniListId/index.ts b/src/controllers/episodes/getByAniListId/index.ts index 71f95c1..f7f790c 100644 --- a/src/controllers/episodes/getByAniListId/index.ts +++ b/src/controllers/episodes/getByAniListId/index.ts @@ -1,17 +1,13 @@ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"; -import { env } from "hono/adapter"; -import { fetchFromMultipleSources } from "~/libs/fetchFromMultipleSources"; import type { Env } from "~/types/env"; -import { EpisodesResponse, EpisodesResponseSchema } from "~/types/episode"; +import { EpisodesResponseSchema } from "~/types/episode"; import { AniListIdQuerySchema, ErrorResponse, ErrorResponseSchema, } from "~/types/schema"; -import { getEpisodesFromAnify } from "./anify"; - const route = createRoute({ tags: ["aniplay", "episodes"], summary: "Fetch episodes for a title", @@ -43,61 +39,25 @@ const route = createRoute({ const app = new OpenAPIHono(); -export function fetchEpisodesFromAllProviders( - aniListId: number, - env: Env, -): Promise { - return Promise.allSettled([ - import("./aniwatch").then(({ getEpisodesFromAniwatch }) => - getEpisodesFromAniwatch(aniListId), - ), - getEpisodesFromAnify(env, aniListId), - ]).then((episodeResults) => - episodeResults - .filter((result) => result.status === "fulfilled") - .map((result) => result.value) - .filter((episodes) => !!episodes), - ); -} - -export function fetchEpisodes( - aniListId: number, - env: Env, - shouldRetry: boolean = false, -) { - return fetchFromMultipleSources([ - () => - import("./aniwatch").then(({ getEpisodesFromAniwatch }) => - getEpisodesFromAniwatch(aniListId, shouldRetry), - ), - () => getEpisodesFromAnify(env, aniListId), - // () => - // import("./consumet").then(({ getEpisodesFromConsumet }) => - // getEpisodesFromConsumet(aniListId), - // ), - ]); +export function fetchEpisodes(aniListId: number, shouldRetry: boolean = false) { + return import("./aniwatch") + .then(({ getEpisodesFromAniwatch }) => + getEpisodesFromAniwatch(aniListId, shouldRetry), + ) + .then((episodeResults) => episodeResults?.episodes ?? []); } app.openapi(route, async (c) => { const aniListId = Number(c.req.param("aniListId")); - const { result, errorOccurred } = await fetchEpisodes( - aniListId, - env(c, "workerd"), - ); - - if (errorOccurred || !result) { - return c.json(ErrorResponse, { status: 500 }); - } - - const { episodes, providerId } = result; - if (!episodes || episodes.length === 0) { + const episodes = await fetchEpisodes(aniListId); + if (episodes.length === 0) { return c.json(ErrorResponse, { status: 404 }); } return c.json({ success: true, - result: { providerId, episodes }, + result: { providerId: "aniwatch", episodes }, }); }); diff --git a/src/controllers/episodes/getEpisodeUrl/aniwatch.ts b/src/controllers/episodes/getEpisodeUrl/aniwatch.ts index dbdb0fd..28ab614 100644 --- a/src/controllers/episodes/getEpisodeUrl/aniwatch.ts +++ b/src/controllers/episodes/getEpisodeUrl/aniwatch.ts @@ -36,12 +36,12 @@ async function getEpisodeUrl(watchId: string, server?: string) { .then( (res) => res.json() as Promise<{ - success: boolean; + status: number; data: AniwatchEpisodeUrlResponse; }>, ) - .then(({ success, data }) => { - if (!success || !data.sources || data.sources.length === 0) { + .then(({ status, data }) => { + if (status >= 300 || !data.sources || data.sources.length === 0) { return { source: null }; } @@ -50,9 +50,10 @@ async function getEpisodeUrl(watchId: string, server?: string) { intro: convertSkipTime(intro), outro: convertSkipTime(outro), source: sources[0].url, - subtitles: tracks - .filter(({ kind }) => kind === "captions") - .map(({ file, label }) => ({ url: file, lang: label ?? "" })), + subtitles: tracks.map(({ url, lang }) => ({ + url, + lang, + })), headers, }; }); @@ -79,8 +80,8 @@ async function getEpisodeServers(watchId: string) { ) .then((res) => res.json() as Promise) .then((res) => { - if (!res.success) { - throw new Error(res.message); + if (res.status >= 300 || !res.data) { + throw new Error("Failed to fetch episode servers"); } return res; @@ -105,21 +106,16 @@ interface Source { } interface Track { - file: string; - label?: string; + url: string; + lang?: string; kind: string; default?: boolean; } -type AniwatchEpisodeServersResponse = - | { - success: true; - data: AniwatchEpisodeServers; - } - | { - success: false; - message: string; - }; +interface AniwatchEpisodeServersResponse { + status: number; + data: AniwatchEpisodeServers; +} interface AniwatchEpisodeServers { sub: AniwatchEpisodeServer[]; diff --git a/src/controllers/episodes/getEpisodeUrl/consumet.ts b/src/controllers/episodes/getEpisodeUrl/consumet.ts deleted file mode 100644 index 45ca582..0000000 --- a/src/controllers/episodes/getEpisodeUrl/consumet.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { aniList } from "~/consumet"; -import { sortByProperty } from "~/libs/sortByProperty"; -import type { FetchUrlResponse } from "~/types/episode/fetch-url-response"; - -import { qualityPriority, subtitlesPriority } from "./priorities"; - -export async function getSourcesFromConsumet( - watchId: string, -): Promise { - try { - const { sources, subtitles, intro, outro } = - await aniList.fetchEpisodeSources(watchId); - - if (sources.length === 0) { - return null; - } - - const source = sources.sort(sortByProperty(qualityPriority, "quality"))[0] - ?.url; - subtitles?.sort(sortByProperty(subtitlesPriority, "lang")); - - return { - source, - subtitles: subtitles ?? [], - audio: [], - intro: intro ? [intro.start, intro.end] : undefined, - outro: outro ? [outro.start, outro.end] : undefined, - }; - } catch (error) { - if (error.message === "Episode not found.") { - return null; - } - - throw error; - } -} diff --git a/src/controllers/episodes/getEpisodeUrl/index.spec.ts b/src/controllers/episodes/getEpisodeUrl/index.spec.ts index 483536b..adb69d3 100644 --- a/src/controllers/episodes/getEpisodeUrl/index.spec.ts +++ b/src/controllers/episodes/getEpisodeUrl/index.spec.ts @@ -6,14 +6,13 @@ import { server } from "~/mocks"; server.listen(); describe('requests the "/episodes/:id/url" route', () => { - it("with sources from Anify", async () => { + it("with sources from Aniwatch", async () => { const response = await app.request( - "/episodes/1/url", + "/episodes/4/url", { method: "POST", body: JSON.stringify({ - provider: "anify", - id: "/ore-dake-level-up-na-ken-episode-2", + episodeNumber: 1, }), headers: { "Content-Type": "application/json" }, }, @@ -26,19 +25,18 @@ describe('requests the "/episodes/:id/url" route', () => { success: true, result: { source: - "https://proxy.anify.tv/video/8CLGIJg8G3k%252BH%252BYV9xyOYVGZ8al8uZqqtbXk44wKRco%252BGATkCrqlkgdRiam3owmOU4f2MAB89GOblOuZbxifwbGsjvp32uxhRC4kZVYrWnZmP%252FrLxtqwi0n6zY%252BvrffUh6dbg6DADSLCWhd2bNUUIg%253D%253D/%7B%7D/.m3u8", + "https://www032.vipanicdn.net/streamhls/aa804a2400535d84dd59454b28d329fb/ep.1.1712504065.m3u8", subtitles: [], audio: [], }, }); }); - it("with no URL from Anify source", async () => { + it("with no URL from Aniwatch source", async () => { const response = await app.request("/episodes/-1/url", { method: "POST", body: JSON.stringify({ - provider: "anify", - id: "/ore-dake-level-up-na-ken-episode-2", + episodeNumber: -1, }), headers: { "Content-Type": "application/json" }, }); @@ -46,85 +44,4 @@ describe('requests the "/episodes/:id/url" route', () => { expect(response.json()).resolves.toEqual({ success: false }); expect(response.status).toBe(404); }); - - it("with sources from Consumet", async () => { - const response = await app.request( - "/episodes/1/url", - { - method: "POST", - body: JSON.stringify({ - provider: "consumet", - id: "/ore-dake-level-up-na-ken-episode-2", - }), - headers: { "Content-Type": "application/json" }, - }, - { - ENABLE_ANIFY: "true", - }, - ); - - expect(response.json()).resolves.toEqual({ - success: true, - result: { - source: "https://consumet.com", - subtitles: [], - audio: [], - }, - }); - }); - - it("with no URL from Consumet source", async () => { - const response = await app.request("/episodes/-1/url", { - method: "POST", - body: JSON.stringify({ - provider: "consumet", - id: "unknown", - }), - headers: { "Content-Type": "application/json" }, - }); - - expect(response.json()).resolves.toEqual({ success: false }); - expect(response.status).toBe(404); - }); - - // it("with sources from Aniwatch", async () => { - // const response = await app.request( - // "/episodes/1/url", - // { - // method: "POST", - // body: JSON.stringify({ - // provider: "aniwatch", - // id: "ore-dake-level-up-na-ken-episode-2", - // }), - // headers: { "Content-Type": "application/json" }, - // }, - // { - // ENABLE_ANIFY: "true", - // }, - // ); - - // expect(response.json()).resolves.toEqual({ - // success: true, - // result: { - // source: - // "https://www032.vipanicdn.net/streamhls/aa804a2400535d84dd59454b28d329fb/ep.1.1712504065.m3u8", - // subtitles: [], - // audio: [], - // }, - // }); - // }); - - // it("with no URL from Aniwatch source", async () => { - // const response = await app.request("/episodes/-1/url", { - // method: "POST", - // body: JSON.stringify({ - // provider: "aniwatch", - // id: "unknown", - // }), - // headers: { "Content-Type": "application/json" }, - // }); - - // expect(response.json()).resolves.toEqual({ success: false }); - // expect(response.status).toBe(404); - // }); }); diff --git a/src/controllers/episodes/getEpisodeUrl/index.ts b/src/controllers/episodes/getEpisodeUrl/index.ts index 77d5bab..9b3d7c0 100644 --- a/src/controllers/episodes/getEpisodeUrl/index.ts +++ b/src/controllers/episodes/getEpisodeUrl/index.ts @@ -1,9 +1,6 @@ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"; -import { env } from "hono/adapter"; -import { readEnvVariable } from "~/libs/readEnvVariable"; import type { Env } from "~/types/env"; -import type { Episode } from "~/types/episode"; import { FetchUrlResponse } from "~/types/episode/fetch-url-response"; import { AniListIdQuerySchema, @@ -12,12 +9,9 @@ import { ErrorResponseSchema, } from "~/types/schema"; -import { fetchEpisodesFromAllProviders } from "../getByAniListId"; +import { fetchEpisodes } from "../getByAniListId"; -const FetchUrlRequest = z.union([ - z.object({ id: z.string(), provider: z.string() }), - z.object({ episodeNumber: EpisodeNumberSchema }), -]); +const FetchUrlRequest = z.object({ episodeNumber: EpisodeNumberSchema }); const route = createRoute({ tags: ["aniplay", "episodes"], @@ -73,89 +67,40 @@ const route = createRoute({ const app = new OpenAPIHono(); -export async function fetchEpisodeUrlFromAllProviders( - aniListId: number, - episodeNumber: number, - env: Env, -) { - const results = await fetchEpisodesFromAllProviders(aniListId, env); - if (results.length === 0) { - return { episodes: null, fetchUrlResult: null }; - } - - let episodes: Episode[] | null = null; - let fetchUrlResult: FetchUrlResponse | null = null; - - for (const { episodes: episodesResult, providerId } of results) { - const episode = episodesResult.find( - (episode) => episode.number === episodeNumber, - ); - if (!episode) { - continue; - } - episodes = episodesResult; - - const urlResult = await fetchEpisodeUrl( - providerId, - episode.id, - aniListId, - readEnvVariable(env, "ENABLE_ANIFY"), - ); - if (!urlResult) { - episodes = null; - continue; - } - - fetchUrlResult = urlResult; - break; - } - - return { episodes, fetchUrlResult }; -} - -export async function fetchEpisodeUrl( - provider: string, - id: string, - aniListId: number, - isAnifyEnabled: boolean, -): Promise { - if (provider === "consumet" || !isAnifyEnabled) { - try { - const result = await import("./consumet").then( - ({ getSourcesFromConsumet }) => getSourcesFromConsumet(id), - ); - if (!result) { - return null; - } - - return result; - } catch (e) { - console.error("Failed to fetch download URL from Consumet", e); - - throw e; - } - } - - if (provider === "aniwatch") { - try { - const result = await import("./aniwatch").then( - ({ getSourcesFromAniwatch }) => getSourcesFromAniwatch(id), - ); - if (!result) { - return null; - } - - return result; - } catch (e) { - console.error("Failed to fetch download URL from Aniwatch", e); - - throw e; - } - } - +export async function fetchEpisodeUrl({ + id, + aniListId, + episodeNumber, +}: + | { id: string; aniListId?: number; episodeNumber?: number } + | { + id?: string; + aniListId: number; + episodeNumber: number; + }): Promise { try { - const result = await import("./anify").then(({ getSourcesFromAnify }) => - getSourcesFromAnify(provider, id, aniListId), + let episodeId = id; + if (!id) { + const episodes = await fetchEpisodes(aniListId!); + if (episodes.length === 0) { + console.error(`Failed to fetch episodes for title ${aniListId}`); + return null; + } + const episode = episodes.find( + (episode) => episode.number === episodeNumber, + ); + if (!episode) { + console.error( + `Episode ${episodeNumber} not found for title ${aniListId}`, + ); + return null; + } + + episodeId = episode.id; + } + + const result = await import("./aniwatch").then( + ({ getSourcesFromAniwatch }) => getSourcesFromAniwatch(episodeId!), ); if (!result) { return null; @@ -163,7 +108,7 @@ export async function fetchEpisodeUrl( return result; } catch (e) { - console.error("Failed to fetch download URL from Anify", e); + console.error("Failed to fetch download URL from Aniwatch", e); throw e; } @@ -171,37 +116,21 @@ export async function fetchEpisodeUrl( app.openapi(route, async (c) => { const aniListId = Number(c.req.param("aniListId")); - const { provider, id, episodeNumber } = - await c.req.json(); - if (!provider && episodeNumber == undefined) { + const { episodeNumber } = await c.req.json(); + if (episodeNumber == undefined) { return c.json(ErrorResponse, { status: 400 }); } try { - const isAnifyEnabled = readEnvVariable(c.env, "ENABLE_ANIFY"); - let result: FetchUrlResponse | null; - if (provider) { - console.log(`Fetching sources from ${provider} for ${aniListId}`); - result = await fetchEpisodeUrl(provider, id, aniListId, isAnifyEnabled); - } else { - console.log(`Fetching sources from all providers for ${aniListId}`); - const { fetchUrlResult } = await fetchEpisodeUrlFromAllProviders( - aniListId, - episodeNumber!, - env(c, "workerd"), - ); - if (!fetchUrlResult) { - return c.json(ErrorResponse, { status: 404 }); - } - - result = fetchUrlResult; - } - - if (result) { - return c.json({ success: true, result }); - } else { + console.log( + `Fetching episode URL for aniListId: ${aniListId}, episodeNumber: ${episodeNumber}`, + ); + const fetchUrlResult = await fetchEpisodeUrl({ aniListId, episodeNumber }); + if (!fetchUrlResult) { return c.json(ErrorResponse, { status: 404 }); } + + return c.json({ success: true, result: fetchUrlResult }); } catch (error) { return c.json(ErrorResponse, { status: 500 }); } diff --git a/src/controllers/internal/new-episode/index.ts b/src/controllers/internal/new-episode/index.ts index 10c2826..25f9011 100644 --- a/src/controllers/internal/new-episode/index.ts +++ b/src/controllers/internal/new-episode/index.ts @@ -3,7 +3,7 @@ import { Hono } from "hono"; import { env } from "hono/adapter"; import { z } from "zod"; -import { fetchEpisodeUrlFromAllProviders } from "~/controllers/episodes/getEpisodeUrl"; +import { fetchEpisodeUrl } from "~/controllers/episodes/getEpisodeUrl"; import { getAdminSdkCredentials } from "~/libs/gcloud/getAdminSdkCredentials"; import { sendFcmMessage } from "~/libs/gcloud/sendFcmMessage"; import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode"; @@ -44,20 +44,7 @@ app.post( ); } - const { episodes, fetchUrlResult } = await fetchEpisodeUrlFromAllProviders( - aniListId, - episodeNumber, - env(c, "workerd"), - ); - - if (!episodes) { - console.error(`Failed to fetch episodes for title ${aniListId}`); - return c.json( - { success: false, message: "Failed to fetch episodes" }, - 500, - ); - } - + const fetchUrlResult = await fetchEpisodeUrl({ aniListId, episodeNumber }); if (!fetchUrlResult) { console.error(`Failed to fetch episode URL for episode`); return c.json( diff --git a/src/index.ts b/src/index.ts index 392442c..332cae7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,3 +66,10 @@ app.doc("/openapi.json", { app.get("/docs", swaggerUI({ url: "/openapi.json" })); export default app; + +export class AniwatchApiContainer /* extends Container */ { + // Port the container listens on (default: 8080) + defaultPort = 4444; + // Time before container sleeps due to inactivity (default: 30s) + sleepAfter = "2m"; +} diff --git a/wrangler.toml b/wrangler.toml index 0390810..0170230 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -6,7 +6,7 @@ compatibility_date = "2024-09-23" [vars] TURSO_URL = "libsql://aniplay-v2-silverandroid.turso.io" -ENABLE_ANIFY = true +ENABLE_ANIFY = false [observability] enabled = true