@serifpersia/dango 3.3.5 → 3.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-CXx2EIN7.js → AnimeCard-WlhU88fn.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-C6ehrfYW.js → AnimeInfo-BCLagpbu.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-pgnMg0On.js → AnimeInfoPage-o0U_OXKP.js} +2 -2
  5. package/client/dist/assets/{Asmr-DfA8xB-U.js → Asmr-ieGJ_BcQ.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-QUNnEh9C.js → AsmrResetProgressModal-BwaoVski.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-C1_iBSyL.css → AvSyncCalibrator-BPa6KaGC.css} +1 -1
  8. package/client/dist/assets/AvSyncCalibrator-CXvBuY4l.js +10 -0
  9. package/client/dist/assets/Home-DjLpvsp8.js +1 -0
  10. package/client/dist/assets/Insights-Dc8xLmsr.css +1 -0
  11. package/client/dist/assets/Insights-lQcQoeES.js +1 -0
  12. package/client/dist/assets/LibraryListPage-B2nhFFrc.js +1 -0
  13. package/client/dist/assets/{ListeningList-BzbQwmh0.js → ListeningList-okIpsKCr.js} +1 -1
  14. package/client/dist/assets/{Manga-223panCD.js → Manga-3u9nVNZC.js} +1 -1
  15. package/client/dist/assets/{MangaInfoPage-DBqX-Ord.js → MangaInfoPage-Cat-B487.js} +1 -1
  16. package/client/dist/assets/{MangaReadPage-DuXdVyTd.js → MangaReadPage-CyLRxzvl.js} +1 -1
  17. package/client/dist/assets/{MangaResetProgressModal-6C3iaR6g.js → MangaResetProgressModal-Ch8RjwbO.js} +1 -1
  18. package/client/dist/assets/Mature-pINwl5wj.js +1 -0
  19. package/client/dist/assets/{MediaCard-xfJv6irD.js → MediaCard-DDiYEAm8.js} +1 -1
  20. package/client/dist/assets/{Music-DkIeCRq1.js → Music-B1swlVLI.js} +1 -1
  21. package/client/dist/assets/{OptionListMenu-JQ_--ggT.js → OptionListMenu-CzH2VSPh.js} +1 -1
  22. package/client/dist/assets/Player-SKPWb-o8.js +56 -0
  23. package/client/dist/assets/PlayerSettings-jrmY95-i.js +1 -0
  24. package/client/dist/assets/{QueueOptionsButton-BKNDfpCl.js → QueueOptionsButton-CcVP7Vi6.js} +1 -1
  25. package/client/dist/assets/{QueueRail-Cgljhm64.js → QueueRail-Cj-PU9EV.js} +1 -1
  26. package/client/dist/assets/{Radio-wbxhaiqy.js → Radio-CRlbaxQt.js} +1 -1
  27. package/client/dist/assets/{ReadingList-DNJAx_LW.js → ReadingList-wRtHGNo8.js} +1 -1
  28. package/client/dist/assets/{ResetProgressModal-By-iSYwL.js → ResetProgressModal-BokfoYFB.js} +1 -1
  29. package/client/dist/assets/Search-BOOBeTVA.js +1 -0
  30. package/client/dist/assets/{Settings-BeS0llCE.js → Settings-BgrcswJD.js} +1 -1
  31. package/client/dist/assets/SkeletonGrid-Bge3aRl2.css +1 -0
  32. package/client/dist/assets/SkeletonGrid-C6S7OYHn.js +1 -0
  33. package/client/dist/assets/{Trackers-ACFHfLXZ.js → Trackers-DLcTEJJW.js} +1 -1
  34. package/client/dist/assets/Tv-ByVckozq.js +4 -0
  35. package/client/dist/assets/Tv-C2SWcVzN.css +1 -0
  36. package/client/dist/assets/{TvInfo-fFNHV1RJ.js → TvInfo-fJ9SFMK8.js} +1 -1
  37. package/client/dist/assets/{TvPopup-DYKTHRrD.js → TvPopup-yfH7p0y0.js} +1 -1
  38. package/client/dist/assets/TvSearch-CBNWDByW.js +1 -0
  39. package/client/dist/assets/{TvWatchlist-CTUZAvfa.js → TvWatchlist-BqjZ2-4A.js} +1 -1
  40. package/client/dist/assets/Watchlist-B_Q2hEiJ.js +1 -0
  41. package/client/dist/assets/{hls-Cbou9Eb-.js → hls-kIF7bMpF.js} +1 -1
  42. package/client/dist/assets/{index-CfvzoNYQ.js → index-C65i4HWv.js} +2 -2
  43. package/client/dist/assets/{useAnimeInfoData-D-vEehTG.js → useAnimeInfoData-BKSrS8eN.js} +1 -1
  44. package/client/dist/assets/{useAsmr-B-3EYd-B.js → useAsmr-Byj8Qiwp.js} +1 -1
  45. package/client/dist/assets/{useAsmrPopup-D9BxuTDg.js → useAsmrPopup-CLcWCCXf.js} +1 -1
  46. package/client/dist/assets/{useMangaLibrary-DtRKgk2x.js → useMangaLibrary-U7tUo8nx.js} +1 -1
  47. package/client/dist/assets/{useMangaPopup-BNWJ5pR2.js → useMangaPopup-ZonOP5_c.js} +1 -1
  48. package/client/dist/assets/{useProviders-jxh6fvMH.js → useProviders-CymA42ZB.js} +1 -1
  49. package/client/dist/assets/{useTv-CP4mofvR.js → useTv-CPj6Cf42.js} +1 -1
  50. package/client/dist/assets/{useTvLibrary-DeGkJuvp.js → useTvLibrary-COsyN8yu.js} +1 -1
  51. package/client/dist/index.html +1 -1
  52. package/package.json +2 -2
  53. package/server/dist/controllers/insights.controller.js +5 -1
  54. package/server/dist/controllers/proxy.controller.js +94 -23
  55. package/server/dist/controllers/watchlist.controller.js +42 -0
  56. package/server/dist/lib/discord-roles-sync.service.js +237 -2
  57. package/server/dist/repositories/insights.repository.js +10 -0
  58. package/server/dist/repositories/shows-meta.repository.js +4 -1
  59. package/server/dist/repositories/watchlist.repository.js +1 -0
  60. package/server/dist/routes/insights.routes.js +1 -0
  61. package/server/package.json +1 -1
  62. package/client/dist/assets/AvSyncCalibrator-Bs4iejCG.js +0 -10
  63. package/client/dist/assets/Home-Csx6zT32.js +0 -1
  64. package/client/dist/assets/Insights-DKtoVe6L.css +0 -1
  65. package/client/dist/assets/Insights-MW8oRqeX.js +0 -1
  66. package/client/dist/assets/LibraryListPage-VYaHXgXt.js +0 -1
  67. package/client/dist/assets/Mature-3VlywJMR.js +0 -1
  68. package/client/dist/assets/Player-Czg_D7FN.js +0 -56
  69. package/client/dist/assets/PlayerSettings-KrFECely.js +0 -1
  70. package/client/dist/assets/Search-63TcSgOa.js +0 -1
  71. package/client/dist/assets/SkeletonGrid-CnE_aoEU.js +0 -1
  72. package/client/dist/assets/SkeletonGrid-DhPYefXr.css +0 -1
  73. package/client/dist/assets/Tv-BCmNLUsp.js +0 -4
  74. package/client/dist/assets/Tv-CuEl3Iol.css +0 -1
  75. package/client/dist/assets/TvSearch-CpenLEBz.js +0 -1
  76. package/client/dist/assets/Watchlist-B10pIG1F.js +0 -1
@@ -1 +1 @@
1
- import{u as e,S as y,L as g,d as k,A as L,q as r}from"./vendor-core-8R2fFbyN.js";import{I as a}from"./vendor-icons-CDMuNDeL.js";import{c as P}from"./useAsmr-B-3EYd-B.js";import{p as s,a as f}from"./MediaCard-xfJv6irD.js";import{a as A}from"./Asmr.module-ChUXDZKx.js";const B=({data:n,anchorRect:l,bookmarked:i,onToggleBookmark:c,onMouseEnter:o,onMouseLeave:m,onRequestClose:p})=>{const{data:t,isLoading:d}=P(n.rjCode||null),u=t?.tracks.length??0,N=u>0,h=!d;return e(f,{anchorRect:l,onMouseEnter:o,onMouseLeave:m,onRequestClose:p,children:[e("div",{className:s.header,children:[n.thumbnail&&e("img",{src:n.thumbnail,alt:n.title,loading:"lazy",decoding:"async",className:A.popupCover}),e("div",{className:s.title,children:n.title}),e("div",{className:s.synopsis,children:n.rjCode})]}),e("div",{className:s.body,children:[e("div",{className:s.metaRow,children:[e("div",{className:s.metaItem,children:[e(a,{name:"headphones",size:14}),e("span",{children:h?N?e(y,{children:[u," track",u===1?"":"s"]}):"No tracks":"Loading tracks…"})]}),n.rating&&e("div",{className:s.metaItem,children:e("span",{children:n.rating.toUpperCase()})}),n.isAdult&&e("div",{className:s.metaItem,children:e("span",{children:"18+"})}),n.progressLabel&&e("div",{className:s.metaItem,children:[e(a,{name:"bookmark",size:12}),e("span",{children:n.progressLabel})]})]}),e("div",{className:s.synopsis,children:!h&&!t?.description?"Loading description…":t?.description??"No description available."})]}),e("div",{className:s.footer,children:[e("div",{className:s.primaryAction,children:N?e(g,{to:n.listenTarget,className:s.watchBtn,children:[e(a,{name:"play",size:14}),"Listen now"]}):e("span",{className:s.watchBtn,"aria-disabled":"true",style:{opacity:.5,pointerEvents:"none"},children:[e(a,{name:"play",size:14}),h?"No tracks":"Loading…"]})}),e("div",{className:s.secondaryActions,children:[e("button",{className:`${s.watchlistBtn} ${i?s.active:""}`,onClick:v=>{v.preventDefault(),v.stopPropagation(),c()},children:[i?e(a,{name:"check",size:12}):e(a,{name:"plus",size:12}),e("span",{children:i?"Remove":"Bookmark"})]}),e(g,{to:n.listenTarget,className:s.detailsBtn,children:"Details"})]})]})]})},C=300;function D(){const[n,l]=k(null),i=L(null),c=r(()=>{i.current&&(clearTimeout(i.current),i.current=null)},[]),o=r((t,d)=>{c(),l({data:d,rect:t})},[c]),m=r(()=>{c(),i.current=setTimeout(()=>{i.current=null,l(null)},C)},[c]),p=r(()=>{c(),l(null)},[c]);return{popup:n,openPopup:o,scheduleClose:m,cancelClose:c,closePopup:p}}export{B as A,D as u};
1
+ import{u as e,S as y,L as g,d as k,A as L,q as r}from"./vendor-core-8R2fFbyN.js";import{I as a}from"./vendor-icons-CDMuNDeL.js";import{c as P}from"./useAsmr-Byj8Qiwp.js";import{p as s,a as f}from"./MediaCard-DDiYEAm8.js";import{a as A}from"./Asmr.module-ChUXDZKx.js";const B=({data:n,anchorRect:l,bookmarked:i,onToggleBookmark:c,onMouseEnter:o,onMouseLeave:m,onRequestClose:p})=>{const{data:t,isLoading:d}=P(n.rjCode||null),u=t?.tracks.length??0,N=u>0,h=!d;return e(f,{anchorRect:l,onMouseEnter:o,onMouseLeave:m,onRequestClose:p,children:[e("div",{className:s.header,children:[n.thumbnail&&e("img",{src:n.thumbnail,alt:n.title,loading:"lazy",decoding:"async",className:A.popupCover}),e("div",{className:s.title,children:n.title}),e("div",{className:s.synopsis,children:n.rjCode})]}),e("div",{className:s.body,children:[e("div",{className:s.metaRow,children:[e("div",{className:s.metaItem,children:[e(a,{name:"headphones",size:14}),e("span",{children:h?N?e(y,{children:[u," track",u===1?"":"s"]}):"No tracks":"Loading tracks…"})]}),n.rating&&e("div",{className:s.metaItem,children:e("span",{children:n.rating.toUpperCase()})}),n.isAdult&&e("div",{className:s.metaItem,children:e("span",{children:"18+"})}),n.progressLabel&&e("div",{className:s.metaItem,children:[e(a,{name:"bookmark",size:12}),e("span",{children:n.progressLabel})]})]}),e("div",{className:s.synopsis,children:!h&&!t?.description?"Loading description…":t?.description??"No description available."})]}),e("div",{className:s.footer,children:[e("div",{className:s.primaryAction,children:N?e(g,{to:n.listenTarget,className:s.watchBtn,children:[e(a,{name:"play",size:14}),"Listen now"]}):e("span",{className:s.watchBtn,"aria-disabled":"true",style:{opacity:.5,pointerEvents:"none"},children:[e(a,{name:"play",size:14}),h?"No tracks":"Loading…"]})}),e("div",{className:s.secondaryActions,children:[e("button",{className:`${s.watchlistBtn} ${i?s.active:""}`,onClick:v=>{v.preventDefault(),v.stopPropagation(),c()},children:[i?e(a,{name:"check",size:12}):e(a,{name:"plus",size:12}),e("span",{children:i?"Remove":"Bookmark"})]}),e(g,{to:n.listenTarget,className:s.detailsBtn,children:"Details"})]})]})]})},C=300;function D(){const[n,l]=k(null),i=L(null),c=r(()=>{i.current&&(clearTimeout(i.current),i.current=null)},[]),o=r((t,d)=>{c(),l({data:d,rect:t})},[c]),m=r(()=>{c(),i.current=setTimeout(()=>{i.current=null,l(null)},C)},[c]),p=r(()=>{c(),l(null)},[c]);return{popup:n,openPopup:o,scheduleClose:m,cancelClose:c,closePopup:p}}export{B as A,D as u};
@@ -1 +1 @@
1
- import{b as y,c as b,u as m,a as d}from"./vendor-query-BXRECSI6.js";import{q as s,z as o}from"./index-CfvzoNYQ.js";import{T as S}from"./vendor-core-8R2fFbyN.js";const q=300*1e3,f={mangadex:"https://mangadex.org/",mangapill:"https://www.mangapill.com/"},K=(e,a)=>`/api/proxy?url=${encodeURIComponent(a)}&referer=${encodeURIComponent(f[e])}`,M=(e,a)=>a&&(e==="mangapill"?K(e,a):a),$=(e,a=!0)=>{const{provider:n,query:t,page:u,sort:i,status:g,type:c,rating:l,mature:p}=e;return y({queryKey:["mangaBrowse",n,t,u,i,g,c,l,p],queryFn:()=>{const r=new URLSearchParams;return r.set("provider",n),t.trim()&&r.set("q",t.trim()),r.set("page",String(u)),i&&r.set("sort",i),g&&r.set("status",g),c&&r.set("type",c),l&&r.set("rating",l),p&&r.set("mature","1"),s(`/api/manga/search?${r.toString()}`)},staleTime:q,enabled:a&&!!n})},k=(e,a=14)=>{const{provider:n,query:t,sort:u,status:i,type:g,rating:c,mature:l}=e;return b({queryKey:["mangaBrowseInfinite",n,t,u,i,g,c,l,a],queryFn:({pageParam:p=1})=>{const r=new URLSearchParams;return r.set("provider",n),t.trim()&&r.set("q",t.trim()),r.set("page",String(p)),r.set("limit",String(a)),u&&r.set("sort",u),i&&r.set("status",i),g&&r.set("type",g),c&&r.set("rating",c),l&&r.set("mature","1"),s(`/api/manga/search?${r.toString()}`)},initialPageParam:1,getNextPageParam:(p,r)=>p.hasNext?r.length+1:void 0,staleTime:q,enabled:!!n})},C=(e,a,n="safe",t=!1)=>y({queryKey:["mangaDetail",e,a,n,t],queryFn:()=>{const u=new URLSearchParams;return u.set("provider",e||""),u.set("id",a||""),n&&u.set("rating",n),t&&u.set("mature","1"),s(`/api/manga/info?${u.toString()}`)},enabled:!!e&&!!a,staleTime:q}),w=(e,a,n)=>y({queryKey:["mangaPages",e,a],queryFn:()=>s(`/api/manga/pages?provider=${e}&id=${encodeURIComponent(a||"")}`),enabled:!!e&&!!a,staleTime:1800*1e3}),O=()=>y({queryKey:["mangaTrending"],queryFn:()=>s("/api/manga/trending"),staleTime:600*1e3}),N=e=>e.contentRating==="erotica"||e.contentRating==="pornographic"||e.type==="doujinshi";function I(e,a){const n=e.titles??{};return a==="englishName"?n.en||e.title:a==="nativeName"&&(n.native||n.romaji||e.altTitle)||e.title}function B(e){return{englishName:e.titles?.en??e.title,nativeName:e.titles?.native??e.titles?.romaji??e.altTitle??void 0}}const E=["Reading","Completed","On-Hold","Dropped","Planned"],v=(e,a)=>`${e.toLowerCase()}:${a}`,U=(e="All",a=1,n=24)=>y({queryKey:["manga-library",e,a,n],queryFn:()=>{const t=new URLSearchParams;return t.set("status",e),t.set("page",String(a)),t.set("limit",String(n)),s(`/api/manga/library?${t.toString()}`)}}),h=()=>y({queryKey:["manga-library-ids"],queryFn:()=>s("/api/manga/library/ids"),staleTime:1e3*60}),L=e=>y({queryKey:["manga-library-check",e],queryFn:()=>s(`/api/manga/library/check/${encodeURIComponent(e||"")}`),enabled:!!e,staleTime:1e3*60}),A=(e=24)=>y({queryKey:["manga-continue-reading",e],queryFn:()=>s(`/api/manga/continue-reading?limit=${e}`)}),_=e=>y({queryKey:["manga-progress",e],queryFn:()=>s(`/api/manga/progress/${encodeURIComponent(e||"")}`),enabled:!!e}),P=()=>{const e=m();return d({mutationFn:async a=>{const{silent:n,...t}=a;return s("/api/manga/library/add",{method:"POST",body:JSON.stringify(t)})},onSuccess:(a,n)=>{n.silent||o.success("Bookmarked"),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]}),e.invalidateQueries({queryKey:["manga-library-check",v(n.provider,n.mangaId)]})},onError:a=>{o.error(`Failed to bookmark: ${a.message}`)}})},R=()=>{const e=m();return d({mutationFn:async a=>{await s("/api/manga/library/remove",{method:"POST",body:JSON.stringify({id:a})})},onSuccess:(a,n)=>{o.success("Bookmark removed"),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library-check",n]})},onError:a=>{o.error(`Failed to remove: ${a.message}`)}})},J=()=>{const e=m();return d({mutationFn:async({id:a,status:n})=>{await s("/api/manga/library/status",{method:"POST",body:JSON.stringify({id:a,status:n})})},onSuccess:()=>{o.success("Status updated"),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]})},onError:a=>{o.error(`Failed to update status: ${a.message}`)}})},x=()=>{const e=m();return d({mutationFn:async a=>{await s("/api/manga/progress",{method:"POST",body:JSON.stringify(a)})},onSuccess:(a,n)=>{e.invalidateQueries({queryKey:["manga-progress",n.mangaId]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]})}})},D=()=>{const e=m();return d({mutationFn:async({mangaId:a,chapterId:n})=>{await s("/api/manga/progress/remove",{method:"POST",body:JSON.stringify({mangaId:a,chapterId:n})})},onSuccess:()=>{o.success("Progress reset"),e.invalidateQueries({queryKey:["manga-progress"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library"]})},onError:a=>{o.error(`Failed to reset progress: ${a.message}`)}})},j=()=>{const e=m();return d({mutationFn:async({ids:a,status:n})=>s("/api/manga/library/batch-status",{method:"POST",body:JSON.stringify({ids:a,status:n})}),onSuccess:a=>{o.success(`Status updated for ${a.updated??0} items`),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]})},onError:a=>{o.error(`Failed to update statuses: ${a.message}`)}})},V=()=>{const e=m();return d({mutationFn:async a=>s("/api/manga/library/remove-many",{method:"POST",body:JSON.stringify({ids:a})}),onSuccess:a=>{const n=a.removed??0;o.success(`Removed ${n} ${n===1?"item":"items"} from reading list`),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]})},onError:a=>{o.error(`Failed to remove: ${a.message}`)}})},z=()=>{const e=m();return d({mutationFn:async a=>s("/api/manga/progress/remove-many",{method:"POST",body:JSON.stringify({ids:a})}),onSuccess:a=>{const n=a.removed??0;o.success(`Reset progress for ${n} ${n===1?"item":"items"}`),e.invalidateQueries({queryKey:["manga-progress"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library"]})},onError:a=>{o.error(`Failed to reset progress: ${a.message}`)}})},G=()=>{const e=P(),a=R(),{data:n}=h(),t=S(()=>new Set(n?.ids||[]),[n]);return{toggle:i=>{const g=v(i.provider,i.id);t.has(g)?a.mutate(g):e.mutate({provider:i.provider,mangaId:i.id,title:i.title,cover:i.cover,altTitle:i.altTitle,contentRating:i.contentRating})},bookmarkedIds:t,pending:e.isPending||a.isPending}};export{E as M,k as a,M as b,B as c,A as d,O as e,$ as f,C as g,L as h,N as i,_ as j,P as k,R as l,v as m,J as n,w as o,K as p,x as q,I as r,U as s,j as t,G as u,V as v,z as w,D as x};
1
+ import{b as y,c as b,u as m,a as d}from"./vendor-query-BXRECSI6.js";import{q as s,z as o}from"./index-C65i4HWv.js";import{T as S}from"./vendor-core-8R2fFbyN.js";const q=300*1e3,f={mangadex:"https://mangadex.org/",mangapill:"https://www.mangapill.com/"},K=(e,a)=>`/api/proxy?url=${encodeURIComponent(a)}&referer=${encodeURIComponent(f[e])}`,M=(e,a)=>a&&(e==="mangapill"?K(e,a):a),$=(e,a=!0)=>{const{provider:n,query:t,page:u,sort:i,status:g,type:c,rating:l,mature:p}=e;return y({queryKey:["mangaBrowse",n,t,u,i,g,c,l,p],queryFn:()=>{const r=new URLSearchParams;return r.set("provider",n),t.trim()&&r.set("q",t.trim()),r.set("page",String(u)),i&&r.set("sort",i),g&&r.set("status",g),c&&r.set("type",c),l&&r.set("rating",l),p&&r.set("mature","1"),s(`/api/manga/search?${r.toString()}`)},staleTime:q,enabled:a&&!!n})},k=(e,a=14)=>{const{provider:n,query:t,sort:u,status:i,type:g,rating:c,mature:l}=e;return b({queryKey:["mangaBrowseInfinite",n,t,u,i,g,c,l,a],queryFn:({pageParam:p=1})=>{const r=new URLSearchParams;return r.set("provider",n),t.trim()&&r.set("q",t.trim()),r.set("page",String(p)),r.set("limit",String(a)),u&&r.set("sort",u),i&&r.set("status",i),g&&r.set("type",g),c&&r.set("rating",c),l&&r.set("mature","1"),s(`/api/manga/search?${r.toString()}`)},initialPageParam:1,getNextPageParam:(p,r)=>p.hasNext?r.length+1:void 0,staleTime:q,enabled:!!n})},C=(e,a,n="safe",t=!1)=>y({queryKey:["mangaDetail",e,a,n,t],queryFn:()=>{const u=new URLSearchParams;return u.set("provider",e||""),u.set("id",a||""),n&&u.set("rating",n),t&&u.set("mature","1"),s(`/api/manga/info?${u.toString()}`)},enabled:!!e&&!!a,staleTime:q}),w=(e,a,n)=>y({queryKey:["mangaPages",e,a],queryFn:()=>s(`/api/manga/pages?provider=${e}&id=${encodeURIComponent(a||"")}`),enabled:!!e&&!!a,staleTime:1800*1e3}),O=()=>y({queryKey:["mangaTrending"],queryFn:()=>s("/api/manga/trending"),staleTime:600*1e3}),N=e=>e.contentRating==="erotica"||e.contentRating==="pornographic"||e.type==="doujinshi";function I(e,a){const n=e.titles??{};return a==="englishName"?n.en||e.title:a==="nativeName"&&(n.native||n.romaji||e.altTitle)||e.title}function B(e){return{englishName:e.titles?.en??e.title,nativeName:e.titles?.native??e.titles?.romaji??e.altTitle??void 0}}const E=["Reading","Completed","On-Hold","Dropped","Planned"],v=(e,a)=>`${e.toLowerCase()}:${a}`,U=(e="All",a=1,n=24)=>y({queryKey:["manga-library",e,a,n],queryFn:()=>{const t=new URLSearchParams;return t.set("status",e),t.set("page",String(a)),t.set("limit",String(n)),s(`/api/manga/library?${t.toString()}`)}}),h=()=>y({queryKey:["manga-library-ids"],queryFn:()=>s("/api/manga/library/ids"),staleTime:1e3*60}),L=e=>y({queryKey:["manga-library-check",e],queryFn:()=>s(`/api/manga/library/check/${encodeURIComponent(e||"")}`),enabled:!!e,staleTime:1e3*60}),A=(e=24)=>y({queryKey:["manga-continue-reading",e],queryFn:()=>s(`/api/manga/continue-reading?limit=${e}`)}),_=e=>y({queryKey:["manga-progress",e],queryFn:()=>s(`/api/manga/progress/${encodeURIComponent(e||"")}`),enabled:!!e}),P=()=>{const e=m();return d({mutationFn:async a=>{const{silent:n,...t}=a;return s("/api/manga/library/add",{method:"POST",body:JSON.stringify(t)})},onSuccess:(a,n)=>{n.silent||o.success("Bookmarked"),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]}),e.invalidateQueries({queryKey:["manga-library-check",v(n.provider,n.mangaId)]})},onError:a=>{o.error(`Failed to bookmark: ${a.message}`)}})},R=()=>{const e=m();return d({mutationFn:async a=>{await s("/api/manga/library/remove",{method:"POST",body:JSON.stringify({id:a})})},onSuccess:(a,n)=>{o.success("Bookmark removed"),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library-check",n]})},onError:a=>{o.error(`Failed to remove: ${a.message}`)}})},J=()=>{const e=m();return d({mutationFn:async({id:a,status:n})=>{await s("/api/manga/library/status",{method:"POST",body:JSON.stringify({id:a,status:n})})},onSuccess:()=>{o.success("Status updated"),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]})},onError:a=>{o.error(`Failed to update status: ${a.message}`)}})},x=()=>{const e=m();return d({mutationFn:async a=>{await s("/api/manga/progress",{method:"POST",body:JSON.stringify(a)})},onSuccess:(a,n)=>{e.invalidateQueries({queryKey:["manga-progress",n.mangaId]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]})}})},D=()=>{const e=m();return d({mutationFn:async({mangaId:a,chapterId:n})=>{await s("/api/manga/progress/remove",{method:"POST",body:JSON.stringify({mangaId:a,chapterId:n})})},onSuccess:()=>{o.success("Progress reset"),e.invalidateQueries({queryKey:["manga-progress"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library"]})},onError:a=>{o.error(`Failed to reset progress: ${a.message}`)}})},j=()=>{const e=m();return d({mutationFn:async({ids:a,status:n})=>s("/api/manga/library/batch-status",{method:"POST",body:JSON.stringify({ids:a,status:n})}),onSuccess:a=>{o.success(`Status updated for ${a.updated??0} items`),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]})},onError:a=>{o.error(`Failed to update statuses: ${a.message}`)}})},V=()=>{const e=m();return d({mutationFn:async a=>s("/api/manga/library/remove-many",{method:"POST",body:JSON.stringify({ids:a})}),onSuccess:a=>{const n=a.removed??0;o.success(`Removed ${n} ${n===1?"item":"items"} from reading list`),e.invalidateQueries({queryKey:["manga-library"]}),e.invalidateQueries({queryKey:["manga-library-ids"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]})},onError:a=>{o.error(`Failed to remove: ${a.message}`)}})},z=()=>{const e=m();return d({mutationFn:async a=>s("/api/manga/progress/remove-many",{method:"POST",body:JSON.stringify({ids:a})}),onSuccess:a=>{const n=a.removed??0;o.success(`Reset progress for ${n} ${n===1?"item":"items"}`),e.invalidateQueries({queryKey:["manga-progress"]}),e.invalidateQueries({queryKey:["manga-continue-reading"]}),e.invalidateQueries({queryKey:["manga-library"]})},onError:a=>{o.error(`Failed to reset progress: ${a.message}`)}})},G=()=>{const e=P(),a=R(),{data:n}=h(),t=S(()=>new Set(n?.ids||[]),[n]);return{toggle:i=>{const g=v(i.provider,i.id);t.has(g)?a.mutate(g):e.mutate({provider:i.provider,mangaId:i.id,title:i.title,cover:i.cover,altTitle:i.altTitle,contentRating:i.contentRating})},bookmarkedIds:t,pending:e.isPending||a.isPending}};export{E as M,k as a,M as b,B as c,A as d,O as e,$ as f,C as g,L as h,N as i,_ as j,P as k,R as l,v as m,J as n,w as o,K as p,x as q,I as r,U as s,j as t,G as u,V as v,z as w,D as x};
@@ -1 +1 @@
1
- import{u as e,S as T,L as N,d as P,A as y,q as o}from"./vendor-core-8R2fFbyN.js";import{I as c}from"./vendor-icons-CDMuNDeL.js";import{g as I,r as A}from"./useMangaLibrary-DtRKgk2x.js";import{d as L}from"./index-CfvzoNYQ.js";import{p as s,a as M}from"./MediaCard-xfJv6irD.js";const S=({data:l,anchorRect:t,bookmarked:a,onToggleBookmark:i,onMouseEnter:d,onMouseLeave:m,onRequestClose:p})=>{const{data:n,isLoading:h}=I(l.provider,l.mangaId,l.rating||"safe",l.mature||!1),{titlePreference:f}=L(),u=n?.chapters.length??0,v=[n?.status,n?.type].filter(Boolean).join(" · "),g=A({title:l.title,altTitle:n?.altTitle??l.altTitle},f);return e(M,{anchorRect:t,onMouseEnter:d,onMouseLeave:m,onRequestClose:p,children:h?e("div",{className:s.loading,children:[e("div",{className:s.spinner}),e("span",{children:"Fetching details..."})]}):e(T,{children:[e("div",{className:s.header,children:[e("div",{className:s.title,children:g}),n?.altTitle&&n.altTitle!==g&&e("div",{className:s.synopsis,children:["Also known as: ",n.altTitle]})]}),e("div",{className:s.body,children:[e("div",{className:s.metaRow,children:[v&&e("div",{className:s.metaItem,children:[e(c,{name:"book",size:14}),e("span",{children:v})]}),u>0&&e("div",{className:s.metaItem,children:e("span",{children:[u," chapter",u===1?"":"s"]})}),l.progressLabel&&e("div",{className:s.metaItem,children:[e(c,{name:"bookmark",size:12}),e("span",{children:l.progressLabel})]})]}),e("div",{className:s.synopsis,children:n?.description?n.description:"No synopsis available."}),Array.isArray(n?.genres)&&(n?.genres?.length??0)>0&&e("div",{className:s.details,children:e("div",{className:s.genres,children:n.genres.filter(Boolean).slice(0,4).map(r=>e("span",{className:s.genre,children:r},r))})})]}),e("div",{className:s.footer,children:[e("div",{className:s.primaryAction,children:e(N,{to:l.readTarget,className:s.watchBtn,children:[e(c,{name:"book",size:14}),"Read now"]})}),e("div",{className:s.secondaryActions,children:[e("button",{className:`${s.watchlistBtn} ${a?s.active:""}`,onClick:r=>{r.preventDefault(),r.stopPropagation(),i()},children:[a?e(c,{name:"check",size:12}):e(c,{name:"plus",size:12}),e("span",{children:a?"Remove":"Bookmark"})]}),e(N,{to:`/manga/${l.provider}/${encodeURIComponent(l.mangaId)}`,className:s.detailsBtn,children:"Read more"})]})]})]})})},b=300;function $(){const[l,t]=P(null),a=y(null),i=o(()=>{a.current&&(clearTimeout(a.current),a.current=null)},[]),d=o((n,h)=>{i(),t({data:h,rect:n})},[i]),m=o(()=>{i(),a.current=setTimeout(()=>{a.current=null,t(null)},b)},[i]),p=o(()=>{i(),t(null)},[i]);return{popup:l,openPopup:d,scheduleClose:m,cancelClose:i,closePopup:p}}export{S as M,$ as u};
1
+ import{u as e,S as T,L as N,d as P,A as y,q as o}from"./vendor-core-8R2fFbyN.js";import{I as c}from"./vendor-icons-CDMuNDeL.js";import{g as I,r as A}from"./useMangaLibrary-U7tUo8nx.js";import{d as L}from"./index-C65i4HWv.js";import{p as s,a as M}from"./MediaCard-DDiYEAm8.js";const S=({data:l,anchorRect:t,bookmarked:a,onToggleBookmark:i,onMouseEnter:d,onMouseLeave:m,onRequestClose:p})=>{const{data:n,isLoading:h}=I(l.provider,l.mangaId,l.rating||"safe",l.mature||!1),{titlePreference:f}=L(),u=n?.chapters.length??0,v=[n?.status,n?.type].filter(Boolean).join(" · "),g=A({title:l.title,altTitle:n?.altTitle??l.altTitle},f);return e(M,{anchorRect:t,onMouseEnter:d,onMouseLeave:m,onRequestClose:p,children:h?e("div",{className:s.loading,children:[e("div",{className:s.spinner}),e("span",{children:"Fetching details..."})]}):e(T,{children:[e("div",{className:s.header,children:[e("div",{className:s.title,children:g}),n?.altTitle&&n.altTitle!==g&&e("div",{className:s.synopsis,children:["Also known as: ",n.altTitle]})]}),e("div",{className:s.body,children:[e("div",{className:s.metaRow,children:[v&&e("div",{className:s.metaItem,children:[e(c,{name:"book",size:14}),e("span",{children:v})]}),u>0&&e("div",{className:s.metaItem,children:e("span",{children:[u," chapter",u===1?"":"s"]})}),l.progressLabel&&e("div",{className:s.metaItem,children:[e(c,{name:"bookmark",size:12}),e("span",{children:l.progressLabel})]})]}),e("div",{className:s.synopsis,children:n?.description?n.description:"No synopsis available."}),Array.isArray(n?.genres)&&(n?.genres?.length??0)>0&&e("div",{className:s.details,children:e("div",{className:s.genres,children:n.genres.filter(Boolean).slice(0,4).map(r=>e("span",{className:s.genre,children:r},r))})})]}),e("div",{className:s.footer,children:[e("div",{className:s.primaryAction,children:e(N,{to:l.readTarget,className:s.watchBtn,children:[e(c,{name:"book",size:14}),"Read now"]})}),e("div",{className:s.secondaryActions,children:[e("button",{className:`${s.watchlistBtn} ${a?s.active:""}`,onClick:r=>{r.preventDefault(),r.stopPropagation(),i()},children:[a?e(c,{name:"check",size:12}):e(c,{name:"plus",size:12}),e("span",{children:a?"Remove":"Bookmark"})]}),e(N,{to:`/manga/${l.provider}/${encodeURIComponent(l.mangaId)}`,className:s.detailsBtn,children:"Read more"})]})]})]})})},b=300;function $(){const[l,t]=P(null),a=y(null),i=o(()=>{a.current&&(clearTimeout(a.current),a.current=null)},[]),d=o((n,h)=>{i(),t({data:h,rect:n})},[i]),m=o(()=>{i(),a.current=setTimeout(()=>{a.current=null,t(null)},b)},[i]),p=o(()=>{i(),t(null)},[i]);return{popup:l,openPopup:d,scheduleClose:m,cancelClose:i,closePopup:p}}export{S as M,$ as u};
@@ -1 +1 @@
1
- import{T as o}from"./vendor-core-8R2fFbyN.js";import{b as a}from"./vendor-query-BXRECSI6.js";import{q as i}from"./index-CfvzoNYQ.js";const n=300*1e3;function u(r){return{value:r.id,label:r.label,mature:r.mature,kind:r.kind,sub:r.sub,tier:r.tier,browse:r.browse,facets:r.facets,servers:r.servers}}const d=()=>{const r=a({queryKey:["providers"],queryFn:()=>i("/api/providers"),staleTime:n,retry:1}),e=Array.isArray(r.data)?r.data:null,t=o(()=>e&&e.length>0?e.filter(s=>s.loaded).map(u):[],[e]);return{...r,options:t,isFallback:!e||e.length===0}};export{d as u};
1
+ import{T as o}from"./vendor-core-8R2fFbyN.js";import{b as a}from"./vendor-query-BXRECSI6.js";import{q as i}from"./index-C65i4HWv.js";const n=300*1e3;function u(r){return{value:r.id,label:r.label,mature:r.mature,kind:r.kind,sub:r.sub,tier:r.tier,browse:r.browse,facets:r.facets,servers:r.servers}}const d=()=>{const r=a({queryKey:["providers"],queryFn:()=>i("/api/providers"),staleTime:n,retry:1}),e=Array.isArray(r.data)?r.data:null,t=o(()=>e&&e.length>0?e.filter(s=>s.loaded).map(u):[],[e]);return{...r,options:t,isFallback:!e||e.length===0}};export{d as u};
@@ -1 +1 @@
1
- import{N as d,u as o,S as v}from"./vendor-core-8R2fFbyN.js";import{I as g}from"./vendor-icons-CDMuNDeL.js";import{M as f}from"./MediaCard-xfJv6irD.js";import{T as m}from"./TvPopup-DYKTHRrD.js";import{c as T,i as h}from"./useTvLibrary-DeGkJuvp.js";import{b as u}from"./vendor-query-BXRECSI6.js";import{q as y}from"./index-CfvzoNYQ.js";const b=d(({item:e})=>{const r=e.type==="tv"||e.type==="tvSeries"||e.type==="tvMiniSeries",t=T(e.type,e.id);return o(f,{item:{id:String(e.id),title:e.title,thumbnail:e.image,typeBadge:r?"TV":"Movie",isAdult:h(e)},linkTo:t,hoverIcon:"info",showInfoButton:!0,metaRow:o(v,{children:[e.year&&o("span",{style:{opacity:.75},children:e.year}),e.vote_average!=null&&o("span",{style:{display:"inline-flex",alignItems:"center",gap:3},children:[o(g,{name:"star",size:10}),Number(e.vote_average).toFixed(1)]})]}),renderPopup:(n,a)=>o(m,{item:e,anchorRect:n,onMouseEnter:a.onMouseEnter,onMouseLeave:a.onMouseLeave,onRequestClose:a.close})})}),c=1e3*60*5,w=e=>{const{query:r,type:t,genre:n,year:a,sort_by:i,page:l}=e,s=new URLSearchParams;r?.trim()&&s.set("q",r.trim()),t&&t!=="multi"&&s.set("type",t),n&&s.set("genre",n),a&&a!=="ALL"&&s.set("year",a),i&&i!=="popularity.desc"&&s.set("sort_by",i),s.set("page",String(l||1));const p=s.toString();return u({queryKey:["tv-search-paginated",p],queryFn:()=>y(`/api/tv/search?${p}`),staleTime:c,retry:1})},A=(e="all",r="week",t=1,n=!0)=>u({queryKey:["tv-trending",e,r,t],queryFn:()=>y(`/api/tv/trending?media_type=${e}&time_window=${r}&page=${t}`),enabled:n,staleTime:c,retry:1});export{b as T,w as a,A as u};
1
+ import{N as d,u as o,S as v}from"./vendor-core-8R2fFbyN.js";import{I as g}from"./vendor-icons-CDMuNDeL.js";import{M as f}from"./MediaCard-DDiYEAm8.js";import{T as m}from"./TvPopup-yfH7p0y0.js";import{c as T,i as h}from"./useTvLibrary-COsyN8yu.js";import{b as u}from"./vendor-query-BXRECSI6.js";import{q as y}from"./index-C65i4HWv.js";const b=d(({item:e})=>{const r=e.type==="tv"||e.type==="tvSeries"||e.type==="tvMiniSeries",t=T(e.type,e.id);return o(f,{item:{id:String(e.id),title:e.title,thumbnail:e.image,typeBadge:r?"TV":"Movie",isAdult:h(e)},linkTo:t,hoverIcon:"info",showInfoButton:!0,metaRow:o(v,{children:[e.year&&o("span",{style:{opacity:.75},children:e.year}),e.vote_average!=null&&o("span",{style:{display:"inline-flex",alignItems:"center",gap:3},children:[o(g,{name:"star",size:10}),Number(e.vote_average).toFixed(1)]})]}),renderPopup:(n,a)=>o(m,{item:e,anchorRect:n,onMouseEnter:a.onMouseEnter,onMouseLeave:a.onMouseLeave,onRequestClose:a.close})})}),c=1e3*60*5,w=e=>{const{query:r,type:t,genre:n,year:a,sort_by:i,page:l}=e,s=new URLSearchParams;r?.trim()&&s.set("q",r.trim()),t&&t!=="multi"&&s.set("type",t),n&&s.set("genre",n),a&&a!=="ALL"&&s.set("year",a),i&&i!=="popularity.desc"&&s.set("sort_by",i),s.set("page",String(l||1));const p=s.toString();return u({queryKey:["tv-search-paginated",p],queryFn:()=>y(`/api/tv/search?${p}`),staleTime:c,retry:1})},A=(e="all",r="week",t=1,n=!0)=>u({queryKey:["tv-trending",e,r,t],queryFn:()=>y(`/api/tv/trending?media_type=${e}&time_window=${r}&page=${t}`),enabled:n,staleTime:c,retry:1});export{b as T,w as a,A as u};
@@ -1 +1 @@
1
- import{T as m}from"./vendor-core-8R2fFbyN.js";import{b as y,u as o,a as u}from"./vendor-query-BXRECSI6.js";import{q as i,z as a}from"./index-CfvzoNYQ.js";const d=e=>String(e).toLowerCase()==="movie"?"movie":"tv",g=(e,t)=>`${d(e)}:${t}`,K=(e,t)=>{const r=d(e);return`/tv/${t}?type=${r}`},Q=(e,t,r,s)=>{const n=new URLSearchParams;return n.set("type",d(e)),r!==void 0&&n.set("s",String(r)),s!==void 0&&n.set("e",String(s)),`/tv/${t}/watch?${n.toString()}`},$=e=>e.adult===!0||e.adult===1,f=["Watching","Completed","On-Hold","Dropped","Planned"],l=(e,t)=>g(e,t),w=(e="All",t=1,r=24)=>y({queryKey:["tv-library",e,t,r],queryFn:()=>{const s=new URLSearchParams;return s.set("status",e),s.set("page",String(t)),s.set("limit",String(r)),i(`/api/tv/library?${s.toString()}`)}}),p=()=>y({queryKey:["tv-library-ids"],queryFn:()=>i("/api/tv/library/ids"),staleTime:1e3*60}),P=e=>y({queryKey:["tv-library-check",e],queryFn:()=>i(`/api/tv/library/check/${encodeURIComponent(e||"")}`),enabled:!!e,staleTime:1e3*60}),F=(e=24)=>y({queryKey:["tv-continue-watching",e],queryFn:()=>i(`/api/tv/continue-watching?limit=${e}`)}),O=e=>y({queryKey:["tv-progress",e],queryFn:()=>i(`/api/tv/progress/${encodeURIComponent(e||"")}`),enabled:!!e}),C=(e,t,r)=>y({queryKey:["tv-progress-latest",e,t,r],queryFn:()=>{const s=new URLSearchParams;t!==void 0&&s.set("season",String(t)),r!==void 0&&s.set("episode",String(r));const n=s.toString();return i(`/api/tv/progress/${encodeURIComponent(e||"")}/latest${n?`?${n}`:""}`)},enabled:!!e,staleTime:0}),q=()=>{const e=o();return u({mutationFn:async t=>{const{silent:r,...s}=t;return i("/api/tv/library/add",{method:"POST",body:JSON.stringify(s)})},onSuccess:(t,r)=>{r.silent||a.success("Added to TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]}),e.invalidateQueries({queryKey:["tv-library-check",l(r.mediaType,r.tmdbId)]})},onError:t=>{a.error(`Failed to add: ${t.message}`)}})},b=()=>{const e=o();return u({mutationFn:async t=>{await i("/api/tv/library/remove",{method:"POST",body:JSON.stringify({id:t})})},onSuccess:(t,r)=>{a.success("Removed from TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library-check",r]})},onError:t=>{a.error(`Failed to remove: ${t.message}`)}})},R=()=>{const e=o();return u({mutationFn:async({id:t,status:r})=>{await i("/api/tv/library/status",{method:"POST",body:JSON.stringify({id:t,status:r})})},onSuccess:()=>{a.success("Status updated"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]})},onError:t=>{a.error(`Failed to update status: ${t.message}`)}})},k=()=>{const e=o();return u({mutationFn:async t=>{await i("/api/tv/progress",{method:"POST",body:JSON.stringify(t)})},onSuccess:(t,r)=>{e.invalidateQueries({queryKey:["tv-progress",r.mediaId]}),e.invalidateQueries({queryKey:["tv-progress-latest",r.mediaId]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]})}})},L=()=>{const e=o();return u({mutationFn:async({mediaId:t,season:r,episode:s})=>{await i("/api/tv/progress/remove",{method:"POST",body:JSON.stringify({mediaId:t,season:r,episode:s})})},onSuccess:()=>{a.success("Progress reset"),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]})},onError:t=>{a.error(`Failed to reset progress: ${t.message}`)}})},I=()=>{const e=o();return u({mutationFn:async({ids:t,status:r})=>i("/api/tv/library/batch-status",{method:"POST",body:JSON.stringify({ids:t,status:r})}),onSuccess:t=>{a.success(`Status updated for ${t.updated??0} items`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]})},onError:t=>{a.error(`Failed to update statuses: ${t.message}`)}})},U=()=>{const e=o();return u({mutationFn:async t=>i("/api/tv/library/remove-many",{method:"POST",body:JSON.stringify({ids:t})}),onSuccess:t=>{const r=t.removed??0;a.success(`Removed ${r} ${r===1?"item":"items"} from TV watchlist`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]})},onError:t=>{a.error(`Failed to remove: ${t.message}`)}})},E=()=>{const e=o();return u({mutationFn:async t=>i("/api/tv/progress/remove-many",{method:"POST",body:JSON.stringify({ids:t})}),onSuccess:t=>{const r=t.removed??0;a.success(`Reset progress for ${r} ${r===1?"item":"items"}`),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]})},onError:t=>{a.error(`Failed to reset progress: ${t.message}`)}})},J=()=>{const e=q(),t=b(),{data:r}=p(),s=m(()=>new Set(r?.ids||[]),[r]);return{toggle:c=>{const v=l(c.mediaType,c.tmdbId);s.has(v)?t.mutate(v):e.mutate({...c,poster:c.poster||""})},bookmarkedIds:s,pending:e.isPending||t.isPending}};export{f as T,b as a,L as b,K as c,g as d,P as e,J as f,k as g,R as h,$ as i,C as j,O as k,w as l,I as m,d as n,U as o,E as p,Q as t,F as u};
1
+ import{T as m}from"./vendor-core-8R2fFbyN.js";import{b as y,u as o,a as u}from"./vendor-query-BXRECSI6.js";import{q as i,z as a}from"./index-C65i4HWv.js";const d=e=>String(e).toLowerCase()==="movie"?"movie":"tv",g=(e,t)=>`${d(e)}:${t}`,K=(e,t)=>{const r=d(e);return`/tv/${t}?type=${r}`},Q=(e,t,r,s)=>{const n=new URLSearchParams;return n.set("type",d(e)),r!==void 0&&n.set("s",String(r)),s!==void 0&&n.set("e",String(s)),`/tv/${t}/watch?${n.toString()}`},$=e=>e.adult===!0||e.adult===1,f=["Watching","Completed","On-Hold","Dropped","Planned"],l=(e,t)=>g(e,t),w=(e="All",t=1,r=24)=>y({queryKey:["tv-library",e,t,r],queryFn:()=>{const s=new URLSearchParams;return s.set("status",e),s.set("page",String(t)),s.set("limit",String(r)),i(`/api/tv/library?${s.toString()}`)}}),p=()=>y({queryKey:["tv-library-ids"],queryFn:()=>i("/api/tv/library/ids"),staleTime:1e3*60}),P=e=>y({queryKey:["tv-library-check",e],queryFn:()=>i(`/api/tv/library/check/${encodeURIComponent(e||"")}`),enabled:!!e,staleTime:1e3*60}),F=(e=24)=>y({queryKey:["tv-continue-watching",e],queryFn:()=>i(`/api/tv/continue-watching?limit=${e}`)}),O=e=>y({queryKey:["tv-progress",e],queryFn:()=>i(`/api/tv/progress/${encodeURIComponent(e||"")}`),enabled:!!e}),C=(e,t,r)=>y({queryKey:["tv-progress-latest",e,t,r],queryFn:()=>{const s=new URLSearchParams;t!==void 0&&s.set("season",String(t)),r!==void 0&&s.set("episode",String(r));const n=s.toString();return i(`/api/tv/progress/${encodeURIComponent(e||"")}/latest${n?`?${n}`:""}`)},enabled:!!e,staleTime:0}),q=()=>{const e=o();return u({mutationFn:async t=>{const{silent:r,...s}=t;return i("/api/tv/library/add",{method:"POST",body:JSON.stringify(s)})},onSuccess:(t,r)=>{r.silent||a.success("Added to TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]}),e.invalidateQueries({queryKey:["tv-library-check",l(r.mediaType,r.tmdbId)]})},onError:t=>{a.error(`Failed to add: ${t.message}`)}})},b=()=>{const e=o();return u({mutationFn:async t=>{await i("/api/tv/library/remove",{method:"POST",body:JSON.stringify({id:t})})},onSuccess:(t,r)=>{a.success("Removed from TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library-check",r]})},onError:t=>{a.error(`Failed to remove: ${t.message}`)}})},R=()=>{const e=o();return u({mutationFn:async({id:t,status:r})=>{await i("/api/tv/library/status",{method:"POST",body:JSON.stringify({id:t,status:r})})},onSuccess:()=>{a.success("Status updated"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]})},onError:t=>{a.error(`Failed to update status: ${t.message}`)}})},k=()=>{const e=o();return u({mutationFn:async t=>{await i("/api/tv/progress",{method:"POST",body:JSON.stringify(t)})},onSuccess:(t,r)=>{e.invalidateQueries({queryKey:["tv-progress",r.mediaId]}),e.invalidateQueries({queryKey:["tv-progress-latest",r.mediaId]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]})}})},L=()=>{const e=o();return u({mutationFn:async({mediaId:t,season:r,episode:s})=>{await i("/api/tv/progress/remove",{method:"POST",body:JSON.stringify({mediaId:t,season:r,episode:s})})},onSuccess:()=>{a.success("Progress reset"),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]})},onError:t=>{a.error(`Failed to reset progress: ${t.message}`)}})},I=()=>{const e=o();return u({mutationFn:async({ids:t,status:r})=>i("/api/tv/library/batch-status",{method:"POST",body:JSON.stringify({ids:t,status:r})}),onSuccess:t=>{a.success(`Status updated for ${t.updated??0} items`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]})},onError:t=>{a.error(`Failed to update statuses: ${t.message}`)}})},U=()=>{const e=o();return u({mutationFn:async t=>i("/api/tv/library/remove-many",{method:"POST",body:JSON.stringify({ids:t})}),onSuccess:t=>{const r=t.removed??0;a.success(`Removed ${r} ${r===1?"item":"items"} from TV watchlist`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-library-ids"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]})},onError:t=>{a.error(`Failed to remove: ${t.message}`)}})},E=()=>{const e=o();return u({mutationFn:async t=>i("/api/tv/progress/remove-many",{method:"POST",body:JSON.stringify({ids:t})}),onSuccess:t=>{const r=t.removed??0;a.success(`Reset progress for ${r} ${r===1?"item":"items"}`),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]})},onError:t=>{a.error(`Failed to reset progress: ${t.message}`)}})},J=()=>{const e=q(),t=b(),{data:r}=p(),s=m(()=>new Set(r?.ids||[]),[r]);return{toggle:c=>{const v=l(c.mediaType,c.tmdbId);s.has(v)?t.mutate(v):e.mutate({...c,poster:c.poster||""})},bookmarkedIds:s,pending:e.isPending||t.isPending}};export{f as T,b as a,L as b,K as c,g as d,P as e,J as f,k as g,R as h,$ as i,C as j,O as k,w as l,I as m,d as n,U as o,E as p,Q as t,F as u};
@@ -33,7 +33,7 @@
33
33
  background-color: #0c0d12;
34
34
  }
35
35
  </style>
36
- <script type="module" crossorigin src="/assets/index-CfvzoNYQ.js"></script>
36
+ <script type="module" crossorigin src="/assets/index-C65i4HWv.js"></script>
37
37
  <link rel="modulepreload" crossorigin href="/assets/vendor-core-8R2fFbyN.js">
38
38
  <link rel="modulepreload" crossorigin href="/assets/vendor-query-BXRECSI6.js">
39
39
  <link rel="modulepreload" crossorigin href="/assets/vendor-icons-CDMuNDeL.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serifpersia/dango",
3
- "version": "3.3.5",
3
+ "version": "3.3.7",
4
4
  "description": "A web application for watching anime.",
5
5
  "main": "orchestrator.js",
6
6
  "repository": {
@@ -81,5 +81,5 @@
81
81
  "allowScripts": {
82
82
  "esbuild@0.28.2": true
83
83
  },
84
- "versionDate": "2026-09-24T10:00:06.202Z"
84
+ "versionDate": "2026-09-25T14:47:03.564Z"
85
85
  }
@@ -1,6 +1,6 @@
1
1
  import logger from '../logger.js';
2
2
  import { InsightsRepository } from '../repositories/insights.repository.js';
3
- import { computeDiscordSyncStats } from '../lib/discord-roles-sync.service.js';
3
+ import { computeDiscordSyncStats, getCachedRecommendations, } from '../lib/discord-roles-sync.service.js';
4
4
  export class InsightsController {
5
5
  getWatchInsights = async (req, res) => {
6
6
  const db = req.db;
@@ -195,4 +195,8 @@ export class InsightsController {
195
195
  const db = req.db;
196
196
  res.json(await computeDiscordSyncStats(db));
197
197
  };
198
+ getRecommendations = async (req, res) => {
199
+ const db = req.db;
200
+ res.json({ candidates: await getCachedRecommendations(db) });
201
+ };
198
202
  }
@@ -128,6 +128,40 @@ export class ProxyController {
128
128
  'vibeplayer.site',
129
129
  'aniwatchtv.site',
130
130
  ];
131
+ static NEXABLOOM_FAMILY_SUFFIXES = [
132
+ '.quavex.top',
133
+ '.nexabloom.top',
134
+ '.hiddenvertex.top',
135
+ '.ironhorizon.top',
136
+ '.solarhaven.top',
137
+ ];
138
+ static NEXABLOOM_FALLBACK_HOSTS = ['fetch.nexabloom.top'];
139
+ static nexabloomFallbackUrls(urlStr) {
140
+ let host = '';
141
+ try {
142
+ host = new URL(urlStr).hostname.toLowerCase();
143
+ }
144
+ catch {
145
+ return [urlStr];
146
+ }
147
+ if (!ProxyController.NEXABLOOM_FAMILY_SUFFIXES.some((s) => host.endsWith(s))) {
148
+ return [urlStr];
149
+ }
150
+ const urls = [urlStr];
151
+ for (const fallback of ProxyController.NEXABLOOM_FALLBACK_HOSTS) {
152
+ if (fallback === host)
153
+ continue;
154
+ try {
155
+ const candidate = new URL(urlStr);
156
+ candidate.hostname = fallback;
157
+ urls.push(candidate.href);
158
+ }
159
+ catch {
160
+ // ignore
161
+ }
162
+ }
163
+ return urls;
164
+ }
131
165
  static isGotScrapingHost(urlStr) {
132
166
  try {
133
167
  const host = new URL(urlStr).hostname.toLowerCase();
@@ -259,17 +293,26 @@ export class ProxyController {
259
293
  .set('Access-Control-Allow-Origin', '*')
260
294
  .send(cached);
261
295
  }
262
- const resp = await gotScraping({
263
- url: isNexabloomMasterUrl(urlStr) ? signNexabloomMasterUrl(urlStr) : urlStr,
264
- method: 'GET',
265
- headers,
266
- responseType: 'text',
267
- timeout: { request: 30000 },
268
- followRedirect: true,
269
- throwHttpErrors: false,
270
- });
271
- if (resp.statusCode !== 200 && resp.statusCode !== 206) {
272
- return res.status(resp.statusCode ?? 502).send('Upstream error');
296
+ const resp = await (async () => {
297
+ let last = null;
298
+ for (const candidate of ProxyController.nexabloomFallbackUrls(urlStr)) {
299
+ const attempt = await gotScraping({
300
+ url: isNexabloomMasterUrl(candidate) ? signNexabloomMasterUrl(candidate) : candidate,
301
+ method: 'GET',
302
+ headers,
303
+ responseType: 'text',
304
+ timeout: { request: 30000 },
305
+ followRedirect: true,
306
+ throwHttpErrors: false,
307
+ });
308
+ last = attempt;
309
+ if (attempt.statusCode === 200 || attempt.statusCode === 206)
310
+ return attempt;
311
+ }
312
+ return last;
313
+ })();
314
+ if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
315
+ return res.status(resp?.statusCode ?? 502).send('Upstream error');
273
316
  }
274
317
  const finalUrl = resp.url || urlStr;
275
318
  const baseUrl = new URL(finalUrl);
@@ -314,17 +357,23 @@ export class ProxyController {
314
357
  if (ProxyController.isGotScrapingHost(urlStr)) {
315
358
  if (req.headers.range)
316
359
  headers['Range'] = req.headers.range;
317
- const resp = await gotScraping({
318
- url: urlStr,
319
- method: 'GET',
320
- headers,
321
- responseType: 'buffer',
322
- timeout: { request: 30000 },
323
- followRedirect: true,
324
- throwHttpErrors: false,
325
- });
326
- if (resp.statusCode !== 200 && resp.statusCode !== 206) {
327
- return res.status(resp.statusCode ?? 502).send('Upstream error');
360
+ let resp = null;
361
+ for (const candidate of ProxyController.nexabloomFallbackUrls(urlStr)) {
362
+ const attempt = await gotScraping({
363
+ url: candidate,
364
+ method: 'GET',
365
+ headers,
366
+ responseType: 'buffer',
367
+ timeout: { request: 30000 },
368
+ followRedirect: true,
369
+ throwHttpErrors: false,
370
+ });
371
+ resp = attempt;
372
+ if (attempt.statusCode === 200 || attempt.statusCode === 206)
373
+ break;
374
+ }
375
+ if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
376
+ return res.status(resp?.statusCode ?? 502).send('Upstream error');
328
377
  }
329
378
  const ct = resp.headers['content-type'];
330
379
  const isVtt = urlStr.endsWith('.vtt') || urlStr.includes('.vtt?');
@@ -342,7 +391,29 @@ export class ProxyController {
342
391
  res.send(resp.body);
343
392
  }
344
393
  else {
345
- const upstream = await fetchWithRetry(urlStr, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abortController.signal });
394
+ let upstream = null;
395
+ for (const candidate of ProxyController.nexabloomFallbackUrls(urlStr)) {
396
+ if (abortController.signal.aborted)
397
+ break;
398
+ try {
399
+ const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abortController.signal });
400
+ upstream = attempt;
401
+ if (attempt.status === 200 || attempt.status === 206)
402
+ break;
403
+ try {
404
+ await attempt.body?.cancel();
405
+ }
406
+ catch {
407
+ // ignore
408
+ }
409
+ }
410
+ catch {
411
+ // ignore
412
+ }
413
+ }
414
+ if (!upstream) {
415
+ return res.status(502).send('Upstream error');
416
+ }
346
417
  const status = upstream.status;
347
418
  if (status !== 200 && status !== 206) {
348
419
  try {
@@ -11,6 +11,7 @@ import { dbAll, dbGet } from '../utils/db-utils.js';
11
11
  import { searchAnilist, searchAnilistByTitle, getAiredEpisodesForShows, getAnilistEpisodes, getShowMetaById, isAnilistRateLimited, batchGetShowStatuses, } from '../lib/anilist.js';
12
12
  import { kitsuSearchAnime } from '../lib/kitsu.js';
13
13
  import { getMigratedId } from '../lib/migration.js';
14
+ import { offlineDb } from '../lib/offline-db.js';
14
15
  const BACKGROUND_DISCOVERY_INTERVAL_MS = 5 * 60 * 1000;
15
16
  const NUDGE_THROTTLE_MS = 120 * 1000;
16
17
  const SLOW_MAX_RUN_MS = 5 * 60 * 1000;
@@ -466,6 +467,47 @@ export class WatchlistController {
466
467
  }
467
468
  }));
468
469
  db.scheduleSave();
470
+ await this.sweepOfflinePosters(db);
471
+ }
472
+ async sweepOfflinePosters(db) {
473
+ const [wlMissing, metaMissing] = await Promise.all([
474
+ WatchlistRepository.getMissingThumbnails(db),
475
+ ShowsMetaRepository.getShowIdsMissingMeta(db),
476
+ ]);
477
+ const ids = [
478
+ ...new Set([...wlMissing.map((r) => r.id), ...metaMissing.map((r) => r.id)]),
479
+ ].slice(0, 500);
480
+ if (ids.length === 0)
481
+ return;
482
+ for (const id of ids) {
483
+ const poster = this.resolveOfflinePoster(id);
484
+ if (poster)
485
+ await this.applyPoster(db, id, poster);
486
+ }
487
+ db.scheduleSave();
488
+ }
489
+ resolveOfflinePoster(id) {
490
+ if (/^\d+$/.test(id)) {
491
+ return offlineDb.getByAnilistId(Number(id))?.thumbnail?.trim() || null;
492
+ }
493
+ const mal = /^mal-(\d+)$/i.exec(id)?.[1];
494
+ if (mal)
495
+ return offlineDb.getByMalId(Number(mal))?.thumbnail?.trim() || null;
496
+ return null;
497
+ }
498
+ async applyPoster(db, id, poster) {
499
+ try {
500
+ ShowsMetaRepository.upsert(db, { id, thumbnail: poster });
501
+ }
502
+ catch {
503
+ // ignore
504
+ }
505
+ try {
506
+ await WatchlistRepository.updateThumbnail(db, id, poster);
507
+ }
508
+ catch {
509
+ // ignore
510
+ }
469
511
  }
470
512
  async getContinueWatchingData(req, limit) {
471
513
  const rows = await WatchedEpisodesRepository.getContinueWatching(req.db, limit);