@serifpersia/dango 3.6.7 → 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 (149) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-DwHCd9yB.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-DguKjI_t.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-CQpyXTLH.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-CsH4x_I_.js → Player-DQoEQkHL.js} +2 -2
  21. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-DF6g6odd.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DFkhuk-x.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-DLytPJUj.js} +1 -1
  32. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-BGCX7mJy.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-CnfZwsds.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-SymV1J-b.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-BqeSmyc4.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 -783
  66. package/server/dist/hono/tv.js +5 -200
  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 -614
  92. package/server/dist/providers/remote-types.js +1 -159
  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 -310
  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
@@ -1,1026 +1 @@
1
- import { TRPCError } from '@trpc/server';
2
- import { protectedProcedure, router } from '../index.js';
3
- import { badRequest, defineSchema, failed, optNum, optStr, readCount, reqObj, reqStr, } from '../validation.js';
4
- import { parseJsonBody } from '../../utils/http.utils.js';
5
- import { getTrending, getLatestReleases, getSeasonal, getSchedule, searchAnilist, searchAnilistByTitle, getSpotlightBanners, getBatchedHomeData, getGenreTagLists, anilistUnavailable, wasAnilistDownAtBoot, checkAnilistStatus, getShowMetaById, getAnilistEpisodes, parseMalId, getShowMetaByMalId, fromAnilistMedia, } from '../../lib/anilist.js';
6
- import { malAnimeDetail, malSearchMedia, malSearchTitle } from '../../lib/mal.js';
7
- import { malCacheStore } from '../../repositories/mal-cache.repository.js';
8
- import { getMigratedId } from '../../lib/migration.js';
9
- import { isTempShowId } from '../../lib/temp-ids.js';
10
- import { TempShowIdsRepository } from '../../repositories/temp-show-ids.repository.js';
11
- import { pickBestMatch } from '../../providers/title-matching.js';
12
- import { authRequiredError } from '../errors.js';
13
- import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js';
14
- import { WatchlistRepository } from '../../repositories/watchlist.repository.js';
15
- import { dbRun } from '../../utils/db-utils.js';
16
- import logger from '../../logger.js';
17
- async function cached(ctx, key, ttl, validate, produce) {
18
- const hit = ctx.apiCache.get(key);
19
- if (hit)
20
- return hit;
21
- const body = await produce();
22
- if (validate(body)) {
23
- if (ttl !== undefined)
24
- ctx.apiCache.set(key, body, ttl);
25
- else
26
- ctx.apiCache.set(key, body);
27
- }
28
- return body;
29
- }
30
- const defaultValidate = (d) => Array.isArray(d) && d.length > 0;
31
- const dataScheduleInput = () => defineSchema((value) => {
32
- const obj = reqObj(value);
33
- const out = { date: reqStr(obj, 'date') };
34
- const format = optStr(obj, 'format');
35
- if (format !== undefined)
36
- out.format = format;
37
- return out;
38
- });
39
- const dataLatestReleasesInput = () => defineSchema((value) => {
40
- const obj = reqObj(value);
41
- const out = {};
42
- const format = optStr(obj, 'format');
43
- if (format !== undefined)
44
- out.format = format;
45
- if (obj.page !== undefined)
46
- out.page = readCount(obj, 'page', 1);
47
- if (obj.size !== undefined)
48
- out.size = readCount(obj, 'size', 12);
49
- return out;
50
- });
51
- const dataSearchInput = () => defineSchema((value) => {
52
- const obj = reqObj(value);
53
- const out = {};
54
- const query = optStr(obj, 'query');
55
- if (query !== undefined)
56
- out.query = query;
57
- if (obj.page !== undefined)
58
- out.page = readCount(obj, 'page', 1);
59
- if (obj.limit !== undefined)
60
- out.limit = readCount(obj, 'limit', 14);
61
- const sortBy = optStr(obj, 'sortBy');
62
- if (sortBy !== undefined)
63
- out.sortBy = sortBy;
64
- const type = optStr(obj, 'type');
65
- if (type !== undefined)
66
- out.type = type;
67
- const status = optStr(obj, 'status');
68
- if (status !== undefined)
69
- out.status = status;
70
- const season = optStr(obj, 'season');
71
- if (season !== undefined)
72
- out.season = season;
73
- const year = optNum(obj, 'year');
74
- if (year !== undefined)
75
- out.year = year;
76
- const country = optStr(obj, 'country');
77
- if (country !== undefined)
78
- out.country = country;
79
- const genres = optStr(obj, 'genres');
80
- if (genres !== undefined)
81
- out.genres = genres;
82
- const excludeGenres = optStr(obj, 'excludeGenres');
83
- if (excludeGenres !== undefined)
84
- out.excludeGenres = excludeGenres;
85
- const excludeTags = optStr(obj, 'excludeTags');
86
- if (excludeTags !== undefined)
87
- out.excludeTags = excludeTags;
88
- const minScore = optNum(obj, 'minScore');
89
- if (minScore !== undefined)
90
- out.minScore = minScore;
91
- const minEpisodes = optNum(obj, 'minEpisodes');
92
- if (minEpisodes !== undefined)
93
- out.minEpisodes = minEpisodes;
94
- const adult = optStr(obj, 'adult');
95
- if (adult !== undefined)
96
- out.adult = adult;
97
- return out;
98
- });
99
- const dataMatureResolveInput = () => defineSchema((value) => {
100
- const obj = reqObj(value);
101
- const out = {};
102
- const title = optStr(obj, 'title');
103
- if (title !== undefined)
104
- out.title = title;
105
- return out;
106
- });
107
- const dataSkipTimesInput = () => defineSchema((value) => {
108
- const obj = reqObj(value);
109
- const out = {};
110
- const showId = optStr(obj, 'showId');
111
- if (showId !== undefined)
112
- out.showId = showId;
113
- const episodeNumber = optStr(obj, 'episodeNumber');
114
- if (episodeNumber !== undefined)
115
- out.episodeNumber = episodeNumber;
116
- return out;
117
- });
118
- const dataSeasonalInput = () => defineSchema((value) => {
119
- const obj = reqObj(value);
120
- const out = {};
121
- if (obj.page !== undefined)
122
- out.page = readCount(obj, 'page', 1);
123
- if (obj.size !== undefined)
124
- out.size = readCount(obj, 'size', 14);
125
- const format = optStr(obj, 'format');
126
- if (format !== undefined)
127
- out.format = format;
128
- return out;
129
- });
130
- const dataPopularListInput = () => defineSchema((value) => {
131
- const obj = reqObj(value);
132
- const out = {};
133
- const sort = optStr(obj, 'sort');
134
- if (sort !== undefined)
135
- out.sort = sort;
136
- if (obj.page !== undefined)
137
- out.page = readCount(obj, 'page', 1);
138
- if (obj.size !== undefined)
139
- out.size = readCount(obj, 'size', 20);
140
- return out;
141
- });
142
- const dataHomeInput = () => defineSchema((value) => {
143
- if (value === undefined || value === null)
144
- return {};
145
- const obj = reqObj(value);
146
- const out = {};
147
- const format = optStr(obj, 'format');
148
- if (format !== undefined)
149
- out.format = format;
150
- return out;
151
- });
152
- const dataEpisodesInput = () => defineSchema((value) => {
153
- if (value === undefined || value === null)
154
- return {};
155
- const obj = reqObj(value);
156
- const out = {};
157
- const showId = optStr(obj, 'showId');
158
- if (showId !== undefined)
159
- out.showId = showId;
160
- const mode = optStr(obj, 'mode');
161
- if (mode === 'sub' || mode === 'dub')
162
- out.mode = mode;
163
- const provider = optStr(obj, 'provider');
164
- if (provider !== undefined)
165
- out.provider = provider;
166
- return out;
167
- });
168
- const dataShowMetaInput = () => defineSchema((value) => ({
169
- id: reqStr(reqObj(value), 'id'),
170
- }));
171
- const dataVideoInput = () => defineSchema((value) => {
172
- if (value === undefined || value === null)
173
- return {};
174
- const obj = reqObj(value);
175
- const out = {};
176
- const showId = optStr(obj, 'showId');
177
- if (showId !== undefined)
178
- out.showId = showId;
179
- const episodeNumber = optStr(obj, 'episodeNumber');
180
- if (episodeNumber !== undefined)
181
- out.episodeNumber = episodeNumber;
182
- const mode = optStr(obj, 'mode');
183
- if (mode === 'sub' || mode === 'dub')
184
- out.mode = mode;
185
- const provider = optStr(obj, 'provider');
186
- if (provider !== undefined)
187
- out.provider = provider;
188
- return out;
189
- });
190
- const dataMatureSearchInput = () => defineSchema((value) => {
191
- const obj = reqObj(value);
192
- const out = {};
193
- for (const k of [
194
- 'provider',
195
- 'query',
196
- 'type',
197
- 'status',
198
- 'season',
199
- 'country',
200
- 'genres',
201
- 'genre',
202
- 'excludeGenres',
203
- 'sortBy',
204
- 'order',
205
- 'studio',
206
- 'blacklist',
207
- ]) {
208
- const v = optStr(obj, k);
209
- if (v !== undefined)
210
- out[k] = v;
211
- }
212
- if (obj.page !== undefined)
213
- out.page = readCount(obj, 'page', 1);
214
- if (obj.limit !== undefined)
215
- out.limit = readCount(obj, 'limit', 14);
216
- const year = optNum(obj, 'year');
217
- if (year !== undefined)
218
- out.year = year;
219
- const minScore = optNum(obj, 'minScore');
220
- if (minScore !== undefined)
221
- out.minScore = minScore;
222
- const minEpisodes = optNum(obj, 'minEpisodes');
223
- if (minEpisodes !== undefined)
224
- out.minEpisodes = minEpisodes;
225
- return out;
226
- });
227
- const dataMatureAllocateInput = () => defineSchema((value) => {
228
- const obj = reqObj(value);
229
- const out = {};
230
- const provider = optStr(obj, 'provider');
231
- if (provider !== undefined)
232
- out.provider = provider;
233
- const nativeId = optStr(obj, 'nativeId');
234
- if (nativeId !== undefined)
235
- out.nativeId = nativeId;
236
- const title = optStr(obj, 'title');
237
- if (title !== undefined)
238
- out.title = title;
239
- const thumbnail = optStr(obj, 'thumbnail');
240
- if (thumbnail !== undefined)
241
- out.thumbnail = thumbnail;
242
- return out;
243
- });
244
- function getProvider(ctx, queryProvider) {
245
- const providerName = queryProvider?.toLowerCase();
246
- if (!providerName)
247
- return null;
248
- return ctx.getProviders()[providerName] || null;
249
- }
250
- function isMatureStreamingProvider(ctx, provider) {
251
- const key = (provider || '').toLowerCase();
252
- const providers = ctx.getProviders();
253
- if (!key || !providers[key] || key === 'mal' || key === 'anilist')
254
- return false;
255
- const mature = matureStreamingIds(ctx);
256
- return mature ? mature.has(key) : true;
257
- }
258
- function matureStreamingIds(ctx) {
259
- const catalog = ctx.getCatalog();
260
- if (!catalog || catalog.length === 0)
261
- return null;
262
- return new Set(catalog
263
- .filter((c) => c.mature && (c.kind ?? 'anime') === 'anime' && c.loaded)
264
- .map((c) => c.id.toLowerCase()));
265
- }
266
- async function resolveMatureStreamingId(ctx, title, wanted) {
267
- const providers = ctx.getProviders();
268
- const mature = matureStreamingIds(ctx);
269
- const pool = mature ? [...mature] : Object.keys(providers);
270
- const names = [wanted, ...pool].filter((p) => !!p && p !== 'mal' && p !== 'anilist' && !!providers[p]);
271
- const seen = new Set();
272
- for (const name of names) {
273
- if (seen.has(name))
274
- continue;
275
- seen.add(name);
276
- try {
277
- const resolved = await providers[name].resolveShowId?.(title);
278
- if (resolved)
279
- return { provider: providers[name], nativeId: resolved };
280
- }
281
- catch {
282
- // ignore
283
- }
284
- }
285
- return null;
286
- }
287
- function parseListParam(value) {
288
- if (typeof value !== 'string' || !value.trim())
289
- return undefined;
290
- const list = value
291
- .split(',')
292
- .map((v) => v.trim())
293
- .filter(Boolean);
294
- return list.length > 0 ? list : undefined;
295
- }
296
- export const dataRouter = router({
297
- schedule: protectedProcedure.input(dataScheduleInput()).query(async ({ ctx, input }) => {
298
- const date = input.date;
299
- const format = input.format || 'TV';
300
- return cached(ctx, `schedule-${date}-${format}`, 1800, defaultValidate, async () => {
301
- try {
302
- const dateObj = new Date(date + 'T00:00:00.000Z');
303
- const adult = format === 'ADULT';
304
- return await getSchedule(dateObj, adult ? undefined : format, adult);
305
- }
306
- catch (e) {
307
- logger.error({ err: e, date }, 'Schedule fetch failed');
308
- return [];
309
- }
310
- });
311
- }),
312
- latestReleases: protectedProcedure
313
- .input(dataLatestReleasesInput())
314
- .query(async ({ ctx, input }) => {
315
- const formatStr = input.format || 'TV';
316
- const pageNum = input.page || 1;
317
- const sizeNum = input.size || 12;
318
- return cached(ctx, `latest-releases-${formatStr}-${pageNum}-${sizeNum}`, 300, defaultValidate, async () => {
319
- try {
320
- return await getLatestReleases(formatStr, pageNum, sizeNum);
321
- }
322
- catch (e) {
323
- logger.error({ err: e }, 'Latest releases fetch failed');
324
- return [];
325
- }
326
- });
327
- }),
328
- search: protectedProcedure.input(dataSearchInput()).query(async ({ ctx, input }) => {
329
- const queryEntries = {};
330
- if (input.query !== undefined)
331
- queryEntries.query = input.query;
332
- if (input.page !== undefined)
333
- queryEntries.page = String(input.page);
334
- if (input.limit !== undefined)
335
- queryEntries.limit = String(input.limit);
336
- if (input.sortBy !== undefined)
337
- queryEntries.sortBy = input.sortBy;
338
- if (input.type !== undefined)
339
- queryEntries.type = input.type;
340
- if (input.status !== undefined)
341
- queryEntries.status = input.status;
342
- if (input.season !== undefined)
343
- queryEntries.season = input.season;
344
- if (input.year !== undefined)
345
- queryEntries.year = String(input.year);
346
- if (input.country !== undefined)
347
- queryEntries.country = input.country;
348
- if (input.genres !== undefined)
349
- queryEntries.genres = input.genres;
350
- if (input.excludeGenres !== undefined)
351
- queryEntries.excludeGenres = input.excludeGenres;
352
- if (input.excludeTags !== undefined)
353
- queryEntries.excludeTags = input.excludeTags;
354
- if (input.minScore !== undefined)
355
- queryEntries.minScore = String(input.minScore);
356
- if (input.minEpisodes !== undefined)
357
- queryEntries.minEpisodes = String(input.minEpisodes);
358
- if (input.adult !== undefined)
359
- queryEntries.adult = input.adult;
360
- return cached(ctx, `search-${JSON.stringify(queryEntries)}`, 1800, defaultValidate, async () => {
361
- try {
362
- return await searchAnilist({
363
- query: input.query || '',
364
- page: input.page || 1,
365
- perPage: input.limit || 14,
366
- format: input.type,
367
- status: input.status,
368
- season: input.season,
369
- seasonYear: input.year,
370
- countryOfOrigin: input.country,
371
- genre: input.genres,
372
- genre_not_in: parseListParam(input.excludeGenres),
373
- tag_not_in: parseListParam(input.excludeTags),
374
- averageScore_greater: input.minScore,
375
- episodes_greater: input.minEpisodes,
376
- isAdult: input.adult === 'true' ? true : input.adult === 'false' ? false : undefined,
377
- sort: input.sortBy,
378
- });
379
- }
380
- catch (e) {
381
- logger.error({ err: e }, 'search failed');
382
- return [];
383
- }
384
- });
385
- }),
386
- matureResolve: protectedProcedure
387
- .input(dataMatureResolveInput())
388
- .query(async ({ ctx, input }) => {
389
- return cached(ctx, `mature-resolve-${input.title || ''}`, 3600, defaultValidate, async () => {
390
- try {
391
- const title = (input.title || '').trim();
392
- if (!title)
393
- throw badRequest('title is required');
394
- const anilistResult = await searchAnilistByTitle(title);
395
- if (anilistResult) {
396
- const id = anilistResult.id;
397
- return { id: typeof id === 'number' && id < 0 ? `mal-${Math.abs(id)}` : id };
398
- }
399
- const malResult = await malSearchTitle(malCacheStore(), title);
400
- if (malResult)
401
- return { id: malResult, provider: 'mal' };
402
- throw new TRPCError({ code: 'NOT_FOUND', message: 'No match found' });
403
- }
404
- catch (e) {
405
- if (e instanceof TRPCError)
406
- throw e;
407
- logger.error({ err: e }, 'mature resolve failed');
408
- throw failed('Resolve failed');
409
- }
410
- });
411
- }),
412
- skipTimes: protectedProcedure.input(dataSkipTimesInput()).query(async ({ input }) => {
413
- try {
414
- const showId = input.showId || '';
415
- const episodeNumber = input.episodeNumber || '';
416
- if (/^\d+$/.test(showId)) {
417
- const skipRes = await fetch(`https://api.aniskip.com/v1/skip-times/${showId}/${episodeNumber}?types=op&types=ed`);
418
- if (skipRes.ok) {
419
- return await parseJsonBody(skipRes);
420
- }
421
- }
422
- return { found: false, results: [] };
423
- }
424
- catch {
425
- return { found: false, results: [] };
426
- }
427
- }),
428
- seasonal: protectedProcedure.input(dataSeasonalInput()).query(async ({ ctx, input }) => {
429
- const pageNum = input.page || 1;
430
- const sizeNum = input.size || 14;
431
- const formatStr = input.format;
432
- return cached(ctx, `seasonal-${formatStr || 'ALL'}-${pageNum}-${sizeNum}`, 300, defaultValidate, async () => {
433
- try {
434
- return await getSeasonal(pageNum, sizeNum, formatStr);
435
- }
436
- catch (e) {
437
- logger.error({ err: e }, 'Seasonal fetch failed');
438
- return [];
439
- }
440
- });
441
- }),
442
- popularList: protectedProcedure.input(dataPopularListInput()).query(async ({ ctx, input }) => {
443
- const sort = input.sort === 'POPULARITY_DESC' ? 'POPULARITY_DESC' : 'TRENDING_DESC';
444
- const pageNum = input.page || 1;
445
- const sizeNum = input.size || 20;
446
- return cached(ctx, `popular-list-${input.sort || 'TRENDING_DESC'}-${pageNum}-${sizeNum}`, 300, defaultValidate, async () => {
447
- try {
448
- return await getTrending(pageNum, sizeNum, sort);
449
- }
450
- catch (e) {
451
- logger.error({ err: e }, 'Popular list fetch failed');
452
- return [];
453
- }
454
- });
455
- }),
456
- trending: protectedProcedure.query(async ({ ctx }) => {
457
- return cached(ctx, 'trending', 300, defaultValidate, async () => {
458
- try {
459
- return await getTrending(1, 20, 'TRENDING_DESC', 'RELEASING');
460
- }
461
- catch (e) {
462
- logger.error({ err: e }, 'Trending fetch failed');
463
- return [];
464
- }
465
- });
466
- }),
467
- spotlight: protectedProcedure.query(async ({ ctx }) => {
468
- return cached(ctx, 'spotlight', 300, defaultValidate, async () => {
469
- try {
470
- return await getSpotlightBanners(1, 20);
471
- }
472
- catch (e) {
473
- logger.error({ err: e }, 'Spotlight fetch failed');
474
- return [];
475
- }
476
- });
477
- }),
478
- home: protectedProcedure.input(dataHomeInput()).query(async ({ ctx, input }) => {
479
- const formatStr = input.format;
480
- return cached(ctx, `home-${formatStr || 'TV'}`, 300, defaultValidate, async () => {
481
- try {
482
- return await getBatchedHomeData(formatStr);
483
- }
484
- catch (e) {
485
- logger.error({ err: e }, 'Batched home fetch failed');
486
- return { trending: [], seasonal: [], spotlight: [] };
487
- }
488
- });
489
- }),
490
- genresAndTags: protectedProcedure.query(async () => {
491
- try {
492
- return await getGenreTagLists();
493
- }
494
- catch (e) {
495
- logger.error({ err: e }, 'genres-and-tags failed');
496
- throw failed('Failed to load genres');
497
- }
498
- }),
499
- anilistStatus: protectedProcedure.query(async () => {
500
- const available = !anilistUnavailable();
501
- if (!available) {
502
- checkAnilistStatus().catch(() => { });
503
- }
504
- return { available, wasDownAtBoot: wasAnilistDownAtBoot() };
505
- }),
506
- systemNotifications: protectedProcedure.query(async () => {
507
- const notifications = [];
508
- if (wasAnilistDownAtBoot() && anilistUnavailable()) {
509
- const fb = 'MAL + Kitsu';
510
- notifications.push({
511
- id: 'system-anilist-down',
512
- type: 'system',
513
- title: 'AniList API',
514
- message: `AniList metadata API is currently down. dango is experiencing degraded performance. ${fb} is being used as a fallback in the meantime. Some features may be limited until service is restored.`,
515
- icon: 'warning',
516
- createdAt: Date.now(),
517
- });
518
- }
519
- return notifications;
520
- }),
521
- episodes: protectedProcedure.input(dataEpisodesInput()).query(async ({ ctx, input }) => {
522
- const showIdParam = input.showId;
523
- return cached(ctx, `episodes-${showIdParam || ''}`, 3600, defaultValidate, async () => {
524
- const showIdRaw = showIdParam;
525
- if (!showIdRaw)
526
- return { episodes: [] };
527
- const db = ctx.db;
528
- const providers = ctx.getProviders();
529
- let showId = await getMigratedId(db, showIdRaw);
530
- const epsMalId = parseMalId(showId);
531
- if (epsMalId) {
532
- try {
533
- const malMeta = await getShowMetaByMalId(epsMalId);
534
- if (malMeta?.anilistId && malMeta.anilistId > 0)
535
- showId = String(malMeta.anilistId);
536
- }
537
- catch {
538
- // ignore
539
- }
540
- }
541
- if (isTempShowId(showId)) {
542
- try {
543
- const row = await TempShowIdsRepository.getById(db, showId);
544
- if (!row || !isMatureStreamingProvider(ctx, row.provider))
545
- return { episodes: [] };
546
- const wanted = String(input.provider || '').toLowerCase();
547
- const own = providers[row.provider];
548
- if (own && (!wanted || wanted === row.provider)) {
549
- const data = await own.getEpisodes(row.nativeId, input.mode);
550
- if (data?.episodes?.length)
551
- return data;
552
- return { episodes: [] };
553
- }
554
- const hit = await resolveMatureStreamingId(ctx, row.title, wanted);
555
- if (hit) {
556
- const data = await hit.provider.getEpisodes(hit.nativeId, input.mode);
557
- if (data?.episodes?.length)
558
- return data;
559
- }
560
- }
561
- catch (e) {
562
- if (e.message === 'AUTH_REQUIRED')
563
- throw e;
564
- }
565
- return { episodes: [] };
566
- }
567
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(showId)) {
568
- try {
569
- if (providers['animepahe']) {
570
- const data = await providers['animepahe'].getEpisodes(showId, input.mode);
571
- return data || { episodes: [] };
572
- }
573
- }
574
- catch (e) {
575
- if (e.message === 'AUTH_REQUIRED')
576
- throw e;
577
- return { episodes: [] };
578
- }
579
- }
580
- const isNumeric = /^(mal-\d+|-?\d+)$/i.test(showId);
581
- if (isNumeric) {
582
- let episodes = [];
583
- try {
584
- episodes = await getAnilistEpisodes(showId);
585
- }
586
- catch (e) {
587
- logger.error({ err: e, showId }, 'Episodes fetch failed');
588
- }
589
- if (episodes.length === 0) {
590
- const absId = Math.abs(parseInt(showId, 10));
591
- if (!Number.isFinite(absId))
592
- return { episodes };
593
- try {
594
- const d = await malAnimeDetail(malCacheStore(), absId);
595
- const total = d.detail?.episodes;
596
- if (total && total > 0 && total <= 500) {
597
- episodes = Array.from({ length: total }, (_, i) => (i + 1).toString());
598
- }
599
- }
600
- catch {
601
- // ignore
602
- }
603
- }
604
- if (episodes.length === 0) {
605
- const providerName = input.provider?.toLowerCase();
606
- const provider = providerName ? providers[providerName] : undefined;
607
- if (provider?.isDirectId?.(showId)) {
608
- try {
609
- const data = await provider.getEpisodes(showId, input.mode);
610
- if (data?.episodes?.length)
611
- return data;
612
- }
613
- catch (e) {
614
- if (e.message === 'AUTH_REQUIRED')
615
- throw e;
616
- }
617
- }
618
- }
619
- return { episodes };
620
- }
621
- const providerName = input.provider?.toLowerCase();
622
- const provider = providerName ? providers[providerName] : undefined;
623
- if (provider) {
624
- try {
625
- const data = await provider.getEpisodes(showId, input.mode);
626
- if (data?.episodes?.length)
627
- return data;
628
- }
629
- catch (e) {
630
- if (e.message === 'AUTH_REQUIRED')
631
- throw e;
632
- }
633
- }
634
- return { episodes: [] };
635
- });
636
- }),
637
- showMeta: protectedProcedure.input(dataShowMetaInput()).query(async ({ ctx, input }) => {
638
- const showIdRaw = input.id;
639
- return cached(ctx, `meta-${showIdRaw}`, 3600, (d) => !!d, async () => {
640
- const db = ctx.db;
641
- const id = await getMigratedId(db, showIdRaw);
642
- if (isTempShowId(id)) {
643
- const row = await TempShowIdsRepository.getById(db, id);
644
- if (!row || !isMatureStreamingProvider(ctx, row.provider))
645
- return {};
646
- return {
647
- _id: row.id,
648
- id: row.id,
649
- name: row.title,
650
- englishName: row.title,
651
- thumbnail: row.thumbnail || '',
652
- type: 'TV',
653
- isAdult: true,
654
- status: 'UNKNOWN',
655
- provider: row.provider,
656
- nativeId: row.nativeId,
657
- };
658
- }
659
- const isNumeric = /^(mal-\d+|-?\d+)$/i.test(id);
660
- if (isNumeric) {
661
- let meta = null;
662
- try {
663
- meta = await getShowMetaById(id);
664
- }
665
- catch (e) {
666
- logger.warn({ err: e, id }, 'AniList show-meta fetch failed, trying local cache');
667
- const localMeta = (await ShowsMetaRepository.getById(db, id));
668
- if (localMeta) {
669
- if (typeof localMeta.genres === 'string') {
670
- try {
671
- localMeta.genres = JSON.parse(localMeta.genres);
672
- }
673
- catch {
674
- localMeta.genres = [];
675
- }
676
- }
677
- return localMeta;
678
- }
679
- }
680
- if (meta) {
681
- const poster = meta.thumbnail?.trim() ? meta.thumbnail : undefined;
682
- const hasPoster = !!poster;
683
- try {
684
- await ShowsMetaRepository.upsert(db, {
685
- id,
686
- name: meta.name,
687
- thumbnail: poster,
688
- nativeName: meta.nativeName,
689
- englishName: meta.englishName,
690
- popularityScore: meta.score ?? (meta.averageScore != null ? meta.averageScore / 10 : undefined),
691
- genres: meta.genres
692
- ? JSON.stringify(meta.genres.map((g) => (typeof g === 'string' ? g : g?.name)).filter(Boolean))
693
- : undefined,
694
- status: meta.status,
695
- episodeCount: meta.episodeCount != null ? Number(meta.episodeCount) : undefined,
696
- type: meta.type,
697
- anilistId: meta.anilistId,
698
- });
699
- }
700
- catch {
701
- // ignore
702
- }
703
- if (hasPoster && poster) {
704
- const existingWatchlist = (await WatchlistRepository.getById(db, id));
705
- if (existingWatchlist && existingWatchlist.thumbnail !== poster) {
706
- try {
707
- await WatchlistRepository.updateThumbnail(db, id, poster);
708
- }
709
- catch {
710
- // ignore
711
- }
712
- }
713
- }
714
- }
715
- if (/^(mal-\d+|-\d+)$/i.test(id) && meta?.anilistId && meta.anilistId > 0) {
716
- try {
717
- dbRun(db, 'INSERT OR REPLACE INTO legacy_id_mapping (legacyId, numericId) VALUES (?, ?)', [id, String(meta.anilistId)]);
718
- }
719
- catch {
720
- // ignore
721
- }
722
- }
723
- return meta || {};
724
- }
725
- return {};
726
- });
727
- }),
728
- matureSearch: protectedProcedure.input(dataMatureSearchInput()).query(async ({ ctx, input }) => {
729
- const provider = (input.provider || 'anilist').toLowerCase();
730
- if (provider !== 'anilist' && provider !== 'mal' && !isMatureStreamingProvider(ctx, provider)) {
731
- throw badRequest('Unknown mature provider');
732
- }
733
- const queryEntries = {};
734
- for (const [k, v] of Object.entries(input)) {
735
- if (v !== undefined)
736
- queryEntries[k] = String(v);
737
- }
738
- return cached(ctx, `mature-search-${JSON.stringify(queryEntries)}`, 600, (d) => !!d && typeof d === 'object' && Array.isArray(d.data), async () => {
739
- try {
740
- const query = input.query || '';
741
- const page = input.page || 1;
742
- const limit = input.limit || 14;
743
- if (provider === 'anilist') {
744
- const result = await searchAnilist({
745
- query,
746
- page,
747
- perPage: limit,
748
- format: input.type,
749
- status: input.status,
750
- season: input.season,
751
- seasonYear: input.year,
752
- countryOfOrigin: input.country,
753
- genre: input.genres,
754
- genre_not_in: parseListParam(input.excludeGenres),
755
- isAdult: true,
756
- sort: input.sortBy,
757
- });
758
- return { data: result, hasMore: result.length >= limit };
759
- }
760
- if (isMatureStreamingProvider(ctx, provider)) {
761
- const streaming = ctx.getProviders()[provider];
762
- if (streaming?.browse) {
763
- const genre = input.genre || input.genres || undefined;
764
- const result = await streaming.browse({
765
- query,
766
- page,
767
- limit,
768
- pageSize: limit,
769
- genre,
770
- genres: input.genres || genre,
771
- order: input.order || input.sortBy || undefined,
772
- sort: input.sortBy || input.order || undefined,
773
- studio: input.studio || undefined,
774
- blacklist: input.blacklist || undefined,
775
- });
776
- return {
777
- data: result.shows,
778
- hasMore: result.hasMore,
779
- ...(result.total !== undefined ? { total: result.total } : {}),
780
- ...(result.genres ? { genres: result.genres } : {}),
781
- };
782
- }
783
- const shows = await streaming.search({ query });
784
- return { data: shows.map((s) => ({ ...s, isAdult: true })), hasMore: false };
785
- }
786
- if (provider === 'mal') {
787
- let result = [];
788
- try {
789
- result = await malSearchMedia(malCacheStore(), {
790
- query,
791
- page,
792
- perPage: 50,
793
- format: input.type,
794
- status: input.status,
795
- genre: input.genres || undefined,
796
- genre_not_in: parseListParam(input.excludeGenres),
797
- isAdult: true,
798
- sort: input.sortBy,
799
- averageScore_greater: input.minScore,
800
- episodes_greater: input.minEpisodes,
801
- });
802
- }
803
- catch (e) {
804
- logger.error({ err: e }, 'mal mature search failed');
805
- }
806
- const shows = result.map((m) => {
807
- const show = fromAnilistMedia(m);
808
- return { ...show, isAdult: true };
809
- });
810
- const sliced = shows.slice(0, limit);
811
- return { data: sliced, hasMore: shows.length >= limit };
812
- }
813
- return { data: [], hasMore: false };
814
- }
815
- catch (e) {
816
- logger.error({ err: e }, 'mature search failed');
817
- return { data: [], hasMore: false };
818
- }
819
- });
820
- }),
821
- matureFilters: protectedProcedure.query(async ({ ctx }) => {
822
- try {
823
- const catalog = ctx.getCatalog() ?? [];
824
- const providers = {};
825
- for (const item of catalog) {
826
- if (!item.mature || (item.kind ?? 'anime') !== 'anime' || !item.loaded)
827
- continue;
828
- providers[item.id] = {
829
- label: item.label,
830
- ...(item.browse ? { browse: item.browse } : {}),
831
- ...(item.facets?.genres?.length ? { genres: item.facets.genres } : {}),
832
- ...(item.facets?.orders?.length ? { orders: item.facets.orders } : {}),
833
- };
834
- }
835
- return { providers };
836
- }
837
- catch (e) {
838
- logger.error({ err: e }, 'mature filters failed');
839
- throw failed('Failed to load filters');
840
- }
841
- }),
842
- matureAllocate: protectedProcedure
843
- .input(dataMatureAllocateInput())
844
- .mutation(async ({ ctx, input }) => {
845
- try {
846
- const provider = String(input.provider || '').toLowerCase();
847
- const nativeId = String(input.nativeId || '').trim();
848
- const title = String(input.title || '').trim();
849
- const thumbnail = String(input.thumbnail || '');
850
- if (!provider || !isMatureStreamingProvider(ctx, provider)) {
851
- throw badRequest('Unknown provider');
852
- }
853
- if (!nativeId || !title) {
854
- throw badRequest('nativeId and title are required');
855
- }
856
- const row = await TempShowIdsRepository.allocate(ctx.db, {
857
- provider,
858
- nativeId,
859
- title,
860
- thumbnail,
861
- });
862
- return { id: row.id };
863
- }
864
- catch (e) {
865
- if (e instanceof TRPCError)
866
- throw e;
867
- logger.error({ err: e }, 'temp show allocate failed');
868
- throw failed('Allocate failed');
869
- }
870
- }),
871
- video: protectedProcedure.input(dataVideoInput()).query(async ({ ctx, input }) => {
872
- const db = ctx.db;
873
- const providers = ctx.getProviders();
874
- try {
875
- let showId = input.showId;
876
- const videoMalId = parseMalId(showId);
877
- if (videoMalId) {
878
- try {
879
- const malMeta = await getShowMetaByMalId(videoMalId);
880
- if (malMeta?.anilistId && malMeta.anilistId > 0)
881
- showId = String(malMeta.anilistId);
882
- }
883
- catch {
884
- // ignore
885
- }
886
- }
887
- if (isTempShowId(showId)) {
888
- try {
889
- const row = await TempShowIdsRepository.getById(db, showId);
890
- if (!row || !isMatureStreamingProvider(ctx, row.provider))
891
- return [];
892
- const wanted = String(input.provider || '').toLowerCase();
893
- const own = providers[row.provider];
894
- if (own && (!wanted || wanted === row.provider)) {
895
- const urls = await own.getStreamUrls(row.nativeId, input.episodeNumber, input.mode);
896
- return urls || [];
897
- }
898
- const hit = await resolveMatureStreamingId(ctx, row.title, wanted);
899
- if (hit) {
900
- const urls = await hit.provider.getStreamUrls(hit.nativeId, input.episodeNumber, input.mode);
901
- return urls || [];
902
- }
903
- }
904
- catch (e) {
905
- logger.error({ err: e, showId }, 'Temp show video fetch failed');
906
- }
907
- return [];
908
- }
909
- const providerName = input.provider;
910
- const providerKey = providerName?.toLowerCase();
911
- const stillMal = parseMalId(showId) !== null;
912
- const isNativeId = !!(providerKey && providers[providerKey]?.isDirectId?.(showId));
913
- const needsResolution = !isNativeId && (/^\d+$/.test(showId) || stillMal);
914
- if (providerKey && needsResolution) {
915
- const meta = (await ShowsMetaRepository.getById(db, showId));
916
- let targetTitle = meta?.englishName || meta?.name;
917
- let anilistShow = null;
918
- if (!targetTitle) {
919
- try {
920
- anilistShow = await getShowMetaById(showId);
921
- targetTitle = anilistShow?.englishName || anilistShow?.name;
922
- if (anilistShow && targetTitle) {
923
- await ShowsMetaRepository.upsert(db, {
924
- id: showId,
925
- name: anilistShow.name,
926
- thumbnail: anilistShow.thumbnail,
927
- nativeName: anilistShow.nativeName,
928
- englishName: anilistShow.englishName,
929
- genres: anilistShow.genres
930
- ? JSON.stringify(anilistShow.genres.map((genre) => genre.name))
931
- : undefined,
932
- status: anilistShow.status,
933
- episodeCount: anilistShow.episodeCount != null ? Number(anilistShow.episodeCount) : undefined,
934
- type: anilistShow.type,
935
- anilistId: anilistShow.anilistId,
936
- });
937
- }
938
- }
939
- catch (err) {
940
- logger.warn({ err, provider: providerKey, showId }, '[Video] AniList metadata lookup failed while resolving numeric showId');
941
- }
942
- }
943
- if (targetTitle) {
944
- let romaji = anilistShow?.names?.romaji;
945
- let nativeName = anilistShow?.names?.native;
946
- let synonyms = anilistShow?.names?.synonyms;
947
- if (!romaji || !nativeName) {
948
- try {
949
- const show = await getShowMetaById(showId);
950
- romaji = romaji || show?.names?.romaji;
951
- nativeName = nativeName || show?.names?.native;
952
- synonyms = synonyms || show?.names?.synonyms;
953
- }
954
- catch {
955
- // ignore
956
- }
957
- }
958
- const titleTargets = [
959
- targetTitle,
960
- romaji,
961
- nativeName,
962
- ...(synonyms ?? []).slice(0, 2),
963
- ].filter((t) => !!t && t.trim().length > 0);
964
- const distinctTargets = [...new Set(titleTargets.map((t) => t.trim()))].filter((t, i, a) => a.findIndex((x) => x.toLowerCase() === t.toLowerCase()) === i);
965
- let resolved = null;
966
- for (const variant of distinctTargets) {
967
- try {
968
- resolved =
969
- (await providers[providerKey]?.resolveShowId?.(variant, romaji, input.mode)) ?? null;
970
- }
971
- catch (err) {
972
- if (err.message === 'AUTH_REQUIRED')
973
- throw err;
974
- resolved = null;
975
- }
976
- if (resolved)
977
- break;
978
- }
979
- if (resolved) {
980
- showId = resolved;
981
- }
982
- else {
983
- logger.warn({ provider: providerKey, showId, title: targetTitle, romaji }, '[Video] resolveShowId failed, attempting fallback provider search');
984
- try {
985
- const fallbackResults = await providers[providerKey]?.search?.({ query: targetTitle });
986
- const fallbackMatch = pickBestMatch((fallbackResults || []).map((r) => ({
987
- title: r.name || r.englishName || '',
988
- id: r.id || r._id || '',
989
- })), distinctTargets);
990
- if (fallbackMatch) {
991
- showId = fallbackMatch.item.id;
992
- }
993
- else {
994
- logger.warn({ provider: providerKey, showId, title: targetTitle }, '[Video] fallback provider search returned no results');
995
- return [];
996
- }
997
- }
998
- catch (fallbackErr) {
999
- if (fallbackErr.message === 'AUTH_REQUIRED') {
1000
- throw fallbackErr;
1001
- }
1002
- logger.error({ err: fallbackErr, provider: providerKey, showId, title: targetTitle }, '[Video] fallback provider search failed');
1003
- return [];
1004
- }
1005
- }
1006
- }
1007
- else {
1008
- logger.warn({ provider: providerKey, showId }, '[Video] numeric showId passed to provider but local meta missing title');
1009
- return [];
1010
- }
1011
- }
1012
- const provider = getProvider(ctx, input.provider);
1013
- if (!provider)
1014
- return [];
1015
- const urls = await provider.getStreamUrls(showId, input.episodeNumber, input.mode);
1016
- return urls || [];
1017
- }
1018
- catch (e) {
1019
- if (e.message === 'AUTH_REQUIRED') {
1020
- throw authRequiredError(e.provider || input.provider || 'animepahe');
1021
- }
1022
- logger.error({ err: e, provider: input.provider }, 'Provider video fetch failed');
1023
- return [];
1024
- }
1025
- }),
1026
- });
1
+ import{TRPCError as $}from"@trpc/server";import{protectedProcedure as h,router as re}from"../index.js";import{badRequest as L,defineSchema as b,failed as P,optNum as M,optStr as l,readCount as N,reqObj as S,reqStr as V}from"../validation.js";import{parseJsonBody as se}from"../../utils/http.utils.js";import{getTrending as H,getLatestReleases as ne,getSeasonal as ae,getSchedule as ie,searchAnilist as Q,searchAnilistByTitle as oe,getSpotlightBanners as de,getBatchedHomeData as ue,getGenreTagLists as ce,anilistUnavailable as J,wasAnilistDownAtBoot as F,checkAnilistStatus as le,getShowMetaById as U,getAnilistEpisodes as fe,parseMalId as j,getShowMetaByMalId as Y,fromAnilistMedia as me}from"../../lib/anilist.js";import{malAnimeDetail as ge,malSearchMedia as pe,malSearchTitle as he}from"../../lib/mal.js";import{malCacheStore as O}from"../../repositories/mal-cache.repository.js";import{getMigratedId as K}from"../../lib/migration.js";import{isTempShowId as z}from"../../lib/temp-ids.js";import{TempShowIdsRepository as k}from"../../repositories/temp-show-ids.repository.js";import{pickBestMatch as ye}from"../../providers/title-matching.js";import{authRequiredError as we}from"../errors.js";import{ShowsMetaRepository as B}from"../../repositories/shows-meta.repository.js";import{WatchlistRepository as W}from"../../repositories/watchlist.repository.js";import{dbRun as Ie}from"../../utils/db-utils.js";import g from"../../logger.js";async function D(r,e,t,s,n){const i=r.apiCache.get(e);if(i)return i;const a=await n();return s(a)&&(t!==void 0?r.apiCache.set(e,a,t):r.apiCache.set(e,a)),a}const E=r=>Array.isArray(r)&&r.length>0,ve=()=>b(r=>{const e=S(r),t={date:V(e,"date")},s=l(e,"format");return s!==void 0&&(t.format=s),t}),be=()=>b(r=>{const e=S(r),t={},s=l(e,"format");return s!==void 0&&(t.format=s),e.page!==void 0&&(t.page=N(e,"page",1)),e.size!==void 0&&(t.size=N(e,"size",12)),t}),Se=()=>b(r=>{const e=S(r),t={},s=l(e,"query");s!==void 0&&(t.query=s),e.page!==void 0&&(t.page=N(e,"page",1)),e.limit!==void 0&&(t.limit=N(e,"limit",14));const n=l(e,"sortBy");n!==void 0&&(t.sortBy=n);const i=l(e,"type");i!==void 0&&(t.type=i);const a=l(e,"status");a!==void 0&&(t.status=a);const o=l(e,"season");o!==void 0&&(t.season=o);const f=M(e,"year");f!==void 0&&(t.year=f);const p=l(e,"country");p!==void 0&&(t.country=p);const y=l(e,"genres");y!==void 0&&(t.genres=y);const d=l(e,"excludeGenres");d!==void 0&&(t.excludeGenres=d);const v=l(e,"excludeTags");v!==void 0&&(t.excludeTags=v);const m=M(e,"minScore");m!==void 0&&(t.minScore=m);const u=M(e,"minEpisodes");u!==void 0&&(t.minEpisodes=u);const c=l(e,"adult");return c!==void 0&&(t.adult=c),t}),De=()=>b(r=>{const e=S(r),t={},s=l(e,"title");return s!==void 0&&(t.title=s),t}),Ne=()=>b(r=>{const e=S(r),t={},s=l(e,"showId");s!==void 0&&(t.showId=s);const n=l(e,"episodeNumber");return n!==void 0&&(t.episodeNumber=n),t}),Ee=()=>b(r=>{const e=S(r),t={};e.page!==void 0&&(t.page=N(e,"page",1)),e.size!==void 0&&(t.size=N(e,"size",14));const s=l(e,"format");return s!==void 0&&(t.format=s),t}),Te=()=>b(r=>{const e=S(r),t={},s=l(e,"sort");return s!==void 0&&(t.sort=s),e.page!==void 0&&(t.page=N(e,"page",1)),e.size!==void 0&&(t.size=N(e,"size",20)),t}),Re=()=>b(r=>{if(r==null)return{};const e=S(r),t={},s=l(e,"format");return s!==void 0&&(t.format=s),t}),Ae=()=>b(r=>{if(r==null)return{};const e=S(r),t={},s=l(e,"showId");s!==void 0&&(t.showId=s);const n=l(e,"mode");(n==="sub"||n==="dub")&&(t.mode=n);const i=l(e,"provider");return i!==void 0&&(t.provider=i),t}),Me=()=>b(r=>({id:V(S(r),"id")})),qe=()=>b(r=>{if(r==null)return{};const e=S(r),t={},s=l(e,"showId");s!==void 0&&(t.showId=s);const n=l(e,"episodeNumber");n!==void 0&&(t.episodeNumber=n);const i=l(e,"mode");(i==="sub"||i==="dub")&&(t.mode=i);const a=l(e,"provider");return a!==void 0&&(t.provider=a),t}),Ce=()=>b(r=>{const e=S(r),t={};for(const a of["provider","query","type","status","season","country","genres","genre","excludeGenres","sortBy","order","studio","blacklist"]){const o=l(e,a);o!==void 0&&(t[a]=o)}e.page!==void 0&&(t.page=N(e,"page",1)),e.limit!==void 0&&(t.limit=N(e,"limit",14));const s=M(e,"year");s!==void 0&&(t.year=s);const n=M(e,"minScore");n!==void 0&&(t.minScore=n);const i=M(e,"minEpisodes");return i!==void 0&&(t.minEpisodes=i),t}),Le=()=>b(r=>{const e=S(r),t={},s=l(e,"provider");s!==void 0&&(t.provider=s);const n=l(e,"nativeId");n!==void 0&&(t.nativeId=n);const i=l(e,"title");i!==void 0&&(t.title=i);const a=l(e,"thumbnail");return a!==void 0&&(t.thumbnail=a),t});function Pe(r,e){const t=e?.toLowerCase();return t&&r.getProviders()[t]||null}function q(r,e){const t=(e||"").toLowerCase(),s=r.getProviders();if(!t||!s[t]||t==="mal"||t==="anilist")return!1;const n=Z(r);return n?n.has(t):!0}function Z(r){const e=r.getCatalog();return!e||e.length===0?null:new Set(e.filter(t=>t.mature&&(t.kind??"anime")==="anime"&&t.loaded).map(t=>t.id.toLowerCase()))}async function X(r,e,t){const s=r.getProviders(),n=Z(r),i=n?[...n]:Object.keys(s),a=[t,...i].filter(f=>!!f&&f!=="mal"&&f!=="anilist"&&!!s[f]),o=new Set;for(const f of a)if(!o.has(f)){o.add(f);try{const p=await s[f].resolveShowId?.(e);if(p)return{provider:s[f],nativeId:p}}catch{}}return null}function _(r){if(typeof r!="string"||!r.trim())return;const e=r.split(",").map(t=>t.trim()).filter(Boolean);return e.length>0?e:void 0}const Ke=re({schedule:h.input(ve()).query(async({ctx:r,input:e})=>{const t=e.date,s=e.format||"TV";return D(r,`schedule-${t}-${s}`,1800,E,async()=>{try{const n=new Date(t+"T00:00:00.000Z"),i=s==="ADULT";return await ie(n,i?void 0:s,i)}catch(n){return g.error({err:n,date:t},"Schedule fetch failed"),[]}})}),latestReleases:h.input(be()).query(async({ctx:r,input:e})=>{const t=e.format||"TV",s=e.page||1,n=e.size||12;return D(r,`latest-releases-${t}-${s}-${n}`,300,E,async()=>{try{return await ne(t,s,n)}catch(i){return g.error({err:i},"Latest releases fetch failed"),[]}})}),search:h.input(Se()).query(async({ctx:r,input:e})=>{const t={};return e.query!==void 0&&(t.query=e.query),e.page!==void 0&&(t.page=String(e.page)),e.limit!==void 0&&(t.limit=String(e.limit)),e.sortBy!==void 0&&(t.sortBy=e.sortBy),e.type!==void 0&&(t.type=e.type),e.status!==void 0&&(t.status=e.status),e.season!==void 0&&(t.season=e.season),e.year!==void 0&&(t.year=String(e.year)),e.country!==void 0&&(t.country=e.country),e.genres!==void 0&&(t.genres=e.genres),e.excludeGenres!==void 0&&(t.excludeGenres=e.excludeGenres),e.excludeTags!==void 0&&(t.excludeTags=e.excludeTags),e.minScore!==void 0&&(t.minScore=String(e.minScore)),e.minEpisodes!==void 0&&(t.minEpisodes=String(e.minEpisodes)),e.adult!==void 0&&(t.adult=e.adult),D(r,`search-${JSON.stringify(t)}`,1800,E,async()=>{try{return await Q({query:e.query||"",page:e.page||1,perPage:e.limit||14,format:e.type,status:e.status,season:e.season,seasonYear:e.year,countryOfOrigin:e.country,genre:e.genres,genre_not_in:_(e.excludeGenres),tag_not_in:_(e.excludeTags),averageScore_greater:e.minScore,episodes_greater:e.minEpisodes,isAdult:e.adult==="true"?!0:e.adult==="false"?!1:void 0,sort:e.sortBy})}catch(s){return g.error({err:s},"search failed"),[]}})}),matureResolve:h.input(De()).query(async({ctx:r,input:e})=>D(r,`mature-resolve-${e.title||""}`,3600,E,async()=>{try{const t=(e.title||"").trim();if(!t)throw L("title is required");const s=await oe(t);if(s){const i=s.id;return{id:typeof i=="number"&&i<0?`mal-${Math.abs(i)}`:i}}const n=await he(O(),t);if(n)return{id:n,provider:"mal"};throw new $({code:"NOT_FOUND",message:"No match found"})}catch(t){throw t instanceof $?t:(g.error({err:t},"mature resolve failed"),P("Resolve failed"))}})),skipTimes:h.input(Ne()).query(async({input:r})=>{try{const e=r.showId||"",t=r.episodeNumber||"";if(/^\d+$/.test(e)){const s=await fetch(`https://api.aniskip.com/v1/skip-times/${e}/${t}?types=op&types=ed`);if(s.ok)return await se(s)}return{found:!1,results:[]}}catch{return{found:!1,results:[]}}}),seasonal:h.input(Ee()).query(async({ctx:r,input:e})=>{const t=e.page||1,s=e.size||14,n=e.format;return D(r,`seasonal-${n||"ALL"}-${t}-${s}`,300,E,async()=>{try{return await ae(t,s,n)}catch(i){return g.error({err:i},"Seasonal fetch failed"),[]}})}),popularList:h.input(Te()).query(async({ctx:r,input:e})=>{const t=e.sort==="POPULARITY_DESC"?"POPULARITY_DESC":"TRENDING_DESC",s=e.page||1,n=e.size||20;return D(r,`popular-list-${e.sort||"TRENDING_DESC"}-${s}-${n}`,300,E,async()=>{try{return await H(s,n,t)}catch(i){return g.error({err:i},"Popular list fetch failed"),[]}})}),trending:h.query(async({ctx:r})=>D(r,"trending",300,E,async()=>{try{return await H(1,20,"TRENDING_DESC","RELEASING")}catch(e){return g.error({err:e},"Trending fetch failed"),[]}})),spotlight:h.query(async({ctx:r})=>D(r,"spotlight",300,E,async()=>{try{return await de(1,20)}catch(e){return g.error({err:e},"Spotlight fetch failed"),[]}})),home:h.input(Re()).query(async({ctx:r,input:e})=>{const t=e.format;return D(r,`home-${t||"TV"}`,300,E,async()=>{try{return await ue(t)}catch(s){return g.error({err:s},"Batched home fetch failed"),{trending:[],seasonal:[],spotlight:[]}}})}),genresAndTags:h.query(async()=>{try{return await ce()}catch(r){throw g.error({err:r},"genres-and-tags failed"),P("Failed to load genres")}}),anilistStatus:h.query(async()=>{const r=!J();return r||le().catch(()=>{}),{available:r,wasDownAtBoot:F()}}),systemNotifications:h.query(async()=>{const r=[];return F()&&J()&&r.push({id:"system-anilist-down",type:"system",title:"AniList API",message:"AniList metadata API is currently down. dango is experiencing degraded performance. MAL + Kitsu is being used as a fallback in the meantime. Some features may be limited until service is restored.",icon:"warning",createdAt:Date.now()}),r}),episodes:h.input(Ae()).query(async({ctx:r,input:e})=>{const t=e.showId;return D(r,`episodes-${t||""}`,3600,E,async()=>{const s=t;if(!s)return{episodes:[]};const n=r.db,i=r.getProviders();let a=await K(n,s);const o=j(a);if(o)try{const d=await Y(o);d?.anilistId&&d.anilistId>0&&(a=String(d.anilistId))}catch{}if(z(a)){try{const d=await k.getById(n,a);if(!d||!q(r,d.provider))return{episodes:[]};const v=String(e.provider||"").toLowerCase(),m=i[d.provider];if(m&&(!v||v===d.provider)){const c=await m.getEpisodes(d.nativeId,e.mode);return c?.episodes?.length?c:{episodes:[]}}const u=await X(r,d.title,v);if(u){const c=await u.provider.getEpisodes(u.nativeId,e.mode);if(c?.episodes?.length)return c}}catch(d){if(d.message==="AUTH_REQUIRED")throw d}return{episodes:[]}}if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(a))try{if(i.animepahe)return await i.animepahe.getEpisodes(a,e.mode)||{episodes:[]}}catch(d){if(d.message==="AUTH_REQUIRED")throw d;return{episodes:[]}}if(/^(mal-\d+|-?\d+)$/i.test(a)){let d=[];try{d=await fe(a)}catch(v){g.error({err:v,showId:a},"Episodes fetch failed")}if(d.length===0){const v=Math.abs(parseInt(a,10));if(!Number.isFinite(v))return{episodes:d};try{const u=(await ge(O(),v)).detail?.episodes;u&&u>0&&u<=500&&(d=Array.from({length:u},(c,I)=>(I+1).toString()))}catch{}}if(d.length===0){const v=e.provider?.toLowerCase(),m=v?i[v]:void 0;if(m?.isDirectId?.(a))try{const u=await m.getEpisodes(a,e.mode);if(u?.episodes?.length)return u}catch(u){if(u.message==="AUTH_REQUIRED")throw u}}return{episodes:d}}const p=e.provider?.toLowerCase(),y=p?i[p]:void 0;if(y)try{const d=await y.getEpisodes(a,e.mode);if(d?.episodes?.length)return d}catch(d){if(d.message==="AUTH_REQUIRED")throw d}return{episodes:[]}})}),showMeta:h.input(Me()).query(async({ctx:r,input:e})=>{const t=e.id;return D(r,`meta-${t}`,3600,s=>!!s,async()=>{const s=r.db,n=await K(s,t);if(z(n)){const a=await k.getById(s,n);return!a||!q(r,a.provider)?{}:{_id:a.id,id:a.id,name:a.title,englishName:a.title,thumbnail:a.thumbnail||"",type:"TV",isAdult:!0,status:"UNKNOWN",provider:a.provider,nativeId:a.nativeId}}if(/^(mal-\d+|-?\d+)$/i.test(n)){let a=null;try{a=await U(n)}catch(o){g.warn({err:o,id:n},"AniList show-meta fetch failed, trying local cache");const f=await B.getById(s,n);if(f){if(typeof f.genres=="string")try{f.genres=JSON.parse(f.genres)}catch{f.genres=[]}return f}}if(a){const o=a.thumbnail?.trim()?a.thumbnail:void 0,f=!!o;try{await B.upsert(s,{id:n,name:a.name,thumbnail:o,nativeName:a.nativeName,englishName:a.englishName,popularityScore:a.score??(a.averageScore!=null?a.averageScore/10:void 0),genres:a.genres?JSON.stringify(a.genres.map(p=>typeof p=="string"?p:p?.name).filter(Boolean)):void 0,status:a.status,episodeCount:a.episodeCount!=null?Number(a.episodeCount):void 0,type:a.type,anilistId:a.anilistId})}catch{}if(f&&o){const p=await W.getById(s,n);if(p&&p.thumbnail!==o)try{await W.updateThumbnail(s,n,o)}catch{}}}if(/^(mal-\d+|-\d+)$/i.test(n)&&a?.anilistId&&a.anilistId>0)try{Ie(s,"INSERT OR REPLACE INTO legacy_id_mapping (legacyId, numericId) VALUES (?, ?)",[n,String(a.anilistId)])}catch{}return a||{}}return{}})}),matureSearch:h.input(Ce()).query(async({ctx:r,input:e})=>{const t=(e.provider||"anilist").toLowerCase();if(t!=="anilist"&&t!=="mal"&&!q(r,t))throw L("Unknown mature provider");const s={};for(const[n,i]of Object.entries(e))i!==void 0&&(s[n]=String(i));return D(r,`mature-search-${JSON.stringify(s)}`,600,n=>!!n&&typeof n=="object"&&Array.isArray(n.data),async()=>{try{const n=e.query||"",i=e.page||1,a=e.limit||14;if(t==="anilist"){const o=await Q({query:n,page:i,perPage:a,format:e.type,status:e.status,season:e.season,seasonYear:e.year,countryOfOrigin:e.country,genre:e.genres,genre_not_in:_(e.excludeGenres),isAdult:!0,sort:e.sortBy});return{data:o,hasMore:o.length>=a}}if(q(r,t)){const o=r.getProviders()[t];if(o?.browse){const p=e.genre||e.genres||void 0,y=await o.browse({query:n,page:i,limit:a,pageSize:a,genre:p,genres:e.genres||p,order:e.order||e.sortBy||void 0,sort:e.sortBy||e.order||void 0,studio:e.studio||void 0,blacklist:e.blacklist||void 0});return{data:y.shows,hasMore:y.hasMore,...y.total!==void 0?{total:y.total}:{},...y.genres?{genres:y.genres}:{}}}return{data:(await o.search({query:n})).map(p=>({...p,isAdult:!0})),hasMore:!1}}if(t==="mal"){let o=[];try{o=await pe(O(),{query:n,page:i,perPage:50,format:e.type,status:e.status,genre:e.genres||void 0,genre_not_in:_(e.excludeGenres),isAdult:!0,sort:e.sortBy,averageScore_greater:e.minScore,episodes_greater:e.minEpisodes})}catch(y){g.error({err:y},"mal mature search failed")}const f=o.map(y=>({...me(y),isAdult:!0}));return{data:f.slice(0,a),hasMore:f.length>=a}}return{data:[],hasMore:!1}}catch(n){return g.error({err:n},"mature search failed"),{data:[],hasMore:!1}}})}),matureFilters:h.query(async({ctx:r})=>{try{const e=r.getCatalog()??[],t={};for(const s of e)!s.mature||(s.kind??"anime")!=="anime"||!s.loaded||(t[s.id]={label:s.label,...s.browse?{browse:s.browse}:{},...s.facets?.genres?.length?{genres:s.facets.genres}:{},...s.facets?.orders?.length?{orders:s.facets.orders}:{}});return{providers:t}}catch(e){throw g.error({err:e},"mature filters failed"),P("Failed to load filters")}}),matureAllocate:h.input(Le()).mutation(async({ctx:r,input:e})=>{try{const t=String(e.provider||"").toLowerCase(),s=String(e.nativeId||"").trim(),n=String(e.title||"").trim(),i=String(e.thumbnail||"");if(!t||!q(r,t))throw L("Unknown provider");if(!s||!n)throw L("nativeId and title are required");return{id:(await k.allocate(r.db,{provider:t,nativeId:s,title:n,thumbnail:i})).id}}catch(t){throw t instanceof $?t:(g.error({err:t},"temp show allocate failed"),P("Allocate failed"))}}),video:h.input(qe()).query(async({ctx:r,input:e})=>{const t=r.db,s=r.getProviders();try{let n=e.showId;const i=j(n);if(i)try{const m=await Y(i);m?.anilistId&&m.anilistId>0&&(n=String(m.anilistId))}catch{}if(z(n)){try{const m=await k.getById(t,n);if(!m||!q(r,m.provider))return[];const u=String(e.provider||"").toLowerCase(),c=s[m.provider];if(c&&(!u||u===m.provider))return await c.getStreamUrls(m.nativeId,e.episodeNumber,e.mode)||[];const I=await X(r,m.title,u);if(I)return await I.provider.getStreamUrls(I.nativeId,e.episodeNumber,e.mode)||[]}catch(m){g.error({err:m,showId:n},"Temp show video fetch failed")}return[]}const o=e.provider?.toLowerCase(),f=j(n)!==null,y=!!!(o&&s[o]?.isDirectId?.(n))&&(/^\d+$/.test(n)||f);if(o&&y){const m=await B.getById(t,n);let u=m?.englishName||m?.name,c=null;if(!u)try{c=await U(n),u=c?.englishName||c?.name,c&&u&&await B.upsert(t,{id:n,name:c.name,thumbnail:c.thumbnail,nativeName:c.nativeName,englishName:c.englishName,genres:c.genres?JSON.stringify(c.genres.map(I=>I.name)):void 0,status:c.status,episodeCount:c.episodeCount!=null?Number(c.episodeCount):void 0,type:c.type,anilistId:c.anilistId})}catch(I){g.warn({err:I,provider:o,showId:n},"[Video] AniList metadata lookup failed while resolving numeric showId")}if(u){let I=c?.names?.romaji,R=c?.names?.native,x=c?.names?.synonyms;if(!I||!R)try{const w=await U(n);I=I||w?.names?.romaji,R=R||w?.names?.native,x=x||w?.names?.synonyms}catch{}const ee=[u,I,R,...(x??[]).slice(0,2)].filter(w=>!!w&&w.trim().length>0),G=[...new Set(ee.map(w=>w.trim()))].filter((w,T,A)=>A.findIndex(te=>te.toLowerCase()===w.toLowerCase())===T);let C=null;for(const w of G){try{C=await s[o]?.resolveShowId?.(w,I,e.mode)??null}catch(T){if(T.message==="AUTH_REQUIRED")throw T;C=null}if(C)break}if(C)n=C;else{g.warn({provider:o,showId:n,title:u,romaji:I},"[Video] resolveShowId failed, attempting fallback provider search");try{const w=await s[o]?.search?.({query:u}),T=ye((w||[]).map(A=>({title:A.name||A.englishName||"",id:A.id||A._id||""})),G);if(T)n=T.item.id;else return g.warn({provider:o,showId:n,title:u},"[Video] fallback provider search returned no results"),[]}catch(w){if(w.message==="AUTH_REQUIRED")throw w;return g.error({err:w,provider:o,showId:n,title:u},"[Video] fallback provider search failed"),[]}}}else return g.warn({provider:o,showId:n},"[Video] numeric showId passed to provider but local meta missing title"),[]}const d=Pe(r,e.provider);return d?await d.getStreamUrls(n,e.episodeNumber,e.mode)||[]:[]}catch(n){if(n.message==="AUTH_REQUIRED")throw we(n.provider||e.provider||"animepahe");return g.error({err:n,provider:e.provider},"Provider video fetch failed"),[]}})});export{Ke as dataRouter};