@serifpersia/dango 3.6.5 → 3.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-JkXG40Es.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-2L0UJkbB.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-BDs03vKk.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-D2mVKEl9.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-MQA_bNQM.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/AvSyncCalibrator-DwHCd9yB.js +10 -0
  8. package/client/dist/assets/{Home-DxMBHFmc.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-hmLWsSXY.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-CWI7hgUh.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-NOc28KAV.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-B5qNhI7v.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-D4wIyb-V.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-Ce1VX7-0.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-P3oY4_14.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-D8EwGYQq.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard-Dp8DjyXB.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-DULtVsAM.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-fIHdE-nq.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-B8XeOmso.js → Player-DQoEQkHL.js} +6 -6
  21. package/client/dist/assets/{PlayerSettings-BycNCiCs.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-T1J37IZT.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-BAwI-apz.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-Bv9DyKtZ.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-Cl0CcVp3.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal--N60WWCB.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DvgVDlUR.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-dCz_-bJn.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-BZHKk1I6.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-C1T5VcPu.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-Dhv_cg4y.js → Tv-DLytPJUj.js} +4 -4
  32. package/client/dist/assets/{TvInfo-DgaLK2H6.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DyX7f8kC.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-Bp0H6kue.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-ohMtMeKa.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-BbQtzsER.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-Bi1cjpLZ.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-BTnhzofk.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-Cvmso3IK.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-CbzGCzmv.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-DaREZ9Ie.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-BVQRhsFh.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-CzsCbndG.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-UuUPzft-.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-CSsbWKH9.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-a_Y0c2FS.js → useTvLibrary-By4Gt-R_.js} +1 -1
  47. package/client/dist/index.html +1 -1
  48. package/package.json +2 -2
  49. package/server/dist/app-auth.js +1 -157
  50. package/server/dist/app-hono.js +7 -557
  51. package/server/dist/config.js +1 -174
  52. package/server/dist/db/drizzle.js +1 -16
  53. package/server/dist/db/schema-anime.js +1 -61
  54. package/server/dist/db/schema-asmr.js +1 -13
  55. package/server/dist/db/schema-manga.js +1 -31
  56. package/server/dist/db/schema-tv.js +1 -18
  57. package/server/dist/db.js +1 -217
  58. package/server/dist/discord-gateway.js +1 -385
  59. package/server/dist/discord-rpc.js +1 -413
  60. package/server/dist/github-sync.js +1 -508
  61. package/server/dist/google.js +1 -741
  62. package/server/dist/hono/asmr.js +1 -10
  63. package/server/dist/hono/auth.js +3 -26
  64. package/server/dist/hono/music.js +1 -209
  65. package/server/dist/hono/proxy.js +36 -905
  66. package/server/dist/hono/tv.js +5 -169
  67. package/server/dist/hono/watchlist.js +2 -338
  68. package/server/dist/lib/anilist.js +26 -1458
  69. package/server/dist/lib/anischedule.js +1 -191
  70. package/server/dist/lib/insights.js +1 -185
  71. package/server/dist/lib/ipc.js +1 -23
  72. package/server/dist/lib/kitsu.js +1 -517
  73. package/server/dist/lib/mal-import.js +1 -291
  74. package/server/dist/lib/mal.js +1 -957
  75. package/server/dist/lib/migration.js +1 -310
  76. package/server/dist/lib/offline-db.js +4 -384
  77. package/server/dist/lib/potoken.js +1 -135
  78. package/server/dist/lib/recommendations.service.js +1 -216
  79. package/server/dist/lib/temp-ids.js +1 -3
  80. package/server/dist/lib/tmdb.js +1 -72
  81. package/server/dist/lib/tracker/anilist-tracker.js +8 -245
  82. package/server/dist/lib/tracker/manga-sync.service.js +1 -773
  83. package/server/dist/lib/tracker/sync-state.js +1 -21
  84. package/server/dist/lib/tracker/sync.service.js +1 -476
  85. package/server/dist/lib/translate.js +3 -79
  86. package/server/dist/lib/ump.js +1 -106
  87. package/server/dist/lib/ytmusic.js +1 -162
  88. package/server/dist/logger.js +1 -16
  89. package/server/dist/providers/manga/manga.types.js +0 -1
  90. package/server/dist/providers/provider.interface.js +0 -1
  91. package/server/dist/providers/remote-loader.js +2 -605
  92. package/server/dist/providers/remote-types.js +1 -124
  93. package/server/dist/providers/title-matching.js +1 -226
  94. package/server/dist/providers/tv.types.js +0 -1
  95. package/server/dist/rclone.js +2 -120
  96. package/server/dist/repositories/asmr.repository.js +11 -46
  97. package/server/dist/repositories/insights.repository.js +11 -25
  98. package/server/dist/repositories/library.repository.js +3 -94
  99. package/server/dist/repositories/mal-cache.repository.js +1 -52
  100. package/server/dist/repositories/manga.repository.js +14 -58
  101. package/server/dist/repositories/notifications.repository.js +6 -52
  102. package/server/dist/repositories/queue.repository.js +5 -58
  103. package/server/dist/repositories/settings.repository.js +2 -29
  104. package/server/dist/repositories/shows-meta.repository.js +3 -56
  105. package/server/dist/repositories/temp-show-ids.repository.js +3 -63
  106. package/server/dist/repositories/tv.repository.js +11 -54
  107. package/server/dist/repositories/watched-episodes.repository.js +6 -55
  108. package/server/dist/repositories/watchlist.repository.js +3 -54
  109. package/server/dist/scripts/backfill-episode-duration.js +1 -42
  110. package/server/dist/server.js +1 -307
  111. package/server/dist/shipped-defaults.js +1 -6
  112. package/server/dist/sync-payload.js +1 -91
  113. package/server/dist/sync.js +3 -647
  114. package/server/dist/trpc/context.js +1 -7
  115. package/server/dist/trpc/errors.js +1 -15
  116. package/server/dist/trpc/index.js +1 -18
  117. package/server/dist/trpc/router.js +1 -49
  118. package/server/dist/trpc/routers/asmr-library.js +1 -112
  119. package/server/dist/trpc/routers/asmr-progress.js +1 -121
  120. package/server/dist/trpc/routers/asmr.js +1 -87
  121. package/server/dist/trpc/routers/auth.js +1 -194
  122. package/server/dist/trpc/routers/continue-watching.js +2 -58
  123. package/server/dist/trpc/routers/data.js +1 -1026
  124. package/server/dist/trpc/routers/discord.js +1 -285
  125. package/server/dist/trpc/routers/discovery.js +1 -11
  126. package/server/dist/trpc/routers/insights.js +1 -14
  127. package/server/dist/trpc/routers/manga-library.js +1 -263
  128. package/server/dist/trpc/routers/manga-progress.js +1 -169
  129. package/server/dist/trpc/routers/manga.js +1 -201
  130. package/server/dist/trpc/routers/music.js +1 -655
  131. package/server/dist/trpc/routers/notifications.js +1 -80
  132. package/server/dist/trpc/routers/progress.js +1 -109
  133. package/server/dist/trpc/routers/queue.js +1 -93
  134. package/server/dist/trpc/routers/radio.js +1 -145
  135. package/server/dist/trpc/routers/settings.js +1 -96
  136. package/server/dist/trpc/routers/tracker.js +1 -213
  137. package/server/dist/trpc/routers/tv-library.js +1 -135
  138. package/server/dist/trpc/routers/tv-progress.js +1 -159
  139. package/server/dist/trpc/routers/tv.js +1 -551
  140. package/server/dist/trpc/routers/watchlist.js +2 -549
  141. package/server/dist/trpc/validation.js +1 -258
  142. package/server/dist/utils/cache.utils.js +1 -25
  143. package/server/dist/utils/cookie.utils.js +1 -13
  144. package/server/dist/utils/db-utils.js +5 -17
  145. package/server/dist/utils/env.utils.js +5 -77
  146. package/server/dist/utils/http.utils.js +1 -82
  147. package/server/dist/utils/request-context.js +1 -2
  148. package/server/dist/utils/security.utils.js +1 -76
  149. package/server/package.json +3 -2
  150. package/client/dist/assets/AvSyncCalibrator-DmoH5eRA.js +0 -10
  151. package/server/dist/utils/megaplay.utils.js +0 -40
@@ -1,213 +1 @@
1
- import { TRPCError } from '@trpc/server';
2
- import { protectedProcedure, router } from '../index.js';
3
- import { SettingsRepository } from '../../repositories/settings.repository.js';
4
- import { performWriteTransactionAsync } from '../../sync.js';
5
- import { AniListTracker } from '../../lib/tracker/anilist-tracker.js';
6
- import { syncAniList, importFromMalUsername, importFromUsername, } from '../../lib/tracker/sync.service.js';
7
- import { syncAniListManga, importFromUsernameManga, importMangaFromMalUsername, } from '../../lib/tracker/manga-sync.service.js';
8
- import { badRequest, defineSchema, failed, reqObj } from '../validation.js';
9
- const TRACKER_TOKEN_KEY = 'tracker_anilist_token';
10
- const TRACKER_USER_KEY = 'tracker_anilist_user';
11
- function unavailable(message) {
12
- return new TRPCError({ code: 'SERVICE_UNAVAILABLE', message });
13
- }
14
- function optBody(value) {
15
- if (value === undefined || value === null)
16
- return {};
17
- return reqObj(value);
18
- }
19
- const trackerTokenInput = () => defineSchema((value) => {
20
- const obj = optBody(value);
21
- return { token: obj.token, code: obj.code, redirectUri: obj.redirectUri };
22
- });
23
- const trackerSyncInput = () => defineSchema((value) => ({
24
- provider: optBody(value).provider,
25
- }));
26
- const trackerUsernameInput = () => defineSchema((value) => {
27
- const obj = optBody(value);
28
- return { username: obj.username, erase: obj.erase };
29
- });
30
- const trackerMalInput = () => defineSchema((value) => {
31
- const obj = optBody(value);
32
- return {
33
- username: obj.username,
34
- erase: obj.erase,
35
- useOfflineDb: obj.useOfflineDb,
36
- skipFallback: obj.skipFallback,
37
- };
38
- });
39
- const trackerMangaSyncInput = () => defineSchema((value) => ({
40
- direction: optBody(value).direction,
41
- }));
42
- function mapImportError(message) {
43
- if (message.includes('private') || message.includes('not found')) {
44
- return new TRPCError({ code: 'NOT_FOUND', message });
45
- }
46
- if (message.includes('blocked') || message.includes('HTTP 429')) {
47
- return new TRPCError({ code: 'TOO_MANY_REQUESTS', message });
48
- }
49
- return failed(message);
50
- }
51
- export const trackerRouter = router({
52
- status: protectedProcedure.query(async ({ ctx }) => {
53
- try {
54
- const tokenRow = await SettingsRepository.getByKey(ctx.db, TRACKER_TOKEN_KEY);
55
- const userRow = await SettingsRepository.getByKey(ctx.db, TRACKER_USER_KEY);
56
- let user = null;
57
- if (userRow?.value) {
58
- try {
59
- user = JSON.parse(userRow.value);
60
- }
61
- catch {
62
- user = null;
63
- }
64
- }
65
- return { anilist: { connected: !!tokenRow?.value, user } };
66
- }
67
- catch {
68
- throw failed('Failed to read tracker status');
69
- }
70
- }),
71
- anilistAuth: protectedProcedure.input(trackerTokenInput()).mutation(async ({ ctx, input }) => {
72
- const accessToken = typeof input.token === 'string' ? input.token.trim() : '';
73
- if (!accessToken) {
74
- throw badRequest('Access token is required');
75
- }
76
- try {
77
- const tracker = new AniListTracker(accessToken);
78
- const viewer = await tracker.getViewer();
79
- await performWriteTransactionAsync(ctx.db, async (tx) => {
80
- await SettingsRepository.upsert(tx, TRACKER_TOKEN_KEY, accessToken);
81
- await SettingsRepository.upsert(tx, TRACKER_USER_KEY, JSON.stringify(viewer));
82
- });
83
- return { success: true, user: viewer };
84
- }
85
- catch (err) {
86
- throw new TRPCError({
87
- code: 'UNAUTHORIZED',
88
- message: err instanceof Error ? err.message : 'Authentication failed',
89
- });
90
- }
91
- }),
92
- anilistDisconnect: protectedProcedure.mutation(async ({ ctx }) => {
93
- try {
94
- await performWriteTransactionAsync(ctx.db, async (tx) => {
95
- await SettingsRepository.upsert(tx, TRACKER_TOKEN_KEY, '');
96
- await SettingsRepository.upsert(tx, TRACKER_USER_KEY, '');
97
- });
98
- return { success: true };
99
- }
100
- catch {
101
- throw failed('Failed to disconnect');
102
- }
103
- }),
104
- sync: protectedProcedure.input(trackerSyncInput()).mutation(async ({ ctx, input }) => {
105
- const { provider = 'anilist' } = input;
106
- if (provider !== 'anilist') {
107
- throw badRequest(`Provider "${provider}" is not supported yet`);
108
- }
109
- try {
110
- const summary = await syncAniList(ctx.db);
111
- return { success: true, summary };
112
- }
113
- catch (err) {
114
- throw failed(err instanceof Error ? err.message : 'Sync failed');
115
- }
116
- }),
117
- anilistImport: protectedProcedure
118
- .input(trackerUsernameInput())
119
- .mutation(async ({ ctx, input }) => {
120
- const { username, erase } = input;
121
- if (!username || typeof username !== 'string') {
122
- throw badRequest('Username is required');
123
- }
124
- try {
125
- const count = await importFromUsername(ctx.db, username.trim(), erase === true);
126
- return { success: true, count };
127
- }
128
- catch (err) {
129
- throw failed(err instanceof Error ? err.message : 'Import failed');
130
- }
131
- }),
132
- malImport: protectedProcedure.input(trackerMalInput()).mutation(async ({ ctx, input }) => {
133
- const { username, erase, useOfflineDb, skipFallback } = input;
134
- if (!username || typeof username !== 'string') {
135
- throw badRequest('MAL username is required');
136
- }
137
- try {
138
- const count = await importFromMalUsername(ctx.db, username.trim(), {
139
- erase: erase === true,
140
- useOfflineDb: useOfflineDb !== false,
141
- skipFallback: skipFallback === true,
142
- });
143
- return { success: true, count };
144
- }
145
- catch (err) {
146
- throw mapImportError(err instanceof Error ? err.message : 'Import failed');
147
- }
148
- }),
149
- mangaSync: protectedProcedure.input(trackerMangaSyncInput()).mutation(async ({ ctx, input }) => {
150
- const { direction } = input;
151
- try {
152
- const mangaDb = ctx.mangaDb;
153
- if (!mangaDb || mangaDb.isClosedCheck()) {
154
- throw unavailable('Manga database is not ready');
155
- }
156
- const syncDirection = direction === 'pull-only' ? 'pull-only' : 'two-way';
157
- const result = await syncAniListManga(ctx.db, mangaDb, {
158
- direction: syncDirection,
159
- });
160
- return {
161
- success: true,
162
- summary: result.summary,
163
- details: result.details,
164
- direction: syncDirection,
165
- };
166
- }
167
- catch (err) {
168
- if (err instanceof TRPCError)
169
- throw err;
170
- throw failed(err instanceof Error ? err.message : 'Manga sync failed');
171
- }
172
- }),
173
- mangaImport: protectedProcedure.input(trackerUsernameInput()).mutation(async ({ ctx, input }) => {
174
- const { username, erase } = input;
175
- if (!username || typeof username !== 'string') {
176
- throw badRequest('Username is required');
177
- }
178
- try {
179
- const mangaDb = ctx.mangaDb;
180
- if (!mangaDb || mangaDb.isClosedCheck()) {
181
- throw unavailable('Manga database is not ready');
182
- }
183
- const count = await importFromUsernameManga(ctx.db, mangaDb, username.trim(), erase === true);
184
- return { success: true, count };
185
- }
186
- catch (err) {
187
- if (err instanceof TRPCError)
188
- throw err;
189
- throw failed(err instanceof Error ? err.message : 'Import failed');
190
- }
191
- }),
192
- mangaMalImport: protectedProcedure
193
- .input(trackerUsernameInput())
194
- .mutation(async ({ ctx, input }) => {
195
- const { username, erase } = input;
196
- if (!username || typeof username !== 'string') {
197
- throw badRequest('Username is required');
198
- }
199
- try {
200
- const mangaDb = ctx.mangaDb;
201
- if (!mangaDb || mangaDb.isClosedCheck()) {
202
- throw unavailable('Manga database is not ready');
203
- }
204
- const result = await importMangaFromMalUsername(ctx.db, mangaDb, username.trim(), erase === true);
205
- return { success: true, ...result };
206
- }
207
- catch (err) {
208
- if (err instanceof TRPCError)
209
- throw err;
210
- throw mapImportError(err instanceof Error ? err.message : 'Import failed');
211
- }
212
- }),
213
- });
1
+ import{TRPCError as i}from"@trpc/server";import{protectedProcedure as s,router as g}from"../index.js";import{SettingsRepository as u}from"../../repositories/settings.repository.js";import{performWriteTransactionAsync as h}from"../../sync.js";import{AniListTracker as T}from"../../lib/tracker/anilist-tracker.js";import{syncAniList as I,importFromMalUsername as E,importFromUsername as M}from"../../lib/tracker/sync.service.js";import{syncAniListManga as U,importFromUsernameManga as S,importMangaFromMalUsername as R}from"../../lib/tracker/manga-sync.service.js";import{badRequest as m,defineSchema as p,failed as c,reqObj as A}from"../validation.js";const k="tracker_anilist_token",f="tracker_anilist_user";function y(r){return new i({code:"SERVICE_UNAVAILABLE",message:r})}function d(r){return r==null?{}:A(r)}const D=()=>p(r=>{const e=d(r);return{token:e.token,code:e.code,redirectUri:e.redirectUri}}),O=()=>p(r=>({provider:d(r).provider})),w=()=>p(r=>{const e=d(r);return{username:e.username,erase:e.erase}}),C=()=>p(r=>{const e=d(r);return{username:e.username,erase:e.erase,useOfflineDb:e.useOfflineDb,skipFallback:e.skipFallback}}),F=()=>p(r=>({direction:d(r).direction}));function b(r){return r.includes("private")||r.includes("not found")?new i({code:"NOT_FOUND",message:r}):r.includes("blocked")||r.includes("HTTP 429")?new i({code:"TOO_MANY_REQUESTS",message:r}):c(r)}const B=g({status:s.query(async({ctx:r})=>{try{const e=await u.getByKey(r.db,k),a=await u.getByKey(r.db,f);let n=null;if(a?.value)try{n=JSON.parse(a.value)}catch{n=null}return{anilist:{connected:!!e?.value,user:n}}}catch{throw c("Failed to read tracker status")}}),anilistAuth:s.input(D()).mutation(async({ctx:r,input:e})=>{const a=typeof e.token=="string"?e.token.trim():"";if(!a)throw m("Access token is required");try{const t=await new T(a).getViewer();return await h(r.db,async o=>{await u.upsert(o,k,a),await u.upsert(o,f,JSON.stringify(t))}),{success:!0,user:t}}catch(n){throw new i({code:"UNAUTHORIZED",message:n instanceof Error?n.message:"Authentication failed"})}}),anilistDisconnect:s.mutation(async({ctx:r})=>{try{return await h(r.db,async e=>{await u.upsert(e,k,""),await u.upsert(e,f,"")}),{success:!0}}catch{throw c("Failed to disconnect")}}),sync:s.input(O()).mutation(async({ctx:r,input:e})=>{const{provider:a="anilist"}=e;if(a!=="anilist")throw m(`Provider "${a}" is not supported yet`);try{return{success:!0,summary:await I(r.db)}}catch(n){throw c(n instanceof Error?n.message:"Sync failed")}}),anilistImport:s.input(w()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n}=e;if(!a||typeof a!="string")throw m("Username is required");try{return{success:!0,count:await M(r.db,a.trim(),n===!0)}}catch(t){throw c(t instanceof Error?t.message:"Import failed")}}),malImport:s.input(C()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n,useOfflineDb:t,skipFallback:o}=e;if(!a||typeof a!="string")throw m("MAL username is required");try{return{success:!0,count:await E(r.db,a.trim(),{erase:n===!0,useOfflineDb:t!==!1,skipFallback:o===!0})}}catch(l){throw b(l instanceof Error?l.message:"Import failed")}}),mangaSync:s.input(F()).mutation(async({ctx:r,input:e})=>{const{direction:a}=e;try{const n=r.mangaDb;if(!n||n.isClosedCheck())throw y("Manga database is not ready");const t=a==="pull-only"?"pull-only":"two-way",o=await U(r.db,n,{direction:t});return{success:!0,summary:o.summary,details:o.details,direction:t}}catch(n){throw n instanceof i?n:c(n instanceof Error?n.message:"Manga sync failed")}}),mangaImport:s.input(w()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n}=e;if(!a||typeof a!="string")throw m("Username is required");try{const t=r.mangaDb;if(!t||t.isClosedCheck())throw y("Manga database is not ready");return{success:!0,count:await S(r.db,t,a.trim(),n===!0)}}catch(t){throw t instanceof i?t:c(t instanceof Error?t.message:"Import failed")}}),mangaMalImport:s.input(w()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n}=e;if(!a||typeof a!="string")throw m("Username is required");try{const t=r.mangaDb;if(!t||t.isClosedCheck())throw y("Manga database is not ready");return{success:!0,...await R(r.db,t,a.trim(),n===!0)}}catch(t){throw t instanceof i?t:b(t instanceof Error?t.message:"Import failed")}})});export{B as trackerRouter};
@@ -1,135 +1 @@
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, readPageLimit } 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 = { ...readPageLimit(obj) };
12
- const status = optStr(obj, 'status');
13
- if (status !== undefined)
14
- out.status = status;
15
- return out;
16
- });
17
- const tvLibraryIdInput = () => defineSchema((value) => ({
18
- id: reqStr(reqObj(value), 'id'),
19
- }));
20
- const tvLibraryAddInput = () => defineSchema((value) => {
21
- const obj = reqObj(value);
22
- return {
23
- tmdbId: obj.tmdbId,
24
- mediaType: obj.mediaType,
25
- id: obj.id,
26
- title: obj.title,
27
- poster: obj.poster,
28
- backdrop: obj.backdrop,
29
- year: obj.year,
30
- overview: obj.overview,
31
- status: obj.status,
32
- adult: obj.adult,
33
- };
34
- });
35
- const tvLibraryRemoveInput = () => defineSchema((value) => {
36
- if (typeof value !== 'string' || value.length === 0)
37
- throw new Error('id must be a string');
38
- return value;
39
- });
40
- const tvLibraryStatusInput = () => defineSchema((value) => {
41
- const obj = reqObj(value);
42
- return { id: reqStr(obj, 'id'), status: reqStr(obj, 'status') };
43
- });
44
- const tvLibraryBatchStatusInput = () => defineSchema((value) => {
45
- const obj = reqObj(value);
46
- return { ids: reqStrArray(obj, 'ids', true), status: reqStr(obj, 'status') };
47
- });
48
- const tvLibraryRemoveManyInput = () => defineSchema((value) => {
49
- if (!Array.isArray(value) || value.length === 0)
50
- throw new Error('ids must be a non-empty array');
51
- return value.map((id) => String(id));
52
- });
53
- export const tvLibraryRouter = router({
54
- list: protectedProcedure.input(tvLibraryListInput()).query(async ({ ctx, input }) => {
55
- const page = Math.max(parseInt(String(input.page ?? 1)) || 1, 1);
56
- const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
57
- const offset = (page - 1) * limit;
58
- const rows = await TvLibraryRepository.getAll(ctx.tvDb, input.status, limit, offset);
59
- const total = await TvLibraryRepository.getCount(ctx.tvDb, input.status);
60
- return { data: rows, total, page, limit };
61
- }),
62
- ids: protectedProcedure.query(async ({ ctx }) => {
63
- return { ids: await TvLibraryRepository.getIds(ctx.tvDb) };
64
- }),
65
- check: protectedProcedure.input(tvLibraryIdInput()).query(async ({ ctx, input }) => {
66
- const item = await TvLibraryRepository.getById(ctx.tvDb, input.id);
67
- return { inLibrary: !!item, status: item?.status ?? null };
68
- }),
69
- add: protectedProcedure.input(tvLibraryAddInput()).mutation(async ({ ctx, input }) => {
70
- const tmdbIdNum = Number(input.tmdbId);
71
- if (!tmdbIdNum || (!input.id && !input.mediaType)) {
72
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'tmdbId and mediaType are required' });
73
- }
74
- const mediaType = normalizeMediaType(input.mediaType || (String(input.id).startsWith('movie:') ? 'movie' : 'tv'));
75
- const id = input.id && String(input.id).includes(':')
76
- ? String(input.id)
77
- : buildTvId(mediaType, tmdbIdNum);
78
- const cleanStatus = typeof input.status === 'string' && TV_STATUSES.includes(input.status)
79
- ? input.status
80
- : 'Watching';
81
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
82
- await TvLibraryRepository.upsert(tx, {
83
- id,
84
- tmdbId: tmdbIdNum,
85
- mediaType,
86
- title: String(input.title || ''),
87
- poster: String(input.poster || ''),
88
- backdrop: input.backdrop ? String(input.backdrop) : undefined,
89
- year: input.year ? String(input.year) : undefined,
90
- overview: input.overview ? String(input.overview) : undefined,
91
- status: cleanStatus,
92
- adult: input.adult === true,
93
- });
94
- });
95
- return { success: true, id };
96
- }),
97
- remove: protectedProcedure.input(tvLibraryRemoveInput()).mutation(async ({ ctx, input }) => {
98
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
99
- await TvLibraryRepository.delete(tx, String(input));
100
- await TvProgressRepository.deleteByMedia(tx, String(input));
101
- });
102
- return { success: true };
103
- }),
104
- setStatus: protectedProcedure.input(tvLibraryStatusInput()).mutation(async ({ ctx, input }) => {
105
- if (!input.id || !TV_STATUSES.includes(input.status)) {
106
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'id and a valid status are required' });
107
- }
108
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
109
- await TvLibraryRepository.updateStatus(tx, String(input.id), String(input.status));
110
- });
111
- return { success: true };
112
- }),
113
- batchStatus: protectedProcedure
114
- .input(tvLibraryBatchStatusInput())
115
- .mutation(async ({ ctx, input }) => {
116
- if (!input.status || !TV_STATUSES.includes(input.status)) {
117
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'a valid status is required' });
118
- }
119
- const ids = input.ids.map((id) => String(id));
120
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
121
- await TvLibraryRepository.updateStatusMany(tx, ids, String(input.status));
122
- });
123
- return { success: true, updated: ids.length };
124
- }),
125
- removeMany: protectedProcedure
126
- .input(tvLibraryRemoveManyInput())
127
- .mutation(async ({ ctx, input }) => {
128
- const ids = input.map((id) => String(id));
129
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
130
- await TvLibraryRepository.deleteMany(tx, ids);
131
- await TvProgressRepository.deleteMany(tx, ids);
132
- });
133
- return { success: true, removed: ids.length };
134
- }),
135
- });
1
+ import{TRPCError as b}from"@trpc/server";import{protectedProcedure as n,router as g}from"../index.js";import{performTvWriteTransactionAsync as o}from"../../sync.js";import{buildTvId as w,TV_STATUSES as v,TvLibraryRepository as s,TvProgressRepository as p}from"../../repositories/tv.repository.js";import{defineSchema as i,reqObj as u,reqStr as y,optStr as S,reqStrArray as I,readPageLimit as T}from"../validation.js";function L(r){return String(r).toLowerCase()==="movie"?"movie":"tv"}const l=()=>i(r=>{const t=u(r),a={...T(t)},e=S(t,"status");return e!==void 0&&(a.status=e),a}),f=()=>i(r=>({id:y(u(r),"id")})),h=()=>i(r=>{const t=u(r);return{tmdbId:t.tmdbId,mediaType:t.mediaType,id:t.id,title:t.title,poster:t.poster,backdrop:t.backdrop,year:t.year,overview:t.overview,status:t.status,adult:t.adult}}),k=()=>i(r=>{if(typeof r!="string"||r.length===0)throw new Error("id must be a string");return r}),A=()=>i(r=>{const t=u(r);return{id:y(t,"id"),status:y(t,"status")}}),D=()=>i(r=>{const t=u(r);return{ids:I(t,"ids",!0),status:y(t,"status")}}),E=()=>i(r=>{if(!Array.isArray(r)||r.length===0)throw new Error("ids must be a non-empty array");return r.map(t=>String(t))}),x=g({list:n.input(l()).query(async({ctx:r,input:t})=>{const a=Math.max(parseInt(String(t.page??1))||1,1),e=Math.min(Math.max(parseInt(String(t.limit??24))||24,1),100),d=(a-1)*e,c=await s.getAll(r.tvDb,t.status,e,d),m=await s.getCount(r.tvDb,t.status);return{data:c,total:m,page:a,limit:e}}),ids:n.query(async({ctx:r})=>({ids:await s.getIds(r.tvDb)})),check:n.input(f()).query(async({ctx:r,input:t})=>{const a=await s.getById(r.tvDb,t.id);return{inLibrary:!!a,status:a?.status??null}}),add:n.input(h()).mutation(async({ctx:r,input:t})=>{const a=Number(t.tmdbId);if(!a||!t.id&&!t.mediaType)throw new b({code:"BAD_REQUEST",message:"tmdbId and mediaType are required"});const e=L(t.mediaType||(String(t.id).startsWith("movie:")?"movie":"tv")),d=t.id&&String(t.id).includes(":")?String(t.id):w(e,a),c=typeof t.status=="string"&&v.includes(t.status)?t.status:"Watching";return await o(r.tvDb,async m=>{await s.upsert(m,{id:d,tmdbId:a,mediaType:e,title:String(t.title||""),poster:String(t.poster||""),backdrop:t.backdrop?String(t.backdrop):void 0,year:t.year?String(t.year):void 0,overview:t.overview?String(t.overview):void 0,status:c,adult:t.adult===!0})}),{success:!0,id:d}}),remove:n.input(k()).mutation(async({ctx:r,input:t})=>(await o(r.tvDb,async a=>{await s.delete(a,String(t)),await p.deleteByMedia(a,String(t))}),{success:!0})),setStatus:n.input(A()).mutation(async({ctx:r,input:t})=>{if(!t.id||!v.includes(t.status))throw new b({code:"BAD_REQUEST",message:"id and a valid status are required"});return await o(r.tvDb,async a=>{await s.updateStatus(a,String(t.id),String(t.status))}),{success:!0}}),batchStatus:n.input(D()).mutation(async({ctx:r,input:t})=>{if(!t.status||!v.includes(t.status))throw new b({code:"BAD_REQUEST",message:"a valid status is required"});const a=t.ids.map(e=>String(e));return await o(r.tvDb,async e=>{await s.updateStatusMany(e,a,String(t.status))}),{success:!0,updated:a.length}}),removeMany:n.input(E()).mutation(async({ctx:r,input:t})=>{const a=t.map(e=>String(e));return await o(r.tvDb,async e=>{await s.deleteMany(e,a),await p.deleteMany(e,a)}),{success:!0,removed:a.length}})});export{x as tvLibraryRouter};
@@ -1,159 +1 @@
1
- import { TRPCError } from '@trpc/server';
2
- import { protectedProcedure, router } from '../index.js';
3
- import { performTvWriteTransactionAsync } from '../../sync.js';
4
- import { adultNonListIds } 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, strOrNull } from '../validation.js';
8
- const isTvContinueAdult = (row) => row.adult === true || row.adult === 1;
9
- const getAdultNonListMediaIds = (db) => adultNonListIds(db, 'tv_progress', 'tv_library', 'mediaId', 'COALESCE(l.adult, p.adult) = 1');
10
- const tvProgressMediaInput = () => defineSchema((value) => ({
11
- mediaId: reqStr(reqObj(value), 'mediaId'),
12
- }));
13
- const tvProgressLatestInput = () => defineSchema((value) => {
14
- const obj = reqObj(value);
15
- return { mediaId: reqStr(obj, 'mediaId'), season: obj.season, episode: obj.episode };
16
- });
17
- const tvProgressSaveInput = () => defineSchema((value) => {
18
- const obj = reqObj(value);
19
- return {
20
- mediaId: reqStr(obj, 'mediaId'),
21
- season: obj.season,
22
- episode: obj.episode,
23
- currentTime: obj.currentTime,
24
- duration: obj.duration,
25
- completed: obj.completed,
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'),
31
- tmdbId: obj.tmdbId ?? null,
32
- mediaType: strOrNull(obj, 'mediaType'),
33
- adult: obj.adult ?? null,
34
- };
35
- });
36
- const tvProgressRemoveInput = () => defineSchema((value) => {
37
- const obj = reqObj(value);
38
- return { mediaId: reqStr(obj, 'mediaId'), season: obj.season, episode: obj.episode };
39
- });
40
- const tvProgressRemoveManyInput = () => defineSchema((value) => {
41
- if (!Array.isArray(value) || value.length === 0)
42
- throw new Error('ids must be a non-empty array');
43
- return value.map((id) => String(id));
44
- });
45
- const tvContinueWatchingInput = () => defineSchema((value) => {
46
- const obj = reqObj(value);
47
- const out = {};
48
- if (obj.limit !== undefined && obj.limit !== null && obj.limit !== '') {
49
- const limit = Number(obj.limit);
50
- if (Number.isFinite(limit))
51
- out.limit = limit;
52
- }
53
- return out;
54
- });
55
- export const tvProgressRouter = router({
56
- getByMedia: protectedProcedure.input(tvProgressMediaInput()).query(async ({ ctx, input }) => {
57
- const rows = await TvProgressRepository.getByMedia(ctx.tvDb, normalizeTvMediaId(input.mediaId));
58
- return { progress: rows };
59
- }),
60
- latest: protectedProcedure.input(tvProgressLatestInput()).query(async ({ ctx, input }) => {
61
- const mediaId = normalizeTvMediaId(input.mediaId);
62
- const season = parseInt(String(input.season), 10);
63
- const episode = parseInt(String(input.episode), 10);
64
- if (Number.isFinite(season) && Number.isFinite(episode)) {
65
- const row = await TvProgressRepository.getEpisode(ctx.tvDb, mediaId, season, episode);
66
- return row || { currentTime: 0, duration: 0, completed: 0 };
67
- }
68
- const row = await TvProgressRepository.getLatest(ctx.tvDb, mediaId);
69
- return row || { currentTime: 0, duration: 0, completed: 0 };
70
- }),
71
- save: protectedProcedure.input(tvProgressSaveInput()).mutation(async ({ ctx, input }) => {
72
- const mediaId = normalizeTvMediaId(input.mediaId);
73
- if (!mediaId) {
74
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'mediaId is required' });
75
- }
76
- const seasonNum = Math.max(Number(input.season) || 1, 1);
77
- const episodeNum = Math.max(Number(input.episode) || 1, 1);
78
- const timeNum = Math.max(Number(input.currentTime) || 0, 0);
79
- const durationNum = Math.max(Number(input.duration) || 0, 0);
80
- const clampedTime = durationNum > 0 ? Math.min(timeNum, durationNum) : timeNum;
81
- const storedTime = durationNum > 0 && clampedTime >= durationNum * 0.8 ? durationNum : clampedTime;
82
- const completedNow = Number(input.completed) === 1 || (durationNum > 0 && storedTime >= durationNum * 0.8) ? 1 : 0;
83
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
84
- await TvProgressRepository.upsert(tx, {
85
- mediaId: String(mediaId),
86
- season: Math.round(seasonNum),
87
- episode: Math.round(episodeNum),
88
- currentTime: storedTime,
89
- duration: durationNum,
90
- completed: completedNow,
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,
96
- tmdbId: input.tmdbId != null ? Number(input.tmdbId) : null,
97
- mediaType: input.mediaType ?? null,
98
- adult: input.adult != null ? Number(input.adult) : null,
99
- });
100
- await TvLibraryRepository.touchProgress(tx, String(mediaId), {
101
- season: Math.round(seasonNum),
102
- episode: Math.round(episodeNum),
103
- });
104
- });
105
- return { success: true };
106
- }),
107
- remove: protectedProcedure.input(tvProgressRemoveInput()).mutation(async ({ ctx, input }) => {
108
- const mediaId = normalizeTvMediaId(input.mediaId);
109
- if (!mediaId) {
110
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'mediaId is required' });
111
- }
112
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
113
- if (input.season !== undefined && input.episode !== undefined) {
114
- await TvProgressRepository.deleteEpisode(tx, String(mediaId), Number(input.season), Number(input.episode));
115
- }
116
- else {
117
- await TvProgressRepository.deleteByMedia(tx, String(mediaId));
118
- }
119
- });
120
- return { success: true };
121
- }),
122
- removeMany: protectedProcedure
123
- .input(tvProgressRemoveManyInput())
124
- .mutation(async ({ ctx, input }) => {
125
- const ids = input.map((id) => normalizeTvMediaId(id));
126
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
127
- await TvProgressRepository.deleteMany(tx, ids);
128
- });
129
- return { success: true, removed: ids.length };
130
- }),
131
- continueWatching: protectedProcedure
132
- .input(tvContinueWatchingInput())
133
- .query(async ({ ctx, input }) => {
134
- const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
135
- const { ignoreAdult, listOnly } = await SettingsRepository.readContinueFlags(ctx.db, 'tvIgnoreAdultContent', 'tvCwWatchlistOnly');
136
- const rows = (await TvProgressRepository.getContinueWatching(ctx.tvDb, limit)).filter((row) => {
137
- if (ignoreAdult && isTvContinueAdult(row))
138
- return false;
139
- if (listOnly && row.watchlistStatus !== 'Watching')
140
- return false;
141
- return true;
142
- });
143
- return { data: rows, total: rows.length };
144
- }),
145
- adultCount: protectedProcedure.query(async ({ ctx }) => {
146
- const ids = await getAdultNonListMediaIds(ctx.tvDb);
147
- return { count: ids.length };
148
- }),
149
- purgeAdult: protectedProcedure.mutation(async ({ ctx }) => {
150
- const ids = await getAdultNonListMediaIds(ctx.tvDb);
151
- if (ids.length > 0) {
152
- await performTvWriteTransactionAsync(ctx.tvDb, async (tx) => {
153
- for (const id of ids)
154
- await TvProgressRepository.deleteByMedia(tx, id);
155
- });
156
- }
157
- return { success: true, removed: ids.length };
158
- }),
159
- });
1
+ import{TRPCError as b}from"@trpc/server";import{protectedProcedure as a,router as h}from"../index.js";import{performTvWriteTransactionAsync as p}from"../../sync.js";import{adultNonListIds as f}from"../../utils/db-utils.js";import{SettingsRepository as M}from"../../repositories/settings.repository.js";import{normalizeTvMediaId as l,TvLibraryRepository as P,TvProgressRepository as s}from"../../repositories/tv.repository.js";import{defineSchema as u,reqObj as c,reqStr as g,strOrNull as m}from"../validation.js";const N=t=>t.adult===!0||t.adult===1,w=t=>f(t,"tv_progress","tv_library","mediaId","COALESCE(l.adult, p.adult) = 1"),S=()=>u(t=>({mediaId:g(c(t),"mediaId")})),k=()=>u(t=>{const e=c(t);return{mediaId:g(e,"mediaId"),season:e.season,episode:e.episode}}),C=()=>u(t=>{const e=c(t);return{mediaId:g(e,"mediaId"),season:e.season,episode:e.episode,currentTime:e.currentTime,duration:e.duration,completed:e.completed,title:m(e,"title"),poster:m(e,"poster"),backdrop:m(e,"backdrop"),year:m(e,"year"),overview:m(e,"overview"),tmdbId:e.tmdbId??null,mediaType:m(e,"mediaType"),adult:e.adult??null}}),D=()=>u(t=>{const e=c(t);return{mediaId:g(e,"mediaId"),season:e.season,episode:e.episode}}),R=()=>u(t=>{if(!Array.isArray(t)||t.length===0)throw new Error("ids must be a non-empty array");return t.map(e=>String(e))}),W=()=>u(t=>{const e=c(t),n={};if(e.limit!==void 0&&e.limit!==null&&e.limit!==""){const r=Number(e.limit);Number.isFinite(r)&&(n.limit=r)}return n}),O=h({getByMedia:a.input(S()).query(async({ctx:t,input:e})=>({progress:await s.getByMedia(t.tvDb,l(e.mediaId))})),latest:a.input(k()).query(async({ctx:t,input:e})=>{const n=l(e.mediaId),r=parseInt(String(e.season),10),i=parseInt(String(e.episode),10);return Number.isFinite(r)&&Number.isFinite(i)?await s.getEpisode(t.tvDb,n,r,i)||{currentTime:0,duration:0,completed:0}:await s.getLatest(t.tvDb,n)||{currentTime:0,duration:0,completed:0}}),save:a.input(C()).mutation(async({ctx:t,input:e})=>{const n=l(e.mediaId);if(!n)throw new b({code:"BAD_REQUEST",message:"mediaId is required"});const r=Math.max(Number(e.season)||1,1),i=Math.max(Number(e.episode)||1,1),d=Math.max(Number(e.currentTime)||0,0),o=Math.max(Number(e.duration)||0,0),v=o>0?Math.min(d,o):d,I=o>0&&v>=o*.8?o:v,T=Number(e.completed)===1||o>0&&I>=o*.8?1:0;return await p(t.tvDb,async y=>{await s.upsert(y,{mediaId:String(n),season:Math.round(r),episode:Math.round(i),currentTime:I,duration:o,completed:T,title:e.title??null,poster:e.poster??null,backdrop:e.backdrop??null,year:e.year??null,overview:e.overview??null,tmdbId:e.tmdbId!=null?Number(e.tmdbId):null,mediaType:e.mediaType??null,adult:e.adult!=null?Number(e.adult):null}),await P.touchProgress(y,String(n),{season:Math.round(r),episode:Math.round(i)})}),{success:!0}}),remove:a.input(D()).mutation(async({ctx:t,input:e})=>{const n=l(e.mediaId);if(!n)throw new b({code:"BAD_REQUEST",message:"mediaId is required"});return await p(t.tvDb,async r=>{e.season!==void 0&&e.episode!==void 0?await s.deleteEpisode(r,String(n),Number(e.season),Number(e.episode)):await s.deleteByMedia(r,String(n))}),{success:!0}}),removeMany:a.input(R()).mutation(async({ctx:t,input:e})=>{const n=e.map(r=>l(r));return await p(t.tvDb,async r=>{await s.deleteMany(r,n)}),{success:!0,removed:n.length}}),continueWatching:a.input(W()).query(async({ctx:t,input:e})=>{const n=Math.min(Math.max(parseInt(String(e.limit??24))||24,1),100),{ignoreAdult:r,listOnly:i}=await M.readContinueFlags(t.db,"tvIgnoreAdultContent","tvCwWatchlistOnly"),d=(await s.getContinueWatching(t.tvDb,n)).filter(o=>!(r&&N(o)||i&&o.watchlistStatus!=="Watching"));return{data:d,total:d.length}}),adultCount:a.query(async({ctx:t})=>({count:(await w(t.tvDb)).length})),purgeAdult:a.mutation(async({ctx:t})=>{const e=await w(t.tvDb);return e.length>0&&await p(t.tvDb,async n=>{for(const r of e)await s.deleteByMedia(n,r)}),{success:!0,removed:e.length}})});export{O as tvProgressRouter};