@serifpersia/dango 3.0.3

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/client/dist/assets/AnimeInfo-CMxs0nRH.css +1 -0
  4. package/client/dist/assets/AnimeInfo-DVdaocCl.js +1 -0
  5. package/client/dist/assets/AnimeInfoPage-Bx5EvGt-.js +2 -0
  6. package/client/dist/assets/Asmr-C2i9GKD4.css +1 -0
  7. package/client/dist/assets/Asmr-CIjmFtRH.js +2 -0
  8. package/client/dist/assets/Asmr.module-DPG2JhDJ.js +1 -0
  9. package/client/dist/assets/AvSyncCalibrator-CIYcyon5.css +1 -0
  10. package/client/dist/assets/AvSyncCalibrator-vqErH7Cm.js +10 -0
  11. package/client/dist/assets/ErrorMessage-BwSjzblw.js +1 -0
  12. package/client/dist/assets/ErrorMessage-CXoRbrLN.css +1 -0
  13. package/client/dist/assets/Home-D7YfGBUk.js +1 -0
  14. package/client/dist/assets/Home-DKCfEiwl.css +1 -0
  15. package/client/dist/assets/Insights-BljI64Ua.css +1 -0
  16. package/client/dist/assets/Insights-CLQGrlpX.js +1 -0
  17. package/client/dist/assets/Manga-DQ9HYnpk.js +1 -0
  18. package/client/dist/assets/Manga-H8Wb04w3.css +1 -0
  19. package/client/dist/assets/Map-CXFqXmsZ.css +1 -0
  20. package/client/dist/assets/Map-L_ryVBTq.js +1 -0
  21. package/client/dist/assets/Mature-BeWzAJ7B.js +1 -0
  22. package/client/dist/assets/Mature-DJmUUtlF.css +1 -0
  23. package/client/dist/assets/Player-CYpd__FP.js +30 -0
  24. package/client/dist/assets/Player-DreAhybc.css +1 -0
  25. package/client/dist/assets/PlayerRedirect-fxUAp9JV.js +1 -0
  26. package/client/dist/assets/PlayerSettings-BcsotxP9.js +1 -0
  27. package/client/dist/assets/PlayerSettings-Cqua1CDD.css +1 -0
  28. package/client/dist/assets/QueueRail-B9Pgbqc3.js +1 -0
  29. package/client/dist/assets/QueueRail-BnaltOP8.css +1 -0
  30. package/client/dist/assets/Radio-BvdY5twD.js +1 -0
  31. package/client/dist/assets/Radio-CUuYy3DY.css +1 -0
  32. package/client/dist/assets/Search-BzO-aRP7.css +1 -0
  33. package/client/dist/assets/Search-R9YLAXS1.js +1 -0
  34. package/client/dist/assets/Search.module-D8ZbQAiK.js +1 -0
  35. package/client/dist/assets/Settings-9kYcXdTR.js +21 -0
  36. package/client/dist/assets/Settings-DF8JThUL.css +1 -0
  37. package/client/dist/assets/SkeletonGrid-D5vRvX5q.js +1 -0
  38. package/client/dist/assets/SkeletonGrid-DEbYtO4H.css +1 -0
  39. package/client/dist/assets/SynopsisText-BlFw0R-0.js +1 -0
  40. package/client/dist/assets/SynopsisText-CUgOjOxt.css +1 -0
  41. package/client/dist/assets/Trackers-DfZlnnVq.js +2 -0
  42. package/client/dist/assets/Trackers-Dt9lR_uP.css +1 -0
  43. package/client/dist/assets/Tv-CVKC2YBs.css +1 -0
  44. package/client/dist/assets/Tv-Db1Wx_b7.js +15 -0
  45. package/client/dist/assets/Watchlist-B2nkNZqM.css +1 -0
  46. package/client/dist/assets/Watchlist-a77CYtzu.js +1 -0
  47. package/client/dist/assets/hls-6Owgw2Aj.js +1 -0
  48. package/client/dist/assets/hls-BOLXTnjL.js +42 -0
  49. package/client/dist/assets/index-B6qdXFtl.css +1 -0
  50. package/client/dist/assets/index-B8TPVaIS.js +30354 -0
  51. package/client/dist/assets/index-CpjgU8ou.js +178 -0
  52. package/client/dist/assets/useAnimeInfoData-BOfyi9UJ.js +1 -0
  53. package/client/dist/assets/useIsMobile-CM15uQn8.css +1 -0
  54. package/client/dist/assets/useIsMobile-Cb8HcKRj.js +1 -0
  55. package/client/dist/assets/useMatureConsent-CZMYBVXd.js +1 -0
  56. package/client/dist/assets/vendor-rLNfZm7Y.js +1 -0
  57. package/client/dist/av-sync-test.mp4 +0 -0
  58. package/client/dist/favicon.ico +0 -0
  59. package/client/dist/index.html +31 -0
  60. package/client/dist/logo-mochi.png +0 -0
  61. package/client/dist/logo-sakura.png +0 -0
  62. package/client/dist/logo.png +0 -0
  63. package/client/dist/placeholder.svg +4 -0
  64. package/client/dist/robots.txt +3 -0
  65. package/orchestrator.js +416 -0
  66. package/package.json +89 -0
  67. package/server/dist/app-auth.js +231 -0
  68. package/server/dist/config.js +133 -0
  69. package/server/dist/controllers/auth.controller.js +247 -0
  70. package/server/dist/controllers/data.controller.js +785 -0
  71. package/server/dist/controllers/insights.controller.js +197 -0
  72. package/server/dist/controllers/proxy.controller.js +665 -0
  73. package/server/dist/controllers/settings.controller.js +267 -0
  74. package/server/dist/controllers/watchlist.controller.js +967 -0
  75. package/server/dist/db.js +152 -0
  76. package/server/dist/discord-gateway.js +381 -0
  77. package/server/dist/discord-rpc.js +424 -0
  78. package/server/dist/github-sync.js +403 -0
  79. package/server/dist/google.js +549 -0
  80. package/server/dist/lib/anilist.js +1519 -0
  81. package/server/dist/lib/anischedule.js +202 -0
  82. package/server/dist/lib/ipc.js +32 -0
  83. package/server/dist/lib/kitsu.js +552 -0
  84. package/server/dist/lib/mal.js +886 -0
  85. package/server/dist/lib/migration.js +335 -0
  86. package/server/dist/lib/temp-ids.js +11 -0
  87. package/server/dist/lib/tmdb.js +71 -0
  88. package/server/dist/lib/tracker/anilist-tracker.js +250 -0
  89. package/server/dist/lib/tracker/sync.service.js +312 -0
  90. package/server/dist/logger.js +19 -0
  91. package/server/dist/providers/123anime.provider.js +240 -0
  92. package/server/dist/providers/anilight.provider.js +366 -0
  93. package/server/dist/providers/animepahe.provider.js +531 -0
  94. package/server/dist/providers/animeya.provider.js +746 -0
  95. package/server/dist/providers/base-provider.js +45 -0
  96. package/server/dist/providers/hn.provider.js +473 -0
  97. package/server/dist/providers/ht.provider.js +439 -0
  98. package/server/dist/providers/jasmr.provider.js +504 -0
  99. package/server/dist/providers/kaa.provider.js +388 -0
  100. package/server/dist/providers/manga/manga.types.js +10 -0
  101. package/server/dist/providers/manga/mangadex.provider.js +227 -0
  102. package/server/dist/providers/manga/mangapill.provider.js +232 -0
  103. package/server/dist/providers/megaplay.provider.js +583 -0
  104. package/server/dist/providers/op.provider.js +440 -0
  105. package/server/dist/providers/provider.interface.js +2 -0
  106. package/server/dist/providers/title-matching.js +234 -0
  107. package/server/dist/providers/wh.provider.js +600 -0
  108. package/server/dist/rclone.js +126 -0
  109. package/server/dist/repositories/insights.repository.js +43 -0
  110. package/server/dist/repositories/mal-cache.repository.js +66 -0
  111. package/server/dist/repositories/notifications.repository.js +40 -0
  112. package/server/dist/repositories/queue.repository.js +64 -0
  113. package/server/dist/repositories/settings.repository.js +15 -0
  114. package/server/dist/repositories/shows-meta.repository.js +43 -0
  115. package/server/dist/repositories/temp-show-ids.repository.js +54 -0
  116. package/server/dist/repositories/watched-episodes.repository.js +54 -0
  117. package/server/dist/repositories/watchlist.repository.js +74 -0
  118. package/server/dist/routes/asmr.routes.js +72 -0
  119. package/server/dist/routes/auth.routes.js +28 -0
  120. package/server/dist/routes/data.routes.js +50 -0
  121. package/server/dist/routes/discord-gateway.routes.js +57 -0
  122. package/server/dist/routes/insights.routes.js +12 -0
  123. package/server/dist/routes/lan-auth.routes.js +49 -0
  124. package/server/dist/routes/manga.routes.js +115 -0
  125. package/server/dist/routes/proxy.routes.js +15 -0
  126. package/server/dist/routes/radio.routes.js +143 -0
  127. package/server/dist/routes/settings.routes.js +27 -0
  128. package/server/dist/routes/tracker.routes.js +143 -0
  129. package/server/dist/routes/translate.routes.js +90 -0
  130. package/server/dist/routes/tv.routes.js +843 -0
  131. package/server/dist/routes/watchlist.routes.js +312 -0
  132. package/server/dist/server.js +310 -0
  133. package/server/dist/shipped-defaults.js +8 -0
  134. package/server/dist/sync-config.js +32 -0
  135. package/server/dist/sync.js +506 -0
  136. package/server/dist/utils/cookie.utils.js +17 -0
  137. package/server/dist/utils/db-utils.js +15 -0
  138. package/server/dist/utils/env.utils.js +83 -0
  139. package/server/dist/utils/machine-id.js +46 -0
  140. package/server/dist/utils/request-context.js +5 -0
  141. package/server/dist/utils/security.utils.js +99 -0
@@ -0,0 +1,439 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HtProvider = exports.HT_GENRES = void 0;
7
+ const logger_1 = __importDefault(require("../logger"));
8
+ const base_provider_1 = require("./base-provider");
9
+ const title_matching_1 = require("./title-matching");
10
+ const BASE_URL = 'https://hentai.tv';
11
+ const API_URL = 'https://hentai.tv/api/search';
12
+ const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
13
+ function imageUrl(path) {
14
+ if (!path)
15
+ return '';
16
+ if (path.startsWith('http'))
17
+ return path;
18
+ return `${BASE_URL}${path}`;
19
+ }
20
+ async function fetchPageHtml(url) {
21
+ const res = await fetch(url, {
22
+ headers: {
23
+ 'User-Agent': UA,
24
+ Referer: `${BASE_URL}/`,
25
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
26
+ },
27
+ signal: AbortSignal.timeout(20000),
28
+ });
29
+ if (!res.ok)
30
+ throw new Error(`HTTP ${res.status}: ${url}`);
31
+ return res.text();
32
+ }
33
+ async function searchApi(query, limit = 40) {
34
+ const url = `${API_URL}?q=${encodeURIComponent(query)}&limit=${limit}`;
35
+ const res = await fetch(url, {
36
+ headers: {
37
+ 'User-Agent': UA,
38
+ Referer: `${BASE_URL}/`,
39
+ Accept: 'application/json',
40
+ },
41
+ signal: AbortSignal.timeout(15000),
42
+ });
43
+ if (!res.ok)
44
+ return [];
45
+ const data = await res.json();
46
+ return data?.videos || [];
47
+ }
48
+ function groupBySeries(videos) {
49
+ const map = new Map();
50
+ for (const v of videos) {
51
+ const existing = map.get(v.titleSlug) || [];
52
+ existing.push(v);
53
+ map.set(v.titleSlug, existing);
54
+ }
55
+ return map;
56
+ }
57
+ function parseFlightVideos(html) {
58
+ const videos = [];
59
+ const seen = new Set();
60
+ const segments = html.split('\\"titleSlug\\":\\"');
61
+ for (let i = 1; i < segments.length; i++) {
62
+ const seg = segments[i].slice(0, 6000);
63
+ const slug = seg.split('\\"')[0];
64
+ const epSlug = seg.match(/\\"slug\\":\\"([^\\]+)\\"/)?.[1] || '';
65
+ const title = seg.match(/\\"title\\":\\"([^\\]+)\\"/)?.[1] || '';
66
+ if (!slug || !title)
67
+ continue;
68
+ const key = `${slug}::${epSlug}`;
69
+ if (seen.has(key))
70
+ continue;
71
+ seen.add(key);
72
+ const num = (name) => {
73
+ const m = seg.match(new RegExp(`\\"${name}\\":([\\d.]+)`));
74
+ return m ? Number(m[1]) : 0;
75
+ };
76
+ const cover = seg.match(/\\"(cover|thumb|featureImage)\\":\\"([^\\]+)\\"/)?.[2] || '';
77
+ const tagsM = seg.match(/\\"tags\\":\[([^\]]{0,2000})\]/);
78
+ const tags = tagsM ? Array.from(tagsM[1].matchAll(/\\"([^\\]+)\\"/g)).map((m) => m[1]) : [];
79
+ videos.push({
80
+ id: '',
81
+ slug: epSlug,
82
+ title,
83
+ titleSlug: slug,
84
+ titleId: '',
85
+ ep: num('ep'),
86
+ views: num('views'),
87
+ likes: num('likes'),
88
+ dislikes: num('dislikes'),
89
+ rating: num('rating'),
90
+ censored: seg.includes('\\"censored\\":true'),
91
+ brand: seg.match(/\\"brand\\":\\"([^\\]*)\\"/)?.[1] || '',
92
+ quality: '',
93
+ year: num('year'),
94
+ language: '',
95
+ duration: '',
96
+ tags,
97
+ cover,
98
+ thumb: '',
99
+ backdrop: null,
100
+ featureImage: '',
101
+ embedUrl: null,
102
+ description: '',
103
+ grad: [],
104
+ releasedAt: '',
105
+ });
106
+ }
107
+ return videos;
108
+ }
109
+ exports.HT_GENRES = [
110
+ '3d',
111
+ 'ahegao',
112
+ 'anal',
113
+ 'bdsm',
114
+ 'big-boobs',
115
+ 'big-breasts',
116
+ 'big-tits',
117
+ 'blow-job',
118
+ 'blowjob',
119
+ 'bondage',
120
+ 'boob-job',
121
+ 'censored',
122
+ 'cheating',
123
+ 'comedy',
124
+ 'corruption',
125
+ 'cosplay',
126
+ 'cream-pie',
127
+ 'creampie',
128
+ 'dark-skin',
129
+ 'doggy-style',
130
+ 'drama',
131
+ 'elf',
132
+ 'erotic-game',
133
+ 'exhibitionism',
134
+ 'facial',
135
+ 'fantasy',
136
+ 'ffm-threesome',
137
+ 'filmed',
138
+ 'first-kiss',
139
+ 'foot-job',
140
+ 'futanari',
141
+ 'gangbang',
142
+ 'glasses',
143
+ 'group-sex',
144
+ 'gyaru',
145
+ 'hand-job',
146
+ 'harem',
147
+ 'hd',
148
+ 'hentai',
149
+ 'high-school',
150
+ 'horror',
151
+ 'housewife',
152
+ 'huge-breasts',
153
+ 'humiliation',
154
+ 'impregnation',
155
+ 'incest',
156
+ 'inflation',
157
+ 'lactation',
158
+ 'loli',
159
+ 'loli-01',
160
+ 'magic',
161
+ 'maid',
162
+ 'masturbation',
163
+ 'milf',
164
+ 'mind-break',
165
+ 'mind-control',
166
+ 'monster',
167
+ 'nekomimi',
168
+ 'ntr',
169
+ 'nudity',
170
+ 'nurse',
171
+ 'office-lady',
172
+ 'oral',
173
+ 'orgy',
174
+ 'paizuri',
175
+ 'plot',
176
+ 'pov',
177
+ 'pregnant',
178
+ 'public-sex',
179
+ 'rape',
180
+ 'reverse-rape',
181
+ 'rimjob',
182
+ 'romance',
183
+ 'scat',
184
+ 'school-girl',
185
+ 'school-life',
186
+ 'schoolgirl',
187
+ 'sex',
188
+ 'sex-toys',
189
+ 'short',
190
+ 'shota',
191
+ 'small-tits',
192
+ 'softcore',
193
+ 'swimsuit',
194
+ 'teacher',
195
+ 'teasing',
196
+ 'tentacle',
197
+ 'threesome',
198
+ 'toys',
199
+ 'trap',
200
+ 'tsundere',
201
+ 'ugly-bastard',
202
+ 'uncensored',
203
+ 'vanilla',
204
+ 'virgin',
205
+ 'voyeurism',
206
+ 'watersports',
207
+ 'x-ray',
208
+ 'yaoi',
209
+ 'yuri',
210
+ ];
211
+ function bestMatch(series, query) {
212
+ if (!series.length)
213
+ return null;
214
+ const q = query.toLowerCase().trim();
215
+ let best = series[0];
216
+ let bestScore = -1;
217
+ for (const item of series) {
218
+ const title = item.title.toLowerCase();
219
+ let score = 0;
220
+ if (title === q)
221
+ score = 3;
222
+ else if (title.startsWith(q))
223
+ score = 2;
224
+ else if (title.includes(q))
225
+ score = 1;
226
+ if (score > bestScore) {
227
+ bestScore = score;
228
+ best = item;
229
+ if (score === 3)
230
+ break;
231
+ }
232
+ }
233
+ return { ...best, score: bestScore };
234
+ }
235
+ class HtProvider extends base_provider_1.BaseProvider {
236
+ name = 'Ht';
237
+ async browse(options) {
238
+ try {
239
+ const query = (options.query || '').trim();
240
+ const genre = (options.genre || '').trim().toLowerCase();
241
+ const page = Math.max(1, options.page || 1);
242
+ const limit = Math.min(100, Math.max(1, options.limit || 40));
243
+ const cacheKey = `ht_browse_${genre || query}_${page}_${limit}`;
244
+ const cached = this.cache.get(cacheKey);
245
+ if (cached)
246
+ return cached;
247
+ let videos = [];
248
+ let hasMore = false;
249
+ if (genre) {
250
+ const url = page > 1 ? `${BASE_URL}/genre/${genre}?page=${page}` : `${BASE_URL}/genre/${genre}`;
251
+ const html = await fetchPageHtml(url);
252
+ videos = parseFlightVideos(html);
253
+ hasMore = new RegExp(`[?&]page=${page + 1}\\b`).test(html);
254
+ }
255
+ else if (query) {
256
+ videos = await searchApi(query, limit);
257
+ }
258
+ else {
259
+ const html = await fetchPageHtml(`${BASE_URL}/browse`);
260
+ videos = parseFlightVideos(html);
261
+ }
262
+ const seriesMap = groupBySeries(videos);
263
+ const shows = Array.from(seriesMap.entries()).map(([slug, eps]) => {
264
+ const first = eps[0];
265
+ return {
266
+ _id: slug,
267
+ id: slug,
268
+ name: first.title,
269
+ englishName: first.title,
270
+ thumbnail: imageUrl(first.cover || first.thumb),
271
+ type: 'TV',
272
+ year: first.year || null,
273
+ isAdult: true,
274
+ score: first.rating || null,
275
+ availableEpisodesDetail: {
276
+ sub: eps.map((e) => String(e.ep)),
277
+ dub: [],
278
+ },
279
+ };
280
+ });
281
+ const output = { shows, hasMore };
282
+ this.cache.set(cacheKey, output, 300);
283
+ return output;
284
+ }
285
+ catch (error) {
286
+ logger_1.default.error({ error }, '[HT] Browse failed');
287
+ return { shows: [], hasMore: false };
288
+ }
289
+ }
290
+ async search(options) {
291
+ try {
292
+ const query = (options.query || '').trim();
293
+ if (!query)
294
+ return [];
295
+ const cacheKey = `ht_search_${query}`;
296
+ const cached = this.cache.get(cacheKey);
297
+ if (cached)
298
+ return cached;
299
+ const videos = await searchApi(query, 40);
300
+ if (videos.length === 0)
301
+ return [];
302
+ const seriesMap = groupBySeries(videos);
303
+ const uniqueSeries = Array.from(seriesMap.entries()).map(([slug, eps]) => ({
304
+ title: eps[0].title,
305
+ titleSlug: slug,
306
+ }));
307
+ const match = bestMatch(uniqueSeries, query) || uniqueSeries[0];
308
+ const episodes = seriesMap.get(match.titleSlug) || [];
309
+ const first = episodes[0];
310
+ const result = [
311
+ {
312
+ _id: match.titleSlug,
313
+ id: match.titleSlug,
314
+ name: first.title,
315
+ englishName: first.title,
316
+ thumbnail: imageUrl(first.cover || first.thumb),
317
+ type: 'TV',
318
+ year: first.year || null,
319
+ availableEpisodesDetail: {
320
+ sub: episodes.map((e) => String(e.ep)),
321
+ dub: [],
322
+ },
323
+ },
324
+ ];
325
+ this.cache.set(cacheKey, result, 300);
326
+ return result;
327
+ }
328
+ catch (error) {
329
+ logger_1.default.error({ error }, '[HT] Search failed');
330
+ return [];
331
+ }
332
+ }
333
+ async resolveShowId(title, romaji) {
334
+ const query = (romaji || title).trim();
335
+ if (!query)
336
+ return null;
337
+ const targets = [title, romaji].filter((t) => !!t);
338
+ for (const variant of (0, title_matching_1.buildQueryVariants)(title, romaji)) {
339
+ const videos = await searchApi(variant, 20);
340
+ if (videos.length === 0)
341
+ continue;
342
+ const seriesMap = groupBySeries(videos);
343
+ const uniqueSeries = Array.from(seriesMap.entries()).map(([slug, eps]) => ({
344
+ title: eps[0].title,
345
+ titleSlug: slug,
346
+ }));
347
+ const match = (0, title_matching_1.pickBestMatch)(uniqueSeries, targets);
348
+ if (match) {
349
+ return match.item.titleSlug;
350
+ }
351
+ }
352
+ return null;
353
+ }
354
+ async getEpisodes(showId) {
355
+ try {
356
+ if (!showId)
357
+ return null;
358
+ const cacheKey = `ht_eps_${showId}`;
359
+ const cached = this.cache.get(cacheKey);
360
+ if (cached)
361
+ return cached;
362
+ const videos = await searchApi(showId, 40);
363
+ const episodes = videos
364
+ .filter((v) => v.titleSlug === showId)
365
+ .sort((a, b) => a.ep - b.ep)
366
+ .map((v) => String(v.ep));
367
+ if (episodes.length === 0) {
368
+ const fallback = videos
369
+ .filter((v) => v.title.toLowerCase().includes(showId.replace(/-/g, ' ').toLowerCase()))
370
+ .sort((a, b) => a.ep - b.ep)
371
+ .map((v) => String(v.ep));
372
+ if (fallback.length > 0) {
373
+ const desc = videos[0]?.description || '';
374
+ const result = { episodes: fallback, description: desc };
375
+ this.cache.set(cacheKey, result, 300);
376
+ return result;
377
+ }
378
+ return null;
379
+ }
380
+ const desc = videos.find((v) => v.titleSlug === showId)?.description || '';
381
+ const result = { episodes, description: desc };
382
+ this.cache.set(cacheKey, result, 300);
383
+ return result;
384
+ }
385
+ catch (error) {
386
+ logger_1.default.error({ error, showId }, '[HT] getEpisodes failed');
387
+ return null;
388
+ }
389
+ }
390
+ async getEmbedUrl(showId, episodeNumber) {
391
+ const cacheKey = `ht_ep_${showId}_${episodeNumber}`;
392
+ const cached = this.cache.get(cacheKey);
393
+ if (cached)
394
+ return cached;
395
+ const videos = await searchApi(showId, 40);
396
+ const match = videos.find((v) => v.titleSlug === showId && String(v.ep) === String(episodeNumber));
397
+ if (match?.embedUrl) {
398
+ this.cache.set(cacheKey, match.embedUrl, 3600);
399
+ return match.embedUrl;
400
+ }
401
+ const fuzzy = videos.find((v) => v.titleSlug === showId && Math.abs(v.ep - parseFloat(episodeNumber)) < 0.01);
402
+ if (fuzzy?.embedUrl) {
403
+ this.cache.set(cacheKey, fuzzy.embedUrl, 3600);
404
+ return fuzzy.embedUrl;
405
+ }
406
+ return null;
407
+ }
408
+ async getStreamUrls(showId, episodeNumber, _mode) {
409
+ try {
410
+ let targetEpisode = episodeNumber;
411
+ if (episodeNumber === '0')
412
+ targetEpisode = '1';
413
+ const embedUrl = await this.getEmbedUrl(showId, targetEpisode);
414
+ if (!embedUrl) {
415
+ logger_1.default.warn({ showId, episodeNumber }, '[HT] Could not resolve embed URL');
416
+ return null;
417
+ }
418
+ const cacheKey = `ht_stream_${showId}_${targetEpisode}`;
419
+ const cached = this.cache.get(cacheKey);
420
+ if (cached)
421
+ return cached;
422
+ const result = [
423
+ {
424
+ sourceName: 'HT',
425
+ links: [{ resolutionStr: 'Auto', link: embedUrl, hls: false }],
426
+ type: 'iframe',
427
+ actualEpisodeNumber: targetEpisode,
428
+ },
429
+ ];
430
+ this.cache.set(cacheKey, result, 3600);
431
+ return result;
432
+ }
433
+ catch (error) {
434
+ logger_1.default.error({ error, showId, episodeNumber }, '[HT] getStreamUrls failed');
435
+ return null;
436
+ }
437
+ }
438
+ }
439
+ exports.HtProvider = HtProvider;