@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,504 @@
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.JasmrProvider = void 0;
7
+ const logger_1 = __importDefault(require("../logger"));
8
+ const request_context_1 = require("../utils/request-context");
9
+ const cookie_utils_1 = require("../utils/cookie.utils");
10
+ const base_provider_1 = require("./base-provider");
11
+ const BASE_URL = 'https://japaneseasmr.com';
12
+ const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36';
13
+ function parseRatingFromClasses(classAttr) {
14
+ const classes = ` ${classAttr} `;
15
+ if (classes.includes(' category-sfw '))
16
+ return { rating: 'SFW', isAdult: false };
17
+ if (classes.includes(' category-r-15 '))
18
+ return { rating: 'R-15', isAdult: false };
19
+ if (classes.includes(' category-maniax '))
20
+ return { rating: 'R-18', isAdult: true };
21
+ return { rating: '', isAdult: true };
22
+ }
23
+ function decodeEntities(text) {
24
+ return text
25
+ .replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, code) => {
26
+ try {
27
+ if (code.startsWith('#x') || code.startsWith('#X')) {
28
+ return String.fromCodePoint(parseInt(code.slice(2), 16));
29
+ }
30
+ if (code.startsWith('#')) {
31
+ return String.fromCodePoint(parseInt(code.slice(1), 10));
32
+ }
33
+ const entities = {
34
+ amp: '&',
35
+ lt: '<',
36
+ gt: '>',
37
+ quot: '"',
38
+ apos: "'",
39
+ nbsp: ' ',
40
+ hellip: '\u2026',
41
+ mdash: '\u2014',
42
+ ndash: '\u2013',
43
+ };
44
+ const lower = code.toLowerCase();
45
+ return entities[lower] ?? match;
46
+ }
47
+ catch {
48
+ return match;
49
+ }
50
+ })
51
+ .replace(/\s+/g, ' ')
52
+ .trim();
53
+ }
54
+ function isCloudflareChallenge(status, text) {
55
+ if (status === 403 || status === 503)
56
+ return true;
57
+ if (/Just a moment/i.test(text))
58
+ return true;
59
+ if (/challenge-platform\/h\//i.test(text))
60
+ return true;
61
+ return false;
62
+ }
63
+ async function fetchText(url) {
64
+ const store = request_context_1.requestContext.getStore();
65
+ const ua = store?.get('jasmr_ua') || UA;
66
+ const rawCookie = store?.get('jasmr_cookie');
67
+ const cookieHeader = rawCookie ? (0, cookie_utils_1.buildCfClearanceCookie)(rawCookie) : '';
68
+ const res = await fetch(url, {
69
+ headers: {
70
+ 'User-Agent': ua,
71
+ Referer: `${BASE_URL}/`,
72
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
73
+ 'Accept-Language': 'ja,en-US;q=0.8,en;q=0.6',
74
+ ...(cookieHeader ? { Cookie: cookieHeader } : {}),
75
+ },
76
+ signal: AbortSignal.timeout(30000),
77
+ });
78
+ const text = await res.text();
79
+ if (res.status !== 200 || isCloudflareChallenge(res.status, text)) {
80
+ if (isCloudflareChallenge(res.status, text)) {
81
+ throw Object.assign(new Error('AUTH_REQUIRED'), { status: 403 });
82
+ }
83
+ throw new Error(`HTTP ${res.status}: ${url}`);
84
+ }
85
+ return text;
86
+ }
87
+ function parseArchivePosts(html) {
88
+ const works = [];
89
+ const seen = new Set();
90
+ const chunks = html.split('<li class="site-archive-post').slice(1);
91
+ for (const rawChunk of chunks) {
92
+ const classEnd = rawChunk.indexOf('">');
93
+ const classAttr = classEnd === -1 ? '' : rawChunk.slice(0, classEnd);
94
+ const { rating, isAdult } = parseRatingFromClasses(classAttr);
95
+ const end = rawChunk.indexOf('</li>');
96
+ const chunk = end === -1 ? rawChunk : rawChunk.slice(0, end);
97
+ const titleMatch = chunk.match(/<h2 class="entry-title"><a href="([^"]+)">([\s\S]*?)<\/a><\/h2>/);
98
+ if (!titleMatch)
99
+ continue;
100
+ const postUrl = titleMatch[1];
101
+ const postIdMatch = postUrl.match(/\/(\d+)\/?/);
102
+ if (!postIdMatch)
103
+ continue;
104
+ const thumbMatch = chunk.match(/data-src="(https:\/\/pic\.weeabo0\.xyz\/[^"]+?)"/);
105
+ let rjCode = '';
106
+ if (thumbMatch) {
107
+ const rjFromThumb = thumbMatch[1].match(/RJ\d{5,}/i);
108
+ if (rjFromThumb)
109
+ rjCode = rjFromThumb[0].toUpperCase();
110
+ }
111
+ if (!rjCode) {
112
+ const rjFromText = chunk.match(/\[(RJ\d{5,})\]/i);
113
+ if (rjFromText)
114
+ rjCode = rjFromText[1].toUpperCase();
115
+ }
116
+ if (!rjCode)
117
+ continue;
118
+ const cvMatch = chunk.match(/CV:\s*([^<]+)</);
119
+ const metaMatch = chunk.match(/\[(\d{6})\]\[([^\]]+)\]/);
120
+ const work = {
121
+ rjCode,
122
+ postId: postIdMatch[1],
123
+ title: decodeEntities(titleMatch[2]),
124
+ thumbnail: thumbMatch ? thumbMatch[1] : '',
125
+ cv: cvMatch ? decodeEntities(cvMatch[1]) : undefined,
126
+ circle: metaMatch ? decodeEntities(metaMatch[2]) : undefined,
127
+ releaseDate: metaMatch ? metaMatch[1] : undefined,
128
+ rating,
129
+ isAdult,
130
+ };
131
+ if (seen.has(work.rjCode))
132
+ continue;
133
+ seen.add(work.rjCode);
134
+ works.push(work);
135
+ }
136
+ return works;
137
+ }
138
+ function hasNextPage(html, page) {
139
+ return html.includes(`/${page + 1}/"`) || html.includes(`/page/${page + 1}/`);
140
+ }
141
+ function toProxyImage(url) {
142
+ if (!url)
143
+ return '';
144
+ const store = request_context_1.requestContext.getStore();
145
+ const cookie = store?.get('jasmr_cookie');
146
+ const ua = store?.get('jasmr_ua');
147
+ let u = `/api/image-proxy?url=${encodeURIComponent(url)}&referer=${encodeURIComponent(`${BASE_URL}/`)}`;
148
+ if (cookie)
149
+ u += `&cookie=${encodeURIComponent(cookie)}`;
150
+ if (ua)
151
+ u += `&ua=${encodeURIComponent(ua)}`;
152
+ return u;
153
+ }
154
+ function toShow(work) {
155
+ return {
156
+ _id: work.rjCode,
157
+ id: work.rjCode,
158
+ name: work.title,
159
+ englishName: work.title,
160
+ nativeName: work.title,
161
+ thumbnail: toProxyImage(work.thumbnail),
162
+ description: [
163
+ work.circle ? `Circle: ${work.circle}` : '',
164
+ work.cv ? `CV: ${work.cv}` : '',
165
+ work.releaseDate ? `Released: ${work.releaseDate}` : '',
166
+ ]
167
+ .filter(Boolean)
168
+ .join('\n'),
169
+ type: 'TV',
170
+ rating: work.rating || (work.isAdult ? 'R-18' : ''),
171
+ isAdult: work.isAdult,
172
+ availableEpisodesDetail: { sub: ['1'], dub: [] },
173
+ availableEpisodes: { sub: 1 },
174
+ };
175
+ }
176
+ const ARCHIVE_PATHS = {
177
+ sfw: 'category/rating/sfw',
178
+ 'r-15': 'category/rating/r-15',
179
+ 'r-18': 'category/rating/maniax',
180
+ yuri: 'tag/yuri-girls-love',
181
+ otokonoko: 'category/otokonoko',
182
+ futanari: 'category/futanari',
183
+ 'r-18g': 'category/rating/extreme',
184
+ };
185
+ function buildArchiveUrl(query, page, sort, archiveKey) {
186
+ const params = new URLSearchParams();
187
+ if (query)
188
+ params.set('s', query);
189
+ switch (sort) {
190
+ case 'oldest':
191
+ params.set('orderby', 'date');
192
+ params.set('order', 'asc');
193
+ break;
194
+ case 'title_asc':
195
+ params.set('orderby', 'title');
196
+ params.set('order', 'asc');
197
+ break;
198
+ case 'title_desc':
199
+ params.set('orderby', 'title');
200
+ params.set('order', 'desc');
201
+ break;
202
+ case 'popular_recent':
203
+ params.set('orderby', 'post_views');
204
+ params.set('order', 'desc');
205
+ params.set('date', 'recent');
206
+ break;
207
+ case 'popular_week':
208
+ params.set('orderby', 'post_views');
209
+ params.set('order', 'desc');
210
+ params.set('date', 'week');
211
+ break;
212
+ case 'popular_month':
213
+ params.set('orderby', 'post_views');
214
+ params.set('order', 'desc');
215
+ params.set('date', 'month');
216
+ break;
217
+ case 'popular_6_months':
218
+ params.set('orderby', 'post_views');
219
+ params.set('order', 'desc');
220
+ params.set('date', '6_months');
221
+ break;
222
+ case 'popular_year':
223
+ params.set('orderby', 'post_views');
224
+ params.set('order', 'desc');
225
+ params.set('date', 'year');
226
+ break;
227
+ case 'popular':
228
+ params.set('orderby', 'post_views');
229
+ params.set('order', 'desc');
230
+ break;
231
+ case 'comments_week':
232
+ params.set('orderby', 'comment_count');
233
+ params.set('order', 'desc');
234
+ params.set('date', 'week');
235
+ break;
236
+ case 'comments_month':
237
+ params.set('orderby', 'comment_count');
238
+ params.set('order', 'desc');
239
+ params.set('date', 'month');
240
+ break;
241
+ case 'comments_year':
242
+ params.set('orderby', 'comment_count');
243
+ params.set('order', 'desc');
244
+ params.set('date', 'year');
245
+ break;
246
+ case 'comments':
247
+ params.set('orderby', 'comment_count');
248
+ params.set('order', 'desc');
249
+ break;
250
+ case 'random':
251
+ params.set('orderby', 'rand');
252
+ break;
253
+ default:
254
+ break;
255
+ }
256
+ const queryString = params.toString();
257
+ const archivePath = archiveKey ? ARCHIVE_PATHS[archiveKey] : undefined;
258
+ const basePath = archivePath ? `${BASE_URL}/${archivePath}` : BASE_URL;
259
+ const pageSuffix = page > 1 ? `/page/${page}/` : '/';
260
+ return queryString ? `${basePath}${pageSuffix}?${queryString}` : `${basePath}${pageSuffix}`;
261
+ }
262
+ function parseTags(html) {
263
+ const matches = Array.from(html.matchAll(/<a href="https:\/\/japaneseasmr\.com\/tag\/[^"]+"[^>]*>([^<]+)<\/a>/g)).map((m) => decodeEntities(m[1]));
264
+ return Array.from(new Set(matches)).slice(0, 15);
265
+ }
266
+ function parseChapters(html) {
267
+ const table = html.match(/id="plyr-chapter-playlist"[\s\S]*?<\/table>/)?.[0] ??
268
+ html.match(/id="basic-chapter-playlist"[\s\S]*?<\/table>/)?.[0] ??
269
+ '';
270
+ const chapters = [];
271
+ const seen = new Set();
272
+ const re = /data-value="(\d+)"[^>]*>[^<]*<\/a>\s*<\/td>\s*<td class="chapter_list chapter_title">\s*<a[^>]*?data-track-title="([^"]*)"/g;
273
+ for (const m of html.matchAll(re)) {
274
+ const time = parseInt(m[1], 10);
275
+ const label = decodeEntities(m[2]);
276
+ if (!Number.isFinite(time) || !label)
277
+ continue;
278
+ const key = `${time}|${label}`;
279
+ if (seen.has(key))
280
+ continue;
281
+ seen.add(key);
282
+ chapters.push({ time, label });
283
+ }
284
+ return chapters;
285
+ }
286
+ class JasmrProvider extends base_provider_1.BaseProvider {
287
+ name = 'JAsmr';
288
+ async fetchArchive(query, page, sort, archiveKey) {
289
+ const url = buildArchiveUrl(query, page, sort, archiveKey);
290
+ const html = await fetchText(url);
291
+ return { works: parseArchivePosts(html), hasNext: hasNextPage(html, page) };
292
+ }
293
+ async browse(options) {
294
+ try {
295
+ const query = (options.query || '').trim();
296
+ const page = options.page && options.page > 0 ? options.page : 1;
297
+ const sort = options.sort || undefined;
298
+ const archiveKey = options.rating && ARCHIVE_PATHS[options.rating] ? options.rating : undefined;
299
+ const noCache = sort === 'random';
300
+ const cacheKey = `jasmr_browse_${query || '__latest__'}_${page}_${sort || '__default__'}_${archiveKey || '__all__'}`;
301
+ if (!noCache) {
302
+ const cached = this.cache.get(cacheKey);
303
+ if (cached)
304
+ return cached;
305
+ }
306
+ const { works, hasNext } = await this.fetchArchive(query, page, sort, archiveKey);
307
+ const result = { shows: works.map(toShow), hasNext };
308
+ if (!noCache)
309
+ this.cache.set(cacheKey, result, 300);
310
+ return result;
311
+ }
312
+ catch (error) {
313
+ if (error.message === 'AUTH_REQUIRED')
314
+ throw error;
315
+ logger_1.default.error({ error }, '[JAsmr] Browse failed');
316
+ return { shows: [], hasNext: false };
317
+ }
318
+ }
319
+ async search(options) {
320
+ const { shows } = await this.browse(options);
321
+ return shows;
322
+ }
323
+ async getWorkMeta(rjCode) {
324
+ const cacheKey = `jasmr_meta_${rjCode}`;
325
+ const cached = this.cache.get(cacheKey);
326
+ if (cached)
327
+ return cached;
328
+ const html = await fetchText(`${BASE_URL}/?s=${encodeURIComponent(rjCode)}`);
329
+ const work = parseArchivePosts(html).find((w) => w.rjCode === rjCode) || null;
330
+ if (work)
331
+ this.cache.set(cacheKey, work, 3600);
332
+ return work;
333
+ }
334
+ async getPostHtml(rjCode) {
335
+ const meta = await this.getWorkMeta(rjCode);
336
+ if (!meta)
337
+ return null;
338
+ const cacheKey = `jasmr_post_${meta.postId}`;
339
+ const cached = this.cache.get(cacheKey);
340
+ if (cached)
341
+ return cached;
342
+ const html = await fetchText(`${BASE_URL}/${meta.postId}/`);
343
+ this.cache.set(cacheKey, html, 600);
344
+ return html;
345
+ }
346
+ async getEpisodes(showId) {
347
+ try {
348
+ const rjCode = showId.trim().toUpperCase();
349
+ if (!/^RJ\d{5,}$/.test(rjCode))
350
+ return null;
351
+ const cacheKey = `jasmr_eps_${rjCode}`;
352
+ const cached = this.cache.get(cacheKey);
353
+ if (cached)
354
+ return cached;
355
+ let description = '';
356
+ try {
357
+ const meta = await this.getWorkMeta(rjCode);
358
+ if (meta) {
359
+ description = [
360
+ meta.circle ? `Circle: ${meta.circle}` : '',
361
+ meta.cv ? `CV: ${meta.cv}` : '',
362
+ meta.releaseDate ? `Released: ${meta.releaseDate}` : '',
363
+ ]
364
+ .filter(Boolean)
365
+ .join('\n');
366
+ const postHtml = await this.getPostHtml(rjCode);
367
+ if (postHtml) {
368
+ const tags = parseTags(postHtml);
369
+ if (tags.length > 0) {
370
+ description += `\nTags: ${tags.join(', ')}`;
371
+ }
372
+ }
373
+ }
374
+ }
375
+ catch (err) {
376
+ logger_1.default.warn({ err, rjCode }, '[JAsmr] Work meta lookup failed');
377
+ }
378
+ const result = { episodes: ['1'], description };
379
+ this.cache.set(cacheKey, result, 1800);
380
+ return result;
381
+ }
382
+ catch (error) {
383
+ if (error.message === 'AUTH_REQUIRED')
384
+ throw error;
385
+ logger_1.default.error({ error, showId }, '[JAsmr] getEpisodes failed');
386
+ return null;
387
+ }
388
+ }
389
+ async getImages(rjCodeRaw) {
390
+ try {
391
+ const rjCode = rjCodeRaw.trim().toUpperCase();
392
+ if (!/^RJ\d{5,}$/.test(rjCode))
393
+ return [];
394
+ const cacheKey = `jasmr_images_${rjCode}`;
395
+ const cached = this.cache.get(cacheKey);
396
+ if (cached)
397
+ return cached;
398
+ const postHtml = await this.getPostHtml(rjCode);
399
+ if (!postHtml)
400
+ return [];
401
+ const matches = Array.from(postHtml.matchAll(/href="(https:\/\/img\.weeabo0\.xyz\/[^"]+?\.(?:jpg|jpeg|png|webp))"/g)).map((m) => m[1]);
402
+ const images = Array.from(new Set(matches))
403
+ .filter((url) => !url.includes('_img_main'))
404
+ .slice(0, 30)
405
+ .map(toProxyImage);
406
+ this.cache.set(cacheKey, images, 3600);
407
+ return images;
408
+ }
409
+ catch (error) {
410
+ if (error.message === 'AUTH_REQUIRED')
411
+ throw error;
412
+ logger_1.default.error({ error, rjCode: rjCodeRaw }, '[JAsmr] getImages failed');
413
+ return [];
414
+ }
415
+ }
416
+ async getChapters(rjCodeRaw) {
417
+ try {
418
+ const rjCode = rjCodeRaw.trim().toUpperCase();
419
+ if (!/^RJ\d{5,}$/.test(rjCode))
420
+ return [];
421
+ const cacheKey = `jasmr_chapters_${rjCode}`;
422
+ const cached = this.cache.get(cacheKey);
423
+ if (cached)
424
+ return cached;
425
+ const postHtml = await this.getPostHtml(rjCode);
426
+ if (!postHtml)
427
+ return [];
428
+ const chapters = parseChapters(postHtml);
429
+ this.cache.set(cacheKey, chapters, 3600);
430
+ return chapters;
431
+ }
432
+ catch (error) {
433
+ if (error.message === 'AUTH_REQUIRED')
434
+ throw error;
435
+ logger_1.default.error({ error, rjCode: rjCodeRaw }, '[JAsmr] getChapters failed');
436
+ return [];
437
+ }
438
+ }
439
+ async getStreamUrls(showId, episodeNumber, _mode) {
440
+ try {
441
+ const rjCode = showId.trim().toUpperCase();
442
+ if (!/^RJ\d{5,}$/.test(rjCode)) {
443
+ logger_1.default.warn({ showId }, '[JAsmr] Invalid RJ code');
444
+ return null;
445
+ }
446
+ const cacheKey = `jasmr_stream_${rjCode}_${episodeNumber}`;
447
+ const cached = this.cache.get(cacheKey);
448
+ if (cached)
449
+ return cached;
450
+ const meta = await this.getWorkMeta(rjCode);
451
+ if (!meta) {
452
+ logger_1.default.warn({ rjCode }, '[JAsmr] Could not resolve post page');
453
+ return null;
454
+ }
455
+ const html = (await this.getPostHtml(rjCode)) || '';
456
+ const store = request_context_1.requestContext.getStore();
457
+ const jasmrCookie = store?.get('jasmr_cookie');
458
+ const jasmrUa = store?.get('jasmr_ua');
459
+ const toProxy = (rawUrl) => {
460
+ let u = `/api/proxy?url=${encodeURIComponent(rawUrl)}&referer=${encodeURIComponent(`${BASE_URL}/`)}`;
461
+ if (jasmrCookie)
462
+ u += `&cookie=${encodeURIComponent(jasmrCookie)}`;
463
+ if (jasmrUa)
464
+ u += `&ua=${encodeURIComponent(jasmrUa)}`;
465
+ return u;
466
+ };
467
+ const links = [];
468
+ const m3u8Match = html.match(/<source src="(https:\/\/v\.weeab0o\.xyz\/[^"]+\.m3u8)"/);
469
+ const audioSrcMatch = html.match(/audioSrc\s*=\s*'(https:\/\/[^']+\.m3u8)'/);
470
+ const streamUrl = m3u8Match?.[1] || audioSrcMatch?.[1];
471
+ if (streamUrl) {
472
+ links.push({ resolutionStr: 'Audio', link: toProxy(streamUrl), hls: true });
473
+ }
474
+ else {
475
+ const mp3Matches = Array.from(html.matchAll(/<source src="(https:\/\/v\.weeab0o\.xyz\/[^"]+\.mp3)"/g)).map((m) => m[1]);
476
+ const uniqueMp3s = Array.from(new Set(mp3Matches));
477
+ if (uniqueMp3s.length === 0) {
478
+ logger_1.default.warn({ rjCode, postId: meta.postId }, '[JAsmr] No audio sources found on post page');
479
+ return null;
480
+ }
481
+ uniqueMp3s.forEach((mp3Url, i) => {
482
+ links.push({ resolutionStr: `Track ${i + 1}`, link: toProxy(mp3Url), hls: false });
483
+ });
484
+ }
485
+ const result = [
486
+ {
487
+ sourceName: this.name,
488
+ links,
489
+ type: 'player',
490
+ actualEpisodeNumber: episodeNumber || '1',
491
+ },
492
+ ];
493
+ this.cache.set(cacheKey, result, 3600);
494
+ return result;
495
+ }
496
+ catch (error) {
497
+ if (error.message === 'AUTH_REQUIRED')
498
+ throw error;
499
+ logger_1.default.error({ error, showId, episodeNumber }, '[JAsmr] getStreamUrls failed');
500
+ return null;
501
+ }
502
+ }
503
+ }
504
+ exports.JasmrProvider = JasmrProvider;