@serifpersia/dango 3.6.7 → 3.6.9

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 (156) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DKDJK8Mp.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-CTcvfjva.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_UiacIN.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-DMSN2vxl.js} +2 -2
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-pwmK9_Eh.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-m7kFzg2A.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-C1wpPvu6.js} +1 -1
  11. package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-ChRCExhK.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DSHJPo2A.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-CzOVY1wA.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-CifQMfhs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CM10yB9V.js} +1 -1
  18. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-6Kovbsqw.js} +1 -1
  19. package/client/dist/assets/{Player-CsH4x_I_.js → Player-CK0hT-u4.js} +2 -2
  20. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-Bb66uqT9.js} +1 -1
  21. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
  22. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-DUK7URAE.js} +1 -1
  23. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-Ck9LJThU.js} +1 -1
  24. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BUphIv0k.js} +1 -1
  25. package/client/dist/assets/{Search-DFkhuk-x.js → Search-zJyP15h6.js} +1 -1
  26. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-BYdoSEUh.js} +1 -1
  27. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CqgbOhjX.js} +1 -1
  28. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-BVjyv6CM.js} +1 -1
  29. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-L13ylswR.js} +1 -1
  30. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-Cvcm5U2D.js} +1 -1
  31. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-DhxfKvYm.js} +1 -1
  32. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-P8bFE6_X.js} +1 -1
  33. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-D5gVPjtb.js} +1 -1
  34. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-Cjq8l3rU.js} +1 -1
  35. package/client/dist/assets/{hls-BGCX7mJy.js → hls-BkzuANAg.js} +1 -1
  36. package/client/dist/assets/{index-CnfZwsds.js → index-CLVgzKLk.js} +19 -19
  37. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-DZJZu-so.js} +1 -1
  38. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CvrMGHuo.js} +1 -1
  39. package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
  40. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary--MiN11uR.js} +1 -1
  41. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-CR5XhKKS.js} +1 -1
  42. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-DqdT4fA5.js} +1 -1
  43. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-BL0HMDyP.js} +1 -1
  44. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-D4U4iqN8.js} +1 -1
  45. package/client/dist/index.html +1 -1
  46. package/package.json +3 -5
  47. package/server/dist/config.js +1 -174
  48. package/server/dist/server.js +1068 -289
  49. package/server/dist/shipped-defaults.js +1 -6
  50. package/server/package.json +4 -5
  51. package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
  52. package/client/dist/assets/ListeningList-DguKjI_t.js +0 -1
  53. package/client/dist/assets/Music-BGH_0voi.css +0 -1
  54. package/client/dist/assets/Music-CQpyXTLH.js +0 -1
  55. package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
  56. package/client/dist/assets/Radio-DF6g6odd.js +0 -1
  57. package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
  58. package/client/dist/assets/useAsmrPopup-SymV1J-b.js +0 -1
  59. package/server/dist/app-auth.js +0 -157
  60. package/server/dist/app-hono.js +0 -598
  61. package/server/dist/db/drizzle.js +0 -16
  62. package/server/dist/db/schema-anime.js +0 -61
  63. package/server/dist/db/schema-asmr.js +0 -13
  64. package/server/dist/db/schema-manga.js +0 -31
  65. package/server/dist/db/schema-tv.js +0 -18
  66. package/server/dist/db.js +0 -217
  67. package/server/dist/discord-gateway.js +0 -385
  68. package/server/dist/discord-rpc.js +0 -413
  69. package/server/dist/github-sync.js +0 -508
  70. package/server/dist/google.js +0 -741
  71. package/server/dist/hono/asmr.js +0 -10
  72. package/server/dist/hono/auth.js +0 -49
  73. package/server/dist/hono/music.js +0 -209
  74. package/server/dist/hono/proxy.js +0 -827
  75. package/server/dist/hono/tv.js +0 -200
  76. package/server/dist/hono/watchlist.js +0 -341
  77. package/server/dist/lib/anilist.js +0 -1583
  78. package/server/dist/lib/anischedule.js +0 -191
  79. package/server/dist/lib/insights.js +0 -185
  80. package/server/dist/lib/ipc.js +0 -23
  81. package/server/dist/lib/kitsu.js +0 -517
  82. package/server/dist/lib/mal-import.js +0 -291
  83. package/server/dist/lib/mal.js +0 -957
  84. package/server/dist/lib/migration.js +0 -310
  85. package/server/dist/lib/offline-db.js +0 -395
  86. package/server/dist/lib/potoken.js +0 -135
  87. package/server/dist/lib/recommendations.service.js +0 -216
  88. package/server/dist/lib/temp-ids.js +0 -3
  89. package/server/dist/lib/tmdb.js +0 -72
  90. package/server/dist/lib/tracker/anilist-tracker.js +0 -308
  91. package/server/dist/lib/tracker/manga-sync.service.js +0 -773
  92. package/server/dist/lib/tracker/sync-state.js +0 -21
  93. package/server/dist/lib/tracker/sync.service.js +0 -476
  94. package/server/dist/lib/translate.js +0 -79
  95. package/server/dist/lib/ump.js +0 -106
  96. package/server/dist/lib/ytmusic.js +0 -162
  97. package/server/dist/logger.js +0 -16
  98. package/server/dist/providers/manga/manga.types.js +0 -1
  99. package/server/dist/providers/provider.interface.js +0 -1
  100. package/server/dist/providers/remote-loader.js +0 -614
  101. package/server/dist/providers/remote-types.js +0 -159
  102. package/server/dist/providers/title-matching.js +0 -226
  103. package/server/dist/providers/tv.types.js +0 -1
  104. package/server/dist/rclone.js +0 -120
  105. package/server/dist/repositories/asmr.repository.js +0 -77
  106. package/server/dist/repositories/insights.repository.js +0 -106
  107. package/server/dist/repositories/library.repository.js +0 -94
  108. package/server/dist/repositories/mal-cache.repository.js +0 -52
  109. package/server/dist/repositories/manga.repository.js +0 -95
  110. package/server/dist/repositories/notifications.repository.js +0 -60
  111. package/server/dist/repositories/queue.repository.js +0 -72
  112. package/server/dist/repositories/settings.repository.js +0 -30
  113. package/server/dist/repositories/shows-meta.repository.js +0 -58
  114. package/server/dist/repositories/temp-show-ids.repository.js +0 -63
  115. package/server/dist/repositories/tv.repository.js +0 -93
  116. package/server/dist/repositories/watched-episodes.repository.js +0 -82
  117. package/server/dist/repositories/watchlist.repository.js +0 -60
  118. package/server/dist/scripts/backfill-episode-duration.js +0 -42
  119. package/server/dist/sync-payload.js +0 -91
  120. package/server/dist/sync.js +0 -657
  121. package/server/dist/trpc/context.js +0 -7
  122. package/server/dist/trpc/errors.js +0 -15
  123. package/server/dist/trpc/index.js +0 -18
  124. package/server/dist/trpc/router.js +0 -49
  125. package/server/dist/trpc/routers/asmr-library.js +0 -112
  126. package/server/dist/trpc/routers/asmr-progress.js +0 -121
  127. package/server/dist/trpc/routers/asmr.js +0 -87
  128. package/server/dist/trpc/routers/auth.js +0 -194
  129. package/server/dist/trpc/routers/continue-watching.js +0 -81
  130. package/server/dist/trpc/routers/data.js +0 -1026
  131. package/server/dist/trpc/routers/discord.js +0 -285
  132. package/server/dist/trpc/routers/discovery.js +0 -11
  133. package/server/dist/trpc/routers/insights.js +0 -14
  134. package/server/dist/trpc/routers/manga-library.js +0 -263
  135. package/server/dist/trpc/routers/manga-progress.js +0 -169
  136. package/server/dist/trpc/routers/manga.js +0 -201
  137. package/server/dist/trpc/routers/music.js +0 -655
  138. package/server/dist/trpc/routers/notifications.js +0 -80
  139. package/server/dist/trpc/routers/progress.js +0 -109
  140. package/server/dist/trpc/routers/queue.js +0 -93
  141. package/server/dist/trpc/routers/radio.js +0 -145
  142. package/server/dist/trpc/routers/settings.js +0 -96
  143. package/server/dist/trpc/routers/tracker.js +0 -213
  144. package/server/dist/trpc/routers/tv-library.js +0 -135
  145. package/server/dist/trpc/routers/tv-progress.js +0 -159
  146. package/server/dist/trpc/routers/tv.js +0 -551
  147. package/server/dist/trpc/routers/watchlist.js +0 -550
  148. package/server/dist/trpc/validation.js +0 -258
  149. package/server/dist/utils/cache.utils.js +0 -25
  150. package/server/dist/utils/cookie.utils.js +0 -13
  151. package/server/dist/utils/db-utils.js +0 -17
  152. package/server/dist/utils/env.utils.js +0 -77
  153. package/server/dist/utils/http.utils.js +0 -82
  154. package/server/dist/utils/request-context.js +0 -2
  155. package/server/dist/utils/security.utils.js +0 -76
  156. /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
@@ -1,1026 +0,0 @@
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
- });