@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,550 +1,3 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import { WatchlistRepository } from '../../repositories/watchlist.repository.js';
3
- import { WatchedEpisodesRepository, } from '../../repositories/watched-episodes.repository.js';
4
- import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js';
5
- import { NotificationsRepository } from '../../repositories/notifications.repository.js';
6
- import { QueueRepository } from '../../repositories/queue.repository.js';
7
- import { SettingsRepository } from '../../repositories/settings.repository.js';
8
- import { performWriteTransactionAsync } from '../../sync.js';
9
- import { getMigratedId } from '../../lib/migration.js';
10
- import { isAnilistRateLimited, searchAnilist, searchAnilistByTitle, getAnilistEpisodes, getShowMetaById, } from '../../lib/anilist.js';
11
- import { kitsuSearchAnime } from '../../lib/kitsu.js';
12
- import { offlineDb } from '../../lib/offline-db.js';
13
- import { dbAll } from '../../utils/db-utils.js';
14
- import { defineSchema, idInput, idsInput, optNum, optStr, reqObj, showIdInput, watchlistAddInput, watchlistBatchStatusInput, watchlistStatusInput, } from '../validation.js';
15
- const watchlistListInput = () => defineSchema((value) => {
16
- const obj = reqObj(value);
17
- const out = { ...readFilterFields(obj) };
18
- const status = optStr(obj, 'status');
19
- if (status !== undefined)
20
- out.status = status;
21
- const page = optNum(obj, 'page');
22
- if (page !== undefined)
23
- out.page = page;
24
- const limit = optNum(obj, 'limit');
25
- if (limit !== undefined)
26
- out.limit = limit;
27
- const showMature = optStr(obj, 'showMature');
28
- if (showMature !== undefined)
29
- out.showMature = showMature;
30
- return out;
31
- });
32
- const continueWatchingAllInput = () => defineSchema((value) => {
33
- const obj = reqObj(value);
34
- const out = { ...readFilterFields(obj) };
35
- const page = optNum(obj, 'page');
36
- if (page !== undefined)
37
- out.page = page;
38
- const limit = optNum(obj, 'limit');
39
- if (limit !== undefined)
40
- out.limit = limit;
41
- return out;
42
- });
43
- export const watchlistRouter = router({
44
- check: protectedProcedure.input(showIdInput()).query(async ({ ctx, input }) => {
45
- const showId = await getMigratedId(ctx.db, input.showId);
46
- const item = await WatchlistRepository.getById(ctx.db, showId);
47
- return { inWatchlist: !!item, status: item?.status ?? null };
48
- }),
49
- add: protectedProcedure.input(watchlistAddInput()).mutation(async ({ ctx, input }) => {
50
- const id = await getMigratedId(ctx.db, input.id);
51
- await performWriteTransactionAsync(ctx.db, async (tx) => {
52
- await WatchlistRepository.upsert(tx, {
53
- id,
54
- name: input.name,
55
- thumbnail: input.thumbnail || '',
56
- status: input.status || 'Watching',
57
- nativeName: input.nativeName || '',
58
- englishName: input.englishName || '',
59
- type: input.type || 'TV',
60
- });
61
- if (typeof input.isAdult === 'boolean') {
62
- await ShowsMetaRepository.upsert(tx, { id, isAdult: input.isAdult ? 1 : 0 });
63
- }
64
- });
65
- if (input.name && !/^\d+$/.test(id)) {
66
- const db = ctx.db;
67
- const name = input.name;
68
- const resolveAndSave = async () => {
69
- if (!isAnilistRateLimited()) {
70
- const result = await searchAnilistByTitle(name);
71
- if (result?.id) {
72
- await ShowsMetaRepository.upsert(db, { id, anilistId: result.id });
73
- return;
74
- }
75
- }
76
- try {
77
- const kitsuResults = await kitsuSearchAnime({ query: name, page: 1, perPage: 3 });
78
- if (kitsuResults.length > 0) {
79
- const anilistId = Math.abs(kitsuResults[0].id);
80
- await ShowsMetaRepository.upsert(db, { id, anilistId });
81
- }
82
- }
83
- catch {
84
- // ignore
85
- }
86
- };
87
- void resolveAndSave().catch(() => { });
88
- }
89
- return { success: true };
90
- }),
91
- remove: protectedProcedure.input(idInput()).mutation(async ({ ctx, input }) => {
92
- const id = await getMigratedId(ctx.db, input.id);
93
- await performWriteTransactionAsync(ctx.db, async (tx) => {
94
- await WatchlistRepository.delete(tx, id);
95
- await WatchedEpisodesRepository.deleteByShow(tx, id);
96
- await NotificationsRepository.deleteByShow(tx, id);
97
- });
98
- return { success: true };
99
- }),
100
- removeMany: protectedProcedure.input(idsInput()).mutation(async ({ ctx, input }) => {
101
- const ids = await Promise.all(input.ids.map((id) => getMigratedId(ctx.db, id)));
102
- await performWriteTransactionAsync(ctx.db, async (tx) => {
103
- await WatchlistRepository.deleteMany(tx, ids);
104
- for (const id of ids) {
105
- await WatchedEpisodesRepository.deleteByShow(tx, id);
106
- await NotificationsRepository.deleteByShow(tx, id);
107
- }
108
- });
109
- return { success: true, removed: ids.length };
110
- }),
111
- setStatus: protectedProcedure.input(watchlistStatusInput()).mutation(async ({ ctx, input }) => {
112
- const id = await getMigratedId(ctx.db, input.id);
113
- await performWriteTransactionAsync(ctx.db, async (tx) => {
114
- await WatchlistRepository.updateStatus(tx, id, input.status);
115
- });
116
- return { success: true };
117
- }),
118
- batchStatus: protectedProcedure
119
- .input(watchlistBatchStatusInput())
120
- .mutation(async ({ ctx, input }) => {
121
- const ids = await Promise.all(input.ids.map((id) => getMigratedId(ctx.db, id)));
122
- await performWriteTransactionAsync(ctx.db, async (tx) => {
123
- await WatchlistRepository.updateStatusMany(tx, ids, input.status);
124
- });
125
- return { success: true, updated: ids.length };
126
- }),
127
- list: protectedProcedure.input(watchlistListInput()).query(async ({ ctx, input }) => {
128
- const db = ctx.db;
129
- const page = Math.max(parseInt(String(input.page ?? 1)) || 1, 1);
130
- const limit = Math.min(Math.max(parseInt(String(input.limit ?? 10)) || 10, 1), 100);
131
- const showMature = input.showMature;
132
- const offset = (page - 1) * limit;
133
- const filters = getWatchlistFilters({
134
- query: input.query,
135
- type: input.type,
136
- season: input.season,
137
- year: input.year,
138
- genres: input.genres,
139
- excludeGenres: input.excludeGenres,
140
- sortBy: input.sortBy,
141
- titlePreference: input.titlePreference,
142
- });
143
- const status = input.status;
144
- const allRows = await WatchlistRepository.getAll(db, status);
145
- let filteredRows = await filterWatchlistRows(allRows, filters, db);
146
- if (showMature === 'false') {
147
- const ids = filteredRows.map((r) => r.id);
148
- if (ids.length > 0) {
149
- const placeholders = ids.map(() => '?').join(',');
150
- const adultIds = await dbAll(db, `SELECT id FROM shows_meta WHERE id IN (${placeholders}) AND isAdult = 1`, ids);
151
- const adultSet = new Set(adultIds.map((r) => r.id));
152
- filteredRows = filteredRows.filter((row) => !adultSet.has(row.id));
153
- }
154
- }
155
- const rows = filteredRows.slice(offset, offset + limit);
156
- for (const row of rows) {
157
- if (!row.thumbnail || row.thumbnail.trim() === '') {
158
- try {
159
- const meta = (await ShowsMetaRepository.getById(db, row.id));
160
- if (meta?.thumbnail && meta.thumbnail.trim() !== '') {
161
- row.thumbnail = meta.thumbnail;
162
- }
163
- }
164
- catch {
165
- // ignore
166
- }
167
- }
168
- }
169
- await backfillMissingPosters(db, rows);
170
- const rowIds = rows.map((r) => r.id);
171
- let adultIds = new Set();
172
- if (rowIds.length > 0) {
173
- const placeholders = rowIds.map(() => '?').join(',');
174
- const adultRows = await dbAll(db, `SELECT id FROM shows_meta WHERE id IN (${placeholders}) AND isAdult = 1`, rowIds);
175
- adultIds = new Set(adultRows.map((r) => r.id));
176
- }
177
- return {
178
- data: rows.map((row) => ({
179
- ...row,
180
- _id: row.id,
181
- thumbnail: row.thumbnail || '',
182
- isAdult: adultIds.has(row.id),
183
- })),
184
- total: filteredRows.length,
185
- page,
186
- limit,
187
- };
188
- }),
189
- continueWatchingAll: protectedProcedure
190
- .input(continueWatchingAllInput())
191
- .query(async ({ ctx, input }) => {
192
- const db = ctx.db;
193
- const page = Math.max(parseInt(String(input.page ?? 1)) || 1, 1);
194
- const limit = Math.min(Math.max(parseInt(String(input.limit ?? 10)) || 10, 1), 100);
195
- const offset = (page - 1) * limit;
196
- const filters = getWatchlistFilters({
197
- query: input.query,
198
- type: input.type,
199
- season: input.season,
200
- year: input.year,
201
- genres: input.genres,
202
- excludeGenres: input.excludeGenres,
203
- sortBy: input.sortBy,
204
- titlePreference: input.titlePreference,
205
- });
206
- const data = await filterWatchlistRows(await getContinueWatchingData(db), filters, db);
207
- return {
208
- data: data.slice(offset, offset + limit),
209
- total: data.length,
210
- page,
211
- limit,
212
- };
213
- }),
214
- queueSuggested: protectedProcedure.input(showIdInput()).query(async ({ ctx, input }) => {
215
- const db = ctx.db;
216
- const showId = await getMigratedId(db, input.showId);
217
- const resumeProgress = await WatchedEpisodesRepository.getLatestResumeProgress(db, showId);
218
- if (resumeProgress) {
219
- return {
220
- showId,
221
- episodeNumber: resumeProgress.episodeNumber,
222
- resumeTime: resumeProgress.currentTime || 0,
223
- };
224
- }
225
- const [watchedEpisodes, episodes] = await Promise.all([
226
- WatchedEpisodesRepository.getByShow(db, showId),
227
- resolveAvailableEpisodes(showId),
228
- ]);
229
- const watchedSet = new Set(watchedEpisodes.map((ep) => ep.episodeNumber.toString()));
230
- const finishedEpisodes = watchedEpisodes
231
- .filter((ep) => ep.duration > 0 && ep.currentTime >= ep.duration * 0.8)
232
- .map((ep) => parseFloat(ep.episodeNumber))
233
- .filter((ep) => !Number.isNaN(ep));
234
- const nextAfterFinished = finishedEpisodes.length > 0 ? String(Math.max(...finishedEpisodes) + 1) : undefined;
235
- const episodeNumber = (nextAfterFinished &&
236
- episodes.includes(nextAfterFinished) &&
237
- !watchedSet.has(nextAfterFinished)
238
- ? nextAfterFinished
239
- : episodes.find((ep) => !watchedSet.has(ep))) ||
240
- episodes[0] ||
241
- '1';
242
- return { showId, episodeNumber, resumeTime: 0 };
243
- }),
244
- queueRemaining: protectedProcedure.input(showIdInput()).query(async ({ ctx, input }) => {
245
- const db = ctx.db;
246
- const showId = await getMigratedId(db, input.showId);
247
- const [watchedEpisodes, queuedEpisodes, episodes] = await Promise.all([
248
- WatchedEpisodesRepository.getByShow(db, showId),
249
- QueueRepository.getByShow(db, showId),
250
- resolveAvailableEpisodes(showId),
251
- ]);
252
- const watchedSet = new Set(watchedEpisodes.map((ep) => ep.episodeNumber.toString()));
253
- const queuedSet = new Set(queuedEpisodes.map((ep) => ep.episodeNumber.toString()));
254
- const remaining = episodes.filter((ep) => !watchedSet.has(ep) && !queuedSet.has(ep));
255
- return { showId, episodes: remaining };
256
- }),
257
- });
258
- function readFilterFields(obj) {
259
- const out = {};
260
- const query = optStr(obj, 'query');
261
- if (query !== undefined)
262
- out.query = query;
263
- const type = optStr(obj, 'type');
264
- if (type !== undefined)
265
- out.type = type;
266
- const season = optStr(obj, 'season');
267
- if (season !== undefined)
268
- out.season = season;
269
- const year = optStr(obj, 'year');
270
- if (year !== undefined)
271
- out.year = year;
272
- const genres = optStr(obj, 'genres');
273
- if (genres !== undefined)
274
- out.genres = genres;
275
- const excludeGenres = optStr(obj, 'excludeGenres');
276
- if (excludeGenres !== undefined)
277
- out.excludeGenres = excludeGenres;
278
- const sortBy = optStr(obj, 'sortBy');
279
- if (sortBy !== undefined)
280
- out.sortBy = sortBy;
281
- const titlePreference = optStr(obj, 'titlePreference');
282
- if (titlePreference !== undefined)
283
- out.titlePreference = titlePreference;
284
- return out;
285
- }
286
- function normalizeFilterValue(value) {
287
- if (typeof value !== 'string')
288
- return undefined;
289
- const trimmed = value.trim();
290
- return trimmed && trimmed !== 'ALL' ? trimmed : undefined;
291
- }
292
- function getWatchlistFilters(query) {
293
- return {
294
- query: normalizeFilterValue(query['query']),
295
- type: normalizeFilterValue(query['type']),
296
- season: normalizeFilterValue(query['season']),
297
- year: normalizeFilterValue(query['year']),
298
- genres: normalizeFilterValue(query['genres']),
299
- excludeGenres: normalizeFilterValue(query['excludeGenres']),
300
- sortBy: normalizeFilterValue(query['sortBy']),
301
- titlePreference: ['name', 'nativeName', 'englishName'].includes(String(query['titlePreference']))
302
- ? query['titlePreference']
303
- : 'name',
304
- };
305
- }
306
- function matchesLocalFilters(row, filters) {
307
- if (filters.query) {
308
- const queryWords = new Set(filters.query
309
- .toLowerCase()
310
- .split(/\s+/)
311
- .filter((w) => w.length >= 2));
312
- const rowTitle = (row.englishName || row.name || row.nativeName || '').toLowerCase();
313
- const titleWords = rowTitle.split(/\s+/);
314
- const overlap = titleWords.filter((w) => queryWords.has(w)).length;
315
- if (overlap < queryWords.size)
316
- return false;
317
- }
318
- return true;
319
- }
320
- function sortFilteredRows(rows, filters) {
321
- const getSortTitle = (row) => {
322
- const preferredTitle = filters.titlePreference ? row[filters.titlePreference] : undefined;
323
- return preferredTitle || row.name || '';
324
- };
325
- if (filters.sortBy === 'name_asc') {
326
- return [...rows].sort((a, b) => getSortTitle(a).localeCompare(getSortTitle(b)));
327
- }
328
- if (filters.sortBy === 'name_desc') {
329
- return [...rows].sort((a, b) => getSortTitle(b).localeCompare(getSortTitle(a)));
330
- }
331
- return rows;
332
- }
333
- async function getAnilistSeasonYearMatches(season, year) {
334
- const matched = new Map();
335
- if (!year || year === 'ALL') {
336
- return matched;
337
- }
338
- const seasonYear = parseInt(year);
339
- if (Number.isNaN(seasonYear))
340
- return matched;
341
- const perPage = 50;
342
- let page = 1;
343
- while (true) {
344
- const searchVars = {
345
- seasonYear,
346
- page,
347
- perPage,
348
- };
349
- if (season && season !== 'ALL') {
350
- searchVars.season = season.toUpperCase();
351
- }
352
- const results = await searchAnilist(searchVars);
353
- for (const show of results) {
354
- if (show.anilistId) {
355
- matched.set(show.anilistId, { title: show.names || {} });
356
- }
357
- }
358
- if (results.length < perPage)
359
- break;
360
- page++;
361
- if (page > 10)
362
- break;
363
- }
364
- return matched;
365
- }
366
- function rowMatchesAnilistSeasonYear(row, anilistMatches) {
367
- if (anilistMatches.size === 0)
368
- return true;
369
- if (/^\d+$/.test(row.id) && anilistMatches.has(parseInt(row.id))) {
370
- return true;
371
- }
372
- const rowTitle = (row.englishName || row.name || row.nativeName || '').toLowerCase();
373
- if (!rowTitle)
374
- return false;
375
- const rowWords = new Set(rowTitle.split(/\s+/).filter((w) => w.length >= 2));
376
- if (rowWords.size === 0)
377
- return false;
378
- for (const [, media] of anilistMatches) {
379
- const titles = [media.title?.romaji, media.title?.english, media.title?.native].filter(Boolean);
380
- for (const title of titles) {
381
- const titleWords = new Set(title
382
- .toLowerCase()
383
- .split(/\s+/)
384
- .filter((w) => w.length >= 2));
385
- const overlap = [...rowWords].filter((w) => titleWords.has(w)).length;
386
- const minLen = Math.min(rowWords.size, titleWords.size);
387
- if (minLen >= 2 && overlap / minLen >= 0.7)
388
- return true;
389
- }
390
- }
391
- return false;
392
- }
393
- async function filterWatchlistRows(rows, filters, db) {
394
- let filtered = rows.filter((row) => matchesLocalFilters(row, filters));
395
- if (filters.type === 'ADULT' && db && filtered.length > 0) {
396
- const ids = filtered.map((r) => r.id);
397
- const placeholders = ids.map(() => '?').join(',');
398
- const adultRows = await dbAll(db, `SELECT id FROM shows_meta WHERE id IN (${placeholders}) AND isAdult = 1`, ids);
399
- const adultSet = new Set(adultRows.map((r) => r.id));
400
- filtered = filtered.filter((row) => adultSet.has(row.id));
401
- }
402
- else if (filters.type && filters.type !== 'ALL' && filters.type !== 'ADULT') {
403
- filtered = filtered.filter((row) => row.type === filters.type);
404
- }
405
- if ((filters.genres || filters.excludeGenres) && db) {
406
- const ids = filtered.map((r) => r.id);
407
- const placeholders = ids.map(() => '?').join(',');
408
- const genreRows = await dbAll(db, `SELECT id, genres FROM shows_meta WHERE id IN (${placeholders})`, ids);
409
- const idToGenres = new Map(genreRows.map((r) => [r.id, r.genres ? JSON.parse(r.genres) : []]));
410
- const includeList = filters.genres
411
- ?.split(',')
412
- .map((g) => g.trim())
413
- .filter(Boolean) || [];
414
- const excludeList = filters.excludeGenres
415
- ?.split(',')
416
- .map((g) => g.trim())
417
- .filter(Boolean) || [];
418
- filtered = filtered.filter((row) => {
419
- const rowGenres = idToGenres.get(row.id) || [];
420
- if (includeList.length && !includeList.every((g) => rowGenres.includes(g)))
421
- return false;
422
- if (excludeList.length && excludeList.some((g) => rowGenres.includes(g)))
423
- return false;
424
- return true;
425
- });
426
- }
427
- if (((filters.season && filters.season !== 'ALL') || (filters.year && filters.year !== 'ALL')) &&
428
- filtered.length > 0) {
429
- const anilistMatches = await getAnilistSeasonYearMatches(filters.season, filters.year);
430
- if (anilistMatches.size > 0) {
431
- filtered = filtered.filter((row) => rowMatchesAnilistSeasonYear(row, anilistMatches));
432
- }
433
- }
434
- return sortFilteredRows(filtered, filters);
435
- }
436
- async function backfillMissingPosters(db, rows) {
437
- const missing = rows.filter((r) => !r.thumbnail || r.thumbnail.trim() === '');
438
- if (missing.length === 0)
439
- return;
440
- const slice = missing.slice(0, 10);
441
- await Promise.allSettled(slice.map(async (row) => {
442
- try {
443
- const meta = await getShowMetaById(row.id);
444
- const poster = meta?.thumbnail?.trim();
445
- if (!poster)
446
- return;
447
- row.thumbnail = poster;
448
- const dual = row;
449
- if (dual._id !== undefined)
450
- dual._id = row.id;
451
- try {
452
- await ShowsMetaRepository.upsert(db, {
453
- id: row.id,
454
- thumbnail: poster,
455
- popularityScore: meta?.score ?? (meta?.averageScore != null ? meta.averageScore / 10 : undefined),
456
- });
457
- }
458
- catch {
459
- // ignore
460
- }
461
- try {
462
- await WatchlistRepository.updateThumbnail(db, row.id, poster);
463
- }
464
- catch {
465
- // ignore
466
- }
467
- }
468
- catch {
469
- // ignore
470
- }
471
- }));
472
- await sweepOfflinePosters(db);
473
- }
474
- async function sweepOfflinePosters(db) {
475
- try {
476
- db.run(`UPDATE shows_meta SET name = (SELECT w.name FROM watchlist w WHERE w.id = shows_meta.id)
1
+ import{protectedProcedure as g,router as _}from"../index.js";import{WatchlistRepository as p}from"../../repositories/watchlist.repository.js";import{WatchedEpisodesRepository as N}from"../../repositories/watched-episodes.repository.js";import{ShowsMetaRepository as b}from"../../repositories/shows-meta.repository.js";import{NotificationsRepository as M}from"../../repositories/notifications.repository.js";import{QueueRepository as k}from"../../repositories/queue.repository.js";import{SettingsRepository as v}from"../../repositories/settings.repository.js";import{performWriteTransactionAsync as I}from"../../sync.js";import{getMigratedId as y}from"../../lib/migration.js";import{isAnilistRateLimited as $,searchAnilist as z,searchAnilistByTitle as H,getAnilistEpisodes as U,getShowMetaById as j}from"../../lib/anilist.js";import{kitsuSearchAnime as V}from"../../lib/kitsu.js";import{offlineDb as E}from"../../lib/offline-db.js";import{dbAll as A}from"../../utils/db-utils.js";import{defineSchema as B,idInput as Y,idsInput as K,optNum as W,optStr as h,reqObj as C,showIdInput as R,watchlistAddInput as J,watchlistBatchStatusInput as Q,watchlistStatusInput as X}from"../validation.js";const Z=()=>B(t=>{const e=C(t),s={...F(e)},n=h(e,"status");n!==void 0&&(s.status=n);const i=W(e,"page");i!==void 0&&(s.page=i);const r=W(e,"limit");r!==void 0&&(s.limit=r);const a=h(e,"showMature");return a!==void 0&&(s.showMature=a),s}),ee=()=>B(t=>{const e=C(t),s={...F(e)},n=W(e,"page");n!==void 0&&(s.page=n);const i=W(e,"limit");return i!==void 0&&(s.limit=i),s}),Ae=_({check:g.input(R()).query(async({ctx:t,input:e})=>{const s=await y(t.db,e.showId),n=await p.getById(t.db,s);return{inWatchlist:!!n,status:n?.status??null}}),add:g.input(J()).mutation(async({ctx:t,input:e})=>{const s=await y(t.db,e.id);if(await I(t.db,async n=>{await p.upsert(n,{id:s,name:e.name,thumbnail:e.thumbnail||"",status:e.status||"Watching",nativeName:e.nativeName||"",englishName:e.englishName||"",type:e.type||"TV"}),typeof e.isAdult=="boolean"&&await b.upsert(n,{id:s,isAdult:e.isAdult?1:0})}),e.name&&!/^\d+$/.test(s)){const n=t.db,i=e.name;(async()=>{if(!$()){const a=await H(i);if(a?.id){await b.upsert(n,{id:s,anilistId:a.id});return}}try{const a=await V({query:i,page:1,perPage:3});if(a.length>0){const l=Math.abs(a[0].id);await b.upsert(n,{id:s,anilistId:l})}}catch{}})().catch(()=>{})}return{success:!0}}),remove:g.input(Y()).mutation(async({ctx:t,input:e})=>{const s=await y(t.db,e.id);return await I(t.db,async n=>{await p.delete(n,s),await N.deleteByShow(n,s),await M.deleteByShow(n,s)}),{success:!0}}),removeMany:g.input(K()).mutation(async({ctx:t,input:e})=>{const s=await Promise.all(e.ids.map(n=>y(t.db,n)));return await I(t.db,async n=>{await p.deleteMany(n,s);for(const i of s)await N.deleteByShow(n,i),await M.deleteByShow(n,i)}),{success:!0,removed:s.length}}),setStatus:g.input(X()).mutation(async({ctx:t,input:e})=>{const s=await y(t.db,e.id);return await I(t.db,async n=>{await p.updateStatus(n,s,e.status)}),{success:!0}}),batchStatus:g.input(Q()).mutation(async({ctx:t,input:e})=>{const s=await Promise.all(e.ids.map(n=>y(t.db,n)));return await I(t.db,async n=>{await p.updateStatusMany(n,s,e.status)}),{success:!0,updated:s.length}}),list:g.input(Z()).query(async({ctx:t,input:e})=>{const s=t.db,n=Math.max(parseInt(String(e.page??1))||1,1),i=Math.min(Math.max(parseInt(String(e.limit??10))||10,1),100),r=e.showMature,a=(n-1)*i,l=q({query:e.query,type:e.type,season:e.season,year:e.year,genres:e.genres,excludeGenres:e.excludeGenres,sortBy:e.sortBy,titlePreference:e.titlePreference}),o=e.status,d=await p.getAll(s,o);let c=await O(d,l,s);if(r==="false"){const m=c.map(f=>f.id);if(m.length>0){const f=m.map(()=>"?").join(","),T=await A(s,`SELECT id FROM shows_meta WHERE id IN (${f}) AND isAdult = 1`,m),G=new Set(T.map(P=>P.id));c=c.filter(P=>!G.has(P.id))}}const u=c.slice(a,a+i);for(const m of u)if(!m.thumbnail||m.thumbnail.trim()==="")try{const f=await b.getById(s,m.id);f?.thumbnail&&f.thumbnail.trim()!==""&&(m.thumbnail=f.thumbnail)}catch{}await x(s,u);const w=u.map(m=>m.id);let L=new Set;if(w.length>0){const m=w.map(()=>"?").join(","),f=await A(s,`SELECT id FROM shows_meta WHERE id IN (${m}) AND isAdult = 1`,w);L=new Set(f.map(T=>T.id))}return{data:u.map(m=>({...m,_id:m.id,thumbnail:m.thumbnail||"",isAdult:L.has(m.id)})),total:c.length,page:n,limit:i}}),continueWatchingAll:g.input(ee()).query(async({ctx:t,input:e})=>{const s=t.db,n=Math.max(parseInt(String(e.page??1))||1,1),i=Math.min(Math.max(parseInt(String(e.limit??10))||10,1),100),r=(n-1)*i,a=q({query:e.query,type:e.type,season:e.season,year:e.year,genres:e.genres,excludeGenres:e.excludeGenres,sortBy:e.sortBy,titlePreference:e.titlePreference}),l=await O(await le(s),a,s);return{data:l.slice(r,r+i),total:l.length,page:n,limit:i}}),queueSuggested:g.input(R()).query(async({ctx:t,input:e})=>{const s=t.db,n=await y(s,e.showId),i=await N.getLatestResumeProgress(s,n);if(i)return{showId:n,episodeNumber:i.episodeNumber,resumeTime:i.currentTime||0};const[r,a]=await Promise.all([N.getByShow(s,n),D(n)]),l=new Set(r.map(u=>u.episodeNumber.toString())),o=r.filter(u=>u.duration>0&&u.currentTime>=u.duration*.8).map(u=>parseFloat(u.episodeNumber)).filter(u=>!Number.isNaN(u)),d=o.length>0?String(Math.max(...o)+1):void 0,c=(d&&a.includes(d)&&!l.has(d)?d:a.find(u=>!l.has(u)))||a[0]||"1";return{showId:n,episodeNumber:c,resumeTime:0}}),queueRemaining:g.input(R()).query(async({ctx:t,input:e})=>{const s=t.db,n=await y(s,e.showId),[i,r,a]=await Promise.all([N.getByShow(s,n),k.getByShow(s,n),D(n)]),l=new Set(i.map(c=>c.episodeNumber.toString())),o=new Set(r.map(c=>c.episodeNumber.toString())),d=a.filter(c=>!l.has(c)&&!o.has(c));return{showId:n,episodes:d}})});function F(t){const e={},s=h(t,"query");s!==void 0&&(e.query=s);const n=h(t,"type");n!==void 0&&(e.type=n);const i=h(t,"season");i!==void 0&&(e.season=i);const r=h(t,"year");r!==void 0&&(e.year=r);const a=h(t,"genres");a!==void 0&&(e.genres=a);const l=h(t,"excludeGenres");l!==void 0&&(e.excludeGenres=l);const o=h(t,"sortBy");o!==void 0&&(e.sortBy=o);const d=h(t,"titlePreference");return d!==void 0&&(e.titlePreference=d),e}function S(t){if(typeof t!="string")return;const e=t.trim();return e&&e!=="ALL"?e:void 0}function q(t){return{query:S(t.query),type:S(t.type),season:S(t.season),year:S(t.year),genres:S(t.genres),excludeGenres:S(t.excludeGenres),sortBy:S(t.sortBy),titlePreference:["name","nativeName","englishName"].includes(String(t.titlePreference))?t.titlePreference:"name"}}function te(t,e){if(e.query){const s=new Set(e.query.toLowerCase().split(/\s+/).filter(a=>a.length>=2));if((t.englishName||t.name||t.nativeName||"").toLowerCase().split(/\s+/).filter(a=>s.has(a)).length<s.size)return!1}return!0}function ne(t,e){const s=n=>(e.titlePreference?n[e.titlePreference]:void 0)||n.name||"";return e.sortBy==="name_asc"?[...t].sort((n,i)=>s(n).localeCompare(s(i))):e.sortBy==="name_desc"?[...t].sort((n,i)=>s(i).localeCompare(s(n))):t}async function se(t,e){const s=new Map;if(!e||e==="ALL")return s;const n=parseInt(e);if(Number.isNaN(n))return s;const i=50;let r=1;for(;;){const a={seasonYear:n,page:r,perPage:i};t&&t!=="ALL"&&(a.season=t.toUpperCase());const l=await z(a);for(const o of l)o.anilistId&&s.set(o.anilistId,{title:o.names||{}});if(l.length<i||(r++,r>10))break}return s}function ie(t,e){if(e.size===0||/^\d+$/.test(t.id)&&e.has(parseInt(t.id)))return!0;const s=(t.englishName||t.name||t.nativeName||"").toLowerCase();if(!s)return!1;const n=new Set(s.split(/\s+/).filter(i=>i.length>=2));if(n.size===0)return!1;for(const[,i]of e){const r=[i.title?.romaji,i.title?.english,i.title?.native].filter(Boolean);for(const a of r){const l=new Set(a.toLowerCase().split(/\s+/).filter(c=>c.length>=2)),o=[...n].filter(c=>l.has(c)).length,d=Math.min(n.size,l.size);if(d>=2&&o/d>=.7)return!0}}return!1}async function O(t,e,s){let n=t.filter(i=>te(i,e));if(e.type==="ADULT"&&s&&n.length>0){const i=n.map(o=>o.id),r=i.map(()=>"?").join(","),a=await A(s,`SELECT id FROM shows_meta WHERE id IN (${r}) AND isAdult = 1`,i),l=new Set(a.map(o=>o.id));n=n.filter(o=>l.has(o.id))}else e.type&&e.type!=="ALL"&&e.type!=="ADULT"&&(n=n.filter(i=>i.type===e.type));if((e.genres||e.excludeGenres)&&s){const i=n.map(c=>c.id),r=i.map(()=>"?").join(","),a=await A(s,`SELECT id, genres FROM shows_meta WHERE id IN (${r})`,i),l=new Map(a.map(c=>[c.id,c.genres?JSON.parse(c.genres):[]])),o=e.genres?.split(",").map(c=>c.trim()).filter(Boolean)||[],d=e.excludeGenres?.split(",").map(c=>c.trim()).filter(Boolean)||[];n=n.filter(c=>{const u=l.get(c.id)||[];return!(o.length&&!o.every(w=>u.includes(w))||d.length&&d.some(w=>u.includes(w)))})}if((e.season&&e.season!=="ALL"||e.year&&e.year!=="ALL")&&n.length>0){const i=await se(e.season,e.year);i.size>0&&(n=n.filter(r=>ie(r,i)))}return ne(n,e)}async function x(t,e){const s=e.filter(i=>!i.thumbnail||i.thumbnail.trim()==="");if(s.length===0)return;const n=s.slice(0,10);await Promise.allSettled(n.map(async i=>{try{const r=await j(i.id),a=r?.thumbnail?.trim();if(!a)return;i.thumbnail=a;const l=i;l._id!==void 0&&(l._id=i.id);try{await b.upsert(t,{id:i.id,thumbnail:a,popularityScore:r?.score??(r?.averageScore!=null?r.averageScore/10:void 0)})}catch{}try{await p.updateThumbnail(t,i.id,a)}catch{}}catch{}})),await ae(t)}async function ae(t){try{t.run(`UPDATE shows_meta SET name = (SELECT w.name FROM watchlist w WHERE w.id = shows_meta.id)
477
2
  WHERE (name IS NULL OR TRIM(name) = '')
478
- AND EXISTS (SELECT 1 FROM watchlist w WHERE w.id = shows_meta.id AND w.name IS NOT NULL AND TRIM(w.name) != '')`);
479
- }
480
- catch {
481
- // ignore
482
- }
483
- const [wlMissing, metaMissing] = await Promise.all([
484
- WatchlistRepository.getMissingThumbnails(db),
485
- ShowsMetaRepository.getShowIdsMissingMeta(db),
486
- ]);
487
- const ids = [...new Set([...wlMissing.map((r) => r.id), ...metaMissing.map((r) => r.id)])].slice(0, 500);
488
- if (ids.length === 0)
489
- return;
490
- for (const id of ids) {
491
- const poster = resolveOfflinePoster(id);
492
- if (poster)
493
- await applyPoster(db, id, poster);
494
- }
495
- }
496
- function resolveOfflinePoster(id) {
497
- if (/^\d+$/.test(id)) {
498
- return offlineDb.getByAnilistId(Number(id))?.thumbnail?.trim() || null;
499
- }
500
- const mal = /^mal-(\d+)$/i.exec(id)?.[1];
501
- if (mal)
502
- return offlineDb.getByMalId(Number(mal))?.thumbnail?.trim() || null;
503
- return null;
504
- }
505
- async function applyPoster(db, id, poster) {
506
- try {
507
- await ShowsMetaRepository.upsert(db, { id, thumbnail: poster });
508
- }
509
- catch {
510
- // ignore
511
- }
512
- try {
513
- await WatchlistRepository.updateThumbnail(db, id, poster);
514
- }
515
- catch {
516
- // ignore
517
- }
518
- }
519
- async function getContinueWatchingData(db, limit) {
520
- const rows = await WatchedEpisodesRepository.getContinueWatching(db, limit);
521
- const ignoreAdultRow = await SettingsRepository.getByKey(db, 'ignoreAdultContent');
522
- const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
523
- const watchlistOnlyRow = await SettingsRepository.getByKey(db, 'cwWatchlistOnly');
524
- const watchlistOnly = watchlistOnlyRow
525
- ? watchlistOnlyRow.value === 'true' || watchlistOnlyRow.value === '1'
526
- : false;
527
- const enrichedRows = rows
528
- .filter((show) => {
529
- if (ignoreAdult && show.isAdult)
530
- return false;
531
- if (watchlistOnly && show.watchlistStatus !== 'Watching')
532
- return false;
533
- return true;
534
- })
535
- .map((show) => ({
536
- ...show,
537
- episodeCount: show.episodeCount,
538
- type: show.type || show.smType,
539
- thumbnail: show.thumbnail ?? '',
540
- }));
541
- await backfillMissingPosters(db, enrichedRows);
542
- return enrichedRows;
543
- }
544
- async function resolveAvailableEpisodes(showId) {
545
- const episodeData = await getAnilistEpisodes(showId);
546
- const episodes = Array.isArray(episodeData) && episodeData.length
547
- ? [...episodeData].sort((a, b) => parseFloat(a) - parseFloat(b))
548
- : [];
549
- return episodes;
550
- }
3
+ AND EXISTS (SELECT 1 FROM watchlist w WHERE w.id = shows_meta.id AND w.name IS NOT NULL AND TRIM(w.name) != '')`)}catch{}const[e,s]=await Promise.all([p.getMissingThumbnails(t),b.getShowIdsMissingMeta(t)]),n=[...new Set([...e.map(i=>i.id),...s.map(i=>i.id)])].slice(0,500);if(n.length!==0)for(const i of n){const r=re(i);r&&await oe(t,i,r)}}function re(t){if(/^\d+$/.test(t))return E.getByAnilistId(Number(t))?.thumbnail?.trim()||null;const e=/^mal-(\d+)$/i.exec(t)?.[1];return e&&E.getByMalId(Number(e))?.thumbnail?.trim()||null}async function oe(t,e,s){try{await b.upsert(t,{id:e,thumbnail:s})}catch{}try{await p.updateThumbnail(t,e,s)}catch{}}async function le(t,e){const s=await N.getContinueWatching(t,e),n=await v.getByKey(t,"ignoreAdultContent"),i=n?n.value!=="false":!0,r=await v.getByKey(t,"cwWatchlistOnly"),a=r?r.value==="true"||r.value==="1":!1,l=s.filter(o=>!(i&&o.isAdult||a&&o.watchlistStatus!=="Watching")).map(o=>({...o,episodeCount:o.episodeCount,type:o.type||o.smType,thumbnail:o.thumbnail??""}));return await x(t,l),l}async function D(t){const e=await U(t);return Array.isArray(e)&&e.length?[...e].sort((n,i)=>parseFloat(n)-parseFloat(i)):[]}export{Ae as watchlistRouter};