@serifpersia/dango 3.4.5 → 3.4.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 (90) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-I3S6qw9d.js → AnimeCard-Cll26FQe.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-CLU2NSBR.js → AnimeInfo-zMQgNycF.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-4Mw9ArDy.js → AnimeInfoPage-DEPX3FFf.js} +2 -2
  5. package/client/dist/assets/Asmr-BbHVH9pC.js +2 -0
  6. package/client/dist/assets/Asmr-D1KuQwQy.css +1 -0
  7. package/client/dist/assets/Asmr.module-CdWlioTY.js +1 -0
  8. package/client/dist/assets/{AsmrResetProgressModal-DyU1Z2Gw.js → AsmrResetProgressModal-D4zRL_b8.js} +1 -1
  9. package/client/dist/assets/AvSyncCalibrator-CbzA-56T.js +10 -0
  10. package/client/dist/assets/{AvSyncCalibrator-C2wu1ydI.css → AvSyncCalibrator-gEONBv5a.css} +1 -1
  11. package/client/dist/assets/Home-q7a1RjQM.js +1 -0
  12. package/client/dist/assets/{Insights-i34-qV4C.js → Insights-6H3qK9Q_.js} +1 -1
  13. package/client/dist/assets/{LibraryListPage-DE-ASOX2.js → LibraryListPage-Cc-I_48d.js} +1 -1
  14. package/client/dist/assets/ListeningList-CxfHV13T.js +1 -0
  15. package/client/dist/assets/Manga-BmNPKJB8.js +1 -0
  16. package/client/dist/assets/Manga-CqAceKxR.css +1 -0
  17. package/client/dist/assets/Manga.module-depGEacJ.js +1 -0
  18. package/client/dist/assets/MangaInfoPage-Csyq4TAu.js +1 -0
  19. package/client/dist/assets/MangaReadPage-jaSR2qX6.js +1 -0
  20. package/client/dist/assets/{MangaResetProgressModal-DOjpLAMp.js → MangaResetProgressModal-DrfFCtDE.js} +1 -1
  21. package/client/dist/assets/{Mature-D2xb9EGB.js → Mature-20QVU1lQ.js} +1 -1
  22. package/client/dist/assets/{MediaCard-CKUEcFTd.js → MediaCard-CoRm07Vb.js} +1 -1
  23. package/client/dist/assets/Music-Yry6890T.js +1 -0
  24. package/client/dist/assets/OptionListMenu-DSTuvk6i.js +1 -0
  25. package/client/dist/assets/Player-yVWIsyOS.js +56 -0
  26. package/client/dist/assets/PlayerSettings-CiazXo-J.js +1 -0
  27. package/client/dist/assets/{QueueOptionsButton-BMvAC6Ut.js → QueueOptionsButton-CpyBvbsh.js} +1 -1
  28. package/client/dist/assets/{QueueRail-CAoGJvHP.js → QueueRail-DRxByiUx.js} +1 -1
  29. package/client/dist/assets/Radio-DxL91QOj.js +1 -0
  30. package/client/dist/assets/ReadingList-CNm6nFC1.js +1 -0
  31. package/client/dist/assets/{ResetProgressModal-vFC6N-Qw.js → ResetProgressModal-DiYoRAaR.js} +1 -1
  32. package/client/dist/assets/{Search-Bh9Y6nIJ.js → Search-BBRaD1PE.js} +1 -1
  33. package/client/dist/assets/{Settings-DRumujfh.js → Settings-C3ZhNo9P.js} +1 -1
  34. package/client/dist/assets/{SkeletonGrid-BtzUnQE0.js → SkeletonGrid-BAQn8Rru.js} +1 -1
  35. package/client/dist/assets/{Trackers-D0nqy8wu.js → Trackers-CZd3KCwM.js} +1 -1
  36. package/client/dist/assets/Tv-O1-yrBt9.js +4 -0
  37. package/client/dist/assets/{TvInfo-BkECbOKN.js → TvInfo-oMcOTrSG.js} +1 -1
  38. package/client/dist/assets/{TvPopup-D8WWY6o4.js → TvPopup-BaAmlbH4.js} +1 -1
  39. package/client/dist/assets/{TvSearch-kWPep3gL.js → TvSearch-CjGUazdR.js} +1 -1
  40. package/client/dist/assets/{TvWatchlist-dD4emIHx.js → TvWatchlist-W51KZDQP.js} +1 -1
  41. package/client/dist/assets/{Watchlist-cWxw38nU.js → Watchlist-DBtN7kPt.js} +1 -1
  42. package/client/dist/assets/{hls-uG_Y3i5s.js → hls-CSxWA5I-.js} +1 -1
  43. package/client/dist/assets/index-CQWyVhME.js +178 -0
  44. package/client/dist/assets/page-DRvjODkg.css +1 -0
  45. package/client/dist/assets/page.module-C1MlZzf3.js +1 -0
  46. package/client/dist/assets/{useAnimeInfoData-DjPBg24u.js → useAnimeInfoData-CthDMEMc.js} +1 -1
  47. package/client/dist/assets/{useAsmr-DQyobNQX.js → useAsmr-DH8inTBw.js} +1 -1
  48. package/client/dist/assets/useAsmrPopup-BfkGmSSw.js +1 -0
  49. package/client/dist/assets/{useMangaLibrary-C6L8mrnS.js → useMangaLibrary-CpQLCvkU.js} +1 -1
  50. package/client/dist/assets/useMangaPopup-B5LXZINf.js +1 -0
  51. package/client/dist/assets/usePopup-DS9jMzp6.js +1 -0
  52. package/client/dist/assets/useProviders-DYy7lHj6.js +1 -0
  53. package/client/dist/assets/{useTv-DzSYasru.js → useTv-DvKLIudk.js} +1 -1
  54. package/client/dist/assets/{useTvLibrary-BnbHS-gg.js → useTvLibrary-ChI7tnUw.js} +1 -1
  55. package/client/dist/index.html +1 -1
  56. package/package.json +2 -2
  57. package/server/dist/hono/proxy.js +102 -75
  58. package/server/dist/hono/tv.js +2 -2
  59. package/server/dist/hono/watchlist.js +2 -291
  60. package/server/dist/lib/anilist.js +0 -3
  61. package/server/dist/lib/mal.js +0 -38
  62. package/server/dist/lib/offline-db.js +0 -6
  63. package/server/dist/providers/remote-loader.js +15 -26
  64. package/server/dist/providers/title-matching.js +5 -5
  65. package/server/dist/repositories/manga.repository.js +0 -1
  66. package/server/dist/repositories/watched-episodes.repository.js +0 -2
  67. package/server/dist/utils/megaplay.utils.js +1 -2
  68. package/server/package.json +1 -1
  69. package/client/dist/assets/Asmr-Cq2ua6Zk.css +0 -1
  70. package/client/dist/assets/Asmr-DN9XBc5Y.js +0 -2
  71. package/client/dist/assets/Asmr.module-ChUXDZKx.js +0 -1
  72. package/client/dist/assets/AvSyncCalibrator-DxL0mr_4.js +0 -10
  73. package/client/dist/assets/Home-CeTeJ-gM.js +0 -1
  74. package/client/dist/assets/ListeningList-DrNce5ll.js +0 -1
  75. package/client/dist/assets/Manga-DOrf7Fk6.js +0 -1
  76. package/client/dist/assets/Manga-Dp_-l1Pw.css +0 -1
  77. package/client/dist/assets/Manga.module-BcfOdsNP.js +0 -1
  78. package/client/dist/assets/MangaInfoPage-CAZMLMGw.js +0 -1
  79. package/client/dist/assets/MangaReadPage-DQVEo8_F.js +0 -1
  80. package/client/dist/assets/Music-w_a34r7r.js +0 -1
  81. package/client/dist/assets/OptionListMenu-DxZHBMIZ.js +0 -1
  82. package/client/dist/assets/Player-B8YHeATv.js +0 -56
  83. package/client/dist/assets/PlayerSettings-Ddlg-MvW.js +0 -1
  84. package/client/dist/assets/Radio-6O7rAUv1.js +0 -1
  85. package/client/dist/assets/ReadingList-k3g8oHvs.js +0 -1
  86. package/client/dist/assets/Tv-DVebLMNS.js +0 -4
  87. package/client/dist/assets/index-BWIWE3Qy.js +0 -178
  88. package/client/dist/assets/useAsmrPopup-CDoroMBD.js +0 -1
  89. package/client/dist/assets/useMangaPopup-BSuMa_vA.js +0 -1
  90. package/client/dist/assets/useProviders-XIdvDof6.js +0 -1
@@ -1 +1 @@
1
- import{T as y}from"./vendor-core-DfaOB3Rn.js";import{b as v,u as o,a as n}from"./vendor-query-BBT5_sHx.js";import{h as a,z as i,t as p}from"./index-BWIWE3Qy.js";const d=t=>String(t).toLowerCase()==="movie"?"movie":"tv",m=(t,e)=>`${d(t)}:${e}`,L=(t,e)=>{const r=d(t);return`/tv/${e}?type=${r}`},S=(t,e,r,s)=>{const u=new URLSearchParams;return u.set("type",d(t)),r!==void 0&&u.set("s",String(r)),s!==void 0&&u.set("e",String(s)),`/tv/${e}/watch?${u.toString()}`},F=t=>t.adult===!0||t.adult===1,K=["Watching","Completed","On-Hold","Dropped","Planned"],g=(t,e)=>m(t,e),$=(t="All",e=1,r=24)=>{const s=a();return v(s.tvLibrary.list.queryOptions({status:t,page:e,limit:r}))},h=()=>{const t=a();return v({...t.tvLibrary.ids.queryOptions(),staleTime:1e3*60})},C=t=>{const e=a();return v({...e.tvLibrary.check.queryOptions({id:t||""}),enabled:!!t,staleTime:1e3*60})},w=(t=24)=>{const e=a();return v(e.tvProgress.continueWatching.queryOptions({limit:t}))},O=t=>{const e=a();return v({...e.tvProgress.getByMedia.queryOptions({mediaId:t||""}),enabled:!!t})},f=(t,e,r)=>{const s=a();return v({...s.tvProgress.latest.queryOptions({mediaId:t||"",season:e,episode:r}),enabled:!!t,staleTime:0})},b=()=>{const t=a(),e=o();return n({mutationFn:async r=>{const{silent:s,...u}=r;return p.tvLibrary.add.mutate(u)},onSuccess:(r,s)=>{s.silent||i.success("Added to TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter())},onError:r=>{i.error(`Failed to add: ${r.message}`)}})},Q=()=>{const t=a(),e=o();return n(t.tvLibrary.remove.mutationOptions({onSuccess:()=>{i.success("Removed from TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to remove: ${r.message}`)}}))},k=()=>{const t=a(),e=o();return n(t.tvLibrary.setStatus.mutationOptions({onSuccess:()=>{i.success("Status updated"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to update status: ${r.message}`)}}))},R=()=>{const t=a(),e=o();return n(t.tvProgress.save.mutationOptions({onSuccess:(r,s)=>{e.invalidateQueries({queryKey:["tv-progress",s.mediaId]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())}}))},B=()=>{const t=a(),e=o();return n(t.tvProgress.remove.mutationOptions({onSuccess:()=>{i.success("Progress reset"),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to reset progress: ${r.message}`)}}))},E=()=>{const t=a(),e=o();return n(t.tvLibrary.batchStatus.mutationOptions({onSuccess:r=>{i.success(`Status updated for ${r.updated??0} items`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to update statuses: ${r.message}`)}}))},I=()=>{const t=a(),e=o();return n(t.tvLibrary.removeMany.mutationOptions({onSuccess:r=>{const s=r.removed??0;i.success(`Removed ${s} ${s===1?"item":"items"} from TV watchlist`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to remove: ${r.message}`)}}))},A=()=>{const t=a(),e=o();return n(t.tvProgress.removeMany.mutationOptions({onSuccess:r=>{const s=r.removed??0;i.success(`Reset progress for ${s} ${s===1?"item":"items"}`),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to reset progress: ${r.message}`)}}))},M=()=>{const t=b(),e=Q(),{data:r}=h(),s=y(()=>new Set(r?.ids||[]),[r]);return{toggle:c=>{const l=g(c.mediaType,c.tmdbId);s.has(l)?e.mutate(l):t.mutate({...c,poster:c.poster||""})},bookmarkedIds:s,pending:t.isPending||e.isPending}};export{K as T,Q as a,B as b,L as c,m as d,C as e,M as f,R as g,k as h,F as i,f as j,O as k,$ as l,E as m,d as n,I as o,A as p,S as t,w as u};
1
+ import{T as y}from"./vendor-core-DfaOB3Rn.js";import{b as v,u as o,a as n}from"./vendor-query-BBT5_sHx.js";import{h as a,z as i,t as p}from"./index-CQWyVhME.js";const d=t=>String(t).toLowerCase()==="movie"?"movie":"tv",m=(t,e)=>`${d(t)}:${e}`,L=(t,e)=>{const r=d(t);return`/tv/${e}?type=${r}`},S=(t,e,r,s)=>{const u=new URLSearchParams;return u.set("type",d(t)),r!==void 0&&u.set("s",String(r)),s!==void 0&&u.set("e",String(s)),`/tv/${e}/watch?${u.toString()}`},F=t=>t.adult===!0||t.adult===1,K=["Watching","Completed","On-Hold","Dropped","Planned"],g=(t,e)=>m(t,e),$=(t="All",e=1,r=24)=>{const s=a();return v(s.tvLibrary.list.queryOptions({status:t,page:e,limit:r}))},h=()=>{const t=a();return v({...t.tvLibrary.ids.queryOptions(),staleTime:1e3*60})},C=t=>{const e=a();return v({...e.tvLibrary.check.queryOptions({id:t||""}),enabled:!!t,staleTime:1e3*60})},w=(t=24)=>{const e=a();return v(e.tvProgress.continueWatching.queryOptions({limit:t}))},O=t=>{const e=a();return v({...e.tvProgress.getByMedia.queryOptions({mediaId:t||""}),enabled:!!t})},f=(t,e,r)=>{const s=a();return v({...s.tvProgress.latest.queryOptions({mediaId:t||"",season:e,episode:r}),enabled:!!t,staleTime:0})},b=()=>{const t=a(),e=o();return n({mutationFn:async r=>{const{silent:s,...u}=r;return p.tvLibrary.add.mutate(u)},onSuccess:(r,s)=>{s.silent||i.success("Added to TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter())},onError:r=>{i.error(`Failed to add: ${r.message}`)}})},Q=()=>{const t=a(),e=o();return n(t.tvLibrary.remove.mutationOptions({onSuccess:()=>{i.success("Removed from TV watchlist"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to remove: ${r.message}`)}}))},k=()=>{const t=a(),e=o();return n(t.tvLibrary.setStatus.mutationOptions({onSuccess:()=>{i.success("Status updated"),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to update status: ${r.message}`)}}))},R=()=>{const t=a(),e=o();return n(t.tvProgress.save.mutationOptions({onSuccess:(r,s)=>{e.invalidateQueries({queryKey:["tv-progress",s.mediaId]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())}}))},B=()=>{const t=a(),e=o();return n(t.tvProgress.remove.mutationOptions({onSuccess:()=>{i.success("Progress reset"),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to reset progress: ${r.message}`)}}))},E=()=>{const t=a(),e=o();return n(t.tvLibrary.batchStatus.mutationOptions({onSuccess:r=>{i.success(`Status updated for ${r.updated??0} items`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to update statuses: ${r.message}`)}}))},I=()=>{const t=a(),e=o();return n(t.tvLibrary.removeMany.mutationOptions({onSuccess:r=>{const s=r.removed??0;i.success(`Removed ${s} ${s===1?"item":"items"} from TV watchlist`),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to remove: ${r.message}`)}}))},A=()=>{const t=a(),e=o();return n(t.tvProgress.removeMany.mutationOptions({onSuccess:r=>{const s=r.removed??0;i.success(`Reset progress for ${s} ${s===1?"item":"items"}`),e.invalidateQueries({queryKey:["tv-progress"]}),e.invalidateQueries({queryKey:["tv-continue-watching"]}),e.invalidateQueries({queryKey:["tv-library"]}),e.invalidateQueries(t.tvLibrary.pathFilter()),e.invalidateQueries(t.tvProgress.pathFilter())},onError:r=>{i.error(`Failed to reset progress: ${r.message}`)}}))},M=()=>{const t=b(),e=Q(),{data:r}=h(),s=y(()=>new Set(r?.ids||[]),[r]);return{toggle:c=>{const l=g(c.mediaType,c.tmdbId);s.has(l)?e.mutate(l):t.mutate({...c,poster:c.poster||""})},bookmarkedIds:s,pending:t.isPending||e.isPending}};export{K as T,Q as a,B as b,L as c,m as d,C as e,M as f,R as g,k as h,F as i,f as j,O as k,$ as l,E as m,d as n,I as o,A as p,S as t,w as u};
@@ -33,7 +33,7 @@
33
33
  background-color: #0c0d12;
34
34
  }
35
35
  </style>
36
- <script type="module" crossorigin src="/assets/index-BWIWE3Qy.js"></script>
36
+ <script type="module" crossorigin src="/assets/index-CQWyVhME.js"></script>
37
37
  <link rel="modulepreload" crossorigin href="/assets/vendor-core-DfaOB3Rn.js">
38
38
  <link rel="modulepreload" crossorigin href="/assets/vendor-query-BBT5_sHx.js">
39
39
  <link rel="modulepreload" crossorigin href="/assets/vendor-icons-DrybRNxP.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serifpersia/dango",
3
- "version": "3.4.5",
3
+ "version": "3.4.7",
4
4
  "description": "A web application for watching anime.",
5
5
  "main": "orchestrator.js",
6
6
  "repository": {
@@ -83,5 +83,5 @@
83
83
  "allowScripts": {
84
84
  "esbuild@0.28.2": true
85
85
  },
86
- "versionDate": "2026-09-30T14:34:23.844Z"
86
+ "versionDate": "2026-10-02T12:09:50.083Z"
87
87
  }
@@ -82,6 +82,50 @@ function assToVtt(body) {
82
82
  return null;
83
83
  return `WEBVTT\n\n${cues.join('\n\n')}\n`;
84
84
  }
85
+ const SUB_WRAP_AT = 50;
86
+ function splitBalancedCueLine(line) {
87
+ const words = line.split(/\s+/).filter((w) => w.length > 0);
88
+ if (words.length < 2)
89
+ return [line];
90
+ const total = words.join(' ').length;
91
+ if (total <= SUB_WRAP_AT)
92
+ return [line];
93
+ const target = total / 2;
94
+ let acc = words[0]?.length ?? 0;
95
+ let best = 1;
96
+ let bestDiff = Math.abs(acc - target);
97
+ for (let i = 1; i < words.length; i++) {
98
+ acc += 1 + (words[i]?.length ?? 0);
99
+ const diff = Math.abs(acc - target);
100
+ if (diff < bestDiff) {
101
+ bestDiff = diff;
102
+ best = i + 1;
103
+ }
104
+ }
105
+ if (best <= 0 || best >= words.length)
106
+ return [line];
107
+ return [words.slice(0, best).join(' '), words.slice(best).join(' ')];
108
+ }
109
+ function rewrapVttCues(vtt) {
110
+ const blocks = vtt.replace(/\r\n/g, '\n').split(/\n\n+/);
111
+ return blocks
112
+ .map((block, idx) => {
113
+ if (idx === 0 && /^\s*WEBVTT/i.test(block))
114
+ return block;
115
+ const lines = block.split('\n');
116
+ const tIdx = lines.findIndex((l) => l.includes('-->'));
117
+ if (tIdx < 0)
118
+ return block;
119
+ const payload = lines.slice(tIdx + 1).filter((l) => l.trim() !== '');
120
+ if (payload.length !== 1)
121
+ return block;
122
+ const split = splitBalancedCueLine((payload[0] ?? '').trim());
123
+ if (split.length < 2)
124
+ return block;
125
+ return [...lines.slice(0, tIdx + 1), ...split].join('\n');
126
+ })
127
+ .join('\n\n');
128
+ }
85
129
  const KWIK_DOMAINS = new Set(['kwik.cx', 'kwik.si', 'kwik.pro']);
86
130
  const ANIMEPAHE_URL = 'https://animepahe.pw/';
87
131
  const VAULT_CDN_HOSTS = new Set(['uwucdn.top', 'owocdn.top']);
@@ -159,6 +203,20 @@ function nexabloomFallbackUrls(urlStr) {
159
203
  }
160
204
  return urls;
161
205
  }
206
+ async function fetchNexabloomFallback(urlStr, signal, attempt, isOk) {
207
+ let last = null;
208
+ for (const candidate of nexabloomFallbackUrls(urlStr)) {
209
+ if (signal.aborted)
210
+ break;
211
+ const r = await attempt(candidate).catch(() => null);
212
+ if (!r)
213
+ continue;
214
+ if (isOk(r))
215
+ return r;
216
+ last = r;
217
+ }
218
+ return last;
219
+ }
162
220
  function isGotScrapingHost(urlStr) {
163
221
  try {
164
222
  const host = new URL(urlStr).hostname.toLowerCase();
@@ -374,24 +432,15 @@ export function registerProxy(app) {
374
432
  headers.set('Access-Control-Allow-Origin', '*');
375
433
  return new Response(cached, { headers });
376
434
  }
377
- const resp = await (async () => {
378
- let last = null;
379
- for (const candidate of nexabloomFallbackUrls(urlStr)) {
380
- const attempt = await gotScraping({
381
- url: isNexabloomMasterUrl(candidate) ? signNexabloomMasterUrl(candidate) : candidate,
382
- method: 'GET',
383
- headers,
384
- responseType: 'text',
385
- timeout: { request: 30000 },
386
- followRedirect: true,
387
- throwHttpErrors: false,
388
- });
389
- last = attempt;
390
- if (attempt.statusCode === 200 || attempt.statusCode === 206)
391
- return attempt;
392
- }
393
- return last;
394
- })();
435
+ const resp = await fetchNexabloomFallback(urlStr, abort.signal, (candidate) => gotScraping({
436
+ url: isNexabloomMasterUrl(candidate) ? signNexabloomMasterUrl(candidate) : candidate,
437
+ method: 'GET',
438
+ headers,
439
+ responseType: 'text',
440
+ timeout: { request: 30000 },
441
+ followRedirect: true,
442
+ throwHttpErrors: false,
443
+ }), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
395
444
  if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
396
445
  return c.text('Upstream error', (resp?.statusCode || 502));
397
446
  }
@@ -438,21 +487,15 @@ export function registerProxy(app) {
438
487
  if (isGotScrapingHost(urlStr)) {
439
488
  if (range)
440
489
  headers['Range'] = range;
441
- let resp = null;
442
- for (const candidate of nexabloomFallbackUrls(urlStr)) {
443
- const attempt = await gotScraping({
444
- url: candidate,
445
- method: 'GET',
446
- headers,
447
- responseType: 'buffer',
448
- timeout: { request: 30000 },
449
- followRedirect: true,
450
- throwHttpErrors: false,
451
- });
452
- resp = attempt;
453
- if (attempt.statusCode === 200 || attempt.statusCode === 206)
454
- break;
455
- }
490
+ const resp = await fetchNexabloomFallback(urlStr, abort.signal, (candidate) => gotScraping({
491
+ url: candidate,
492
+ method: 'GET',
493
+ headers,
494
+ responseType: 'buffer',
495
+ timeout: { request: 30000 },
496
+ followRedirect: true,
497
+ throwHttpErrors: false,
498
+ }), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
456
499
  if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
457
500
  return c.text('Upstream error', (resp?.statusCode || 502));
458
501
  }
@@ -467,15 +510,9 @@ export function registerProxy(app) {
467
510
  });
468
511
  }
469
512
  else {
470
- let upstream = null;
471
- for (const candidate of nexabloomFallbackUrls(urlStr)) {
472
- if (abort.signal.aborted)
473
- break;
474
- try {
475
- const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abort.signal });
476
- upstream = attempt;
477
- if (attempt.status === 200 || attempt.status === 206)
478
- break;
513
+ const upstream = await fetchNexabloomFallback(urlStr, abort.signal, async (candidate) => {
514
+ const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abort.signal });
515
+ if (attempt.status !== 200 && attempt.status !== 206) {
479
516
  try {
480
517
  await attempt.body?.cancel();
481
518
  }
@@ -483,10 +520,8 @@ export function registerProxy(app) {
483
520
  // ignore
484
521
  }
485
522
  }
486
- catch {
487
- // ignore
488
- }
489
- }
523
+ return attempt;
524
+ }, (attempt) => attempt.status === 200 || attempt.status === 206);
490
525
  if (!upstream) {
491
526
  return c.text('Upstream error', 502);
492
527
  }
@@ -649,6 +684,12 @@ export function registerProxy(app) {
649
684
  headers.set('Cache-Control', cacheControl);
650
685
  return headers;
651
686
  };
687
+ const subtitleError = (text) => {
688
+ const headers = new Headers();
689
+ headers.set('Access-Control-Allow-Origin', '*');
690
+ headers.set('Cache-Control', 'no-store');
691
+ return new Response(text, { status: 502, headers });
692
+ };
652
693
  try {
653
694
  const headers = buildHeaders();
654
695
  let result = await fetchText(subUrl, 1);
@@ -660,10 +701,7 @@ export function registerProxy(app) {
660
701
  return new Response(null, { status: 499 });
661
702
  }
662
703
  if (!result || !result.body) {
663
- const headers = new Headers();
664
- headers.set('Access-Control-Allow-Origin', '*');
665
- headers.set('Cache-Control', 'no-store');
666
- return new Response('Subtitle upstream error', { status: 502, headers });
704
+ return subtitleError('Subtitle upstream error');
667
705
  }
668
706
  const body = result.body;
669
707
  const baseForSegments = innerRawUrl || subUrl;
@@ -709,32 +747,30 @@ export function registerProxy(app) {
709
747
  }
710
748
  }
711
749
  if (parts.length === 0) {
712
- const headers = new Headers();
713
- headers.set('Access-Control-Allow-Origin', '*');
714
- headers.set('Cache-Control', 'no-store');
715
- return new Response('Subtitle playlist empty', { status: 502, headers });
750
+ return subtitleError('Subtitle playlist empty');
716
751
  }
717
- return new Response(`WEBVTT\n\n${parts.join('\n\n')}\n`, {
752
+ return new Response(rewrapVttCues(`WEBVTT\n\n${parts.join('\n\n')}\n`), {
718
753
  headers: vttHeaders('public, max-age=86400'),
719
754
  });
720
755
  }
721
756
  if (/^\s*WEBVTT/i.test(body)) {
722
- return new Response(body, { headers: vttHeaders('public, max-age=86400') });
757
+ return new Response(rewrapVttCues(body), {
758
+ headers: vttHeaders('public, max-age=86400'),
759
+ });
723
760
  }
724
761
  const vttFromAss = assToVtt(body);
725
762
  if (vttFromAss) {
726
- return new Response(vttFromAss, { headers: vttHeaders('public, max-age=86400') });
763
+ return new Response(rewrapVttCues(vttFromAss), {
764
+ headers: vttHeaders('public, max-age=86400'),
765
+ });
727
766
  }
728
- return new Response(`WEBVTT\n\n${body.replace(/\r\n/g, '\n').replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2')}`, { headers: vttHeaders('public, max-age=86400') });
767
+ return new Response(rewrapVttCues(`WEBVTT\n\n${body.replace(/\r\n/g, '\n').replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2')}`), { headers: vttHeaders('public, max-age=86400') });
729
768
  }
730
769
  catch {
731
770
  if (abort.signal.aborted)
732
771
  return new Response(null, { status: 499 });
733
772
  logger.warn({ url: subUrl }, '[subtitle-proxy] failed');
734
- const headers = new Headers();
735
- headers.set('Access-Control-Allow-Origin', '*');
736
- headers.set('Cache-Control', 'no-store');
737
- return new Response('Subtitle upstream error', { status: 502, headers });
773
+ return subtitleError('Subtitle upstream error');
738
774
  }
739
775
  });
740
776
  app.get('/api/image-proxy', async (c) => {
@@ -758,12 +794,7 @@ export function registerProxy(app) {
758
794
  try {
759
795
  if (targetUrl.includes('animepahe')) {
760
796
  refererValue = 'https://animepahe.pw/';
761
- const rawCookie = cookie || '';
762
- let sanitized = rawCookie.trim();
763
- sanitized = sanitized.replace(/^cf_clearance/i, '');
764
- sanitized = sanitized.replace(/^[:=]\s*/, '');
765
- sanitized = sanitized.replace(/["']/g, '').trim();
766
- headers['Cookie'] = `cf_clearance=${sanitized}`;
797
+ headers['Cookie'] = `cf_clearance=${sanitizeCfClearance(cookie)}`;
767
798
  }
768
799
  else if (targetUrl.includes('anilist.co')) {
769
800
  refererValue = 'https://anilist.co/';
@@ -779,13 +810,9 @@ export function registerProxy(app) {
779
810
  }
780
811
  else if (targetUrl.includes('weeabo0.xyz')) {
781
812
  refererValue = 'https://japaneseasmr.com/';
782
- const rawCookie = cookie || '';
783
- let sanitized = rawCookie.trim();
784
- sanitized = sanitized.replace(/^cf_clearance/i, '');
785
- sanitized = sanitized.replace(/^[:=]\s*/, '');
786
- sanitized = sanitized.replace(/["']/g, '').trim();
787
- if (sanitized)
788
- headers['Cookie'] = `cf_clearance=${sanitized}`;
813
+ const jasmrCookieHeader = buildCfClearanceCookie(cookie);
814
+ if (jasmrCookieHeader)
815
+ headers['Cookie'] = jasmrCookieHeader;
789
816
  const uaParam = ua || '';
790
817
  if (uaParam)
791
818
  headers['User-Agent'] = uaParam;
@@ -5,7 +5,7 @@ import { isSafeExternalUrl } from '../utils/security.utils.js';
5
5
  function proxiedMediaUrl(targetUrl, refererStr) {
6
6
  return `/api/tv/stream-proxy?url=${encodeURIComponent(targetUrl)}&referer=${encodeURIComponent(refererStr)}`;
7
7
  }
8
- export function rewriteTvPlaylist(body, baseUrl, refererStr) {
8
+ function rewriteTvPlaylist(body, baseUrl, refererStr) {
9
9
  return body
10
10
  .split('\n')
11
11
  .map((line) => {
@@ -23,7 +23,7 @@ export function rewriteTvPlaylist(body, baseUrl, refererStr) {
23
23
  })
24
24
  .join('\n');
25
25
  }
26
- export function isPlaylistBody(body) {
26
+ function isPlaylistBody(body) {
27
27
  return (body.length >= 7 &&
28
28
  body
29
29
  .subarray(0, 32)
@@ -1,13 +1,11 @@
1
1
  import logger from '../logger.js';
2
2
  import { WatchlistRepository } from '../repositories/watchlist.repository.js';
3
- import { WatchedEpisodesRepository, } from '../repositories/watched-episodes.repository.js';
3
+ import { WatchedEpisodesRepository } from '../repositories/watched-episodes.repository.js';
4
4
  import { ShowsMetaRepository } from '../repositories/shows-meta.repository.js';
5
5
  import { NotificationsRepository } from '../repositories/notifications.repository.js';
6
- import { SettingsRepository } from '../repositories/settings.repository.js';
7
6
  import { dbAll } from '../utils/db-utils.js';
8
7
  import { AppCache } from '../utils/cache.utils.js';
9
- import { searchAnilist, getAiredEpisodesForShows, getAnilistEpisodes, getShowMetaById, isAnilistRateLimited, batchGetShowStatuses, } from '../lib/anilist.js';
10
- import { offlineDb } from '../lib/offline-db.js';
8
+ import { getAiredEpisodesForShows, isAnilistRateLimited, batchGetShowStatuses, } from '../lib/anilist.js';
11
9
  import { parseJsonBody } from '../utils/http.utils.js';
12
10
  import { pickBestMatch } from '../providers/title-matching.js';
13
11
  const BACKGROUND_DISCOVERY_INTERVAL_MS = 5 * 60 * 1000;
@@ -258,286 +256,6 @@ export function startWatchlistDiscovery(getDb) {
258
256
  runDiscovery(false);
259
257
  }, BACKGROUND_DISCOVERY_INTERVAL_MS);
260
258
  }
261
- async function showsMetaChanged(db, showId, candidate) {
262
- const existing = (await ShowsMetaRepository.getById(db, showId));
263
- if (!existing)
264
- return true;
265
- const differs = (incoming, stored) => {
266
- if (incoming === undefined || incoming === null || incoming === '')
267
- return false;
268
- return String(incoming) !== String(stored ?? '');
269
- };
270
- return (differs(candidate.name, existing.name) ||
271
- differs(candidate.thumbnail, existing.thumbnail) ||
272
- differs(candidate.nativeName, existing.nativeName) ||
273
- differs(candidate.englishName, existing.englishName) ||
274
- differs(candidate.genres, existing.genres) ||
275
- differs(candidate.popularityScore, existing.popularityScore) ||
276
- differs(candidate.status, existing.status) ||
277
- differs(candidate.episodeCount, existing.episodeCount) ||
278
- differs(candidate.type, existing.type) ||
279
- differs(candidate.anilistId, existing.anilistId) ||
280
- differs(candidate.isAdult, existing.isAdult) ||
281
- differs(candidate.episodeDuration, existing.episodeDuration));
282
- }
283
- function normalizeFilterValue(value) {
284
- if (typeof value !== 'string')
285
- return undefined;
286
- const trimmed = value.trim();
287
- return trimmed && trimmed !== 'ALL' ? trimmed : undefined;
288
- }
289
- function getWatchlistFilters(query) {
290
- return {
291
- query: normalizeFilterValue(query['query']),
292
- type: normalizeFilterValue(query['type']),
293
- season: normalizeFilterValue(query['season']),
294
- year: normalizeFilterValue(query['year']),
295
- genres: normalizeFilterValue(query['genres']),
296
- excludeGenres: normalizeFilterValue(query['excludeGenres']),
297
- sortBy: normalizeFilterValue(query['sortBy']),
298
- titlePreference: ['name', 'nativeName', 'englishName'].includes(String(query['titlePreference']))
299
- ? query['titlePreference']
300
- : 'name',
301
- };
302
- }
303
- function matchesLocalFilters(row, filters) {
304
- if (filters.query) {
305
- const queryWords = new Set(filters.query
306
- .toLowerCase()
307
- .split(/\s+/)
308
- .filter((w) => w.length >= 2));
309
- const rowTitle = (row.englishName || row.name || row.nativeName || '').toLowerCase();
310
- const titleWords = rowTitle.split(/\s+/);
311
- const overlap = titleWords.filter((w) => queryWords.has(w)).length;
312
- if (overlap < queryWords.size)
313
- return false;
314
- }
315
- return true;
316
- }
317
- function sortFilteredRows(rows, filters) {
318
- const getSortTitle = (row) => {
319
- const preferredTitle = filters.titlePreference ? row[filters.titlePreference] : undefined;
320
- return preferredTitle || row.name || '';
321
- };
322
- if (filters.sortBy === 'name_asc') {
323
- return [...rows].sort((a, b) => getSortTitle(a).localeCompare(getSortTitle(b)));
324
- }
325
- if (filters.sortBy === 'name_desc') {
326
- return [...rows].sort((a, b) => getSortTitle(b).localeCompare(getSortTitle(a)));
327
- }
328
- return rows;
329
- }
330
- async function getAnilistSeasonYearMatches(season, year) {
331
- const matched = new Map();
332
- if (!year || year === 'ALL') {
333
- return matched;
334
- }
335
- const seasonYear = parseInt(year);
336
- if (Number.isNaN(seasonYear))
337
- return matched;
338
- const perPage = 50;
339
- let page = 1;
340
- while (true) {
341
- const searchVars = {
342
- seasonYear,
343
- page,
344
- perPage,
345
- };
346
- if (season && season !== 'ALL') {
347
- searchVars.season = season.toUpperCase();
348
- }
349
- const results = await searchAnilist(searchVars);
350
- for (const show of results) {
351
- if (show.anilistId) {
352
- matched.set(show.anilistId, { title: show.names || {} });
353
- }
354
- }
355
- if (results.length < perPage)
356
- break;
357
- page++;
358
- if (page > 10)
359
- break;
360
- }
361
- return matched;
362
- }
363
- function rowMatchesAnilistSeasonYear(row, anilistMatches) {
364
- if (anilistMatches.size === 0)
365
- return true;
366
- if (/^\d+$/.test(row.id) && anilistMatches.has(parseInt(row.id))) {
367
- return true;
368
- }
369
- const rowTitle = (row.englishName || row.name || row.nativeName || '').toLowerCase();
370
- if (!rowTitle)
371
- return false;
372
- const rowWords = new Set(rowTitle.split(/\s+/).filter((w) => w.length >= 2));
373
- if (rowWords.size === 0)
374
- return false;
375
- for (const [, media] of anilistMatches) {
376
- const titles = [media.title?.romaji, media.title?.english, media.title?.native].filter(Boolean);
377
- for (const title of titles) {
378
- const titleWords = new Set(title
379
- .toLowerCase()
380
- .split(/\s+/)
381
- .filter((w) => w.length >= 2));
382
- const overlap = [...rowWords].filter((w) => titleWords.has(w)).length;
383
- const minLen = Math.min(rowWords.size, titleWords.size);
384
- if (minLen >= 2 && overlap / minLen >= 0.7)
385
- return true;
386
- }
387
- }
388
- return false;
389
- }
390
- async function filterWatchlistRows(rows, filters, db) {
391
- let filtered = rows.filter((row) => matchesLocalFilters(row, filters));
392
- if (filters.type === 'ADULT' && db && filtered.length > 0) {
393
- const ids = filtered.map((r) => r.id);
394
- const placeholders = ids.map(() => '?').join(',');
395
- const adultRows = await dbAll(db, `SELECT id FROM shows_meta WHERE id IN (${placeholders}) AND isAdult = 1`, ids);
396
- const adultSet = new Set(adultRows.map((r) => r.id));
397
- filtered = filtered.filter((row) => adultSet.has(row.id));
398
- }
399
- else if (filters.type && filters.type !== 'ALL' && filters.type !== 'ADULT') {
400
- filtered = filtered.filter((row) => row.type === filters.type);
401
- }
402
- if ((filters.genres || filters.excludeGenres) && db) {
403
- const ids = filtered.map((r) => r.id);
404
- const placeholders = ids.map(() => '?').join(',');
405
- const genreRows = await dbAll(db, `SELECT id, genres FROM shows_meta WHERE id IN (${placeholders})`, ids);
406
- const idToGenres = new Map(genreRows.map((r) => [r.id, r.genres ? JSON.parse(r.genres) : []]));
407
- const includeList = filters.genres
408
- ?.split(',')
409
- .map((g) => g.trim())
410
- .filter(Boolean) || [];
411
- const excludeList = filters.excludeGenres
412
- ?.split(',')
413
- .map((g) => g.trim())
414
- .filter(Boolean) || [];
415
- filtered = filtered.filter((row) => {
416
- const rowGenres = idToGenres.get(row.id) || [];
417
- if (includeList.length && !includeList.every((g) => rowGenres.includes(g)))
418
- return false;
419
- if (excludeList.length && excludeList.some((g) => rowGenres.includes(g)))
420
- return false;
421
- return true;
422
- });
423
- }
424
- if (((filters.season && filters.season !== 'ALL') || (filters.year && filters.year !== 'ALL')) &&
425
- filtered.length > 0) {
426
- const anilistMatches = await getAnilistSeasonYearMatches(filters.season, filters.year);
427
- if (anilistMatches.size > 0) {
428
- filtered = filtered.filter((row) => rowMatchesAnilistSeasonYear(row, anilistMatches));
429
- }
430
- }
431
- return sortFilteredRows(filtered, filters);
432
- }
433
- async function backfillMissingPosters(db, rows) {
434
- const missing = rows.filter((r) => !r.thumbnail || r.thumbnail.trim() === '');
435
- if (missing.length === 0)
436
- return;
437
- const slice = missing.slice(0, 10);
438
- await Promise.allSettled(slice.map(async (row) => {
439
- try {
440
- const meta = await getShowMetaById(row.id);
441
- const poster = meta?.thumbnail?.trim();
442
- if (!poster)
443
- return;
444
- row.thumbnail = poster;
445
- const dual = row;
446
- if (dual._id !== undefined)
447
- dual._id = row.id;
448
- try {
449
- await ShowsMetaRepository.upsert(db, {
450
- id: row.id,
451
- thumbnail: poster,
452
- popularityScore: meta?.score ?? (meta?.averageScore != null ? meta.averageScore / 10 : undefined),
453
- });
454
- }
455
- catch {
456
- // ignore
457
- }
458
- try {
459
- await WatchlistRepository.updateThumbnail(db, row.id, poster);
460
- }
461
- catch {
462
- // ignore
463
- }
464
- }
465
- catch {
466
- // ignore
467
- }
468
- }));
469
- await sweepOfflinePosters(db);
470
- }
471
- async function sweepOfflinePosters(db) {
472
- try {
473
- db.run(`UPDATE shows_meta SET name = (SELECT w.name FROM watchlist w WHERE w.id = shows_meta.id)
474
- WHERE (name IS NULL OR TRIM(name) = '')
475
- AND EXISTS (SELECT 1 FROM watchlist w WHERE w.id = shows_meta.id AND w.name IS NOT NULL AND TRIM(w.name) != '')`);
476
- }
477
- catch {
478
- // ignore
479
- }
480
- const [wlMissing, metaMissing] = await Promise.all([
481
- WatchlistRepository.getMissingThumbnails(db),
482
- ShowsMetaRepository.getShowIdsMissingMeta(db),
483
- ]);
484
- const ids = [...new Set([...wlMissing.map((r) => r.id), ...metaMissing.map((r) => r.id)])].slice(0, 500);
485
- if (ids.length === 0)
486
- return;
487
- for (const id of ids) {
488
- const poster = resolveOfflinePoster(id);
489
- if (poster)
490
- await applyPoster(db, id, poster);
491
- }
492
- }
493
- function resolveOfflinePoster(id) {
494
- if (/^\d+$/.test(id)) {
495
- return offlineDb.getByAnilistId(Number(id))?.thumbnail?.trim() || null;
496
- }
497
- const mal = /^mal-(\d+)$/i.exec(id)?.[1];
498
- if (mal)
499
- return offlineDb.getByMalId(Number(mal))?.thumbnail?.trim() || null;
500
- return null;
501
- }
502
- async function applyPoster(db, id, poster) {
503
- try {
504
- await ShowsMetaRepository.upsert(db, { id, thumbnail: poster });
505
- }
506
- catch {
507
- // ignore
508
- }
509
- try {
510
- await WatchlistRepository.updateThumbnail(db, id, poster);
511
- }
512
- catch {
513
- // ignore
514
- }
515
- }
516
- async function getContinueWatchingData(db, limit) {
517
- const rows = await WatchedEpisodesRepository.getContinueWatching(db, limit);
518
- const ignoreAdultRow = await SettingsRepository.getByKey(db, 'ignoreAdultContent');
519
- const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
520
- const watchlistOnlyRow = await SettingsRepository.getByKey(db, 'cwWatchlistOnly');
521
- const watchlistOnly = watchlistOnlyRow
522
- ? watchlistOnlyRow.value === 'true' || watchlistOnlyRow.value === '1'
523
- : false;
524
- const enrichedRows = rows
525
- .filter((show) => {
526
- if (ignoreAdult && show.isAdult)
527
- return false;
528
- if (watchlistOnly && show.watchlistStatus !== 'Watching')
529
- return false;
530
- return true;
531
- })
532
- .map((show) => ({
533
- ...show,
534
- episodeCount: show.episodeCount,
535
- type: show.type || show.smType,
536
- thumbnail: show.thumbnail ?? '',
537
- }));
538
- await backfillMissingPosters(db, enrichedRows);
539
- return enrichedRows;
540
- }
541
259
  export async function getAdultNonWatchlistShowIds(db) {
542
260
  const rows = await dbAll(db, `SELECT DISTINCT we.showId as showId
543
261
  FROM watched_episodes we
@@ -546,13 +264,6 @@ export async function getAdultNonWatchlistShowIds(db) {
546
264
  WHERE w.id IS NULL`);
547
265
  return rows.map((r) => r.showId);
548
266
  }
549
- async function resolveAvailableEpisodes(db, showId) {
550
- const episodeData = await getAnilistEpisodes(showId);
551
- const episodes = Array.isArray(episodeData) && episodeData.length
552
- ? [...episodeData].sort((a, b) => parseFloat(a) - parseFloat(b))
553
- : [];
554
- return episodes;
555
- }
556
267
  const dlsitePosterCache = new AppCache({ ttlSeconds: 3600, maxKeys: 2000 });
557
268
  const mangadexCoverCache = new AppCache({ ttlSeconds: 3600, maxKeys: 2000 });
558
269
  export async function getMangadexCover(title) {
@@ -21,9 +21,6 @@ export function anilistUnavailable() {
21
21
  }
22
22
  return false;
23
23
  }
24
- export function anilistIsDown() {
25
- return anilistDownUntil > Date.now();
26
- }
27
24
  export function isAnilistRateLimited() {
28
25
  return anilistRateLimited && anilistCooldownUntil > Date.now();
29
26
  }