@serifpersia/dango 3.4.4 → 3.4.6
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-4CEz8Buu.js → AnimeCard-BVdH0EeW.js} +1 -1
- package/client/dist/assets/{AnimeInfo-DyX9qTwg.js → AnimeInfo-Dw2Nop6Q.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-Dvp_c8vK.js → AnimeInfoPage-Cw-Tq3YX.js} +2 -2
- package/client/dist/assets/{Asmr-qg3lMAJn.js → Asmr-BrjzzyOi.js} +1 -1
- package/client/dist/assets/{AsmrResetProgressModal-BpPlg_Vv.js → AsmrResetProgressModal-CYjpcYpb.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-FJllOgOH.js → AvSyncCalibrator-BuHurVgo.js} +3 -3
- package/client/dist/assets/AvSyncCalibrator-D0FYSwiz.css +1 -0
- package/client/dist/assets/{Home-A7gCeEST.js → Home-KdnP7sNM.js} +1 -1
- package/client/dist/assets/{Insights-G9IXepHJ.js → Insights-DDWry-t1.js} +1 -1
- package/client/dist/assets/{LibraryListPage-Bh2qCJPh.js → LibraryListPage-CV8Z9JlO.js} +1 -1
- package/client/dist/assets/{ListeningList-Czv4cTRl.js → ListeningList-DSMbMZT7.js} +1 -1
- package/client/dist/assets/{Manga-qdAAS1z7.js → Manga-J6XoqEKq.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-1rTgZzf2.js → MangaInfoPage-N7TxsN5u.js} +1 -1
- package/client/dist/assets/{MangaReadPage-CeyxPG6w.js → MangaReadPage-D_VzSNWq.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-C_ppotmh.js → MangaResetProgressModal-DyhQYBDy.js} +1 -1
- package/client/dist/assets/{Mature-Bkhxnr3K.js → Mature-Db53R-LR.js} +1 -1
- package/client/dist/assets/{MediaCard-BQZsutEo.js → MediaCard-BcLjlpfR.js} +1 -1
- package/client/dist/assets/{Music-C0rGzSbi.js → Music-DjxQjJOl.js} +1 -1
- package/client/dist/assets/{OptionListMenu-CyfpCBEy.js → OptionListMenu-DngTgHxH.js} +1 -1
- package/client/dist/assets/Player-Be_7Ne5p.js +56 -0
- package/client/dist/assets/{PlayerSettings-icy4SfQa.js → PlayerSettings-D47ItGDR.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-5eloKF5J.js → QueueOptionsButton-ClVTpH8J.js} +1 -1
- package/client/dist/assets/{QueueRail-DaMjlKL4.js → QueueRail-DeJKVRs5.js} +1 -1
- package/client/dist/assets/{Radio-FxGqCxR4.js → Radio-DU0ma3GA.js} +1 -1
- package/client/dist/assets/{ReadingList-CCm3IAbt.js → ReadingList-qXW7Dk5z.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CeI4V3Y5.js → ResetProgressModal-C6ukct_c.js} +1 -1
- package/client/dist/assets/{Search-qpILaAiQ.js → Search-DjjKA_Wu.js} +1 -1
- package/client/dist/assets/{Settings-Bw9M3NGz.js → Settings-DQt_S5Qw.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-0dY_xMq3.js → SkeletonGrid-nx2HEi4P.js} +1 -1
- package/client/dist/assets/{Trackers-Zy5GOs0P.js → Trackers-BcGzeqj2.js} +1 -1
- package/client/dist/assets/Tv-COvyHdeJ.js +4 -0
- package/client/dist/assets/{TvInfo-CHIsVzZ0.js → TvInfo-CUd1agFy.js} +1 -1
- package/client/dist/assets/{TvPopup-D_tVTyZ2.js → TvPopup-CJeu3tgc.js} +1 -1
- package/client/dist/assets/{TvSearch-BVxedBW-.js → TvSearch-BIypjbU3.js} +1 -1
- package/client/dist/assets/{TvWatchlist-BEIwq9R9.js → TvWatchlist-DZrBdiqZ.js} +1 -1
- package/client/dist/assets/{Watchlist-CSngjqlz.js → Watchlist-Dw8cPe0n.js} +1 -1
- package/client/dist/assets/{hls-D48GVTkk.js → hls-B3_1VuDP.js} +1 -1
- package/client/dist/assets/{index-CVB5dYhT.js → index-gMxawd-E.js} +2 -2
- package/client/dist/assets/{useAnimeInfoData-DxdOg_B7.js → useAnimeInfoData-_iOLEAa8.js} +1 -1
- package/client/dist/assets/{useAsmr-ClckxnPe.js → useAsmr-Blk4v41t.js} +1 -1
- package/client/dist/assets/{useAsmrPopup-Bbgxefy_.js → useAsmrPopup-D9SQIgZa.js} +1 -1
- package/client/dist/assets/{useMangaLibrary-B3_BUKVk.js → useMangaLibrary-B60at6ni.js} +1 -1
- package/client/dist/assets/{useMangaPopup-Celk4lzf.js → useMangaPopup-DEfGjWSu.js} +1 -1
- package/client/dist/assets/{useProviders-pwxPZPHh.js → useProviders-Ca0bZWt7.js} +1 -1
- package/client/dist/assets/{useTv-D9sQrDop.js → useTv-WJCDL5_5.js} +1 -1
- package/client/dist/assets/{useTvLibrary-UsQpgBTB.js → useTvLibrary-Fgn5i9rJ.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +4 -2
- package/server/dist/app-hono.js +2 -0
- package/server/dist/hono/asmr.js +10 -1
- package/server/dist/hono/music.js +122 -332
- package/server/dist/hono/proxy.js +105 -99
- package/server/dist/hono/tv.js +5 -58
- package/server/dist/hono/watchlist.js +2 -291
- package/server/dist/lib/anilist.js +0 -3
- package/server/dist/lib/mal.js +4 -41
- package/server/dist/lib/offline-db.js +0 -6
- package/server/dist/lib/potoken.js +135 -0
- package/server/dist/lib/tracker/manga-sync.service.js +3 -22
- package/server/dist/lib/tracker/sync-state.js +21 -0
- package/server/dist/lib/tracker/sync.service.js +3 -22
- package/server/dist/lib/ump.js +106 -0
- package/server/dist/providers/remote-loader.js +17 -27
- package/server/dist/providers/title-matching.js +5 -5
- package/server/dist/repositories/asmr.repository.js +4 -53
- package/server/dist/repositories/library.repository.js +46 -0
- package/server/dist/repositories/manga.repository.js +3 -53
- package/server/dist/repositories/settings.repository.js +8 -0
- package/server/dist/repositories/tv.repository.js +3 -46
- package/server/dist/repositories/watched-episodes.repository.js +0 -2
- package/server/dist/server.js +5 -1
- package/server/dist/sync.js +3 -5
- package/server/dist/trpc/routers/asmr-library.js +2 -12
- package/server/dist/trpc/routers/asmr-progress.js +10 -22
- package/server/dist/trpc/routers/data.js +17 -36
- package/server/dist/trpc/routers/discord.js +1 -8
- package/server/dist/trpc/routers/insights.js +15 -16
- package/server/dist/trpc/routers/manga-library.js +5 -22
- package/server/dist/trpc/routers/manga-progress.js +8 -20
- package/server/dist/trpc/routers/music.js +6 -11
- package/server/dist/trpc/routers/queue.js +1 -1
- package/server/dist/trpc/routers/tracker.js +1 -7
- package/server/dist/trpc/routers/tv-library.js +2 -12
- package/server/dist/trpc/routers/tv-progress.js +16 -28
- package/server/dist/trpc/routers/tv.js +5 -21
- package/server/dist/trpc/routers/watchlist.js +1 -8
- package/server/dist/trpc/validation.js +35 -0
- package/server/dist/utils/db-utils.js +8 -0
- package/server/dist/utils/http.utils.js +12 -0
- package/server/dist/utils/machine-id.js +1 -2
- package/server/dist/utils/megaplay.utils.js +1 -2
- package/server/package.json +4 -1
- package/client/dist/assets/AvSyncCalibrator-C2wu1ydI.css +0 -1
- package/client/dist/assets/Player-yhiTlpYU.js +0 -56
- package/client/dist/assets/Tv-DREwc7BX.js +0 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { defineSchema, optStr, reqObj, reqStr } from '../validation.js';
|
|
3
|
+
import { badRequest, defineSchema, failed, optNum, optStr, readCount, reqObj, reqStr, } from '../validation.js';
|
|
4
4
|
import { parseJsonBody } from '../../utils/http.utils.js';
|
|
5
5
|
import { getTrending, getLatestReleases, getSeasonal, getSchedule, searchAnilist, searchAnilistByTitle, getSpotlightBanners, getBatchedHomeData, getGenreTagLists, anilistUnavailable, wasAnilistDownAtBoot, checkAnilistStatus, getShowMetaById, getAnilistEpisodes, parseMalId, getShowMetaByMalId, fromAnilistMedia, } from '../../lib/anilist.js';
|
|
6
6
|
import { malAnimeDetail, malSearchMedia, malSearchTitle } from '../../lib/mal.js';
|
|
@@ -14,25 +14,6 @@ import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js
|
|
|
14
14
|
import { WatchlistRepository } from '../../repositories/watchlist.repository.js';
|
|
15
15
|
import { dbRun } from '../../utils/db-utils.js';
|
|
16
16
|
import logger from '../../logger.js';
|
|
17
|
-
function failed(message) {
|
|
18
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
19
|
-
}
|
|
20
|
-
function badRequest(message) {
|
|
21
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
22
|
-
}
|
|
23
|
-
function optInt(obj, name, fallback) {
|
|
24
|
-
const value = obj[name];
|
|
25
|
-
if (value === undefined || value === null || value === '')
|
|
26
|
-
return fallback;
|
|
27
|
-
return parseInt(String(value), 10) || fallback;
|
|
28
|
-
}
|
|
29
|
-
function optIntOrUndefined(obj, name) {
|
|
30
|
-
const value = obj[name];
|
|
31
|
-
if (value === undefined || value === null || value === '')
|
|
32
|
-
return undefined;
|
|
33
|
-
const parsed = parseInt(String(value), 10);
|
|
34
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
35
|
-
}
|
|
36
17
|
async function cached(ctx, key, ttl, validate, produce) {
|
|
37
18
|
const hit = ctx.apiCache.get(key);
|
|
38
19
|
if (hit)
|
|
@@ -62,9 +43,9 @@ const dataLatestReleasesInput = () => defineSchema((value) => {
|
|
|
62
43
|
if (format !== undefined)
|
|
63
44
|
out.format = format;
|
|
64
45
|
if (obj.page !== undefined)
|
|
65
|
-
out.page =
|
|
46
|
+
out.page = readCount(obj, 'page', 1);
|
|
66
47
|
if (obj.size !== undefined)
|
|
67
|
-
out.size =
|
|
48
|
+
out.size = readCount(obj, 'size', 12);
|
|
68
49
|
return out;
|
|
69
50
|
});
|
|
70
51
|
const dataSearchInput = () => defineSchema((value) => {
|
|
@@ -74,9 +55,9 @@ const dataSearchInput = () => defineSchema((value) => {
|
|
|
74
55
|
if (query !== undefined)
|
|
75
56
|
out.query = query;
|
|
76
57
|
if (obj.page !== undefined)
|
|
77
|
-
out.page =
|
|
58
|
+
out.page = readCount(obj, 'page', 1);
|
|
78
59
|
if (obj.limit !== undefined)
|
|
79
|
-
out.limit =
|
|
60
|
+
out.limit = readCount(obj, 'limit', 14);
|
|
80
61
|
const sortBy = optStr(obj, 'sortBy');
|
|
81
62
|
if (sortBy !== undefined)
|
|
82
63
|
out.sortBy = sortBy;
|
|
@@ -89,7 +70,7 @@ const dataSearchInput = () => defineSchema((value) => {
|
|
|
89
70
|
const season = optStr(obj, 'season');
|
|
90
71
|
if (season !== undefined)
|
|
91
72
|
out.season = season;
|
|
92
|
-
const year =
|
|
73
|
+
const year = optNum(obj, 'year');
|
|
93
74
|
if (year !== undefined)
|
|
94
75
|
out.year = year;
|
|
95
76
|
const country = optStr(obj, 'country');
|
|
@@ -104,10 +85,10 @@ const dataSearchInput = () => defineSchema((value) => {
|
|
|
104
85
|
const excludeTags = optStr(obj, 'excludeTags');
|
|
105
86
|
if (excludeTags !== undefined)
|
|
106
87
|
out.excludeTags = excludeTags;
|
|
107
|
-
const minScore =
|
|
88
|
+
const minScore = optNum(obj, 'minScore');
|
|
108
89
|
if (minScore !== undefined)
|
|
109
90
|
out.minScore = minScore;
|
|
110
|
-
const minEpisodes =
|
|
91
|
+
const minEpisodes = optNum(obj, 'minEpisodes');
|
|
111
92
|
if (minEpisodes !== undefined)
|
|
112
93
|
out.minEpisodes = minEpisodes;
|
|
113
94
|
const adult = optStr(obj, 'adult');
|
|
@@ -138,9 +119,9 @@ const dataSeasonalInput = () => defineSchema((value) => {
|
|
|
138
119
|
const obj = reqObj(value);
|
|
139
120
|
const out = {};
|
|
140
121
|
if (obj.page !== undefined)
|
|
141
|
-
out.page =
|
|
122
|
+
out.page = readCount(obj, 'page', 1);
|
|
142
123
|
if (obj.size !== undefined)
|
|
143
|
-
out.size =
|
|
124
|
+
out.size = readCount(obj, 'size', 14);
|
|
144
125
|
const format = optStr(obj, 'format');
|
|
145
126
|
if (format !== undefined)
|
|
146
127
|
out.format = format;
|
|
@@ -153,9 +134,9 @@ const dataPopularListInput = () => defineSchema((value) => {
|
|
|
153
134
|
if (sort !== undefined)
|
|
154
135
|
out.sort = sort;
|
|
155
136
|
if (obj.page !== undefined)
|
|
156
|
-
out.page =
|
|
137
|
+
out.page = readCount(obj, 'page', 1);
|
|
157
138
|
if (obj.size !== undefined)
|
|
158
|
-
out.size =
|
|
139
|
+
out.size = readCount(obj, 'size', 20);
|
|
159
140
|
return out;
|
|
160
141
|
});
|
|
161
142
|
const dataHomeInput = () => defineSchema((value) => {
|
|
@@ -229,16 +210,16 @@ const dataMatureSearchInput = () => defineSchema((value) => {
|
|
|
229
210
|
out[k] = v;
|
|
230
211
|
}
|
|
231
212
|
if (obj.page !== undefined)
|
|
232
|
-
out.page =
|
|
213
|
+
out.page = readCount(obj, 'page', 1);
|
|
233
214
|
if (obj.limit !== undefined)
|
|
234
|
-
out.limit =
|
|
235
|
-
const year =
|
|
215
|
+
out.limit = readCount(obj, 'limit', 14);
|
|
216
|
+
const year = optNum(obj, 'year');
|
|
236
217
|
if (year !== undefined)
|
|
237
218
|
out.year = year;
|
|
238
|
-
const minScore =
|
|
219
|
+
const minScore = optNum(obj, 'minScore');
|
|
239
220
|
if (minScore !== undefined)
|
|
240
221
|
out.minScore = minScore;
|
|
241
|
-
const minEpisodes =
|
|
222
|
+
const minEpisodes = optNum(obj, 'minEpisodes');
|
|
242
223
|
if (minEpisodes !== undefined)
|
|
243
224
|
out.minEpisodes = minEpisodes;
|
|
244
225
|
return out;
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import { TRPCError } from '@trpc/server';
|
|
2
1
|
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { defineSchema, optStr, reqObj } from '../validation.js';
|
|
2
|
+
import { badRequest, defineSchema, failed, optStr, reqObj } from '../validation.js';
|
|
4
3
|
import { discordRPCService } from '../../discord-rpc.js';
|
|
5
4
|
import { discordGatewayService } from '../../discord-gateway.js';
|
|
6
5
|
import { getDlsitePoster, getMangadexCover } from '../../hono/watchlist.js';
|
|
7
6
|
import { updateEnvFile } from '../../utils/env.utils.js';
|
|
8
7
|
import { CONFIG } from '../../config.js';
|
|
9
8
|
import logger from '../../logger.js';
|
|
10
|
-
function failed(message) {
|
|
11
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
12
|
-
}
|
|
13
|
-
function badRequest(message) {
|
|
14
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
15
|
-
}
|
|
16
9
|
function maskToken(t) {
|
|
17
10
|
if (!t)
|
|
18
11
|
return 'none';
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { TRPCError } from '@trpc/server';
|
|
2
1
|
import { protectedProcedure, router } from '../index.js';
|
|
3
2
|
import { getWatchInsights, getGenreCards } from '../../lib/insights.js';
|
|
4
3
|
import { computeDiscordSyncStats, getCachedRecommendations, getLinkedDiscordUser, setLinkedDiscordUser, syncDiscordRoles, } from '../../lib/discord-roles-sync.service.js';
|
|
5
|
-
import { defineSchema, reqObj } from '../validation.js';
|
|
4
|
+
import { defineSchema, optStr, reqObj, reqStr } from '../validation.js';
|
|
6
5
|
const discordUserInput = () => defineSchema((value) => {
|
|
7
6
|
if (value === undefined || value === null)
|
|
8
7
|
return {};
|
|
9
|
-
|
|
8
|
+
const raw = reqObj(value).user;
|
|
9
|
+
if (raw === undefined || raw === null)
|
|
10
|
+
return { user: null };
|
|
11
|
+
const obj = reqObj(raw);
|
|
12
|
+
return {
|
|
13
|
+
user: {
|
|
14
|
+
id: reqStr(obj, 'id'),
|
|
15
|
+
username: optStr(obj, 'username') ?? '',
|
|
16
|
+
avatar: optStr(obj, 'avatar') ?? null,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
10
19
|
});
|
|
11
20
|
export const insightsRouter = router({
|
|
12
21
|
summary: protectedProcedure.query(async ({ ctx }) => {
|
|
@@ -25,19 +34,9 @@ export const insightsRouter = router({
|
|
|
25
34
|
return { user: await getLinkedDiscordUser(ctx.db) };
|
|
26
35
|
}),
|
|
27
36
|
setDiscordUser: protectedProcedure.input(discordUserInput()).mutation(async ({ ctx, input }) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
(typeof user !== 'object' ||
|
|
32
|
-
typeof user.id !== 'string' ||
|
|
33
|
-
!user.id)) {
|
|
34
|
-
throw new TRPCError({
|
|
35
|
-
code: 'BAD_REQUEST',
|
|
36
|
-
message: 'Invalid user payload: expected { id, ... } or null',
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
await setLinkedDiscordUser(ctx.db, user || null);
|
|
40
|
-
return { success: true, user: user || null };
|
|
37
|
+
const user = input.user ?? null;
|
|
38
|
+
await setLinkedDiscordUser(ctx.db, user);
|
|
39
|
+
return { success: true, user };
|
|
41
40
|
}),
|
|
42
41
|
discordSyncNow: protectedProcedure.mutation(async ({ ctx }) => {
|
|
43
42
|
return syncDiscordRoles(ctx.db, true);
|
|
@@ -2,7 +2,7 @@ import { TRPCError } from '@trpc/server';
|
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
3
|
import { performMangaWriteTransactionAsync } from '../../sync.js';
|
|
4
4
|
import { buildMangaId, MANGA_STATUSES, MangaLibraryRepository, MangaProgressRepository, } from '../../repositories/manga.repository.js';
|
|
5
|
-
import { defineSchema, optStr, reqObj } from '../validation.js';
|
|
5
|
+
import { badRequest, defineSchema, failed, optStr, readCount, reqObj, reqStr, } from '../validation.js';
|
|
6
6
|
function mangaDb(ctx) {
|
|
7
7
|
const db = ctx.mangaDb;
|
|
8
8
|
if (!db || db.isClosedCheck())
|
|
@@ -14,19 +14,6 @@ function normalizeId(provider, mangaId, idRaw) {
|
|
|
14
14
|
return idRaw;
|
|
15
15
|
return buildMangaId(provider, mangaId || idRaw || '');
|
|
16
16
|
}
|
|
17
|
-
function badRequest(message) {
|
|
18
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
19
|
-
}
|
|
20
|
-
function failed(message) {
|
|
21
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
22
|
-
}
|
|
23
|
-
function readCount(obj, name, fallback) {
|
|
24
|
-
const raw = obj[name];
|
|
25
|
-
if (raw === undefined || raw === null || raw === '')
|
|
26
|
-
return fallback;
|
|
27
|
-
const n = Math.floor(Number(raw));
|
|
28
|
-
return Number.isFinite(n) ? n : fallback;
|
|
29
|
-
}
|
|
30
17
|
const mangaLibraryListInput = () => defineSchema((value) => {
|
|
31
18
|
const obj = reqObj(value);
|
|
32
19
|
const status = optStr(obj, 'status');
|
|
@@ -34,13 +21,9 @@ const mangaLibraryListInput = () => defineSchema((value) => {
|
|
|
34
21
|
const limit = Math.min(Math.max(readCount(obj, 'limit', 24), 1), 100);
|
|
35
22
|
return { status, page, limit };
|
|
36
23
|
});
|
|
37
|
-
const mangaLibraryIdInput = () => defineSchema((value) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (typeof id !== 'string')
|
|
41
|
-
throw new Error('id must be a string');
|
|
42
|
-
return { id };
|
|
43
|
-
});
|
|
24
|
+
const mangaLibraryIdInput = () => defineSchema((value) => ({
|
|
25
|
+
id: reqStr(reqObj(value), 'id'),
|
|
26
|
+
}));
|
|
44
27
|
const mangaLibraryAddInput = () => defineSchema((value) => {
|
|
45
28
|
const obj = reqObj(value);
|
|
46
29
|
const out = {
|
|
@@ -179,7 +162,7 @@ export const mangaLibraryRouter = router({
|
|
|
179
162
|
setStatus: protectedProcedure
|
|
180
163
|
.input(mangaLibraryStatusInput())
|
|
181
164
|
.mutation(async ({ ctx, input }) => {
|
|
182
|
-
if (!input.id || !MANGA_STATUSES.includes(input.status)) {
|
|
165
|
+
if (!input.id || !input.status || !MANGA_STATUSES.includes(input.status)) {
|
|
183
166
|
throw badRequest('id and a valid status are required');
|
|
184
167
|
}
|
|
185
168
|
try {
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { protectedProcedure, router } from '../index.js';
|
|
2
2
|
import { defineSchema, optStr, reqObj, reqStr, reqStrArray } from '../validation.js';
|
|
3
3
|
import { performMangaWriteTransactionAsync } from '../../sync.js';
|
|
4
|
-
import {
|
|
4
|
+
import { adultNonListIds } from '../../utils/db-utils.js';
|
|
5
5
|
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
6
6
|
import { MangaLibraryRepository, MangaProgressRepository, } from '../../repositories/manga.repository.js';
|
|
7
7
|
const MANGA_ADULT_RATINGS = ['erotica', 'pornographic'];
|
|
8
8
|
const isMangaContinueAdult = (row) => !!row.contentRating && MANGA_ADULT_RATINGS.includes(row.contentRating);
|
|
9
|
-
|
|
10
|
-
const rows = await dbAll(db, `SELECT DISTINCT p.mangaId as mangaId
|
|
11
|
-
FROM manga_progress p
|
|
12
|
-
LEFT JOIN manga_library l ON l.id = p.mangaId
|
|
13
|
-
WHERE COALESCE(l.contentRating, p.contentRating) IN ('erotica', 'pornographic')
|
|
14
|
-
AND l.id IS NULL`);
|
|
15
|
-
return rows.map((r) => r.mangaId);
|
|
16
|
-
}
|
|
9
|
+
const getAdultNonListMangaIds = (db) => adultNonListIds(db, 'manga_progress', 'manga_library', 'mangaId', "COALESCE(l.contentRating, p.contentRating) IN ('erotica', 'pornographic')");
|
|
17
10
|
const mangaIdInput = () => defineSchema((value) => ({
|
|
18
11
|
mangaId: reqStr(reqObj(value), 'mangaId'),
|
|
19
12
|
}));
|
|
@@ -102,11 +95,11 @@ export const mangaProgressRouter = router({
|
|
|
102
95
|
chapterNumber: String(input.chapterNumber || ''),
|
|
103
96
|
page: pageNum,
|
|
104
97
|
pageCount: pageCountNum,
|
|
105
|
-
title:
|
|
106
|
-
cover:
|
|
107
|
-
provider:
|
|
108
|
-
altTitle:
|
|
109
|
-
contentRating:
|
|
98
|
+
title: input.title ?? null,
|
|
99
|
+
cover: input.cover ?? null,
|
|
100
|
+
provider: input.provider ?? null,
|
|
101
|
+
altTitle: input.altTitle ?? null,
|
|
102
|
+
contentRating: input.contentRating ?? null,
|
|
110
103
|
});
|
|
111
104
|
await MangaLibraryRepository.touchProgress(tx, String(input.mangaId), {
|
|
112
105
|
chapterId: String(input.chapterId),
|
|
@@ -141,12 +134,7 @@ export const mangaProgressRouter = router({
|
|
|
141
134
|
.query(async ({ ctx, input }) => {
|
|
142
135
|
try {
|
|
143
136
|
const limit = Math.min(Math.max(parseInt(String(input.limit)) || 24, 1), 100);
|
|
144
|
-
const
|
|
145
|
-
const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
|
|
146
|
-
const listOnlyRow = await SettingsRepository.getByKey(ctx.db, 'mangaCwWatchlistOnly');
|
|
147
|
-
const listOnly = listOnlyRow
|
|
148
|
-
? listOnlyRow.value === 'true' || listOnlyRow.value === '1'
|
|
149
|
-
: false;
|
|
137
|
+
const { ignoreAdult, listOnly } = await SettingsRepository.readContinueFlags(ctx.db, 'mangaIgnoreAdultContent', 'mangaCwWatchlistOnly');
|
|
150
138
|
const rows = (await MangaProgressRepository.getContinueReading(ctx.mangaDb, limit)).filter((row) => {
|
|
151
139
|
if (ignoreAdult && isMangaContinueAdult(row))
|
|
152
140
|
return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { defineSchema, optStr, reqObj } from '../validation.js';
|
|
3
|
+
import { badRequest, defineSchema, failed, optStr, reqObj } from '../validation.js';
|
|
4
4
|
import logger from '../../logger.js';
|
|
5
5
|
import { getInnertube, getAuthedInnertube, refreshAuthedInnertube, saveMusicCookie, getMusicAuthStatus, signOutMusic, isParserVariantError, } from '../../lib/ytmusic.js';
|
|
6
6
|
const NAME_SUFFIXES = [
|
|
@@ -152,7 +152,8 @@ function upnextToTrack(node, trustLike = false) {
|
|
|
152
152
|
}
|
|
153
153
|
function parseUpnext(panel, trustLike) {
|
|
154
154
|
const tracks = [];
|
|
155
|
-
const
|
|
155
|
+
const shaped = panel;
|
|
156
|
+
const contents = shaped.contents ?? [];
|
|
156
157
|
for (const item of contents) {
|
|
157
158
|
const track = upnextToTrack(item, trustLike);
|
|
158
159
|
if (track)
|
|
@@ -160,7 +161,7 @@ function parseUpnext(panel, trustLike) {
|
|
|
160
161
|
if (tracks.length >= 50)
|
|
161
162
|
break;
|
|
162
163
|
}
|
|
163
|
-
const playlistId =
|
|
164
|
+
const playlistId = shaped.playlist_id ?? null;
|
|
164
165
|
return { tracks, playlistId };
|
|
165
166
|
}
|
|
166
167
|
function asMenuItems(menu) {
|
|
@@ -285,15 +286,9 @@ async function readLibrary(yt) {
|
|
|
285
286
|
logger.info(`[music] library parsed tracks=${tracks.length} playlists=${playlists.length} gridItems=${gridItems.length}`);
|
|
286
287
|
return { tracks, playlists };
|
|
287
288
|
}
|
|
288
|
-
function badRequest(message) {
|
|
289
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
290
|
-
}
|
|
291
289
|
function unauthorized(message) {
|
|
292
290
|
return new TRPCError({ code: 'UNAUTHORIZED', message });
|
|
293
291
|
}
|
|
294
|
-
function failed(message) {
|
|
295
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
296
|
-
}
|
|
297
292
|
const musicSearchInput = () => defineSchema((value) => {
|
|
298
293
|
const obj = reqObj(value);
|
|
299
294
|
const out = {};
|
|
@@ -598,8 +593,8 @@ export const musicRouter = router({
|
|
|
598
593
|
throw unauthorized('MUSIC_AUTH_REQUIRED');
|
|
599
594
|
}
|
|
600
595
|
const attempt = async (session) => {
|
|
601
|
-
const
|
|
602
|
-
|
|
596
|
+
const shaped = session;
|
|
597
|
+
const actions = shaped.actions ?? shaped.session?.actions;
|
|
603
598
|
if (!actions)
|
|
604
599
|
throw new Error('Actions unavailable');
|
|
605
600
|
const panel = await session.music.getUpNext(id);
|
|
@@ -68,7 +68,7 @@ export const queueRouter = router({
|
|
|
68
68
|
}),
|
|
69
69
|
removeMany: protectedProcedure.input(queueRemoveManyInput()).mutation(async ({ ctx, input }) => {
|
|
70
70
|
const showId = await getMigratedId(ctx.db, input.showId);
|
|
71
|
-
let removed;
|
|
71
|
+
let removed = [];
|
|
72
72
|
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
73
73
|
removed = ((await QueueRepository.getByShow(tx, showId)) || []).map((ep) => ep.episodeNumber);
|
|
74
74
|
const toRemove = Array.isArray(input.episodeNumbers) && input.episodeNumbers.length
|
|
@@ -5,15 +5,9 @@ import { performWriteTransactionAsync } from '../../sync.js';
|
|
|
5
5
|
import { AniListTracker } from '../../lib/tracker/anilist-tracker.js';
|
|
6
6
|
import { syncAniList, importFromMalUsername, importFromUsername, } from '../../lib/tracker/sync.service.js';
|
|
7
7
|
import { syncAniListManga, importFromUsernameManga, importMangaFromMalUsername, } from '../../lib/tracker/manga-sync.service.js';
|
|
8
|
-
import { defineSchema, reqObj } from '../validation.js';
|
|
8
|
+
import { badRequest, defineSchema, failed, reqObj } from '../validation.js';
|
|
9
9
|
const TRACKER_TOKEN_KEY = 'tracker_anilist_token';
|
|
10
10
|
const TRACKER_USER_KEY = 'tracker_anilist_user';
|
|
11
|
-
function badRequest(message) {
|
|
12
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
13
|
-
}
|
|
14
|
-
function failed(message) {
|
|
15
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
16
|
-
}
|
|
17
11
|
function unavailable(message) {
|
|
18
12
|
return new TRPCError({ code: 'SERVICE_UNAVAILABLE', message });
|
|
19
13
|
}
|
|
@@ -2,26 +2,16 @@ import { TRPCError } from '@trpc/server';
|
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
3
|
import { performTvWriteTransactionAsync } from '../../sync.js';
|
|
4
4
|
import { buildTvId, TV_STATUSES, TvLibraryRepository, TvProgressRepository, } from '../../repositories/tv.repository.js';
|
|
5
|
-
import { defineSchema, reqObj, reqStr, optStr, reqStrArray } from '../validation.js';
|
|
5
|
+
import { defineSchema, reqObj, reqStr, optStr, reqStrArray, readPageLimit } from '../validation.js';
|
|
6
6
|
function normalizeMediaType(raw) {
|
|
7
7
|
return String(raw).toLowerCase() === 'movie' ? 'movie' : 'tv';
|
|
8
8
|
}
|
|
9
9
|
const tvLibraryListInput = () => defineSchema((value) => {
|
|
10
10
|
const obj = reqObj(value);
|
|
11
|
-
const out = {};
|
|
11
|
+
const out = { ...readPageLimit(obj) };
|
|
12
12
|
const status = optStr(obj, 'status');
|
|
13
13
|
if (status !== undefined)
|
|
14
14
|
out.status = status;
|
|
15
|
-
if (obj.page !== undefined && obj.page !== null && obj.page !== '') {
|
|
16
|
-
const page = Number(obj.page);
|
|
17
|
-
if (Number.isFinite(page))
|
|
18
|
-
out.page = page;
|
|
19
|
-
}
|
|
20
|
-
if (obj.limit !== undefined && obj.limit !== null && obj.limit !== '') {
|
|
21
|
-
const limit = Number(obj.limit);
|
|
22
|
-
if (Number.isFinite(limit))
|
|
23
|
-
out.limit = limit;
|
|
24
|
-
}
|
|
25
15
|
return out;
|
|
26
16
|
});
|
|
27
17
|
const tvLibraryIdInput = () => defineSchema((value) => ({
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
3
|
import { performTvWriteTransactionAsync } from '../../sync.js';
|
|
4
|
-
import {
|
|
4
|
+
import { adultNonListIds } from '../../utils/db-utils.js';
|
|
5
5
|
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
6
6
|
import { normalizeTvMediaId, TvLibraryRepository, TvProgressRepository, } from '../../repositories/tv.repository.js';
|
|
7
|
-
import { defineSchema, reqObj, reqStr } from '../validation.js';
|
|
7
|
+
import { defineSchema, reqObj, reqStr, strOrNull } from '../validation.js';
|
|
8
8
|
const isTvContinueAdult = (row) => row.adult === true || row.adult === 1;
|
|
9
|
-
|
|
10
|
-
const rows = await dbAll(db, `SELECT DISTINCT p.mediaId as mediaId
|
|
11
|
-
FROM tv_progress p
|
|
12
|
-
LEFT JOIN tv_library l ON l.id = p.mediaId
|
|
13
|
-
WHERE COALESCE(l.adult, p.adult) = 1
|
|
14
|
-
AND l.id IS NULL`);
|
|
15
|
-
return rows.map((r) => r.mediaId);
|
|
16
|
-
}
|
|
9
|
+
const getAdultNonListMediaIds = (db) => adultNonListIds(db, 'tv_progress', 'tv_library', 'mediaId', 'COALESCE(l.adult, p.adult) = 1');
|
|
17
10
|
const tvProgressMediaInput = () => defineSchema((value) => ({
|
|
18
11
|
mediaId: reqStr(reqObj(value), 'mediaId'),
|
|
19
12
|
}));
|
|
@@ -30,13 +23,13 @@ const tvProgressSaveInput = () => defineSchema((value) => {
|
|
|
30
23
|
currentTime: obj.currentTime,
|
|
31
24
|
duration: obj.duration,
|
|
32
25
|
completed: obj.completed,
|
|
33
|
-
title: obj
|
|
34
|
-
poster: obj
|
|
35
|
-
backdrop: obj
|
|
36
|
-
year: obj
|
|
37
|
-
overview: obj
|
|
26
|
+
title: strOrNull(obj, 'title'),
|
|
27
|
+
poster: strOrNull(obj, 'poster'),
|
|
28
|
+
backdrop: strOrNull(obj, 'backdrop'),
|
|
29
|
+
year: strOrNull(obj, 'year'),
|
|
30
|
+
overview: strOrNull(obj, 'overview'),
|
|
38
31
|
tmdbId: obj.tmdbId ?? null,
|
|
39
|
-
mediaType: obj
|
|
32
|
+
mediaType: strOrNull(obj, 'mediaType'),
|
|
40
33
|
adult: obj.adult ?? null,
|
|
41
34
|
};
|
|
42
35
|
});
|
|
@@ -95,13 +88,13 @@ export const tvProgressRouter = router({
|
|
|
95
88
|
currentTime: storedTime,
|
|
96
89
|
duration: durationNum,
|
|
97
90
|
completed: completedNow,
|
|
98
|
-
title:
|
|
99
|
-
poster:
|
|
100
|
-
backdrop:
|
|
101
|
-
year:
|
|
102
|
-
overview:
|
|
91
|
+
title: input.title ?? null,
|
|
92
|
+
poster: input.poster ?? null,
|
|
93
|
+
backdrop: input.backdrop ?? null,
|
|
94
|
+
year: input.year ?? null,
|
|
95
|
+
overview: input.overview ?? null,
|
|
103
96
|
tmdbId: input.tmdbId != null ? Number(input.tmdbId) : null,
|
|
104
|
-
mediaType:
|
|
97
|
+
mediaType: input.mediaType ?? null,
|
|
105
98
|
adult: input.adult != null ? Number(input.adult) : null,
|
|
106
99
|
});
|
|
107
100
|
await TvLibraryRepository.touchProgress(tx, String(mediaId), {
|
|
@@ -139,12 +132,7 @@ export const tvProgressRouter = router({
|
|
|
139
132
|
.input(tvContinueWatchingInput())
|
|
140
133
|
.query(async ({ ctx, input }) => {
|
|
141
134
|
const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
|
|
142
|
-
const
|
|
143
|
-
const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
|
|
144
|
-
const listOnlyRow = await SettingsRepository.getByKey(ctx.db, 'tvCwWatchlistOnly');
|
|
145
|
-
const listOnly = listOnlyRow
|
|
146
|
-
? listOnlyRow.value === 'true' || listOnlyRow.value === '1'
|
|
147
|
-
: false;
|
|
135
|
+
const { ignoreAdult, listOnly } = await SettingsRepository.readContinueFlags(ctx.db, 'tvIgnoreAdultContent', 'tvCwWatchlistOnly');
|
|
148
136
|
const rows = (await TvProgressRepository.getContinueWatching(ctx.tvDb, limit)).filter((row) => {
|
|
149
137
|
if (ignoreAdult && isTvContinueAdult(row))
|
|
150
138
|
return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { defineSchema, optStr, reqObj, reqStr } from '../validation.js';
|
|
3
|
+
import { defineSchema, failed, optStr, readCount, reqId, reqObj, reqStr } from '../validation.js';
|
|
4
4
|
import { getTmdbKey, TMDB_BASE, TMDB_IMAGE } from '../../lib/tmdb.js';
|
|
5
5
|
import { parseJsonBody } from '../../utils/http.utils.js';
|
|
6
6
|
async function resolveTvMeta(mediaType, numericTmdbId, partial) {
|
|
@@ -43,22 +43,6 @@ function toSearchResult(item, type) {
|
|
|
43
43
|
genre_ids: item.genre_ids || [],
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function failed(message) {
|
|
47
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
48
|
-
}
|
|
49
|
-
function optNum(obj, name, fallback) {
|
|
50
|
-
const value = obj[name];
|
|
51
|
-
if (value === undefined || value === null || value === '')
|
|
52
|
-
return fallback;
|
|
53
|
-
return parseInt(String(value), 10) || fallback;
|
|
54
|
-
}
|
|
55
|
-
function reqId(obj, name) {
|
|
56
|
-
const value = obj[name];
|
|
57
|
-
if ((typeof value !== 'string' && typeof value !== 'number') || String(value).length === 0) {
|
|
58
|
-
throw new Error(`${name} must be a string`);
|
|
59
|
-
}
|
|
60
|
-
return String(value);
|
|
61
|
-
}
|
|
62
46
|
const tvSearchInput = () => defineSchema((value) => {
|
|
63
47
|
const obj = reqObj(value);
|
|
64
48
|
const out = {};
|
|
@@ -66,7 +50,7 @@ const tvSearchInput = () => defineSchema((value) => {
|
|
|
66
50
|
if (q !== undefined)
|
|
67
51
|
out.q = q;
|
|
68
52
|
if (obj.page !== undefined)
|
|
69
|
-
out.page =
|
|
53
|
+
out.page = readCount(obj, 'page', 1);
|
|
70
54
|
const type = optStr(obj, 'type');
|
|
71
55
|
if (type !== undefined)
|
|
72
56
|
out.type = type;
|
|
@@ -91,7 +75,7 @@ const tvTrendingInput = () => defineSchema((value) => {
|
|
|
91
75
|
if (timeWindow !== undefined)
|
|
92
76
|
out.timeWindow = timeWindow;
|
|
93
77
|
if (obj.page !== undefined)
|
|
94
|
-
out.page =
|
|
78
|
+
out.page = readCount(obj, 'page', 1);
|
|
95
79
|
return out;
|
|
96
80
|
});
|
|
97
81
|
const tvDetailsInput = () => defineSchema((value) => {
|
|
@@ -132,9 +116,9 @@ const tvSourcesInput = () => defineSchema((value) => {
|
|
|
132
116
|
tmdbId: reqId(obj, 'tmdbId'),
|
|
133
117
|
};
|
|
134
118
|
if (obj.season !== undefined)
|
|
135
|
-
out.season =
|
|
119
|
+
out.season = readCount(obj, 'season', 1);
|
|
136
120
|
if (obj.episode !== undefined)
|
|
137
|
-
out.episode =
|
|
121
|
+
out.episode = readCount(obj, 'episode', 1);
|
|
138
122
|
const server = optStr(obj, 'server');
|
|
139
123
|
if (server !== undefined)
|
|
140
124
|
out.server = server;
|
|
@@ -11,7 +11,7 @@ import { isAnilistRateLimited, searchAnilist, searchAnilistByTitle, getAnilistEp
|
|
|
11
11
|
import { kitsuSearchAnime } from '../../lib/kitsu.js';
|
|
12
12
|
import { offlineDb } from '../../lib/offline-db.js';
|
|
13
13
|
import { dbAll } from '../../utils/db-utils.js';
|
|
14
|
-
import { defineSchema, idInput, idsInput, optStr, reqObj, showIdInput, watchlistAddInput, watchlistBatchStatusInput, watchlistStatusInput, } from '../validation.js';
|
|
14
|
+
import { defineSchema, idInput, idsInput, optNum, optStr, reqObj, showIdInput, watchlistAddInput, watchlistBatchStatusInput, watchlistStatusInput, } from '../validation.js';
|
|
15
15
|
const watchlistListInput = () => defineSchema((value) => {
|
|
16
16
|
const obj = reqObj(value);
|
|
17
17
|
const out = { ...readFilterFields(obj) };
|
|
@@ -255,13 +255,6 @@ export const watchlistRouter = router({
|
|
|
255
255
|
return { showId, episodes: remaining };
|
|
256
256
|
}),
|
|
257
257
|
});
|
|
258
|
-
function optNum(obj, name) {
|
|
259
|
-
const value = obj[name];
|
|
260
|
-
if (value === undefined || value === null || value === '')
|
|
261
|
-
return undefined;
|
|
262
|
-
const num = Number(value);
|
|
263
|
-
return Number.isFinite(num) ? num : undefined;
|
|
264
|
-
}
|
|
265
258
|
function readFilterFields(obj) {
|
|
266
259
|
const out = {};
|
|
267
260
|
const query = optStr(obj, 'query');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
1
2
|
export function defineSchema(parse) {
|
|
2
3
|
return {
|
|
3
4
|
'~standard': {
|
|
@@ -49,6 +50,40 @@ export function optBool(obj, name) {
|
|
|
49
50
|
return undefined;
|
|
50
51
|
return value;
|
|
51
52
|
}
|
|
53
|
+
export function badRequest(message) {
|
|
54
|
+
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
55
|
+
}
|
|
56
|
+
export function failed(message) {
|
|
57
|
+
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
58
|
+
}
|
|
59
|
+
export function reqId(obj, name) {
|
|
60
|
+
const value = obj[name];
|
|
61
|
+
if ((typeof value !== 'string' && typeof value !== 'number') || String(value).length === 0) {
|
|
62
|
+
throw new Error(`${name} must be a string`);
|
|
63
|
+
}
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
66
|
+
export function readCount(obj, name, fallback) {
|
|
67
|
+
const raw = obj[name];
|
|
68
|
+
if (raw === undefined || raw === null || raw === '')
|
|
69
|
+
return fallback;
|
|
70
|
+
const n = Math.floor(Number(raw));
|
|
71
|
+
return Number.isFinite(n) ? n : fallback;
|
|
72
|
+
}
|
|
73
|
+
export function strOrNull(obj, name) {
|
|
74
|
+
const value = obj[name];
|
|
75
|
+
return typeof value === 'string' ? value : null;
|
|
76
|
+
}
|
|
77
|
+
export function readPageLimit(obj) {
|
|
78
|
+
const out = {};
|
|
79
|
+
const page = optNum(obj, 'page');
|
|
80
|
+
if (page !== undefined)
|
|
81
|
+
out.page = page;
|
|
82
|
+
const limit = optNum(obj, 'limit');
|
|
83
|
+
if (limit !== undefined)
|
|
84
|
+
out.limit = limit;
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
52
87
|
function reqEpNum(obj, name) {
|
|
53
88
|
const value = obj[name];
|
|
54
89
|
if ((typeof value !== 'string' && typeof value !== 'number') || String(value).length === 0) {
|
|
@@ -7,3 +7,11 @@ export const dbGet = (db, sql, params = []) => {
|
|
|
7
7
|
export const dbRun = (db, sql, params = []) => {
|
|
8
8
|
db.run(sql, params);
|
|
9
9
|
};
|
|
10
|
+
export async function adultNonListIds(db, progressTable, libraryTable, keyCol, adultPred) {
|
|
11
|
+
const rows = await dbAll(db, `SELECT DISTINCT p."${keyCol}" as id
|
|
12
|
+
FROM "${progressTable}" p
|
|
13
|
+
LEFT JOIN "${libraryTable}" l ON l.id = p."${keyCol}"
|
|
14
|
+
WHERE ${adultPred}
|
|
15
|
+
AND l.id IS NULL`);
|
|
16
|
+
return rows.map((r) => r.id);
|
|
17
|
+
}
|
|
@@ -31,6 +31,18 @@ export async function parseJsonBody(res) {
|
|
|
31
31
|
throw err;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
export function linkAbort(signal, abort) {
|
|
35
|
+
signal.addEventListener('abort', () => abort.abort(), { once: true });
|
|
36
|
+
}
|
|
37
|
+
export function setProxyHeaders(out, get) {
|
|
38
|
+
for (const name of ['content-length', 'content-range', 'accept-ranges']) {
|
|
39
|
+
const raw = get(name);
|
|
40
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
41
|
+
if (value)
|
|
42
|
+
out.set(name, value);
|
|
43
|
+
}
|
|
44
|
+
out.set('Access-Control-Allow-Origin', '*');
|
|
45
|
+
}
|
|
34
46
|
export function isAbortError(err) {
|
|
35
47
|
if (!err || typeof err !== 'object')
|
|
36
48
|
return false;
|