@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.
- package/README.md +1 -1
- package/client/dist/assets/{AnimeCard-I3S6qw9d.js → AnimeCard-Cll26FQe.js} +1 -1
- package/client/dist/assets/{AnimeInfo-CLU2NSBR.js → AnimeInfo-zMQgNycF.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-4Mw9ArDy.js → AnimeInfoPage-DEPX3FFf.js} +2 -2
- package/client/dist/assets/Asmr-BbHVH9pC.js +2 -0
- package/client/dist/assets/Asmr-D1KuQwQy.css +1 -0
- package/client/dist/assets/Asmr.module-CdWlioTY.js +1 -0
- package/client/dist/assets/{AsmrResetProgressModal-DyU1Z2Gw.js → AsmrResetProgressModal-D4zRL_b8.js} +1 -1
- package/client/dist/assets/AvSyncCalibrator-CbzA-56T.js +10 -0
- package/client/dist/assets/{AvSyncCalibrator-C2wu1ydI.css → AvSyncCalibrator-gEONBv5a.css} +1 -1
- package/client/dist/assets/Home-q7a1RjQM.js +1 -0
- package/client/dist/assets/{Insights-i34-qV4C.js → Insights-6H3qK9Q_.js} +1 -1
- package/client/dist/assets/{LibraryListPage-DE-ASOX2.js → LibraryListPage-Cc-I_48d.js} +1 -1
- package/client/dist/assets/ListeningList-CxfHV13T.js +1 -0
- package/client/dist/assets/Manga-BmNPKJB8.js +1 -0
- package/client/dist/assets/Manga-CqAceKxR.css +1 -0
- package/client/dist/assets/Manga.module-depGEacJ.js +1 -0
- package/client/dist/assets/MangaInfoPage-Csyq4TAu.js +1 -0
- package/client/dist/assets/MangaReadPage-jaSR2qX6.js +1 -0
- package/client/dist/assets/{MangaResetProgressModal-DOjpLAMp.js → MangaResetProgressModal-DrfFCtDE.js} +1 -1
- package/client/dist/assets/{Mature-D2xb9EGB.js → Mature-20QVU1lQ.js} +1 -1
- package/client/dist/assets/{MediaCard-CKUEcFTd.js → MediaCard-CoRm07Vb.js} +1 -1
- package/client/dist/assets/Music-Yry6890T.js +1 -0
- package/client/dist/assets/OptionListMenu-DSTuvk6i.js +1 -0
- package/client/dist/assets/Player-yVWIsyOS.js +56 -0
- package/client/dist/assets/PlayerSettings-CiazXo-J.js +1 -0
- package/client/dist/assets/{QueueOptionsButton-BMvAC6Ut.js → QueueOptionsButton-CpyBvbsh.js} +1 -1
- package/client/dist/assets/{QueueRail-CAoGJvHP.js → QueueRail-DRxByiUx.js} +1 -1
- package/client/dist/assets/Radio-DxL91QOj.js +1 -0
- package/client/dist/assets/ReadingList-CNm6nFC1.js +1 -0
- package/client/dist/assets/{ResetProgressModal-vFC6N-Qw.js → ResetProgressModal-DiYoRAaR.js} +1 -1
- package/client/dist/assets/{Search-Bh9Y6nIJ.js → Search-BBRaD1PE.js} +1 -1
- package/client/dist/assets/{Settings-DRumujfh.js → Settings-C3ZhNo9P.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-BtzUnQE0.js → SkeletonGrid-BAQn8Rru.js} +1 -1
- package/client/dist/assets/{Trackers-D0nqy8wu.js → Trackers-CZd3KCwM.js} +1 -1
- package/client/dist/assets/Tv-O1-yrBt9.js +4 -0
- package/client/dist/assets/{TvInfo-BkECbOKN.js → TvInfo-oMcOTrSG.js} +1 -1
- package/client/dist/assets/{TvPopup-D8WWY6o4.js → TvPopup-BaAmlbH4.js} +1 -1
- package/client/dist/assets/{TvSearch-kWPep3gL.js → TvSearch-CjGUazdR.js} +1 -1
- package/client/dist/assets/{TvWatchlist-dD4emIHx.js → TvWatchlist-W51KZDQP.js} +1 -1
- package/client/dist/assets/{Watchlist-cWxw38nU.js → Watchlist-DBtN7kPt.js} +1 -1
- package/client/dist/assets/{hls-uG_Y3i5s.js → hls-CSxWA5I-.js} +1 -1
- package/client/dist/assets/index-CQWyVhME.js +178 -0
- package/client/dist/assets/page-DRvjODkg.css +1 -0
- package/client/dist/assets/page.module-C1MlZzf3.js +1 -0
- package/client/dist/assets/{useAnimeInfoData-DjPBg24u.js → useAnimeInfoData-CthDMEMc.js} +1 -1
- package/client/dist/assets/{useAsmr-DQyobNQX.js → useAsmr-DH8inTBw.js} +1 -1
- package/client/dist/assets/useAsmrPopup-BfkGmSSw.js +1 -0
- package/client/dist/assets/{useMangaLibrary-C6L8mrnS.js → useMangaLibrary-CpQLCvkU.js} +1 -1
- package/client/dist/assets/useMangaPopup-B5LXZINf.js +1 -0
- package/client/dist/assets/usePopup-DS9jMzp6.js +1 -0
- package/client/dist/assets/useProviders-DYy7lHj6.js +1 -0
- package/client/dist/assets/{useTv-DzSYasru.js → useTv-DvKLIudk.js} +1 -1
- package/client/dist/assets/{useTvLibrary-BnbHS-gg.js → useTvLibrary-ChI7tnUw.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +2 -2
- package/server/dist/hono/proxy.js +102 -75
- package/server/dist/hono/tv.js +2 -2
- package/server/dist/hono/watchlist.js +2 -291
- package/server/dist/lib/anilist.js +0 -3
- package/server/dist/lib/mal.js +0 -38
- package/server/dist/lib/offline-db.js +0 -6
- package/server/dist/providers/remote-loader.js +15 -26
- package/server/dist/providers/title-matching.js +5 -5
- package/server/dist/repositories/manga.repository.js +0 -1
- package/server/dist/repositories/watched-episodes.repository.js +0 -2
- package/server/dist/utils/megaplay.utils.js +1 -2
- package/server/package.json +1 -1
- package/client/dist/assets/Asmr-Cq2ua6Zk.css +0 -1
- package/client/dist/assets/Asmr-DN9XBc5Y.js +0 -2
- package/client/dist/assets/Asmr.module-ChUXDZKx.js +0 -1
- package/client/dist/assets/AvSyncCalibrator-DxL0mr_4.js +0 -10
- package/client/dist/assets/Home-CeTeJ-gM.js +0 -1
- package/client/dist/assets/ListeningList-DrNce5ll.js +0 -1
- package/client/dist/assets/Manga-DOrf7Fk6.js +0 -1
- package/client/dist/assets/Manga-Dp_-l1Pw.css +0 -1
- package/client/dist/assets/Manga.module-BcfOdsNP.js +0 -1
- package/client/dist/assets/MangaInfoPage-CAZMLMGw.js +0 -1
- package/client/dist/assets/MangaReadPage-DQVEo8_F.js +0 -1
- package/client/dist/assets/Music-w_a34r7r.js +0 -1
- package/client/dist/assets/OptionListMenu-DxZHBMIZ.js +0 -1
- package/client/dist/assets/Player-B8YHeATv.js +0 -56
- package/client/dist/assets/PlayerSettings-Ddlg-MvW.js +0 -1
- package/client/dist/assets/Radio-6O7rAUv1.js +0 -1
- package/client/dist/assets/ReadingList-k3g8oHvs.js +0 -1
- package/client/dist/assets/Tv-DVebLMNS.js +0 -4
- package/client/dist/assets/index-BWIWE3Qy.js +0 -178
- package/client/dist/assets/useAsmrPopup-CDoroMBD.js +0 -1
- package/client/dist/assets/useMangaPopup-BSuMa_vA.js +0 -1
- 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-
|
|
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};
|
package/client/dist/index.html
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
background-color: #0c0d12;
|
|
34
34
|
}
|
|
35
35
|
</style>
|
|
36
|
-
<script type="module" crossorigin src="/assets/index-
|
|
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.
|
|
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-
|
|
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 (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
if (
|
|
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
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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, {
|
|
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')}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
783
|
-
|
|
784
|
-
|
|
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;
|
package/server/dist/hono/tv.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
}
|