@serifpersia/dango 3.3.8 → 3.4.0
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-D4qo4289.js → AnimeCard-CJy1SXH3.js} +1 -1
- package/client/dist/assets/{AnimeInfo-BdRRm4So.js → AnimeInfo-BoXlVvZ-.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-DeJNAKMR.js → AnimeInfoPage-BxbA_7AV.js} +2 -2
- package/client/dist/assets/Asmr--Zc8G9zv.js +2 -0
- package/client/dist/assets/AsmrResetProgressModal-CKYm-8cp.js +1 -0
- package/client/dist/assets/AvSyncCalibrator-3yjZS7s6.js +10 -0
- package/client/dist/assets/Home-rrhOGQcg.js +1 -0
- package/client/dist/assets/Insights-Bay90Yeg.js +1 -0
- package/client/dist/assets/{LibraryListPage-D3hTqTRU.js → LibraryListPage-HzuDavbb.js} +1 -1
- package/client/dist/assets/{ListeningList-BgWsfu40.js → ListeningList-DYcQnnsG.js} +1 -1
- package/client/dist/assets/{Manga-Dn-CYins.js → Manga-D8CT9aO0.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-EmQoK7Bv.js → MangaInfoPage-DGK6CYp1.js} +1 -1
- package/client/dist/assets/MangaReadPage-B9XoDKF9.js +1 -0
- package/client/dist/assets/{MangaResetProgressModal-Dian37PQ.js → MangaResetProgressModal-QjZyIAjl.js} +1 -1
- package/client/dist/assets/Mature-CHt_-Hz5.js +1 -0
- package/client/dist/assets/MediaCard-B5584Cq9.js +1 -0
- package/client/dist/assets/Music-HeLrEZ2d.js +1 -0
- package/client/dist/assets/OptionListMenu-CJ90CdwI.js +1 -0
- package/client/dist/assets/Player-BYtEax_z.js +56 -0
- package/client/dist/assets/PlayerSettings-4-cUAY4B.js +1 -0
- package/client/dist/assets/QueueOptionsButton-Ba0dg_82.js +1 -0
- package/client/dist/assets/{QueueRail-B7DeSFoA.js → QueueRail-O7f4RqjB.js} +1 -1
- package/client/dist/assets/Radio-DPZxQnyD.js +1 -0
- package/client/dist/assets/{ReadingList-BT8kUjCB.js → ReadingList-n6rr8ygy.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-DWeiO-hs.js → ResetProgressModal-CcStlbhP.js} +1 -1
- package/client/dist/assets/Search-C2wrWOdy.js +1 -0
- package/client/dist/assets/Settings-CluUYs8U.js +21 -0
- package/client/dist/assets/{SkeletonGrid-B5loz7ii.js → SkeletonGrid-CLEPY9nP.js} +1 -1
- package/client/dist/assets/Trackers-CTGPxs0V.js +2 -0
- package/client/dist/assets/Tv-BwHlDdi8.js +4 -0
- package/client/dist/assets/Tv-ZEA45gXK.css +1 -0
- package/client/dist/assets/TvInfo-CXnrLekr.js +1 -0
- package/client/dist/assets/TvPopup-BekfiNcX.js +1 -0
- package/client/dist/assets/{TvSearch-BYwCf5kO.js → TvSearch-DuqR-z3v.js} +1 -1
- package/client/dist/assets/{TvWatchlist-BiJU6pKT.js → TvWatchlist-CBvgFrA0.js} +1 -1
- package/client/dist/assets/Watchlist-mj8mTOam.js +1 -0
- package/client/dist/assets/{hls-C11FOWHj.js → hls-Boje3IOE.js} +1 -1
- package/client/dist/assets/index-BXwhFWB_.js +178 -0
- package/client/dist/assets/useAnimeInfoData-DJ7A4B4T.js +1 -0
- package/client/dist/assets/useAsmr-DIwIsJXF.js +1 -0
- package/client/dist/assets/{useAsmrPopup-cT_Uu41e.js → useAsmrPopup-e4zNN9ig.js} +1 -1
- package/client/dist/assets/useMangaLibrary-C9Lci5N4.js +1 -0
- package/client/dist/assets/{useMangaPopup-PnmSctHu.js → useMangaPopup-B8nnpvyi.js} +1 -1
- package/client/dist/assets/useProviders-B5Qr4I1y.js +1 -0
- package/client/dist/assets/useTv-Dm8MCYlL.js +1 -0
- package/client/dist/assets/useTvLibrary-B7jEk2Q7.js +1 -0
- package/client/dist/assets/{vendor-query-BXRECSI6.js → vendor-query-BUTye89G.js} +1 -1
- package/client/dist/index.html +2 -2
- package/orchestrator.js +1 -2
- package/package.json +2 -6
- package/server/dist/app-auth.js +9 -29
- package/server/dist/app-hono.js +621 -0
- package/server/dist/config.js +0 -1
- package/server/dist/db/drizzle.js +32 -0
- package/server/dist/db/schema-anime.js +64 -0
- package/server/dist/db/schema-asmr.js +25 -0
- package/server/dist/db/schema-manga.js +31 -0
- package/server/dist/db/schema-tv.js +33 -0
- package/server/dist/db.js +75 -4
- package/server/dist/discord-gateway.js +1 -1
- package/server/dist/google.js +0 -7
- package/server/dist/hono/asmr.js +1 -0
- package/server/dist/hono/auth.js +56 -0
- package/server/dist/hono/music.js +419 -0
- package/server/dist/{controllers/proxy.controller.js → hono/proxy.js} +390 -394
- package/server/dist/hono/tv.js +197 -0
- package/server/dist/hono/watchlist.js +733 -0
- package/server/dist/lib/anilist.js +3 -3
- package/server/dist/lib/insights.js +185 -0
- package/server/dist/lib/ipc.js +1 -2
- package/server/dist/lib/kitsu.js +1 -28
- package/server/dist/lib/mal-import.js +291 -0
- package/server/dist/lib/migration.js +20 -34
- package/server/dist/lib/offline-db.js +36 -36
- package/server/dist/lib/tracker/manga-sync.service.js +21 -21
- package/server/dist/lib/tracker/sync.service.js +20 -20
- package/server/dist/lib/translate.js +79 -0
- package/server/dist/lib/ytmusic.js +15 -4
- package/server/dist/providers/title-matching.js +0 -1
- package/server/dist/repositories/asmr.repository.js +90 -87
- package/server/dist/repositories/insights.repository.js +24 -13
- package/server/dist/repositories/library.repository.js +33 -93
- package/server/dist/repositories/manga.repository.js +108 -116
- package/server/dist/repositories/notifications.repository.js +50 -31
- package/server/dist/repositories/queue.repository.js +36 -25
- package/server/dist/repositories/settings.repository.js +18 -8
- package/server/dist/repositories/shows-meta.repository.js +54 -41
- package/server/dist/repositories/temp-show-ids.repository.js +43 -31
- package/server/dist/repositories/tv.repository.js +98 -115
- package/server/dist/repositories/watched-episodes.repository.js +52 -20
- package/server/dist/repositories/watchlist.repository.js +36 -48
- package/server/dist/server.js +59 -151
- package/server/dist/sync.js +41 -4
- package/server/dist/trpc/context.js +7 -0
- package/server/dist/trpc/errors.js +21 -0
- package/server/dist/trpc/index.js +19 -0
- package/server/dist/trpc/router.js +49 -0
- package/server/dist/trpc/routers/asmr-library.js +122 -0
- package/server/dist/trpc/routers/asmr-progress.js +133 -0
- package/server/dist/trpc/routers/asmr.js +87 -0
- package/server/dist/trpc/routers/auth.js +247 -0
- package/server/dist/trpc/routers/continue-watching.js +83 -0
- package/server/dist/trpc/routers/data.js +1039 -0
- package/server/dist/trpc/routers/discord.js +288 -0
- package/server/dist/trpc/routers/discovery.js +15 -0
- package/server/dist/trpc/routers/insights.js +45 -0
- package/server/dist/trpc/routers/manga-library.js +280 -0
- package/server/dist/trpc/routers/manga-progress.js +181 -0
- package/server/dist/trpc/routers/manga.js +201 -0
- package/server/dist/{controllers/music.controller.js → trpc/routers/music.js} +235 -295
- package/server/dist/trpc/routers/notifications.js +80 -0
- package/server/dist/trpc/routers/progress.js +15 -0
- package/server/dist/trpc/routers/queue.js +95 -0
- package/server/dist/{routes/radio.routes.js → trpc/routers/radio.js} +30 -23
- package/server/dist/trpc/routers/settings.js +105 -0
- package/server/dist/trpc/routers/tracker.js +219 -0
- package/server/dist/trpc/routers/tv-library.js +145 -0
- package/server/dist/trpc/routers/tv-progress.js +171 -0
- package/server/dist/trpc/routers/tv.js +565 -0
- package/server/dist/trpc/routers/watchlist.js +564 -0
- package/server/dist/trpc/validation.js +159 -0
- package/server/dist/utils/security.utils.js +0 -18
- package/server/package.json +5 -10
- package/client/dist/assets/Asmr-D0EGRYsv.js +0 -2
- package/client/dist/assets/AsmrResetProgressModal-Gs0GmVaT.js +0 -1
- package/client/dist/assets/AvSyncCalibrator-D8hqarVB.js +0 -10
- package/client/dist/assets/Home-BnBzjFkW.js +0 -1
- package/client/dist/assets/Insights-i9QgY3nE.js +0 -1
- package/client/dist/assets/MangaReadPage-xJB8K2YU.js +0 -1
- package/client/dist/assets/Mature-DQtXKOXc.js +0 -1
- package/client/dist/assets/MediaCard-DpKoY-1j.js +0 -1
- package/client/dist/assets/Music-BP43Zqmq.js +0 -1
- package/client/dist/assets/OptionListMenu-Bj9ebsW3.js +0 -1
- package/client/dist/assets/Player-O2Cy2GF1.js +0 -56
- package/client/dist/assets/PlayerSettings-DYZ272Yp.js +0 -1
- package/client/dist/assets/QueueOptionsButton-lv26-6jp.js +0 -1
- package/client/dist/assets/Radio-qQBYnVfY.js +0 -1
- package/client/dist/assets/Search-BkvPYN33.js +0 -1
- package/client/dist/assets/Settings-CyZEBF6S.js +0 -21
- package/client/dist/assets/Trackers-JK-aXBYC.js +0 -2
- package/client/dist/assets/Tv-BwXWJxMy.js +0 -4
- package/client/dist/assets/Tv-C2SWcVzN.css +0 -1
- package/client/dist/assets/TvInfo-Dzyq6ucH.js +0 -1
- package/client/dist/assets/TvPopup-PlCU30FO.js +0 -1
- package/client/dist/assets/Watchlist-BOEeGXH9.js +0 -1
- package/client/dist/assets/index-CtoKRNLc.js +0 -178
- package/client/dist/assets/useAnimeInfoData-kr3WOIWH.js +0 -1
- package/client/dist/assets/useAsmr-CB9THf0m.js +0 -1
- package/client/dist/assets/useMangaLibrary-rM6z3_0E.js +0 -1
- package/client/dist/assets/useProviders-B3VB7A9M.js +0 -1
- package/client/dist/assets/useTv-BTUKMH7Q.js +0 -1
- package/client/dist/assets/useTvLibrary-C0sX-ilZ.js +0 -1
- package/server/dist/controllers/asmr-library.controller.js +0 -288
- package/server/dist/controllers/auth.controller.js +0 -225
- package/server/dist/controllers/data.controller.js +0 -767
- package/server/dist/controllers/insights.controller.js +0 -203
- package/server/dist/controllers/manga-library.controller.js +0 -350
- package/server/dist/controllers/settings.controller.js +0 -609
- package/server/dist/controllers/tv-library.controller.js +0 -314
- package/server/dist/controllers/watchlist.controller.js +0 -1069
- package/server/dist/request-logger.js +0 -16
- package/server/dist/routes/asmr-library.routes.js +0 -23
- package/server/dist/routes/asmr.routes.js +0 -81
- package/server/dist/routes/auth.routes.js +0 -25
- package/server/dist/routes/data.routes.js +0 -47
- package/server/dist/routes/discord-gateway.routes.js +0 -51
- package/server/dist/routes/insights.routes.js +0 -35
- package/server/dist/routes/lan-auth.routes.js +0 -46
- package/server/dist/routes/manga-library.routes.js +0 -25
- package/server/dist/routes/manga.routes.js +0 -141
- package/server/dist/routes/music.routes.js +0 -20
- package/server/dist/routes/providers.routes.js +0 -17
- package/server/dist/routes/proxy.routes.js +0 -12
- package/server/dist/routes/settings.routes.js +0 -24
- package/server/dist/routes/tracker.routes.js +0 -287
- package/server/dist/routes/translate.routes.js +0 -88
- package/server/dist/routes/tv-library.routes.js +0 -23
- package/server/dist/routes/tv.routes.js +0 -629
- package/server/dist/routes/watchlist.routes.js +0 -341
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import { protectedProcedure, router } from '../index.js';
|
|
3
|
+
import { performTvWriteTransactionAsync } from '../../sync.js';
|
|
4
|
+
import { buildTvId, TV_STATUSES, TvLibraryRepository, TvProgressRepository, } from '../../repositories/tv.repository.js';
|
|
5
|
+
import { defineSchema, reqObj, reqStr, optStr, reqStrArray } from '../validation.js';
|
|
6
|
+
function normalizeMediaType(raw) {
|
|
7
|
+
return String(raw).toLowerCase() === 'movie' ? 'movie' : 'tv';
|
|
8
|
+
}
|
|
9
|
+
const tvLibraryListInput = () => defineSchema((value) => {
|
|
10
|
+
const obj = reqObj(value);
|
|
11
|
+
const out = {};
|
|
12
|
+
const status = optStr(obj, 'status');
|
|
13
|
+
if (status !== undefined)
|
|
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
|
+
return out;
|
|
26
|
+
});
|
|
27
|
+
const tvLibraryIdInput = () => defineSchema((value) => ({
|
|
28
|
+
id: reqStr(reqObj(value), 'id'),
|
|
29
|
+
}));
|
|
30
|
+
const tvLibraryAddInput = () => defineSchema((value) => {
|
|
31
|
+
const obj = reqObj(value);
|
|
32
|
+
return {
|
|
33
|
+
tmdbId: obj.tmdbId,
|
|
34
|
+
mediaType: obj.mediaType,
|
|
35
|
+
id: obj.id,
|
|
36
|
+
title: obj.title,
|
|
37
|
+
poster: obj.poster,
|
|
38
|
+
backdrop: obj.backdrop,
|
|
39
|
+
year: obj.year,
|
|
40
|
+
overview: obj.overview,
|
|
41
|
+
status: obj.status,
|
|
42
|
+
adult: obj.adult,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
const tvLibraryRemoveInput = () => defineSchema((value) => {
|
|
46
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
47
|
+
throw new Error('id must be a string');
|
|
48
|
+
return value;
|
|
49
|
+
});
|
|
50
|
+
const tvLibraryStatusInput = () => defineSchema((value) => {
|
|
51
|
+
const obj = reqObj(value);
|
|
52
|
+
return { id: reqStr(obj, 'id'), status: reqStr(obj, 'status') };
|
|
53
|
+
});
|
|
54
|
+
const tvLibraryBatchStatusInput = () => defineSchema((value) => {
|
|
55
|
+
const obj = reqObj(value);
|
|
56
|
+
return { ids: reqStrArray(obj, 'ids', true), status: reqStr(obj, 'status') };
|
|
57
|
+
});
|
|
58
|
+
const tvLibraryRemoveManyInput = () => defineSchema((value) => {
|
|
59
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
60
|
+
throw new Error('ids must be a non-empty array');
|
|
61
|
+
return value.map((id) => String(id));
|
|
62
|
+
});
|
|
63
|
+
export const tvLibraryRouter = router({
|
|
64
|
+
list: protectedProcedure.input(tvLibraryListInput()).query(async ({ ctx, input }) => {
|
|
65
|
+
const page = Math.max(parseInt(String(input.page ?? 1)) || 1, 1);
|
|
66
|
+
const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
|
|
67
|
+
const offset = (page - 1) * limit;
|
|
68
|
+
const rows = await TvLibraryRepository.getAll(ctx.tvDb, input.status, limit, offset);
|
|
69
|
+
const total = await TvLibraryRepository.getCount(ctx.tvDb, input.status);
|
|
70
|
+
return { data: rows, total, page, limit };
|
|
71
|
+
}),
|
|
72
|
+
ids: protectedProcedure.query(async ({ ctx }) => {
|
|
73
|
+
return { ids: await TvLibraryRepository.getIds(ctx.tvDb) };
|
|
74
|
+
}),
|
|
75
|
+
check: protectedProcedure.input(tvLibraryIdInput()).query(async ({ ctx, input }) => {
|
|
76
|
+
const item = await TvLibraryRepository.getById(ctx.tvDb, input.id);
|
|
77
|
+
return { inLibrary: !!item, status: item?.status ?? null };
|
|
78
|
+
}),
|
|
79
|
+
add: protectedProcedure.input(tvLibraryAddInput()).mutation(async ({ ctx, input }) => {
|
|
80
|
+
const tmdbIdNum = Number(input.tmdbId);
|
|
81
|
+
if (!tmdbIdNum || (!input.id && !input.mediaType)) {
|
|
82
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'tmdbId and mediaType are required' });
|
|
83
|
+
}
|
|
84
|
+
const mediaType = normalizeMediaType(input.mediaType || (String(input.id).startsWith('movie:') ? 'movie' : 'tv'));
|
|
85
|
+
const id = input.id && String(input.id).includes(':')
|
|
86
|
+
? String(input.id)
|
|
87
|
+
: buildTvId(mediaType, tmdbIdNum);
|
|
88
|
+
const cleanStatus = typeof input.status === 'string' && TV_STATUSES.includes(input.status)
|
|
89
|
+
? input.status
|
|
90
|
+
: 'Watching';
|
|
91
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
92
|
+
await TvLibraryRepository.upsert(tx, {
|
|
93
|
+
id,
|
|
94
|
+
tmdbId: tmdbIdNum,
|
|
95
|
+
mediaType,
|
|
96
|
+
title: String(input.title || ''),
|
|
97
|
+
poster: String(input.poster || ''),
|
|
98
|
+
backdrop: input.backdrop ? String(input.backdrop) : undefined,
|
|
99
|
+
year: input.year ? String(input.year) : undefined,
|
|
100
|
+
overview: input.overview ? String(input.overview) : undefined,
|
|
101
|
+
status: cleanStatus,
|
|
102
|
+
adult: input.adult === true,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
return { success: true, id };
|
|
106
|
+
}),
|
|
107
|
+
remove: protectedProcedure.input(tvLibraryRemoveInput()).mutation(async ({ ctx, input }) => {
|
|
108
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
109
|
+
await TvLibraryRepository.delete(tx, String(input));
|
|
110
|
+
await TvProgressRepository.deleteByMedia(tx, String(input));
|
|
111
|
+
});
|
|
112
|
+
return { success: true };
|
|
113
|
+
}),
|
|
114
|
+
setStatus: protectedProcedure.input(tvLibraryStatusInput()).mutation(async ({ ctx, input }) => {
|
|
115
|
+
if (!input.id || !TV_STATUSES.includes(input.status)) {
|
|
116
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'id and a valid status are required' });
|
|
117
|
+
}
|
|
118
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
119
|
+
await TvLibraryRepository.updateStatus(tx, String(input.id), String(input.status));
|
|
120
|
+
});
|
|
121
|
+
return { success: true };
|
|
122
|
+
}),
|
|
123
|
+
batchStatus: protectedProcedure
|
|
124
|
+
.input(tvLibraryBatchStatusInput())
|
|
125
|
+
.mutation(async ({ ctx, input }) => {
|
|
126
|
+
if (!input.status || !TV_STATUSES.includes(input.status)) {
|
|
127
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'a valid status is required' });
|
|
128
|
+
}
|
|
129
|
+
const ids = input.ids.map((id) => String(id));
|
|
130
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
131
|
+
await TvLibraryRepository.updateStatusMany(tx, ids, String(input.status));
|
|
132
|
+
});
|
|
133
|
+
return { success: true, updated: ids.length };
|
|
134
|
+
}),
|
|
135
|
+
removeMany: protectedProcedure
|
|
136
|
+
.input(tvLibraryRemoveManyInput())
|
|
137
|
+
.mutation(async ({ ctx, input }) => {
|
|
138
|
+
const ids = input.map((id) => String(id));
|
|
139
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
140
|
+
await TvLibraryRepository.deleteMany(tx, ids);
|
|
141
|
+
await TvProgressRepository.deleteMany(tx, ids);
|
|
142
|
+
});
|
|
143
|
+
return { success: true, removed: ids.length };
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import { protectedProcedure, router } from '../index.js';
|
|
3
|
+
import { performTvWriteTransactionAsync } from '../../sync.js';
|
|
4
|
+
import { dbAll } from '../../utils/db-utils.js';
|
|
5
|
+
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
6
|
+
import { normalizeTvMediaId, TvLibraryRepository, TvProgressRepository, } from '../../repositories/tv.repository.js';
|
|
7
|
+
import { defineSchema, reqObj, reqStr } from '../validation.js';
|
|
8
|
+
const isTvContinueAdult = (row) => row.adult === true || row.adult === 1;
|
|
9
|
+
async function getAdultNonListMediaIds(db) {
|
|
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
|
+
}
|
|
17
|
+
const tvProgressMediaInput = () => defineSchema((value) => ({
|
|
18
|
+
mediaId: reqStr(reqObj(value), 'mediaId'),
|
|
19
|
+
}));
|
|
20
|
+
const tvProgressLatestInput = () => defineSchema((value) => {
|
|
21
|
+
const obj = reqObj(value);
|
|
22
|
+
return { mediaId: reqStr(obj, 'mediaId'), season: obj.season, episode: obj.episode };
|
|
23
|
+
});
|
|
24
|
+
const tvProgressSaveInput = () => defineSchema((value) => {
|
|
25
|
+
const obj = reqObj(value);
|
|
26
|
+
return {
|
|
27
|
+
mediaId: reqStr(obj, 'mediaId'),
|
|
28
|
+
season: obj.season,
|
|
29
|
+
episode: obj.episode,
|
|
30
|
+
currentTime: obj.currentTime,
|
|
31
|
+
duration: obj.duration,
|
|
32
|
+
completed: obj.completed,
|
|
33
|
+
title: obj.title ?? null,
|
|
34
|
+
poster: obj.poster ?? null,
|
|
35
|
+
backdrop: obj.backdrop ?? null,
|
|
36
|
+
year: obj.year ?? null,
|
|
37
|
+
overview: obj.overview ?? null,
|
|
38
|
+
tmdbId: obj.tmdbId ?? null,
|
|
39
|
+
mediaType: obj.mediaType ?? null,
|
|
40
|
+
adult: obj.adult ?? null,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
const tvProgressRemoveInput = () => defineSchema((value) => {
|
|
44
|
+
const obj = reqObj(value);
|
|
45
|
+
return { mediaId: reqStr(obj, 'mediaId'), season: obj.season, episode: obj.episode };
|
|
46
|
+
});
|
|
47
|
+
const tvProgressRemoveManyInput = () => defineSchema((value) => {
|
|
48
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
49
|
+
throw new Error('ids must be a non-empty array');
|
|
50
|
+
return value.map((id) => String(id));
|
|
51
|
+
});
|
|
52
|
+
const tvContinueWatchingInput = () => defineSchema((value) => {
|
|
53
|
+
const obj = reqObj(value);
|
|
54
|
+
const out = {};
|
|
55
|
+
if (obj.limit !== undefined && obj.limit !== null && obj.limit !== '') {
|
|
56
|
+
const limit = Number(obj.limit);
|
|
57
|
+
if (Number.isFinite(limit))
|
|
58
|
+
out.limit = limit;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
});
|
|
62
|
+
export const tvProgressRouter = router({
|
|
63
|
+
getByMedia: protectedProcedure.input(tvProgressMediaInput()).query(async ({ ctx, input }) => {
|
|
64
|
+
const rows = await TvProgressRepository.getByMedia(ctx.tvDb, normalizeTvMediaId(input.mediaId));
|
|
65
|
+
return { progress: rows };
|
|
66
|
+
}),
|
|
67
|
+
latest: protectedProcedure.input(tvProgressLatestInput()).query(async ({ ctx, input }) => {
|
|
68
|
+
const mediaId = normalizeTvMediaId(input.mediaId);
|
|
69
|
+
const season = parseInt(String(input.season), 10);
|
|
70
|
+
const episode = parseInt(String(input.episode), 10);
|
|
71
|
+
if (Number.isFinite(season) && Number.isFinite(episode)) {
|
|
72
|
+
const row = await TvProgressRepository.getEpisode(ctx.tvDb, mediaId, season, episode);
|
|
73
|
+
return row || { currentTime: 0, duration: 0, completed: 0 };
|
|
74
|
+
}
|
|
75
|
+
const row = await TvProgressRepository.getLatest(ctx.tvDb, mediaId);
|
|
76
|
+
return row || { currentTime: 0, duration: 0, completed: 0 };
|
|
77
|
+
}),
|
|
78
|
+
save: protectedProcedure.input(tvProgressSaveInput()).mutation(async ({ ctx, input }) => {
|
|
79
|
+
const mediaId = normalizeTvMediaId(input.mediaId);
|
|
80
|
+
if (!mediaId) {
|
|
81
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'mediaId is required' });
|
|
82
|
+
}
|
|
83
|
+
const seasonNum = Math.max(Number(input.season) || 1, 1);
|
|
84
|
+
const episodeNum = Math.max(Number(input.episode) || 1, 1);
|
|
85
|
+
const timeNum = Math.max(Number(input.currentTime) || 0, 0);
|
|
86
|
+
const durationNum = Math.max(Number(input.duration) || 0, 0);
|
|
87
|
+
const clampedTime = durationNum > 0 ? Math.min(timeNum, durationNum) : timeNum;
|
|
88
|
+
const storedTime = durationNum > 0 && clampedTime >= durationNum * 0.8 ? durationNum : clampedTime;
|
|
89
|
+
const completedNow = Number(input.completed) === 1 || (durationNum > 0 && storedTime >= durationNum * 0.8) ? 1 : 0;
|
|
90
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
91
|
+
await TvProgressRepository.upsert(tx, {
|
|
92
|
+
mediaId: String(mediaId),
|
|
93
|
+
season: Math.round(seasonNum),
|
|
94
|
+
episode: Math.round(episodeNum),
|
|
95
|
+
currentTime: storedTime,
|
|
96
|
+
duration: durationNum,
|
|
97
|
+
completed: completedNow,
|
|
98
|
+
title: (input.title ?? null),
|
|
99
|
+
poster: (input.poster ?? null),
|
|
100
|
+
backdrop: (input.backdrop ?? null),
|
|
101
|
+
year: (input.year ?? null),
|
|
102
|
+
overview: (input.overview ?? null),
|
|
103
|
+
tmdbId: input.tmdbId != null ? Number(input.tmdbId) : null,
|
|
104
|
+
mediaType: (input.mediaType ?? null),
|
|
105
|
+
adult: input.adult != null ? Number(input.adult) : null,
|
|
106
|
+
});
|
|
107
|
+
await TvLibraryRepository.touchProgress(tx, String(mediaId), {
|
|
108
|
+
season: Math.round(seasonNum),
|
|
109
|
+
episode: Math.round(episodeNum),
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
return { success: true };
|
|
113
|
+
}),
|
|
114
|
+
remove: protectedProcedure.input(tvProgressRemoveInput()).mutation(async ({ ctx, input }) => {
|
|
115
|
+
const mediaId = normalizeTvMediaId(input.mediaId);
|
|
116
|
+
if (!mediaId) {
|
|
117
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'mediaId is required' });
|
|
118
|
+
}
|
|
119
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
120
|
+
if (input.season !== undefined && input.episode !== undefined) {
|
|
121
|
+
await TvProgressRepository.deleteEpisode(tx, String(mediaId), Number(input.season), Number(input.episode));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await TvProgressRepository.deleteByMedia(tx, String(mediaId));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return { success: true };
|
|
128
|
+
}),
|
|
129
|
+
removeMany: protectedProcedure
|
|
130
|
+
.input(tvProgressRemoveManyInput())
|
|
131
|
+
.mutation(async ({ ctx, input }) => {
|
|
132
|
+
const ids = input.map((id) => normalizeTvMediaId(id));
|
|
133
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
134
|
+
await TvProgressRepository.deleteMany(tx, ids);
|
|
135
|
+
});
|
|
136
|
+
return { success: true, removed: ids.length };
|
|
137
|
+
}),
|
|
138
|
+
continueWatching: protectedProcedure
|
|
139
|
+
.input(tvContinueWatchingInput())
|
|
140
|
+
.query(async ({ ctx, input }) => {
|
|
141
|
+
const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
|
|
142
|
+
const ignoreAdultRow = await SettingsRepository.getByKey(ctx.db, 'tvIgnoreAdultContent');
|
|
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;
|
|
148
|
+
const rows = (await TvProgressRepository.getContinueWatching(ctx.tvDb, limit)).filter((row) => {
|
|
149
|
+
if (ignoreAdult && isTvContinueAdult(row))
|
|
150
|
+
return false;
|
|
151
|
+
if (listOnly && row.watchlistStatus !== 'Watching')
|
|
152
|
+
return false;
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
return { data: rows, total: rows.length };
|
|
156
|
+
}),
|
|
157
|
+
adultCount: protectedProcedure.query(async ({ ctx }) => {
|
|
158
|
+
const ids = await getAdultNonListMediaIds(ctx.tvDb);
|
|
159
|
+
return { count: ids.length };
|
|
160
|
+
}),
|
|
161
|
+
purgeAdult: protectedProcedure.mutation(async ({ ctx }) => {
|
|
162
|
+
const ids = await getAdultNonListMediaIds(ctx.tvDb);
|
|
163
|
+
if (ids.length > 0) {
|
|
164
|
+
await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
|
|
165
|
+
for (const id of ids)
|
|
166
|
+
await TvProgressRepository.deleteByMedia(tx, id);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return { success: true, removed: ids.length };
|
|
170
|
+
}),
|
|
171
|
+
});
|