@serifpersia/dango 3.6.5 → 3.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-JkXG40Es.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-2L0UJkbB.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-BDs03vKk.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-D2mVKEl9.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-MQA_bNQM.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/AvSyncCalibrator-DwHCd9yB.js +10 -0
  8. package/client/dist/assets/{Home-DxMBHFmc.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-hmLWsSXY.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-CWI7hgUh.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-NOc28KAV.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-B5qNhI7v.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-D4wIyb-V.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-Ce1VX7-0.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-P3oY4_14.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-D8EwGYQq.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard-Dp8DjyXB.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-DULtVsAM.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-fIHdE-nq.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-B8XeOmso.js → Player-DQoEQkHL.js} +6 -6
  21. package/client/dist/assets/{PlayerSettings-BycNCiCs.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-T1J37IZT.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-BAwI-apz.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-Bv9DyKtZ.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-Cl0CcVp3.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal--N60WWCB.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DvgVDlUR.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-dCz_-bJn.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-BZHKk1I6.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-C1T5VcPu.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-Dhv_cg4y.js → Tv-DLytPJUj.js} +4 -4
  32. package/client/dist/assets/{TvInfo-DgaLK2H6.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DyX7f8kC.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-Bp0H6kue.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-ohMtMeKa.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-BbQtzsER.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-Bi1cjpLZ.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-BTnhzofk.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-Cvmso3IK.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-CbzGCzmv.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-DaREZ9Ie.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-BVQRhsFh.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-CzsCbndG.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-UuUPzft-.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-CSsbWKH9.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-a_Y0c2FS.js → useTvLibrary-By4Gt-R_.js} +1 -1
  47. package/client/dist/index.html +1 -1
  48. package/package.json +2 -2
  49. package/server/dist/app-auth.js +1 -157
  50. package/server/dist/app-hono.js +7 -557
  51. package/server/dist/config.js +1 -174
  52. package/server/dist/db/drizzle.js +1 -16
  53. package/server/dist/db/schema-anime.js +1 -61
  54. package/server/dist/db/schema-asmr.js +1 -13
  55. package/server/dist/db/schema-manga.js +1 -31
  56. package/server/dist/db/schema-tv.js +1 -18
  57. package/server/dist/db.js +1 -217
  58. package/server/dist/discord-gateway.js +1 -385
  59. package/server/dist/discord-rpc.js +1 -413
  60. package/server/dist/github-sync.js +1 -508
  61. package/server/dist/google.js +1 -741
  62. package/server/dist/hono/asmr.js +1 -10
  63. package/server/dist/hono/auth.js +3 -26
  64. package/server/dist/hono/music.js +1 -209
  65. package/server/dist/hono/proxy.js +36 -905
  66. package/server/dist/hono/tv.js +5 -169
  67. package/server/dist/hono/watchlist.js +2 -338
  68. package/server/dist/lib/anilist.js +26 -1458
  69. package/server/dist/lib/anischedule.js +1 -191
  70. package/server/dist/lib/insights.js +1 -185
  71. package/server/dist/lib/ipc.js +1 -23
  72. package/server/dist/lib/kitsu.js +1 -517
  73. package/server/dist/lib/mal-import.js +1 -291
  74. package/server/dist/lib/mal.js +1 -957
  75. package/server/dist/lib/migration.js +1 -310
  76. package/server/dist/lib/offline-db.js +4 -384
  77. package/server/dist/lib/potoken.js +1 -135
  78. package/server/dist/lib/recommendations.service.js +1 -216
  79. package/server/dist/lib/temp-ids.js +1 -3
  80. package/server/dist/lib/tmdb.js +1 -72
  81. package/server/dist/lib/tracker/anilist-tracker.js +8 -245
  82. package/server/dist/lib/tracker/manga-sync.service.js +1 -773
  83. package/server/dist/lib/tracker/sync-state.js +1 -21
  84. package/server/dist/lib/tracker/sync.service.js +1 -476
  85. package/server/dist/lib/translate.js +3 -79
  86. package/server/dist/lib/ump.js +1 -106
  87. package/server/dist/lib/ytmusic.js +1 -162
  88. package/server/dist/logger.js +1 -16
  89. package/server/dist/providers/manga/manga.types.js +0 -1
  90. package/server/dist/providers/provider.interface.js +0 -1
  91. package/server/dist/providers/remote-loader.js +2 -605
  92. package/server/dist/providers/remote-types.js +1 -124
  93. package/server/dist/providers/title-matching.js +1 -226
  94. package/server/dist/providers/tv.types.js +0 -1
  95. package/server/dist/rclone.js +2 -120
  96. package/server/dist/repositories/asmr.repository.js +11 -46
  97. package/server/dist/repositories/insights.repository.js +11 -25
  98. package/server/dist/repositories/library.repository.js +3 -94
  99. package/server/dist/repositories/mal-cache.repository.js +1 -52
  100. package/server/dist/repositories/manga.repository.js +14 -58
  101. package/server/dist/repositories/notifications.repository.js +6 -52
  102. package/server/dist/repositories/queue.repository.js +5 -58
  103. package/server/dist/repositories/settings.repository.js +2 -29
  104. package/server/dist/repositories/shows-meta.repository.js +3 -56
  105. package/server/dist/repositories/temp-show-ids.repository.js +3 -63
  106. package/server/dist/repositories/tv.repository.js +11 -54
  107. package/server/dist/repositories/watched-episodes.repository.js +6 -55
  108. package/server/dist/repositories/watchlist.repository.js +3 -54
  109. package/server/dist/scripts/backfill-episode-duration.js +1 -42
  110. package/server/dist/server.js +1 -307
  111. package/server/dist/shipped-defaults.js +1 -6
  112. package/server/dist/sync-payload.js +1 -91
  113. package/server/dist/sync.js +3 -647
  114. package/server/dist/trpc/context.js +1 -7
  115. package/server/dist/trpc/errors.js +1 -15
  116. package/server/dist/trpc/index.js +1 -18
  117. package/server/dist/trpc/router.js +1 -49
  118. package/server/dist/trpc/routers/asmr-library.js +1 -112
  119. package/server/dist/trpc/routers/asmr-progress.js +1 -121
  120. package/server/dist/trpc/routers/asmr.js +1 -87
  121. package/server/dist/trpc/routers/auth.js +1 -194
  122. package/server/dist/trpc/routers/continue-watching.js +2 -58
  123. package/server/dist/trpc/routers/data.js +1 -1026
  124. package/server/dist/trpc/routers/discord.js +1 -285
  125. package/server/dist/trpc/routers/discovery.js +1 -11
  126. package/server/dist/trpc/routers/insights.js +1 -14
  127. package/server/dist/trpc/routers/manga-library.js +1 -263
  128. package/server/dist/trpc/routers/manga-progress.js +1 -169
  129. package/server/dist/trpc/routers/manga.js +1 -201
  130. package/server/dist/trpc/routers/music.js +1 -655
  131. package/server/dist/trpc/routers/notifications.js +1 -80
  132. package/server/dist/trpc/routers/progress.js +1 -109
  133. package/server/dist/trpc/routers/queue.js +1 -93
  134. package/server/dist/trpc/routers/radio.js +1 -145
  135. package/server/dist/trpc/routers/settings.js +1 -96
  136. package/server/dist/trpc/routers/tracker.js +1 -213
  137. package/server/dist/trpc/routers/tv-library.js +1 -135
  138. package/server/dist/trpc/routers/tv-progress.js +1 -159
  139. package/server/dist/trpc/routers/tv.js +1 -551
  140. package/server/dist/trpc/routers/watchlist.js +2 -549
  141. package/server/dist/trpc/validation.js +1 -258
  142. package/server/dist/utils/cache.utils.js +1 -25
  143. package/server/dist/utils/cookie.utils.js +1 -13
  144. package/server/dist/utils/db-utils.js +5 -17
  145. package/server/dist/utils/env.utils.js +5 -77
  146. package/server/dist/utils/http.utils.js +1 -82
  147. package/server/dist/utils/request-context.js +1 -2
  148. package/server/dist/utils/security.utils.js +1 -76
  149. package/server/package.json +3 -2
  150. package/client/dist/assets/AvSyncCalibrator-DmoH5eRA.js +0 -10
  151. package/server/dist/utils/megaplay.utils.js +0 -40
@@ -1,551 +1 @@
1
- import { TRPCError } from '@trpc/server';
2
- import { protectedProcedure, router } from '../index.js';
3
- import { defineSchema, failed, optStr, readCount, reqId, reqObj, reqStr } from '../validation.js';
4
- import { getTmdbKey, TMDB_BASE, TMDB_IMAGE } from '../../lib/tmdb.js';
5
- import { parseJsonBody } from '../../utils/http.utils.js';
6
- async function resolveTvMeta(mediaType, numericTmdbId, partial) {
7
- const meta = { ...partial };
8
- if (meta.title && meta.imdbId && meta.year)
9
- return meta;
10
- try {
11
- const tmdbKey = await getTmdbKey();
12
- if (tmdbKey) {
13
- const tmdbRes = await fetch(`${TMDB_BASE}/${mediaType}/${numericTmdbId}?api_key=${tmdbKey}&append_to_response=external_ids`, { headers: { 'User-Agent': 'Mozilla/5.0' }, signal: AbortSignal.timeout(4000) });
14
- if (tmdbRes.ok) {
15
- const d = await parseJsonBody(tmdbRes);
16
- if (!meta.title)
17
- meta.title = d.title || d.name || '';
18
- if (!meta.year)
19
- meta.year = (d.release_date || d.first_air_date || '').split('-')[0] || '';
20
- if (!meta.imdbId)
21
- meta.imdbId = d.external_ids?.imdb_id || d.imdb_id || '';
22
- if (mediaType === 'tv' && d.number_of_seasons)
23
- meta.totalSeasons = String(d.number_of_seasons);
24
- }
25
- }
26
- }
27
- catch {
28
- // ignore
29
- }
30
- return meta;
31
- }
32
- function toSearchResult(item, type) {
33
- return {
34
- id: item.id,
35
- title: item.title || item.name || '',
36
- year: (item.release_date || item.first_air_date || '').split('-')[0],
37
- type,
38
- image: item.poster_path ? `${TMDB_IMAGE}/w500${item.poster_path}` : '',
39
- backdrop: item.backdrop_path ? `${TMDB_IMAGE}/w780${item.backdrop_path}` : '',
40
- overview: item.overview || '',
41
- vote_average: item.vote_average || 0,
42
- adult: item.adult === true,
43
- genre_ids: item.genre_ids || [],
44
- };
45
- }
46
- const tvSearchInput = () => defineSchema((value) => {
47
- const obj = reqObj(value);
48
- const out = {};
49
- const q = optStr(obj, 'q');
50
- if (q !== undefined)
51
- out.q = q;
52
- if (obj.page !== undefined)
53
- out.page = readCount(obj, 'page', 1);
54
- const type = optStr(obj, 'type');
55
- if (type !== undefined)
56
- out.type = type;
57
- const genre = optStr(obj, 'genre');
58
- if (genre !== undefined)
59
- out.genre = genre;
60
- const year = optStr(obj, 'year');
61
- if (year !== undefined)
62
- out.year = year;
63
- const sortBy = optStr(obj, 'sortBy');
64
- if (sortBy !== undefined)
65
- out.sortBy = sortBy;
66
- return out;
67
- });
68
- const tvTrendingInput = () => defineSchema((value) => {
69
- const obj = reqObj(value);
70
- const out = {};
71
- const mediaType = optStr(obj, 'mediaType');
72
- if (mediaType !== undefined)
73
- out.mediaType = mediaType;
74
- const timeWindow = optStr(obj, 'timeWindow');
75
- if (timeWindow !== undefined)
76
- out.timeWindow = timeWindow;
77
- if (obj.page !== undefined)
78
- out.page = readCount(obj, 'page', 1);
79
- return out;
80
- });
81
- const tvDetailsInput = () => defineSchema((value) => {
82
- const obj = reqObj(value);
83
- return { type: reqStr(obj, 'type'), id: reqId(obj, 'id') };
84
- });
85
- const tvEpisodesInput = () => defineSchema((value) => {
86
- const obj = reqObj(value);
87
- return { id: reqId(obj, 'id'), season: reqId(obj, 'season') };
88
- });
89
- const tvLookupImdbInput = () => defineSchema((value) => ({
90
- imdbId: reqStr(reqObj(value), 'imdbId'),
91
- }));
92
- const tvSearchImdbInput = () => defineSchema((value) => {
93
- const obj = reqObj(value);
94
- const out = {};
95
- const q = optStr(obj, 'q');
96
- if (q !== undefined)
97
- out.q = q;
98
- return out;
99
- });
100
- const tvSubtitlesInput = () => defineSchema((value) => {
101
- const obj = reqObj(value);
102
- const out = { type: reqStr(obj, 'type'), tmdbId: reqId(obj, 'tmdbId') };
103
- const season = optStr(obj, 'season');
104
- if (season !== undefined)
105
- out.season = season;
106
- const episode = optStr(obj, 'episode');
107
- if (episode !== undefined)
108
- out.episode = episode;
109
- return out;
110
- });
111
- const tvSourcesInput = () => defineSchema((value) => {
112
- const obj = reqObj(value);
113
- const out = {
114
- provider: reqStr(obj, 'provider'),
115
- type: reqStr(obj, 'type'),
116
- tmdbId: reqId(obj, 'tmdbId'),
117
- };
118
- if (obj.season !== undefined)
119
- out.season = readCount(obj, 'season', 1);
120
- if (obj.episode !== undefined)
121
- out.episode = readCount(obj, 'episode', 1);
122
- const server = optStr(obj, 'server');
123
- if (server !== undefined)
124
- out.server = server;
125
- const title = optStr(obj, 'title');
126
- if (title !== undefined)
127
- out.title = title;
128
- const year = optStr(obj, 'year');
129
- if (year !== undefined)
130
- out.year = year;
131
- const imdbId = optStr(obj, 'imdbId');
132
- if (imdbId !== undefined)
133
- out.imdbId = imdbId;
134
- const totalSeasons = optStr(obj, 'totalSeasons');
135
- if (totalSeasons !== undefined)
136
- out.totalSeasons = totalSeasons;
137
- return out;
138
- });
139
- export const tvRouter = router({
140
- search: protectedProcedure.input(tvSearchInput()).query(async ({ ctx, input }) => {
141
- const query = input.q || '';
142
- const page = input.page || 1;
143
- const type = input.type || 'multi';
144
- const genre = input.genre || '';
145
- const year = input.year || '';
146
- const sortBy = input.sortBy || 'popularity.desc';
147
- const cacheKey = `tv-search-${query.toLowerCase()}-${page}-${type}-${genre}-${year}-${sortBy}`;
148
- const cached = ctx.apiCache.get(cacheKey);
149
- if (cached)
150
- return cached;
151
- const key = await getTmdbKey();
152
- if (!key)
153
- throw failed('No TMDB API key available');
154
- try {
155
- let url;
156
- if (type === 'tv' || type === 'movie') {
157
- if (!query.trim()) {
158
- const params = new URLSearchParams({
159
- api_key: key,
160
- page: String(page),
161
- sort_by: sortBy,
162
- });
163
- if (genre)
164
- params.set('with_genres', genre);
165
- if (year) {
166
- if (type === 'tv')
167
- params.set('first_air_date_year', year);
168
- else
169
- params.set('primary_release_year', year);
170
- }
171
- url = `${TMDB_BASE}/discover/${type}?${params.toString()}`;
172
- }
173
- else {
174
- const params = new URLSearchParams({
175
- api_key: key,
176
- query: encodeURIComponent(query.trim()),
177
- page: String(page),
178
- include_adult: 'true',
179
- });
180
- url = `${TMDB_BASE}/search/${type}?${params.toString()}`;
181
- }
182
- }
183
- else if (!query.trim()) {
184
- const params = new URLSearchParams({
185
- api_key: key,
186
- page: String(page),
187
- sort_by: sortBy,
188
- });
189
- if (genre)
190
- params.set('with_genres', genre);
191
- if (year)
192
- params.set('first_air_date_year', year);
193
- url = `${TMDB_BASE}/discover/tv?${params.toString()}`;
194
- }
195
- else {
196
- const params = new URLSearchParams({
197
- api_key: key,
198
- query: encodeURIComponent(query),
199
- page: String(page),
200
- include_adult: 'true',
201
- });
202
- url = `${TMDB_BASE}/search/multi?${params.toString()}`;
203
- }
204
- const r = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
205
- if (!r.ok)
206
- throw failed('TMDB search failed');
207
- const d = await parseJsonBody(r);
208
- let results;
209
- if (type === 'tv' || type === 'movie') {
210
- results = (d.results || []).map((item) => toSearchResult(item, type));
211
- }
212
- else {
213
- results = (d.results || [])
214
- .filter((item) => item.media_type !== 'person')
215
- .map((item) => toSearchResult(item, item.media_type || 'tv'));
216
- }
217
- const payload = {
218
- results,
219
- total_results: d.total_results || 0,
220
- total_pages: d.total_pages || 0,
221
- page: d.page || page,
222
- };
223
- ctx.apiCache.set(cacheKey, payload, 1800);
224
- return payload;
225
- }
226
- catch (e) {
227
- if (e instanceof TRPCError)
228
- throw e;
229
- throw failed(e.message);
230
- }
231
- }),
232
- trending: protectedProcedure.input(tvTrendingInput()).query(async ({ ctx, input }) => {
233
- const mediaType = input.mediaType || 'all';
234
- const timeWindow = input.timeWindow || 'week';
235
- const page = input.page || 1;
236
- const cacheKey = `tv-trending-${mediaType}-${timeWindow}-${page}`;
237
- const cached = ctx.apiCache.get(cacheKey);
238
- if (cached)
239
- return cached;
240
- const key = await getTmdbKey();
241
- if (!key)
242
- throw failed('No TMDB API key available');
243
- try {
244
- const r = await fetch(`${TMDB_BASE}/trending/${mediaType}/${timeWindow}?api_key=${key}&page=${page}`, { headers: { 'User-Agent': 'Mozilla/5.0' } });
245
- if (!r.ok)
246
- throw failed('TMDB trending failed');
247
- const d = await parseJsonBody(r);
248
- const results = (d.results || [])
249
- .filter((item) => item.media_type !== 'person')
250
- .map((item) => toSearchResult(item, item.media_type || 'tv'));
251
- const payload = {
252
- results,
253
- total_results: d.total_results || 0,
254
- total_pages: d.total_pages || 0,
255
- page: d.page || page,
256
- };
257
- ctx.apiCache.set(cacheKey, payload, 3600);
258
- return payload;
259
- }
260
- catch (e) {
261
- if (e instanceof TRPCError)
262
- throw e;
263
- throw failed(e.message);
264
- }
265
- }),
266
- genres: protectedProcedure.query(async ({ ctx }) => {
267
- const cacheKey = 'tv-genres-list';
268
- const cached = ctx.apiCache.get(cacheKey);
269
- if (cached)
270
- return cached;
271
- const key = await getTmdbKey();
272
- if (!key)
273
- throw failed('No TMDB API key available');
274
- try {
275
- const [tvRes, movieRes] = await Promise.all([
276
- fetch(`${TMDB_BASE}/genre/tv/list?api_key=${key}`, {
277
- headers: { 'User-Agent': 'Mozilla/5.0' },
278
- }),
279
- fetch(`${TMDB_BASE}/genre/movie/list?api_key=${key}`, {
280
- headers: { 'User-Agent': 'Mozilla/5.0' },
281
- }),
282
- ]);
283
- const tvData = tvRes.ok
284
- ? await parseJsonBody(tvRes)
285
- : { genres: [] };
286
- const movieData = movieRes.ok
287
- ? await parseJsonBody(movieRes)
288
- : { genres: [] };
289
- const payload = {
290
- tv: (tvData.genres || []).map((g) => ({
291
- id: g.id,
292
- name: g.name,
293
- })),
294
- movie: (movieData.genres || []).map((g) => ({
295
- id: g.id,
296
- name: g.name,
297
- })),
298
- };
299
- ctx.apiCache.set(cacheKey, payload, 86400);
300
- return payload;
301
- }
302
- catch (e) {
303
- if (e instanceof TRPCError)
304
- throw e;
305
- throw failed(e.message);
306
- }
307
- }),
308
- details: protectedProcedure.input(tvDetailsInput()).query(async ({ input }) => {
309
- const type = input.type;
310
- const id = input.id;
311
- const key = await getTmdbKey();
312
- if (!key)
313
- throw failed('No TMDB API key available');
314
- try {
315
- const r = await fetch(`${TMDB_BASE}/${type}/${id}?api_key=${key}&append_to_response=external_ids`, { headers: { 'User-Agent': 'Mozilla/5.0' } });
316
- if (!r.ok)
317
- throw failed('TMDB details failed');
318
- const d = await parseJsonBody(r);
319
- const result = {
320
- id: d.id,
321
- imdb_id: d.external_ids?.imdb_id || d.imdb_id || null,
322
- title: d.title || d.name,
323
- overview: d.overview,
324
- vote_average: d.vote_average,
325
- year: (d.release_date || d.first_air_date || '').split('-')[0],
326
- poster: d.poster_path ? `${TMDB_IMAGE}/w500${d.poster_path}` : '',
327
- backdrop: d.backdrop_path ? `${TMDB_IMAGE}/original${d.backdrop_path}` : '',
328
- adult: d.adult === true,
329
- };
330
- if (type === 'tv') {
331
- result.seasons = (d.seasons || [])
332
- .filter((s) => s.season_number > 0)
333
- .map((s) => ({
334
- season_number: s.season_number,
335
- episode_count: s.episode_count,
336
- }));
337
- result.number_of_seasons = d.number_of_seasons;
338
- }
339
- return result;
340
- }
341
- catch (e) {
342
- if (e instanceof TRPCError)
343
- throw e;
344
- throw failed(e.message);
345
- }
346
- }),
347
- episodes: protectedProcedure.input(tvEpisodesInput()).query(async ({ input }) => {
348
- const id = input.id;
349
- const season = input.season;
350
- const key = await getTmdbKey();
351
- if (!key)
352
- throw failed('No TMDB API key available');
353
- try {
354
- const r = await fetch(`${TMDB_BASE}/tv/${id}/season/${season}?api_key=${key}`, {
355
- headers: { 'User-Agent': 'Mozilla/5.0' },
356
- });
357
- if (!r.ok)
358
- throw failed('TMDB episodes failed');
359
- const d = await parseJsonBody(r);
360
- const episodes = (d.episodes || []).map((ep) => ({
361
- episode_number: ep.episode_number,
362
- name: ep.name,
363
- vote_average: ep.vote_average,
364
- overview: ep.overview,
365
- still_path: ep.still_path,
366
- }));
367
- return { episodes };
368
- }
369
- catch (e) {
370
- if (e instanceof TRPCError)
371
- throw e;
372
- throw failed(e.message);
373
- }
374
- }),
375
- lookupImdb: protectedProcedure.input(tvLookupImdbInput()).query(async ({ input }) => {
376
- const key = await getTmdbKey();
377
- if (!key)
378
- return { tmdbId: null, error: 'No TMDB API key available' };
379
- try {
380
- const r = await fetch(`${TMDB_BASE}/find/${input.imdbId}?api_key=${key}&external_source=imdb_id`, { headers: { 'User-Agent': 'Mozilla/5.0' } });
381
- if (!r.ok)
382
- return { tmdbId: null, error: `TMDB error ${r.status}` };
383
- const d = await parseJsonBody(r);
384
- const movie = d.movie_results?.[0];
385
- const tv = d.tv_results?.[0];
386
- const result = movie || tv;
387
- return {
388
- tmdbId: result?.id || null,
389
- type: movie ? 'movie' : tv ? 'tv' : null,
390
- title: result?.title || result?.name || null,
391
- year: (result?.release_date || result?.first_air_date || '').substring(0, 4) || null,
392
- };
393
- }
394
- catch (e) {
395
- return { tmdbId: null, error: e.message };
396
- }
397
- }),
398
- searchImdb: protectedProcedure.input(tvSearchImdbInput()).query(async ({ ctx, input }) => {
399
- const query = input.q || '';
400
- if (!query)
401
- return [];
402
- const cacheKey = `tv-imdb-search-${query.toLowerCase()}`;
403
- const cached = ctx.apiCache.get(cacheKey);
404
- if (cached)
405
- return cached;
406
- try {
407
- const response = await fetch(`https://v3.sg.media-imdb.com/suggestion/x/${encodeURIComponent(query)}.json`, { headers: { 'User-Agent': 'Mozilla/5.0' } });
408
- if (!response.ok)
409
- throw failed('IMDB search failed');
410
- const data = await parseJsonBody(response);
411
- const TV_TYPES = new Set(['tvSeries', 'tvMiniSeries', 'movie']);
412
- const matches = (data.d || [])
413
- .filter((entry) => Boolean(entry.id && entry.l) && TV_TYPES.has(entry.qid || ''))
414
- .slice(0, 10)
415
- .map((entry) => ({
416
- id: entry.id,
417
- title: entry.l,
418
- year: entry.y,
419
- type: entry.qid,
420
- image: entry.i?.imageUrl || '',
421
- }));
422
- ctx.apiCache.set(cacheKey, matches, 3600);
423
- return matches;
424
- }
425
- catch (e) {
426
- if (e instanceof TRPCError)
427
- throw e;
428
- throw failed(e.message);
429
- }
430
- }),
431
- subtitles: protectedProcedure.input(tvSubtitlesInput()).query(async ({ ctx, input }) => {
432
- const type = input.type;
433
- const tmdbId = input.tmdbId;
434
- const numericTmdbId = parseInt(tmdbId, 10);
435
- if (!numericTmdbId)
436
- return { subtitles: [] };
437
- const mediaType = type === 'movie' ? 'movie' : 'tv';
438
- const season = input.season || '1';
439
- const episode = input.episode || '1';
440
- const cacheKey = mediaType === 'tv'
441
- ? `tv-subs-${numericTmdbId}-${season}-${episode}`
442
- : `tv-subs-${numericTmdbId}-movie`;
443
- const cached = ctx.apiCache.get(cacheKey);
444
- if (cached)
445
- return cached;
446
- try {
447
- const searchUrl = mediaType === 'tv'
448
- ? `https://subtitles.vidy.st/search?id=${numericTmdbId}&season=${encodeURIComponent(season)}&episode=${encodeURIComponent(episode)}`
449
- : `https://subtitles.vidy.st/search?id=${numericTmdbId}`;
450
- const r = await fetch(searchUrl, {
451
- headers: { 'User-Agent': 'Mozilla/5.0' },
452
- signal: AbortSignal.timeout(10000),
453
- });
454
- if (!r.ok)
455
- return { subtitles: [] };
456
- const items = (await parseJsonBody(r));
457
- const subtitles = (Array.isArray(items) ? items : [])
458
- .filter((s) => typeof s.url === 'string' && s.url.startsWith('https://'))
459
- .map((s) => ({
460
- language: String(s.language || 'en'),
461
- label: String(s.display || s.language || 'Unknown'),
462
- url: String(s.url),
463
- trusted: s.isTrusted === true,
464
- hi: s.isHearingImpaired === true,
465
- downloads: Number(s.downloadCount) || 0,
466
- media: String(s.media || s.release || ''),
467
- }))
468
- .sort((a, b) => {
469
- const aEn = a.language.toLowerCase().startsWith('en') ? 0 : 1;
470
- const bEn = b.language.toLowerCase().startsWith('en') ? 0 : 1;
471
- if (aEn !== bEn)
472
- return aEn - bEn;
473
- if (a.trusted !== b.trusted)
474
- return a.trusted ? -1 : 1;
475
- return b.downloads - a.downloads;
476
- })
477
- .slice(0, 50);
478
- const payload = { subtitles };
479
- ctx.apiCache.set(cacheKey, payload, 3600);
480
- return payload;
481
- }
482
- catch {
483
- return { subtitles: [] };
484
- }
485
- }),
486
- sources: protectedProcedure.input(tvSourcesInput()).query(async ({ ctx, input }) => {
487
- const provider = input.provider;
488
- const type = input.type;
489
- const tmdbId = input.tmdbId;
490
- const mediaType = type === 'movie' ? 'movie' : 'tv';
491
- const numericTmdbId = parseInt(tmdbId, 10);
492
- if (!numericTmdbId)
493
- return { sources: [], audioTracks: [], error: 'Bad tmdb id' };
494
- const mod = ctx.getTvProvider(String(provider).toLowerCase());
495
- if (!mod || typeof mod.getSources !== 'function') {
496
- return { sources: [], audioTracks: [], error: `Unknown TV provider ${provider}` };
497
- }
498
- const season = input.season || 1;
499
- const episode = input.episode || 1;
500
- const server = input.server;
501
- const meta = await resolveTvMeta(mediaType, numericTmdbId, {
502
- title: input.title || '',
503
- year: input.year || '',
504
- imdbId: input.imdbId || '',
505
- totalSeasons: input.totalSeasons || '1',
506
- });
507
- const media = {
508
- tmdbId: numericTmdbId,
509
- type: mediaType,
510
- season,
511
- episode,
512
- title: meta.title,
513
- year: meta.year,
514
- imdbId: meta.imdbId,
515
- totalSeasons: parseInt(meta.totalSeasons, 10) || 1,
516
- };
517
- try {
518
- const result = await mod.getSources(media, server);
519
- if (!result || !Array.isArray(result.sources) || result.sources.length === 0) {
520
- return {
521
- provider: mod.name,
522
- server: result?.server || server,
523
- sources: [],
524
- audioTracks: [],
525
- valid: false,
526
- error: 'No sources found',
527
- };
528
- }
529
- return {
530
- provider: mod.name,
531
- server: result.server || server,
532
- servers: result.servers,
533
- embedUrl: result.embedUrl,
534
- sources: result.sources,
535
- audioTracks: result.audioTracks || [],
536
- subtitles: result.subtitles || [],
537
- referer: result.referer || '',
538
- valid: true,
539
- };
540
- }
541
- catch (e) {
542
- return {
543
- provider: mod.name,
544
- sources: [],
545
- audioTracks: [],
546
- valid: false,
547
- error: e.message,
548
- };
549
- }
550
- }),
551
- });
1
+ import{TRPCError as $}from"@trpc/server";import{protectedProcedure as h,router as C}from"../index.js";import{defineSchema as I,failed as g,optStr as b,readCount as q,reqId as k,reqObj as T,reqStr as M}from"../validation.js";import{getTmdbKey as w,TMDB_BASE as f,TMDB_IMAGE as B}from"../../lib/tmdb.js";import{parseJsonBody as _}from"../../utils/http.utils.js";async function D(s,e,r){const n={...r};if(n.title&&n.imdbId&&n.year)return n;try{const t=await w();if(t){const a=await fetch(`${f}/${s}/${e}?api_key=${t}&append_to_response=external_ids`,{headers:{"User-Agent":"Mozilla/5.0"},signal:AbortSignal.timeout(4e3)});if(a.ok){const i=await _(a);n.title||(n.title=i.title||i.name||""),n.year||(n.year=(i.release_date||i.first_air_date||"").split("-")[0]||""),n.imdbId||(n.imdbId=i.external_ids?.imdb_id||i.imdb_id||""),s==="tv"&&i.number_of_seasons&&(n.totalSeasons=String(i.number_of_seasons))}}}catch{}return n}function P(s,e){return{id:s.id,title:s.title||s.name||"",year:(s.release_date||s.first_air_date||"").split("-")[0],type:e,image:s.poster_path?`${B}/w500${s.poster_path}`:"",backdrop:s.backdrop_path?`${B}/w780${s.backdrop_path}`:"",overview:s.overview||"",vote_average:s.vote_average||0,adult:s.adult===!0,genre_ids:s.genre_ids||[]}}const E=()=>I(s=>{const e=T(s),r={},n=b(e,"q");n!==void 0&&(r.q=n),e.page!==void 0&&(r.page=q(e,"page",1));const t=b(e,"type");t!==void 0&&(r.type=t);const a=b(e,"genre");a!==void 0&&(r.genre=a);const i=b(e,"year");i!==void 0&&(r.year=i);const o=b(e,"sortBy");return o!==void 0&&(r.sortBy=o),r}),R=()=>I(s=>{const e=T(s),r={},n=b(e,"mediaType");n!==void 0&&(r.mediaType=n);const t=b(e,"timeWindow");return t!==void 0&&(r.timeWindow=t),e.page!==void 0&&(r.page=q(e,"page",1)),r}),L=()=>I(s=>{const e=T(s);return{type:M(e,"type"),id:k(e,"id")}}),x=()=>I(s=>{const e=T(s);return{id:k(e,"id"),season:k(e,"season")}}),j=()=>I(s=>({imdbId:M(T(s),"imdbId")})),z=()=>I(s=>{const e=T(s),r={},n=b(e,"q");return n!==void 0&&(r.q=n),r}),W=()=>I(s=>{const e=T(s),r={type:M(e,"type"),tmdbId:k(e,"tmdbId")},n=b(e,"season");n!==void 0&&(r.season=n);const t=b(e,"episode");return t!==void 0&&(r.episode=t),r}),N=()=>I(s=>{const e=T(s),r={provider:M(e,"provider"),type:M(e,"type"),tmdbId:k(e,"tmdbId")};e.season!==void 0&&(r.season=q(e,"season",1)),e.episode!==void 0&&(r.episode=q(e,"episode",1));const n=b(e,"server");n!==void 0&&(r.server=n);const t=b(e,"title");t!==void 0&&(r.title=t);const a=b(e,"year");a!==void 0&&(r.year=a);const i=b(e,"imdbId");i!==void 0&&(r.imdbId=i);const o=b(e,"totalSeasons");return o!==void 0&&(r.totalSeasons=o),r}),O=C({search:h.input(E()).query(async({ctx:s,input:e})=>{const r=e.q||"",n=e.page||1,t=e.type||"multi",a=e.genre||"",i=e.year||"",o=e.sortBy||"popularity.desc",l=`tv-search-${r.toLowerCase()}-${n}-${t}-${a}-${i}-${o}`,d=s.apiCache.get(l);if(d)return d;const v=await w();if(!v)throw g("No TMDB API key available");try{let c;if(t==="tv"||t==="movie")if(r.trim()){const u=new URLSearchParams({api_key:v,query:encodeURIComponent(r.trim()),page:String(n),include_adult:"true"});c=`${f}/search/${t}?${u.toString()}`}else{const u=new URLSearchParams({api_key:v,page:String(n),sort_by:o});a&&u.set("with_genres",a),i&&(t==="tv"?u.set("first_air_date_year",i):u.set("primary_release_year",i)),c=`${f}/discover/${t}?${u.toString()}`}else if(r.trim()){const u=new URLSearchParams({api_key:v,query:encodeURIComponent(r),page:String(n),include_adult:"true"});c=`${f}/search/multi?${u.toString()}`}else{const u=new URLSearchParams({api_key:v,page:String(n),sort_by:o});a&&u.set("with_genres",a),i&&u.set("first_air_date_year",i),c=`${f}/discover/tv?${u.toString()}`}const y=await fetch(c,{headers:{"User-Agent":"Mozilla/5.0"}});if(!y.ok)throw g("TMDB search failed");const m=await _(y);let S;t==="tv"||t==="movie"?S=(m.results||[]).map(u=>P(u,t)):S=(m.results||[]).filter(u=>u.media_type!=="person").map(u=>P(u,u.media_type||"tv"));const p={results:S,total_results:m.total_results||0,total_pages:m.total_pages||0,page:m.page||n};return s.apiCache.set(l,p,1800),p}catch(c){throw c instanceof $?c:g(c.message)}}),trending:h.input(R()).query(async({ctx:s,input:e})=>{const r=e.mediaType||"all",n=e.timeWindow||"week",t=e.page||1,a=`tv-trending-${r}-${n}-${t}`,i=s.apiCache.get(a);if(i)return i;const o=await w();if(!o)throw g("No TMDB API key available");try{const l=await fetch(`${f}/trending/${r}/${n}?api_key=${o}&page=${t}`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!l.ok)throw g("TMDB trending failed");const d=await _(l),c={results:(d.results||[]).filter(y=>y.media_type!=="person").map(y=>P(y,y.media_type||"tv")),total_results:d.total_results||0,total_pages:d.total_pages||0,page:d.page||t};return s.apiCache.set(a,c,3600),c}catch(l){throw l instanceof $?l:g(l.message)}}),genres:h.query(async({ctx:s})=>{const e="tv-genres-list",r=s.apiCache.get(e);if(r)return r;const n=await w();if(!n)throw g("No TMDB API key available");try{const[t,a]=await Promise.all([fetch(`${f}/genre/tv/list?api_key=${n}`,{headers:{"User-Agent":"Mozilla/5.0"}}),fetch(`${f}/genre/movie/list?api_key=${n}`,{headers:{"User-Agent":"Mozilla/5.0"}})]),i=t.ok?await _(t):{genres:[]},o=a.ok?await _(a):{genres:[]},l={tv:(i.genres||[]).map(d=>({id:d.id,name:d.name})),movie:(o.genres||[]).map(d=>({id:d.id,name:d.name}))};return s.apiCache.set(e,l,86400),l}catch(t){throw t instanceof $?t:g(t.message)}}),details:h.input(L()).query(async({input:s})=>{const e=s.type,r=s.id,n=await w();if(!n)throw g("No TMDB API key available");try{const t=await fetch(`${f}/${e}/${r}?api_key=${n}&append_to_response=external_ids`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!t.ok)throw g("TMDB details failed");const a=await _(t),i={id:a.id,imdb_id:a.external_ids?.imdb_id||a.imdb_id||null,title:a.title||a.name,overview:a.overview,vote_average:a.vote_average,year:(a.release_date||a.first_air_date||"").split("-")[0],poster:a.poster_path?`${B}/w500${a.poster_path}`:"",backdrop:a.backdrop_path?`${B}/original${a.backdrop_path}`:"",adult:a.adult===!0};return e==="tv"&&(i.seasons=(a.seasons||[]).filter(o=>o.season_number>0).map(o=>({season_number:o.season_number,episode_count:o.episode_count})),i.number_of_seasons=a.number_of_seasons),i}catch(t){throw t instanceof $?t:g(t.message)}}),episodes:h.input(x()).query(async({input:s})=>{const e=s.id,r=s.season,n=await w();if(!n)throw g("No TMDB API key available");try{const t=await fetch(`${f}/tv/${e}/season/${r}?api_key=${n}`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!t.ok)throw g("TMDB episodes failed");return{episodes:((await _(t)).episodes||[]).map(o=>({episode_number:o.episode_number,name:o.name,vote_average:o.vote_average,overview:o.overview,still_path:o.still_path}))}}catch(t){throw t instanceof $?t:g(t.message)}}),lookupImdb:h.input(j()).query(async({input:s})=>{const e=await w();if(!e)return{tmdbId:null,error:"No TMDB API key available"};try{const r=await fetch(`${f}/find/${s.imdbId}?api_key=${e}&external_source=imdb_id`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!r.ok)return{tmdbId:null,error:`TMDB error ${r.status}`};const n=await _(r),t=n.movie_results?.[0],a=n.tv_results?.[0],i=t||a;return{tmdbId:i?.id||null,type:t?"movie":a?"tv":null,title:i?.title||i?.name||null,year:(i?.release_date||i?.first_air_date||"").substring(0,4)||null}}catch(r){return{tmdbId:null,error:r.message}}}),searchImdb:h.input(z()).query(async({ctx:s,input:e})=>{const r=e.q||"";if(!r)return[];const n=`tv-imdb-search-${r.toLowerCase()}`,t=s.apiCache.get(n);if(t)return t;try{const a=await fetch(`https://v3.sg.media-imdb.com/suggestion/x/${encodeURIComponent(r)}.json`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!a.ok)throw g("IMDB search failed");const i=await _(a),o=new Set(["tvSeries","tvMiniSeries","movie"]),l=(i.d||[]).filter(d=>!!(d.id&&d.l)&&o.has(d.qid||"")).slice(0,10).map(d=>({id:d.id,title:d.l,year:d.y,type:d.qid,image:d.i?.imageUrl||""}));return s.apiCache.set(n,l,3600),l}catch(a){throw a instanceof $?a:g(a.message)}}),subtitles:h.input(W()).query(async({ctx:s,input:e})=>{const r=e.type,n=e.tmdbId,t=parseInt(n,10);if(!t)return{subtitles:[]};const a=r==="movie"?"movie":"tv",i=e.season||"1",o=e.episode||"1",l=a==="tv"?`tv-subs-${t}-${i}-${o}`:`tv-subs-${t}-movie`,d=s.apiCache.get(l);if(d)return d;try{const v=a==="tv"?`https://subtitles.vidy.st/search?id=${t}&season=${encodeURIComponent(i)}&episode=${encodeURIComponent(o)}`:`https://subtitles.vidy.st/search?id=${t}`,c=await fetch(v,{headers:{"User-Agent":"Mozilla/5.0"},signal:AbortSignal.timeout(1e4)});if(!c.ok)return{subtitles:[]};const y=await _(c),S={subtitles:(Array.isArray(y)?y:[]).filter(p=>typeof p.url=="string"&&p.url.startsWith("https://")).map(p=>({language:String(p.language||"en"),label:String(p.display||p.language||"Unknown"),url:String(p.url),trusted:p.isTrusted===!0,hi:p.isHearingImpaired===!0,downloads:Number(p.downloadCount)||0,media:String(p.media||p.release||"")})).sort((p,u)=>{const U=p.language.toLowerCase().startsWith("en")?0:1,A=u.language.toLowerCase().startsWith("en")?0:1;return U!==A?U-A:p.trusted!==u.trusted?p.trusted?-1:1:u.downloads-p.downloads}).slice(0,50)};return s.apiCache.set(l,S,3600),S}catch{return{subtitles:[]}}}),sources:h.input(N()).query(async({ctx:s,input:e})=>{const r=e.provider,n=e.type,t=e.tmdbId,a=n==="movie"?"movie":"tv",i=parseInt(t,10);if(!i)return{sources:[],audioTracks:[],error:"Bad tmdb id"};const o=s.getTvProvider(String(r).toLowerCase());if(!o||typeof o.getSources!="function")return{sources:[],audioTracks:[],error:`Unknown TV provider ${r}`};const l=e.season||1,d=e.episode||1,v=e.server,c=await D(a,i,{title:e.title||"",year:e.year||"",imdbId:e.imdbId||"",totalSeasons:e.totalSeasons||"1"}),y={tmdbId:i,type:a,season:l,episode:d,title:c.title,year:c.year,imdbId:c.imdbId,totalSeasons:parseInt(c.totalSeasons,10)||1};try{const m=await o.getSources(y,v);return!m||!Array.isArray(m.sources)||m.sources.length===0?{provider:o.name,server:m?.server||v,sources:[],audioTracks:[],valid:!1,error:"No sources found"}:{provider:o.name,server:m.server||v,servers:m.servers,embedUrl:m.embedUrl,sources:m.sources,audioTracks:m.audioTracks||[],subtitles:m.subtitles||[],referer:m.referer||"",valid:!0}}catch(m){return{provider:o.name,sources:[],audioTracks:[],valid:!1,error:m.message}}})});export{O as tvRouter};